You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Joi验证sharesSold(卖出股数)不超过sharesBought(买入股数)?

问题:限制Sell的sharesSold不超过对应Buy的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 17:55:17