Skip to content

Latest commit

History

15 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Bridge

Bridge用于为SpringMvc项目生成TypeScript客户端代码。并且为基于TS的前端项目提供类型提示。实现前后端自动对接。

支持Java和Kotlin语言。

对于后端开发,不再需要编写接口文档。
对于前端开发,不再需要根据文档编写对接代码。 通过Bridge调用接口可以获得类似RPC一样调用本地方法的体验。

开始

Maven 引入依赖

<dependency>
<groupId>io.github.swqxdba</groupId>
<artifactId>bridge</artifactId>
<version>0.0.9</version>
</dependency>

在项目中 新建一个Main方法然后运行

publicclassApiGenerator {
publicstaticvoidmain(String[] args) {
genCode();
}
privatestaticvoidgenCode() {
//可以修改生成的Ts属性名 比如根据JSON框架的注解BridgeGlobalConfig.setPropertyNameConverter(newPropertyNameConverter() {
@NotNull@OverridepublicStringconvert(@NotNullStrings, @NotNullClass<?> aClass) {
try {
FielddeclaredField = aClass.getDeclaredField(s);
JsonPropertydeclaredAnnotation = declaredField.getDeclaredAnnotation(JsonProperty.class);
if(declaredAnnotation!=null){
returndeclaredAnnotation.value();
}
JSONFieldjsonField = declaredField.getDeclaredAnnotation(JSONField.class);
if(jsonField!=null){
returnjsonField.name();
}
returns;
} catch (NoSuchFieldExceptione) {
returns;
}
}
});
//输入你项目的源码的绝对文件路径 该路径用于生成代码注释。如果不设置,生成的TS代码中就没有注释了。BridgeGlobalConfig.setSourceCodeDir("C:\\Users\\yourname\\IdeaProjects\\yourprojectname\\src");
//指定扫描一个类所在包下的所有controller,可以指定一个条件来进行过滤List<ControllerMeta> controllerMetas = BridgeUtil.scan(Application.class,
name -> name.contains("Controller"));
//可以忽略一些controller的方法参数List<Class<?>> ignoreParamParentTypes = newArrayList<>();
ignoreParamParentTypes.add(ServletRequest.class);
ignoreParamParentTypes.add(ServletResponse.class);
TsGenGlobalConfig.getIgnoreParamParentTypes().addAll(ignoreParamParentTypes);
TsGeneratortsGenerator = newTsGenerator();
//生成时覆盖旧的文件tsGenerator.setOverrideFile(true);
//设置生成的Ts路径,请确保生成的路径存在 可能需要手动创建一下文件夹tsGenerator.setBasePath("C:\\Users\\yourname\\WebstormProjects\\yourprojectname\\src\\api");
//生成时 会把上述文件夹清空后重新生成tsGenerator.setCleanBasePath(true);
//执行Ts代码生成tsGenerator.generate(controllerMetas);
}
}

更多配置

详见 TsGenGlobalConfigBridgeGlobalConfig

前端接入

生成的文件中 有两个特殊的文件 Apis.ts 和 HttpClient.ts

Apis.ts中包含了所有的接口,是一个API聚合类,你可以在某个地方创建一个Apis对象然后export出去。

HttpClient.ts定义了一个接口,用于实现发送请求,你需要自己实现这个接口。你可以使用Axios或者Fetch。

HttpClient.ts

/** * 用于发送请求的适配器 */exportinterfaceHttpClient{/** * * @param url 相对路径 如/api/login * @param method 请求方法 如 'get' 'post' * @param body 请求体 * @param contentType 请求体类型 */request<T>(url: string,method: string,body: any,contentType:string): Promise<T>}

这里提供一个参考实现
ApiUtil.ts

