介绍 genapi:一个 Golang HTTP Client 生成代码库

本文将为大家介绍 genapi,一个用于自动生成 Golang HTTP Client 的代码库。如果你对这个项目感兴趣,可以访问 genapi 官网 或 GitHub 仓库 获取更多技术细节。 从手工到自动:Golang HTTP Client 的演进之路 在 Golang 开发中,调用 HTTP API 是一个非常常见的需求。本文将通过一个天气 API 的示例,介绍 HTTP Client 代码是如何从手工编写演进到自动生成的。让我们看看这个简单的天气 API: GET /api/weather?city=shanghai Response: { "temperature": 25, "humidity": 60, "condition": "sunny" } 原始手工编写 最初,我们可能会直接编写如下代码: func getWeather(city string) (*Weather, error) { resp, err := http.Get("https://api.weather.com/api/weather?city=" + city) if err != nil { return nil, err } defer resp.Body.Close() var weather Weather if err := json.NewDecoder(resp.Body).Decode(&weather); err != nil { return nil, err } return &weather, nil } 这种方式简单直接,但存在以下问题: URL 硬编码在代码中 参数拼接容易产生错误 错误处理逻辑重复 响应解析代码重复 模板化请求 为了解决上述问题,我们开始对代码进行抽象和模板化改造: type Client struct { baseURL string client *http.Client } func (c *Client) doRequest(method, path string, query url.Values, result interface{}) error { u, _ := url.Parse(c.baseURL + path) u.RawQuery = query.Encode() req, err := http.NewRequest(method, u.String(), nil) if err != nil { return err } resp, err := c.client.Do(req) if err != nil { return err } defer resp.Body.Close() return json.NewDecoder(resp.Body).Decode(result) } func (c *Client) GetWeather(city string) (*Weather, error) { query := url.Values{} query.Set("city", city) var weather Weather err := c.doRequest("GET", "/api/weather", query, &weather) return &weather, err } 这样的改进带来了以下好处: ...

三月 3, 2025 · 2 分钟 · Lex Cao

「翻译」复式记账法 (The Double-Entry Counting Method)

翻译自 The Double-Entry Counting Method 介绍 本文是一份关于复式记账的简要介绍,从计算机科学家的角度撰写。它试图以尽可能简单的方法解释基础记账,简化会计中通常涉及到的某些特殊性。它也代表了 Beancount 的工作方式,并且对所有使用纯文本记账的用户都应该适用。 请注意,我不是会计师,在编写此文档过程中,我可能使用与传统会计培训教授略有不同或不常见的术语。我给自己授权创造一些新的、甚至是不寻常的东西,以便将这些想法尽可能简单明了地解释给那些对它们不熟悉的人。 我认为每个高中生都应该在高中阶段学习复式记账法,因为这是一项极其有用的组织技能,并且我希望这篇文章可以帮助将其知识传播到专业圈以外的领域。 复式记账的基础 复式记账法只是一种简单的计数方法,只有一些简单的规则。 让我们从定义账户的概念开始。账户是一种可以容纳物品的东西,就像一个袋子。它用于计算和累积物品。让我们画一条水平箭头来直观地表示随着时间推移账户中不断变化的内容: 左侧,是描述过去,而右侧则是不断增长的时间:现在、未来等。 现在,让我们假设账户只能包含一种东西,例如美元。所有的账户都以零美元的空内容开始。我们将称账户中单位数量为账户的 余额 (Balance)。请注意,它代表了特定时间点上其内容的情况。我会使用一个数字在帐户时间轴上方绘制余额: 账户的内容会随着时间而变化。为了改变账户的内容,我们必须向其添加一些东西。我们将这个添加称为对账户的记账,我会在该账户的时间轴上画一个带圈数字来表示这种变化,例如:向该账户中添加 100 美元: 现在,我们可以在记账后绘制更新后的账户余额,并在其后面加上另一个小数字: 账户加上 100 美元后,余额现在为 100 美元。 我们也可以从账户中减去一定金额。例如,我们可以减去 25 美元,这样账户余额就变成了 75 美元: 如果我们减去的金额超过账户余额,账户余额也可能变为负数。例如,如果我们从该账户中取出 200 美元,则余额现在变为 -125 美元: 账户中包含负数是完全正常的。请记住,我们所做的只是计数。很快我们会看到,有些账户在它们的时间轴上将保持负余额。 报表 (Statement) 值得注意的是,我在前一节中写下的时间线记账与机构为每个客户维护并通常通过邮件发送的纸质账户报表类似: 时间 描述 金额 余额 2016-10-02 … 100.00 1100.00 2016-10-05 .. -25.00 1075.00 2016-10-06 .. -200.00 875.00 最终结余 875.00 有时候金额栏会被分成两个,一个显示正数,另一个显示负数: ...

