start
This commit is contained in:
45
models/index.js
Normal file
45
models/index.js
Normal 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
293
models/schema/user.js
Normal 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 };
|
||||
Reference in New Issue
Block a user