585 lines
18 KiB
Markdown
585 lines
18 KiB
Markdown
# yx-generate-api
|
||
|
||
`yx-generate-api` 是一个面向前端项目的 Node CLI,用来从 Swagger/OpenAPI JSON 生成 API 文件,并把生成目录的导出同步到你项目里的统一入口文件。
|
||
|
||
它主要解决两件事:
|
||
|
||
- 根据接口文档自动生成 `generated/*.js`
|
||
- 自动维护业务侧 `index.js` 里的导出区块,避免手写和漏改
|
||
|
||
## 先理解这 4 个命令
|
||
|
||
如果你先记住这四个命令,基本就会用了:
|
||
|
||
- `init`
|
||
在当前目录生成模板配置文件和 Windows 启动脚本。
|
||
- `generate`
|
||
只生成 API 文件,不改外部入口文件。
|
||
- `sync`
|
||
只同步外部 `index.js` 的受管导出区块。
|
||
- `gen`
|
||
先执行 `generate`,再执行 `sync`。日常最常用。
|
||
|
||
## 环境要求
|
||
|
||
- Node `^20.19.0 || >=22.12.0`
|
||
- 业务项目里要有一个默认导出的 `request` 模块,生成代码会按 `requestImport` 指向它
|
||
- Swagger 源可以是 `http(s)` 地址、`file://` URL,或者本地 JSON 文件路径
|
||
|
||
## 安装
|
||
|
||
在业务项目中直接安装:
|
||
|
||
```bash
|
||
npm install -D git+https://gitea.23544.com/wangyang/yx_generate_api_js.git
|
||
```
|
||
|
||
安装完成后,通常通过 `npx yx-generate-api ...` 调用。
|
||
|
||
## 快速开始
|
||
|
||
### 1. 生成模板文件
|
||
|
||
在业务项目根目录执行:
|
||
|
||
```bash
|
||
npx yx-generate-api init
|
||
```
|
||
|
||
会创建两个文件:
|
||
|
||
- `yx-generate-api.config.mjs`
|
||
- `run-yx-generate-api.bat`
|
||
|
||
其中 `yx-generate-api.config.mjs` 现在默认自带字段注释,方便首次接入时直接按提示修改。
|
||
|
||
如果文件已存在,使用 `--force` 覆盖:
|
||
|
||
```bash
|
||
npx yx-generate-api init --force
|
||
```
|
||
|
||
### 2. 修改配置
|
||
|
||
下面是一个和 `init` 模板一致的带注释示例:
|
||
|
||
```js
|
||
export default {
|
||
// Swagger/OpenAPI 来源。
|
||
// 支持 http(s)、file://,也支持本地 JSON 文件路径。
|
||
swaggerUrl: 'http://127.0.0.1:8080/swagger/v1/swagger.json',
|
||
|
||
// 远程 Swagger 加载超时时间,单位毫秒。
|
||
swaggerTimeoutMs: 20000,
|
||
|
||
// 生成文件输出目录。
|
||
// 相对路径会基于当前配置文件所在目录解析。
|
||
outputDir: 'src/api/aixue/generated',
|
||
|
||
// 是否生成 outputDir/index.js。
|
||
// 关闭后只保留 shared.js 和各模块文件;sync / gen 也会默认不再同步。
|
||
generateIndexFile: true,
|
||
|
||
// 由 `sync` / `gen` 维护的外部 API 入口文件。
|
||
externalIndexFile: 'src/api/aixue/index.js',
|
||
|
||
// 写入到生成模块中的 request 导入路径。
|
||
// 这个路径必须相对于每个生成后的模块文件来写。
|
||
requestImport: '../request',
|
||
|
||
// 生成函数的参数风格,可选 'object' 或 'positional'。
|
||
paramStyle: 'object',
|
||
|
||
// 全量生成时,是否清理已经过期的自动生成模块文件。
|
||
// 如果是部分模块生成,会自动跳过清理,避免误删其他模块。
|
||
cleanOutput: true,
|
||
|
||
sync: {
|
||
// 如果你只想生成文件、不想改 externalIndexFile,可以设为 false。
|
||
// 当 generateIndexFile=false 时,这个开关默认也会变成 false。
|
||
enabled: true,
|
||
|
||
// 是否在受管区块中附带 generated/index.js 的注释快照。
|
||
includeGeneratedIndexSnapshot: true,
|
||
|
||
// externalIndexFile 里受管区块的开始和结束标记。
|
||
blockStart: '// AUTO-GENERATED API EXPORTS START',
|
||
blockEnd: '// AUTO-GENERATED API EXPORTS END',
|
||
|
||
// 可选:
|
||
// snapshotTitle: '// generated/index.js content:',
|
||
// exportFrom: './generated',
|
||
},
|
||
}
|
||
```
|
||
|
||
### 3. 执行生成
|
||
|
||
```bash
|
||
npx yx-generate-api gen
|
||
```
|
||
|
||
这条命令会:
|
||
|
||
1. 拉取 `swaggerUrl`
|
||
2. 在 `outputDir` 下生成 API 文件
|
||
3. 如果 `generateIndexFile=true`,生成 `outputDir/index.js`
|
||
4. 如果 `sync.enabled=true`,把导出同步到 `externalIndexFile`
|
||
|
||
## 一个完整的日常流程
|
||
|
||
第一次接入时:
|
||
|
||
```bash
|
||
npx yx-generate-api init
|
||
npx yx-generate-api gen
|
||
```
|
||
|
||
后端接口更新后,通常只需要再执行一次:
|
||
|
||
```bash
|
||
npx yx-generate-api gen
|
||
```
|
||
|
||
如果你只是想重新整理外部入口文件,而不重新拉 Swagger:
|
||
|
||
```bash
|
||
npx yx-generate-api sync
|
||
```
|
||
|
||
## 配置说明
|
||
|
||
所有写在配置文件里的路径,默认都相对于配置文件所在目录,而不是命令执行目录。
|
||
|
||
### 顶层配置
|
||
|
||
- `swaggerUrl`
|
||
Swagger/OpenAPI JSON 来源。支持 `http(s)`、`file://`、本地 JSON 文件路径。生成时必填。
|
||
- `swaggerTimeoutMs`
|
||
拉取远程 Swagger 的超时时间,单位毫秒,默认 `20000`。
|
||
- `outputDir`
|
||
生成目录。默认是 `src/api/generated`。
|
||
- `externalIndexFile`
|
||
业务侧统一导出文件,例如 `src/api/index.js`。如果不填,默认不会执行同步。
|
||
- `generateIndexFile`
|
||
是否生成 `outputDir/index.js`。默认 `true`。设为 `false` 后,工具只生成 `shared.js` 和各模块文件;`sync.enabled` 的默认值也会随之变成 `false`。
|
||
- `requestImport`
|
||
生成模块里 `import request from '...'` 的路径。它应该相对于每个生成出来的模块文件。
|
||
- `paramStyle`
|
||
函数参数风格,可选 `object` 或 `positional`,默认 `object`。
|
||
- `cleanOutput`
|
||
是否在“全量生成”时清理当前输出目录里已经过期的自动生成模块文件。默认 `true`。
|
||
- `sync`
|
||
控制 `sync` / `gen` 如何维护外部入口文件。
|
||
|
||
### sync 配置
|
||
|
||
- `sync.enabled`
|
||
是否启用同步。默认值等于 `Boolean(externalIndexFile && generateIndexFile)`。
|
||
- `sync.blockStart`
|
||
受管区块开始标记。
|
||
- `sync.blockEnd`
|
||
受管区块结束标记。
|
||
- `sync.includeGeneratedIndexSnapshot`
|
||
是否把 `generated/index.js` 的内容以注释形式写进受管区块。默认 `true`。
|
||
- `sync.snapshotTitle`
|
||
快照标题。默认是 `// generated/index.js content:`。
|
||
- `sync.exportFrom`
|
||
自定义 `export * from '...'` 的路径。不填时会自动根据 `externalIndexFile` 和 `outputDir` 计算。
|
||
|
||
## 命令详解
|
||
|
||
### `init`
|
||
|
||
用途:在当前目录创建模板配置文件和 Windows 启动脚本。
|
||
|
||
```bash
|
||
npx yx-generate-api init
|
||
npx yx-generate-api init --force
|
||
```
|
||
|
||
### `generate`
|
||
|
||
用途:只生成 API 文件,不同步外部入口。
|
||
|
||
```bash
|
||
npx yx-generate-api generate
|
||
```
|
||
|
||
常用写法:
|
||
|
||
```bash
|
||
npx yx-generate-api generate Curriculum
|
||
npx yx-generate-api generate class-assignment Ranking
|
||
npx yx-generate-api generate --modules=Curriculum,class-assignment
|
||
npx yx-generate-api generate --config ./yx-generate-api.config.mjs
|
||
npx yx-generate-api generate --url=http://127.0.0.1:8080/swagger/v1/swagger.json
|
||
npx yx-generate-api generate --url ./swagger/swagger.json
|
||
npx yx-generate-api generate --outDir=src/api/tmp-generated
|
||
npx yx-generate-api generate --requestImport=../request
|
||
npx yx-generate-api generate --timeout 10000
|
||
npx yx-generate-api generate --no-clean
|
||
npx yx-generate-api generate --paramStyle=positional
|
||
```
|
||
|
||
支持的参数:
|
||
|
||
- `--config=...`
|
||
指定配置文件路径。
|
||
- `--url=...`
|
||
临时覆盖 `swaggerUrl`。
|
||
- `--timeout=...`
|
||
临时覆盖 Swagger 拉取超时,单位毫秒。
|
||
- `--outDir=...`
|
||
临时覆盖输出目录。
|
||
- `--requestImport=...`
|
||
临时覆盖生成文件里的 request 导入路径。
|
||
- `--modules=...`
|
||
逗号分隔的模块列表。
|
||
- `--clean`
|
||
全量生成时清理过期的自动生成模块文件。
|
||
- `--no-clean`
|
||
保留已有生成模块文件。
|
||
- `--paramStyle=object|positional`
|
||
临时覆盖参数风格。
|
||
|
||
命令行参数既支持 `--key=value`,也支持 `--key value`。
|
||
|
||
### `sync`
|
||
|
||
用途:只同步外部入口文件。
|
||
|
||
```bash
|
||
npx yx-generate-api sync
|
||
npx yx-generate-api sync --config=./yx-generate-api.config.mjs
|
||
```
|
||
|
||
如果 `sync.enabled=false`,命令会直接跳过。
|
||
|
||
### `gen`
|
||
|
||
用途:先 `generate`,再 `sync`。这是推荐的日常命令。
|
||
|
||
```bash
|
||
npx yx-generate-api gen
|
||
npx yx-generate-api gen Curriculum
|
||
npx yx-generate-api gen --modules=Curriculum,class-assignment
|
||
```
|
||
|
||
`gen` 接受和 `generate` 相同的运行时参数,例如 `--config`、`--url`、`--timeout`、`--outDir`、`--requestImport`、`--paramStyle`、`--modules`、`--clean`。
|
||
|
||
## 模块筛选规则
|
||
|
||
如果不传模块名,会生成 Swagger 里的全部模块。
|
||
|
||
如果传了模块名,只会生成匹配到的模块,例如:
|
||
|
||
```bash
|
||
npx yx-generate-api generate Curriculum
|
||
npx yx-generate-api generate class-assignment Ranking
|
||
npx yx-generate-api generate --modules=Curriculum,class-assignment
|
||
```
|
||
|
||
模块匹配时会同时参考这些值:
|
||
|
||
- 推导出的模块名
|
||
- 模块文件名的 kebab-case 形式
|
||
- Swagger operation 的 `tags`
|
||
|
||
匹配时会忽略大小写和大部分分隔符,所以下面这些通常都能匹配到同一个模块:
|
||
|
||
- `Curriculum`
|
||
- `curriculum`
|
||
- `class-assignment`
|
||
- `classAssignment`
|
||
|
||
### 模块名是怎么推导的
|
||
|
||
生成器会优先从接口路径推导模块名:
|
||
|
||
- `/api/v1/Curriculum/list` -> `Curriculum`
|
||
- `/api/Curriculum/list` -> `Curriculum`
|
||
- 其他路径会优先使用第一个路径段
|
||
- 如果路径不合适,会退回到 Swagger `tags[0]`
|
||
|
||
## 生成结果长什么样
|
||
|
||
假设配置如下:
|
||
|
||
```js
|
||
export default {
|
||
outputDir: 'src/api/aixue/generated',
|
||
externalIndexFile: 'src/api/aixue/index.js',
|
||
requestImport: '../request',
|
||
}
|
||
```
|
||
|
||
执行 `npx yx-generate-api gen` 后,通常会得到这样的结构:
|
||
|
||
```text
|
||
src/api/aixue/
|
||
index.js
|
||
request.js
|
||
generated/
|
||
shared.js
|
||
curriculum.js
|
||
class-assignment.js
|
||
index.js
|
||
```
|
||
|
||
其中:
|
||
|
||
- `shared.js`
|
||
提供 `buildUrl`、`stringifyParams` 等公共方法,不再依赖额外的 `qs` 包。
|
||
- `curriculum.js`
|
||
某个模块的 API 方法集合。
|
||
- `generated/index.js`
|
||
汇总导出每个模块,同时提供“命名空间导出”和“扁平函数导出”。
|
||
- `src/api/aixue/index.js`
|
||
业务侧入口文件,`sync` 会在里面维护一个受管区块。
|
||
|
||
`generated/index.js` 的内容类似这样:
|
||
|
||
```js
|
||
export * as classAssignmentApi from './class-assignment'
|
||
export * as curriculumApi from './curriculum'
|
||
|
||
export * from './class-assignment'
|
||
export * from './curriculum'
|
||
```
|
||
|
||
这样你可以同时支持两种写法:
|
||
|
||
```js
|
||
import { curriculumApi } from '@/api/aixue/generated'
|
||
import { getCurriculumListApi } from '@/api/aixue/generated'
|
||
```
|
||
|
||
如果你不需要这个聚合入口,也可以这样关闭:
|
||
|
||
```js
|
||
generateIndexFile: false
|
||
```
|
||
|
||
关闭后会有这些变化:
|
||
|
||
- 不再生成 `generated/index.js`
|
||
- 旧的自动生成 `generated/index.js` 会在下次生成时被移除
|
||
- 不再对“跨模块扁平导出重名”做 `generated/index.js` 级别的冲突校验
|
||
- `sync.enabled` 默认会变成 `false`
|
||
|
||
如果你又显式把 `sync.enabled` 设成 `true`,命令会直接报错,因为 `sync` 依赖 `generated/index.js`
|
||
|
||
需要注意:
|
||
|
||
- 如果不同模块里恰好生成了同名函数,例如都生成了 `getListApi`
|
||
- 那么 `generated/index.js` 里的 `export * from './module'` 会产生冲突
|
||
- 如果某个接口的默认函数名刚好会和模块命名空间导出名撞上,例如 `/api/Values` 同时会推导出模块命名空间 `valuesApi`
|
||
- 工具会在生成阶段自动把函数名调整成不冲突的形式,例如 `getValuesApi`
|
||
- 工具会先列出冲突函数、对应模块名,以及能识别到的完整接口 URL
|
||
- 在交互终端里会给你两个选择:
|
||
- `1` 在函数名后自动追加模块名,例如 `getListEnglishWordApi`
|
||
- `2` 退出本次生成
|
||
- 如果自动重命名后仍然冲突,工具会直接失败,并提示“依然冲突无法执行”
|
||
|
||
## 参数风格
|
||
|
||
`paramStyle` 决定生成函数的签名长什么样。
|
||
|
||
### `object`
|
||
|
||
默认值。路径参数和查询参数统一放到 `params`,请求体放到 `data`。
|
||
|
||
```js
|
||
const detailApi = (params = {}) => request.get(buildUrl(`/api/v1/course/{id}`, params))
|
||
|
||
const createApi = (params = {}, data) => request.post(buildUrl(`/api/v1/course/{id}`, params), data)
|
||
```
|
||
|
||
适合大多数前端项目,调用时更稳定,也更适合参数经常变动的接口。
|
||
|
||
### `positional`
|
||
|
||
路径参数和查询参数会展开成位置参数,请求体仍然放最后一个 `data`。
|
||
|
||
```js
|
||
const detailApi = (id, tab) => request.get(buildUrl(`/api/v1/course/{id}`, { id, tab }))
|
||
|
||
const createApi = (id, data) => request.post(buildUrl(`/api/v1/course/{id}`, { id }), data)
|
||
```
|
||
|
||
适合你明确想要“函数参数看起来更直接”的场景。
|
||
|
||
## 文件下载接口
|
||
|
||
如果响应声明为 `type: string, format: binary`、Swagger 2 的 `type: file`,或者使用常见文件下载 MIME 类型,例如 `application/octet-stream`、`application/pdf`、Excel、CSV、图片、音视频等,生成代码会自动带上 `responseType: 'blob'`。
|
||
|
||
有些后端 Swagger 不会给导出接口声明响应 `content` / `schema`。这种情况下,如果接口路径、`operationId`、摘要或描述里能识别到 `导出`、`下载`、`export`、`download`,或者 GET 接口名里有独立的 `File` 单词,也会按文件下载处理。若响应已经明确声明为 JSON 或普通 schema,则不会走这个兜底推断。
|
||
|
||
```js
|
||
const exportApi = (params = {}) =>
|
||
request.get(buildUrl(`/api/v1/report/export`, params), { responseType: 'blob' })
|
||
|
||
const createExportApi = (data) =>
|
||
request.post(`/api/v1/report/export`, data, { responseType: 'blob' })
|
||
```
|
||
|
||
## `sync` 会怎么改外部入口文件
|
||
|
||
`sync` 不会粗暴覆盖整个 `externalIndexFile`,它只维护一段带开始和结束标记的受管区块。
|
||
|
||
默认写进去的内容类似这样:
|
||
|
||
```js
|
||
// AUTO-GENERATED API EXPORTS START
|
||
// Synced from 'src/api/aixue/generated/index.js'. Do not edit manually.
|
||
// generated/index.js content:
|
||
// export * as curriculumApi from './curriculum'
|
||
// export * as rankingApi from './ranking'
|
||
// export * from './curriculum'
|
||
// export * from './ranking'
|
||
export * from './generated'
|
||
// AUTO-GENERATED API EXPORTS END
|
||
```
|
||
|
||
规则是:
|
||
|
||
- 如果外部文件里已经有这段标记,`sync` 会替换这段区块
|
||
- 如果还没有,`sync` 会把区块追加到文件末尾
|
||
- 标记外的内容会保留
|
||
|
||
如果你不想把 `generated/index.js` 的快照写进注释,可以把:
|
||
|
||
```js
|
||
sync: {
|
||
includeGeneratedIndexSnapshot: false,
|
||
}
|
||
```
|
||
|
||
## Windows 双击运行
|
||
|
||
`init` 会同时创建 `run-yx-generate-api.bat`。
|
||
|
||
它支持两种使用方式:
|
||
|
||
- 直接双击运行
|
||
会先提示你输入模块名或额外参数;留空则执行全量生成。
|
||
- 在命令行里带参数运行
|
||
会直接把参数透传给 `yx-generate-api gen`。
|
||
|
||
它内部的核心行为相当于:
|
||
|
||
```bat
|
||
npx yx-generate-api gen %*
|
||
```
|
||
|
||
双击时可输入的内容示例:
|
||
|
||
```bat
|
||
Curriculum
|
||
Ranking EnglishWord
|
||
--modules=Ranking,EnglishWord
|
||
```
|
||
|
||
适合给不常开命令行的同事直接双击执行,也保留了命令行传参的灵活性。
|
||
|
||
也可以在命令行里继续传参:
|
||
|
||
```bat
|
||
run-yx-generate-api.bat Curriculum
|
||
run-yx-generate-api.bat --modules=Curriculum,class-assignment
|
||
```
|
||
|
||
无论你是直接执行 `npx yx-generate-api gen`,还是走 `.bat`,后续真正执行的都是同一套 CLI。
|
||
|
||
所以如果遇到 API 函数名冲突,两边都会出现同样的冲突提示和同样的两个处理选项。
|
||
|
||
## 常见问题
|
||
|
||
### 1. 为什么 `gen` 没有同步外部 `index.js`
|
||
|
||
通常是下面几种情况:
|
||
|
||
- 没有配置 `externalIndexFile`
|
||
- `sync.enabled=false`
|
||
- 你执行的是 `generate`,不是 `gen`
|
||
|
||
### 2. 为什么生成文件里的 `request` 路径不对
|
||
|
||
`requestImport` 会原样写进生成文件,所以它必须相对于生成后的模块文件来写,而不是相对于 `externalIndexFile`。
|
||
|
||
例如生成目录是 `src/api/aixue/generated`,请求封装在 `src/api/aixue/request.js`,那么应该写:
|
||
|
||
```js
|
||
requestImport: '../request'
|
||
```
|
||
|
||
### 3. 为什么命令行传的 `--outDir` 看起来和配置文件规则不一样
|
||
|
||
配置文件里的路径相对于“配置文件所在目录”。
|
||
|
||
命令行传入的 `--config`、`--outDir`,相对于“当前执行命令的目录”。
|
||
|
||
### 4. 为什么提示模块找不到
|
||
|
||
说明你传入的模块名没有匹配到任何已解析模块。可以先不带模块参数跑一次全量生成,观察生成出来的模块文件名,再按那个名字筛选。
|
||
|
||
### 5. 为什么有些旧模块文件没有被删掉
|
||
|
||
只有“全量生成”时,`cleanOutput=true` 才会清理过期的自动生成模块文件。
|
||
|
||
如果你这次是只生成部分模块,例如:
|
||
|
||
```bash
|
||
npx yx-generate-api generate Curriculum
|
||
```
|
||
|
||
工具会保留其他已有模块,避免误删。
|
||
|
||
### 6. 为什么生成时报“Duplicate API export names detected”
|
||
|
||
说明不同模块之间出现了重复的扁平函数导出名。
|
||
|
||
常见场景有两种:
|
||
|
||
- 两个或多个模块里生成了同名函数,例如都导出了 `getListApi`
|
||
- 你只生成了部分模块,但保留目录里已有模块,导致新旧模块之间出现了同名导出
|
||
|
||
因为 `generated/index.js` 会使用:
|
||
|
||
```js
|
||
export * from './module-a'
|
||
export * from './module-b'
|
||
```
|
||
|
||
这种模式下,重复的扁平导出名不能直接共存。
|
||
|
||
像 `/api/Values` 这类“函数默认名刚好和模块命名空间名相同”的情况,工具会在生成阶段自动规避,不会再单独报这个冲突。
|
||
|
||
如果你是从旧版本升级过来,而 `generated` 目录里还保留着旧规则生成的模块文件,也可能在部分生成时触发这类校验。遇到这种情况,直接做一次全量生成,或者把冲突模块一起重新生成即可。
|
||
|
||
所以工具会先把冲突明细列出来,内容会尽量包含:
|
||
|
||
- 冲突函数名
|
||
- 对应模块名
|
||
- 对应的完整接口 URL
|
||
|
||
然后给你两个选择:
|
||
|
||
- 选 `1`
|
||
自动把冲突函数改成“原函数名 + 模块名”,例如 `getListApi` -> `getListEnglishWordApi`
|
||
- 选 `2`
|
||
退出生成,不写入本次结果
|
||
|
||
如果你选了自动重命名,但重命名后依然冲突,例如模块内已经存在同名函数,那么工具会直接失败并提示当前结果仍然无法安全生成。
|
||
|
||
## 本地开发
|
||
|
||
在工具仓库里直接查看帮助:
|
||
|
||
```bash
|
||
node ./bin/yx-generate-api.js --help
|
||
node ./bin/yx-generate-api.js generate --help
|
||
node ./bin/yx-generate-api.js sync --help
|
||
```
|