# Axios 公共请求层重构 Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 删除现有 `fetch` 请求实现,以本地 Axios 浏览器版和 `utils/AxiosRequestUtil.js` 替换为唯一公共请求层,页面业务 JS 不再依赖或实现底层网络请求。 **Architecture:** `utils/axios.js` 是固定版本的第三方浏览器依赖;`utils/AxiosRequestUtil.js` 负责 Axios 实例、请求头、token、参数、响应解包和错误转换;`utils/ApiClient.js` 负责所有 PC 接口契约与 token 生命周期。所有页面在页面业务脚本前加载三个 utils 文件,旧 `utils/RequestUtil.js` 和 `public/js/api-client.js` 被删除且不保留兼容入口。 **Tech Stack:** Axios 1.7.9 浏览器非压缩构建、原生 JavaScript、Node `assert` 测试、PowerShell 静态扫描。 ## Global Constraints - 计划创建时间:2026-07-10 09:27:30 +08:00。 - `utils` 只存通用依赖和公共请求封装;`public/js` 只存页面业务脚本,不存 API 客户端或底层请求实现。 - 不保留 `fetch`、`fetchImpl`、`root.fetch`、`RequestUtil`、`public/js/api-client.js` 或旧脚本引用;不做 Axios/fetch 双实现兼容。 - Axios 使用本地 `utils/axios.js`,页面运行时不依赖 CDN。 - 普通 JSON 请求传对象给 Axios;文件上传传 `FormData`;不手写 JSON 字符串。 - 每个请求都带 `clientid`;有 token 且 `auth !== false` 时带 `Authorization: Bearer `。 - PC-014 在本重构完成后重新执行,重构前的复核结论不作为最终证据。 --- ### Task 0: 收敛环境配置职责 **Files:** - Modify: `config.js` - Modify: `tests/config.test.js` - Modify: `utils/ApiClient.js` **Interfaces:** - Produces: `GenealogyConfig.getEnvironment()`,只返回 `development` 或 `production`。 - Produces: `GenealogyConfig.getApiBaseUrl()`,只返回当前环境的接口基础地址。 - Produces: `GenealogyConfig.getConfig()`,只返回 `{ environment, apiBaseUrl }`。 - Consumes: `ApiClient` 自己定义 PC `clientId`、`tenantId`、token key 和 token header。 - [x] **Step 1: 写配置职责失败测试** ```js assert.deepStrictEqual(config.getConfig(), { environment: 'development', apiBaseUrl: 'http://test-genealogy-api.ddxcjp.cn' }); assert.strictEqual(config.getClientId, undefined); assert.strictEqual(config.getTenantId, undefined); assert.strictEqual(config.getTokenKey, undefined); ``` - [x] **Step 2: 运行测试确认旧配置职责过宽** Run: `node tests\\config.test.js` Expected: FAIL,旧配置仍暴露 clientId、tenantId、token key 或 token header。 - [x] **Step 3: 重写 config.js 为环境和域名唯一入口** ```js var environments = { development: { apiBaseUrl: 'http://test-genealogy-api.ddxcjp.cn' }, production: { apiBaseUrl: 'http://test-genealogy-api.ddxcjp.cn' } }; function getConfig() { return { environment: getEnvironment(), apiBaseUrl: getApiBaseUrl() }; } ``` - [x] **Step 4: 将 PC 身份和 token 常量保留在 ApiClient** ```js var DEFAULT_CLIENT_ID = 'ced7e5f0498645c6ec642dcf450b036f'; var DEFAULT_TENANT_ID = '000000'; var DEFAULT_TOKEN_KEY = 'genealogy_auth_token'; var DEFAULT_TOKEN_HEADER_NAME = 'Authorization'; ``` 补充约束:`ApiClient` 不定义接口基础地址;必须由根目录 `config.js` 的 `apiBaseUrl` 或调用方显式 `baseUrl` 提供。 - [x] **Step 5: 运行配置与 API 客户端测试** Run: `node tests\\config.test.js; node tests\\api-client.test.js; node --check config.js; node --check utils\\ApiClient.js` Expected: 所有命令通过。 ### Task 1: 锁定 Axios 请求契约的失败测试 **Files:** - Modify: `tests/utils.test.js` - Modify: `tests/api-client.test.js` **Interfaces:** - Produces: `AxiosRequestUtil.createRequester(options)`。 - Consumes: `options.axiosInstance`,该对象实现 `request(config)`。 - Produces: `request(method, path, { query, body, headers, auth })`,向 Axios 传入 `{ method, url, params, data, headers }`。 - [x] **Step 1: 把工具测试改为 Axios 假实例** ```js const axiosCalls = []; const requester = AxiosRequestUtil.createRequester({ baseUrl: 'https://api.example.test', clientId: 'pc-client', getToken: () => 'token-x', axiosInstance: { request: async (config) => { axiosCalls.push(config); return { status: 200, data: { code: 200, data: { ok: true } } }; } } }); ``` - [x] **Step 2: 运行测试确认因旧 fetch 契约失败** Run: `node tests\\utils.test.js; node tests\\api-client.test.js` Expected: FAIL,提示缺少 `AxiosRequestUtil` 或仍使用 `fetchImpl`。 ### Task 2: 创建 Axios 工具层并删除 fetch 工具层 **Files:** - Create: `utils/axios.js` - Create: `utils/AxiosRequestUtil.js` - Create: `utils/ApiClient.js` - Delete: `utils/RequestUtil.js` - Delete: `public/js/api-client.js` **Interfaces:** - `AxiosRequestUtil.createRequester(options)` 只接受 Axios 实例,不接受 `fetchImpl`。 - `GenealogyApi.createClient(options)` 由 `utils/ApiClient.js` 提供,只接受 `axiosInstance` 测试注入;生产环境读取 `window.axios`。 - [x] **Step 1: 下载固定 Axios 非压缩浏览器构建** Run: `curl.exe --fail --location https://cdn.jsdelivr.net/npm/axios@1.7.9/dist/axios.js --output utils\\axios.js` Expected: `utils/axios.js` 存在且首行包含 Axios 版本信息。 - [x] **Step 2: 实现 AxiosRequestUtil 最小请求流程** ```js var response = await axiosInstance.request({ method: method.toLowerCase(), url: path, params: requestOptions.query, data: requestOptions.body, headers: headers }); return unwrapResponse(response.data); ``` - [x] **Step 3: 从零实现 utils/ApiClient.js 并只依赖 AxiosRequestUtil** ```js var requestUtil = getAxiosRequestUtil(); var requester = requestUtil.createRequester({ baseUrl: baseUrl, clientId: clientId, getToken: getToken, axiosInstance: settings.axiosInstance || root.axios }); ``` - [x] **Step 4: 删除旧 fetch 请求文件和旧公共 API 文件** Run: `Remove-Item -LiteralPath utils\\RequestUtil.js; Remove-Item -LiteralPath public\\js\\api-client.js` Expected: 两个文件不存在,后续扫描无 `RequestUtil`、`fetchImpl`、`root.fetch`、`fetch(` 或 `public/js/api-client.js` 匹配。 - [x] **Step 5: 运行工具和 API 客户端测试** Run: `node tests\\utils.test.js; node tests\\api-client.test.js; node --check utils\\AxiosRequestUtil.js; node --check utils\\ApiClient.js` Expected: 所有命令通过。 ### Task 3: 更新页面公共脚本顺序 **Files:** - Modify: 所有根目录中加载 `public/js/api-client.js` 或 `utils/ApiClient.js` 的 HTML 页面。 - Test: `tests/auth-page-structure.test.js` - Test: `tests/api-script-order.test.js` - [x] **Step 1: 以自动发现的脚本顺序测试锁定页面依赖** ```js assert.strictEqual(pages.length, 45); assert.ok(axiosIndex < axiosRequestUtilIndex); assert.ok(axiosRequestUtilIndex < apiClientIndex); assert.strictEqual(html.includes('utils/RequestUtil.js'), false); ``` - [x] **Step 2: 运行测试确认旧脚本顺序不满足新契约** Run: `node tests\\auth-page-structure.test.js` Expected: FAIL,仍有页面加载旧 API 客户端或未加载 Axios 和 AxiosRequestUtil。 - [x] **Step 3: 逐页替换公共脚本引用** ```html ``` - [x] **Step 4: 运行结构测试和静态扫描** Run: `node tests\\auth-page-structure.test.js; rg -n "utils/RequestUtil\\.js|public/js/api-client\\.js|fetchImpl|root\\.fetch|fetch\\(" --glob "!public/tac/**" --glob "!utils/axios.js" .` Expected: 测试通过,旧请求层标识无匹配。 ### Task 4: 重跑 PC 复核并更新记录 **Files:** - Modify: `docs/pc-api-page-integration-tracker-2026-07-09.md` - Modify: `docs/superpowers/plans/2026-07-10-axios-request-layer-refactor.md` - Modify: `docs/superpowers/plans/2026-07-10-pc-tracker-reverification.md` - [x] **Step 1: 运行所有 Node 测试与语法检查** Run: `Get-ChildItem -Path tests -Filter *.test.js | Sort-Object Name | ForEach-Object { node $_.FullName }; node --check utils\\AxiosRequestUtil.js; node --check utils\\ApiClient.js; git diff --check` Expected: 所有测试通过,语法检查和差异检查无错误。 - [x] **Step 2: 执行 PC-014 复核命令并记录外部网关状态** Run: `curl.exe -I --max-time 15 http://test-genealogy-api.ddxcjp.cn/captcha/require` Expected: 记录当次结果;仅本地验证通过的项标为“本地复核通过”,网关不可用时标注“外部环境待验证”。 - [x] **Step 3: 写入完成时间和清理结论** Record: Axios 文件来源、删除的 `fetch` 文件、页面脚本替换范围、所有验证命令及结果。 ## 自检 - 所有底层网络调用都从 `utils/AxiosRequestUtil.js` 发起。 - `public/js` 中不含 `fetch`、Axios 实例创建或请求头拼装。 - 所有页面在页面业务脚本前加载 `utils/axios.js`、`utils/AxiosRequestUtil.js` 和 `utils/ApiClient.js`。 - `utils/RequestUtil.js`、`public/js/api-client.js`、`fetchImpl`、`root.fetch` 和旧页面脚本引用均不存在。 ## 完成记录 - 完成时间:2026-07-10 10:10:56 +08:00。 - Axios 来源:`axios@1.7.9/dist/axios.js` 的本地非压缩浏览器构建,存放于 `utils/axios.js`。 - 请求契约:所有 PC 请求均由 `utils/AxiosRequestUtil.js` 通过 `axios.request` 发起;`utils/ApiClient.js` 仅封装 PC 接口和 token 生命周期。 - 配置边界:根目录 `config.js` 是接口基础地址唯一来源;`ApiClient` 不再保存 `DEFAULT_BASE_URL`。 - 页面范围:45 个加载接口客户端的 HTML 页面已统一脚本顺序;旧 `utils/RequestUtil.js` 和 `public/js/api-client.js` 已删除。 - 验证:全量 Node 测试、语法检查、45 页脚本顺序检查、旧 fetch/旧脚本扫描和 `git diff --check` 均通过。