继续上一篇文章《unionj-generator快速上手-后端篇》,本文介绍如何通过unionj-generator生成前端代码并在vue项目中使用。
网页版代码下载器
我们为了减轻后端同事帮前端同事生成客户端代码的负担,开发了一个网页版代码下载器,可以点击openapi-svc项目github仓库地址查看源码。
本地开发
- 下载unionj-generator源码
git clone git@github.com:unionj-cloud/unionj-generator.git
- 切到v1.6.0分支
git checkout tags/v1.6.0 -b v1.6.0
- 安装unionj-generator到本地maven仓库
mvn clean install -Dmaven.test.skip=true
- 下载openapi-svc源码:
git clone git@github.com:unionj-cloud/openapi-svc.git
- 编译打包
mvn clean install -Dmaven.test.skip=true
- 打包docker镜像
cd svc-server && docker build -t openapi-svc .
- 启动容器
docker run -it --rm -p 8080:8080 openapi-svc
- 打开浏览器,看到如下截图所示的界面即表示启动成功
- 推送镜像到远程镜像仓库
docker tag openapi-svc $yourdockerimageregisry/openapi-svc:$version
docker push $yourdockerimageregisry/openapi-svc:$version
k8s部署
- 下载k8s部署文件
wget命令
wget https://github.com/unionj-cloud/openapi-svc/blob/main/svc-server/deployment.yaml
curl命令
curl https://github.com/unionj-cloud/openapi-svc/blob/main/svc-server/deployment.yaml -o deployment.yaml
- 修改ingress配置(用于公网访问)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: openapi-ingress
spec:
rules:
- host: RLACE_WITH_YOUR_OWN_HOST
http:
paths:
- path: /openapi-server
pathType: Prefix
backend:
service:
name: openapi-service
port:
number: 8080
RLACE_WITH_YOUR_OWN_HOST: 替换成你的k8s ingress地址
- 部署
kubectl apply -f deployment.yaml
接口
有两个接口地址可供发起请求,用于制作CI/CD工具或者前端工程化工具
POST /ts/upload: 上传OpenAPI 3.0规范的json文档下载代码zip压缩包
请求参数:file
返回参数:二进制流GET /ts/url:通过url参数传递OpenAPI 3.0规范的json文档下载地址下载代码zip压缩包
请求参数:url
返回参数:二进制流
后端接口改造
为了配合本文,我们对上一篇文章的后端接口代码做了修改,引入了内嵌的mongodb,在内存里存储数据,用户id类型改成了string字符串类型,然后实现了接口,保证前端可以正常请求到。
POM
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
</dependency>
UserController
package cloud.unionj.guide.api.controller;
import cloud.unionj.guide.proto.UserProto;
import cloud.unionj.guide.vo.*;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBObject;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
/**
* @author created by wubin
* @version v0.0.1
* description: cloud.unionj.guide.api.controller
* date:2021/6/9
*/
@RestController
@RequiredArgsConstructor
public class UserController implements UserProto {
private final MongoTemplate mongoTemplate;
@SneakyThrows
@Override
public ResponseEntity<byte[]> getApiUserAvatar(String id) {
UserDetailVO vo = mongoTemplate.findById(id, UserDetailVO.class, "guide");
File file = new File(vo.getAvatar());
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + file.getName())
.header(HttpHeaders.CONTENT_TYPE, "application/octet-stream")
.body(Files.readAllBytes(Paths.get(vo.getAvatar())));
}
@Override
public ResultVO<UserDetailVO> getApiUserDetail(String id) {
UserDetailVO vo = mongoTemplate.findById(id, UserDetailVO.class, "guide");
ResultVO<UserDetailVO> ret = new ResultVO<>();
ret.setCode(0);
ret.setData(vo);
return ret;
}
@SneakyThrows
@Override
public ResultVO<String> postApiUserEdit(String name, Integer age, String sex, String id, MultipartFile avatar) {
byte[] bytes = avatar.getBytes();
String outputDir = System.getProperty("user.dir") + "/output";
new File(outputDir).mkdirs();
Path path = Paths.get(outputDir + "/" + avatar.getOriginalFilename());
Files.write(path, bytes);
DBObject user = BasicDBObjectBuilder.start()
.add("name", name)
.add("age", age)
.add("sex", sex)
.add("avatar", path.toString())
.add("_id", new ObjectId(id))
.get();
mongoTemplate.save(user, "guide");
ResultVO<String> ret = new ResultVO<>();
ret.setCode(0);
ret.setData("OK");
return ret;
}
@Override
public ResultVO<PageResultVO<UserDetailVO>> postApiUserPage(UserPageReqVO body) {
List<UserDetailVO> vos = mongoTemplate.findAll(UserDetailVO.class, "guide");
PageResultVO<UserDetailVO> pageResultVO = new PageResultVO<>();
pageResultVO.setPages(1);
pageResultVO.setCurrent(1);
pageResultVO.setSize(10);
pageResultVO.setTotal(1l);
pageResultVO.setItems(vos);
ResultVO<PageResultVO<UserDetailVO>> ret = new ResultVO<>();
ret.setCode(0);
ret.setData(pageResultVO);
return ret;
}
@Override
public ResultVO<UserRegisterRespVO> postApiUserRegister(String username, String password) {
DBObject user = BasicDBObjectBuilder.start()
.add("username", username)
.add("password", password)
.get();
DBObject savedUser = mongoTemplate.save(user, "guide");
UserRegisterRespVO vo = new UserRegisterRespVO();
vo.setId(((ObjectId) savedUser.get("_id")).toString());
ResultVO<UserRegisterRespVO> ret = new ResultVO<>();
ret.setCode(0);
ret.setData(vo);
return ret;
}
}
处理跨域cors问题
@SpringBootApplication
@ComponentScan(basePackages = {"cloud.unionj.guide"})
public class ApiApplication {
public static void main(String[] args) {
SpringApplication.run(ApiApplication.class, args);
}
@Bean
public CorsFilter corsFilter() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.addAllowedHeader("*");
configuration.addAllowedMethod("*");
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return new CorsFilter(source);
}
}
使用ts客户端代码
准备脚手架
我们为本文准备了一个vue2+ts的脚手架工程,可直接用于项目开发
- 克隆代码
git clone git@github.com:unionj-cloud/vue2-ts-scaffold.git guide-ui
- 安装依赖
npm install --registry=https://registry.npm.taobao.org
- 运行程序
yarn serve
- 浏览器打开
下载客户端代码
在上文提到的网页版代码下载器上下载ts客户端代码,解压后如图:
services文件夹里就是封装了axios的http客户端代码。将services文件夹放入上文初始化的工程的src目录下,如图
组件中使用
- 修改src/services/BizService.ts文件里serverName的值
/**
* Generated by unionj-generator.
* You can edit it as your need.
*/
import { responseUse } from "@/utils/response";
export class BizService {
static serverName: string | undefined = process.env.VUE_APP_BIZ_SERVICE;
axios: any;
constructor(axios: any) {
this.axios = axios;
this.axios.interceptors.response.use(responseUse,null);
}
protected addPrefix(endpoint: string){
if(BizService.serverName) {
if(BizService.serverName.indexOf("/") === 0 || BizService.serverName.indexOf("http") === 0) {
return BizService.serverName + endpoint
}
return "/" + BizService.serverName + endpoint
}
return endpoint
}
}
export default BizService;
- 配置guide后端接口baseUrl
VUE_APP_BIZ_SERVICE=http://localhost:8080
- 修改src/App.vue文件
这段代码是用bootstrap写了一个table组件
<template>
<div id="app">
<h1>unionj-generator-guide frontend vue2 + typescript scaffold</h1>
<b-table striped hover :items="data" :fields="fields" fixed>
<template #cell(avatar)="data">
<img :src="data.value" alt="">
</template>
</b-table>
</div>
</template>
这段代码首先在mounted钩子里new了一个UserService实例,我们在methods里通过this.userService来调用生成代码里的接口方法,发起http请求,获取返回值。下面的代码里以调用postApiUserPage方法为例,说明了使用方法,非常直观和简单。
<script lang="ts">
import Vue from 'vue'
import axios from 'axios'
import UserService from './services/UserService'
import { ResultVOPageResultVOUserDetailVO, UserDetailVO, UserPageReqVO } from './services/types'
export default Vue.extend({
name: 'App',
data() {
return {
userService: {} as UserService,
fields: [
{
key: 'id',
label: 'ID',
},
{
key: 'avatar',
label: '头像',
},
{
key: 'name',
label: '姓名',
},
{
key: 'age',
label: '年龄',
},
{
key: 'sex',
label: '性别',
},
],
data: [] as UserDetailVO[],
}
},
mounted() {
this.userService = new UserService(axios)
this.getAllUser()
},
methods: {
getAllUser() {
const payload = {
size: 10,
current: 1,
} as UserPageReqVO
this.userService.postApiUserPage(payload).then((resp: ResultVOPageResultVOUserDetailVO) => {
if (resp.code === 0) {
this.data = resp.data.items
this.data.forEach((x) => {
x.avatar = process.env.VUE_APP_BIZ_SERVICE + "/api/user/avatar?id=" + x.id
})
}
})
},
},
})
</script>
通过postman准备数据
- 注册用户
- 编辑用户信息
- 查看数据
启动项目看效果
总结
通过上文的讲解和效果展示,我们可以看出unionj-generator生成的typescript客户端代码是非常易用和直接的。通过在自己团队部署网页版下载器,可以轻松下载到代码,放入前端工程中使用。请点击链接查看文本中演示项目的源码。其实还有更高级的玩法,那就是写一个命令行代码下载器,写进package.json里的scripts里,通过npm scripts脚本命令来直接下载代码压缩包,解压到指定的目录。我们已经写好一个工具,已在团队中使用,介绍文章《unionj-generator快速上手-命令行客户端下载器》正在写作中...