This commit is contained in:
lik
2026-05-25 12:02:28 +08:00
parent 17c8f0e1e8
commit 2c6efee887
13 changed files with 2337 additions and 0 deletions

45
models/index.js Normal file
View File

@@ -0,0 +1,45 @@
'use strict';
import mongoose from 'mongoose';
import { UserSchema } from "./schema/user.js"
import config from '../conf.json' with { type: 'json' };
import logger from '../utils/logger.js';
class MongoDBSchema {
constructor() {
this.dbConnection = null;
this.User = null;
this.EscortRecord = null;
this.Hospital = null;
}
init() {
mongoose.set("debug", config.mongodb.debug);
this.dbConnection = mongoose.createConnection(config.mongodb.str, config.mongodb.option);
this.dbConnection.on("error", () => {
logger.error.bind(logger, "...mongodb connect error ...")
});
this.dbConnection.on("connected", async () => {
logger.info("Mongodb: " + config.mongodb.str + " connected");
});
this.dbConnection.on("disconnected", () =>
logger.warn("Mongodb: " + config.mongodb.str + " disconnected")
);
this.dbConnection.on("reconnected", () =>
logger.info("Mongodb: " + config.mongodb.str + " reconnected")
);
this.dbConnection.on("disconnecting", () =>
logger.warn("Mongodb: " + config.mongodb.str + " disconnecting")
);
this.dbConnection.on("close", () =>
logger.warn("Mongodb: " + config.mongodb.str + " closed")
);
this.User = this.dbConnection.model('user', UserSchema)
}
}
const DBModel = new MongoDBSchema()
export { DBModel };

293
models/schema/user.js Normal file
View File

@@ -0,0 +1,293 @@
"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 };