如何用Joi验证sharesSold(卖出股数)不超过sharesBought(买入股数)?
我为交易应用编写了两个Mongoose Schema:
buySchema:
const buySchema = new Schema({ parentId: String, buyingPrice: { type: Number, min: [0, 'Price can\'t be negative'] }, sharesBought: { type: Number, min: [0, 'No. of Shares can\'t be negative'] }, day: { type: Date, default: Date.now }, sells: [{ type: Schema.Types.ObjectId, ref: 'Sell' }], })
sellSchema:
const sellSchema = new Schema({ sellingPrice:Number, sharesSold:Number, day: { type: Date, default: Date.now } })
需要确保sellSchema中的sharesSold字段值永远不大于对应buySchema中的sharesBought字段值,现有的Joi校验规则卡在了max值的设置上:
module.exports.sellSchema = Joi.object({ sell: Joi.object({ sellingPrice: Joi.number().min(0).required(), sharesSold: Joi.number().min(0).max(***'How to handle this part'***).required(), day: Joi.date().required() }).required() })
解决方案
1. 先获取对应Buy记录的可售份额
Joi的静态Schema无法直接访问数据库,需要先在请求处理逻辑中,根据关联的Buy ID(比如从请求参数buyId获取)查询Buy模型,同时计算该Buy记录下已卖出的总份额,得到剩余可售份额:
const Buy = mongoose.model('Buy', buySchema); const Sell = mongoose.model('Sell', sellSchema); // 创建Sell的路由处理函数 app.post('/buys/:buyId/sells', async (req, res) => { const buyId = req.params.buyId; const buyRecord = await Buy.findById(buyId).populate('sells'); // 计算已卖出的总份额 const totalSold = buyRecord.sells.reduce((sum, sell) => sum + sell.sharesSold, 0); // 剩余可售份额 = 总买入份额 - 已卖出份额 const availableShares = buyRecord.sharesBought - totalSold; // 后续用availableShares做Joi校验 });
2. 动态设置Joi的max值
把计算出的availableShares传入Joi校验,直接作为max的参数:
// 调整Joi Schema为可接收外部参数的函数 module.exports.validateSell = (availableShares) => { return Joi.object({ sell: Joi.object({ sellingPrice: Joi.number().min(0).required(), sharesSold: Joi.number().min(0).max(availableShares).required() .messages({ 'number.max': `Shares sold can't exceed ${availableShares}` }), day: Joi.date().required() }).required() }); }; // 在路由中使用校验 const { error } = validateSell(availableShares).validate(req.body); if (error) { return res.status(400).send(error.details[0].message); }
3. Joi自定义异步校验(可选)
如果希望把查询逻辑整合到Joi中,可以使用Joi.custom()编写异步校验函数:
module.exports.sellSchema = Joi.object({ sell: Joi.object({ sellingPrice: Joi.number().min(0).required(), sharesSold: Joi.number().min(0).required() .custom(async (value, helpers) => { // 从校验上下文获取buyId const buyId = helpers.state.ctx.buyId; const buyRecord = await Buy.findById(buyId).populate('sells'); const totalSold = buyRecord.sells.reduce((sum, sell) => sum + sell.sharesSold, 0); const availableShares = buyRecord.sharesBought - totalSold; if (value > availableShares) { return helpers.error('any.invalid', { message: `Shares sold can't exceed ${availableShares}` }); } return value; }), day: Joi.date().required() }).required() }); // 校验时传入上下文 const { error } = sellSchema.validate(req.body, { context: { buyId: req.params.buyId } });
4. Mongoose中间件补充校验(数据库层面保障)
为避免绕过Joi校验的场景(比如直接操作数据库),在Sell模型中添加pre('save')中间件:
// 先给sellSchema添加关联Buy的字段 const sellSchema = new Schema({ buyId: { type: Schema.Types.ObjectId, ref: 'Buy', required: true }, sellingPrice:Number, sharesSold:Number, day: { type: Date, default: Date.now } }) // 添加前置保存校验 sellSchema.pre('save', async function(next) { const buyRecord = await Buy.findById(this.buyId).populate('sells'); const totalSold = buyRecord.sells.reduce((sum, sell) => { // 更新操作时排除当前记录 if (sell._id.toString() !== this._id.toString()) { return sum + sell.sharesSold; } return sum; }, 0); const availableShares = buyRecord.sharesBought - totalSold; if (this.sharesSold > availableShares) { throw new Error(`Shares sold can't exceed ${availableShares}`); } next(); });
内容的提问来源于stack exchange,提问作者Jack King
相关产品推荐
相关产品推荐

