refactor(frontend): 重构状态管理使用Zustand替代Context API

feat(auth): 添加账户锁定功能及自动解锁机制
feat(user): 在用户模型中添加lockedUntil字段
feat(api): 实现账户锁定逻辑和剩余尝试次数提示

perf(3d): 优化3D场景状态管理性能
perf(floorplan): 优化平面图状态管理性能

chore(deps): 添加zustand依赖
chore(config): 更新安全配置锁定时间为3分钟

docs: 更新部分组件注释
style: 调整登录页面样式
This commit is contained in:
zhang1106
2026-05-08 11:17:29 +08:00
parent 03e320af9e
commit 2826a00192
33 changed files with 2026 additions and 1442 deletions
+24 -5
View File
@@ -8,6 +8,7 @@ const { generateToken, authMiddleware } = require('../middleware/auth');
const {
SALT_ROUNDS,
MAX_LOGIN_ATTEMPTS,
LOCK_TIME,
PASSWORD_MIN_LENGTH,
USERNAME_MIN_LENGTH,
USERNAME_MAX_LENGTH,
@@ -154,10 +155,18 @@ router.post('/login', async (req, res) => {
}
if (user.status === 'locked') {
return res.status(403).json({
success: false,
message: '账户已被锁定,请联系管理员',
});
const now = new Date();
if (user.lockedUntil && user.lockedUntil > now) {
const remainingMinutes = Math.ceil((user.lockedUntil - now) / 60000);
return res.status(403).json({
success: false,
message: `账户已被锁定,请在 ${remainingMinutes} 分钟后重试`,
});
}
user.status = 'active';
user.loginCount = 0;
user.lockedUntil = null;
await user.save();
}
if (user.status === 'inactive') {
@@ -180,12 +189,21 @@ router.post('/login', async (req, res) => {
user.loginCount = (user.loginCount || 0) + 1;
if (user.loginCount >= MAX_LOGIN_ATTEMPTS) {
user.status = 'locked';
user.lockedUntil = new Date(Date.now() + LOCK_TIME);
}
await user.save();
const remainingAttempts = MAX_LOGIN_ATTEMPTS - user.loginCount;
let message = '用户名或密码错误';
if (remainingAttempts > 0) {
message += `,剩余 ${remainingAttempts} 次尝试机会`;
} else {
message = `账户已被锁定,请在 3 分钟后重试`;
}
return res.status(401).json({
success: false,
message: '用户名或密码错误',
message,
});
}
@@ -201,6 +219,7 @@ router.post('/login', async (req, res) => {
user.lastLoginTime = new Date();
user.lastLoginIp = req.ip || req.connection.remoteAddress;
user.loginCount = 0;
user.lockedUntil = null;
await user.save();
res.json({