2025-12-25 10:48:35 +08:00
|
|
|
|
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');
|
2026-03-06 14:39:52 +08:00
|
|
|
|
const { SALT_ROUNDS, PASSWORD_MIN_LENGTH, FILE_UPLOAD, PAGINATION } = require('../config');
|
2026-03-20 17:15:14 +08:00
|
|
|
|
const { logUserOperation } = require('../utils/operationLogger');
|
2025-12-25 10:48:35 +08:00
|
|
|
|
|
|
|
|
|
|
const router = express.Router();
|
|
|
|
|
|
|
|
|
|
|
|
const generateId = () => {
|
|
|
|
|
|
return 'user_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 9);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const getWhereClause = (query) => {
|
|
|
|
|
|
const where = {};
|
2026-03-20 17:15:14 +08:00
|
|
|
|
|
2025-12-25 10:48:35 +08:00
|
|
|
|
if (query.username) {
|
|
|
|
|
|
where.username = { [Op.like]: `%${query.username}%` };
|
|
|
|
|
|
}
|
2026-03-20 17:15:14 +08:00
|
|
|
|
|
2025-12-25 10:48:35 +08:00
|
|
|
|
if (query.status) {
|
|
|
|
|
|
where.status = query.status;
|
|
|
|
|
|
}
|
2026-03-20 17:15:14 +08:00
|
|
|
|
|
2025-12-25 10:48:35 +08:00
|
|
|
|
if (query.realName) {
|
|
|
|
|
|
where.realName = { [Op.like]: `%${query.realName}%` };
|
|
|
|
|
|
}
|
2026-03-20 17:15:14 +08:00
|
|
|
|
|
2025-12-25 10:48:35 +08:00
|
|
|
|
return where;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-20 17:15:14 +08:00
|
|
|
|
const getUserRoleIds = async (userId) => {
|
|
|
|
|
|
const userRoles = await UserRole.findAll({
|
|
|
|
|
|
where: { UserId: userId },
|
|
|
|
|
|
attributes: ['RoleId']
|
|
|
|
|
|
});
|
|
|
|
|
|
return userRoles.map(ur => ur.RoleId);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-12-25 10:48:35 +08:00
|
|
|
|
const { Op } = require('sequelize');
|
|
|
|
|
|
|
|
|
|
|
|
router.get('/', authMiddleware, async (req, res) => {
|
|
|
|
|
|
try {
|
2026-03-06 14:39:52 +08:00
|
|
|
|
const { page = 1, pageSize = PAGINATION.DEFAULT_PAGE_SIZE, username, status, realName } = req.query;
|
2025-12-25 10:48:35 +08:00
|
|
|
|
const offset = (parseInt(page) - 1) * parseInt(pageSize);
|
2026-03-06 14:39:52 +08:00
|
|
|
|
const limit = Math.min(parseInt(pageSize), PAGINATION.MAX_PAGE_SIZE);
|
2025-12-25 10:48:35 +08:00
|
|
|
|
|
|
|
|
|
|
const where = getWhereClause({ username, status, realName });
|
|
|
|
|
|
|
|
|
|
|
|
const { count, rows: users } = await User.findAndCountAll({
|
|
|
|
|
|
where,
|
|
|
|
|
|
attributes: { exclude: ['password'] },
|
|
|
|
|
|
limit,
|
|
|
|
|
|
offset,
|
|
|
|
|
|
order: [['createdAt', 'DESC']]
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-01-20 14:31:20 +08:00
|
|
|
|
if (users.length > 0) {
|
|
|
|
|
|
const userIds = users.map(u => u.userId);
|
|
|
|
|
|
const allUserRoles = await UserRole.findAll({
|
2025-12-25 10:48:35 +08:00
|
|
|
|
include: [{
|
|
|
|
|
|
model: Role,
|
2026-01-20 14:31:20 +08:00
|
|
|
|
where: { status: 'active' },
|
|
|
|
|
|
attributes: ['roleId', 'roleName', 'roleCode']
|
2025-12-25 10:48:35 +08:00
|
|
|
|
}],
|
2026-01-20 14:31:20 +08:00
|
|
|
|
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 = [];
|
2025-12-25 10:48:35 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
data: {
|
|
|
|
|
|
total: count,
|
|
|
|
|
|
page: parseInt(page),
|
|
|
|
|
|
pageSize: parseInt(pageSize),
|
|
|
|
|
|
users
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取用户列表错误:', error);
|
|
|
|
|
|
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) {
|
|
|
|
|
|
console.error('获取所有用户错误:', error);
|
|
|
|
|
|
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) {
|
|
|
|
|
|
console.error('获取用户详情错误:', error);
|
|
|
|
|
|
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
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-20 17:15:14 +08:00
|
|
|
|
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 }
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-12-25 10:48:35 +08:00
|
|
|
|
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) {
|
|
|
|
|
|
console.error('创建用户错误:', error);
|
|
|
|
|
|
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: '用户不存在'
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-20 17:15:14 +08:00
|
|
|
|
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);
|
|
|
|
|
|
|
2025-12-25 10:48:35 +08:00
|
|
|
|
if (username !== undefined && username !== user.username) {
|
2026-03-20 17:15:14 +08:00
|
|
|
|
const existingUser = await User.findOne({
|
|
|
|
|
|
where: { username, userId: { [Op.ne]: user.userId } }
|
2025-12-25 10:48:35 +08:00
|
|
|
|
});
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
2026-03-06 14:39:52 +08:00
|
|
|
|
if (newPassword && newPassword.length >= PASSWORD_MIN_LENGTH) {
|
2025-12-25 10:48:35 +08:00
|
|
|
|
user.password = await bcrypt.hash(newPassword, SALT_ROUNDS);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await user.save();
|
|
|
|
|
|
|
2026-03-20 17:15:14 +08:00
|
|
|
|
let permissionChanged = false;
|
|
|
|
|
|
let oldRoleNames = [];
|
|
|
|
|
|
let newRoleNames = [];
|
|
|
|
|
|
|
2025-12-25 10:48:35 +08:00
|
|
|
|
if (roleIds !== undefined) {
|
2026-03-20 17:15:14 +08:00
|
|
|
|
const oldRoles = await Role.findAll({ where: { roleId: { [Op.in]: oldRoleIds || [] } } });
|
|
|
|
|
|
oldRoleNames = oldRoles.map(r => r.roleName);
|
|
|
|
|
|
|
2025-12-25 10:48:35 +08:00
|
|
|
|
await UserRole.destroy({ where: { UserId: user.userId } });
|
2026-03-20 17:15:14 +08:00
|
|
|
|
|
2025-12-25 10:48:35 +08:00
|
|
|
|
for (const roleId of roleIds) {
|
|
|
|
|
|
await UserRole.create({
|
|
|
|
|
|
UserId: user.userId,
|
|
|
|
|
|
RoleId: roleId
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-03-20 17:15:14 +08:00
|
|
|
|
|
|
|
|
|
|
const newRoles = await Role.findAll({ where: { roleId: { [Op.in]: roleIds } } });
|
|
|
|
|
|
newRoleNames = newRoles.map(r => r.roleName);
|
|
|
|
|
|
permissionChanged = true;
|
2025-12-25 10:48:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const updatedUser = await User.findByPk(req.params.userId, {
|
|
|
|
|
|
attributes: { exclude: ['password'] }
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-03-20 17:15:14 +08:00
|
|
|
|
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 }
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-25 10:48:35 +08:00
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
message: '更新成功',
|
|
|
|
|
|
data: updatedUser
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('更新用户错误:', error);
|
|
|
|
|
|
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: '用户不存在'
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-06 14:39:52 +08:00
|
|
|
|
if (!newPassword || newPassword.length < PASSWORD_MIN_LENGTH) {
|
2025-12-25 10:48:35 +08:00
|
|
|
|
return res.status(400).json({
|
|
|
|
|
|
success: false,
|
2026-03-06 14:39:52 +08:00
|
|
|
|
message: `密码长度不能少于${PASSWORD_MIN_LENGTH}个字符`
|
2025-12-25 10:48:35 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
user.password = await bcrypt.hash(newPassword, SALT_ROUNDS);
|
|
|
|
|
|
await user.save();
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
message: '密码重置成功'
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('重置密码错误:', error);
|
|
|
|
|
|
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: '不能删除当前登录用户'
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-20 17:15:14 +08:00
|
|
|
|
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
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-12-25 10:48:35 +08:00
|
|
|
|
await UserRole.destroy({ where: { UserId: user.userId } });
|
|
|
|
|
|
await user.destroy();
|
|
|
|
|
|
|
2026-03-20 17:15:14 +08:00
|
|
|
|
await logUserOperation('delete', `删除用户【${userName}】(姓名:${userRealName || '未填写'},邮箱:${userEmail || '未填写'})`, {
|
|
|
|
|
|
targetId: req.params.userId,
|
|
|
|
|
|
targetName: userName,
|
|
|
|
|
|
beforeState,
|
|
|
|
|
|
req,
|
|
|
|
|
|
metadata: { deletedUsername: userName, realName: userRealName, email: userEmail }
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-12-25 10:48:35 +08:00
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
message: '删除成功'
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('删除用户错误:', error);
|
|
|
|
|
|
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 格式的图片'
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-06 14:39:52 +08:00
|
|
|
|
if (avatarFile.size > FILE_UPLOAD.MAX_AVATAR_SIZE) {
|
2025-12-25 10:48:35 +08:00
|
|
|
|
return res.status(400).json({
|
|
|
|
|
|
success: false,
|
2026-03-06 14:39:52 +08:00
|
|
|
|
message: `图片大小不能超过 ${FILE_UPLOAD.MAX_AVATAR_SIZE / 1024 / 1024}MB`
|
2025-12-25 10:48:35 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
|
console.error('上传头像错误:', error);
|
|
|
|
|
|
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) {
|
|
|
|
|
|
console.error('删除头像错误:', error);
|
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
message: '删除头像失败'
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-02-05 15:12:31 +08:00
|
|
|
|
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) {
|
|
|
|
|
|
console.error('审核用户错误:', error);
|
|
|
|
|
|
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) {
|
|
|
|
|
|
console.error('拒绝用户错误:', error);
|
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
message: '操作失败'
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-12-25 10:48:35 +08:00
|
|
|
|
module.exports = router;
|