本文正在参加「金石计划」 随着 TypeScript 使用越来越普遍,根据后端的接口定义,我们需要生成对应的 TypeScript 文件,包括类型定义文件和接口文件。这个纯手工转换的过程重复又机械,因此,我们完全可以将其转换过程通过程序完成,提供劳动生产力 😉 。本文记录了自己使用 node 工具实现的过程包括 js 和 ts 两个版本,当然大家也可以使用第三方包 swagger-typescript-api 去实现哦。文章末尾附源码。
swagger.json
通过浏览器 F12 查看Network 得出:我们平时看到如图的 swagger 生成的文档的数据是 petstore.swagger.io/v2/swagger.… 请求得到的。
swagger.json 中包含了项目所有的类型和接口定义。
接口返回中,
- definitions 是关于项目所有类型的定义
- paths 是所有的接口定义信息
- tags 是所有的 tag 信息,而接口则根据 tag 进行分类。在下面的生成接口定义文件中,我们可以根据 tag 进行划分,将其生成到对应的文件中。
更多信息参见:swagger.io/docs/specif…
node
我们需要将转化过程封装成一个函数,通过 Node.js 运行程序,执行方法,得到我们生成的类型文件和接口定义文件。
- 通过 axios 请求 petstore.swagger.io/v2/swagger.… 得到相应的数据
- 通过 fs 文件模块生成类型定义文件和接口文件。
生成定义文件 newTypes.ts
在生成类型定义文件中的一些约定:
- 生成的定义字段统一使用可选类型 ?
- 类型转换规则:
| 后端定义类型 | 前端类型 |
|---|---|
| number | number |
| integer | number |
| string | string |
| boolean | boolean |
| object | object |
/**
* 得到 interface 定义字符串
* @param prop interface 名称
* @param definition interface 字段信息
* @returns interface 定义字符串
*/
const getDefinitionType = (prop, definition) => {
const cleanProp = prop.replace('«', '').replace('»', '')
let types = `export interface ${cleanProp} {\n`
const properties = definition.properties
for(let p in properties){
const detail = properties[p]
const cur = getType(detail)
if(detail.description){
types += ` /** ${detail.description} */\n`
types += ` ${p}?: ${cur}\n\n`
}else{
types += ` ${p}?: ${cur}\n`
}
}
types += `}\n\n`
return types
}
根据 tag 生成相应的接口文件
关于请求参数的类型包括:
- query parameters, such as
/users?role=admin - path parameters, such as
/users/{id} - body parameters that describe the body of POST, PUT and PATCH requests (see Describing Request Body)
- header parameters, such as
X-MyHeader: Value - form parameters – a variety of body parameters used to describe the payload of requests with
Content-Typeofapplication/x-www-form-urlencodedandmultipart/form-data(the latter is typically used for file uploads)
我们主要需要处理 query、path 以及 body 这三种类型:
注意:根据 swagger.io/docs/specif… 文档说明,其中 query 和 path 参数只能是基本类型,而 body 类型的参数可以是基本类型也可以是对象类型,并且 body 只会有一个。
/**
* @param basePath 请求的 basePath
* @param paths 具体URL对象
*/
const handleService = (basePath, paths)=>{
for(let prop in paths){
const content = paths[prop]
const methodsArr = Object.keys(content)
for(let fetchMethod of methodsArr){
const serviceName = getServiceName(prop, fetchMethod)
const methodValueObj = content[fetchMethod]
const curTag = methodValueObj.tags.pop()
const tagInfo = globalTags.find(item => item.name === curTag)
let serviceStr = tagInfo.serviceStr
serviceStr += `/**\n* @description ${methodValueObj.description}\n* @tags ${curTag}\n* @request ${fetchMethod}:${prop}\n*/\nexport const ${serviceName} = `
const importType = tagInfo.importType
// 返回值类型
const normalResponse = methodValueObj.responses['200'] ? methodValueObj.responses['200'].schema : undefined
const resType = normalResponse ? getType(normalResponse) : undefined
const convertTmp = resType?.replace('[', '')?.replace(']', '')
if((normalResponse?.$ref || normalResponse?.items?.$ref) && !importType.includes(convertTmp)){
// 预设类型没有
if(!Object.values(convertTypeMap).includes(convertTmp)){
importType.push(convertTmp)
}
}
const queryParams = []
const pathParams = []
const parameters = content[fetchMethod].parameters
let bodyStr = ''
for(let paramsItem of parameters){
const inValue = paramsItem.in;
const convertItem = {
name: paramsItem.name,
type: getType(paramsItem),
required: paramsItem.required,
}
if(inValue === 'body'){
// 只会有一个 body
bodyStr = getType(paramsItem.schema)
if(paramsItem.schema?.$ref){
if(!importType.includes(bodyStr)){
importType.push(bodyStr)
}
}
}else if(inValue==='query'){
// such as /users?role=admin
queryParams.push(convertItem)
}else if(inValue === 'path'){
// such as /users/{id}
pathParams.push(convertItem)
}
}
const queryStr = getParamsStr(queryParams)
const pathStr = handlePathParams(pathParams)
serviceStr += `(${queryStr ? 'params: '+ queryStr+', ' : ''}data: ${bodyStr ? bodyStr : '{}'},${pathStr}) => axios.${fetchMethod}${resType ? '<'+resType+'>' : ''}(\`${basePath}${convertPath(prop)}\`, {${queryStr ? ' params, ' : ' '}data })\n\n`
tagInfo.serviceStr = serviceStr
tagInfo.importType = importType
}
}
globalTags.forEach(tagFile => {
const fileName = lowerWord(tagFile.description.replace(/\s*/g, '')) + '.ts'
const dftStr = getImportStr(tagFile.importType) + tagFile.serviceStr
fs.writeFile(fileName, dftStr, (err)=>{
if(err) throw err;
console.log(`${fileName} is created successfully`)
})
})
}
对示例 JSON 解析得到如下文件:
然后我们就可以在项目中使用啦。
源码
源码地址: github.com/YY88Xu/swag…