小工具      在线工具  汉语词典  css  js  c++  java

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

# go-zero,golang,微服务,开发语言,go-zero 额外说明

收录于:152天前

简介

项目架构来源于行动中的归零:让微服务走起来。此对该项目有所删减,相对简单适合初学者。

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

环境搭建

  1. 安装goctl
go install github.com/zeromicro/go-zero/tools/goctl@latest
  1. 安装协议
goctl env check --install --verbose --force
  1. 安装归零
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.Config
	c.Host = "0.0.0.0"
	c.Port = 8000

	server := 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.Config
	c.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 main

import (
	"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.Engine

func init() {
    
	var err error
	e, 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='订单表';

控制器逻辑实现

创建新的逻辑目录

在这里插入图片描述

创建订单逻辑类

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.Order
		err := 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.Order
		err := 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.Order
		err := 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": "更新成功!"})

	}
}

//Remove

func OrderRemove() http.HandlerFunc {
    
	return func(w http.ResponseWriter, r *http.Request) {
    
		var req models.Order
		err := 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": "删除成功!"})
	}
}

//List

func 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服务器注册本地方法,实现服务器之间的内部调用。上述服务中,使用的是插件生成的目录,只修改了部分配置文件。您可以参考以下链接以获得清晰的目录结构层次。

归零实战

删除旧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方法。如下图

在这里插入图片描述

该方法是在文件的服务器目录下生成一个包,如下:

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

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

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

当然,无论如何解耦,核心仍然是服务器目录下的一个文件。必须在这里注册逻辑函数,才能在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 main

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)

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

}

在这里插入图片描述

别忘了克隆_grpc.pbpb文件。

此时,服务订单即可同时提供api服务和rpc服务。

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

. . .

相关推荐

额外说明

centos8 - 安装rabbitmq (3.9.9) 进行个人测试

文章目录 安装版本 官方版本对照表 安装步骤 官网方式参照 添加yum存储库信息 yum 安装 修改登录拦截 启动rabbitmq 开启图形化 查看运行状态 创建新用户 赋予权限 登录验证 命令拓展 卸载rabbitmq 卸载erlang支持 其他命令

额外说明

【Python入门教程】第十七章for循环语句

本篇我们介绍 Python for 循环语句,学习如何使用 for 循环语句多次执行某个代码块。 基本 for 循环语句 在编写程序时,我们经常需要重复多次执行某个代码块。为此,我们可以使用 for 循环语句。以下是该语句的语法: for index i

额外说明

day01MyBatisPlus的CRUD 接口(03)

一、insert 1、插入操作 @RunWith(SpringRunner.class) @SpringBootTest public class CRUDTests { @Autowired private UserMapper us

额外说明

Java基础——可变参数,集合操作工具类Collections

(1)可变参数: 1.特点: 可变参数用在形参中可接收多个数据; 可变参数的格式:数据类型...参数名称; 2.作用: 传输参数灵活,方便。可不传参数,可传1个或多个,可传数组; 可变参数在方法内部本质上就是一个数组;  3.注意事项: 一个形参列表中可

额外说明

JavaWeb+JSP+路径问题+跳转(HTML|Servlet|JSP)|这一篇就够了(超详细)

-作者简介:练习时长两年半的Java up主 -个人主页:老茶icon - ps:点赞-是免费的,却可以让写博客的作者开兴好久好久- -系列专栏:Java全栈,计算机系列(火速更新中) - 格言:种一棵树最好的时间是十年前,其次是现在 -动动小手,点个关

额外说明

Netty网络编程(四):Event、Handler和Pipeline

文章目录 简介 ChannelPipeline ChannelHandler ChannelHandlerContext ChannelHandler中的状态变量 异步Handler 总结 简介 上一节我们讲解了netty中的Channel,知道了cha

额外说明

【软考】系统集成项目管理工程师(二十)项目风险管理

一、项目风险管理概述 1. 风险概念 2. 风险分类 3. 风险成本 二、项目风险管理子过程 1. 规划风险管理 2. 识别风险 3. 实施定性风险分析 4. 实施定量风险分析 5. 规划风险应对 6. 控制风险 三、项目风险管理流程梳理 一、项目风险管

额外说明

Windows系统目录ktmw32.dll文件丢失如何解决?

其实很多用户玩单机游戏或者安装软件的时候就出现过这种问题,如果是新手第一时间会认为是软件或游戏出错了,其实并不是这样,其主要原因就是你电脑系统的该dll文件丢失了或没有安装一些系统软件平台所需要的动态链接库,这时你可以下载这个ktmw32.dll文件(挑

ads via 小工具