# Dl API Doc For Frontend

> 给前端 AI 看的后端接口联调文档生成器（团队 NestJS 项目通用）。当用户开发/改完一个较大的后端模块，需要"生成接口文档 / 联调文档 / 给前端的 API 说明 / 本次改了哪些接口 / 入参返回值字段说明"，或要把某个 controller 模块整理成前端能照着联调的文档时，用这个 skill。产出是面向**前端 AI**、机器可读性强的 Markdown：含本次改动摘要、每个接口的完整路径/方法/鉴权/入参/返回值/字段释义/枚举/错误码/请求响应示例，并把**该项目实测到的**全局约定（响应包装、路由前缀、鉴权、参数校验行为）写进文档头，避免前端 AI 踩坑。全局约定一律**读目标项目的代码核对**（团队 dl-nestjs-starter 默认值只作 fallback），绝不照抄死值。手动触发，结果写到 docs/（不进版本管理），主要针对大模块开发。

- Skill: `zhiqingresearch/dl-api-doc-for-frontend` (Agent Skill)
- Install (CLI): `npx skillmds@latest add zhiqingresearch/dl-api-doc-for-frontend`
- Raw SKILL.md: https://api.skillmd.com/api/skills/zhiqingresearch/dl-api-doc-for-frontend/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: ZhiqingResearch (https://skillmd.com/u/zhiqingresearch)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/zhiqingresearch/dl-api-doc-for-frontend

---


# 前端联调接口文档生成器（团队通用）

## 这个 skill 解决什么

大模块开发完，前端（通常也是 AI）要联调。Swagger 虽然有，但对 AI 不够友好：要点开点去、看不到全局响应包装、不知道哪些接口要带 token、枚举和字段语义散落。这个 skill 把**一个模块**整理成一份**结构固定、自包含、机器可读**的 Markdown，让前端 AI 一次读懂、照着就能调。

它解决的不是"提醒你读代码"——一个像样的 agent 读代码、核对约定是本能。它解决的是两件 agent 自己推不出来的事：

1. **结构一致**：每次产出同一套小标题，前端 AI 能稳定解析，而不是每次换一种排版。
2. **团队工作流约定**：本次改动从哪来、产物文件名/位置、多模块怎么合并——这些是约定，不在代码里。

**读者是前端 AI**，写作目标：结构规整、字段类型明确、示例是真实线上格式（含包装层）、不依赖外部上下文。宁可啰嗦写全，也不要让读者去猜。

## 触发与产出

- **手动触发**，针对一次开发涉及的接口（通常一个大模块，如 `user`、`order`）。
- **每次调用都优先生成新文件**，不覆盖旧文档——文件名带**年月日 + 时分秒**，便于区分每次产出：
  - 文件名 `docs/<module>-<YYYYMMDD-HHmmss>.api.md`（如 `docs/order-20260624-143052.api.md`）。
  - 确认 `docs/` 已在 `.gitignore`；若没有，提示用户加上——它是联调用的临时产物，源头永远是代码。
- **涉及多个模块时，仍只生成一个文件**，在文件内按模块拆分章节（每个模块一个二级标题段落），不要拆成多个文件：
  - 文件名 `docs/<module1>-<module2>-<YYYYMMDD-HHmmss>.api.md`，或模块多时用 `docs/api-<YYYYMMDD-HHmmss>.api.md`。

## 本次改动从哪来（新文件不靠对比旧文档）

每次都是新文件、不覆盖，所以**别拿旧文档做 diff**。"本次改动"来源按优先级：

1. **本次开发的真实代码改动**——`git diff`（工作区 / 本分支 vs 主开发分支，团队一般是 `develop`）里命中该模块 controller / DTO / 返回映射文件（如 `*.select.ts` / mapper）/ Prisma schema 的部分，就是这次改了什么。这是最准的来源。
2. **当前对话上下文**——如果这次产出紧接着一段开发，直接用刚做的改动归纳。
3. 两者都没有（纯整理一个老模块）→ 写"全量整理"，不要硬凑改动。

> 想跟上次产出对比，可以人工看 `docs/` 下同模块上一个时间戳的文件，但文档本身不依赖它。

## 怎么提取信息（数据来源是代码，不是脑补）

按模块读这些文件，拼出每个接口的全貌：

