Motivation
在写js的时候,经常为某些结构量没有类型而感到痛苦,访问属性必须查阅另外的手册,在typescript出来以后,可以为某些常用的结构定义类型暗示,在使用时就有类型检查和自动补全了。
于是,前后端交互的接口,是不是也应该有某种工具,自动生成ts版本的接口呢?让js开发能够直接传入具有类型的参数,返回值也有对应的类型,restful的api会变得非常简单,js调用后端接口等同于调用生成出来的函数。而且,在生成函数这里,还可以实现切面编程,做一些mock、filter的工作。
最后,我找到了yapi、rap这样的工具,不过在考察的时候,意识到grpc也可能是不错的选择。
Yapi
首先,java开发后端时,使用idea插件,可以将controller定义的接口,推送至yapi。插件还支持一些注解,提供mock、注释等功能。
前端可以用yapi-to-ts生成ts代码。
java插件用起来没什么大问题,推送很方便。
web插件有些许的问题,作者保留了一些接口,让用户来填写实现,比如最终生成的请求方式,是需要自己实现的。
我实现了一个用fetch请求后端的版本,同时还支持路径参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| const baseUrl = options.server === 'mock' ? payload.mockUrl : options.server === 'dev' ? payload.devUrl : payload.prodUrl
let url = `${baseUrl}${payload.path}`
if (payload.paramNames.length > 0) { const pathParas: string[] = [] url = url.replace(new RegExp('\\{(\\w*)\\}'), (s, paraName) => { pathParas.push(paraName); return payload.data[paraName] })
const urlParas = payload.paramNames.filter((value) => pathParas.includes(value)) url = url + '?' urlParas.forEach((value) => { url = url + value + '=' + payload.data[value] + '&' }) } const bodyParas: string[] = Object.keys(payload.data).filter((value: string) => !payload.paramNames.includes(value)) let req if (payload.requestBodyType === RequestBodyType.form) { const formdata = new FormData() bodyParas.forEach(value => { formdata.append(value, payload.data[value]) }) req = fetch(url, { method: payload.method, headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: formdata }) } else if (payload.requestBodyType === RequestBodyType.json) { req = fetch(url, { method: payload.method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload.data, bodyParas) }) } else { req = fetch(url, { method: payload.method }) }
req.then((response) => { if (payload.responseBodyType === ResponseBodyType.json) { response.json().then(body => { resolve(body) }).catch(res => { reject(res) }) } else { response.text().then(body => { resolve(body as any) }).catch(res => { reject(res) }) } }).catch(res => { reject(res) })
|
生成函数的名字,要符合js的规范,所以也稍作调整,把一些非法字符去掉。
idea插件推送的类型名称和yapi默认名称不对应,导致前端插件无法正常识别,做一个预处理转换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| import { Config, ExtendedInterface, ChangeCase, Interface } from 'yapi-to-typescript'
const config: Config = [ { serverUrl: 'http://', typesOnly: false, reactHooks: { enabled: false, }, prodEnvName: 'production', devEnvName: 'dev', outputFilePath: 'src/api/index.ts', requestFunctionFilePath: 'src/api/request.ts', dataKey: '', projects: [ { token: process.env.YTT_TOKEN as string, getRequestFunctionName: (interfaceInfo: ExtendedInterface, changeCase: ChangeCase): string => { return interfaceInfo.path.replace(new RegExp("[\\+\\/\\{\\}]","g"), '') + interfaceInfo.method }, preproccessInterface: interfaceInfo => { interfaceInfo.res_body = interfaceInfo.res_body.replace(new RegExp('"(String|int|long|Interger)"', 'g'), (s, s1) => { if (s1 === 'String') { return '"string"' }else if (s1 === 'int' || s1 === 'long' || s1 === 'Interger') { return '"integer"' } }) if (interfaceInfo.req_body_other !== undefined) interfaceInfo.req_body_other = interfaceInfo.req_body_other.replace(new RegExp('"(String|int|long|Interger)"', 'g'), (s, s1) => { if (s1 === 'String') { return '"string"' }else if (s1 === 'int' || s1 === 'long' || s1 === 'Interger') { return '"integer"' } }) return interfaceInfo }, categories: [ { id: 0, } ], }, ], }, ]
export default config
|