在我的应用程序中,我的用户模型如下。当我确认确认密码时,猫鼬模型中存在一些业务逻辑部分。我该如何更好地表达呢?我应该将验证部分与模型分开吗?还是应该将验证保留在猫鼬模型中?
import Joi from 'joi';
import { Schema, model } from 'mongoose';
const userSchema = new Schema(
{
firstName: {
type: String,
required: true,
minlength: 2,
maxlength: 30,
},
lastName: {
type: String,
required: true,
minlength: 2,
maxlength: 30,
},
email: {
type: String,
required: true,
minlength: 5,
maxlength: 100,
unique: true,
lowercase: true
},
password: {
type: String,
required: true,
minlength: 8,
maxlength: 1024,
},
avatar: {
type: String
},
},
);
export const User = model('User', userSchema);
/** Validate user */
export function validateUser(user) {
const schema = {
firstName: Joi.string().min(2).max(30).required(),
lastName: Joi.string().min(2).max(30).required(),
email: Joi.string().min(5).max(100).required().email(),
password: Joi.string().min(8).max(1024).required(),
confirmPassword: Joi.any().valid(Joi.ref('password')).required().options({ language: { any: { allowOnly: 'Passwords do not match' } } }),
avatar: Joi.string(),
};
return Joi.validate(user, schema, { abortEarly: false });
}
/** Validate login */
export function validateLogin(login) {
const schema = {
email: Joi.string().min(5).max(255).required().email(),
password: Joi.string().min(5).max(255).required()
};
return Joi.validate(login, schema);
}
答案 0 :(得分:3)
也许您应该尝试询问CodeReview,因为这更多的是设计而非问题。
无论如何,这是我的观点(因为它是设计的,所以主要是观点问题)。
我不会使用猫鼬来验证与对象相关的约束。
在您的示例中,Mongoose仅声明强制性type
和unique
,因为它要求数据库对该属性建立索引(扩展名为index
或sparse
为好)。
这仅仅是因为Joi的目的是验证对象,使其比Mongoose的功能明显更好,更易于处理。
在您的示例中,Monoose无需({)双重检查minlength
和maxlength
,因为Joi已经做到了-只要您正确控制数据库的入口点即可。 lowercase
也可以是handled by Joi。这些对Mongo存储数据的方式没有影响,就像password
/ confirmPassword
相等性检查一样,这是纯粹的事情。
(作为补充,您也可以.strip()
这个confirmPassword
属性,以避免在请求处理程序中执行此操作)
我不会公开验证实体的函数,而是公开POST和PUT模式。在大多数情况下,您可以从POST自动获取PUT(毕竟只是添加一个ID),但是带有密码确认的用户是需要区分的典型示例。
这样,您几乎可以在此处大致构建经典的REST API:使用文件/模块获取路由的名称,根据所使用的HTTP方法尝试对其进行公开验证,然后使用其公开模型进行持久性工作。然后,您也可以考虑使授权具有通用性,如果您的数据模型在会话中向用户明确表达,则可以使实体具有完全通用的API,但这是另一回事。
将Mongoose和Joi模式保存在同一文件中还可以使开发人员(而且您在6个月内忘记了编码方式时)可以快速了解必须如何使用特定实体。这意味着,当某人开发将数据插入数据库的CLI脚本时,他们将拥有在附近使用的验证模式,如果他们“忘记”使用它,则可以责怪他们:再次,控制数据库的入口点。 ;)
我希望实体文件保持实体文件的状态。
您提供了一种验证登录的方法,但是您知道无法插入不符合Joi和Mongoose模式的内容。因此,如果有人想使用1500个字符长的电子邮件登录,请让他们:由于数据库中没有任何匹配项,因此最终将是通常被拒绝的登录。但这并不意味着您不能实现UI验证,它还意味着在架构更改时都应调整UI代码。
但是,以更通用的方式登录确实不是正常的,可能是通用的端点。这些是您可能需要特殊验证步骤的唯一情况(AFAIK)。由于这些路线完全与业务相关,因此您需要手工制作它们,这就是我要进行这些特殊的(也与业务相关的)验证步骤的地方。无论如何,不是在“最紧密相关”实体中。
关于我在用户实体文件中的内容:
import Joi from 'joi';
import { Schema, model } from 'mongoose';
// purely DB stuff
export default model('User', new Schema(
{
firstName: { type: String },
lastName: { type: String },
email: { type: String, unique: true },
password: { type: String },
avatar: { type: String }
}
));
// purely business stuff
// common schema first
const schema = {
firstName: Joi.string().min(2).max(30).required(),
lastName: Joi.string().min(2).max(30).required(),
email: Joi.string().min(5).max(100).required().lowercase().email(),
password: Joi.string().min(8).max(1024),
avatar: Joi.string(),
};
// POST-specific case
const post = {
...schema,
// note that for this particular case, a UI check is enough
confirmPassword: Joi.string().valid(Joi.ref('password')).required()
.options({ language: { any: { allowOnly: 'Passwords do not match' } } })
.strip()
};
// password is only required on registration
post.password = post.password.required();
// PUT-specific case
const put = {
...schema,
id: Joi.string().required()
};
// you may also want to use a derived Joi schema on GET output to strip some data like password
export const joi = { post, put };