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

ops/2025/3/10 10:50:49/

在这里插入图片描述

本文目录

  • 1. 构建api
    • 梳理调用关系
    • api包的作用
    • 路由梳理
    • 注册Register代码语法
  • 2. 重构错误码

api_4">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_154">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字节),并且在网络传输中数据包更小,处理速度更快。

在这里插入图片描述


http://www.ppmy.cn/ops/164637.html

相关文章

python将目录下的所欲md文件转化为html和pdf

python将目录下的所欲md文件转化为html和pdf import os import subprocess import win32com.client as win32def md_to_docx(md_path, docx_path):"""将 Markdown 文件转换为 DOCX 文件:param md_path: Markdown 文件的路径:param docx_path: 输出 DOCX 文件的…

Pytorch的一小步,昇腾芯片的一大步

Pytorch的一小步,昇腾芯片的一大步 相信在AI圈的人多多少少都看到了最近的信息:PyTorch最新2.1版本宣布支持华为昇腾芯片! 1、 发生了什么事儿? 在2023年10月4日PyTorch 2.1版本的发布博客上,PyTorch介绍的beta版本…

《C陷阱与缺陷》读书笔记(一)

目录 一、 不同于 : 二、& 和 | 不同于 && 和 || : 三、词法分析中的贪心法: 一、 不同于 : 对于C语言中的“”和“”,它们虽然只相差了一个等号,但是含义确实千差万别。“”在C语言中是非…

【Java代码审计 | 第八篇】文件操作漏洞成因及防范

未经许可,不得转载。 文章目录 文件操作漏洞文件读取漏洞基于 InputStream 的读取基于 FileReader 的读取 文件下载漏洞文件删除漏洞防范 文件操作漏洞 分为文件读取漏洞、文件下载漏洞与文件删除漏洞。 文件读取漏洞 在Java中,文件读取通常有两种常见…

DeepSeek-R1本地化部署(Mac)

一、下载 Ollama 本地化部署需要用到 Ollama,它能支持很多大模型。官方网站:https://ollama.com/ 点击 Download 即可,支持macOS,Linux 和 Windows;我下载的是 mac 版本,要求macOS 11 Big Sur or later,Ol…

JAVA实现有趣的数独小游戏(附源码)

文章目录 一、设计来源数独小游戏讲解1.1 主界面1.2 游戏难度配置界面1.3 游戏完成界面 二、效果和源码2.1 动态效果2.2 源代码 源码下载更多优质源码分享 作者:xcLeigh 文章地址:https://blog.csdn.net/weixin_43151418/article/details/146039002 JAVA…

nacos和Eureka的学习

Nacos是一个开源的、易于使用的动态服务发现、配置管理和服务管理平台,特别适用于云原生应用。它为分布式系统提供了一个中心化的服务发现和配置管理解决方案,支持微服务架构。 Nacos的核心功能: 服务发现与治理: 提供服务注册和…

蓝桥备赛(12)- 顺序表和 vector(上)

一、顺序表的概念 1.1 线性表的定义 线性表是 n 个具有 相同特性 的数据元素的有序序列 。 线性表在逻辑上可以想象成是连续的一条线段 , 线段上有很多点 , 比如下图: 相关术语: 线性表是一个比较简单 和 基础的数据结构 。 1.2…