| 要素                | 来源                                                                                                           |
| ------------------- | -------------------------------------------------------------------------------------------------------------- |
| 路径前缀            | `@Controller({ path })` / `@Controller('x')` → 完整路径见下方"全局约定核对"得到的真实前缀                       |
| 方法 + 子路径       | 方法上的 `@Get/@Post/...('sub')`                                                                               |
| 是否要鉴权          | 看该项目的鉴权模型：有免登录装饰器（如 `@Public()`）→ 免登录；否则按全局 Guard 要求带 token                     |
| 入参（body）        | `@Body() dto: XxxDto` → 读该 DTO 的 class-validator 装饰器（必填/长度/正则/类型）+ `@ApiProperty`（描述/示例） |
| 入参（query/param） | `@Query()` / `@Param()`，注意 `ParseIntPipe`、`@Type()` 等是否存在——决定 handler 收到的真实类型（见校验行为） |
| 返回值              | service 方法的返回类型 / Prisma model 字段；敏感/内部字段确认是否被 `select` 排除，**别写进返回**              |
| 字段语义 / 枚举     | DTO 注释、Prisma schema 的 `//` 注释、枚举取值                                                                 |
| 业务说明            | service 里的关键逻辑（如"手机号唯一""密码哈希不返回"）                                                          |

> 拿不准返回结构时，读 service 实现和对应 Prisma model，别编。确实无法确定的字段，标 `// TODO: 待确认` 而不是猜一个。

## 全局约定核对（核心：读代码，不照抄死值）

前端 AI 最容易错的就是全局约定——而**每个项目可能不同**（有的前缀 `api`、有的 `v1`；包装可能是 `{code,message,data,timestamp}` 也可能是别的；有的项目关掉了 implicit conversion）。所以这一段**必须从目标项目代码实测得到**，团队 `dl-nestjs-starter` 的默认值只作"没读到时的 fallback / 对照基线"。

落笔前按这张清单读代码确认，并把**实测结果**写进文档头：

| 约定        | 去哪读                                                                 | starter 默认值（仅 fallback）                                  |
| ----------- | ---------------------------------------------------------------------- | --------------------------------------------------------------- |
| 路由前缀    | `main.ts` 的 `setGlobalPrefix(...)`（含 `exclude`）                     | `api`（health/docs 除外）                                       |
| 响应包装    | 响应拦截器（`*.interceptor.ts`）的 wrapper 结构 + 跳过包装的装饰器       | `{ code, message, data, timestamp }`；`@SkipResponseTransform()` |
| 鉴权        | 全局 Guard + 免登录装饰器（`@Public()` 等）                            | 全局 JwtAuthGuard，`@Public()` 免登录，token 来自登录接口        |
| 参数校验    | `main.ts` 的 `ValidationPipe` 配置                                      | `whitelist`+`forbidNonWhitelisted`+`transform`                  |

> ⚠️ **发现项目偏离 starter 默认值时，以代码为准，并在文档头明确标注"本项目与常见模板不同"**（例：前缀是 `v1` 不是 `api`；包装是 `payload` 不是 `data`；`enableImplicitConversion` 关着）。这是这份文档最该帮前端避开的坑。

把实测到的这四条写进文档头，写法参考：

### 路径拼装

```
{BASE_URL}/<实测前缀>/{controller-path}/{route}
```

例（前缀实测为 `api` 时）：`@Controller({ path: 'user' })` + `@Get('me')` → `GET /api/user/me`

### 统一响应包装（重要！前端拿到的不是裸数据）

所有响应都被包了一层，**真正的业务数据在包装的数据字段里**（字段名以实测为准，starter 是 `data`）：

```jsonc
// 成功（以 starter 结构为例，实测可能不同）
{ "code": 200, "message": "success", "data": <业务数据>, "timestamp": "2026-06-04T..." }
// 失败（异常过滤器封装）
{ "code": 400, "message": "错误信息", "data": null, "timestamp": "...", "path": "/..." }
```

- 例外：标了跳过包装的装饰器（starter 是 `@SkipResponseTransform()`）的接口返回原始数据（文件下载/SSE 等），文档里要特别标注。
- 因此每个接口文档的"返回值"描述的是**包装内数据字段**的内容，并至少给一个**完整包装**的示例。

### 鉴权

- 按实测的全局 Guard 写默认要求；带免登录装饰器的除外。
- token 从登录接口拿。

### 参数校验行为（前端常踩）

- 若开了 `whitelist` + `forbidNonWhitelisted`：**传了 DTO 里没声明的多余字段 → 直接 400**。前端别乱传字段。
- 留意 `transform` 与 `enableImplicitConversion`：**若未开 `enableImplicitConversion`**，query 里的数字/布尔字符串**不会自动转**，全靠 DTO 字段上的 `@Type(() => Number)` / `@Transform(...)` 或 `ParseIntPipe` 显式转换。写入参类型时以装饰器/管道为准——没标显式转换的 query 参数到 handler 里仍是 string。

