cc 3 hete
szülő
commit
aa6abaac17

+ 27 - 0
.workbuddy/memory/2026-09-16.md

@@ -107,3 +107,30 @@ SKILL.md 里写了完整 9 步修复流程与本机编译方式。
 审计覆盖率自查法:统计前端 `defHttp.(postJson|post|put|patch|uploadFile|request)` 总数(本次 174)与解析出的调用点数(171),
 差值必须能逐个解释(本次 3 个全是注释里的假命中)。
 
+---
+
+## 错误提示带上接口地址(2026-09-16 晚,用户要求)
+
+需求:「错误提示加上访问接口地址」→ 所有接口报错时,提示里要能一眼看出是哪个接口。
+
+### 改动(前端 3 文件,`ai-frontend/src/core/utils/http/axios/`)
+| 文件 | 改动 |
+| --- | --- |
+| `helper.ts` | 新增 `appendApiUrl(message, url)`:`'接口请求出错' + '/js/a/dm/doClean'` → `'接口请求出错(/js/a/dm/doClean)'`;url 为空或 message 已含该地址时不重复追加 |
+| `index.ts` | ① `transformRequestHook` 取 `res.config.url`,业务错误(`code !== 200`,含 401 与 `skipErrorMessage` 分支)统一走 `appendApiUrl`;② `responseInterceptorsCatch` 的 `urlPath` 增加 `|| config?.url` 兜底(超时/网络异常时没有 `response`,原来会取到空串);③ 超时/网络异常/`ERR_BAD_RESPONSE` 的 toast 带地址;④ `checkStatus` 调用传入 `urlPath` |
+| `checkStatus.ts` | `checkStatus(status, msg, mode, url?)` 新增第 4 个可选参数,400/403/404/500 等状态码提示统一 `appendApiUrl(errMessage, url)` |
+
+### 关键设计点(以后改这里注意)
+- 格式用**中文全角括号后缀**而不是 `\n`:antd `message` 的 content 是 div,`\n` 会被折叠成空格。
+- 追加位置放在「最终显示/抛出」之前,所以**服务端 message 与前端 i18n 文案(`sys.api.*`)都自动带上地址**,
+  且 `throw new Error(errorMessage)` 里的 message 也带地址 —— 调用方 `error.message` / `getRequestErrorMessage()` 无需改动。
+- 全仓检查过没有任何地方做 `message === 'xxx'` 的精确匹配(只有 cleanProgress 里一段注释掉的代码),所以拼接安全。
+- 保留 `/sys/health` 的静默特例(`urlPath.includes('/sys/health')`),健康检查轮询不会刷屏;其 URL 带 `?_t=` 时间戳也能命中。
+- 覆盖范围:`defHttp` 的所有实例(含 `src/ai/api/http.ts` 的 `aiHttp`,它复用 defHttp);`errorMessageMode==='none'` 的请求仍然不弹提示(未改语义)。
+- 提示内容不含 HTTP method(按「接口地址」字面要求);若以后需要区分同路径不同方法,可在 `appendApiUrl` 里加。
+
+### 验证
+- `eslint`(3 文件,`--max-warnings 0`)→ 0 错误
+- `vue-tsc --noEmit` → 这三个文件 **0 错误**,全仓错误总数仍为 103(与改动前一致,无新增)
+
+

+ 9 - 2
ai-frontend/src/core/utils/http/axios/checkStatus.ts

@@ -6,12 +6,18 @@ import { useI18n } from '@/core/hooks/web/useI18n';
 import projectSetting from '@/core/settings/projectSetting';
 import { SessionTimeoutProcessingEnum } from '@/core/enums/appEnum';
 import { handleUnauthorizedRedirect } from './unauthorized';
+import { appendApiUrl } from './helper';
 
 const { showMessage } = useMessage();
 // const error = createMessage.error!;
 const stp = projectSetting.sessionTimeoutProcessing;
 
-export function checkStatus(status: number, msg: string, errorMessageMode: ErrorMessageMode = 'message'): void {
+export function checkStatus(
+  status: number,
+  msg: string,
+  errorMessageMode: ErrorMessageMode = 'message',
+  url?: string,
+): void {
   const { t } = useI18n();
   let errMessage = '';
 
@@ -65,7 +71,8 @@ export function checkStatus(status: number, msg: string, errorMessageMode: Error
 
   if (errMessage) {
     if (errorMessageMode === 'modal' || errorMessageMode === 'message') {
-      showMessage({ content: errMessage, key: `global_error_message_status_${status}` }, 'error');
+      // 提示里带上本次访问的接口地址,便于直接定位是哪个接口出错
+      showMessage({ content: appendApiUrl(errMessage, url), key: `global_error_message_status_${status}` }, 'error');
     }
   }
 }

+ 13 - 0
ai-frontend/src/core/utils/http/axios/helper.ts

@@ -64,6 +64,19 @@ export function stringifyRequestParams(params: Recordable): string {
   });
 }
 
