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

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/30457.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

游戏引擎学习第145天

仓库:https://gitee.com/mrxiao_com/2d_game_3 今天的计划 目前,我们正在完成遗留的工作。当时我们已经将声音混合器(sound mixer)集成到了 SIMD 中,但由于一个小插曲,没有及时完成循环内部的部分。这个小插曲主要是…

Qt 实现绘图板(支持橡皮擦与 Ctrl+Z 撤销功能)[特殊字符]

作业&#xff1a; 1&#xff1a;实现绘图的时候&#xff0c;颜色的随时调整 2&#xff1a;追加橡皮擦功能 3&#xff1a;配合键盘事件&#xff0c;实现功能 当键盘按 ctrlz的时候&#xff0c;撤销最后一次绘图 头文件.h #ifndef WIDGET_H #define WIDGET_H#include <QWidge…

打造智能聊天体验:前端集成 DeepSeek AI 助你快速上手

DeepSeek AI 聊天助手集成指南 先看完整效果&#xff1a; PixPin_2025-02-19_09-15-59 效果图&#xff1a; 目录 项目概述功能特点环境准备项目结构组件详解 ChatContainerChatInputMessageBubbleTypeWriter 核心代码示例使用指南常见问题 项目概述 基于 Vue 3 TypeScrip…

JPA编程,去重查询ES索引中的字段,对已有数据的去重过滤,而非全部字典数据

一、背景 课程管理界面&#xff0c;查询前&#xff0c;需要把查询元数据给出。 学科列表、学段列表和分类列表&#xff0c;我们把它定义为查询元数据。 一般的业务需求是&#xff1a; 系统维护好多个字典&#xff0c;比如学科、学段等等&#xff0c;相当于属性库。 但是&…

MySQL语法总结

本篇博客说明&#xff1a; &#xff01;&#xff01;&#xff01;.注意此系列都用的是MySQL语句&#xff0c;和SQLServer&#xff0c;PostgreSQL有些细节上的差别&#xff01;&#xff01;&#xff01; 1.每个操作都是先展示出语法格式 2.然后是具体例子 3.本篇注脚与文本顺讯息…

C++ Primer 交换操作

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

go切片定义和初始化

1.简介 切片是数组的一个引用&#xff0c;因此切片是引用类型&#xff0c;在进行传递时&#xff0c;遵守引用传递的机制。切片的使用和数组类似&#xff0c;遍历切片、访问切片的元素和切片的长度都一样。。切片的长度是可以变化的&#xff0c;因此切片是一个可以动态变化的数…

huggingface镜像站hf-mirror的各大AI模型文件下载

一、说明 huggingface地址&#xff1a;https://huggingface.co 但是由于需要国外网络&#xff0c;国内网络延迟较大或无法下载&#xff0c;所以使用国内镜像站&#xff1a; hf-mirror地址&#xff1a;https://hf-mirror.com/ 二、下载方法 1.本机安装了GIT 2.打开HF-Mirro…

Unity Shader 学习15:可交互式雪地流程

本质是 利用顶点变换实现的&#xff1a; 通过一个俯视整个场地的正交摄像机&#xff0c;根据绑定在移动物体身上的粒子系统&#xff0c;来获取物体移动过的位置&#xff0c;记录到一张RenderTexture上作为轨迹图&#xff0c;再通过这张图来对雪地做顶点变换。 1. 由于顶点变换需…

自由学习记录(42)

可能会出现到后面没有教程可以看&#xff0c;走不动&#xff0c;&#xff0c;但还是尝试吧 过程远比想象的要多 那连Live2d的这些脚本怎么控制的都要了解一下 ------------ 文件类型和扩展名 | 编辑手册 | Live2D Manuals & Tutorials 全部导入之后 在这下载SDK Live2D…

LeetCode - 28 找出字符串中第一个匹配项的下标

题目来源 28. 找出字符串中第一个匹配项的下标 - 力扣&#xff08;LeetCode&#xff09; 题目解析 暴力解法 本题如果采用暴力解法的话&#xff0c;可以定义两个指针 i&#xff0c;j&#xff0c;其中 i 指针用于扫描 S&#xff08;haystack&#xff09;串&#xff0c;j 指针…

windows下使用msys2编译ffmpeg

三种方法&#xff1a; 1、在msys2中使用gcc编译 2、在msys2中使用visual studio编译&#xff08;有环境变量&#xff09; 3、在msys2中使用visual studio编译&#xff08;无环境变量&#xff09; 我的环境&#xff1a; 1、msys2-x86_64-20250221 2、vs2015 3、ffmpeg-7.1…

【Python 数据结构 9.树】

我装作漠视一切&#xff0c;其实我在乎的太多&#xff0c;但我知道抓得越紧越容易失去 —— 25.3.6 一、树的基本概念 1.树的定义 树是n个结点的有限集合&#xff0c;n0时为空树。当n大于0的时候&#xff0c;满足如下两个条件&#xff1a; ① 有且仅有一个特定的结点&#xff…

数据结构--AVL树

一、二叉搜索树&#xff08;Binary Search Tree, BST&#xff09; 基本性质 对于树中的每个节点&#xff0c;其左子树中的所有节点值均小于该节点值。其右子树中的所有节点值均大于该节点值。左右子树也分别是二叉搜索树。 极端场景 在极端情况下&#xff0c;如插入节点顺序…

C语言——链表

大神文献&#xff1a;https://blog.csdn.net/weixin_73588765/article/details/128356985 目录 一、链表概念 1. 什么是链表&#xff1f; 1.1 链表的构成 2. 链表和数组的区别 数组的特点&#xff1a; 链表的特点&#xff1a; 二者对比&#xff1a; 二…

Qt:多线程

目录 初识Qt多线程 QThread常用API QThread的使用 Qt中的锁 条件变量和信号量 初识Qt多线程 Qt 多线程 和 Linux 中的线程本质是一个东西 Linux 中学过的 多线程 APl&#xff0c;Linux 系统提供的 pthread 库 Qt 中针对系统提供的线程 API 重新封装了 C11 中&#xff0c;…

如何用Kimi生成PPT?秒出PPT更高效!

做PPT是不是总是让你头疼&#xff1f;&#x1f629; 快速制作出专业的PPT&#xff0c;今天我们要推荐两款超级好用的AI工具——Kimi 和 秒出PPT&#xff01;我们来看看哪一款更适合你吧&#xff01;&#x1f680; &#x1f947; Kimi&#xff1a;让PPT制作更轻松 Kimi的生成效…

计算机毕业设计SpringBoot+Vue.js车辆管理系统(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

机器学习4-PCA降维

1 降维 在数据处理过程中&#xff0c;会碰到维度爆炸&#xff0c;维度灾难的情况&#xff0c;为了得到更精简更有价值的信息&#xff0c;我们需要进一步处理&#xff0c;用的方法就是降维。 降维有两种方式&#xff1a;特征抽取、特征选择 特征抽取&#xff1a;就是特征映射…

探秘沃尔什-哈达玛变换(WHT)原理

沃尔什-哈达玛变换&#xff08;WHT&#xff09;起源 起源与命名&#xff08;20世纪早期&#xff09; 数学基础&#xff1a;该变换的理论基础由法国数学家雅克哈达玛&#xff08;Jacques Hadamard&#xff09;在1893年提出&#xff0c;其核心是哈达玛矩阵的构造。扩展与命名&…