fix(健壮性): Java OOM 三处 + 双TE 502 根因 + admin-vue 403 白屏

Java:
- similarasin Excel 解析改 EasyExcel 流式(原 WorkbookFactory 全量 DOM,大表 OOM)+ 魔数校验
- collectdata 结果组装改游标分批 + 导入改流式(原全量驻留内存)
- GlobalExceptionHandler 转发响应过滤逐跳头与实例标识头(双 Transfer-Encoding 导致 nginx 502 的根因)
admin-vue:
- 403(无后台权限,如工具号 token)与 401 同样跳登录页,修复后台白屏
- task-266 测试断言对齐 daily-files 端点演进
This commit is contained in:
2026-09-13 12:58:49 +08:00
parent 2a766efb83
commit 8cd8390d95
8 changed files with 473 additions and 192 deletions
+7
View File
@@ -46,6 +46,13 @@ export function isUnauthorized(payload: unknown): boolean {
return [record.status, record.statusCode, record.code].some((v) => v === 401)
}
/** 负载/状态码是否 403(已登录但无后台权限)。 */
export function isForbidden(payload: unknown): boolean {
const record = payload as { status?: unknown; statusCode?: unknown; code?: unknown } | null
if (!record || typeof record !== 'object') return false
return [record.status, record.statusCode, record.code].some((v) => v === 403)
}
/** Axios 错误(带 response)或普通 Error 统一取可展示文案。 */
export function requestErrorMessage(error: unknown): string {
const response = (error as { response?: { data?: unknown; status?: number } })?.response
+10 -4
View File
@@ -1,5 +1,5 @@
import axios from 'axios'
import { isUnauthorized, loginRedirectTarget, shouldRedirectUnauthorized } from './envelope'
import { isForbidden, isUnauthorized, loginRedirectTarget, shouldRedirectUnauthorized } from './envelope'
export { unwrap } from './envelope'
@@ -19,12 +19,18 @@ function redirectToLogin(requestUrl?: string): void {
http.interceptors.response.use(
(response) => {
// Java 未登录以 HTTP 200 + body.code=401 返回,需统一按未登录处理
if (isUnauthorized(response.data)) redirectToLogin(response.config?.url)
// Java 未登录以 HTTP 200 + body.code=401 返回,需统一按未登录处理
// 403(已登录但无后台权限,如用工具前端账号 token 访问后台)与 401 同样跳登录页。
if (isUnauthorized(response.data) || isForbidden(response.data)) redirectToLogin(response.config?.url)
return response
},
(error) => {
if (error?.response?.status === 401 || isUnauthorized(error?.response?.data)) {
if (
error?.response?.status === 401 ||
error?.response?.status === 403 ||
isUnauthorized(error?.response?.data) ||
isForbidden(error?.response?.data)
) {
redirectToLogin(error?.config?.url)
}
return Promise.reject(error)
+5 -4
View File
@@ -18,9 +18,10 @@ test('test_task_266_view_normal_primary_path', () => {
test('test_task_266_view_normal_variant_input', () => {
const api = readSource('src/pages/tasks/shop-data-api.ts')
assert.match(api, /fetchShopDataResultDownload/, '单文件下载适配')
assert.match(api, /\/results\/\$\{resultId\}\/download/, '单文件下载走管理端真实端点')
// 端点演进:单文件下载/删除改走每日累计档 daily-files(与 AdminShopDataCrawlTasksController 对齐)。
assert.match(api, /daily-files\/\$\{resultId\}\/download/, '单文件下载走管理端真实端点')
assert.match(api, /deleteShopDataResultHistory/, '删除适配')
assert.match(api, /\/history\/\$\{resultId\}/, '删除走管理端真实端点')
assert.match(api, /daily-files\/\$\{resultId\}/, '删除走管理端真实端点')
})
test('test_task_266_view_normal_repeated_operation_is_idempotent', () => {
@@ -44,13 +45,13 @@ test('test_task_266_view_boundary_single_item', () => {
test('test_task_266_delete_normal_primary_path', () => {
const page = readSource('src/pages/tasks/ShopDataTasksPage.vue')
assert.match(page, /确认删除店铺/, '删除确认含店铺')
assert.match(page, /及结果文件/, '删除确认含结果文件')
assert.match(page, /及其数据文件/, '删除确认含数据文件')
assert.match(page, /删除成功/, '删除成功提示')
})
test('test_task_266_delete_boundary_limit_or_missing_field', () => {
const page = readSource('src/pages/tasks/ShopDataTasksPage.vue')
assert.match(page, /正在删除任务/, '删除中有进行文案')
assert.match(page, /deletingKey/, '删除中状态防重复点击')
})
test('test_task_266_dependency_failure_returns_actionable_message', () => {