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

如何通过用户ID让MongoDB自动填充交易集合的收发方子字段

问题描述

需要实现仅传入收发方用户ID,让MongoDB自动填充transactions集合中sender和receiver的_id、firstName、lastName字段。当前Mongoose Schema定义如下:

const userSchema = new mongoose.Schema({
    username: {
        type: String,
        required: true,
        unique: true,
        trim: true,
        lowercase: true,
        minLength: 3,
        maxLength: 30
    },
    password: {
        type: String,
        required: true,
        minLength: 6
    },
    firstName: {
        type: String,
        required: true,
        trim: true,
        maxLength: 50
    },
    lastName: {
        type: String,
        required: true,
        trim: true,
        maxLength: 50
    }
});

const accountSchema = new mongoose.Schema({
    user: {
        type: mongoose.Schema.Types.ObjectId, 
        ref: "User"
    },
    balance: {
        type: Number,
        minLength: 0,
        required: true
    }
})

const transactionSchema = new mongoose.Schema({
    sender: {
        _id: {
            type: mongoose.Schema.Types.ObjectId,
            ref: "User",
            required: true
        },
        firstName: {
            type: String,
            required: true
        },
        lastName: {
            type: String,
            required: true
        }
    },
    receiver: {
        _id: {
            type: mongoose.Schema.Types.ObjectId,
            ref: "User",
            required: true
        },
        firstName: {
            type: String,
            required: true
        },
        lastName: {
            type: String,
            required: true
        }
    },
    amount: {
        type: Number,
        required: true
    }
});

原本期望通过sender和receiver的_id字段的ref:"User"让Mongoose自动填充姓名,但当前Schema无法支持以下转账逻辑:

accountRouter.post("/transfer", authMiddleware, async (req, res) => {
    const session = await mongoose.startSession();

    session.startTransaction();
    const { amount, to } = req.body;
    
    //...无关逻辑省略
    //...

    // 执行转账
    await AccountModel.updateOne({ user: req.body.userId }, { $inc: { balance: -amount } }).session(session);
    await AccountModel.updateOne({ user: to }, { $inc: { balance: amount } }).session(session);
    await TransactionModel.create({ sender: {_id: req.body.userId}, receiver: {_id: to}, amount: amount });

    // 提交事务
    await session.commitTransaction();
    res.json({
        message: "Transfer successful"
    });
});

代码会抛出firstName和lastName必填的错误,移除必填属性后交易可创建但无姓名信息。需求是创建交易记录时仅传用户ID,同时保存ID和姓名,避免后续查询交易时额外调用数据库获取用户信息。

解决方案

方法1:使用Mongoose pre-save中间件自动填充

修改transactionSchema,添加预保存钩子,在交易保存前自动查询用户信息并填充姓名字段:

const transactionSchema = new mongoose.Schema({
    sender: {
        _id: {
            type: mongoose.Schema.Types.ObjectId,
            ref: "User",
            required: true
        },
        firstName: {
            type: String,
            required: true
        },
        lastName: {
            type: String,
            required: true
        }
    },
    receiver: {
        _id: {
            type: mongoose.Schema.Types.ObjectId,
            ref: "User",
            required: true
        },
        firstName: {
            type: String,
            required: true
        },
        lastName: {
            type: String,
            required: true
        }
    },
    amount: {
        type: Number,
        required: true
    }
});

// 预保存中间件:填充收发方姓名
transactionSchema.pre('save', async function(next) {
    const UserModel = mongoose.model('User');
    // 查询发送方信息并填充
    const senderUser = await UserModel.findById(this.sender._id);
    if (senderUser) {
        this.sender.firstName = senderUser.firstName;
        this.sender.lastName = senderUser.lastName;
    }
    // 查询接收方信息并填充
    const receiverUser = await UserModel.findById(this.receiver._id);
    if (receiverUser) {
        this.receiver.firstName = receiverUser.firstName;
        this.receiver.lastName = receiverUser.lastName;
    }
    next();
});

同时修改转账逻辑中的交易创建代码,确保中间件查询在事务内执行:

// 替换原有的TransactionModel.create
const transaction = new TransactionModel({ 
    sender: {_id: req.body.userId}, 
    receiver: {_id: to}, 
    amount: amount 
});
await transaction.save({ session }); // 传入session保证事务一致性

方法2:主动查询用户信息后创建交易

在创建交易前,先查询收发方的用户信息,再完整传入交易创建参数:

accountRouter.post("/transfer", authMiddleware, async (req, res) => {
    const session = await mongoose.startSession();

    session.startTransaction();
    const { amount, to } = req.body;
    const senderId = req.body.userId;
    
    // 同时查询收发方用户信息,加入事务保证一致性
    const [senderUser, receiverUser] = await Promise.all([
        UserModel.findById(senderId).session(session),
        UserModel.findById(to).session(session)
    ]);

    if (!senderUser || !receiverUser) {
        await session.abortTransaction();
        return res.status(404).json({ message: "用户不存在" });
    }
    
    // 执行余额更新
    await AccountModel.updateOne({ user: senderId }, { $inc: { balance: -amount } }).session(session);
    await AccountModel.updateOne({ user: to }, { $inc: { balance: amount } }).session(session);
    
    // 创建完整的交易记录
    await TransactionModel.create([{
        sender: {
            _id: senderId,
            firstName: senderUser.firstName,
            lastName: senderUser.lastName
        },
        receiver: {
            _id: to,
            firstName: receiverUser.firstName,
            lastName: receiverUser.lastName
        },
        amount: amount
    }], { session });

    await session.commitTransaction();
    res.json({ message: "Transfer successful" });
});

这种方式逻辑直观,能直接确保事务内数据的一致性,避免中间件异步操作可能带来的问题。

方法3:调整Schema结构(可选,不符合需求但作参考)

如果允许后续查询时关联用户表,可以简化Schema,用populate获取用户信息,但这会增加后续查询的数据库调用,不符合当前需求:

const transactionSchema = new mongoose.Schema({
    sender: {
        type: mongoose.Schema.Types.ObjectId,
        ref: "User",
        required: true
    },
    receiver: {
        type: mongoose.Schema.Types.ObjectId,
        ref: "User",
        required: true
    },
    amount: {
        type: Number,
        required: true
    }
});

// 查询时关联用户表获取姓名
const transactions = await TransactionModel.find().populate('sender receiver', 'firstName lastName');

内容的提问来源于stack exchange,提问作者user24956669

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 15:45:10