介绍
在本教程中,您将学习如何充分利用Mongoose模式混合类型,Getters和setter,以操纵MongoDB数据库中的数据
大多数产品评级系统(例如亚马逊的评级系统)都利用了加权平均系统。加权平均值的计算方式几乎与正常的简单平均值相同,除非总和中的每个单元都有一个称为权重的乘数。例如,在五星级评级系统中。一颗恒星的重量可能为1,两颗星的重量为2,依此类推。
例子。
假设产品具有以下明星评论。
一颗恒星 - 8
两颗恒星 - 10
三星星7
四星星5
五星级3
简单的平均值将是
const SA = (8+10+7+5+3) / 5
console.log(Math.round(SA)) // 2
加权平均系统给出了更好的结果,因为它对消费者对此产品的看法给人留下了深刻的印象。
您如何实施?
在后端实现此目标意味着您需要一个地方来存储每个产品的评分,还需要每个启动都会收到的数字。解决这个问题有不同的方法。就个人而言,我更喜欢将杂种架构混合类型与播放器和getters的组合。
要求
对于这个项目,只需作为我们的MongoDB驱动程序和当地的Mongo Shell,并且可以选择可视化。
首先,让S创建一个简单的节点项目并安装猫鼬。
npm install -s mongoose
创建一个文件模型。
const mongoose = require('mongoose');
const connectDB = async () => {
try{
const conn = await mongoose.connect('mongodb://localhost:27017/myapp', {useNewUrlParser: true, useFindAndModify: false})
console.log(`MongoDb connected: ${conn.connection.host}`)
return conn
}
catch (e) {
console.error(e);
process.exit(1);
}
};
接下来,让我们为产品集合创建一个基本的杂种模式。它将只有两个路径命名一个字符串路径来保存产品名称并评估混合路径对象以存储产品的评分。
const ProductSchema = new mongoose.Schema({
name: String,
ratings:{
type: mongoose.Mixed,
// A mixed type object to handle ratings. Each star level is represented in the ratings object
1: Number, // the key is the weight of that star level
2: Number,
3: Number,
4: Number,
5: Number,
default: {1:1, 2:1, 3:1, 4:1, 5:1}}
})
由于我们使用的是Getters,请确保在架构对象中包括以下选项,以便在获得JSON或JAVASCRIPT对象时可以使用getters。
{toObject:{getters: true, }, toJSON:{getters: true}}
getter函数
接下来,我们将编写一个getter函数,以从等级路径获得产品的额定值。当我们在路径上调用getter函数时,它会接受代表路径的单个参数。在这种情况下,它是一个简单的JavaScript对象,其中包含我们的星评论。这简化了getter函数。如下所示。
设置器函数
此功能将处理值设置为等级路径。它将使我们拥有一个简单的界面来更新产品的评分。假设用户给产品的评分为三个,或者我们想用新对象替换整个评级对象。我们希望能够通过这样的简单单线更新产品的评分。
product.ratings = 3
product.ratings = {1:1, 2:1, 3:1, 4:1, 5:1}
Product.findByIdAndUpdate(id, {$inc:{'ratings.3': 1}})
Product.findByIdAndUpdate('61084b72b346c52e8482ed3b', {ratings: {1:3, 2:1, 3:1, 4:1, 5:1}}, {new: true})
前两个摘要将更新3星评级的数量。
set: function(r){
if (!(this instanceof mongoose.Document)){
// only call setter when updating the whole path with an object
if(r instanceof Object) return r
else{throw new Error('')}
}else{
// get the actual ratings object without using the getter which returns an integer value
// r is the ratings which is an integer value that represent the star level from 1 to 5
if(r instanceof Object){
return r // handle setting default when creating object
}
this.get('ratings', null, {getters: false})[r] = 1 + parseInt(this.get('ratings', null, {getters: false})[r])
return this.get('ratings', null, {getters: false})} // return the updated ratings object
},
设置器函数有点复杂,因为我们需要在不使用返回整数值的getter的情况下获得该文档的评分对象。对设置器函数的参数是我们要通过一个键,或一个javaScript对象更新的星级,或者正如架构中指定的那样,将恒星级别作为键。在运行更新操作时以及直接分配到路径时,Mongoose Setter都会被调用。不同之处在于,在运行更新操作时 - 函数中的这是指查询对象,而不是蒙古文档的实例,我们正在更新此操作,因此很难为子路径执行我们的自定义更新操作,因为我们正在更新特定的子路,而不是整个路径。因此,如果有人尝试使用任何Mongoose模型更新方法更新整个路径,我们将引发错误。
setter函数中的剩余逻辑很简单,我们只需使用getters即可获取产品评分对象,然后将参数使用到setter函数来更新特定路径,然后我们将返回更新的额定值对象。
验证
添加一个验证器以防止一个以上的子路径的值增加一个以上,但是在使用增量操作员时,猫鼬验证器不会运行将数据传递到数据库之前,将由我们验证。我们的验证器功能只会阻止在我们的架构中指定的外部添加额外的星级。
validate:{
validator: function(i){
let b = [1, 2, 3, 4, 5] // valid star levels
let v = Object.keys(i).sort()
return b.every((x, j) => (v.length === b.length) && x === parseInt(v[j]))
},
message: "Invalid Star Level"
},
测试
让我们创建一个产品并在没有getters的情况下将其记录到它的外观。这是我们的代码的样子。然后,我们可以复制产品ID来测试我们的更新方法和验证器。
const create = async () => {
let prod = await product.create({name: "Product One"})
// display the newly created object with and without getters
console.log(prod)
console.log(prod.get( 'ratings', null, {getters: false}))
}
create()
// result without the getter
{
ratings: 3,
_id: 61069af7547cd8335409a926,
name: 'Product One',
__v: 0,
id: '61069af7547cd8335409a926'
}
// the ratings object
{ '1': 1, '2': 1, '3': 1, '4': 1, '5': 1 }
看过我们的工作中的人。我们将尝试以几种不同的方式更新产品的评分来测试我们的播放器。
让我们编写一个代码,以给我们的产品一个五星级的评级。如您所见,通过简单地将值分配给评级对象,我们已经更新了1到2的五星级评论的数量。平均评分仍然是三个,因为我们使用的加权平均计算在获得评分中。
const test1 = async () => {
// increment a particular star level.
// by assigning directly to the ratings object
let prod = await product.findById('61084b72b346c52e8482ed3b')
prod.ratings = 5
prod.markModified('ratings') // Add markModified because ratings is a mixed object type
prod.save()
console.log(prod.get( 'ratings', null, {getters: false}))
console.log(prod)
}
test1()
{ '1': 1, '2': 1, '3': 1, '4': 1, '5': 2 }
{
ratings: 3,
_id: 61084b72b346c52e8482ed3b,
name: 'Product One',
__v: 0,
id: '61084b72b346c52e8482ed3b'
}
结论
一如既往地在计算机科学中,有很多方法可以解决一个比其他问题更直观的问题。但是,找到一种适应性,可维护且涉及最少代码的解决方案总是可取的。完整的代码可以在https://github.com/Ichinga-Samuel/Rating-System.git
上找到