"use strict"; import mongoose from "mongoose"; /** * 用户Schema定义 * * 包含6个主要分类块:基本信息、安全信息、社交信息、状态信息、中医相关信息和元数据。 * 支持多种登录方式和中医系统中的角色权限管理。 */ const UserSchema = mongoose.Schema( { // 基本信息 - 用户的个人标识信息 profile: { name: { type: String, comment: '用户姓名' }, pinyin: { type: String, default: '', comment: '姓名的拼音,用于搜索' }, pinyinFL: { type: String, default: '', comment: '姓名拼音的首字母,用于搜索' }, mobile: { type: String, index: true, trim: true, unique: true, sparse: true, comment: '手机号码' }, email: { type: String, index: true, trim: true, unique: true, sparse: true, comment: '电子邮箱' }, idnumber: { type: String, index: true, trim: true, unique: true, sparse: true, comment: '身份证号码' }, ssn: { type: String, index: true, trim: true, unique: true, sparse: true, comment: '社保卡号' }, sex: { type: String, enum: ["male", "female"], default: "male", comment: '性别' }, birth: { type: Date, default: 0, comment: '出生日期' }, }, // 安全信息 - 用户的登录凭证和安全相关数据 security: { passwd: { type: String, comment: '密码(加密存储)' }, passwdSalt: { type: String, comment: '密码盐值' }, token: { type: String, index: true, sparse: true, comment: '认证令牌' }, tokenExpiry: { type: Date, comment: '令牌过期时间' }, failedLoginAttempts: { type: Number, default: 0, comment: '失败登录尝试次数' }, lockedUntil: { type: Date, comment: '账户锁定时间' }, }, // 位置信息 - 用户的省份、城市和区县 location: { province: { type: String, default: '', comment: '省份' }, city: { type: String, default: '', comment: '城市' }, district: { type: String, default: '', comment: '区县' }, }, // 地址信息 - 用户多个地址(用于邮寄等) addresses: [ { label: { type: String, default: '', comment: '地址标签,如"家庭"、"公司"' }, province: { type: String, default: '', comment: '省份' }, city: { type: String, default: '', comment: '城市' }, district: { type: String, default: '', comment: '区县' }, address: { type: String, default: '', comment: '详细地址' }, postcode: { type: String, default: '', comment: '邮政编码' }, isDefault: { type: Boolean, default: false, comment: '是否默认地址' }, }, ], // 社交信息 - 用户的社交账号关联 social: { wechat:{ account: { type: String, default: "", comment: '微信账号' }, unionid: { type: String, index: true, unique: true, sparse: true, comment: '微信UnionID' }, openid: { type: String, index: true, unique: true, sparse: true, comment: '微信OpenID' }, } }, // 状态信息 - 用户账户的状态标记 status: { account: { type: String, enum: ["normal", "lock"], default: "normal", comment: '账户状态' }, }, app: { }, // 元数据 - 系统管理信息 meta: { createtime: { type: Date, default: Date.now, comment: '创建时间' }, updatetime: { type: Date, default: Date.now, comment: '更新时间' }, }, }, { minimize: false, // 允许保存空对象 strict: false, // 允许添加Schema中未定义的字段 collection: "user", // MongoDB集合名称 timestamps: false, // 不自动添加createdAt和updatedAt字段 } ); /** * 根据用户标识信息查找用户 * * 支持通过手机号、邮箱、token或微信unionid查找用户 * * @param {Object} _user - 包含查找条件的用户对象 * @param {Function} cb - 可选的回调函数 * @returns {Promise} 找到的用户对象或null */ UserSchema.statics.findUser = async function (_user, cb) { let filter = {}; if (_user.profile && _user.profile.mobile) { filter = { "profile.mobile": _user.profile.mobile }; } else if (_user.profile && _user.profile.email) { filter = { "profile.email": new RegExp(_user.profile.email, "i") }; } else if (_user.security && _user.security.token) { filter = { "security.token": new RegExp(_user.security.token, "i") }; } else { return null; } return await this.findOne(filter, cb); }; /** * 设置/更新用户信息 * * @param {Object} _user - 要设置或更新的用户对象 * @param {Function} cb - 可选的回调函数 * @returns {Promise} 操作结果 */ UserSchema.statics.setUser = async function (_user, cb) { const filter = {}; let update = {}; // 如果有_id字段,说明是更新操作 if (_user._id) { filter._id = _user._id; // 移除_id字段,避免尝试更新它 delete _user._id; update = _user; update.meta.updatetime = Date.now(); } else { // 新建用户 update = _user; update.meta = { createtime: Date.now(), updatetime: Date.now() }; } try { if (filter._id) { // 更新用户 return await this.findOneAndUpdate(filter, { $set: update }, { new: true, upsert: true }, cb); } else { // 创建新用户 const newUser = new this(update); return await newUser.save(cb); } } catch (error) { return null; } }; /** * 根据用户信息更新用户文档 * * @param {ObjectId} userId - 用户ID * @param {Object} userInfo - 包含要更新字段的用户信息对象 * @returns {Promise} 更新后的用户对象或null */ UserSchema.statics.updateFromUserInfo = async function (userId, userInfo) { const updateData = {}; updateData['meta.updatetime'] = Date.now(); if (userInfo.profile) { if (userInfo.profile.name !== undefined) { updateData['profile.name'] = userInfo.profile.name; } if (userInfo.profile.sex !== undefined) { updateData['profile.sex'] = userInfo.profile.sex; } if (userInfo.profile.birth !== undefined) { updateData['profile.birth'] = userInfo.profile.birth ? new Date(userInfo.profile.birth) : null; } if (userInfo.profile.mobile !== undefined) { updateData['profile.mobile'] = userInfo.profile.mobile; } } if (userInfo.location) { if (userInfo.location.province !== undefined) { updateData['location.province'] = userInfo.location.province; } if (userInfo.location.city !== undefined) { updateData['location.city'] = userInfo.location.city; } if (userInfo.location.district !== undefined) { updateData['location.district'] = userInfo.location.district; } } if (userInfo.addresses) { updateData['addresses'] = userInfo.addresses; } try { return await this.findOneAndUpdate( { _id: userId }, { $set: updateData }, { new: true, upsert: false } ); } catch (error) { return null; } }; /** * 删除用户 * * @param {ObjectId} _id - 用户ID * @param {string} mobile - 手机号码 * @param {string} email - 电子邮箱 * @param {string} wxunionid - 微信unionid * @param {Function} cb - 可选的回调函数 * @returns {Promise} 操作结果 */ UserSchema.statics.delUser = async function (_id, mobile, email, wxunionid, cb) { const filter = {}; if (_id) { filter._id = _id; } else if (mobile) { filter['profile.mobile'] = mobile; } else if (email) { filter['profile.email'] = email; } else if (wxunionid) { filter['social.wechat.unionid'] = wxunionid; } else { return null; } try { return await this.findOneAndDelete(filter, cb); } catch (error) { return null; } }; /** * 增加失败登录尝试次数 * * @param {ObjectId} _id - 用户ID * @returns {Promise} 操作结果 */ UserSchema.statics.incrementFailedLoginAttempts = async function (_id) { try { return await this.findOneAndUpdate( { _id }, { $inc: { 'security.failedLoginAttempts': 1 }, 'meta.updatetime': Date.now() }, { new: true } ); } catch (error) { return null; } }; /** * 重置失败登录尝试次数 * * @param {ObjectId} _id - 用户ID * @returns {Promise} 操作结果 */ UserSchema.statics.resetFailedLoginAttempts = async function (_id) { try { return await this.findOneAndUpdate( { _id }, { 'security.failedLoginAttempts': 0, 'security.lockedUntil': null, 'meta.updatetime': Date.now() }, { new: true } ); } catch (error) { return null; } }; // 索引设置 - 优化查询性能 // 手机号和密码复合索引,用于登录验证 UserSchema.index({ "profile.mobile": 1, "security.passwd": 1 }); export { UserSchema };