go-zero微服务实战——基本环境搭建

简介

项目架构来源于go-zero实战:让微服务Go起来。此对该项目有所删减,相对简单适合初学者。

省去了项目中每个服务占用独立docker的过程,省略了docker-compose的构建过程。每个服务是一个独立的程序不依赖与容器。

环境搭建

  1. 安装goctl
go install github.com/zeromicro/go-zero/tools/goctl@latest
  1. 安装protoc
goctl env check --install --verbose --force
  1. 安装go-zero
go get -u github.com/zeromicro/go-zero@latest
  1. 生成api标准api服务
goctl api new apiservice
  1. 生成rpc服务
goctl rpc new rpcservice

生成代码后go mod tidy下载所需依赖。

apiservice目录下的apiservicelogic.go27行后修改为如下图所示代码:

在这里插入图片描述

删除apiservice目录下etc下的配置文件,在主程序中做如何修改如下修改,直接配置方便一些:

func main() {var c config.Configc.Host = "0.0.0.0"c.Port = 8000server := rest.MustNewServer(c.RestConf)defer server.Stop()ctx := svc.NewServiceContext(c)handler.RegisterHandlers(server, ctx)fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)server.Start()
}

api服务统一为8000系列的端口。

rpcservice目录下的yaml配置文件端口修改为9000系。主函数做如下修改:

func main() {var c config.Configc.ListenOn = "0.0.0.0:9000"c.Mode = "dev"ctx := svc.NewServiceContext(c)s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {rpcservice.RegisterRpcserviceServer(grpcServer, server.NewRpcserviceServer(ctx))if c.Mode == service.DevMode || c.Mode == service.TestMode {reflection.Register(grpcServer)}})defer s.Stop()fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)s.Start()
}

启动程序

在这里插入图片描述

在这里插入图片描述

浏览器访问

在这里插入图片描述

rpc客户端访问

新项目复制两个pb文件,编写客户端主程序。

