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(); /** * @swagger * /api/v1/users: * get: * summary: 获取用户列表(管理员权限) * description: 分页获取用户列表,支持按关键词和用户类型筛选 * tags: * - 用户管理 * security: * - bearerAuth: [] * parameters: * - in: query * name: page * schema: * type: integer * default: 1 * description: 页码 * - in: query * name: limit * schema: * type: integer * default: 10 * description: 每页数量 * - in: query * name: keyword * schema: * type: string * description: 搜索关键词(用户名、手机号或邮箱) * - in: query * name: user_type * schema: * type: integer * description: 用户类型 * responses: * 200: * description: 成功获取用户列表 * content: * application/json: * schema: * type: object * properties: * code: * type: integer * example: 200 * message: * type: string * example: 获取成功 * data: * type: object * properties: * users: * type: array * items: * $ref: '#/components/schemas/User' * pagination: * $ref: '#/components/schemas/Pagination' * 401: * description: 未授权访问 * content: * application/json: * schema: * type: object * properties: * code: * type: integer * example: 401 * message: * type: string * example: 未授权访问 */ 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) } } }); })); /** * @swagger * /api/v1/users: * post: * summary: 创建用户(管理员权限) * description: 管理员创建新用户 * tags: * - 用户管理 * security: * - bearerAuth: [] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - username * - phone * - email * - user_type * - password * properties: * username: * type: string * example: "user123" * phone: * type: string * example: "13800138000" * email: * type: string * example: "user@example.com" * user_type: * type: integer * example: 1 * password: * type: string * example: "password123" * real_name: * type: string * example: "张三" * avatar_url: * type: string * example: "https://example.com/avatar.jpg" * responses: * 201: * description: 用户创建成功 * content: * application/json: * schema: * type: object * properties: * code: * type: integer * example: 201 * message: * type: string * example: 用户创建成功 * data: * $ref: '#/components/schemas/User' * 400: * description: 参数错误 * content: * application/json: * schema: * type: object * properties: * code: * type: integer * example: 400 * message: * type: string * example: 参数错误 * 409: * description: 用户名、邮箱或手机号已存在 * content: * application/json: * schema: * type: object * properties: * code: * type: integer * example: 409 * message: * type: string * example: 用户名已存在 */ 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] }); })); /** * 获取用户详情 */ 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; const { username, phone, email, user_type, real_name, avatar_url } = req.body; // 检查用户是否存在 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 }); } // 验证手机号格式 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 }); } } // 检查邮箱是否已被其他用户使用 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 = []; if (username !== undefined) { updateFields.push('username = ?'); updateValues.push(username); } if (phone !== undefined) { updateFields.push('phone = ?'); updateValues.push(phone); } if (email !== undefined) { updateFields.push('email = ?'); updateValues.push(email); } if (user_type !== undefined) { updateFields.push('user_type = ?'); updateValues.push(user_type); } 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;