七月 4, 2023 · 4 分钟 · Lex Cao

Spring Data JPA 多条件连表查询最佳实践

背景 本文是 Spring Data JPA 多条件连表查询 文章的最佳实践总结。 解决什么问题? 使用 Spring Data JPA 需要针对多条件进行连表查询的场景不使用原生 SQL 或者 HQL 的时候,仅仅通过 JpaSpecificationExecutor 构造 Specification 动态条件语句来实现类型安全的多条件查询。 说明 相关上下文背景请前往 前文 了解。 这里再提一下接下来示例会用到的场景: 三个实体:作者、书、书评。其中,作者与书是一对多的关系,书与书评是一对一的关系(当然书评与读者的评价是一对多的关系,这里省去,仅用一对一来进行演示即可)。 假设有这样的后台查询条件:作者名称、书的发布时间、书评的评分。(这里每个实体取一个字段进行连表查询演示,其他字段同理)。返回书籍列表以及相关表字段。 【本文所有代码在此】 最佳实践 需要 SELECT 查询的字段,通过单独的 Java Bean 进行映射 利用 JPA 的自动实体映射结果集 @EntityGraph 注解标注返回实体需要 Fetch 的字段 无需再手动针对连表进行 fetch,解决 N+1 问题 JOIN ON 查询条件使用 join().on() 拼接 Join<Object, Object> author = root.join("author"); author.on(cb.equal(author.get("name"), param.getAuthorName())); WHERE 查询条件使用 query.where() 拼接 query.where(cb.equal(root.get("publishTime"), param.getBookPublishTime())); 代码示例 针对 Repository 需要 override 已有 findAll 方法,使用 @EntityGraph 注解 使用 @EntityGraph 注解,标注额外属性需要 fetch // BookJoinRepository.java @Repository public interface BookJoinRepository extends JpaRepository<BookJoin, String>, JpaSpecificationExecutor<BookJoin> { @Override @EntityGraph(attributePaths = { "author", "review" }) Page<BookJoin> findAll(Specification<BookJoin> spec, Pageable pageable); } 针对 Specification WHERE 查询条件使用 query.where() 拼接 JOIN ON 查询条件使用 join().on() 拼接 static Specification<BookJoin> multiQuery_04(BookJoinQuery param) { return (root, query, cb) -> { if (null != param.getBookPublishTime()) { query.where(cb.equal(root.get("publishTime"), param.getBookPublishTime())); } if (null != param.getAuthorName()) { Join<Object, Object> author = root.join("author"); author.on(cb.equal(author.get("name"), param.getAuthorName())); } if (null != param.getReviewScore()) { Join<Object, Object> review = root.join("review"); review.on(cb.equal(review.get("score"), param.getReviewScore())); } return query.getRestriction(); }; } 结果 SQL 语句 select bookjoin0_.id as id1_1_0_, bookjoin_a1_.id as id1_0_1_, bookjoin_r2_.id as id1_2_2_, bookjoin0_.author_id as author_i3_1_0_, bookjoin0_.publish_time as publish_2_1_0_, bookjoin0_.review_id as review_i4_1_0_, bookjoin_a1_.name as name2_0_1_, bookjoin_r2_.score as score2_2_2_ from book bookjoin0_ inner join author bookjoin_a1_ on bookjoin0_.author_id = bookjoin_a1_.id and (bookjoin_a1_.name = ?) inner join review bookjoin_r2_ on bookjoin0_.review_id = bookjoin_r2_.id and (bookjoin_r2_.score = ?) where bookjoin0_.publish_time = ? limit ? 当然,这里案例使用的是 INNER JOIN,对于 LEFT JOIN 也是生效的。 ...

