簡(jiǎn)介
項(xiàng)目架構(gòu)來(lái)源于go-zero實(shí)戰(zhàn):讓微服務(wù)Go起來(lái)。此對(duì)該項(xiàng)目有所刪減,相對(duì)簡(jiǎn)單適合初學(xué)者。
省去了項(xiàng)目中每個(gè)服務(wù)占用獨(dú)立docker的過(guò)程,省略了docker-compose的構(gòu)建過(guò)程。每個(gè)服務(wù)是一個(gè)獨(dú)立的程序不依賴與容器。
環(huán)境搭建
- 安裝goctl
go install github.com/zeromicro/go-zero/tools/goctl@latest
- 安裝protoc
goctl env check --install --verbose --force
- 安裝go-zero
go get -u github.com/zeromicro/go-zero@latest
- 生成api標(biāo)準(zhǔn)api服務(wù)
goctl api new apiservice
- 生成rpc服務(wù)
goctl rpc new rpcservice
生成代碼后
go mod tidy
下載所需依賴。
在apiservice
目錄下的apiservicelogic.go
的27
行后修改為如下圖所示代碼:
刪除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服務(wù)統(tǒng)一為8000系列的端口。
在rpcservice
目錄下的yaml配置文件端口修改為9000系。主函數(shù)做如下修改:
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()
}
啟動(dòng)程序
瀏覽器訪問(wèn)
rpc客戶端訪問(wèn)
新項(xiàng)目復(fù)制兩個(gè)pb文件,編寫(xiě)客戶端主程序。
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"rpcclient/rpcservice"
)
func main() {
//配置連連接參數(shù)(無(wú)加密)
dial, _ := grpc.Dial("localhost:9000", grpc.WithTransportCredentials(insecure.NewCredentials()))
defer dial.Close()
//創(chuàng)建客戶端連接
client := rpcservice.NewRpcserviceClient(dial)
//通過(guò)客戶端調(diào)用方法
res, _ := client.Ping(context.Background(), &rpcservice.Request{Ping: "xiaoxu"})
fmt.Println(res.Pong)
}
go-zero api服務(wù)構(gòu)建
整合mysql數(shù)據(jù)庫(kù)
構(gòu)建如項(xiàng)目上的4個(gè)服務(wù)。4個(gè)服務(wù)都是上一節(jié)構(gòu)建基本項(xiàng)目為基礎(chǔ)的gitee地址
整合數(shù)據(jù)庫(kù)就不再過(guò)多贅述了,整合x(chóng)orm框架,返回?cái)?shù)據(jù)庫(kù)引擎即可,如下:
xorm實(shí)戰(zhàn)——結(jié)構(gòu)體映射到實(shí)現(xiàn)數(shù)據(jù)庫(kù)操作
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("數(shù)據(jù)庫(kù)連接失敗!")
}
// err的錯(cuò)誤處理
Engine = e
}
構(gòu)建mysql數(shù)據(jù)庫(kù)后在其他包下通過(guò)庫(kù)名.Engine
即可使用數(shù)據(jù)庫(kù)引擎。
創(chuàng)建數(shù)據(jù)庫(kù)
CREATE TABLE `order` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主鍵',
`order_id` varchar(100) NOT NULL COMMENT '訂單編號(hào)',
`type` tinyint DEFAULT NULL COMMENT '訂單類(lèi)型',
`customer_id` int DEFAULT NULL COMMENT '用戶編號(hào)',
`amount` int DEFAULT NULL COMMENT '數(shù)量',
`payment` tinyint DEFAULT NULL COMMENT '支付方式',
`status` tinyint DEFAULT NULL COMMENT '狀態(tài)',
`create_time` varchar(100) DEFAULT NULL COMMENT '創(chuàng)建時(shí)間',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='訂單表';
控制器邏輯實(shí)現(xiàn)
新建logic目錄
創(chuàng)建訂單邏輯類(lèi)
type orderLogic struct{}
var OrderLogic orderLogic
// 創(chuàng)建訂單
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是構(gòu)建服務(wù)器引擎的包
控制器調(diào)用邏輯代碼
// create
func OrderCreateController() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
//獲取請(qǐng)求參數(shù)
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
方法不使用結(jié)構(gòu)體模擬類(lèi),直接是一個(gè)函數(shù)也是可以的,但是函數(shù)就不能重名了,在微服務(wù)中可以為單個(gè)函數(shù),在單純的web服務(wù)中還是用類(lèi)實(shí)現(xiàn)比較好。
部分代碼:
// create
func OrderCreateController() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
//獲取請(qǐng)求參數(shù)
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對(duì)mysql的CURD操作。
注冊(cè)路由
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(),
},
},
)
}
綜上所屬,已經(jīng)成功的構(gòu)建了一個(gè)web服務(wù),包括對(duì)訂單order的基本操作。
如果不想手寫(xiě)這莫多代碼,可以看看zero的api語(yǔ)法,直接使用api文件一鍵生成更加方便。
go-zero rpc服務(wù)構(gòu)建
在rpc遠(yuǎn)程調(diào)用中主要用于rpc服務(wù)器注冊(cè)本地的方法,實(shí)現(xiàn)服務(wù)器之間的內(nèi)部調(diào)用。在上述服務(wù)中,都是沿用了插件生成的目錄,僅修改了部分配置文件??梢詤⒖既缦逻B接的目錄結(jié)構(gòu)層次清晰。
go-zero實(shí)戰(zhàn)
刪除舊的rpc服務(wù)的所有目錄,保留proto文件,添加如下內(nèi)容:
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);
// 自定義方法區(qū)
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標(biāo)準(zhǔn)服務(wù)命令,而需要自定義rpc服務(wù)即根據(jù)編寫(xiě)的.proto
文件生成服務(wù)需要使用protobuf
插件命令。
protoc-gen-go,protoc-gen-go-grpc
這兩個(gè)插件在grpc服務(wù)中一般是需要獨(dú)立安裝的,但是在go-zero中goctl
集成了這兩個(gè)插件。
protoc --go_out=. *.proto
protoc --go-grpc_out=. *.proto
以下是自定義rpc服務(wù)部分,goctl生成直接跳過(guò)
運(yùn)行完指令后在proto文件指定的目錄生成了grpc服務(wù)源碼,如下
注意如果之前已經(jīng)使用了goctl rpc命令,那么目錄下不止有這兩個(gè)文件,建議刪除goctl生成的文件自定義構(gòu)建,因?yàn)樵S多用不著到。goctl生成的代碼主要是結(jié)合了zrpc.RpcServerConf
的配置,即config目錄下的對(duì)象,如下:
在goctl生成的代碼中也是支持flag
庫(kù)的,如下,這里后續(xù)將會(huì)改成靜態(tài)的省去配置文件。
goctl生成部分
通過(guò)兩個(gè)命令生成rpc服務(wù)文件,如下:
在生成的代碼中已經(jīng)具備了邏輯層的方法,如下:
修改主程序注釋調(diào)用
flag
參數(shù)獲取功能,原因是api服務(wù)和rpc服務(wù)中都有獨(dú)立的yaml文件這是不合理的,兩個(gè)flag參數(shù)獲取存在沖突,所以將兩個(gè)都注冊(cè)掉改為靜態(tài)配置,后續(xù)可以改為一個(gè)配置文件。
//注釋掉flag獲取參數(shù)的部分
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服務(wù)的邏輯如下所示:
編寫(xiě)客戶端訪問(wèn)rpc服務(wù)
import (
"context"
"fmt"
"rpcclient/rpcservice"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
//配置連連接參數(shù)(無(wú)加密)
dial, _ := grpc.Dial("localhost:9000", grpc.WithTransportCredentials(insecure.NewCredentials()))
defer dial.Close()
//創(chuàng)建客戶端連接
client := rpcservice.NewRpcserviceClient(dial)
//通過(guò)客戶端調(diào)用方法
res, err := client.Ping(context.Background(), &rpcservice.Request{ReqJson: "xiaoxu"})
if err != nil {
fmt.Println(err)
return
}
fmt.Println(res)
}
成功訪問(wèn),ping的案例是goctl已經(jīng)實(shí)現(xiàn)了的,雖然早proto中編寫(xiě)了自定義的方法,如下
但是卻仍然無(wú)法調(diào)用,回報(bào)未繼承的錯(cuò)誤,在客戶端加入如下代碼:
//order list
r, err := client.List(context.Background(), &rpcservice.Request{})
if err != nil {
fmt.Println(err)
return
}
fmt.Println(r.ResJson)
List not implemented
該錯(cuò)誤的原因時(shí)雖然存在該方法名,但方法沒(méi)有方法體,也就說(shuō)函數(shù)沒(méi)有將處理邏。
在服務(wù)端通過(guò)反射
獲取到Rpcservice_Ping_FullMethodName
的Ping方法,也就是/rpcservice.Rpcservice/Ping
的Ping方法。如下圖
該方法就是生成文件的server目錄下包的方法,如下:
官方提供的方法ping
調(diào)用了生成的logic的ping方法,其實(shí)就是實(shí)現(xiàn)了邏輯的解耦,將邏輯功能分隔開(kāi)來(lái)。如下所示,logic層部分只寫(xiě)邏輯處理,在serve下調(diào)用邏輯處理部分函數(shù)。
當(dāng)然不論如何解耦,核心還是server目錄的文件,必須在此處注冊(cè)邏輯函數(shù),才可以在rpc武器生成函數(shù)實(shí)例,客戶端才可以成功調(diào)用。
該方法實(shí)際上也是proto中定義的方法的一個(gè)重寫(xiě)過(guò)程,是接口的是實(shí)現(xiàn)。
注冊(cè)自定義函數(shù)
//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
}
上面代碼邏輯部分一起寫(xiě)在rpc方法注冊(cè)的函數(shù)中,當(dāng)邏輯代碼多是就會(huì)十分冗余,最好將邏輯部分提取出來(lái)封裝在新函數(shù)中,在注冊(cè)是調(diào)用新方法即可,想官方提供的模板一樣。
服務(wù)端注冊(cè)成功后,服務(wù)端調(diào)用,代碼如下:
package main
import (
"context"
"fmt"
"rpcclient/rpcservice"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
//配置連連接參數(shù)(無(wú)加密)
dial, _ := grpc.Dial("localhost:9000", grpc.WithTransportCredentials(insecure.NewCredentials()))
defer dial.Close()
//創(chuàng)建客戶端連接
client := rpcservice.NewRpcserviceClient(dial)
//通過(guò)客戶端調(diào)用方法
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.pb
和pb
文件。
到此服務(wù)order已經(jīng)可以同時(shí)提供api服務(wù)和rpc服務(wù)了。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-729772.html
gitee地址:https://gitee.com/fireapproval/xiaoxu/tree/xiaoxu/go/go-zero-test文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-729772.html
到了這里,關(guān)于go-zero微服務(wù)實(shí)戰(zhàn)——基本環(huán)境搭建的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!