openapi-ts-request
openapi-ts-request
介绍
- 根据openapi生成ts的前端api接口定义代码
- 由于openapi2ts不支持对请求路径的过滤,所以使用了 openapi-ts-request库官网
- https://github.com/openapi-ui/openapi-ts-request?tab=readme-ov-file#%E5%8F%82%E6%95%B0安装
- pnpm i openapi-ts-request -D
- 在项目根目录新建 openapi-ts-request.config.ts
export default defineConfig(
//可以配置多组
[{
//地址www为group名字,需要在springboot项目application.yaml中配置
schemaPath: 'http://localhost:8123/v3/api-docs/www',
//存储目录
serversPath: './apis',
//生成哪些路径的方法
includePaths: [
'/www/**'
],
//网络访问库的名字
requestLibPath: '~/request',
//定制方法的名字
hook: {
customFunctionName: function(data: APIDataType): string{
return data.operationId || ''
}
}
}])在package.json 中配置脚本
scripts {
"openapi": "openapi-ts"
}springboot项目application.yaml中配置openapi分组
springdoc:
group-configs:
- group: 'sso'
packages-to-scan:
- cn.com.rstone.api.controller.sso
- group: 'system'
packages-to-scan:
- cn.com.rstone.api.controller.system
- group: 'web'
packages-to-scan:
- cn.com.rstone.api.controller.web
- group: 'www'
packages-to-scan:
- cn.com.rstone.api.controller.www
- group: 'wxmp'
packages-to-scan:
- cn.com.rstone.api.controller.wxmp⚠️解决方法名称准确性
在openapi-ts-request.config.ts 配置文件中做如下定制
export default defineConfig([
{
hook: {
customFunctionName: function(data: any): string{
return data.operationId || ''
},
}⚠️解决在springboot controller方法接收form提交的文件和对象同时存在的问题
此问题解决起来比较麻烦,可以通过修改模板来解决,但由于代码量比较大这里通过新建一个文件夹存放文件,然后引入的方法使用
安装模板解析库
pnpm install -D nunjucks 新建目录结构及文件
#目录及文件结构
- openapi-ts-custome
- serviceController.ts
- serviceController.njk//serviceController.ts
import nunjucks from 'nunjucks';
import { existsSync, readFileSync } from 'fs';
import { join } from 'path';
//@ts-ignore
export const serviceController = (apis: any, context: any): any => {
// 设置输出不转义
const env = nunjucks.configure({
autoescape: false,
});
const template = readFileSync( join(__dirname, './serviceController.njk'),'utf8');
const r = nunjucks.renderString(template, {
disableTypeCheck: false,
api: apis,
genType: 'ts',
namespace: 'API',
requestOptionsType: '{ [key: string]: unknown }'
});
return r;
}//serviceController.njk
{% if api.customTemplate %}
{{ api.data }}
{% else %}
/** {{ api.desc if api.desc else '此处后端没有提供注释' }} {{ api.method | upper }} {{ api.pathInComment | safe }}{{ ' ' if api.apifoxRunLink else '' }}{{ api.apifoxRunLink }} */
export function {{ api.functionName }}({
{%- if api.params and api.hasParams %}
params
{%- if api.hasParams -%}
{{ "," if api.body or api.file }}
{%- endif -%}
{%- endif -%}
{%- if api.body -%}
body
{{ "," if api.file }}
{%- endif %}
{%- if api.file -%}
{%- for file in api.file -%}
{{ file.title | safe }}
{{ "," if not loop.last }}
{%- endfor -%}
{%- endif -%}
{{ "," if api.body or api.hasParams or api.file }}
options
}
{%- if genType === "ts" -%}
: {
{%- if api.params and api.hasParams %}
// 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
params: {{ namespace }}.{{ api.typeName }}
{# header 入参 -#}
{% if api.params.header -%}
& { // header
{% for param in api.params.header -%}
{% if param.description -%}
/** {{ param.description }} */
{% endif -%}
'{{ param.name }}'
{{- "?" if not param.required }}
{{- (": " + param.type + ";") | safe }}
{% endfor -%}
}
{%- endif -%}
{%- if api.hasParams -%}
{{ ";" if api.body or api.file }}
{%- endif -%}
{%- endif -%}
{%- if api.body -%}
body: {{ api.body.type }}
{{ ";" if api.file }}
{%- endif %}
{%- if api.file -%}
{%- for file in api.file -%}
{{ file.title | safe }}
{{- "?" if not api.file.required -}}
: globalThis.File {{ "[]" if file.multiple }}
{{ ";" if not loop.last }}
{%- endfor -%}
{%- endif -%}
{{ ";" if api.body or api.hasParams or api.file }}
options?: {{ requestOptionsType }}
}
{%- endif -%}
)
{
{% if api.params and api.params.path %}
const { {% for param in api.params.path %}'{{ param.name }}': {{ param.alias }}, {% endfor %}
{% if api.params.path -%}
...queryParams
{% endif -%}
} = params;
{% endif %}
{%- if api.hasFormData -%}
const formData = new FormData();
{% if api.file -%}
{% for file in api.file %}
if({{ file.title | safe }}) {
{% if file.multiple %}
{{ file.title | safe }}.forEach(f => formData.append('{{ file.title | safe }}', f || ''));
{% else %}
formData.append('{{ file.title | safe }}', {{ file.title | safe }})
{% endif %}
}
{% endfor %}
{%- endif -%}
{% if api.body %}
Object.keys(body).forEach(ele => {
{% if genType === "ts" %}
const item = (body as { [key: string]: any })[ele];
{% else %}
const item = body[ele];
{% endif %}
if (item !== undefined && item !== null) {
{% if genType === "ts" %}
if (typeof item === 'object' && !(item instanceof globalThis.File)) {
if (item instanceof Array) {
item.forEach((f) => formData.append(ele, f || ''));
} else {
{# 修正开始: 在 mediaType: "multipart/form-data" 时没有做 new Bolod 的问题 -#}
{% if api.hasHeader and api.body.mediaType and api.body.mediaType == 'multipart/form-data' %}
formData.append(ele, new Blob([JSON.stringify(item)], {type: 'application/json'})) ;
{% else %}
formData.append(ele, JSON.stringify(item));
{% endif %}
{# 修正结束 -#}
}
} else {
formData.append(ele, item);
}
{% else %}
formData.append(ele, typeof item === 'object' ? JSON.stringify(item) : item);
{% endif %}
}
});
{% endif %}
{% endif %}
{% if api.hasPathVariables or api.hasApiPrefix -%}
return request{{ ("<" + api.response.type + ">") | safe if genType === "ts" }}(`{{ api.path | safe }}`, {
{% else -%}
return request{{ ("<" + api.response.type + ">") | safe if genType === "ts" }}('{{ api.path }}', {
{% endif -%}
method: '{{ api.method | upper }}',
{%- if api.response.responseType %}
responseType: '{{ api.response.responseType }}',
{%- endif %}
{%- if api.hasHeader and api.body.mediaType %}
headers: {
{%- if api.body.mediaType %}
'Content-Type': '{{ api.body.mediaType | safe }}',
{%- endif %}
},
{%- endif %}
{%- if api.params and api.hasParams %}
params: {
{%- for query in api.params.query %}
{% if query.schema.default -%}
// {{ query.name | safe }} has a default value: {{ query.schema.default | safe }}
'{{ query.name | safe }}': '{{query.schema.default | safe}}',
{%- endif -%}
{%- endfor -%}
...{{ 'queryParams' if api.params and api.params.path else 'params' }},
{%- for query in api.params.query %}
{%- if query.isComplexType %}
'{{ query.name | safe }}': undefined,
...{{ 'queryParams' if api.params and api.params.path else 'params' }}['{{ query.name | safe }}'],
{%- endif %}
{%- endfor -%}
},
{%- endif %}
{%- if api.hasFormData %}
data: formData,
{%- elseif api.body %}
data: body,
{%- endif %}
...(options || {{ api.options | dump }}),
});
}
{% endif %}
在openapi-ts-request.config.ts 配置文件中做如下定制「⚠️如果有多个组,则每个组都要配置」
//引入方法
import {serviceController} from './openapi-ts-custome/serviceController'
export default defineConfig([
{
hook: {
customTemplates: {
serviceController
}
}])