如何通过用户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
相关产品推荐
相关产品推荐

