# API 接口前后端集成 — 面试版 > 简历原话:**"协作设计可信态势感知平台API接口,完成50+数据接口的前后端集成"** > > 这篇文档帮你理解 API 集成层到底做了什么、怎么做的,以及面试时怎么讲。 --- ## 一、先搞清楚:API 集成层是什么? API 集成层是前端和后端之间的"翻译官"和"交通管制员"。它不是简单的 `axios.get('/xxx')`,而是一整套基础设施: ``` 页面组件(Vue 组件) │ 调用 API 函数 ▼ API 函数层(29 个文件,641 个函数) → 定义接口 URL、method、参数格式 │ 调用 request() ▼ 请求基础设施(request.ts,273 行) → Axios 实例、请求拦截器、响应拦截器 → AES 加密/解密、Token 注入、Token 过期自动刷新 → 错误统一处理、缓存降级 │ HTTP 请求 ▼ 后端服务器 ``` **一句话概括**:我参与设计和集成了平台的 API 通信层,包括定义了 50+ 个态势感知相关的数据接口,并搭建了完整的请求基础设施(加密、Token 刷新、错误处理、缓存降级),覆盖全平台 641 个接口。 --- ## 二、API 整体规模 | 数据 | 数值 | |------|------| | API 函数总数 | **641 个** | | API 文件数 | **29 个** | | 金银湖态势感知相关 | **106 个**(situation.ts 50 + index.ts 45 + home.ts 5 + auth.ts 3 + scanCenter.ts 3) | | 最大的文件 | `repo/index.ts`(161 个函数) | ### 按模块分布 | 模块 | 文件 | 函数数 | 负责什么 | |------|------|--------|---------| | **态势感知看板** | `jyh/situation.ts` | 50 | 威胁地图、CVE 趋势、组件排行、漏洞分布等 | | **金银湖核心** | `jyh/index.ts` | 45 | AI 对话、版本详情、SBOM、许可证、修复建议 | | **仓库管理** | `repo/index.ts` | 161 | 仓库 CRUD、分支、标签、Wiki、文件、Webhook | | **组织管理** | `org/index.ts` | 55 | 组织 CRUD、成员、首页、CLA、开发者门户 | | **用户管理** | `user/index.ts` | 55+ | 登录注册、OAuth、个人资料、SSH 密钥、Token | | **合并请求** | `merge/index.ts` | 62 | MR 创建/审查、差异对比、代码评审、门禁 | | **讨论系统** | `discussion/index.ts` | 50 | 讨论 CRUD、分类、评论、投票、排行榜 | | **其他 20+ 个文件** | issue、commit、branch 等 | ~120 | Issue、提交、分支、标签、发布、通知等 | --- ## 三、请求基础设施(面试核心 — request.ts) `src/utils/request.ts`(273 行)是整个平台 API 通信的中枢,每一行都在解决实际问题。 ### 3.1 整体架构 ``` 组件调用 API 函数 │ ▼ proxyService(params) ← 创建一个 Axios 实例 │ ├── 请求拦截器 #1:注入 Header │ ├── DP_TOKEN / DP_REFRESH_TOKEN(Token 头) │ ├── Authorization: Bearer xxx(权限头) │ ├── page-title / page-repo-id / page-ref(页面监控头) │ ├── gitcode-utm-source(来源追踪头) │ └── URL 前缀重写(setPassportPrefix /uc) │ ├── 请求拦截器 #2:缓存降级检查 │ └── degradeInterceptor 决定是否读缓存 │ ├── ===== 发出 HTTP 请求 ===== │ ├── 响应拦截器 #1:成功处理 │ ├── code===500 → 检查登录状态 │ └── AES 解密响应数据($Decrypt) │ ├── 响应拦截器 #1:失败处理 │ ├── 401 → Token 自动刷新(refreshing 锁 + 请求队列) │ ├── 其他 4xx/5xx → dealWarning(防抖 200ms) │ └── 网络超时 → 静默处理 │ └── 响应拦截器 #2:缓存降级存储 └── 成功时存缓存,失败时读缓存返回 ``` ### 3.2 AES 加密解密 ```typescript // request.ts — 所有响应数据经过 AES-128-CBC 解密 import CryptoJS from 'crypto-js'; const sKey = CryptoJS.enc.Utf8.parse('mXzfCSPBKmEA8aLq'); const iv = CryptoJS.enc.Utf8.parse('tbeJJLC6dZQXXtWr'); // 解密:响应拦截器中自动调用 const $Decrypt = (text) => { let src = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Base64.parse(text)); let bytes = CryptoJS.AES.decrypt(src, sKey, { iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); return JSON.parse(bytes.toString(CryptoJS.enc.Utf8)); }; // 响应拦截器中:response.data.data = $Decrypt(response.data.data) ``` **为什么用加密?** 态势感知平台涉及敏感的安全数据,AES-128-CBC 加密保证了传输过程中即使被截获也无法直接读取。 ### 3.3 Token 自动刷新 + 请求队列 这是整个基础设施中设计最巧妙的部分——**当 Token 过期时,多个并发请求只触发一次刷新**: ```typescript // request.ts — Token 刷新逻辑(简化版) let refreshing = false; // 刷新锁 const queue = []; // 请求等待队列 // 响应拦截器的错误处理分支 async (error) => { if (response?.status === 401 && 有Token && 不是刷新接口) { if (refreshing) { // 情况 A:已经有刷新在进行 → 当前请求排队等待 return new Promise((resolve) => { queue.unshift({ config, resolve }); }); } // 情况 B:第一个遇到 401 的请求 → 执行刷新 refreshing = true; const res = await refreshToken(); // 调刷新接口 refreshing = false; if (res.status === 200) { // 刷新成功 → 更新 Token → 重放所有排队的请求 localStorage.setItem('op_access_token', newToken); queue.forEach(({ config, resolve }) => { config.headers.Authorization = `Bearer ${newToken}`; resolve(axios(config)); // 用新 Token 重发 }); queue.splice(0); // 清空队列 return axios(config); // 重发当前请求 } } } ``` **场景**:用户打开一个有 5 个图表的页面,5 个请求同时发出,全部返回 401。第一个请求触发 `refreshToken`,其余 4 个进入队列等待。刷新成功后,5 个请求用新 Token 全部重发。**不会出现 5 个请求各自刷新一次 Token 的情况。** ### 3.4 错误统一处理(status.ts) ```typescript // status.ts — HTTP 状态码 → 用户提示 + 事件通知 export function showMessage(res) { switch (res.status) { case 400: return res.data.error_message; // 参数错误 case 401: emitEvent('logout', ...); break; // 未授权 → 触发登出 case 403: emitEvent('forbiddenRefresh'); // 无权限 → 刷新页面 case 404: break; // 不存在 → 不提示 case 408: return '请求超时'; case 500: return '服务器错误'; case 502: return '网络错误'; case 504: return '网络超时'; default: return res.data.error_message || '连接出错'; } } // 用防抖包裹(200ms),防止短时间内多次弹错误提示 export function dealWarning() { return debounce((res) => { const msg = showMessage(res); if (msg) Message.error(msg); }, 200); } ``` **面试话术**: > "请求基础设施有四个关键设计。第一,AES-128-CBC 加密传输,敏感数据在传输过程中全程加密。第二,Token 自动刷新 + 请求队列——用 refreshing 锁和 Promise 队列机制,保证多个并发的 401 请求只触发一次 Token 刷新,其余排队等待,刷新成功后统一重发。第三,错误统一处理——所有 HTTP 错误状态码映射为用户友好的提示信息,并用 200ms 防抖避免短时间弹多次错误。第四,API 缓存降级——请求成功时缓存到 IndexedDB,失败时自动读缓存兜底。" --- ## 四、API 函数层 — 50+ 态势感知接口怎么组织的? ### 4.1 统一的 API 函数格式 每个 API 函数遵循统一的格式: ```typescript // src/api/jyh/situation.ts — 示例:态势感知看板 API 函数 import request from '@/utils/request'; // 格式:export function 函数名(参数): 返回类型 // ↓ 语义化命名 ↓ TypeScript 泛型 // 全球威胁地图数据 export function mapDataPage1(params?: any): Promise { return request({ url: `/api/v1/page1/map/mapData`, // ← 接口路径 method: 'get', // ← 请求方法 params // ← GET 方式用 params }); } // AI 对话(POST 方式) export function fetchChatUseSql(data): Promise { return request({ url: '/ai/v1/chat/use_sql', // ← AI 服务接口 method: 'POST', // ← POST 请求 data // ← POST 方式用 data 传 JSON body }); } // 文件上传(FormData 方式) export function uploadImage(data): Promise { const formData = new FormData(); formData.append('file', data.file); return request({ url: '/api/v1/upload/image', method: 'POST', data: formData, headers: { 'Content-Type': 'multipart/form-data' } }); } ``` ### 4.2 态势感知相关的 50+ 接口 态势感知模块的接口集中在 `src/api/jyh/situation.ts`(50 个函数),按三个页面组织: | 页面 | 接口前缀 | 端点数 | 干什么 | |------|---------|--------|--------| | **第1页:全球风险态势** | `/api/v1/page1/*` | 13 个 | 威胁地图、CVE 趋势、组件排行、漏洞分布、行业饼图 | | **第2页:开源生态与贡献** | `/api/v1/page2/*` | ~18 个 | 企业榜单、基础设施覆盖率、高校俱乐部、社区健康、语言竞争力、开发者、热力图 | | **第3页:武汉市开源生态** | `/api/v1/page3/*` | ~19 个 | HarmonyOS 统计、项目分布、开发者、企业、高校、社区、政策、镜像站点 | 加上 `jyh/index.ts` 中的 45 个通用接口(AI 对话、版本详情、SBOM、许可证、预警订阅、修复建议、质量任务等),金银湖模块共有 **106 个 API**。 --- ## 五、两个错误处理工具函数 ### 5.1 reqCatch — 安全包装 API 调用 ```typescript // src/utils/catch.ts // 把 try/catch 包装成一个通用函数,避免每个组件都写 try/catch export async function reqCatch(req, params) { try { const data = await req(params); return { data, error: null }; // 成功 → 返回数据 } catch (e) { return { data: null, error: e }; // 失败 → 返回错误对象,不会 throw } } // 用法: const res = await reqCatch(getOrg, { orgId: 'xxx' }); if (!res.error) { // 成功处理 } else if (res.error.error_code === 404) { // 404 处理 } ``` ### 5.2 reqCatchV2 — 升级版,自动发事件 ```typescript export async function reqCatchV2(req) { try { const data = await req(); return { data, error: null }; } catch (e) { emitEvent('responseError', e); // ← 自动通过事件总线通知全局 return { data: null, error: e }; } } ``` **为什么有两版?** `reqCatch` 是早期版本,静默捕获错误让调用方自己处理。`reqCatchV2` 增加了自动事件通知——在路由守卫里用 V2,错误会被 `eventBus` 监听到,触发统一的 403/404 页面跳转。 --- ## 六、API 缓存降级系统(degradeInterceptor.ts) 这是一个 360 行的自研系统,核心思想是:**API 成功时缓存结果,失败时读缓存兜底**。 ``` 请求发出 │ ├── 请求拦截器 │ ├── 是否匹配缓存策略?(URL 正则匹配) │ ├── 是否处于降级状态?(上次请求失败,且在熔断时间内) │ │ → 是:直接读 IndexedDB 缓存返回(不发请求) │ │ → 否:正常发请求(带重试增强) │ ├── 请求成功 → 响应拦截器 │ └── 缓存结果到 IndexedDB + 更新状态为 success │ └── 请求失败 → 响应拦截器 ├── 记录失败状态 + 时间戳 └── 读 IndexedDB 缓存返回(用户看到旧数据而不是错误) ``` **关键配置**(`storage-apis.ts`): ```typescript const storageApis = [ { url: '/api/v1/issues', timeout: 5000, retry: 2 }, { url: '/api/v1/merge_requests', timeout: 5000, retry: 2 }, // ... ]; ``` **面试话术**: > "API 缓存降级系统的思路是'请求成功时缓存,失败时读缓存兜底'。在请求拦截器里判断是否命中缓存策略——如果上次请求失败且在熔断时间内,就直接读 IndexedDB 返回缓存数据,不发网络请求。响应成功时自动更新缓存,失败时记录状态并返回历史缓存。还有缓存条数上限控制和过期清理机制。" --- ## 七、面试问答准备 ### Q1:"完成50+数据接口的前后端集成"具体做了什么? > 我参与了两方面的工作。第一是接口定义——和 backend 协作设计并集成了 50+ 个态势感知相关的数据接口,按三个页面组织(全球态势、开源生态、武汉生态),定义了接口路径、请求方法、参数格式和返回结构。第二是搭建了完整的请求基础设施——包括 Axios 实例封装、AES-128-CBC 加密传输、Token 自动刷新 + 请求队列、HTTP 状态码统一处理、API 缓存降级系统等。全平台共集成了 641 个 API 函数,分布在 29 个文件中。 ### Q2:Token 过期自动刷新是怎么实现的? > 用了一个 `refreshing` 锁 + Promise 请求队列的机制。当请求返回 401 时,先检查 `refreshing` 是否为 true——如果是,说明已经有请求在刷新 Token 了,当前请求就包装成一个 Promise 放进等待队列;如果不是,就执行刷新并设 `refreshing = true`。刷新成功后更新 Token,然后遍历队列用新 Token 重发所有等待的请求。这样无论同时有多少个请求返回 401,都只会发一次刷新请求。 ### Q3:为什么需要 AES 加密? > 这个平台涉及安全态势数据,有一定的敏感性。后端返回的数据在传输层用 AES-128-CBC 加密,前端在响应拦截器中自动解密。加密密钥和 IV 是硬编码在前端代码中的(生产环境会用更安全的方式管理)。加密流程对业务代码完全透明——API 函数只是调用 `request()`,加密解密都在拦截器里自动完成。 ### Q4:reqCatch 和 reqCatchV2 有什么区别? > 都是 try/catch 的通用封装,避免每个页面都写 try/catch。区别在于:`reqCatch` 静默捕获错误,返回 `{ data, error }` 结构,让调用方自己判断。`reqCatchV2` 在捕获错误后还会通过事件总线 `emitEvent('responseError', e)` 通知全局——这主要用于路由守卫里,发生 403/404 时通过事件触发全局的页面跳转。新代码基本都用 V2。 ### Q5:API 缓存降级是怎么运作的? > 这是一套自研的请求降级系统。基于 IndexedDB 做持久化缓存。请求成功时自动缓存响应数据;请求失败时判断是否在熔断时间内——如果是,直接读缓存返回,用户看到的是旧数据而不是错误页面。还有最大缓存条数限制和过期清理机制。通过 `storage-apis.ts` 配置哪些接口走缓存策略,可以设置超时、重试次数、熔断时间等参数。 ### Q6:HTTP 错误怎么统一处理的? > 在响应拦截器里统一处理。根据 HTTP 状态码映射不同的行为:400 返回参数错误提示、401 触发 Token 刷新或登出、403 触发页面刷新、404 静默不提示、5xx 显示服务端错误提示。错误提示用 `dealWarning` 函数包裹了 200ms 防抖,避免短时间内多个请求同时失败时弹出多条错误消息。还有 `customError: true` 参数,允许特定请求跳过默认错误处理,由调用方自行处理。 --- ## 八、关键数字(面试时用) | 数据 | 数值 | |------|------| | 全平台 API 函数总数 | **641 个** | | API 文件数 | **29 个** | | 态势感知(金银湖)API 数 | **106 个** | | 态势感知看板接口(situation.ts) | **50 个** | | 请求基础设施代码量 | `request.ts` 273 行 + `degradeInterceptor.ts` 360 行 + `catch.ts` 46 行 + `status.ts` 76 行 | | Token 刷新机制 | refreshing 锁 + Promise 队列,确保多并发 401 只刷新一次 | | 加密方式 | AES-128-CBC,密钥 mXzfCSPBKmEA8aLq,IV tbeJJLC6dZQXXtWr | | 请求超时 | 30 秒 | | 错误提示防抖 | 200ms | --- ## 九、涉及的源码文件(需要看的时候查) | 做什么 | 文件在哪 | |--------|----------| | 请求核心基础设施 | `src/utils/request.ts`(273 行,Axios、拦截器、加密、Token 刷新) | | API 缓存降级系统 | `src/utils/degradeInterceptor.ts`(360 行,IndexedDB 缓存、熔断、重试) | | 错误捕获包装函数 | `src/utils/catch.ts`(reqCatch + reqCatchV2) | | HTTP 状态码处理 | `src/utils/status.ts`(dealWarning + showMessage) | | 缓存配置 | `src/api/storage-apis.ts`(哪些接口走缓存策略) | | 缓存存储层 | `src/utils/apiStorage.ts`(IndexedDB 封装) | | 态势感知看板 API(50 端点) | `src/api/jyh/situation.ts` | | 金银湖核心 API(45 端点) | `src/api/jyh/index.ts` | | 金银湖首页 API(5 端点) | `src/api/jyh/home.ts` | | 金银湖认证 API(3 端点) | `src/api/jyh/auth.ts` | | 仓库管理 API(161 端点) | `src/api/repo/index.ts` | | 组织管理 API(55 端点) | `src/api/org/index.ts` | | 用户管理 API(55+ 端点) | `src/api/user/index.ts` | | 合并请求 API(62 端点) | `src/api/merge/index.ts` | | 讨论系统 API(50 端点) | `src/api/discussion/index.ts` |