Files

708 lines
18 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const logger = require('../utils/logger').module('UsersRoute');
const express = require('express');
const bcrypt = require('bcryptjs');
const path = require('path');
const fs = require('fs');
const User = require('../models/User');
const Role = require('../models/Role');
const UserRole = require('../models/UserRole');
const { authMiddleware } = require('../middleware/auth');
const { SALT_ROUNDS, PASSWORD_MIN_LENGTH, FILE_UPLOAD, PAGINATION } = require('../config');
const { logUserOperation } = require('../utils/operationLogger');
const router = express.Router();
const generateId = () => {
return 'user_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 9);
};
const getWhereClause = query => {
const where = {};
if (query.username) {
where.username = { [Op.like]: `%${query.username}%` };
}
if (query.status) {
where.status = query.status;
}
if (query.realName) {
where.realName = { [Op.like]: `%${query.realName}%` };
}
return where;
};
const getUserRoleIds = async userId => {
const userRoles = await UserRole.findAll({
where: { UserId: userId },
attributes: ['RoleId'],
});
return userRoles.map(ur => ur.RoleId);
};
const { Op } = require('sequelize');
router.get('/', authMiddleware, async (req, res) => {
try {
const {
page = 1,
pageSize = PAGINATION.DEFAULT_PAGE_SIZE,
username,
status,
realName,
} = req.query;
const offset = (parseInt(page) - 1) * parseInt(pageSize);
const limit = Math.min(parseInt(pageSize), PAGINATION.MAX_PAGE_SIZE);
const where = getWhereClause({ username, status, realName });
const { count, rows: users } = await User.findAndCountAll({
where,
attributes: { exclude: ['password'] },
limit,
offset,
order: [['createdAt', 'DESC']],
});
if (users.length > 0) {
const userIds = users.map(u => u.userId);
const allUserRoles = await UserRole.findAll({
include: [
{
model: Role,
where: { status: 'active' },
attributes: ['roleId', 'roleName', 'roleCode'],
},
],
where: {
UserId: { [Op.in]: userIds },
},
});
const userRolesMap = {};
allUserRoles.forEach(ur => {
if (!userRolesMap[ur.UserId]) {
userRolesMap[ur.UserId] = [];
}
userRolesMap[ur.UserId].push({
roleId: ur.Role.roleId,
roleName: ur.Role.roleName,
roleCode: ur.Role.roleCode,
});
});
users.forEach(user => {
user.dataValues.roles = userRolesMap[user.userId] || [];
});
} else {
users.forEach(user => {
user.dataValues.roles = [];
});
}
res.json({
success: true,
data: {
total: count,
page: parseInt(page),
pageSize: parseInt(pageSize),
users,
},
});
} catch (error) {
logger.error('获取用户列表错误', { error: error.message, stack: error.stack });
res.status(500).json({
success: false,
message: '获取用户列表失败',
});
}
});
router.get('/all', authMiddleware, async (req, res) => {
try {
const users = await User.findAll({
where: { status: 'active' },
attributes: ['userId', 'username', 'realName', 'email'],
order: [['realName', 'ASC']],
});
res.json({
success: true,
data: users,
});
} catch (error) {
logger.error('获取所有用户错误', { error: error.message, stack: error.stack });
res.status(500).json({
success: false,
message: '获取用户列表失败',
});
}
});
router.get('/:userId', authMiddleware, async (req, res) => {
try {
const user = await User.findByPk(req.params.userId, {
attributes: { exclude: ['password'] },
});
if (!user) {
return res.status(404).json({
success: false,
message: '用户不存在',
});
}
const userRoles = await UserRole.findAll({
include: [
{
model: Role,
where: { status: 'active' },
},
],
where: { UserId: user.userId },
});
user.dataValues.roles = userRoles.map(ur => ({
roleId: ur.Role.roleId,
roleName: ur.Role.roleName,
roleCode: ur.Role.roleCode,
}));
res.json({
success: true,
data: user,
});
} catch (error) {
logger.error('获取用户详情错误', { error: error.message, stack: error.stack });
res.status(500).json({
success: false,
message: '获取用户详情失败',
});
}
});
router.post('/', authMiddleware, async (req, res) => {
try {
const { username, password, email, phone, realName, roleIds, status, remark } = req.body;
if (!username || !password) {
return res.status(400).json({
success: false,
message: '用户名和密码不能为空',
});
}
const existingUser = await User.findOne({ where: { username } });
if (existingUser) {
return res.status(400).json({
success: false,
message: '用户名已存在',
});
}
const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS);
const user = await User.create({
userId: generateId(),
username,
password: hashedPassword,
email,
phone,
realName: realName || username,
status: status || 'active',
remark,
});
if (roleIds && roleIds.length > 0) {
for (const roleId of roleIds) {
await UserRole.create({
UserId: user.userId,
RoleId: roleId,
});
}
}
const roleNames =
roleIds && roleIds.length > 0
? (await Role.findAll({ where: { roleId: { [Op.in]: roleIds } } }))
.map(r => r.roleName)
.join('、')
: '未分配角色';
await logUserOperation(
'create',
`创建用户【${username}】(姓名:${realName || '未填写'},邮箱:${email || '未填写'},角色:${roleNames}`,
{
targetId: user.userId,
targetName: username,
afterState: {
username: user.username,
email: user.email,
realName: user.realName,
status: user.status,
},
req,
metadata: { roleIds, roleNames },
}
);
res.status(201).json({
success: true,
message: '创建成功',
data: {
userId: user.userId,
username: user.username,
email: user.email,
realName: user.realName,
status: user.status,
},
});
} catch (error) {
logger.error('创建用户错误', { error: error.message, stack: error.stack });
res.status(500).json({
success: false,
message: '创建用户失败',
});
}
});
router.put('/:userId', authMiddleware, async (req, res) => {
try {
const { username, email, phone, realName, roleIds, status, remark, newPassword } = req.body;
const user = await User.findByPk(req.params.userId);
if (!user) {
return res.status(404).json({
success: false,
message: '用户不存在',
});
}
const beforeState = {
username: user.username,
email: user.email,
phone: user.phone,
realName: user.realName,
status: user.status,
remark: user.remark,
};
const oldRoleIds = roleIds !== undefined ? null : await getUserRoleIds(user.userId);
if (username !== undefined && username !== user.username) {
const existingUser = await User.findOne({
where: { username, userId: { [Op.ne]: user.userId } },
});
if (existingUser) {
return res.status(400).json({
success: false,
message: '用户名已存在',
});
}
user.username = username;
}
if (email !== undefined) {
user.email = email;
}
if (phone !== undefined) {
user.phone = phone;
}
if (realName !== undefined) {
user.realName = realName;
}
if (status !== undefined) {
user.status = status;
}
if (remark !== undefined) {
user.remark = remark;
}
if (newPassword && newPassword.length >= PASSWORD_MIN_LENGTH) {
user.password = await bcrypt.hash(newPassword, SALT_ROUNDS);
}
await user.save();
let permissionChanged = false;
let oldRoleNames = [];
let newRoleNames = [];
if (roleIds !== undefined) {
const oldRoles = await Role.findAll({ where: { roleId: { [Op.in]: oldRoleIds || [] } } });
oldRoleNames = oldRoles.map(r => r.roleName);
await UserRole.destroy({ where: { UserId: user.userId } });
for (const roleId of roleIds) {
await UserRole.create({
UserId: user.userId,
RoleId: roleId,
});
}
const newRoles = await Role.findAll({ where: { roleId: { [Op.in]: roleIds } } });
newRoleNames = newRoles.map(r => r.roleName);
permissionChanged = true;
}
const updatedUser = await User.findByPk(req.params.userId, {
attributes: { exclude: ['password'] },
});
if (permissionChanged) {
const roleChangeDesc = `变更用户【${updatedUser.username}】的角色:${oldRoleNames.join('、') || '无'}${newRoleNames.join('、') || '无'}`;
await logUserOperation('permission_change', roleChangeDesc, {
targetId: updatedUser.userId,
targetName: updatedUser.username,
beforeState: { ...beforeState, roleIds: oldRoleIds, roleNames: oldRoleNames },
afterState: { ...beforeState, roleIds, roleNames: newRoleNames },
req,
metadata: { oldRoleIds, newRoleIds: roleIds, oldRoleNames, newRoleNames },
});
} else {
const afterState = {
username: updatedUser.username,
email: updatedUser.email,
phone: updatedUser.phone,
realName: updatedUser.realName,
status: updatedUser.status,
remark: updatedUser.remark,
};
const changedFields = {};
for (const key of Object.keys(beforeState)) {
if (JSON.stringify(beforeState[key]) !== JSON.stringify(afterState[key])) {
changedFields[key] = { from: beforeState[key], to: afterState[key] };
}
}
const changeDetails = Object.entries(changedFields)
.map(([field, values]) => {
const fieldNames = {
username: '用户名',
email: '邮箱',
phone: '电话',
realName: '姓名',
status: '状态',
remark: '备注',
};
const displayName = fieldNames[field] || field;
return `${displayName}: ${values.from ?? '空'}${values.to ?? '空'}`;
})
.join('');
const updateDesc = changeDetails
? `更新用户【${updatedUser.username}】:${changeDetails}`
: `更新用户【${updatedUser.username}】`;
await logUserOperation('update', updateDesc, {
targetId: updatedUser.userId,
targetName: updatedUser.username,
beforeState,
afterState,
req,
metadata: { changedFields },
});
}
res.json({
success: true,
message: '更新成功',
data: updatedUser,
});
} catch (error) {
logger.error('更新用户错误', { error: error.message, stack: error.stack });
res.status(500).json({
success: false,
message: '更新用户失败',
});
}
});
router.put('/:userId/password', authMiddleware, async (req, res) => {
try {
const { newPassword } = req.body;
const user = await User.findByPk(req.params.userId);
if (!user) {
return res.status(404).json({
success: false,
message: '用户不存在',
});
}
// 权限检查:只能重置自己的密码,或管理员重置他人密码
if (req.user.userId !== user.userId && req.user.role !== 'admin') {
return res.status(403).json({
success: false,
message: '无权重置其他用户的密码',
});
}
if (!newPassword || newPassword.length < PASSWORD_MIN_LENGTH) {
return res.status(400).json({
success: false,
message: `密码长度不能少于${PASSWORD_MIN_LENGTH}个字符`,
});
}
user.password = await bcrypt.hash(newPassword, SALT_ROUNDS);
await user.save();
res.json({
success: true,
message: '密码重置成功',
});
} catch (error) {
logger.error('重置密码错误', { error: error.message, stack: error.stack });
res.status(500).json({
success: false,
message: '重置密码失败',
});
}
});
router.delete('/:userId', authMiddleware, async (req, res) => {
try {
const user = await User.findByPk(req.params.userId);
if (!user) {
return res.status(404).json({
success: false,
message: '用户不存在',
});
}
if (user.userId === req.user.userId) {
return res.status(400).json({
success: false,
message: '不能删除当前登录用户',
});
}
const userName = user.username;
const userRealName = user.realName;
const userEmail = user.email;
const beforeState = {
username: user.username,
email: user.email,
realName: user.realName,
status: user.status,
};
await UserRole.destroy({ where: { UserId: user.userId } });
await user.destroy();
await logUserOperation(
'delete',
`删除用户【${userName}】(姓名:${userRealName || '未填写'},邮箱:${userEmail || '未填写'}`,
{
targetId: req.params.userId,
targetName: userName,
beforeState,
req,
metadata: { deletedUsername: userName, realName: userRealName, email: userEmail },
}
);
res.json({
success: true,
message: '删除成功',
});
} catch (error) {
logger.error('删除用户错误', { error: error.message, stack: error.stack });
res.status(500).json({
success: false,
message: '删除用户失败',
});
}
});
router.post('/:userId/avatar', authMiddleware, async (req, res) => {
try {
const user = await User.findByPk(req.params.userId);
if (!user) {
return res.status(404).json({
success: false,
message: '用户不存在',
});
}
if (!req.files || !req.files.avatar) {
return res.status(400).json({
success: false,
message: '请选择要上传的头像文件',
});
}
const avatarFile = req.files.avatar;
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!allowedTypes.includes(avatarFile.mimetype)) {
return res.status(400).json({
success: false,
message: '只支持 JPG、PNG、GIF 和 WebP 格式的图片',
});
}
if (avatarFile.size > FILE_UPLOAD.MAX_AVATAR_SIZE) {
return res.status(400).json({
success: false,
message: `图片大小不能超过 ${FILE_UPLOAD.MAX_AVATAR_SIZE / 1024 / 1024}MB`,
});
}
const uploadDir = path.join(__dirname, '..', 'uploads', 'avatars');
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
const ext = path.extname(avatarFile.name) || '.jpg';
const filename = `avatar_${user.userId}_${Date.now()}${ext}`;
const filepath = path.join(uploadDir, filename);
await avatarFile.mv(filepath);
if (user.avatar && user.avatar.startsWith('/uploads/avatars/')) {
const oldFile = path.join(__dirname, '..', user.avatar);
if (fs.existsSync(oldFile)) {
fs.unlinkSync(oldFile);
}
}
const avatarUrl = `/uploads/avatars/${filename}`;
user.avatar = avatarUrl;
await user.save();
res.json({
success: true,
message: '头像上传成功',
data: { avatar: avatarUrl },
});
} catch (error) {
logger.error('上传头像错误', { error: error.message, stack: error.stack });
res.status(500).json({
success: false,
message: '上传头像失败',
});
}
});
router.delete('/:userId/avatar', authMiddleware, async (req, res) => {
try {
const user = await User.findByPk(req.params.userId);
if (!user) {
return res.status(404).json({
success: false,
message: '用户不存在',
});
}
if (user.avatar && user.avatar.startsWith('/uploads/avatars/')) {
const oldFile = path.join(__dirname, '..', user.avatar);
if (fs.existsSync(oldFile)) {
fs.unlinkSync(oldFile);
}
}
user.avatar = null;
await user.save();
res.json({
success: true,
message: '头像删除成功',
});
} catch (error) {
logger.error('删除头像错误', { error: error.message, stack: error.stack });
res.status(500).json({
success: false,
message: '删除头像失败',
});
}
});
router.put('/:userId/approve', authMiddleware, async (req, res) => {
try {
const user = await User.findByPk(req.params.userId);
if (!user) {
return res.status(404).json({
success: false,
message: '用户不存在',
});
}
if (user.status !== 'pending') {
return res.status(400).json({
success: false,
message: '该用户不在待审核状态',
});
}
user.status = 'active';
await user.save();
res.json({
success: true,
message: '用户审核通过',
data: {
userId: user.userId,
username: user.username,
status: user.status,
},
});
} catch (error) {
logger.error('审核用户错误', { error: error.message, stack: error.stack });
res.status(500).json({
success: false,
message: '审核用户失败',
});
}
});
router.put('/:userId/reject', authMiddleware, async (req, res) => {
try {
const user = await User.findByPk(req.params.userId);
if (!user) {
return res.status(404).json({
success: false,
message: '用户不存在',
});
}
if (user.status !== 'pending') {
return res.status(400).json({
success: false,
message: '该用户不在待审核状态',
});
}
user.status = 'inactive';
await user.save();
res.json({
success: true,
message: '已拒绝该用户的注册申请',
data: {
userId: user.userId,
username: user.username,
status: user.status,
},
});
} catch (error) {
logger.error('拒绝用户错误', { error: error.message, stack: error.stack });
res.status(500).json({
success: false,
message: '操作失败',
});
}
});
module.exports = router;