18.增加商品詳細API(rpc)


首先定義proto文件

syntax = "proto3";
package Services;
import "Models.proto";
message ProdsRequest {
    // @inject_tag: json:"size" form:"size"
    int32 size = 1;
    // @inject_tag: uri:"pid"
    int32 prod_id = 2; //因為id前面有_所以在生成的時候這個字段會變成ProdId變成大寫,uri的tag是get請求的參數,我們綁定路由時用的代碼
    /*
   func NewGinRouter(prodService Services.ProdService) *gin.Engine {
    ginRouter := gin.Default()
    ginRouter.Use(InitMiddleware(prodService))
    ginRouter.Use(ErrorMiddleware())
    v1Group := ginRouter.Group("/v1")
    {
        v1Group.Handle("POST", "/prods", GetProdsList)
        v1Group.Handle("GET", "/prods/:pid", GetProdDetail) //這里的pid就對應上面反射的tag
    }
    return ginRouter
}
    */
}

message ProdListResponse {
    repeated ProdModel data = 1;
}

message ProdDetailResponse {
    ProdModel data = 1;
}

service ProdService {
    rpc GetProdsList (ProdsRequest) returns (ProdListResponse);
    rpc GetProdDetail (ProdsRequest) returns (ProdDetailResponse);
}

根據生成的pb文件編寫rpc服務端

package ServiceImpl

import (
    "context"
    "go-micro-grpc/Services"
    "strconv"
    "time"
)

type ProdService struct {
}

func (*ProdService) GetProdsList(ctx context.Context, in *Services.ProdsRequest, res *Services.ProdListResponse) error {
    time.Sleep(time.Second * 3) //設置3秒延遲
    ret := make([]*Services.ProdModel, 0)
    var i int32
    for i = 0; i < in.Size; i++ {
        ret = append(ret, newProd(100+i, "prodname"+strconv.Itoa(100+int(i))))
    }
    res.Data = ret
    return nil
}

func (*ProdService) GetProdDetail(ctx context.Context, req *Services.ProdsRequest, res *Services.ProdDetailResponse) error {
    res.Data = newProd(req.ProdId, "無印良品")
    return nil
}

func newProd(id int32, pname string) *Services.ProdModel {
    return &Services.ProdModel{ProdID: id, ProdName: pname}
}

先把proto文件拷貝到gin(rpc客戶端)這里來生成pb文件

首先所有的中間件都是在handler之前執行的,編寫錯誤處理中間件

package Weblib

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "go-micro/Services"
)

func InitMiddleware(prodService Services.ProdService) gin.HandlerFunc { //返回一個中間件
    return func(context *gin.Context) { //使用中間件封裝prodService到context中
        context.Keys = make(map[string]interface{})
        context.Keys["prodservice"] = prodService
        context.Next()
    }
}

func ErrorMiddleware() gin.HandlerFunc { //所有的中間件都是在請求綁定的handler之前執行完成的
    return func(context *gin.Context) {
        defer func() {
            if r := recover(); r != nil {
                context.JSON(500, gin.H{"status": fmt.Sprintf("%s", r)})
                context.Abort()
            }
        }()
        context.Next()
    }
}

gin綁定中間件並綁定handler

package Weblib

import (
"github.com/gin-gonic/gin"
"go-micro/Services"
)

func NewGinRouter(prodService Services.ProdService) *gin.Engine {
    ginRouter := gin.Default()
    ginRouter.Use(InitMiddleware(prodService))
    ginRouter.Use(ErrorMiddleware())
    v1Group := ginRouter.Group("/v1")
    {
        v1Group.Handle("POST", "/prods", GetProdsList)
        v1Group.Handle("GET", "/prods/:pid", GetProdDetail)
    }
    return ginRouter
}

編寫gin的路由處理函數handler並在handler中調用rpc服務

package Weblib

import (
    "context"
    "fmt"
    "github.com/gin-gonic/gin"
    "go-micro/Services"
    "strconv"
)

func newProd(id int32, pname string) *Services.ProdModel {
    return &Services.ProdModel{ProdID: id, ProdName: pname}
}

