293 lines
9.0 KiB
JavaScript
293 lines
9.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, 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 }; |