feat(imLoginFailureLog): 用户登录密码错误次数锁定与解锁能力(管理端)

- 安全策略登录限频由 JSON 文本改为结构化表单(次数/锁定分钟/窗口分钟),编辑校验同步扩展
- 失败日志新增解锁入口:/imLoginFailureLog/unlock 经 internal 通道调用 im-server 释放锁定
- 失败日志模型补充 accountHash 字段,解锁按钮按 risk_action=account_locked 展示
- 租户业务库初始化模板补充 im_login_lockouts 等表;新增解锁按钮菜单权限 SQL
This commit is contained in:
Austin
2026-09-20 17:04:19 +08:00
parent fd4ff85191
commit 8675fe333d
13 changed files with 238 additions and 19 deletions
+22 -3
View File
@@ -79,7 +79,7 @@
import { BasicTable, TableAction } from '@/components/Table';
import { usePermission } from '@/hooks/web/usePermission';
import { adaTableScrollX } from '@/utils/hotgo';
import { AddBlacklist, BanUser, Export, List, View } from '@/api/imLoginFailureLog';
import { AddBlacklist, BanUser, Export, List, Unlock, View } from '@/api/imLoginFailureLog';
import { imQueryTenantOptions, loadImQueryTenantOptions } from '@/views/imAdminQueryShared';
import { columns, loadOptions, schemas } from './model';
@@ -119,6 +119,13 @@
ifShow: () => Number(record.userId) > 0,
onClick: () => openBan(record),
},
{
label: '解锁',
type: 'primary',
auth: ['/imLoginFailureLog/unlock'],
ifShow: () => record.riskAction === 'account_locked' && !!record.accountHash,
onClick: () => unlockLockout(record),
},
],
});
},
@@ -131,8 +138,7 @@
});
const loadDataTable = async (res) => {
const params = { ...searchFormRef.value?.formModel, ...res };
if (!params.tenantId)
return { list: [], page: 1, pageCount: 0, totalCount: 0 };
if (!params.tenantId) return { list: [], page: 1, pageCount: 0, totalCount: 0 };
return List(params);
};
function reloadTable() {
@@ -177,6 +183,19 @@
banReason.value = '';
showBan.value = true;
}
function unlockLockout(record) {
dialog.warning({
title: '解锁账号',
content: `确认解除 ${record.accountMask || '该账号'} 的登录锁定吗?解锁后可立即重新登录。`,
positiveText: '确认解锁',
negativeText: '取消',
onPositiveClick: async () => {
await Unlock({ tenantId: record.tenantId, accountHash: record.accountHash });
message.success('已解锁');
reloadTable();
},
});
}
async function submitBan() {
if (!banReason.value.trim()) {
message.warning('请输入封禁原因');