unionj-generator快速上手-前端篇

Photo by Proxyclick Visitor Management System

继续上一篇文章《unionj-generator快速上手-后端篇》,本文介绍如何通过unionj-generator生成前端代码并在vue项目中使用。

网页版代码下载器


我们为了减轻后端同事帮前端同事生成客户端代码的负担,开发了一个网页版代码下载器,可以点击openapi-svc项目github仓库地址查看源码。

本地开发

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
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
  • 打开浏览器,看到如下截图所示的界面即表示启动成功
image
  • 推送镜像到远程镜像仓库
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
  • 浏览器打开
image

下载客户端代码

在上文提到的网页版代码下载器上下载ts客户端代码,解压后如图:

image

services文件夹里就是封装了axios的http客户端代码。将services文件夹放入上文初始化的工程的src目录下,如图

image

组件中使用

  • 修改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准备数据

  • 注册用户
image
  • 编辑用户信息
image
  • 查看数据
image

启动项目看效果

image

总结


通过上文的讲解和效果展示,我们可以看出unionj-generator生成的typescript客户端代码是非常易用和直接的。通过在自己团队部署网页版下载器,可以轻松下载到代码,放入前端工程中使用。请点击链接查看文本中演示项目的源码。其实还有更高级的玩法,那就是写一个命令行代码下载器,写进package.json里的scripts里,通过npm scripts脚本命令来直接下载代码压缩包,解压到指定的目录。我们已经写好一个工具,已在团队中使用,介绍文章《unionj-generator快速上手-命令行客户端下载器》正在写作中...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容

  • unionj-generator[https://github.com/unionj-cloud/unionj-g...
    wubin1989阅读 320评论 0 3
  • https://juejin.im/post/5d3a7134f265da1b5d57f1ed# 一个人走的更快,...
    videring阅读 631评论 0 0
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,561评论 18 139
  • 作者:范圣刚 链接:https://www.zhihu.com/question/268528001/answer...
    丶温瞳阅读 399评论 0 1
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,518评论 28 53