refactor: 重写系统设置模块,新增维护模式与登录策略功能

- 移除大量无实际效果的冗余配置项
- 新增维护模式拦截逻辑,支持管理员豁免
- 重构登录失败次数限制,从配置动态读取阈值
- 新增站点Logo支持与配置管理
- 优化用户管理页面数据拉取逻辑
- 新增端口批量创建范围模式功能
- 更新依赖包版本与缓存策略
This commit is contained in:
zhang1106
2026-06-12 14:16:29 +08:00
parent 170997ef61
commit 85d6b2c633
19 changed files with 1699 additions and 1600 deletions
+38 -6
View File
@@ -7,12 +7,11 @@ const UserRole = require('../models/UserRole');
const { generateToken, authMiddleware } = require('../middleware/auth');
const {
SALT_ROUNDS,
MAX_LOGIN_ATTEMPTS,
LOCK_TIME,
PASSWORD_MIN_LENGTH,
USERNAME_MIN_LENGTH,
USERNAME_MAX_LENGTH,
} = require('../config');
const SystemSetting = require('../models/SystemSetting');
const { generateId } = require('../utils/idGenerator');
const router = express.Router();
@@ -146,6 +145,38 @@ router.post('/login', async (req, res) => {
});
}
// 检查维护模式:非管理员无法登录
const maintenanceSetting = await SystemSetting.findByPk('maintenance_mode');
const isMaintenanceMode = maintenanceSetting
? JSON.parse(maintenanceSetting.settingValue)
: false;
if (isMaintenanceMode) {
// 先查找用户,判断是否为管理员
const checkUser = await User.findOne({ where: { username } });
if (checkUser) {
const userRole = await UserRole.findOne({
where: { UserId: checkUser.userId },
include: [{ model: Role }],
});
const isAdmin = userRole && userRole.Role && userRole.Role.roleCode === 'admin';
if (!isAdmin) {
return res.status(503).json({
success: false,
code: 'MAINTENANCE_MODE',
message: '系统维护中,暂时无法登录,请联系管理员',
});
}
}
}
// 从系统设置读取最大登录尝试次数
const maxAttemptsSetting = await SystemSetting.findByPk('max_login_attempts');
const maxLoginAttempts = maxAttemptsSetting
? JSON.parse(maxAttemptsSetting.settingValue)
: 5;
const lockTimeMs = 30 * 60 * 1000; // 锁定30分钟
const user = await User.findOne({ where: { username } });
if (!user) {
return res.status(401).json({
@@ -163,6 +194,7 @@ router.post('/login', async (req, res) => {
message: `账户已被锁定,请在 ${remainingMinutes} 分钟后重试`,
});
}
// 锁定时间已过,自动解锁
user.status = 'active';
user.loginCount = 0;
user.lockedUntil = null;
@@ -187,18 +219,18 @@ router.post('/login', async (req, res) => {
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) {
user.loginCount = (user.loginCount || 0) + 1;
if (user.loginCount >= MAX_LOGIN_ATTEMPTS) {
if (user.loginCount >= maxLoginAttempts) {
user.status = 'locked';
user.lockedUntil = new Date(Date.now() + LOCK_TIME);
user.lockedUntil = new Date(Date.now() + lockTimeMs);
}
await user.save();
const remainingAttempts = MAX_LOGIN_ATTEMPTS - user.loginCount;
const remainingAttempts = maxLoginAttempts - user.loginCount;
let message = '用户名或密码错误';
if (remainingAttempts > 0) {
message += `,剩余 ${remainingAttempts} 次尝试机会`;
} else {
message = `账户已被锁定,请在 3 分钟后重试`;
message = `账户已被锁定,请在 30 分钟后重试`;
}
return res.status(401).json({
+19 -93
View File
@@ -4,7 +4,7 @@ const router = express.Router();
const fs = require('fs');
const path = require('path');
const { Op } = require('sequelize');
const { authMiddleware } = require('../middleware/auth');
const { authMiddleware, clearMaintenanceCache } = require('../middleware/auth');
// 读取 package.json 获取版本号
const packageJsonPath = path.join(__dirname, '../../package.json');
@@ -28,7 +28,7 @@ const { FRONTEND } = require('../config');
// 初始化默认系统设置
const initDefaultSettings = async () => {
const defaultSettings = [
// 全局配置
// 基本设置 - 站点信息
{
settingKey: 'site_name',
settingValue: JSON.stringify('机柜管理系统'),
@@ -45,30 +45,8 @@ const initDefaultSettings = async () => {
description: '网站Logo URL',
isEditable: true,
},
{
settingKey: 'timezone',
settingValue: JSON.stringify('Asia/Shanghai'),
settingType: 'string',
category: 'general',
description: '时区设置',
isEditable: true,
},
{
settingKey: 'date_format',
settingValue: JSON.stringify('YYYY-MM-DD'),
settingType: 'string',
category: 'general',
description: '日期格式',
isEditable: true,
},
{
settingKey: 'session_timeout',
settingValue: JSON.stringify(30),
settingType: 'number',
category: 'general',
description: '登录有效期(分钟)',
isEditable: true,
},
// 基本设置 - 安全设置
{
settingKey: 'idle_timeout',
settingValue: JSON.stringify(30),
@@ -77,14 +55,6 @@ const initDefaultSettings = async () => {
description: '用户空闲超时时间(分钟)',
isEditable: true,
},
{
settingKey: 'idle_warning_time',
settingValue: JSON.stringify(60),
settingType: 'number',
category: 'general',
description: '空闲超时前警告时间(秒)',
isEditable: false,
},
{
settingKey: 'max_login_attempts',
settingValue: JSON.stringify(5),
@@ -119,38 +89,6 @@ const initDefaultSettings = async () => {
description: '主题辅助色调',
isEditable: true,
},
{
settingKey: 'compact_mode',
settingValue: JSON.stringify(false),
settingType: 'boolean',
category: 'appearance',
description: '紧凑模式',
isEditable: true,
},
{
settingKey: 'sidebar_collapsed',
settingValue: JSON.stringify(false),
settingType: 'boolean',
category: 'appearance',
description: '侧边栏默认折叠',
isEditable: true,
},
{
settingKey: 'table_row_height',
settingValue: JSON.stringify('default'),
settingType: 'string',
category: 'appearance',
description: '表格行高: small/default/middle/large',
isEditable: true,
},
{
settingKey: 'animation_enabled',
settingValue: JSON.stringify(true),
settingType: 'boolean',
category: 'appearance',
description: '启用动画效果',
isEditable: true,
},
// 关于页面
{
@@ -201,22 +139,6 @@ const initDefaultSettings = async () => {
description: '系统描述',
isEditable: true,
},
{
settingKey: 'privacy_policy',
settingValue: JSON.stringify(''),
settingType: 'string',
category: 'about',
description: '隐私政策URL',
isEditable: true,
},
{
settingKey: 'terms_of_service',
settingValue: JSON.stringify(''),
settingType: 'string',
category: 'about',
description: '服务条款URL',
isEditable: true,
},
];
let createdCount = 0;
@@ -361,6 +283,11 @@ router.put('/:key', async (req, res) => {
settingValue: JSON.stringify(parsedValue),
});
// 如果更新了维护模式设置,清除缓存
if (key === 'maintenance_mode') {
clearMaintenanceCache();
}
res.json({
message: '设置更新成功',
setting: {
@@ -426,6 +353,11 @@ router.put('/', async (req, res) => {
updatedSettings,
errors: errors.length > 0 ? errors : undefined,
});
// 如果更新了维护模式设置,清除缓存
if ('maintenance_mode' in settings) {
clearMaintenanceCache();
}
} catch (error) {
res.status(500).json({ error: error.message });
}
@@ -444,27 +376,16 @@ router.post('/reset/:key', async (req, res) => {
const defaultValues = {
site_name: '机柜管理系统',
site_logo: '',
timezone: 'Asia/Shanghai',
date_format: 'YYYY-MM-DD',
session_timeout: 30,
idle_timeout: 30,
idle_warning_time: 60,
max_login_attempts: 5,
maintenance_mode: false,
frontend_port: FRONTEND.DEFAULT_PORT,
primary_color: '#667eea',
secondary_color: '#764ba2',
compact_mode: false,
sidebar_collapsed: false,
table_row_height: 'default',
animation_enabled: true,
company_name: '',
contact_email: '',
contact_phone: '',
company_address: '',
system_description: '机柜管理系统 - 专业的数据中心设备管理解决方案',
privacy_policy: '',
terms_of_service: '',
};
const defaultValue = defaultValues[key];
@@ -476,6 +397,11 @@ router.post('/reset/:key', async (req, res) => {
settingValue: JSON.stringify(defaultValue),
});
// 如果重置了维护模式设置,清除缓存
if (key === 'maintenance_mode') {
clearMaintenanceCache();
}
res.json({
message: '设置已重置为默认值',
key,