+/**
+ * @description: 把本次请求的接口地址追加到错误提示后面,便于直接定位是哪个接口出错
+ * @example appendApiUrl('接口请求出错,请稍后重试!', '/js/a/dm/doClean')
+ *          → '接口请求出错,请稍后重试!(/js/a/dm/doClean)'
+ */
+export function appendApiUrl(message: string, url?: string | null): string {
+  const target = typeof url === 'string' ? url.trim() : '';
+  if (!target) return message;
+  // 提示里已经带过该地址则不重复追加
+  if (message.includes(target)) return message;
+  return `${message}(${target})`;
+}
+
 /**
  * @description: Format request parameter time
  */

+ 9 - 6
ai-frontend/src/core/utils/http/axios/index.ts

@@ -13,7 +13,7 @@ import { isString } from '@/core/utils/is';
 import { setObjToUrlParams, deepMerge } from '@/core/utils';
 import { useErrorLogStoreWithOut } from '@/core/store/modules/errorLog';
 import { useI18n } from '@/core/hooks/web/useI18n';
-import { joinTimestamp, formatRequestDate, normalizeEmptyRequestValue } from './helper';
+import { joinTimestamp, formatRequestDate, normalizeEmptyRequestValue, appendApiUrl } from './helper';
 import { handleUnauthorizedRedirect } from './unauthorized';
 
 const globSetting = useGlobSetting();
@@ -43,6 +43,8 @@ const transform: AxiosTransform = {
     // 错误的时候返回
 
     const { data, config } = res;
+    // 本次请求的接口地址(已拼接 urlPrefix),用于错误提示中直接定位接口
+    const requestUrl = config?.url || '';
     // if (!data) { // boolean false return error
     //   // return '[HTTP] Request has no return value';
     //   throw new Error(t('sys.api.apiRequestFailed'));
@@ -56,9 +58,9 @@ const transform: AxiosTransform = {
     if (code !== 200) {
       if (code === 401) {
         handleUnauthorizedRedirect();
-        throw new Error(message || t('sys.api.apiRequestFailed'));
+        throw new Error(appendApiUrl(message || t('sys.api.apiRequestFailed'), requestUrl));
       }
-      const errorMessage = message || t('sys.api.apiRequestFailed');
+      const errorMessage = appendApiUrl(message || t('sys.api.apiRequestFailed'), requestUrl);
       if (skipErrorMessage) {
         throw new Error(errorMessage);
       }
@@ -210,7 +212,8 @@ const transform: AxiosTransform = {
     const skipErrorMessage = !!config?.requestOptions?.skipErrorMessage;
     const msg: string = response?.data?.message ?? '';
     const err: string = error?.toString?.() ?? '';
-    const urlPath = response?.config?.url || '';
+    // 接口地址:网络异常/超时等无 response 的场景,退化为取 error.config.url
+    const urlPath = response?.config?.url || config?.url || '';
     if (skipErrorMessage) {
       return Promise.reject(error);
     }
@@ -238,7 +241,7 @@ const transform: AxiosTransform = {
         }
         if (!isShowMessage) {
           isShowMessage = true;
-          showMessage({ content: msg || errMessage }, 'error');
+          showMessage({ content: appendApiUrl(msg || errMessage, urlPath) }, 'error');
           setTimeout(() => (isShowMessage = false), 1000);
         }
         return Promise.reject(error);
@@ -251,7 +254,7 @@ const transform: AxiosTransform = {
       handleUnauthorizedRedirect();
       return Promise.reject(error);
     }
-    checkStatus(response?.status, msg, errorMessageMode);
+    checkStatus(response?.status, msg, errorMessageMode, urlPath);
     return Promise.reject(error);
   },
 };

+ 4 - 4
ai-server/src/main/java/com/zsjz/ai/module/person/controller/DataProfileController.java

@@ -51,8 +51,8 @@ public class DataProfileController {
      */
     @PostMapping("/callOftenTop10")
     //@Cache(key = "#{query.cacheKey()}", tags = StrConsts.CACHE_TAG_QING_JIAN)
-    public Page<CallOftenTop10DTO> callOftenTop10(@RequestBody CallOftenTop10Query query) {
-        return dataProfileService.callOftenTop10(query);
+    public Result<Page<CallOftenTop10DTO>> callOftenTop10(@RequestBody CallOftenTop10Query query) {
+        return Result.succeed(dataProfileService.callOftenTop10(query));
     }
 
     /**
@@ -64,8 +64,8 @@ public class DataProfileController {
      */
     @PostMapping("/callStatStation")
     //@Cache(key = "#{query.cacheKey()}", tags = StrConsts.CACHE_TAG_QING_JIAN)
-    public List<CallStatStationDTO> callStatStation(@RequestBody CallStatStationQuery query) {
-        return dataProfileService.callStatStation(query);
+    public Result<List<CallStatStationDTO>> callStatStation(@RequestBody CallStatStationQuery query) {
+        return Result.succeed(dataProfileService.callStatStation(query));
     }
 
     /**