2025-08-31 23:29:26 +08:00
|
|
|
|
const express = require('express');
|
|
|
|
|
|
const bcrypt = require('bcryptjs');
|
|
|
|
|
|
const validator = require('validator');
|
|
|
|
|
|
const dbConnector = require('../utils/dbConnector');
|
|
|
|
|
|
const { adminRequired } = require('../middlewares/auth');
|
|
|
|
|
|
const { asyncHandler } = require('../middlewares/errorHandler');
|
|
|
|
|
|
|
|
|
|
|
|
const router = express.Router();
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取用户列表(管理员权限)
|
|
|
|
|
|
*/
|
|
|
|
|
|
router.get('/', adminRequired, asyncHandler(async (req, res) => {
|
|
|
|
|
|
const { page = 1, limit = 10, keyword, user_type } = req.query;
|
|
|
|
|
|
const offset = (page - 1) * limit;
|
|
|
|
|
|
|
|
|
|
|
|
let whereClause = 'WHERE status = 1';
|
|
|
|
|
|
let queryParams = [];
|
|
|
|
|
|
|
|
|
|
|
|
if (keyword) {
|
|
|
|
|
|
whereClause += ' AND (username LIKE ? OR phone LIKE ? OR email LIKE ?)';
|
|
|
|
|
|
const likeKeyword = `%${keyword}%`;
|
|
|
|
|
|
queryParams.push(likeKeyword, likeKeyword, likeKeyword);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (user_type) {
|
|
|
|
|
|
whereClause += ' AND user_type = ?';
|
|
|
|
|
|
queryParams.push(user_type);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取用户列表
|
|
|
|
|
|
const users = await dbConnector.query(
|
|
|
|
|
|
`SELECT id, username, phone, email, user_type, avatar_url, created_at, last_login
|
|
|
|
|
|
FROM users ${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ?`,
|
|
|
|
|
|
[...queryParams, parseInt(limit), offset]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 获取总数
|
|
|
|
|
|
const totalResult = await dbConnector.query(
|
|
|
|
|
|
`SELECT COUNT(*) as total FROM users ${whereClause}`,
|
|
|
|
|
|
queryParams
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
code: 200,
|
|
|
|
|
|
message: '获取成功',
|
|
|
|
|
|
data: {
|
|
|
|
|
|
users,
|
|
|
|
|
|
pagination: {
|
|
|
|
|
|
page: parseInt(page),
|
|
|
|
|
|
limit: parseInt(limit),
|
|
|
|
|
|
total: totalResult[0].total,
|
|
|
|
|
|
pages: Math.ceil(totalResult[0].total / limit)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
2025-09-01 02:58:34 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 创建用户(管理员权限)
|
|
|
|
|
|
*/
|
|
|
|
|
|
router.post('/', adminRequired, asyncHandler(async (req, res) => {
|
|
|
|
|
|
const { username, phone, email, user_type, password, real_name, avatar_url } = req.body;
|
|
|
|
|
|
|
|
|
|
|
|
// 验证必填字段
|
|
|
|
|
|
if (!username || !phone || !email || !user_type || !password) {
|
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
|
code: 400,
|
|
|
|
|
|
message: '用户名、手机号、邮箱、用户类型和密码为必填项',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证邮箱格式
|
|
|
|
|
|
if (!validator.isEmail(email)) {
|
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
|
code: 400,
|
|
|
|
|
|
message: '邮箱格式不正确',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证手机号格式
|
|
|
|
|
|
if (!validator.isMobilePhone(phone, 'zh-CN')) {
|
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
|
code: 400,
|
|
|
|
|
|
message: '手机号格式不正确',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证密码长度
|
|
|
|
|
|
if (password.length < 6) {
|
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
|
code: 400,
|
|
|
|
|
|
message: '密码长度不能少于6位',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查用户名是否已存在
|
|
|
|
|
|
const existingUsername = await dbConnector.query(
|
|
|
|
|
|
'SELECT id FROM users WHERE username = ?',
|
|
|
|
|
|
[username]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (existingUsername.length > 0) {
|
|
|
|
|
|
return res.status(409).json({
|
|
|
|
|
|
code: 409,
|
|
|
|
|
|
message: '用户名已存在',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查邮箱是否已存在
|
|
|
|
|
|
const existingEmail = await dbConnector.query(
|
|
|
|
|
|
'SELECT id FROM users WHERE email = ?',
|
|
|
|
|
|
[email]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (existingEmail.length > 0) {
|
|
|
|
|
|
return res.status(409).json({
|
|
|
|
|
|
code: 409,
|
|
|
|
|
|
message: '邮箱已存在',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查手机号是否已存在
|
|
|
|
|
|
const existingPhone = await dbConnector.query(
|
|
|
|
|
|
'SELECT id FROM users WHERE phone = ?',
|
|
|
|
|
|
[phone]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (existingPhone.length > 0) {
|
|
|
|
|
|
return res.status(409).json({
|
|
|
|
|
|
code: 409,
|
|
|
|
|
|
message: '手机号已存在',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 加密密码
|
|
|
|
|
|
const hashedPassword = await bcrypt.hash(password, 12);
|
|
|
|
|
|
|
|
|
|
|
|
// 插入用户数据
|
|
|
|
|
|
const result = await dbConnector.query(
|
|
|
|
|
|
`INSERT INTO users (username, phone, email, user_type, password_hash, real_name, avatar_url)
|
|
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
|
|
|
|
[username, phone, email, user_type, hashedPassword, real_name || null, avatar_url || null]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 获取新创建的用户信息
|
|
|
|
|
|
const newUser = await dbConnector.query(
|
|
|
|
|
|
'SELECT id, username, phone, email, user_type, avatar_url, real_name FROM users WHERE id = ?',
|
|
|
|
|
|
[result.insertId]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
res.status(201).json({
|
|
|
|
|
|
code: 201,
|
|
|
|
|
|
message: '用户创建成功',
|
|
|
|
|
|
data: newUser[0]
|
|
|
|
|
|
});
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
2025-08-31 23:29:26 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取用户详情
|
|
|
|
|
|
*/
|
|
|
|
|
|
router.get('/:id', asyncHandler(async (req, res) => {
|
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
|
|
|
|
|
|
|
const users = await dbConnector.query(
|
|
|
|
|
|
`SELECT id, username, phone, email, user_type, avatar_url,
|
|
|
|
|
|
real_name, created_at, last_login
|
|
|
|
|
|
FROM users WHERE id = ? AND status = 1`,
|
|
|
|
|
|
[id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (users.length === 0) {
|
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
|
code: 404,
|
|
|
|
|
|
message: '用户不存在',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
code: 200,
|
|
|
|
|
|
message: '获取成功',
|
|
|
|
|
|
data: users[0]
|
|
|
|
|
|
});
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更新用户信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
router.put('/:id', asyncHandler(async (req, res) => {
|
|
|
|
|
|
const { id } = req.params;
|
2025-09-01 02:58:34 +08:00
|
|
|
|
const { username, phone, email, user_type, real_name, avatar_url } = req.body;
|
2025-08-31 23:29:26 +08:00
|
|
|
|
|
|
|
|
|
|
// 检查用户是否存在
|
|
|
|
|
|
const existingUser = await dbConnector.query(
|
|
|
|
|
|
'SELECT id FROM users WHERE id = ? AND status = 1',
|
|
|
|
|
|
[id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (existingUser.length === 0) {
|
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
|
code: 404,
|
|
|
|
|
|
message: '用户不存在',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证邮箱格式
|
|
|
|
|
|
if (email && !validator.isEmail(email)) {
|
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
|
code: 400,
|
|
|
|
|
|
message: '邮箱格式不正确',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-01 02:58:34 +08:00
|
|
|
|
// 验证手机号格式
|
|
|
|
|
|
if (phone && !validator.isMobilePhone(phone, 'zh-CN')) {
|
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
|
code: 400,
|
|
|
|
|
|
message: '手机号格式不正确',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查用户名是否已被其他用户使用
|
|
|
|
|
|
if (username) {
|
|
|
|
|
|
const usernameUser = await dbConnector.query(
|
|
|
|
|
|
'SELECT id FROM users WHERE username = ? AND id != ?',
|
|
|
|
|
|
[username, id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (usernameUser.length > 0) {
|
|
|
|
|
|
return res.status(409).json({
|
|
|
|
|
|
code: 409,
|
|
|
|
|
|
message: '用户名已被其他用户使用',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-31 23:29:26 +08:00
|
|
|
|
// 检查邮箱是否已被其他用户使用
|
|
|
|
|
|
if (email) {
|
|
|
|
|
|
const emailUser = await dbConnector.query(
|
|
|
|
|
|
'SELECT id FROM users WHERE email = ? AND id != ?',
|
|
|
|
|
|
[email, id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (emailUser.length > 0) {
|
|
|
|
|
|
return res.status(409).json({
|
|
|
|
|
|
code: 409,
|
|
|
|
|
|
message: '邮箱已被其他用户使用',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建更新字段
|
|
|
|
|
|
const updateFields = [];
|
|
|
|
|
|
const updateValues = [];
|
|
|
|
|
|
|
2025-09-01 02:58:34 +08:00
|
|
|
|
if (username !== undefined) {
|
|
|
|
|
|
updateFields.push('username = ?');
|
|
|
|
|
|
updateValues.push(username);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (phone !== undefined) {
|
|
|
|
|
|
updateFields.push('phone = ?');
|
|
|
|
|
|
updateValues.push(phone);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-31 23:29:26 +08:00
|
|
|
|
if (email !== undefined) {
|
|
|
|
|
|
updateFields.push('email = ?');
|
|
|
|
|
|
updateValues.push(email);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-01 02:58:34 +08:00
|
|
|
|
if (user_type !== undefined) {
|
|
|
|
|
|
updateFields.push('user_type = ?');
|
|
|
|
|
|
updateValues.push(user_type);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-31 23:29:26 +08:00
|
|
|
|
if (real_name !== undefined) {
|
|
|
|
|
|
updateFields.push('real_name = ?');
|
|
|
|
|
|
updateValues.push(real_name);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (avatar_url !== undefined) {
|
|
|
|
|
|
updateFields.push('avatar_url = ?');
|
|
|
|
|
|
updateValues.push(avatar_url);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (updateFields.length === 0) {
|
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
|
code: 400,
|
|
|
|
|
|
message: '没有提供需要更新的字段',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
updateFields.push('updated_at = CURRENT_TIMESTAMP');
|
|
|
|
|
|
updateValues.push(id);
|
|
|
|
|
|
|
|
|
|
|
|
await dbConnector.query(
|
|
|
|
|
|
`UPDATE users SET ${updateFields.join(', ')} WHERE id = ?`,
|
|
|
|
|
|
updateValues
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 获取更新后的用户信息
|
|
|
|
|
|
const updatedUser = await dbConnector.query(
|
|
|
|
|
|
'SELECT id, username, phone, email, user_type, avatar_url, real_name FROM users WHERE id = ?',
|
|
|
|
|
|
[id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
code: 200,
|
|
|
|
|
|
message: '更新成功',
|
|
|
|
|
|
data: updatedUser[0]
|
|
|
|
|
|
});
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 修改密码
|
|
|
|
|
|
*/
|
|
|
|
|
|
router.put('/:id/password', asyncHandler(async (req, res) => {
|
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
|
const { old_password, new_password } = req.body;
|
|
|
|
|
|
|
|
|
|
|
|
if (!old_password || !new_password) {
|
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
|
code: 400,
|
|
|
|
|
|
message: '原密码和新密码为必填项',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (new_password.length < 6) {
|
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
|
code: 400,
|
|
|
|
|
|
message: '新密码长度不能少于6位',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取用户当前密码
|
|
|
|
|
|
const users = await dbConnector.query(
|
|
|
|
|
|
'SELECT password_hash FROM users WHERE id = ?',
|
|
|
|
|
|
[id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (users.length === 0) {
|
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
|
code: 404,
|
|
|
|
|
|
message: '用户不存在',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证原密码
|
|
|
|
|
|
const isValidPassword = await bcrypt.compare(old_password, users[0].password_hash);
|
|
|
|
|
|
if (!isValidPassword) {
|
|
|
|
|
|
return res.status(401).json({
|
|
|
|
|
|
code: 401,
|
|
|
|
|
|
message: '原密码不正确',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 加密新密码
|
|
|
|
|
|
const hashedPassword = await bcrypt.hash(new_password, 12);
|
|
|
|
|
|
|
|
|
|
|
|
await dbConnector.query(
|
|
|
|
|
|
'UPDATE users SET password_hash = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
|
|
|
|
|
[hashedPassword, id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
code: 200,
|
|
|
|
|
|
message: '密码修改成功',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 删除用户(软删除)
|
|
|
|
|
|
*/
|
|
|
|
|
|
router.delete('/:id', adminRequired, asyncHandler(async (req, res) => {
|
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
|
|
|
|
|
|
|
const result = await dbConnector.query(
|
|
|
|
|
|
'UPDATE users SET status = 0, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
|
|
|
|
|
[id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (result.affectedRows === 0) {
|
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
|
code: 404,
|
|
|
|
|
|
message: '用户不存在',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
code: 200,
|
|
|
|
|
|
message: '用户删除成功',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = router;
|