码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 基于Java后端与Typescript前端的代码自动生成 - malcolmcrum


    Java 后端和 Typescript 前端虽然都是类型语言,但传统上这两个域上的类型之间存在脱节。本文推荐的这个工具让我们在一个地方修改一个方法或类,并立即在其他地方直接使用它,或者在我们误用它时在编译时看到错误。

    这个工具捕获了如此多的错误并使开发速度如此之快,以至于我现在无法想象没有它可以工作。

    我知道有一些类似的工具,但它们要么增加了大量的复杂性(OpenAPI),要么被锁定在某个堆栈中(Remix)。构建我们自己的以相对较小的成本提供了显着的控制(即它并不那么复杂)。

    通常,我们有一个 Java 后端,它公开 HTTP 端点供我们的前端调用。,这个后端提供简单的CRUD创建/读取/更新/删除,有些做特定的请求,还有很多要记住的返回许多不同类型的对象。

    因为我们非常依赖这些 API 调用,所以我们在工具上投入了大量精力,以使我们的开发人员尽可能无缝地进行前端/后端通信。

    在我详细介绍之前,这里有一个简短的概述:

    • Java 接口描述了每个 API 及其端点
    • 每个接口都通过一个特殊的 Javalin 处理程序连接到其后端实现,该处理程序通过 HTTP 公开每个端点,解析参数,然后调用实现
    • 一些自定义工具循环遍历每个接口并为每个接口生成一个 Typescript 类,以及调用每个端点的方法
    • Java 库typescript  -generator  为 API 的参数和返回值中使用的所有类生成 Typescript 定义

    最终结果是,当我们向后端添加端点时,我们的 API 客户端会自动生成调用它的方法,从而使前端到后端的调用几乎与本机调用一样简单。

    继续阅读以了解其工作原理,或查看 Github 上的  演示 。

    API 定义

    这是简单的部分。我们的 API 接口如下所示:

    1. interface UserApi {
    2. UserDto getUser(int userId);
    3. }

    我们 API 的后端实现

    Javalin 是一个出色的网络服务器,它提供了我们所需的功能与简单性之间的平衡。后端调用可能如下所示:

    1. POST /api/UsersApi/getUser
    2. {
    3. 'userId': 1001
    4. }

    因此,我们为每个调用创建一个处理程序。它涉及一些反射,这有点毛茸茸,但让开发人员的事情变得更容易:

    1. public static void main(){
    2. var app = Javalin.create();
    3. // UsersApi is the interface that defines the endpoints.
    4. // UsersService is the backend implementation of UsersApi.
    5. // We repeat the below for every API we want to expose.
    6. expose(app,UsersApi.class, new UsersService())
    7. app.start()
    8. }
    9. private void expose(Javalin app, Class api, T implementation) {
    10. String apiName = api.getSimpleName();
    11. for (Method method : api.getMethods()) {
    12. // handle calls to, for example, POST /api/UsersAPI/getUser
    13. app.post("/api/" + apiName + "/" + method.getName(), (ctx) -> {
    14. Map body = ctx.bodyAsClass(Map.class);
    15. List args = new ArrayList<>();
    16. for (Parameter param : method.getParameters()) {
    17. String json = body.get(param.getName());
    18. var arg = GSON.fromJson(json, param.getParameterizedType());
    19. args.add(arg);
    20. }
    21. try {
    22. Object result = method.invoke(implementation, args.toArray());
    23. String json = objectMapper.writeValueAsBytes(result);
    24. ctx.result(json);
    25. } catch (Exception e) {
    26. throw new RuntimeException("Failed to invoke " + apiName + "/" + method, e);
    27. }
    28. });
    29. }
    30. }
    31. 差不多就是这样。对于每个 API,然后是每个方法,公开一个对给定参数进行反序列化的端点,然后使用这些参数调用实际实现的方法。

      我在这里要注意的唯一特别之处是,我们的请求主体不是我们可以立即反序列化的单个对象,而是最好将其视为 JSON 字符串的参数名称的键值对。所以它本质上是双重序列化的 JSON。

      所以!我们的后端已准备好接收请求。接下来是 API 客户端。

      Typescript客户端

      这里的代码和上面的代码有点相似——给定一个像UsersAPI这样的接口,迭代它的方法,并迭代它的参数。但是,在此过程中,我们通过向字符串附加一些 Typescript 来构建字符串。这里的代码有点难看,所以我要写一些伪代码来描述它:

      1. String toTypescript(Class... api) {
      2. for each api:
      3. typescript += "class ${api.getSimpleName()} {"
      4. for each method:
      5. typescript += "${method.getName()}("
      6. for each parameter:
      7. typescript += "${parameter.getName()}: ${getType(parameter)}, "
      8. typescript += "): Promise<${getType(method.returnType)}">
      9. var body = Map
      10. typescript += "return fetch('/api/${api}/${method}', {"
      11. typescript += " method: 'POST',"
      12. typescript += " headers: {'Content-Type': 'application/json', 'Accept': 'application/json'},"
      13. typescript += " body: JSON.stringify({"
      14. for each parameter:
      15. typescript += "${parameter.getName()}: JSON.stringify(${parameter.getName}),
      16. typescript += " }"
      17. typescript += "}).then(res => res.json())
      18. typescript += "}"
      19. return typescript
      20. }
      Vue 使用typescript如何优雅的调用swagger API
      pdf 0星 超过10%的资源 105KB
      下载

      嗯……这样更易读吗?如果您愿意,可以改为阅读  实际代码 。

      以下是您可能希望在生成的 Typescript 中看到的内容,例如:

      1. class UsersAPI {
      2. getUser(userId: number): UserDto {
      3. return fetch('/api/UsersApi/getUser', {
      4. method: 'POST',
      5. headers: {'Content-Type': 'application/json', 'Accept': 'application/json'},
      6. body: JSON.stringify({userId: JSON.stringify(userId)})
      7. }).then(res => res.json())
      8. }
      9. }

      我们将上述文件存储在target/ts/api.ts中,并使用Exec Maven插件生成该文件,该插件让我们在运行mvn package时运行客户端生成器。

      我跳过的一个魔法是getType(parameter)的调用。这可以将Java类转换为Typescript的等价物。这里基本上是转换的工作原理。

      • String -> string
      • int, Integer, float, Float, double, Double, long, Long -> number
      • Object -> any
      • Array, List, Set, Collection -> Array
      • Map -> Record
      • 否则,就使用对象的类名(如UserDto)。

      现在你几乎已经准备好调用new UsersApi().getUser(1001) - 我们只是缺少返回的UserDto的Typescript类型。

      为我们的Java类型提供Typescript定义

      这个问题很简单。我们有一个Java包,里面有我们想在前端使用的所有类型(com.company.dtos),我们把Maven插件typescript-generator指向它。

      1. cz.habarta.typescript-generator
      2. typescript-generator-maven-plugin
      3. 2.32.889
      4. generate
      5. generate
      6. compile
      7. com.company.dto.**
      8. target/ts/types.ts

      假设我们有一个这样的 UserDto 类:

      基于模型的开发与自动代码生成.pptx
      pptx 0星 超过10%的资源 8.00MB
      下载
      public record UserDto(int userId, String username) {}

      我们最终会得到一个types.ts像这样的文件:

      1. interface UserDto {
      2. userId: number,
      3. username: string
      4. }

      把它们放在一起

      所以,现在我们有:

      • 一个 Javalin 服务器,其端点准备好反序列化我们每个后端方法的参数
      • api.ts准备好查询每个端点的类的文件
      • types.ts描述这些端点的参数和返回的类型的文件

      最终结果是添加新端点如下所示:

      1. 添加void changeUsername(int userId, String newUsername)对接口的调用,并在后端实现
      2. 运行mvn package以更新我们的 Typescript 文件
      3. 在前端,写new UsersService().changeUsername(1001, "foo")- 就是这样!

      注意事项

      在此过程中,我们学到了一些值得注意的教训,包括:

      • JavaMap比 Javascript 对象灵活得多。特别是在 Javascript 对象只能有字符串作为键,所以不要返回 Map
      • Javascript没有方法重载,所以如果你声明a getUser() ,getUser(int userId)你会遇到问题
    32. 相关阅读:
      java计算机毕业设计计算机公共课程学习资源管理系统MyBatis+系统+LW文档+源码+调试部署
      【云原生之k8s】kubernetes原理
      PHP反序列化链分析
      I/O多路复用:解锁服务器高性能的钥匙
      vue 实现在线预览Excel-LuckyExcel/LuckySheet实现方案
      负载均衡 - F5
      【Mac使用技巧】Mac和iPhone间接力功能失效解决总结
      Node.js -- 模块化
      java计算机毕业设计高原特色农产品网站设计源码+mysql数据库+系统+lw文档+部署
      windows上安装MongoDB 4.4.16(详细教程)
    33. 原文地址:https://blog.csdn.net/guanshengg/article/details/126379312
      • 最新文章
      • 攻防演习之三天拿下官网站群
        数据安全治理学习——前期安全规划和安全管理体系建设
        企业安全 | 企业内一次钓鱼演练准备过程
        内网渗透测试 | Kerberos协议及其部分攻击手法
        0day的产生 | 不懂代码的"代码审计"
        安装scrcpy-client模块av模块异常,环境问题解决方案
        leetcode hot100【LeetCode 279. 完全平方数】java实现
        OpenWrt下安装Mosquitto
        AnatoMask论文汇总
        【AI日记】24.11.01 LangChain、openai api和github copilot
      • 热门文章
      • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
        奉劝各位学弟学妹们,该打造你的技术影响力了!
        五年了,我在 CSDN 的两个一百万。
        Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
        面试官都震惊,你这网络基础可以啊!
        你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
        心情不好的时候,用 Python 画棵樱花树送给自己吧
        通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
        13 万字 C 语言从入门到精通保姆级教程2021 年版
        10行代码集2000张美女图,Python爬虫120例,再上征途
      Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
      正则表达式工具 cron表达式工具 密码生成工具

      京公网安备 11010502049817号