当前位置: 首页 > news >正文

【Go 与云原生】先从 Go 对与云原生的依赖关系讲起,再讲讲 一个简单的 Go 项目热热身

文章目录

  • 本文的任务
  • GO 语言
    • GO 与 Docker(容器运行时)的关系
    • GO 与 Kubernetes 的关系
    • 要澄清的一点
  • 一个简单的 go 项目——商品信息服务系统
    • Go 项目是前后端分离的
    • 前端网关部分的代码
      • 模拟用户的输入流量
      • grpc 客户端,发送数据
      • 启动前端客户端主程序
    • 后端服务部分的代码
      • 模拟后端服务器经过复杂处理后的输出流量
      • GRPC 服务器的业务化私人定制设置
      • GRPC 服务主程序,接收客户端请求
      • 项目运行,项目效果

推荐一个零声教育学习教程,个人觉得老师讲得不错,分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,点击立即学习: https://github.com/0voice 链接。

本文的任务

我将要介绍 go 语言,并简要详细介绍一个 go 项目——商品信息服务系统。

GO 语言

GO 语言(又称 Golang)是一种由 Google 开发的开源编程语言。(它的运行效率,肯定是不如 C/C++ 高的,但是它的语法简洁,实在很适合作业务开发。)一个程序员没有用很高的计算机设备知识,都可以写业务。

  • 编译型语言:直接编译为机器码,执行效率高
  • 静态类型:类型安全,编译时检查错误
  • 垃圾回收:自动内存管理(GC 机制,所以他会比 C/C++ )
  • 并发原生支持:goroutine 和 channel 机制(协程)
  • 简洁语法:类似 C,但更加现代化和简洁
    在这里插入图片描述

GO 与 Docker(容器运行时)的关系

Docker 是一个云原生工具,它以容器形式让一个程序的运行独立于其开发所在的操作系统环境。也就是说,我在 Linux 环境下开发的 C/C++ 程序,我只需要把依赖的库文件都找出来,放到容器中就行了。

# Docker 的源代码主要是 Go 语言
github.com/docker/docker

在这里插入图片描述

而这个神奇的工具就是由 Go 语言来实现的。

为什么 Docker 选择 Go:
1、编译为静态二进制:单个可执行文件,依赖少
2、跨平台编译:轻松编译为不同架构和操作系统
3、并发处理:适合处理大量容器操作
4、性能优秀:接近 C 的性能,但开发效率更高
5、标准库丰富:特别是网络和系统相关的库

Go 在 Docker 中的优势

  • 部署简单:一个二进制文件包含所有依赖
  • 内存占用小:适合容器化环境
  • 启动快速:毫秒级启动时间

GO 与 Kubernetes 的关系

Docker 容器运行时也有容器编排工具 compose 和 swarm,但是我们一般都会用 Kubernetes,因为这是一个高效的自动运维工具,他是自动化的。

Kubernetes 完全用 Go 编写

# Kubernetes 的所有组件都是 Go 语言
kube-apiserver, kube-controller-manager, kube-scheduler, kubelet, kubectl

在这里插入图片描述


为什么 Kubernetes 选择 Go:

1、并发处理能力

// Kubernetes 使用大量 goroutine 处理并发任务
go podManager.Run(stopCh)
go volumeManager.Run(stopCh)
go probeManager.Run(stopCh)

2、网络编程优势

// 处理 API 请求
http.HandleFunc("/api/v1/pods", func(w http.ResponseWriter, r *http.Request) {// 处理 Pod 相关 API
})

3、跨平台部署

// 轻松编译为各种平台
GOOS=linux GOARCH=amd64 go build

要澄清的一点

尽管 kubenets 和 Docker 都是 go 语言写的,但我们放入其中的程序可以不是 Go 程序(可以是 C/C++ 程序),kubenets 和 Docker 的核心功能只是 “运维”。

一个简单的 go 项目——商品信息服务系统

这个小项目是零声教育的 nick 老师写的案例——商品信息服务,用户请求零声教育的课程 ID 返回对应课程的商品信息(课程 ID + 课程名字 + 课程类别),当然是可以附带价钱信息,这里只是作实例演示。

在项目开始前,我们要梳理一下业务逻辑。我们想要完成的标准化交流
在这里插入图片描述

