fix: 前端深度审计全量修复 — 安全/功能/代码质量
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

严重 BUG 修复:
- 修复 Token 过期后 hash 重定向导致无法跳转登录页
- 修复文章编辑器新建后提交审核使用错误 ID

安全加固:
- HTML 清理函数替换为 ammonia 专业库(替代自定义解析器)
- 文件上传添加 magic bytes 校验(防 Content-Type 伪造)
- 登录添加账户级失败锁定(5次失败→15分钟锁定)
- 审计日志 9 个关键更新操作补充变更前后值(with_changes)

功能缺陷修复:
- 登录/登出时清理 API 缓存(防多账户数据污染)
- 文章编辑器上传改用统一 HTTP 客户端(自动 token 刷新)
- 添加全局 HTTP 错误处理和后端错误消息展示
- PrivateRoute 增加路由级权限检查(系统管理页面)
- 健康数据三个 Tab 添加编辑/删除功能
- 预约创建增加排班可用性校验提示
- 医生详情 API 返回解密后的原始执照号

代码清理:
- 删除未使用的 auth.ts refresh() 函数
- 删除重复的 AuthGuard.tsx 组件
- 删除未使用的 getHealthSummary API
This commit is contained in:
iven
2026-04-26 21:47:26 +08:00
parent f0c3426792
commit 787e64d9a9
23 changed files with 1152 additions and 482 deletions

View File

@@ -1,4 +1,5 @@
import axios from 'axios';
import { message as antMessage } from 'antd';
// 请求缓存:短时间内相同请求复用结果
interface CacheEntry {
@@ -138,7 +139,7 @@ client.interceptors.response.use(
processQueue(refreshError, null);
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
window.location.hash = '#/login';
window.location.hash = '/login';
return Promise.reject(refreshError);
} finally {
isRefreshing = false;
@@ -149,6 +150,32 @@ client.interceptors.response.use(
}
);
// 全局错误提示(仅对未被组件处理的错误显示)
let globalErrorTimer: ReturnType<typeof setTimeout> | null = null;
function showGlobalError(msg: string) {
// 防止短时间内弹出大量相同提示
if (globalErrorTimer) return;
antMessage.error(msg, 3);
globalErrorTimer = setTimeout(() => { globalErrorTimer = null; }, 3000);
}
// 全局错误拦截 — 在响应拦截器之后、组件 catch 之前执行
client.interceptors.response.use(
(response) => response,
(error) => {
if (!error.response) {
showGlobalError('网络连接异常,请检查网络');
} else if (error.response.status === 403) {
showGlobalError('权限不足,无法执行此操作');
} else if (error.response.status === 404) {
// 404 通常由组件自行处理(如跳转),不全局提示
} else if (error.response.status >= 500) {
showGlobalError('服务器异常,请稍后重试');
}
return Promise.reject(error);
}
);
let isRefreshing = false;
let failedQueue: Array<{
resolve: (token: string) => void;
@@ -168,4 +195,12 @@ export function clearApiCache() {
requestCache.clear();
}
// 通用错误处理:提取后端错误消息并展示
export function handleApiError(err: unknown, fallback = '操作失败'): string {
const msg =
(err as { response?: { data?: { message?: string } } })?.response?.data?.message || fallback;
antMessage.error(msg);
return msg;
}
export default client;