## 文档模板

文件名见上方"触发与产出"（带年月日+时分秒），按这个结构写（保持每个接口同一套小标题，方便 AI 解析）。多模块时，在 `## 全局约定` 之后按模块各起一段（`# <模块名> 接口文档` → 接口列表 → 各接口），全局约定只写一次：

````markdown
# <模块名> 接口文档（前端联调）

> 生成时间：<YYYY-MM-DD HH:mm:ss>　|　对应代码：src/modules/<module>/
> 本文件由 dl-api-doc-for-frontend skill 生成，不进版本管理；以代码为准。

## 本次改动

- 新增 `POST /<前缀>/.../xxx`：……
- 字段变更：`Order.status` 新增枚举值 `REFUNDED`
- 行为变更：……
  （来源见上方"本次改动从哪来"；实在没有改动语境就写"全量整理"。）

## 全局约定

<把上面"全局约定核对"实测到的整段写进来：路径拼装 / 响应包装 / 鉴权 / 校验行为。若与 starter 默认不同，开头一句标注"本项目与常见模板不同：……"。>

## 接口列表

| 方法 | 路径            | 鉴权 | 说明     |
| ---- | --------------- | ---- | -------- |
| POST | /<前缀>/auth/login | 否   | 登录     |
| GET  | /<前缀>/user/me    | 是   | 当前用户 |

---

## POST /<前缀>/auth/login — 登录

- **鉴权**：否（免登录装饰器）
- **Content-Type**：application/json
- **业务说明**：手机号 + 密码登录，成功返回 accessToken。

### 入参（body）

| 字段     | 类型   | 必填 | 约束            | 说明   |
| -------- | ------ | ---- | --------------- | ------ |
| phone    | string | 是   | `^1[3-9]\d{9}$` | 手机号 |
| password | string | 是   | 非空            | 密码   |

### 返回值（包装内数据字段的内容）

| 字段          | 类型   | 说明                                  |
| ------------- | ------ | ------------------------------------- |
| accessToken   | string | JWT，后续请求放 Authorization: Bearer |
| user.userId   | number | 用户 ID                               |
| user.username | string | 用户名                                |

### 示例

请求：

```json
POST /<前缀>/auth/login
{ "phone": "13800138000", "password": "P@ssw0rd" }
```

响应（完整包装，以实测包装结构为准）：

```json
{
  "code": 200,
  "message": "success",
  "data": {
    "accessToken": "eyJhbGci...",
    "user": { "userId": 1, "username": "zhangsan" }
  },
  "timestamp": "2026-06-04T08:00:00.000Z"
}
```

### 错误

| code | 场景                        |
| ---- | --------------------------- |
| 400  | 参数校验失败 / 传了多余字段 |
| 401  | 手机号或密码错误            |
````

## 写作要点（针对 AI 读者）

- **类型写具体**：`number` / `string` / `boolean` / `string[]` / 对象展开到字段；枚举列全部取值（`'PENDING' | 'PAID' | ...`）。
- **必填、约束、可空都标清**：前端 AI 据此生成校验和 TS 类型。
- **示例用真实数据**：手机号像手机号、时间按实测格式（ISO 串还是时间戳）；响应示例一定是**带包装**的完整结构。
- **敏感/内部字段不出现在返回**：password / 哈希 / 风控分 / 创建人等内部字段，确认是否已被 service 的 `select` 排除。**若发现接口实际会下发本不该给前端的字段（如 `findUnique` 没加 `select`），按"当前真实输出"如实记录，并单列一条风险提示让后端确认修复**——别假装它被排除了。
- **不可破坏性变更要标红**：删字段、改类型、改路径，在"本次改动"里明确写"⚠️ 破坏性"，让前端 AI 知道要改调用。
- **一次产出一份文件**：单模块就一个模块段落，多模块在同一文件内按模块拆分章节，不要生成多个文件。

## 产出给用户

1. 已写入的文件路径（带年月日+时分秒的新文件）。
2. 一句话说明覆盖了哪些接口、本次改动重点。
3. **若该项目全局约定与团队 starter 默认不同，明确点出**（前缀/包装/校验差异），这是最容易坑前端的地方。
4. 若有字段语义/返回结构无法从代码确定、或发现疑似数据泄露/坏味道，列出来让用户确认（不要猜着写死）。