九月 25, 2022 · 2 分钟 · Lex Cao

Spring Data JPA 多条件连表查询 (2022 更新)

痛点 项目中使用 Spring Data JPA 作为 ORM 框架的时候,实体映射非常方便。Spring Data Repository 的顶层抽象完全解决单实体的查询,面对单实体的复杂查询,也能使用 JpaSpecificationExecutor<T> 构造 Specification<T> 轻松应对。 而对于后台管理报表查询需求来说,需要进行连表多条件动态查询的时候,就显得无从下手。因为它并不像 MyBatis 一样能够在 XML 文件中写出动态 SQL 语句。 尽管可以使用 EntityManager 动态拼接原生 SQL 语句,但是该方法返回值为 ResultSet ,也就是说查出来的实体映射关系需要手动映射(😢这样不太优雅,已经定义出实体,还需要自己去映射)。 所以,本文的目的是,在现有实体关系的基础上,结合 Specification<T> 记录下 Spring Data JPA 多条件动态连表查询操作,以及其中的踩坑和优化。 想要直接看结论的,请看这篇 Spring Data JPA 动态多条件连表查询最佳实践。 基础操作 那么,让我们开始进入代码操作。【本文所有代码在此】 前置说明 相关依赖 Java 11 SpringBoot 2.4.2 build.gradle plugins { id 'org.springframework.boot' version '2.4.2' id 'io.spring.dependency-management' version '1.0.11.RELEASE' id 'java' } dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' compileOnly 'org.projectlombok:lombok' runtimeOnly 'mysql:mysql-connector-java' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'com.h2database:h2' } maven.xml ...

九月 24, 2022 · 8 分钟 · Lex Cao

通过构建全栈待办应用学习 Rust

什么 我想写一篇关于我如何学习Rust的博客。 请原谅我在 2022 年才开始学习这个伟大的编程语言。 为了在实践中学习Rust,我构建了一个全栈待办应用。 你可以在 这里 尝试。相关源码可以在 GitHub 上找到。 现在,我准备写一下它是如何构建的。 如何 首先,和大家一样,我也是从 The Book 中学习 Rust。这是一本不应该跳过入门学习 Rust 的好书。 在学习了一些基本的语法之后,我尝试从零开始使用 Rust 构建一个全栈应用,这是一个使用 Rust tokio 的后端服务和一个使用 Rust WASM(Web Assembly)的前端页面。 我将分别对这两部分做一个简单的介绍。 后端 后台服务是一个的简单的 REST API。使用 actix-web 作为网络框架。 我将写另一篇博客,介绍我如何使用 TDD 开发 Rust 后台服务。 被 Rust 编译器和 Borrow Checker 教育是一次特别的体验。 对于部署,我使用 Supabase 作为 Postgres 服务,使用 Railway 来运行后台服务 docker 镜像。 前端 前端页面有在线和离线数据源,在线是从后台服务器获取数据,而离线是在本地存储。并且有一个按钮来切换它们。 前台由 Rust WASM 和 yew 框架驱动,这是一个类似 React 基于组件构建 Web 应用框架。 如果你熟悉 JSX,你可以在使用 Yew 时感到很自在。 在 Rust 中编写类似 React 的代码体验良好,而且真的很有趣。 但有一些不同之处我想与大家分享,晚点会写一篇博客来谈这个问题,所以敬请关注。 部署的话,是放在 Vercel 上进行托管,用 GitHub Action 来实现自动部署。 ...

五月 2, 2022 · 1 分钟 · Lex Cao