我们可以把以上的结构化信息通信,归纳成以下的 proto 文件。我们可以用 GRPC 框架(内含 protobuf 框架),这个代码项目技术也是和来自于 Google 公司(包括前面说过的 go 语言与 k8s/kubernets 也都是来自与 Google 公司)。

syntax = "proto3";
option go_package = "0voiceGateWay/services/goods/proto";;
package goods_voice;
message GetGoodsReq{int64 goodsId = 1;
}
message GetGoodsRes {int64 goodsId=1;string goodsName = 2;string goodsCategory = 3;
}
service Goods {rpc Get(GetGoodsReq)returns(GetGoodsRes){}
}

syntax = "proto3" 是指定使用 protobuf 的版本 3 语法,
option go_package = "Goods/proto" protoc 编译器会根据这个路径生成 Go 文件,
package goods_voice 用于防止命名冲突,在生成代码时会作为命名空间
GetGoodsReq - 请求消息

message GetGoodsReq{int64 goodsId = 1;
}

GetGoodsRes - 响应消息

message GetGoodsRes {int64 goodsId = 1;string goodsName = 2;string goodsCategory = 3;
}

服务定义(这个服务只包含 GET 方法,没有 POST 和 PUT 之类的方法,因而相对简单)

service Goods {rpc Get(GetGoodsReq) returns (GetGoodsRes) {}
}

现在,我们可以进行 GRPC 编译了,客户端与服务端的信息通讯的 go 语言版的代码(也可以生成 C/C++ 版本的代码),首先生成 protobuf 序列化与反序列化的代码(grpc 服务的信息反序列化需要用到)

qiming@k8s-master1:~/share/CTASK/docker/code$ protoc.exe --go_out=. --go_opt=paths=source_relative .\services\goods\proto\goods.proto 

紧接着生成 grpc 的客户端、服务端的工具代码。需要注意

qiming@k8s-master1:~/share/CTASK/docker/code$  protoc.exe --go-grpc_out=. --go-grpc_opt=paths=source_relative .\services\goods\proto\goods.proto

需要注意(protobuf 和 grpc 读者请自行下载)
1、protoc.exe:Protocol Buffers 编译器

2、--go_out=.:生成 Go 代码到当前目录

3、--go_opt=paths=source_relative:保持与 proto 文件相同的目录结构,即前面 proto 文件的那一串代码 option go_package = "0voiceGateWay/services/goods/proto"

4、--go-grpc_out=.:生成 gRPC 相关的 Go 代码到当前目录,当前目录指代上面那一个目录

5、--go-grpc_opt=paths=source_relative:保持目录结构,即前面 proto 文件的那一串代码 option go_package = "0voiceGateWay/services/goods/proto"

6、.\services\goods\proto\goods.proto 是 proto 文件所在的路径,

于是生了两个代码,我把他们 一式两份 放在了搭建前端、后端的代码文件夹之内,前端与后端用同一份代码。而实际上是分开运行的。

在这里插入图片描述

goods.grpc.pb.go 代码文件的概览(这两个文件我就不细讲了,我只是归纳它们的结构)

// ================================================================================
// 客户端实现
// ================================================================================
1、客户端接口 type GoodsClient interface {Get(ctx context.Context, in *GetGoodsReq, opts ...grpc.CallOption) (*GetGoodsRes, error)}
2、客户端连接通道type goodsClient struct {cc grpc.ClientConnInterface}
3、新建客户端func NewGoodsClient(cc grpc.ClientConnInterface) GoodsClient {return &goodsClient{cc}}
4、客户端发出结构化的请求报文
func (c *goodsClient) Get(ctx context.Context, in *GetGoodsReq, opts ...grpc.CallOption) (*GetGoodsRes, error) {...
}
// =========================================================================================
// 服务端实现
// =========================================================================================
1. 定义服务接口 - 你要实现的方法type GoodsServer interface {Get(context.Context, *GetGoodsReq) (*GetGoodsRes, error)mustEmbedUnimplementedGoodsServer()}
2. 默认的未实现的版本type UnimplementedGoodsServer struct {}func (UnimplementedGoodsServer) Get(context.Context, *GetGoodsReq) (*GetGoodsRes, error) {return nil, status.Errorf(codes.Unimplemented, "method Get not implemented")}func (UnimplementedGoodsServer) mustEmbedUnimplementedGoodsServer() {}type UnsafeGoodsServer interface {mustEmbedUnimplementedGoodsServer()}
3. 服务注册机制,注册一个服务器func RegisterGoodsServer(s grpc.ServiceRegistrar, srv GoodsServer) {s.RegisterService(&Goods_ServiceDesc, srv)}
4. 服务端关心的请求连func _Goods_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {...}
5. 服务器描述符(告诉服务器如何运行,内部网络是怎样的)var Goods_ServiceDesc = grpc.ServiceDesc{ServiceName: "goods_voice.Goods",HandlerType: (*GoodsServer)(nil),Methods: []grpc.MethodDesc{{MethodName: "Get",Handler:    _Goods_Get_Handler,},},Streams:  []grpc.StreamDesc{},Metadata: "services/goods/proto/goods.proto",}

