chore: sync local changes and add documentation

- Update yarn.lock
- Add project implementation docs in docs/
- Add personal internship experience notes in 实习讲解/
This commit is contained in:
2026-06-29 19:47:30 +08:00
parent 2cfe01694a
commit c1e9a4be83
12 changed files with 4110 additions and 4 deletions

View File

@@ -0,0 +1,388 @@
# API 接口前后端集成 — 面试版
> 简历原话:**"协作设计可信态势感知平台API接口完成50+数据接口的前后端集成"**
>
> 这篇文档帮你理解 API 集成层到底做了什么、怎么做的,以及面试时怎么讲。
---
## 一、先搞清楚API 集成层是什么?
API 集成层是前端和后端之间的"翻译官"和"交通管制员"。它不是简单的 `axios.get('/xxx')`,而是一整套基础设施:
```
页面组件Vue 组件)
│ 调用 API 函数
API 函数层29 个文件641 个函数)
→ 定义接口 URL、method、参数格式
│ 调用 request()
请求基础设施request.ts273 行)
→ 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_TOKENToken 头)
│ ├── 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<any> {
return request({
url: `/api/v1/page1/map/mapData`, // ← 接口路径
method: 'get', // ← 请求方法
params // ← GET 方式用 params
});
}
// AI 对话POST 方式)
export function fetchChatUseSql(data): Promise<any> {
return request({
url: '/ai/v1/chat/use_sql', // ← AI 服务接口
method: 'POST', // ← POST 请求
data // ← POST 方式用 data 传 JSON body
});
}
// 文件上传FormData 方式)
export function uploadImage(data): Promise<any> {
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 个文件中。
### Q2Token 过期自动刷新是怎么实现的?
> 用了一个 `refreshing` 锁 + Promise 请求队列的机制。当请求返回 401 时,先检查 `refreshing` 是否为 true——如果是说明已经有请求在刷新 Token 了,当前请求就包装成一个 Promise 放进等待队列;如果不是,就执行刷新并设 `refreshing = true`。刷新成功后更新 Token然后遍历队列用新 Token 重发所有等待的请求。这样无论同时有多少个请求返回 401都只会发一次刷新请求。
### Q3为什么需要 AES 加密?
> 这个平台涉及安全态势数据,有一定的敏感性。后端返回的数据在传输层用 AES-128-CBC 加密,前端在响应拦截器中自动解密。加密密钥和 IV 是硬编码在前端代码中的生产环境会用更安全的方式管理。加密流程对业务代码完全透明——API 函数只是调用 `request()`,加密解密都在拦截器里自动完成。
### Q4reqCatch 和 reqCatchV2 有什么区别?
> 都是 try/catch 的通用封装,避免每个页面都写 try/catch。区别在于`reqCatch` 静默捕获错误,返回 `{ data, error }` 结构,让调用方自己判断。`reqCatchV2` 在捕获错误后还会通过事件总线 `emitEvent('responseError', e)` 通知全局——这主要用于路由守卫里,发生 403/404 时通过事件触发全局的页面跳转。新代码基本都用 V2。
### Q5API 缓存降级是怎么运作的?
> 这是一套自研的请求降级系统。基于 IndexedDB 做持久化缓存。请求成功时自动缓存响应数据;请求失败时判断是否在熔断时间内——如果是,直接读缓存返回,用户看到的是旧数据而不是错误页面。还有最大缓存条数限制和过期清理机制。通过 `storage-apis.ts` 配置哪些接口走缓存策略,可以设置超时、重试次数、熔断时间等参数。
### Q6HTTP 错误怎么统一处理的?
> 在响应拦截器里统一处理。根据 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密钥 mXzfCSPBKmEA8aLqIV 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 封装) |
| 态势感知看板 API50 端点) | `src/api/jyh/situation.ts` |
| 金银湖核心 API45 端点) | `src/api/jyh/index.ts` |
| 金银湖首页 API5 端点) | `src/api/jyh/home.ts` |
| 金银湖认证 API3 端点) | `src/api/jyh/auth.ts` |
| 仓库管理 API161 端点) | `src/api/repo/index.ts` |
| 组织管理 API55 端点) | `src/api/org/index.ts` |
| 用户管理 API55+ 端点) | `src/api/user/index.ts` |
| 合并请求 API62 端点) | `src/api/merge/index.ts` |
| 讨论系统 API50 端点) | `src/api/discussion/index.ts` |