importaxios,{AxiosRequestConfig}from"axios";import{token}from"@/stores";import{HttpClient}from"@/api/api/HttpClient";import{Apis}from"@/api/api/Apis";import{Response}from"@/api/types/Response";import{ElMessage}from"element-plus";constclient=axios.create()//你可以在这里带上你的cookie header等发送你的鉴权信息client.interceptors.request.use((context)=>{if(context.headers){context.headers['x-access-token']=token.value||''}returncontext})/** * 需要实现一个httpClient,可以使用axios fetch等都行。可以在这里做统一处理或者过滤、拦截 */classHttpClientImplimplementsHttpClient{request<T>(url: string,method: string,body: any,contentType: string): Promise<T>{//这里可以从获取项目的基础路径 url='YOUR_BASE_URL'+urlletconfig: AxiosRequestConfig={
url,
method
}config.headers={'Content-Type': contentType}if(contentType==='application/json'){for(letkeyinbody){if(body[key]===undefined){body[key]=null}}config.data=JSON.stringify(body)}elseif(contentType==='application/x-www-form-urlencoded'){leturlSearchParams=this.toUrlSearchParams(body);if(method==='get'||method==='GET'){config.url+='?'+urlSearchParams.toString()}else{config.data=urlSearchParams}}elseif(contentType==='multipart/form-data'){config.data=this.toFormData(body)}//对于返回的响应进行处理returnclient.request(config).then(response=>{if(response.status!==200){thrownewError('请求失败,请稍后重试')}constresp=response.dataif(resp.code==='0000'){returnrespasT}else{ElMessage.error(resp.message)console.log('resp',resp)thrownewError(resp.message||'')}})}privatetoUrlSearchParams(body: any): URLSearchParams{letparam: URLSearchParamsif(bodyinstanceofURLSearchParams){param=body}else{param=newURLSearchParams()for(letkeyinbody){constbodyElement=body[key];if(Array.isArray(bodyElement)){(bodyElementasArray<any>).forEach(item=>{param.append(key,item)})}else{param.append(key,bodyElement)}}}returnparam}privatetoFormData(body: any): FormData{letformData: FormDataif(bodyinstanceofFormData){formData=body}else{formData=newFormData()for(letkeyinbody){constvalue=body[key];if(valueinstanceofArray){value.forEach((item: any)=>{formData.append(key,item)})}if(valueinstanceofFileList){for(leti=0;i<value.length;i++){formData.append(key,value.item(i)asany)}}else{formData.append(key,value)}}}returnformData}}/** * 使用httpClient实现构造聚合Api对象 该对象中有所有的接口 *///实际使用时,将这个对象导出到项目中使用exportconstapis=newApis(newHttpClientImpl());

在上述文件中,我们导出了一个apis全局变量。 那么在其他前端代码中,我们可以直接引入使用。

import{apis}from"./ApiUtil";constuser=awaitapis.UserApi.findUser(userId)

注释搬运

Bridge会在生成TS代码时,从你的java或者kotlin代码中搬运代码注释
包括Controller类上的注释,Controller方法上的注释
其他Dto、Vo类型中类上的注释和字段的注释。
暂不支持搬运继承自父级的注释。

支持文档注释和行内注释

privateStringname;//用户名/** * 用户名 */privateStringname;

支持读取getter方法上的注释 getName方法上的注释会被放到name属性中

//用户名publicStringgetName(){
}

除了自动搬运,你还可以通过@ApiComment注解来指定注释。

(注意,注释解析是通过正则实现的,并不严谨,一些情况下可能会匹配到错误的注释或者匹配不到注释, 目前已知在碰到有跨越多行的注解(Annotation,不是注释)时会找不到注释。
这种情况下可以使用@ApiComment来覆盖,或者保证注解只占一行)

@ApiComment("读取用户")
@PostMapping(value = "get", produces = MediaType.APPLICATION_JSON_VALUE)
publicUsergetUser(){}
/** * 也可以改成这样 让注解只占一行 */@PostMapping(value = "get", produces = MediaType.APPLICATION_JSON_VALUE)
publicUsergetUser(){}

@ApiIgnore

如果你想要某个API被生成时排除,可以在Controller类上加上@ApiIgnore注解来排除整个Controller,
或者在Controller的某个方法上加上@ApiIgnore注解进行单独的排除。

实现细节

Bridge通过运行时反射来扫描读取Controller,然后通过对源码进行正则匹配来寻找方法、属性上的注释。
这意味着可能会触发一些类加载逻辑,或者执行静态代码块中的代码,需要注意。

JavaBean

Bridge会为接口参数、返回值生成对应的TS类型定义,
这个过程中是通过JavaBean规范来读取Dto、Vo上的属性。而不是通过Field读取的。
如BeanUtils.getProperties()

使用建议

建议由一个开发人员在开发环境下运行代码生成,直接生成到前端项目路径中,然后通过git提交到前端代码库中

About

spring to typescript code

Resources

Stars

1 star

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages