Generating commit message...

This commit is contained in:
2025-08-30 14:33:49 +08:00
parent 4d469e95f0
commit 7f9bfbb381
99 changed files with 69225 additions and 35 deletions

212
backend/src/models/User.js Normal file
View File

@@ -0,0 +1,212 @@
const mongoose = require('mongoose')
const bcrypt = require('bcryptjs')
// 用户等级枚举
const UserLevel = {
NORMAL: 1, // 普通用户
VIP: 2, // VIP用户
SUPER_VIP: 3 // 超级VIP
}
// 用户状态枚举
const UserStatus = {
ACTIVE: 'active', // 活跃
INACTIVE: 'inactive', // 非活跃
BANNED: 'banned' // 封禁
}
const userSchema = new mongoose.Schema({
// 基础信息
username: {
type: String,
required: true,
unique: true,
trim: true,
minlength: 3,
maxlength: 20,
match: /^[a-zA-Z0-9_]+$/
},
password: {
type: String,
required: true,
minlength: 6
},
email: {
type: String,
sparse: true,
trim: true,
lowercase: true,
match: /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/
},
phone: {
type: String,
sparse: true,
trim: true,
match: /^1[3-9]\d{9}$/
},
// 个人信息
nickname: {
type: String,
required: true,
trim: true,
maxlength: 20
},
avatar: {
type: String,
default: ''
},
gender: {
type: String,
enum: ['male', 'female', 'unknown'],
default: 'unknown'
},
birthday: Date,
// 账户信息
points: {
type: Number,
default: 0,
min: 0
},
level: {
type: Number,
enum: Object.values(UserLevel),
default: UserLevel.NORMAL
},
status: {
type: String,
enum: Object.values(UserStatus),
default: UserStatus.ACTIVE
},
balance: {
type: Number,
default: 0,
min: 0
},
// 第三方登录
wechatOpenid: {
type: String,
sparse: true
},
wechatUnionid: {
type: String,
sparse: true
},
// 统计信息
travelCount: {
type: Number,
default: 0
},
animalAdoptCount: {
type: Number,
default: 0
},
flowerOrderCount: {
type: Number,
default: 0
},
// 时间戳
lastLoginAt: Date,
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
}, {
timestamps: true,
toJSON: {
transform: function(doc, ret) {
delete ret.password
delete ret.wechatOpenid
delete ret.wechatUnionid
return ret
}
}
})
// 索引 (移除与字段定义中 unique: true 和 sparse: true 重复的索引)
userSchema.index({ createdAt: -1 })
userSchema.index({ points: -1 })
// 密码加密中间件
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next()
try {
const salt = await bcrypt.genSalt(12)
this.password = await bcrypt.hash(this.password, salt)
next()
} catch (error) {
next(error)
}
})
// 比较密码方法
userSchema.methods.comparePassword = async function(candidatePassword) {
return bcrypt.compare(candidatePassword, this.password)
}
// 检查用户状态是否活跃
userSchema.methods.isActive = function() {
return this.status === UserStatus.ACTIVE
}
// 检查是否为VIP用户
userSchema.methods.isVip = function() {
return this.level >= UserLevel.VIP
}
// 添加积分
userSchema.methods.addPoints = function(points) {
this.points += points
return this.save()
}
// 扣除积分
userSchema.methods.deductPoints = function(points) {
if (this.points < points) {
throw new Error('积分不足')
}
this.points -= points
return this.save()
}
// 静态方法:根据用户名查找用户
userSchema.statics.findByUsername = function(username) {
return this.findOne({ username })
}
// 静态方法:根据邮箱查找用户
userSchema.statics.findByEmail = function(email) {
return this.findOne({ email })
}
// 静态方法:根据手机号查找用户
userSchema.statics.findByPhone = function(phone) {
return this.findOne({ phone })
}
// 虚拟字段:用户等级名称
userSchema.virtual('levelName').get(function() {
const levelNames = {
[UserLevel.NORMAL]: '普通用户',
[UserLevel.VIP]: 'VIP用户',
[UserLevel.SUPER_VIP]: '超级VIP'
}
return levelNames[this.level] || '未知'
})
const User = mongoose.model('User', userSchema)
module.exports = {
User,
UserLevel,
UserStatus
}

View File

@@ -0,0 +1,160 @@
const { query, transaction } = require('../config/database');
class UserMySQL {
// 创建用户
static async create(userData) {
const {
openid,
nickname,
avatar = '',
gender = 'other',
birthday = null,
phone = null,
email = null
} = userData;
const sql = `
INSERT INTO users (
openid, nickname, avatar, gender, birthday, phone, email,
created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
`;
const params = [
openid,
nickname,
avatar,
gender,
birthday,
phone,
email
];
const result = await query(sql, params);
return result.insertId;
}
// 根据ID查找用户
static async findById(id) {
const sql = 'SELECT * FROM users WHERE id = ?';
const rows = await query(sql, [id]);
return rows[0] || null;
}
// 根据openid查找用户
static async findByOpenid(openid) {
const sql = 'SELECT * FROM users WHERE openid = ?';
const rows = await query(sql, [openid]);
return rows[0] || null;
}
// 根据手机号查找用户
static async findByPhone(phone) {
const sql = 'SELECT * FROM users WHERE phone = ?';
const rows = await query(sql, [phone]);
return rows[0] || null;
}
// 根据邮箱查找用户
static async findByEmail(email) {
const sql = 'SELECT * FROM users WHERE email = ?';
const rows = await query(sql, [email]);
return rows[0] || null;
}
// 更新用户信息
static async update(id, updates) {
const allowedFields = ['nickname', 'avatar', 'gender', 'birthday', 'phone', 'email'];
const setClauses = [];
const params = [];
for (const [key, value] of Object.entries(updates)) {
if (allowedFields.includes(key) && value !== undefined) {
setClauses.push(`${key} = ?`);
params.push(value);
}
}
if (setClauses.length === 0) {
return false;
}
setClauses.push('updated_at = NOW()');
params.push(id);
const sql = `UPDATE users SET ${setClauses.join(', ')} WHERE id = ?`;
const result = await query(sql, params);
return result.affectedRows > 0;
}
// 更新密码
static async updatePassword(id, newPassword) {
const sql = 'UPDATE users SET password = ?, updated_at = NOW() WHERE id = ?';
const result = await query(sql, [newPassword, id]);
return result.affectedRows > 0;
}
// 更新最后登录时间
static async updateLastLogin(id) {
const sql = 'UPDATE users SET updated_at = NOW() WHERE id = ?';
const result = await query(sql, [id]);
return result.affectedRows > 0;
}
// 检查openid是否已存在
static async isOpenidExists(openid, excludeId = null) {
let sql = 'SELECT COUNT(*) as count FROM users WHERE openid = ?';
const params = [openid];
if (excludeId) {
sql += ' AND id != ?';
params.push(excludeId);
}
const rows = await query(sql, params);
return rows[0].count > 0;
}
// 检查邮箱是否已存在
static async isEmailExists(email, excludeId = null) {
let sql = 'SELECT COUNT(*) as count FROM users WHERE email = ?';
const params = [email];
if (excludeId) {
sql += ' AND id != ?';
params.push(excludeId);
}
const rows = await query(sql, params);
return rows[0].count > 0;
}
// 检查手机号是否已存在
static async isPhoneExists(phone, excludeId = null) {
let sql = 'SELECT COUNT(*) as count FROM users WHERE phone = ?';
const params = [phone];
if (excludeId) {
sql += ' AND id != ?';
params.push(excludeId);
}
const rows = await query(sql, params);
return rows[0].count > 0;
}
// 检查用户名是否已已存在 (根据openid检查)
static async isUsernameExists(username, excludeId = null) {
return await this.isOpenidExists(username, excludeId);
}
// 安全返回用户信息(去除敏感信息)
static sanitize(user) {
if (!user) return null;
const { password, ...safeUser } = user;
return safeUser;
}
}
module.exports = UserMySQL;