265 lines
8.0 KiB
JavaScript
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 }; |