func PanicIfError(err error) {
    if err != nil {
        panic(err)
    }
}

func GetProdDetail(ginCtx *gin.Context) {
    var prodReq Services.ProdsRequest
    PanicIfError(ginCtx.BindUri(&prodReq)) //這里要綁定的是uri,因為是get請求參數從uri中拿到的不是form表單
    prodService := ginCtx.Keys["prodservice"].(Services.ProdService) //類型斷言為對應的請求類型
    resp, _ := prodService.GetProdDetail(context.Background(), &prodReq)
    ginCtx.JSON(200, gin.H{"data": resp.Data})
}

func defaultProds() (*Services.ProdListResponse, error) {
    models := make([]*Services.ProdModel, 0)
    var i int32
    for i = 0; i < 5; i++ {
        models = append(models, newProd(100+i, "prodname"+strconv.Itoa(100+int(i))))
    }
    res := &Services.ProdListResponse{}
    res.Data = models
    return res, nil
}

func GetProdsList(ginCtx *gin.Context) {
    prodService := ginCtx.Keys["prodservice"].(Services.ProdService) //類型斷言為對應的請求類型
    var prodReq Services.ProdsRequest
    err := ginCtx.Bind(&prodReq)
    if err != nil {
        ginCtx.JSON(500, gin.H{"status": err.Error()})
    } else {
        prodRes, _ := prodService.GetProdsList(context.Background(), &prodReq)
        ginCtx.JSON(200, gin.H{"data": prodRes.Data})
    }
}

啟動gin server並且使用postman訪問gin server從而拿到rpc server的數據

package main

import (
    "context"
    "fmt"
    "github.com/micro/go-micro"
    "github.com/micro/go-micro/client"
    "github.com/micro/go-micro/registry"
    "github.com/micro/go-micro/web"
    "github.com/micro/go-plugins/registry/consul"
    "go-micro/Services"
    "go-micro/Weblib"
    "go-micro/Wrappers"
)

type logWrapper struct {
    client.Client
}

func (this *logWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {
    fmt.Println("調用接口") //這樣每一次調用調用接口時都會
    return this.Client.Call(ctx, req, rsp)
}
func NewLogWrapper(c client.Client) client.Client {
    return &logWrapper{c}
}
func main() {
    consulReg := consul.NewRegistry( //新建一個consul注冊的地址,也就是我們consul服務啟動的機器ip+端口
        registry.Addrs("localhost:8500"),
    )
    //下面兩局代碼是注冊rpcserver調用客戶端
    myService := micro.NewService(
        micro.Name("prodservice.client"),
        micro.WrapClient(NewLogWrapper),            //在注冊時只需要傳入方法名即可,底層會自動給這個方法傳入client
        micro.WrapClient(Wrappers.NewProdsWrapper), //在注冊時只需要傳入方法名即可,底層會自動給這個方法傳入client
    )
    prodService := Services.NewProdService("prodservice", myService.Client()) //生成的這個客戶端綁定consul中存儲的prodservice服務,只要調用了prodservice接口就會調用我們上面注冊的中間件



    //其實下面這段代碼的作用就是啟動webserver的同事的時候把服務注冊進去
    httpserver := web.NewService( //go-micro很靈性的實現了注冊和反注冊,我們啟動后直接ctrl+c退出這個server,它會自動幫我們實現反注冊
        web.Name("httpprodservice"),                   //注冊進consul服務中的service名字
        web.Address(":8001"),                          //注冊進consul服務中的端口,也是這里我們gin的server地址
        web.Handler(Weblib.NewGinRouter(prodService)), //web.Handler()返回一個Option,我們直接把ginRouter穿進去,就可以和gin完美的結合
        web.Registry(consulReg),                       //注冊到哪個服務器上的consul中
    )
    httpserver.Init() //加了這句就可以使用命令行的形式去設置我們一些啟動的配置
    httpserver.Run()
}

這里的pid就是之前生成pb文件時綁定的tag uri:pid






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM