Files
api_user/models/schema/user.js
2026-05-25 12:02:28 +08:00

293 lines
9.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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: {
admin: {
role: [{ type: String, enum: ["admin"], comment: '用户在admin应用中的权限' }],
},
// 中医相关信息 - 用户在中医系统中的角色和权限
attendant: {
role: [{ type: String, enum: ["admin", "attendant", "patient"], comment: '用户在attendant应用中的权限' }],
},
},
// 元数据 - 系统管理信息
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<Object|null>} 找到的用户对象或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<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,
'security.lockedUntil': null,
'meta.updatetime': Date.now()
},
{ new: true }
);
} catch (error) {
return null;
}
};
// 索引设置 - 优化查询性能
// 手机号和密码复合索引,用于登录验证
UserSchema.index({ "profile.mobile": 1, "security.passwd": 1 });
export { UserSchema };