package mainimport ("context""fmt""google.golang.org/grpc""google.golang.org/grpc/credentials/insecure""rpcclient/rpcservice"
)func main() {//配置连连接参数(无加密)dial, _ := grpc.Dial("localhost:9000", grpc.WithTransportCredentials(insecure.NewCredentials()))defer dial.Close()//创建客户端连接client := rpcservice.NewRpcserviceClient(dial)//通过客户端调用方法res, _ := client.Ping(context.Background(), &rpcservice.Request{Ping: "xiaoxu"})fmt.Println(res.Pong)}

在这里插入图片描述

go-zero api服务构建

整合mysql数据库

构建如项目上的4个服务。4个服务都是上一节构建基本项目为基础的gitee地址

整合数据库就不再过多赘述了,整合xorm框架,返回数据库引擎即可,如下:

xorm实战——结构体映射到实现数据库操作

import ("fmt"_ "github.com/go-sql-driver/mysql""github.com/go-xorm/xorm"
)var Engine *xorm.Enginefunc init() {var err errore, err := xorm.NewEngine("mysql", "root:root@/zerotest?charset=utf8")if err != nil {fmt.Println("数据库连接失败!")}// err的错误处理Engine = e
}

构建mysql数据库后在其他包下通过库名.Engine即可使用数据库引擎。

创建数据库

CREATE TABLE `order` (`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',`order_id` varchar(100) NOT NULL COMMENT '订单编号',`type` tinyint DEFAULT NULL COMMENT '订单类型',`customer_id` int DEFAULT NULL COMMENT '用户编号',`amount` int DEFAULT NULL COMMENT '数量',`payment` tinyint DEFAULT NULL COMMENT '支付方式',`status` tinyint DEFAULT NULL COMMENT '状态',`create_time` varchar(100) DEFAULT NULL COMMENT '创建时间',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='订单表';

控制器逻辑实现

新建logic目录

在这里插入图片描述

创建订单逻辑类

type orderLogic struct{}var OrderLogic orderLogic// 创建订单
func (this orderLogic) Create(param models.Order) error {param.Id = 0_, err := db.Engine.Insert(param)if err != nil {fmt.Printf("logic module create err:%v", err)return err}return nil
}//db是构建服务器引擎的包

控制器调用逻辑代码

// create
func OrderCreateController() http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {//获取请求参数var req models.Ordererr := httpx.ParseJsonBody(r, &req)if err != nil {//fmt.Printf("ordercontoller err:%v", err)httpx.WriteJson(w, 500, fmt.Sprintf("ordercontoller err:%v", err))return}//******************//err = orderlogic.OrderLogic.Create(req)//******************//if err != nil {//fmt.Printf("order create err:%v", err)httpx.WriteJson(w, 500, fmt.Sprintf("order create err:%v", err))return}httpx.OkJson(w, map[string]string{"code": "200", "message": "插入成功!"})}
}

Create方法不使用结构体模拟类,直接是一个函数也是可以的,但是函数就不能重名了,在微服务中可以为单个函数,在单纯的web服务中还是用类实现比较好。

部分代码:

// create
func OrderCreateController() http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {//获取请求参数var req models.Ordererr := httpx.ParseJsonBody(r, &req)if err != nil {//fmt.Printf("ordercontoller err:%v", err)httpx.WriteJson(w, 500, fmt.Sprintf("ordercontoller err:%v", err))return}err = orderlogic.OrderLogic.Create(req)if err != nil {//fmt.Printf("order create err:%v", err)httpx.WriteJson(w, 500, fmt.Sprintf("order create err:%v", err))return}httpx.OkJson(w, map[string]string{"code": "200", "message": "插入成功!"})}
}// update
func OrderUpdateController() http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {var req models.Ordererr := httpx.ParseJsonBody(r, &req)if err != nil {httpx.WriteJson(w, 500, fmt.Sprintf("update err:%v", err))return}err = orderlogic.OrderLogic.Update(req)if err != nil {//...return}httpx.OkJson(w, map[string]string{"code": "200", "message": "更新成功!"})}
}//Removefunc OrderRemove() http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {var req models.Ordererr := httpx.ParseJsonBody(r, &req)if err != nil {httpx.WriteJson(w, 500, fmt.Sprintf("remove err%v", err))return}err = orderlogic.OrderLogic.Remove(req)if err != nil {//...return}httpx.OkJson(w, map[string]string{"code": "200", "message": "删除成功!"})}
}//Listfunc OrderList() http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {res, err := orderlogic.OrderLogic.List()if err != nil {//...return}httpx.OkJson(w, res)}
}

逻辑层直接省略了,就是xorm对mysql的CURD操作。

注册路由

func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {server.AddRoutes([]rest.Route{{Method:  http.MethodGet,Path:    "/from/:name",Handler: ApiserviceHandler(serverCtx),},//自定义路由{Method: http.MethodPost,Path: "/create",Handler: OrderCreateController(),},{Method: http.MethodPost,Path: "/update",Handler: OrderUpdateController(),},{Method: http.MethodPost,Path: "/remove",Handler: OrderRemove(),},{Method: http.MethodPost,Path: "/list",Handler: OrderList(),},},)
}

在这里插入图片描述
综上所属,已经成功的构建了一个web服务,包括对订单order的基本操作。

如果不想手写这莫多代码,可以看看zero的api语法,直接使用api文件一键生成更加方便。

go-zero rpc服务构建

在rpc远程调用中主要用于rpc服务器注册本地的方法,实现服务器之间的内部调用。在上述服务中,都是沿用了插件生成的目录,仅修改了部分配置文件。可以参考如下连接的目录结构层次清晰。

go-zero实战

删除旧的rpc服务的所有目录,保留proto文件,添加如下内容:

syntax = "proto3";package rpcservice;
option go_package="./rpcservice";message Request {string ping = 1;
}message Response {string pong = 1;
}service Rpcservice {rpc Ping(Request) returns(Response);// 自定义方法区rpc Create (Request) returns (Response);rpc Update (Request) returns (Response);rpc Remove (Request) returns (Response);rpc Detail (Request) returns (Response);rpc List (Request) returns (Response);
}

注意这里不再是goctl rpc new [name]了,该命令是一键生成rpc标准服务命令,而需要自定义rpc服务即根据编写的.proto文件生成服务需要使用protobuf插件命令。

protoc-gen-go,protoc-gen-go-grpc

这两个插件在grpc服务中一般是需要独立安装的,但是在go-zero中goctl集成了这两个插件。

protoc --go_out=. *.proto
protoc --go-grpc_out=. *.proto

以下是自定义rpc服务部分,goctl生成直接跳过

运行完指令后在proto文件指定的目录生成了grpc服务源码,如下

在这里插入图片描述

在这里插入图片描述

注意如果之前已经使用了goctl rpc命令,那么目录下不止有这两个文件,建议删除goctl生成的文件自定义构建,因为许多用不着到。goctl生成的代码主要是结合了zrpc.RpcServerConf的配置,即config目录下的对象,如下:

在这里插入图片描述
在goctl生成的代码中也是支持flag库的,如下,这里后续将会改成静态的省去配置文件。
在这里插入图片描述

goctl生成部分

通过两个命令生成rpc服务文件,如下:

在这里插入图片描述

在生成的代码中已经具备了逻辑层的方法,如下:

在这里插入图片描述

修改主程序注释调用flag参数获取功能,原因是api服务和rpc服务中都有独立的yaml文件这是不合理的,两个flag参数获取存在冲突,所以将两个都注册掉改为静态配置,后续可以改为一个配置文件。

//注释掉flag获取参数的部分
func main() {flag.Parse()var c config.Config// conf.MustLoad(*configFile, &c)c.ListenOn = "0.0.0.0:9000"c.Name = "order.rpc"ctx := svc.NewServiceContext(c)s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {rpcservice.RegisterRpcserviceServer(grpcServer, server.NewRpcserviceServer(ctx))if c.Mode == service.DevMode || c.Mode == service.TestMode {reflection.Register(grpcServer)}})defer s.Stop()fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)s.Start()
}

修改rpc服务的逻辑如下所示:

在这里插入图片描述
编写客户端访问rpc服务

import ("context""fmt""rpcclient/rpcservice""google.golang.org/grpc""google.golang.org/grpc/credentials/insecure"
)func main() {//配置连连接参数(无加密)dial, _ := grpc.Dial("localhost:9000", grpc.WithTransportCredentials(insecure.NewCredentials()))defer dial.Close()//创建客户端连接client := rpcservice.NewRpcserviceClient(dial)//通过客户端调用方法res, err := client.Ping(context.Background(), &rpcservice.Request{ReqJson: "xiaoxu"})if err != nil {fmt.Println(err)return}fmt.Println(res)}

在这里插入图片描述
成功访问,ping的案例是goctl已经实现了的,虽然早proto中编写了自定义的方法,如下

在这里插入图片描述
但是却仍然无法调用,回报未继承的错误,在客户端加入如下代码:

//order list
r, err := client.List(context.Background(), &rpcservice.Request{})
if err != nil {fmt.Println(err)return
}
fmt.Println(r.ResJson)

在这里插入图片描述

List not implemented该错误的原因时虽然存在该方法名,但方法没有方法体,也就说函数没有将处理逻。

在这里插入图片描述
在服务端通过反射获取到Rpcservice_Ping_FullMethodName的Ping方法,也就是/rpcservice.Rpcservice/Ping的Ping方法。如下图

在这里插入图片描述

该方法就是生成文件的server目录下包的方法,如下:

在这里插入图片描述
在这里插入图片描述

官方提供的方法ping调用了生成的logic的ping方法,其实就是实现了逻辑的解耦,将逻辑功能分隔开来。如下所示,logic层部分只写逻辑处理,在serve下调用逻辑处理部分函数。

在这里插入图片描述
在这里插入图片描述

当然不论如何解耦,核心还是server目录的文件,必须在此处注册逻辑函数,才可以在rpc武器生成函数实例,客户端才可以成功调用。

该方法实际上也是proto中定义的方法的一个重写过程,是接口的是实现。

注册自定义函数

//list 继承
func (s *RpcserviceServer) List(ctx context.Context,in *rpcservice.Request) (*rpcservice.Response, error) {//r, err := logic.List(in)o, err := orderlogic.OrderLogic.List()if err != nil {fmt.Printf("rpc err:%v", err)return &rpcservice.Response{}, err}//o 赚json字符串josnstr, _ := json.Marshal(o)return &rpcservice.Response{ResJson: string(josnstr)}, nil
}

上面代码逻辑部分一起写在rpc方法注册的函数中,当逻辑代码多是就会十分冗余,最好将逻辑部分提取出来封装在新函数中,在注册是调用新方法即可,想官方提供的模板一样。

服务端注册成功后,服务端调用,代码如下:

package mainimport ("context""fmt""rpcclient/rpcservice""google.golang.org/grpc""google.golang.org/grpc/credentials/insecure"
)func main() {//配置连连接参数(无加密)dial, _ := grpc.Dial("localhost:9000", grpc.WithTransportCredentials(insecure.NewCredentials()))defer dial.Close()//创建客户端连接client := rpcservice.NewRpcserviceClient(dial)//通过客户端调用方法res, err := client.Ping(context.Background(), &rpcservice.Request{ReqJson: "xiaoxu"})if err != nil {fmt.Println(err)return}fmt.Println(res)//order listr, err := client.List(context.Background(), &rpcservice.Request{})if err != nil {fmt.Println(err)return}fmt.Println(r.ResJson)}

在这里插入图片描述

别忘了克隆_grpc.pbpb文件。

到此服务order已经可以同时提供api服务和rpc服务了。

gitee地址:https://gitee.com/fireapproval/xiaoxu/tree/xiaoxu/go/go-zero-test

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

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

相关文章

033、TiDB特性_AUTO_INCREMENT

自增列 实现原理使用限制相关参数示例 实现原理 每一个自增列使用一个全局可见的键值对用于记录当前已分配的最大ID为了降低分布式系统分配自增ID的网络开销,每个TiDB节点会缓存一个不重复的ID段当前预分配的ID段使用完毕,或重启,都会重新再…

使用楔形步进体模进行X射线骨密度测定

来源:投稿 作者:洪棋 编辑:学姐 骨密度(BMD)被广泛应用于骨折风险的预测和骨质疏松症的常规识别。双能x线骨密度仪(DXA)在临床上广泛用于测量脊柱、髋关节和前臂的骨密度(aBMD)。放射学骨密度测定法(Radiographic absorpometry, RA)是最早的骨…

微信小程序开发学习之页面导航(声明式导航和编程式导航)

微信小程序之页面导航(声明式导航和编程式导航) 1.0 页面导航1.1. 声明式导航1.1.1. 导航到tabBar页面1.1.2. 导航到非tabBar页面1.1.3. 后退导航 1.2. 编程式导航1.2.1. 导航到tabBar页面1.2.2. 导航到非tabBar页面1.2.3. 后退导航 1.3. 导航传参1.3.1.…

ELK之logstash四大组件

ELK之logstash四大组件 一、grok(正则捕获插件) 1.内置正则调用 //内置正则表达式调用 %{SYNTAX:SEMANTIC}●SYNTAX代表匹配值的类型,例如,0.11可以NUMBER类型所匹配,10.222.22.25可以使用IP匹配。●SEMANTIC表示存储该值的一个变量声明&a…

软件设计模式与体系结构-设计模式-行为型软件设计模式-访问者模式

目录 二、访问者模式概念代码类图实例一:名牌运动鞋专卖店销售软件实例二:计算机部件销售软优缺点适用场合课程作业 二、访问者模式 概念 对于系统中的某些对象,它们存储在同一个集合中,具有不同的类型对于该集合中的对象&#…

Kafka request.log中RequestQueueTimeMs、LocalTimeMs、RemoteTimeMs、ThrottleTimeMs、含义

Kafka request.log中RequestQueueTimeMs、LocalTimeMs、RemoteTimeMs、ThrottleTimeMs、含义 要理解各个延时项的含义,必须从Kafka收到TCP请求、处理请求到返回TCP包整个流程开始梳理 RequestQueueTimeMs Processor 执行processNewResponses() 方法,不…

Empirical Evaluation of Gated Recurrent Neural Networks on Sequence Modeling

感想:由于看不懂官方代码的原因,自己这方面耽误了一段时间,一方面,在AI与经济学之间犹豫了许久,另一方面,工作 了半年,也没发工资,没空找培训代码的课程,所以停止更新了三…

wx.getUserProfile too frequently 问题

接口调用频率规范 概念介绍 小程序wx接口可分为“普通接口”和“限频接口”。 “限频接口”指的是一个用户在一段时间内不允许频繁调用的wx接口,此类接口一般会调用到微信后台系统资源,为了保护系统,同时防止用户资源被滥用,开…

软件测试基础概念

什么是需求 什么是需求?简单来说就是我想吃饭,这件事就是我的需求,我不想上课,这也是我的需求。对于软件测试来说,需求可以分为用户需求和软件需求。 用户需求 用户需求简单可以理解为甲方提出的要求,如…

git基础教程(linux)

1.git简介 git 分布式版本控制 git两大特点: 版本控制:支持多人同时开发 分布式: 2.安装与配置 安装 sudo apt-get install git安装成功,运行如下命令: git3.创建一个版本库 (1)新建一个目录git_test&#xff0c…

zabbix服务部署

文章目录 zabbix1 zabbix简介1.1 组成部件1.2 监控原理1.3 Zabbix 6.0新特性1.4 Zabbix6.0功能组件1.4.1 Zabbix Server1.4.2 数据库1.4.3 Web界面1.4.4 Zabbix Agent1.4.5 Zabbix Proxy1.4.6 Java Gateway 2 部署zabbix服务端2.1 部署Nginx2.2 安装PHP2.3 修改Nginx配置2.4 修…

找工作不用愁!送你一份Salesforce顾问面试秘籍(下)(含答案)

作为SaaS行业的先驱,Salesforce的发展一路高歌猛进。在Salesforce生态系统中不仅能学习到最新的技术,而且比其他行业的同岗位享有更高的薪水,这也驱使了越来越多人加入Salesforce大军。 在之前的文章中,自由侠部落为学习者梳理了…