goods.pb.go 代码文件的概览

1、请求消息的结构体type GetGoodsReq struct {// 运行时管理字段(你不直接使用)state         protoimpl.MessageStatesizeCache     protoimpl.SizeCacheunknownFields protoimpl.UnknownFieldsGoodsId int64 `protobuf:"varint,1,opt,name=goodsId,proto3" json:"goodsId,omitempty"`}
2、请求体的方法================================================================================以下是消息体	GetGoodsReq 及其 6 个接口Reset() 			清空消息数据	对象重用、资源池String()			返回可读格式	调试、日志输出ProtoMessage()ProtoReflect()	查看工具接口	读、写、检查、探索(反射)Descriptor()		这个方法是为了向后兼容性而存在的,也是描述这个结构体,相当于用户指南GetGoodsId()		获取消息体的 ID 字段================================================================================
3、响应结构体type GetGoodsRes struct {// 运行时管理字段(你不直接使用)state         protoimpl.MessageStatesizeCache     protoimpl.SizeCacheunknownFields protoimpl.UnknownFieldsGoodsId       int64  `protobuf:"varint,1,opt,name=goodsId,proto3" json:"goodsId,omitempty"`GoodsName     string `protobuf:"bytes,2,opt,name=goodsName,proto3" json:"goodsName,omitempty"`GoodsCategory string `protobuf:"bytes,3,opt,name=goodsCategory,proto3" json:"goodsCategory,omitempty"`}
4、响应体的方法================================================================================以下是消息体	GetGoodsRes 及其 6 个接口Reset() 			清空消息数据	对象重用、资源池String()			返回可读格式	调试、日志输出ProtoMessage()ProtoReflect()	查看工具接口	读、写、检查、探索(反射)Descriptor()		这个方法是为了向后兼容性而存在的,也是描述这个结构体,相当于用户指南GetGoodsId()		获取消息体的 ID 字段GetGoodsName()	获取消息体的 Name 字段GetGoodsCategory()	获取消息体的 Category 字段================================================================================
5、全局变量==============================================================================protobuf 生成的类型注册和依赖关系信息1、File_services_goods_proto_goods_proto,proto 文件的运行时描述符2、file_services_goods_proto_goods_proto_rawDesc,完整的类型系统信息(二进制)3、file_services_goods_proto_goods_proto_rawDescOnce,确保某个函数只执行一次4、file_services_goods_proto_goods_proto_rawDescData,被赋值为 file_services_goods_proto_goods_proto_rawDesc5、函数 file_services_goods_proto_goods_proto_rawDescGZIP	懒加载压缩:第一次调用时才进行 GZIP 压缩6、file_services_goods_proto_goods_proto_msgTypes 存储所有消息类型的元数据数组:索引 0 = TokenRequest 的元数据;索引 1 = TokenResponse 的元数据7、file_services_goods_proto_goods_proto_goTypes - Go 类型映射8、file_services_goods_proto_goods_proto_depIdxs - 依赖关系索引==============================================================================

Go 项目是前后端分离的

从以上的讨论,我们可以看到 Google 公司的 GRPC 与 Protobuf 的项目技术能很好的实现前后端分离(我只是没有实现一个漂亮的前端界面而已)。


前端网关部分的代码

模拟用户的输入流量

由于我们这次是案例演示,我们并不做真实的流量,我们的请求内容是提前以 config.yaml 文件准备好的。

order:id: 10goodsId: 20userId: 30amount: 100.00status: 1
goods:serviceAddr: 192.168.152.128:50051

对应的需要把这个文件加载到前端里面(package config 是文件的命名空间)

package configimport ("github.com/spf13/viper""log"
)
//	"github.com/spf13/viper"  是文件格式化读取的工具链
//	"log" 是日志记录文件工具链
//	import 的多个文件之间是靠 package 来进行语义区分的type Config struct {Order struct {		//	订单信息(身份 + )ID      int64GoodsID int64UserID  int64Amount  float64Status  int}Goods struct {		//	商品信息ServiceAddr string}
}var conf *Config	// 全局配置变量,是指针类型func init() {v := viper.New()					// 创建 Viper 实例v.SetConfigType("yaml")				// 设置配置文件格式v.SetConfigFile("config.yaml")		// 设置配置文件路径v.ReadInConfig()					// 读取配置文件conf = &Config{}					// 创建 Config 结构体实例,取地址err := v.Unmarshal(conf)			// 将 YAML 内容解析到结构体(并且返回失败代码)if err != nil {log.Fatal(err)					// 如果出错,记录日志并退出程序}
}func GetConf() *Config {return conf							// 返回 conf 本身
}

