【GoTeams】-3:构建api、重构错误码

本文目录
- 1. 构建api
- 梳理调用关系
- api包的作用
- 路由梳理
- 注册Register代码语法
- 2. 重构错误码
1. 构建api
首先复制project-user,改名为project-api,放在总的路径下,然后在工作区中进行导入。
运行命令go work use .\project-api\新建工作区之间的关联,同时需要把刚刚复制过来的api下的go.mod文件进行更改,更改module名字,不然工作区会报错。

在api下的main函数中,更改import引用,导入相对应的包,更新如下。

先来看看效果,先分别启动project-user,然后通过project-api暴露的服务,我们来申请验证码,api将会调用user里面的grpc,并获得grpc的返回结果后包装成一个响应返回给服务器。


这样api端也能够响应了。
梳理调用关系
这里使用project-api作为网关层接入层,主要是处理外部的HTTP请求,进行路由转发等,然后project-user作为服务层,提供核心业务逻辑处理。
那么现在前段发来请求之后,应该是这样的:外部请求 → project-api(HTTP:80) → project-user(gRPC:8881)
首先用户发送HTTP请求到API层。
func (*HandlerUser) getCaptcha(ctx *gin.Context) {mobile := ctx.PostForm("mobile")// 通过 gRPC 调用 user 服务rsp, err := LoginServiceClient.GetCaptcha(c, &loginServiceV1.CaptchaMessage{Mobile: mobile})// ...
}
API 层通过 gRPC 调用 User 服务:
func (ls LoginService) GetCaptcha(ctx context.Context, msg *CaptchaMessage) (*CaptchaResponse, error) {// 具体的业务逻辑实现rsp, err := LoginServiceClient.GetCaptcha(c, &loginServiceV1.CaptchaMessage{Mobile: mobile})
}
这样就是实现了 职责分离:API 层负责协议转换和请求处理,服务层专注业务逻辑,并且实现了安全性:内部服务不直接暴露给外部,同时还有 扩展性:可以方便地添加新的服务和 API,以及维护性:各层独立维护和部署。
这里主要就是三个代码,分别是api层下面的user三个包,进行grpc服务的调用。
router.go 代码如下。
package userimport ("github.com/gin-gonic/gin""log""test.com/project-api/router"
)type RouterUser struct {
}func init() {log.Println("init user router")ru := &RouterUser{}router.Register(ru)
}func (*RouterUser) Route(r *gin.Engine) {//初始化grpc的客户端连接InitRpcUserClient()h := New()r.POST("/project/login/getCaptcha", h.getCaptcha)
}
rpc.go代码如下:
package userimport ("google.golang.org/grpc""google.golang.org/grpc/credentials/insecure""log"loginServiceV1 "test.com/project-user/pkg/service/login.service.v1"
)var LoginServiceClient loginServiceV1.LoginServiceClientfunc InitRpcUserClient() {conn, err := grpc.Dial(":8881", grpc.WithTransportCredentials(insecure.NewCredentials()))if err != nil {//这里调用的是Fatalf,理论上调度失败了,不能再继续运行// Fatalf记录信息错误之后会立即调用os.Exit(1)来终止程序//不会继续执行后续代码,也不会执行defer语句log.Fatalf("did not connect: %v", err)}LoginServiceClient = loginServiceV1.NewLoginServiceClient(conn)}
user.go代码如下,作用是发起gRPC调用,然后封装gRPC的结果作为响应给前端。
package userimport ("context""github.com/gin-gonic/gin""net/http"common "test.com/project-common"loginServiceV1 "test.com/project-user/pkg/service/login.service.v1""time"
)type HandlerUser struct {
}func New() *HandlerUser {return &HandlerUser{}
}func (*HandlerUser) getCaptcha(ctx *gin.Context) {result := &common.Result{}mobile := ctx.PostForm("mobile")//发起GPRC调用c, cancel := context.WithTimeout(context.Background(), 5*time.Second)defer cancel()rsp, err := LoginServiceClient.GetCaptcha(c, &loginServiceV1.CaptchaMessage{Mobile: mobile})if err != nil {ctx.JSON(http.StatusOK, result.Fail(2001, err.Error()))return}ctx.JSON(http.StatusOK, result.Success(rsp.Code))
}
api包的作用
这个 api.go 文件的作用是通过空导入(blank import)来初始化 user 包,也就是触发 user 包中的 init() 函数执行,因为我们在router.go的包中,有如下代码。
func init() {log.Println("init user router")ru := &RouterUser{}router.Register(ru)
}

这种设计模式的好处就是,每个功能模块都可以独立管理自己的路由,只需要在api.go中添加相应的导入即可。
路由梳理
这里有很多路由+各种接口的实现、注册等,比较乱,这里梳理下关系,也巩固下对接口的认识。
这个图就比较清晰了,能够知道到底是怎么处理路由的。

注册Register代码语法
这里Register里边有一个代码的语法可以看看,回顾一下Go的语法知识。
func Register(ro ...Router) {routers = append(routers, ro...)
}
这里涉及到两个 Go 语言的特性:可变参数 : ro ...Router,...Router表示这个函数可以接收任意数量的 Router 类型参数,在函数内部, ro 会被当作 Router 类型的切片使用。
比如:
Register(router1) // 传入一个
Register(router1, router2) // 传入多个
在 append 函数调用时, ro... 会将切片 ro 展开成多个独立的参数,routers = append(routers, ro...) 相当于把 ro 切片中的所有元素都追加到 routers 切片中。
// 假设有这样的调用
router1 := &RouterUser{}
router2 := &RouterOrder{}
Register(router1, router2)// 函数内部执行
routers = append(routers, router1, router2) // ro... 被展开成多个参数
2. 重构错误码
把model中的code重构下,错误码为grpc提供的status状态,status 包是 gRPC 提供的错误处理工具,用于创建标准化的 gRPC 错误。

然后在rpc的返回的地方,也对应的进行更改返回参数。

上述方式是通过gRPC提供的status来进行error处理的,这里我们也可以通过自己定义error来实现错误处理,来看看具体的实现。
首先我们在common中定义实现errs的两个go文件,分别如下。
errs.go中的代码作用是自定义了错误结构,以及创建新错误的方法。
package errstype ErrorCode inttype BError struct {Code ErrorCodeMsg string
}func (e *BError) Error() string {return e.Msg
}func NewError(code ErrorCode, msg string) *BError {return &BError{Code: code,Msg: msg,}
}
在grpc_go代码中将业务错误转换为 gRPC 错误,然后还有 解析 gRPC 错误的两个方法。
package errsimport (codes "google.golang.org/grpc/codes""google.golang.org/grpc/status"common "test.com/project-common"
)func GrpcError(err *BError) error {return status.Error(codes.Code(err.Code), err.Msg)
}// 解析GrpcError 返回一个BusinessCode和string类型
func ParseGrpcError(err error) (common.BusinessCode, string) {fromError, _ := status.FromError(err)return common.BusinessCode(fromError.Code()), fromError.Message()
}
所以流程是,服务层发现错误,然后创建业务错误,转换为gRPC错误,通过RPC传输,API层接受错误之后,解析gRPC错误,并且转换为http相应,返回给客户端。
在model中我们定义了业务的code码,也就是下面这个。
package modelimport ("test.com/project-common/errs"
)var (NoLegalMobile = errs.NewError(2001, "手机号不合法")
)
那么通过在service服务端把业务代码包装下,也就是把业务错误,转换为gRPC格式的错误,return nil, errs.GrpcError(model.NoLegalMobile)。

然后通过api网关层的user.go解析错误,并且返回对应的错误。

所以为什么要转换为gRPC错误进行封装和拆解?
因为gRPC 使用特定的错误格式进行传输,并且普通的业务错误无法直接通过 gRPC 传递,gRPC 错误包含标准的错误码和消息格式。
来看看gRPC中关于Error的定义,其中Code是uint32类型的错误码。
那么uint32和int有什么区别呢?int是有符号整数,可以表示正数和负数,而uint32是无符号的,并且uint32一定是4字节,适合网络协议、二进制文件处理等。
uint32可以占用更少的内存(int64是根据主机来的,64位就是8字节,而32位是4字节),并且在网络传输中数据包更小,处理速度更快。

相关文章:
【GoTeams】-3:构建api、重构错误码
本文目录 1. 构建api梳理调用关系api包的作用路由梳理注册Register代码语法 2. 重构错误码 1. 构建api 首先复制project-user,改名为project-api,放在总的路径下,然后在工作区中进行导入。 运行命令go work use .\project-api\新建工作区之…...
《苍穹外卖》SpringBoot后端开发项目重点知识整理(DAY1 to DAY3)
目录 一、在本地部署并启动Nginx服务1. 解压Nginx压缩包2. 启动Nginx服务3. 验证Nginx是否启动成功: 二、导入接口文档1. 黑马程序员提供的YApi平台2. YApi Pro平台3. 推荐工具:Apifox 三、Swagger1. 常用注解1.1 Api与ApiModel1.2 ApiModelProperty与Ap…...
深入解析跨域问题及其解决方案:从原理到代码实践
跨域问题是前端开发中常见的安全限制问题,主要源于浏览器的同源策略(Same-Origin Policy)。同源策略限制了从一个源加载的文档或脚本如何与另一个源的资源进行交互。为了更深入地理解跨域问题及其解决方案,我们需要从原理、限制条…...
BLUEM2引擎源码2025最新版
BLUE 引擎解析:传奇私服圈中的热门引擎 一、BLUE 引擎简介 BLUE 引擎是传奇私服圈子中较为知名的一款游戏引擎,它在传统的传奇引擎基础上进行了优化和扩展,使得私服开发者可以更加方便地搭建和管理服务器。相比于早期的 GEE、LEG、Hero 等引…...
【RAG】检索后排序 提高回答精度
问题: RAG中,有时,最合适的答案不一定排在检索的最前面 user_query "how safe is llama 2" search_results vector_db.search(user_query, 5)for doc in search_results[documents][0]:print(doc"\n")response bot.chat(user_qu…...
7.2 奇异值分解的基与矩阵
一、奇异值分解 奇异值分解(SVD)是线性代数的高光时刻。 A A A 是一个 m n m\times n mn 的矩阵,可以是方阵或者长方形矩阵,秩为 r r r。我们要对角化 A A A,但并不是把它化成 X − 1 A X X^{-1}A X X−1AX 的形…...
Windows控制台函数:控制台输出函数WriteConsoleA()
目录 什么是 WriteConsoleA? 函数签名 参数详解 返回值 一个最简单的例子 跟 ReadConsoleA 对比 再试一个有趣的例子 为什么传地址给 lpNumberOfCharsWritten? 注意事项 什么是 WriteConsoleA? WriteConsoleA 是一个 Windows API 函…...
采用内存局部性分配有什么好处?
内存分配时的局部性分配(Locality of Allocation)是指将相关的内存对象分配在相邻或相近的内存区域中。这种分配策略在现代计算机系统中具有显著的好处,主要体现在以下几个方面: 1. 提高缓存命中率 现代计算机系统依赖于多级缓存…...
【Dubbo+Zookeeper】——SpringBoot+Dubbo+Zookeeper知识整合
🎼个人主页:【Y小夜】 😎作者简介:一位双非学校的大二学生,编程爱好者, 专注于基础和实战分享,欢迎私信咨询! 🎆入门专栏:🎇【MySQL࿰…...
使用阿里云操作系统控制台排查内存溢出
引言 操作系统控制台是阿里云最新推出的一款智能运维工具,专为提升运维效率、优化服务器管理而设计。它集成了多种运维管理功能,包括操作系统助手、插件管理器以及其他实用工具,为用户提供一站式的运维解决方案。无论是个人开发者还是企业运…...
3.3.2 Proteus第一个仿真图
文章目录 文章介绍0 效果图1 新建“点灯”项目2 添加元器件3 元器件布局接线4 补充 文章介绍 本文介绍:使用Proteus仿真软件画第一个仿真图 0 效果图 1 新建“点灯”项目 修改项目名称和路径,之后一直点“下一步”直到完成 2 添加元器件 点击元…...
MyBatis-Plus 与 Spring Boot 的最佳实践
在现代 Java 开发中,MyBatis-Plus 和 Spring Boot 的结合已经成为了一种非常流行的技术栈。MyBatis-Plus 是 MyBatis 的增强工具,提供了许多便捷的功能,而 Spring Boot 则简化了 Spring 应用的开发流程。本文将探讨如何将 MyBatis-Plus 与 Spring Boot 进行整合,并分享一些…...
深入了解Linux —— 调试程序
前言 我们已经学习了linux下许多的工具,vim、gcc、make/makefile等; 已经能够在linux写代码,并且进行编译运行,让程序在linux下跑起来。 但是,如果我们在写代码的时候遇见了错误;但是我们并不知道错误在哪&…...
Hive-优化(语法优化篇)
列裁剪与分区裁剪 在生产环境中,会面临列很多或者数据量很大时,如果使用select * 或者不指定分区进行全列或者全表扫描时效率很低。Hive在读取数据时,可以只读取查询中所需要的列,忽视其他的列,这样做可以节省读取开销…...
物联网(Internet of Things, IoT)中的网络层简介
物联网(Internet of Things, IoT)中的网络层是物联网架构中的关键组成部分,负责设备之间的数据传输和通信。网络层的主要任务是将感知层(传感器、设备等)收集到的数据通过互联网或其他通信网络传输到应用层(数据处理和分析平台)。以下是物联网网络层的知识简介: 1. 物联…...
C++ 提供了多种数据类型组合方式
C 提供了多种数据类型组合方式,允许开发者将基本类型组合成更复杂的数据结构,以满足不同场景的需求。以下是主要的组合方式及其示例: 1. 数组(Array) 同类型元素的集合,可以是静态或动态。 int staticArr…...
八字排盘宝 2025.1.8 | 多模式排盘工具,精准解析八字信息,轻量易用
八字排盘宝是一款轻量高效的排盘工具,实现多模式排盘功能,界面简洁易用,适合命理爱好者和专业人士。支持多种排盘方式,精准解析八字信息,提供快速、便捷的命理分析体验,是日常排盘和命理学习的得力助手。 …...
MySQL面试篇——性能优化
MySQL性能优化 在MySQL中,如何定位慢查询 慢查询表象:页面加载过慢、接口压测响应时间过长(超过1s)。造成慢查询的原因通常有:聚合查询、多表查询、表数据量过大查询、深度分页查询 方案一:开源工具 调试工…...
c#财务软件专业版企业会计做账软件财务管理系统软件
本软件为绍兴客户开发的仿某碟财务软件专业版 功能:可以按会计科目做账录入会计凭证、结转损益、期末结账、拉资产负债表 github下载:https://github.com/oyangxizhe/financial.git...
常见 JVM 工具介绍
1. jps(Java Virtual Machine Process Status Tool) 功能:列出当前用户的所有 Java 进程及其 PID。 常用场景:快速定位目标 Java 应用的进程 ID。 常用命令: bash复制 jps -l # 显示主类全名 jps -v # 显示 JVM 启…...
【含文档+PPT+源码】Python爬虫人口老龄化大数据分析平台的设计与实现
项目介绍 本课程演示的是一款Python爬虫人口老龄化大数据分析平台的设计与实现,主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Python学习者。 1.包含:项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本…...
生成对抗网络(GAN)原理与应用
目录 一、引言 二、GAN的基本原理 (一)生成器(Generator)的工作机制 (二)判别器(Discriminator)的工作机制 (三)对抗训练的过程 三、GAN在AIGC生图中的应…...
Linux安装升级docker
Linux 安装升级docker Linux 安装升级docker背景升级停止docker服务备份原docker数据目录移除旧版本docker安装docker ce恢复数据目录启动docker参考 安装找到docker官网找到docker文档删除旧版本docker配置docker yum源参考官网继续安装docker设置开机自启配置加速测试 Linux …...
clickhouse源码分析
《ClickHouse源码分析》 当我们谈论数据库时,ClickHouse是一个不容忽视的名字。它是一个用于联机分析处理(OLAP)的列式数据库管理系统(DBMS),以其快速的数据查询能力而闻名。对于想要深入了解这个高效工具…...
IDEA 基础配置: maven配置 | 服务窗口配置
文章目录 IDEA版本与MAVEN版本对应关系maven配置镜像源插件idea打开服务工具窗口IDEA中的一些常见问题及其解决方案IDEA版本与MAVEN版本对应关系 查找发布时间在IDEA版本之前的dea2021可以使用maven3.8以及以前的版本 比如我是idea2021.2.2 ,需要将 maven 退到 apache-maven-3.…...
【redis】type命令和定时器的两种实现方式(优先级队列、时间轮)
type——返回 key 对应的数据类型 此处 Redis 所有的 key 都是 string,但是 key 对应的 value 可能会存在多种类型 none —— key 不存在string ——字符串list ——列表set ——集合zset ——有序集合hash ——哈希表stream ——Redis 作为消息队列的时候&#x…...
高并发下订单库存防止超卖策略
文章目录 什么是超卖问题?推荐策略:Redis原子操作(Redis incr)乐观锁lua脚本利用Redis increment 的原子操作,保证库存数安全update使用乐观锁LUA脚本保持库存原子性 什么是超卖问题? 在并发的场景下,比如商城售卖商品…...
task01
1:大模型能够专业的回答各种问题,并且擅长文本处理,代码编写,可以减少一部分人类的工作。 本节学习了大模型提示词的三要素,角色,背景,输出样式,在kimi上我复现了教材的任务…...
【Kotlin】Kotlin基础笔记
一、数据类型 1.1 变量声明与类型推导 变量声明 使用 val 声明不可变变量(相当于常量);使用 var 声明可变变量。 val a 10 // 类型自动推断为 Int,不可变 var b: Double 5.0 // 显示声明为 Double,可变变量…...
DeepSeek教我写词典爬虫获取单词的音标和拼写
Python在爬虫领域展现出了卓越的功能性,不仅能够高效地抓取目标数据,还能便捷地将数据存储至本地。在众多Python爬虫应用中,词典数据的爬取尤为常见。接下来,我们将以dict.cn为例,详细演示如何编写一个用于爬取词典数据…...
