Files
api_user/models/schema/user.js
T
2026-09-01 21:49:41 +08:00

265 lines
8.0 KiB
JavaScript

"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, 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: '失败登录尝试次数' },
lastLoginAt: { type: Date, comment: '最后登录时间' },
lastLoginIp: { type: String, comment: '最后登录IP' },
passwordResetToken: { type: String, sparse: true, comment: '密码重置令牌' },
passwordResetExpiry: { 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:{
unionid: { type: String, index: true, unique: true, sparse: true, comment: '微信UnionID' },
}
},
// 状态信息 - 用户账户的状态标记
status: {
account: { type: String, enum: ["normal", "lock"], default: "normal", comment: '账户状态' },
},
// 带wxopenid
app: {
type: mongoose.Schema.Types.Mixed,
default: {}
},
// 元数据 - 系统管理信息
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字段
}
);
/**
* 设置/更新用户信息
*
* @param {Object} _user - 要设置或更新的用户对象
* @param {Function} cb - 可选的回调函数
* @returns {Promise<Object|null>} 操作结果
*/
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<Object|null>} 更新后的用户对象或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<Object|null>} 操作结果
*/
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<Object|null>} 操作结果
*/
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<Object|null>} 操作结果
*/
UserSchema.statics.resetFailedLoginAttempts = async function (_id) {
try {
return await this.findOneAndUpdate(
{ _id },
{
'security.failedLoginAttempts': 0,
'meta.updatetime': Date.now()
},
{ new: true }
);
} catch (error) {
return null;
}
};
// 索引设置 - 优化查询性能
// 手机号和密码复合索引,用于登录验证
UserSchema.index({ "profile.mobile": 1, "security.passwd": 1 });
export { UserSchema };