grpc 客户端,发送数据

向 grpc 服务器发送订单信息

package orderimport ("0voiceGateway/config"							// 读取配置文件的包"0voiceGateway/services/goods/proto"			// gRPC 商品服务原型 "context"										// 上下文控制"github.com/gin-gonic/gin"						// Web 框架,Gin 引擎"google.golang.org/grpc"						// gRPC 核心库"google.golang.org/grpc/credentials/insecure"	// gRPC 安全凭证"log"											// 日志生产工具链"net/http"										// HTTP 架构
)type Order struct {config *config.Config							// 订单的结构体就是 config 的 package 命名空间语境下的 Config
}func NewOrderInstance(config *config.Config) *Order {return &Order{config: config,								// 这是结构体的初始化,返回	Order 结构体的地址,{} 意思是这个结构体的 config 字段被初始化成传入参数 config}
}//	数据传输结构体,该结构体可序列化,输出为 json 文件
type OrderDetail struct {ID            int64   `json:"id"`				// 告诉JSON序列化器:字段名映射为"id"UserID        int64   `json:"user_id"`			// 告诉JSON序列化器:字段名映射为"user_id"UserName      string  `json:"user_name"`		// 告诉JSON序列化器:字段名映射为"user_name"GoodsID       int64   `json:"goods_id"`			// 告诉JSON序列化器:字段名映射为"goods_id"GoodsName     string  `json:"goods_name"`		// 告诉JSON序列化器:字段名映射为"goods_name"GoodsCategory string  `json:"goods_category"`	// 告诉JSON序列化器:字段名映射为"goods_category"Amount        float64 `json:"amount"`			// 告诉JSON序列化器:字段名映射为"amount"Status        int     `json:"status"`			// 告诉JSON序列化器:字段名映射为"status"
}// 获取订单详情(这是实现了接口)
func (o *Order) GetOrderDetail(c *gin.Context) {detail := o.getOrderDetail()c.JSON(http.StatusOK, detail)		//	用于向客户端返回 JSON 格式的 HTTP 响应
}//	这给对象提供了接口函数
func (o *Order) getOrderDetail() *OrderDetail {userID := o.config.Order.UserID		//	给出 ID user := o.getUser(userID)goodsID := o.config.Order.GoodsID	//	给出 ID goods := o.getGoods(goodsID)detail := &OrderDetail{				//	这是结构体指针的初始化语法,填写信息ID:            o.config.Order.ID,UserID:        userID,UserName:      user.Name,GoodsID:       goodsID,GoodsName:     goods.Name,GoodsCategory: goods.Category,Amount:        o.config.Order.Amount,Status:        o.config.Order.Status,}return detail
}type Goods struct {Id       int64Name     stringCategory string
}//	通过商品 ID 获取商品信息,这给对象提供了接口函数
func (o *Order) getGoods(goodsId int64) *Goods {// 1. 从配置获取商品服务地址goodsAddr := o.config.Goods.ServiceAddr// 2. 建立gRPC连接conn, err := grpc.Dial(goodsAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))if err != nil {log.Println(err)return nil}defer conn.Close()	// 确保连接关闭,defer 的意思是 "延迟执行",函数结束时,conn.Close() 会自动执行!// 3. 创建gRPC客户端client := proto.NewGoodsClient(conn)// 4. 构造请求参数(初始化)in := &proto.GetGoodsReq{GoodsId: o.config.Order.GoodsID,}// 5. 发起gRPC调用,proto 文件是定义了消息与服务的格式res, err := client.Get(context.Background(), in)if err != nil {log.Println(err)return nil}// 6. 转换响应数据return &Goods{Id:       goodsId,Name:     res.GoodsName,Category: res.GoodsCategory,}
}type User struct {Id   int64Name string
}//	通过用户 ID 来获取用户名字,这给对象提供了接口函数
func (o *Order) getUser(userId int64) *User {return &User{Id:   userId,Name: "nick",}
}

我们注意到代码里面有是从配置文件里面获取 192.168.152.128:50051 这个域名-端口信息,并且传入 grpc.Dial 这个函数里面结构化出 conn,以此为依据建立 TCP 连接,

client := proto.NewGoodsClient(conn)

接着就是发起格式化的请求

// 5. 发起gRPC调用,proto 文件是定义了消息与服务的格式
res, err := client.Get(context.Background(), in)

需要注意到这个 client.Get,其实就是前面的 proto 生成代码里面的函数 Get,它调用了请求链

func (c *goodsClient) Get(ctx context.Context, in *GetGoodsReq, opts ...grpc.CallOption) (*GetGoodsRes, error) {out := new(GetGoodsRes)err := c.cc.Invoke(ctx, "/goods_voice.Goods/Get", in, out, opts...)// 	在向一个标准规制化命名的路由,向这个服务器申请服务,进行业务调用/*// 就像打电话:c.cc.Invoke(上下文, "电话号码", 说的话, 听的结果, 通话选项)ctx,            // = 什么时候打电话(超时控制)"/goods_voice.Goods/Get", // = 电话号码in,             // = 你要说的话(请求数据)out,            // = 你听到的回答(响应数据)opts...         // = 通话选项(加密、压缩等)*/if err != nil {return nil, err}return out, nil
}

最后就是,经过服务器的一番折腾返回到客户端,并且把响应报文转化成结构体数据

// 6. 转换响应数据
return &Goods{Id:       goodsId,Name:     res.GoodsName,Category: res.GoodsCategory,
}

启动前端客户端主程序

前端服务器程序其实都有一个共同特点,那就是搭建一个复杂的路由

package mainimport ("0voiceGateway/config"			// 自定义配置包order2 "0voiceGateway/order"	// 自定义订单包,使用别名 order2"github.com/gin-gonic/gin"		// Web 框架"log"							// 日志记录"net"							// 网络操作"net/http"						// HTTP 协议支持
)func main() {r := gin.Default()			// 创建 Gin 引擎实例(带默认中间件),"github.com/gin-gonic/gin"initRouter(r)				// 初始化路由配置r.Run(":8081")				// 启动服务器,监听 8081 端口(路由式的服务器启动,其实是很好用的)
}func initRouter(r *gin.Engine) {conf := config.GetConf()		// 获取配置信息	(初始化)apiGroup := r.Group("/api")		// 创建路由组,所有路径以 /api 开头,服务于网页搜索order := order2.NewOrderInstance(conf)			// 创建订单服务实例apiGroup.GET("/order", order.GetOrderDetail)	// 注册路由:GET /api/orderapiGroup.GET("/health", func(c *gin.Context) {	// 健康检查接口c.JSON(http.StatusOK, gin.H{"message": "OK",})})apiGroup.GET("/ping", func(c *gin.Context) {	// 获取服务器 IP 接口ip := getIP()c.JSON(http.StatusOK, gin.H{"ip": ip,})})
}func getIP() string {var ip stringaddrs, err := net.InterfaceAddrs()	// 获取所有网络接口地址,连同错误值一并返回if err != nil {log.Println(err)	//	log 是一个命名空间return ""}for _, v := range addrs {		// 遍历所有网络接口if ipnet, ok := v.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {if ipnet.IP.To4() != nil {ip = ipnet.IP.String()break}}}return ip
}

必须要注意到代码里面的,函数 GetOrderDetail 里面调用了函数 getGoods

apiGroup.GET("/order", order.GetOrderDetail)

进而调用了 grpc 的客户端 Get 方法

func (c *goodsClient) Get(ctx context.Context, in *GetGoodsReq, opts ...grpc.CallOption)

再多说一嘴,GRPC 的代码里面的那个 Get 方法,调用了服务器里面的请求链 _Goods_Get_Handler,然后调用了拦截器 interceptor(这个拦截器应该是全局唯一的,当然,没有实现拦截器的话也可以。那就不调用了。直接返回 return srv.(GoodsServer).Get(ctx, in)),这个拦截器进而调用了用户自己写的具体业务逻辑服务,程序员只需按要求返回一个有意义的响应体的指针就行了。

后端服务部分的代码

另起一个文件夹,与前面的代码隔离开来

模拟后端服务器经过复杂处理后的输出流量

由于我们这次是案例演示,我们并不做复杂的 mysql-redis 联合操作,获取数据库的数据,我们所给的返回的报文内容,当然也会是提前按照 config.yaml 文件准备好的,就像前面的一样。

goods:name: "c/c++VIP课程"category: "服务器后台"

读取配置,在程序体里面准备好输出流量

package configimport ("github.com/spf13/viper""log"
)type Config struct {Goods struct {Name     stringCategory string}
}var conf *Configfunc init() {v := viper.New()v.SetConfigType("yaml")v.SetConfigFile("config.yaml")v.ReadInConfig()conf = &Config{}err := v.Unmarshal(conf)if err != nil {log.Fatal(err)}
}func GetConf() *Config {return conf
}

GRPC 服务器的业务化私人定制设置

func (s *goodsServer) Get(ctx context.Context, in *proto.GetGoodsReq) 就是我们的私人定制业务函数,我们也看到了,我们是并无设置拦截器的,于是按照默认的方法来处理请求链函数。直接返回对应的结构体的指针了。

package serverimport ("Goods/config""Goods/proto""context"
)type goodsServer struct {config                         *config.Configproto.UnimplementedGoodsServer //	嵌入默认服务器
}func NewGoodsServer(config *config.Config) proto.GoodsServer {return &goodsServer{config: config,}
}func (s *goodsServer) Get(ctx context.Context, in *proto.GetGoodsReq) (*proto.GetGoodsRes, error) {return &proto.GetGoodsRes{GoodsId:       in.GoodsId,GoodsName:     s.config.Goods.Name,GoodsCategory: s.config.Goods.Category,}, nil
}

GRPC 服务主程序,接收客户端请求

这里可以看到,服务器里面实现了 goodsService 这个接口的对象是全局唯一的。

package mainimport ("Goods/config""Goods/proto""Goods/server""log""net""google.golang.org/grpc""google.golang.org/grpc/health""google.golang.org/grpc/health/grpc_health_v1"
)func main() {lis, err := net.Listen("tcp", ":50051")if err != nil {log.Fatal(err)}s := grpc.NewServer() //	goodsServer 这个接口实现的对象conf := config.GetConf()proto.RegisterGoodsServer(s, server.NewGoodsServer(conf))healthCheck := health.NewServer()grpc_health_v1.RegisterHealthServer(s, healthCheck)err = s.Serve(lis) //	grpc 服务器,阻塞执行if err != nil {log.Fatal(err)}
}

s := grpc.NewServer() 实质上是创建了一个 GRPC 服务器,我们可以往里面猛灌执行内容,比如说我们的业务逻辑,比若说我们的 grpc 健康检查,只要是 grpc 官方的东西都可以往里使劲塞。

项目运行,项目效果

我是第一次玩 go 语言代码。go mod init 是创建项目的 go.mod 文件,这个 go.mod 文件是空的,只有项目名(即文件夹的名字)与 go 编译器的版本。随后运行 go mod tidy,编译器会检查代码里面有什么源库文件是我们所需要的,没有这个资源的话就下载,有的话就跳过,生成完整的库文件依赖列表,进而会检测使用了库文件里面的那些对象,都记录在了 go.sum 归纳总结文件里面 。当工程能够顺利编译运行,这两个成熟的 go.sum 和 go.mod 文件就可以作为成熟的经验,分享给有需要的朋友,他们可以按照我们的库文件依赖方案来用,无需试错。因为我们编译的时候,就一定会用到这两个文件。

我们在前端代码的文件夹里面编译前端代码(前面两个命令,我已经提起那做好了生成了 go.mod 和 go.sum 下载好所有的依赖文件了,这是前人的配置经验)

qiming@k8s-master1:~/share/CTASK/docker/code$ go mod init
qiming@k8s-master1:~/share/CTASK/docker/code$ go mod tidy
qiming@k8s-master1:~/share/CTASK/docker/code/0voice-crm/0voiceGateway$ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o 0voiceGateway .

启动前端

qiming@k8s-master1:~/share/CTASK/docker/code/0voice-crm/0voiceGateway$ ./0voiceGateway 
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.- using env:	export GIN_MODE=release- using code:	gin.SetMode(gin.ReleaseMode)[GIN-debug] GET    /api/order                --> 0voiceGateway/order.(*Order).GetOrderDetail-fm (3 handlers)
[GIN-debug] GET    /api/health               --> main.initRouter.func1 (3 handlers)
[GIN-debug] GET    /api/ping                 --> main.initRouter.func2 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8081

我们在后端代码的文件夹里面编译后端代码(前面两个命令,我已经提起那做好了生成了 go.mod 和 go.sum 下载好所有的依赖文件了,这是前人的配置经验)

qiming@k8s-master1:~/share/CTASK/docker/code/0voice-crm/Goods$ go mod init
qiming@k8s-master1:~/share/CTASK/docker/code/0voice-crm/Goods$ go mod tidy
qiming@k8s-master1:~/share/CTASK/docker/code/0voice-crm/Goods$ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o goods .

启动后端

qiming@k8s-master1:~/share/CTASK/docker/code/0voice-crm/Goods$ ./goods

我们采用一个新的超级好用的工具去测试我们的程序,前端程序再端口 8081 里面监听,而且他会发消息去后端程序体(监听端口为 50051,不过现在我们可以先不管)。

在这里插入图片描述
我向前端发送了一个订单请求,获得了成功响应

在这里插入图片描述

在虚拟机后台的前端程序里面则出现了以下情况。细心的你发现了,第一次不成功是因为我的 Gin 路由写错了,当然会出错。

在这里插入图片描述

http://www.dtcms.com/a/589370.html

相关文章:

  • 深圳科技公司排名100搜索引擎优化应注意什么
  • Mac版向日葵command+s保存操作快捷键冲突,打开向日葵设置
  • 解决 地平线4无法连接至地平线生活而无法进行在线游戏 的方法
  • Kafka工作流程及文件存储机制
  • Kafka 消费者
  • RV1126 NO.45:RV1126+OPENCV在视频中添加LOGO图像
  • 在 统一命名空间(UNS)中加入Kafka的方案示例
  • 邯郸网站开发公司电话网站怎么做舆情监测
  • 4.ArrayList 扩容机制与 Fail-Fast 原理
  • 青岛网站域名备案玛酷机器人少儿编程加盟
  • 汽车OTA 测试用例
  • 常州网站建设流程阿里巴巴官网首页登录入口
  • 网站建设流程 知乎网站中文名称注册
  • P7: 《面试准备清单:如何高效覆盖90%的面试考点》
  • 27.短链系统
  • springboot+vue健康食谱交流平台设计(源码+文档+调试+基础修改+答疑)
  • 10.7 密码学中的线性代数
  • 【理论推导】互信息与InfoNCE损失:从公式推导理解对比学习的本质
  • 32HAL——万年历
  • 面向边缘智能的稳健医疗AI:模型性能衰减监控与自适应微调机制深度解析(上)
  • 专业网站发展趋势wordpress html模式
  • 最简单的手机网站制作最常用最齐全wordpress插件大全
  • 【Mybatis笔记】- 1 - MyBatis入门
  • Spring AI Alibaba 提示词入门:从零开始掌握AI对话技巧
  • AI 实战篇:用 LangGraph 串联 RAG+MCP Server,打造能直接操控 Jira 的智能体
  • 爱丽丝的人偶
  • 一个网站里面只放一个图片怎么做的交互式网站有哪些功能
  • 永川区门户网站建设轨迹开发流程管理
  • web中间件——Nginx
  • 读诗的时候我却使用了自己研发的Chrome元素截图插件