feat: 实现用户认证与权限管理系统

- 添加用户、角色、权限等数据模型
- 实现JWT认证中间件和密码加密
- 添加用户注册、登录、个人信息管理接口
- 实现前端认证上下文和受保护路由
- 添加登录历史记录和操作日志功能
- 提供管理员初始化脚本和修复工具
- 实现完整的登录页面和用户管理界面
This commit is contained in:
zhang1106
2025-12-25 10:48:35 +08:00
parent 568f9c7db8
commit a911d02999
29 changed files with 4309 additions and 154 deletions
+33
View File
@@ -0,0 +1,33 @@
const { sequelize } = require('./db');
const User = require('./models/User');
const bcrypt = require('bcryptjs');
async function main() {
try {
await sequelize.authenticate();
console.log('数据库连接成功!\n');
const users = await User.findAll({
attributes: ['userId', 'username', 'password', 'email', 'status', 'createdAt']
});
console.log('用户列表:');
console.log('============');
for (const user of users) {
console.log('用户名:', user.username);
console.log('密码(加密后):', user.password);
console.log('状态:', user.status);
console.log('创建时间:', user.createdAt);
console.log('------------');
}
console.log('\n' + users.length + ' 个用户');
await sequelize.close();
} catch (error) {
console.error('错误:', error.message);
}
}
main();
+125
View File
@@ -0,0 +1,125 @@
const bcrypt = require('bcryptjs');
const { sequelize } = require('./db');
const User = require('./models/User');
const Role = require('./models/Role');
const UserRole = require('./models/UserRole');
async function fixAdminUser() {
try {
await sequelize.authenticate();
console.log('数据库连接成功!\n');
const adminUser = await User.findOne({
where: { username: 'admin' }
});
if (!adminUser) {
console.log('未找到 admin 用户');
return;
}
console.log('当前 admin 用户信息:');
console.log('用户名:', adminUser.username);
console.log('邮箱:', adminUser.email);
console.log('状态:', adminUser.status);
console.log('');
const userRoles = await UserRole.findAll({
where: { UserId: adminUser.userId },
include: [{ model: Role }]
});
console.log('当前用户角色:');
if (userRoles.length === 0) {
console.log(' - 无角色分配');
} else {
userRoles.forEach(ur => {
console.log(` - ${ur.Role.roleName} (${ur.Role.roleCode})`);
});
}
console.log('');
let adminRole = await Role.findOne({
where: { roleCode: 'admin' }
});
if (!adminRole) {
console.log('正在创建管理员角色...');
adminRole = await Role.create({
roleId: 'role_admin',
roleName: '管理员',
roleCode: 'admin',
description: '系统管理员,拥有所有权限',
status: 'active',
permissions: []
});
console.log('管理员角色创建成功!');
} else {
console.log('管理员角色已存在');
}
const existingUserRole = await UserRole.findOne({
where: {
UserId: adminUser.userId,
RoleId: adminRole.roleId
}
});
if (!existingUserRole) {
console.log('正在为 admin 用户分配管理员角色...');
await UserRole.create({
UserId: adminUser.userId,
RoleId: adminRole.roleId
});
console.log('角色分配成功!');
} else {
console.log('管理员角色已分配给该用户');
}
console.log('');
const newPassword = 'admin123';
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(newPassword, salt);
await adminUser.update({
password: hashedPassword
});
console.log('密码已重置为: admin123');
console.log('');
const updatedUser = await User.findOne({
where: { username: 'admin' },
include: [{ model: Role }]
});
console.log('修复后的 admin 用户信息:');
console.log('用户名:', updatedUser.username);
console.log('邮箱:', updatedUser.email);
console.log('状态:', updatedUser.status);
console.log('角色:',
updatedUser.Roles && updatedUser.Roles.length > 0
? updatedUser.Roles.map(r => r.roleName).join(', ')
: '无角色'
);
console.log('');
const testResult = bcrypt.compareSync(newPassword, updatedUser.password);
console.log('密码验证测试:');
console.log(` 输入密码: "${newPassword}"`);
console.log(` 验证结果: ${testResult ? '✓ 成功' : '✗ 失败'}`);
console.log('\n========================================');
console.log('修复完成!现在可以使用以下信息登录:');
console.log(' 用户名: admin');
console.log(' 密码: admin123');
console.log('========================================');
} catch (error) {
console.error('修复过程中发生错误:', error.message);
} finally {
await sequelize.close();
}
}
fixAdminUser();
+133
View File
@@ -0,0 +1,133 @@
const { sequelize } = require('./db');
const Role = require('./models/Role');
const User = require('./models/User');
const UserRole = require('./models/UserRole');
async function fixForeignKeys() {
try {
console.log('开始修复外键约束...');
await sequelize.query('PRAGMA foreign_keys = OFF');
await sequelize.query('DROP TABLE IF EXISTS user_roles');
await sequelize.query('DROP TABLE IF EXISTS users');
await sequelize.query('DROP TABLE IF EXISTS roles');
console.log('已删除现有表,正在重新创建...');
await sequelize.query(`
CREATE TABLE IF NOT EXISTS roles (
roleId TEXT PRIMARY KEY,
roleName TEXT NOT NULL,
roleCode TEXT NOT NULL UNIQUE,
description TEXT,
permissions TEXT DEFAULT '[]',
sort INTEGER DEFAULT 0,
status TEXT DEFAULT 'active',
createdAt DATETIME,
updatedAt DATETIME
)
`);
await sequelize.query(`
CREATE TABLE IF NOT EXISTS users (
userId TEXT PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
email TEXT,
phone TEXT,
realName TEXT,
avatar TEXT,
status TEXT DEFAULT 'active',
lastLoginTime DATETIME,
lastLoginIp TEXT,
loginCount INTEGER DEFAULT 0,
remark TEXT,
createdAt DATETIME,
updatedAt DATETIME
)
`);
await sequelize.query(`
CREATE TABLE IF NOT EXISTS user_roles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
UserId TEXT NOT NULL,
RoleId TEXT NOT NULL,
createdAt DATETIME,
updatedAt DATETIME,
FOREIGN KEY (UserId) REFERENCES users(userId) ON DELETE CASCADE,
FOREIGN KEY (RoleId) REFERENCES roles(roleId) ON DELETE CASCADE,
UNIQUE(UserId, RoleId)
)
`);
await sequelize.query('PRAGMA foreign_keys = ON');
console.log('外键约束修复完成!');
console.log('正在重新初始化角色数据...');
const bcrypt = require('bcryptjs');
const SALT_ROUNDS = 10;
await Role.bulkCreate([
{
roleId: 'role_admin',
roleName: '管理员',
roleCode: 'admin',
description: '系统管理员,拥有所有权限',
permissions: '[]',
sort: 1,
status: 'active'
},
{
roleId: 'role_operator',
roleName: '操作员',
roleCode: 'operator',
description: '设备操作员,可进行设备操作',
permissions: '[]',
sort: 2,
status: 'active'
},
{
roleId: 'role_viewer',
roleName: '访客',
roleCode: 'viewer',
description: '只读权限,只能查看数据',
permissions: '[]',
sort: 3,
status: 'active'
}
]);
console.log('角色数据初始化完成!');
const hashedPassword = await bcrypt.hash('admin123', SALT_ROUNDS);
const adminUser = await User.create({
userId: 'user_admin',
username: 'admin',
password: hashedPassword,
email: 'admin@example.com',
realName: '系统管理员',
status: 'active'
});
await UserRole.create({
UserId: adminUser.userId,
RoleId: 'role_admin'
});
console.log('管理员账户创建完成!');
console.log('用户名: admin');
console.log('密码: admin123');
console.log('\n修复完成!请重启后端服务。');
} catch (error) {
console.error('修复外键约束失败:', error);
} finally {
await sequelize.close();
}
}
fixForeignKeys();
+151
View File
@@ -0,0 +1,151 @@
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const LoginHistory = require('../models/LoginHistory');
const JWT_SECRET = process.env.JWT_SECRET || 'idc-management-secret-key-2024';
const TOKEN_EXPIRY = process.env.TOKEN_EXPIRY || '24h';
const getBrowserInfo = (userAgent) => {
let device = 'Desktop';
let browser = 'Unknown';
let os = 'Unknown';
if (/Mobile|Android|iPhone|iPad|iPod/i.test(userAgent)) {
device = 'Mobile';
if (/iPad/i.test(userAgent)) device = 'Tablet';
}
if (/Firefox/i.test(userAgent)) {
browser = 'Firefox';
} else if (/Chrome/i.test(userAgent)) {
browser = 'Chrome';
} else if (/Safari/i.test(userAgent)) {
browser = 'Safari';
} else if (/Edge/i.test(userAgent)) {
browser = 'Edge';
} else if (/MSIE|Trident/i.test(userAgent)) {
browser = 'IE';
}
if (/Windows/i.test(userAgent)) {
os = 'Windows';
} else if (/Mac OS/i.test(userAgent)) {
os = 'macOS';
} else if (/Linux/i.test(userAgent)) {
os = 'Linux';
} else if (/Android/i.test(userAgent)) {
os = 'Android';
} else if (/iOS|iPhone|iPad|iPod/i.test(userAgent)) {
os = 'iOS';
}
return { device, browser, os };
};
const generateToken = (user) => {
return jwt.sign(
{
userId: user.userId,
username: user.username,
roleId: user.roleId
},
JWT_SECRET,
{ expiresIn: TOKEN_EXPIRY }
);
};
const verifyToken = (token) => {
try {
return jwt.verify(token, JWT_SECRET);
} catch (error) {
return null;
}
};
const authMiddleware = async (req, res, next) => {
try {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({
success: false,
message: '未提供认证令牌'
});
}
const token = authHeader.substring(7);
const decoded = verifyToken(token);
if (!decoded) {
return res.status(401).json({
success: false,
message: '令牌无效或已过期'
});
}
const user = await User.findByPk(decoded.userId);
if (!user) {
return res.status(401).json({
success: false,
message: '用户不存在'
});
}
if (user.status === 'locked') {
return res.status(403).json({
success: false,
message: '账户已被锁定'
});
}
if (user.status === 'inactive') {
return res.status(403).json({
success: false,
message: '账户已禁用'
});
}
req.user = decoded;
req.userModel = user;
next();
} catch (error) {
console.error('认证中间件错误:', error);
return res.status(500).json({
success: false,
message: '认证失败'
});
}
};
const optionalAuth = async (req, res, next) => {
try {
const authHeader = req.headers.authorization;
if (authHeader && authHeader.startsWith('Bearer ')) {
const token = authHeader.substring(7);
const decoded = verifyToken(token);
if (decoded) {
const user = await User.findByPk(decoded.userId);
if (user && user.status === 'active') {
req.user = decoded;
req.userModel = user;
}
}
}
next();
} catch (error) {
next();
}
};
module.exports = {
generateToken,
verifyToken,
authMiddleware,
optionalAuth,
JWT_SECRET,
TOKEN_EXPIRY
};
+57
View File
@@ -0,0 +1,57 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../db');
const LoginHistory = sequelize.define('LoginHistory', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
userId: {
type: DataTypes.STRING,
allowNull: false,
comment: '用户ID'
},
username: {
type: DataTypes.STRING,
allowNull: false,
comment: '用户名'
},
realName: {
type: DataTypes.STRING,
allowNull: true,
comment: '真实姓名'
},
loginTime: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
comment: '登录时间'
},
loginIp: {
type: DataTypes.STRING,
allowNull: true,
comment: '登录IP'
},
userAgent: {
type: DataTypes.TEXT,
allowNull: true,
comment: '浏览器UA'
},
loginType: {
type: DataTypes.ENUM('success', 'failed'),
defaultValue: 'success',
comment: '登录结果'
},
failReason: {
type: DataTypes.STRING,
allowNull: true,
comment: '失败原因'
}
}, {
tableName: 'login_histories',
timestamps: true,
createdAt: 'loginTime',
updatedAt: false
});
module.exports = LoginHistory;
+82
View File
@@ -0,0 +1,82 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../db');
const OperationLog = sequelize.define('OperationLog', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
userId: {
type: DataTypes.STRING,
allowNull: false,
comment: '操作人ID'
},
username: {
type: DataTypes.STRING,
allowNull: false,
comment: '操作人用户名'
},
realName: {
type: DataTypes.STRING,
allowNull: true,
comment: '操作人真实姓名'
},
action: {
type: DataTypes.STRING,
allowNull: false,
comment: '操作类型'
},
module: {
type: DataTypes.STRING,
allowNull: true,
comment: '操作模块'
},
description: {
type: DataTypes.TEXT,
allowNull: true,
comment: '操作描述'
},
targetId: {
type: DataTypes.STRING,
allowNull: true,
comment: '目标对象ID'
},
targetName: {
type: DataTypes.STRING,
allowNull: true,
comment: '目标对象名称'
},
oldValue: {
type: DataTypes.TEXT,
allowNull: true,
comment: '旧值'
},
newValue: {
type: DataTypes.TEXT,
allowNull: true,
comment: '新值'
},
ip: {
type: DataTypes.STRING,
allowNull: true,
comment: '操作IP'
},
status: {
type: DataTypes.ENUM('success', 'failed'),
defaultValue: 'success',
comment: '操作状态'
},
errorMessage: {
type: DataTypes.TEXT,
allowNull: true,
comment: '错误信息'
}
}, {
tableName: 'operation_logs',
timestamps: true,
createdAt: 'operateTime',
updatedAt: false
});
module.exports = OperationLog;
+48
View File
@@ -0,0 +1,48 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../db');
const Permission = sequelize.define('Permission', {
permissionId: {
type: DataTypes.STRING,
primaryKey: true,
allowNull: false
},
permissionName: {
type: DataTypes.STRING,
allowNull: false
},
permissionCode: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
parentId: {
type: DataTypes.STRING,
allowNull: true
},
type: {
type: DataTypes.ENUM('menu', 'button'),
defaultValue: 'button'
},
path: {
type: DataTypes.STRING,
allowNull: true
},
icon: {
type: DataTypes.STRING,
allowNull: true
},
sort: {
type: DataTypes.INTEGER,
defaultValue: 0
},
status: {
type: DataTypes.ENUM('active', 'inactive'),
defaultValue: 'active'
}
}, {
tableName: 'permissions',
timestamps: true
});
module.exports = Permission;
+40
View File
@@ -0,0 +1,40 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../db');
const Role = sequelize.define('Role', {
roleId: {
type: DataTypes.STRING,
primaryKey: true,
allowNull: false
},
roleName: {
type: DataTypes.STRING,
allowNull: false
},
roleCode: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
description: {
type: DataTypes.STRING,
allowNull: true
},
status: {
type: DataTypes.ENUM('active', 'inactive'),
defaultValue: 'active'
},
permissions: {
type: DataTypes.JSON,
defaultValue: []
},
sort: {
type: DataTypes.INTEGER,
defaultValue: 0
}
}, {
tableName: 'roles',
timestamps: true
});
module.exports = Role;
+63
View File
@@ -0,0 +1,63 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../db');
const User = sequelize.define('User', {
userId: {
type: DataTypes.STRING,
primaryKey: true,
allowNull: false
},
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: true,
validate: {
isEmail: true
}
},
phone: {
type: DataTypes.STRING,
allowNull: true
},
realName: {
type: DataTypes.STRING,
allowNull: true
},
avatar: {
type: DataTypes.STRING,
allowNull: true
},
status: {
type: DataTypes.ENUM('active', 'inactive', 'locked'),
defaultValue: 'active'
},
lastLoginTime: {
type: DataTypes.DATE,
allowNull: true
},
lastLoginIp: {
type: DataTypes.STRING,
allowNull: true
},
loginCount: {
type: DataTypes.INTEGER,
defaultValue: 0
},
remark: {
type: DataTypes.TEXT,
allowNull: true
}
}, {
tableName: 'users',
timestamps: true
});
module.exports = User;
+22
View File
@@ -0,0 +1,22 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../db');
const User = require('./User');
const Role = require('./Role');
const UserRole = sequelize.define('UserRole', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
}
}, {
tableName: 'user_roles',
timestamps: true
});
UserRole.belongsTo(User, { foreignKey: 'UserId', onDelete: 'CASCADE' });
UserRole.belongsTo(Role, { foreignKey: 'RoleId', onDelete: 'CASCADE' });
User.hasMany(UserRole, { foreignKey: 'UserId', onDelete: 'CASCADE' });
Role.hasMany(UserRole, { foreignKey: 'RoleId', onDelete: 'CASCADE' });
module.exports = UserRole;
+117
View File
@@ -8,6 +8,7 @@
"name": "idc-backend",
"version": "1.0.0",
"dependencies": {
"bcryptjs": "^3.0.3",
"cors": "^2.8.5",
"csv-parser": "^3.2.0",
"csv-writer": "^1.6.0",
@@ -16,6 +17,7 @@
"express": "^4.18.2",
"express-fileupload": "^1.5.2",
"iconv-lite": "^0.6.3",
"jsonwebtoken": "^9.0.3",
"mysql2": "^3.16.0",
"sequelize": "^6.32.1",
"sqlite3": "^5.1.6",
@@ -281,6 +283,15 @@
],
"license": "MIT"
},
"node_modules/bcryptjs": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz",
"integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==",
"license": "BSD-3-Clause",
"bin": {
"bcrypt": "bin/bcrypt"
}
},
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -398,6 +409,12 @@
"ieee754": "^1.1.13"
}
},
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
"license": "BSD-3-Clause"
},
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -761,6 +778,15 @@
"node": ">= 0.4"
}
},
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -1522,12 +1548,103 @@
"license": "ISC",
"optional": true
},
"node_modules/jsonwebtoken": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
"integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
"license": "MIT",
"dependencies": {
"jws": "^4.0.1",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
"semver": "^7.5.4"
},
"engines": {
"node": ">=12",
"npm": ">=6"
}
},
"node_modules/jsonwebtoken/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/jwa": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
"integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
"license": "MIT",
"dependencies": {
"buffer-equal-constant-time": "^1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jws": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
"integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
"license": "MIT",
"dependencies": {
"jwa": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT"
},
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
"license": "MIT"
},
"node_modules/lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
"license": "MIT"
},
"node_modules/lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
"license": "MIT"
},
"node_modules/lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
"license": "MIT"
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
"license": "MIT"
},
"node_modules/lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
"license": "MIT"
},
"node_modules/lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
"license": "MIT"
},
"node_modules/long": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
+2
View File
@@ -7,6 +7,7 @@
"dev": "nodemon server.js"
},
"dependencies": {
"bcryptjs": "^3.0.3",
"cors": "^2.8.5",
"csv-parser": "^3.2.0",
"csv-writer": "^1.6.0",
@@ -15,6 +16,7 @@
"express": "^4.18.2",
"express-fileupload": "^1.5.2",
"iconv-lite": "^0.6.3",
"jsonwebtoken": "^9.0.3",
"mysql2": "^3.16.0",
"sequelize": "^6.32.1",
"sqlite3": "^5.1.6",
+341
View File
@@ -0,0 +1,341 @@
const express = require('express');
const bcrypt = require('bcryptjs');
const User = require('../models/User');
const Role = require('../models/Role');
const UserRole = require('../models/UserRole');
const { generateToken, authMiddleware } = require('../middleware/auth');
const router = express.Router();
const SALT_ROUNDS = 10;
const generateId = () => {
return 'user_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 9);
};
router.post('/register', async (req, res) => {
try {
const { username, password, email, phone, realName } = req.body;
if (!username || !password) {
return res.status(400).json({
success: false,
message: '用户名和密码不能为空'
});
}
if (username.length < 3 || username.length > 20) {
return res.status(400).json({
success: false,
message: '用户名长度必须在3-20个字符之间'
});
}
if (password.length < 6) {
return res.status(400).json({
success: false,
message: '密码长度不能少于6个字符'
});
}
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 userCount = await User.count();
const isFirstUser = userCount === 0;
const user = await User.create({
userId: generateId(),
username,
password: hashedPassword,
email,
phone,
realName: realName || username,
status: 'active'
});
if (isFirstUser) {
let adminRole = await Role.findOne({ where: { roleCode: 'admin' } });
if (!adminRole) {
adminRole = await Role.create({
roleId: 'role_admin',
roleName: '管理员',
roleCode: 'admin',
description: '系统管理员,拥有所有权限',
status: 'active',
permissions: []
});
}
await UserRole.create({
UserId: user.userId,
RoleId: adminRole.roleId
});
} else {
const defaultRole = await Role.findOne({ where: { roleCode: 'viewer' } });
if (defaultRole) {
await UserRole.create({
UserId: user.userId,
RoleId: defaultRole.roleId
});
}
}
const token = generateToken(user);
res.status(201).json({
success: true,
message: isFirstUser ? '注册成功,已为您分配管理员权限' : '注册成功',
data: {
user: {
userId: user.userId,
username: user.username,
email: user.email,
realName: user.realName
},
token
}
});
} catch (error) {
console.error('注册错误:', error);
res.status(500).json({
success: false,
message: '注册失败',
error: error.message
});
}
});
router.post('/login', async (req, res) => {
try {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({
success: false,
message: '用户名和密码不能为空'
});
}
const user = await User.findOne({ where: { username } });
if (!user) {
return res.status(401).json({
success: false,
message: '用户名或密码错误'
});
}
if (user.status === 'locked') {
return res.status(403).json({
success: false,
message: '账户已被锁定,请联系管理员'
});
}
if (user.status === 'inactive') {
return res.status(403).json({
success: false,
message: '账户已禁用'
});
}
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) {
user.loginCount = (user.loginCount || 0) + 1;
if (user.loginCount >= 5) {
user.status = 'locked';
}
await user.save();
return res.status(401).json({
success: false,
message: '用户名或密码错误'
});
}
const token = generateToken(user);
user.lastLoginTime = new Date();
user.lastLoginIp = req.ip || req.connection.remoteAddress;
user.loginCount = 0;
await user.save();
res.json({
success: true,
message: '登录成功',
data: {
user: {
userId: user.userId,
username: user.username,
email: user.email,
realName: user.realName,
avatar: user.avatar
},
token
}
});
} catch (error) {
console.error('登录错误:', error);
res.status(500).json({
success: false,
message: '登录失败',
error: error.message
});
}
});
router.get('/profile', authMiddleware, async (req, res) => {
try {
const user = await User.findByPk(req.user.userId, {
attributes: { exclude: ['password'] }
});
if (!user) {
return res.status(404).json({
success: false,
message: '用户不存在'
});
}
const roles = await Role.findAll({
include: [{
model: User,
where: { userId: req.user.userId },
attributes: []
}]
});
res.json({
success: true,
data: {
user,
roles: roles.map(r => ({
roleId: r.roleId,
roleName: r.roleName,
roleCode: r.roleCode
}))
}
});
} catch (error) {
console.error('获取profile错误:', error);
res.status(500).json({
success: false,
message: '获取用户信息失败'
});
}
});
router.put('/profile', authMiddleware, async (req, res) => {
try {
const { email, phone, realName, avatar } = req.body;
const user = await User.findByPk(req.user.userId);
if (!user) {
return res.status(404).json({
success: false,
message: '用户不存在'
});
}
if (email !== undefined) user.email = email;
if (phone !== undefined) user.phone = phone;
if (realName !== undefined) user.realName = realName;
if (avatar !== undefined) user.avatar = avatar;
await user.save();
res.json({
success: true,
message: '更新成功',
data: {
userId: user.userId,
username: user.username,
email: user.email,
phone: user.phone,
realName: user.realName,
avatar: user.avatar
}
});
} catch (error) {
console.error('更新profile错误:', error);
res.status(500).json({
success: false,
message: '更新失败'
});
}
});
router.put('/password', authMiddleware, async (req, res) => {
try {
const { oldPassword, newPassword } = req.body;
if (!oldPassword || !newPassword) {
return res.status(400).json({
success: false,
message: '旧密码和新密码都不能为空'
});
}
if (newPassword.length < 6) {
return res.status(400).json({
success: false,
message: '新密码长度不能少于6个字符'
});
}
const user = await User.findByPk(req.user.userId);
const isPasswordValid = await bcrypt.compare(oldPassword, user.password);
if (!isPasswordValid) {
return res.status(401).json({
success: false,
message: '旧密码错误'
});
}
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.post('/check-admin', async (req, res) => {
try {
const userCount = await User.count();
res.json({
success: true,
data: {
hasAdmin: userCount > 0,
userCount
}
});
} catch (error) {
console.error('检查管理员错误:', error);
res.status(500).json({
success: false,
message: '检查失败'
});
}
});
module.exports = router;
+120
View File
@@ -0,0 +1,120 @@
const express = require('express');
const LoginHistory = require('../models/LoginHistory');
const User = require('../models/User');
const { authMiddleware } = require('../middleware/auth');
const router = express.Router();
router.get('/', authMiddleware, async (req, res) => {
try {
const { page = 1, pageSize = 10, userId, loginType, startDate, endDate } = req.query;
const offset = (parseInt(page) - 1) * parseInt(pageSize);
const limit = parseInt(pageSize);
const where = {};
if (userId) where.userId = userId;
if (loginType) where.loginType = loginType;
if (startDate || endDate) {
where.loginTime = {};
if (startDate) where.loginTime[Op.gte] = new Date(startDate);
if (endDate) where.loginTime[Op.lte] = new Date(endDate);
}
const { count, rows } = await LoginHistory.findAndCountAll({
where,
limit,
offset,
order: [['loginTime', 'DESC']]
});
res.json({
success: true,
data: {
total: count,
page: parseInt(page),
pageSize: parseInt(pageSize),
histories: rows.map(h => ({
id: h.id,
userId: h.userId,
username: h.username,
realName: h.realName,
loginTime: h.loginTime,
loginIp: h.loginIp,
userAgent: h.userAgent,
loginType: h.loginType,
failReason: h.failReason
}))
}
});
} catch (error) {
console.error('获取登录历史错误:', error);
res.status(500).json({
success: false,
message: '获取登录历史失败'
});
}
});
router.get('/user/:userId', authMiddleware, async (req, res) => {
try {
const { page = 1, pageSize = 10 } = req.query;
const offset = (parseInt(page) - 1) * parseInt(pageSize);
const limit = parseInt(pageSize);
const { count, rows } = await LoginHistory.findAndCountAll({
where: { userId: req.params.userId },
limit,
offset,
order: [['loginTime', 'DESC']]
});
res.json({
success: true,
data: {
total: count,
page: parseInt(page),
pageSize: parseInt(pageSize),
histories: rows
}
});
} catch (error) {
console.error('获取用户登录历史错误:', error);
res.status(500).json({
success: false,
message: '获取登录历史失败'
});
}
});
router.delete('/:id', authMiddleware, async (req, res) => {
try {
await LoginHistory.destroy({ where: { id: req.params.id } });
res.json({ success: true, message: '删除成功' });
} catch (error) {
console.error('删除登录历史错误:', error);
res.status(500).json({ success: false, message: '删除失败' });
}
});
router.delete('/', authMiddleware, async (req, res) => {
try {
const { days } = req.body;
const where = { loginType: 'success' };
if (days) {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - days);
where.loginTime = { [Op.lt]: cutoffDate };
}
await LoginHistory.destroy({ where });
res.json({ success: true, message: '清理成功' });
} catch (error) {
console.error('清理登录历史错误:', error);
res.status(500).json({ success: false, message: '清理失败' });
}
});
const { Op } = require('sequelize');
module.exports = router;
+162
View File
@@ -0,0 +1,162 @@
const express = require('express');
const OperationLog = require('../models/OperationLog');
const { authMiddleware } = require('../middleware/auth');
const router = express.Router();
const ACTION_TYPES = {
USER_CREATE: '创建用户',
USER_UPDATE: '修改用户',
USER_DELETE: '删除用户',
USER_LOGIN: '用户登录',
USER_LOGOUT: '用户登出',
ROLE_CREATE: '创建角色',
ROLE_UPDATE: '修改角色',
ROLE_DELETE: '删除角色',
ROLE_ASSIGN: '分配角色',
DEVICE_CREATE: '创建设备',
DEVICE_UPDATE: '修改设备',
DEVICE_DELETE: '删除设备',
CONSUMABLE_IN: '耗材入库',
CONSUMABLE_OUT: '耗材出库',
CONSUMABLE_RECORD: '耗材记录',
SYSTEM_CONFIG: '系统配置'
};
router.get('/', authMiddleware, async (req, res) => {
try {
const { page = 1, pageSize = 20, userId, action, module, startDate, endDate } = req.query;
const offset = (parseInt(page) - 1) * parseInt(pageSize);
const limit = parseInt(pageSize);
const where = {};
if (userId) where.userId = userId;
if (action) where.action = action;
if (module) where.module = module;
if (startDate || endDate) {
where.operateTime = {};
if (startDate) where.operateTime[Op.gte] = new Date(startDate);
if (endDate) where.operateTime[Op.lte] = new Date(endDate);
}
const { count, rows } = await OperationLog.findAndCountAll({
where,
limit,
offset,
order: [['operateTime', 'DESC']]
});
res.json({
success: true,
data: {
total: count,
page: parseInt(page),
pageSize: parseInt(pageSize),
logs: rows.map(l => ({
id: l.id,
userId: l.userId,
username: l.username,
realName: l.realName,
action: l.action,
module: l.module,
description: l.description,
targetId: l.targetId,
targetName: l.targetName,
oldValue: l.oldValue,
newValue: l.newValue,
ip: l.ip,
status: l.status,
errorMessage: l.errorMessage,
operateTime: l.operateTime
}))
}
});
} catch (error) {
console.error('获取操作日志错误:', error);
res.status(500).json({
success: false,
message: '获取操作日志失败'
});
}
});
router.get('/actions', authMiddleware, (req, res) => {
res.json({
success: true,
data: Object.entries(ACTION_TYPES).map(([key, value]) => ({
key,
value,
label: value
}))
});
});
router.get('/modules', authMiddleware, (req, res) => {
res.json({
success: true,
data: [
{ key: 'user', value: 'user', label: '用户管理' },
{ key: 'role', value: 'role', label: '角色管理' },
{ key: 'device', value: 'device', label: '设备管理' },
{ key: 'consumable', value: 'consumable', label: '耗材管理' },
{ key: 'system', value: 'system', label: '系统设置' }
]
});
});
router.delete('/:id', authMiddleware, async (req, res) => {
try {
await OperationLog.destroy({ where: { id: req.params.id } });
res.json({ success: true, message: '删除成功' });
} catch (error) {
console.error('删除操作日志错误:', error);
res.status(500).json({ success: false, message: '删除失败' });
}
});
router.delete('/', authMiddleware, async (req, res) => {
try {
const { days } = req.body;
const where = {};
if (days) {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - days);
where.operateTime = { [Op.lt]: cutoffDate };
}
await OperationLog.destroy({ where });
res.json({ success: true, message: '清理成功' });
} catch (error) {
console.error('清理操作日志错误:', error);
res.status(500).json({ success: false, message: '清理失败' });
}
});
const logOperation = async (req, action, module, description, targetId, targetName, oldValue, newValue, status = 'success', errorMessage = null) => {
try {
await OperationLog.create({
userId: req.user?.userId,
username: req.user?.username,
realName: req.userModel?.realName,
action,
module,
description,
targetId,
targetName,
oldValue: oldValue ? JSON.stringify(oldValue) : null,
newValue: newValue ? JSON.stringify(newValue) : null,
ip: req.ip,
status,
errorMessage
});
} catch (error) {
console.error('记录操作日志错误:', error);
}
};
module.exports = router;
module.exports.ACTION_TYPES = ACTION_TYPES;
module.exports.logOperation = logOperation;
const { Op } = require('sequelize');
+275
View File
@@ -0,0 +1,275 @@
const express = require('express');
const Role = require('../models/Role');
const Permission = require('../models/Permission');
const UserRole = require('../models/UserRole');
const User = require('../models/User');
const { authMiddleware } = require('../middleware/auth');
const router = express.Router();
const generateId = () => {
return 'role_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 9);
};
const { Op } = require('sequelize');
router.get('/', authMiddleware, async (req, res) => {
try {
const { page = 1, pageSize = 10, roleName, status } = req.query;
const offset = (parseInt(page) - 1) * parseInt(pageSize);
const limit = parseInt(pageSize);
const where = {};
if (roleName) {
where.roleName = { [Op.like]: `%${roleName}%` };
}
if (status) {
where.status = status;
}
const { count, rows: roles } = await Role.findAndCountAll({
where,
limit,
offset,
order: [['sort', 'ASC'], ['createdAt', 'DESC']]
});
res.json({
success: true,
data: {
total: count,
page: parseInt(page),
pageSize: parseInt(pageSize),
roles
}
});
} catch (error) {
console.error('获取角色列表错误:', error);
res.status(500).json({
success: false,
message: '获取角色列表失败'
});
}
});
router.get('/all', authMiddleware, async (req, res) => {
try {
const roles = await Role.findAll({
where: { status: 'active' },
order: [['sort', 'ASC']]
});
res.json({
success: true,
data: roles
});
} catch (error) {
console.error('获取所有角色错误:', error);
res.status(500).json({
success: false,
message: '获取角色列表失败'
});
}
});
router.get('/:roleId', authMiddleware, async (req, res) => {
try {
const role = await Role.findByPk(req.params.roleId);
if (!role) {
return res.status(404).json({
success: false,
message: '角色不存在'
});
}
const permissions = await Permission.findAll({
where: { status: 'active' },
order: [['sort', 'ASC']]
});
res.json({
success: true,
data: {
role,
permissions,
rolePermissions: role.permissions || []
}
});
} catch (error) {
console.error('获取角色详情错误:', error);
res.status(500).json({
success: false,
message: '获取角色详情失败'
});
}
});
router.post('/', authMiddleware, async (req, res) => {
try {
const { roleName, roleCode, description, permissions, status, sort } = req.body;
if (!roleName || !roleCode) {
return res.status(400).json({
success: false,
message: '角色名称和角色编码不能为空'
});
}
const existingRole = await Role.findOne({ where: { roleCode } });
if (existingRole) {
return res.status(400).json({
success: false,
message: '角色编码已存在'
});
}
const role = await Role.create({
roleId: generateId(),
roleName,
roleCode,
description,
permissions: permissions || [],
status: status || 'active',
sort: sort || 0
});
res.status(201).json({
success: true,
message: '创建成功',
data: role
});
} catch (error) {
console.error('创建角色错误:', error);
res.status(500).json({
success: false,
message: '创建角色失败'
});
}
});
router.put('/:roleId', authMiddleware, async (req, res) => {
try {
const { roleName, description, permissions, status, sort } = req.body;
const role = await Role.findByPk(req.params.roleId);
if (!role) {
return res.status(404).json({
success: false,
message: '角色不存在'
});
}
if (roleName !== undefined) role.roleName = roleName;
if (description !== undefined) role.description = description;
if (permissions !== undefined) role.permissions = permissions;
if (status !== undefined) role.status = status;
if (sort !== undefined) role.sort = sort;
await role.save();
res.json({
success: true,
message: '更新成功',
data: role
});
} catch (error) {
console.error('更新角色错误:', error);
res.status(500).json({
success: false,
message: '更新角色失败'
});
}
});
router.delete('/:roleId', authMiddleware, async (req, res) => {
try {
const role = await Role.findByPk(req.params.roleId);
if (!role) {
return res.status(404).json({
success: false,
message: '角色不存在'
});
}
if (role.roleCode === 'admin') {
return res.status(400).json({
success: false,
message: '不能删除管理员角色'
});
}
const userCount = await UserRole.count({ where: { RoleId: role.roleId } });
if (userCount > 0) {
return res.status(400).json({
success: false,
message: '该角色下有用户,不能删除'
});
}
await role.destroy();
res.json({
success: true,
message: '删除成功'
});
} catch (error) {
console.error('删除角色错误:', error);
res.status(500).json({
success: false,
message: '删除角色失败'
});
}
});
router.post('/init-roles', async (req, res) => {
try {
const defaultRoles = [
{
roleId: 'role_admin',
roleName: '管理员',
roleCode: 'admin',
description: '系统管理员,拥有所有权限',
permissions: ['*'],
status: 'active',
sort: 1
},
{
roleId: 'role_operator',
roleName: '运维人员',
roleCode: 'operator',
description: '负责日常运维操作',
permissions: ['devices:read', 'devices:write', 'racks:read', 'rooms:read', 'consumables:read', 'consumables:write'],
status: 'active',
sort: 2
},
{
roleId: 'role_viewer',
roleName: '只读用户',
roleCode: 'viewer',
description: '仅能查看数据',
permissions: ['devices:read', 'racks:read', 'rooms:read', 'consumables:read'],
status: 'active',
sort: 3
}
];
for (const roleData of defaultRoles) {
await Role.upsert(roleData);
}
res.json({
success: true,
message: '初始化角色成功'
});
} catch (error) {
console.error('初始化角色错误:', error);
res.status(500).json({
success: false,
message: '初始化角色失败'
});
}
});
module.exports = router;
+450
View File
@@ -0,0 +1,450 @@
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 router = express.Router();
const SALT_ROUNDS = 10;
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 { Op } = require('sequelize');
router.get('/', authMiddleware, async (req, res) => {
try {
const { page = 1, pageSize = 10, username, status, realName } = req.query;
const offset = (parseInt(page) - 1) * parseInt(pageSize);
const limit = parseInt(pageSize);
const where = getWhereClause({ username, status, realName });
const { count, rows: users } = await User.findAndCountAll({
where,
attributes: { exclude: ['password'] },
limit,
offset,
order: [['createdAt', 'DESC']]
});
for (const user of users) {
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: {
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
});
}
}
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: '用户不存在'
});
}
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 >= 6) {
user.password = await bcrypt.hash(newPassword, SALT_ROUNDS);
}
await user.save();
if (roleIds !== undefined) {
await UserRole.destroy({ where: { UserId: user.userId } });
for (const roleId of roleIds) {
await UserRole.create({
UserId: user.userId,
RoleId: roleId
});
}
}
const updatedUser = await User.findByPk(req.params.userId, {
attributes: { exclude: ['password'] }
});
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: '用户不存在'
});
}
if (!newPassword || newPassword.length < 6) {
return res.status(400).json({
success: false,
message: '密码长度不能少于6个字符'
});
}
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: '不能删除当前登录用户'
});
}
await UserRole.destroy({ where: { UserId: user.userId } });
await user.destroy();
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 格式的图片'
});
}
if (avatarFile.size > 5 * 1024 * 1024) {
return res.status(400).json({
success: false,
message: '图片大小不能超过 5MB'
});
}
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: '删除头像失败'
});
}
});
module.exports = router;
+10
View File
@@ -42,6 +42,11 @@ const backgroundRoutes = require('./routes/background');
const consumableRoutes = require('./routes/consumables');
const consumableRecordRoutes = require('./routes/consumableRecords');
const consumableCategoryRoutes = require('./routes/consumableCategories');
const authRoutes = require('./routes/auth');
const usersRoutes = require('./routes/users');
const rolesRoutes = require('./routes/roles');
const loginHistoryRoutes = require('./routes/loginHistory');
const operationLogsRoutes = require('./routes/operationLogs');
// 使用路由
app.use('/api/devices', deviceRoutes);
@@ -52,6 +57,11 @@ app.use('/api/background', backgroundRoutes);
app.use('/api/consumables', consumableRoutes);
app.use('/api/consumable-records', consumableRecordRoutes);
app.use('/api/consumable-categories', consumableCategoryRoutes);
app.use('/api/auth', authRoutes);
app.use('/api/users', usersRoutes);
app.use('/api/roles', rolesRoutes);
app.use('/api/login-history', loginHistoryRoutes);
app.use('/api/operation-logs', operationLogsRoutes);
// 静态文件服务
app.use('/uploads', express.static('uploads'));
-13
View File
@@ -1,13 +0,0 @@
$postData = @{
consumableId = "CON1766493042245"
type = "in"
quantity = 5
operator = "测试管理员"
reason = "测试入库"
notes = "测试日志记录功能"
} | ConvertTo-Json
$response = Invoke-RestMethod -Uri "http://localhost:8000/api/consumables/quick-inout" -Method Post -Body $postData -ContentType "application/json"
Write-Host "入库API响应:"
$response | ConvertTo-Json -Depth 5
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB