<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Go + gRPC-Gateway(V2) 構(gòu)建微服務實戰(zhàn)系列,小程序登錄鑒權(quán)服務

          共 6684字,需瀏覽 14分鐘

           ·

          2021-04-09 16:37

          簡介

          小程序可以通過微信官方提供的登錄能力方便地獲取微信提供的用戶身份標識,快速建立小程序內(nèi)的用戶體系。

          系列

          1. 云原生 API 網(wǎng)關(guān),gRPC-Gateway V2 初探

          業(yè)務流程

          • 官方開發(fā)接入文檔

          初始化項目

          開發(fā)環(huán)境

          為少 的本地開發(fā)環(huán)境

          go version
          # go version go1.14.14 darwin/amd64
          protoc --version
          # libprotoc 3.15.7
          protoc-gen-go --version
          # protoc-gen-go v1.26.0
          protoc-gen-go-grpc --version
          # protoc-gen-go-grpc 1.1.0
          protoc-gen-grpc-gateway --version

          初始代碼結(jié)構(gòu)

          使用 go mod init server 初始化 Go 項目,這里(demo)我直接采用 server 作為當前 module 名字。

          go-grpc-gateway-v2-microservice

          ├── auth // 鑒權(quán)微服務
          │   ├── api
          │   ├── ├── gen
          │   ├── ├── ├── v1 // 生成的代碼將放到這里,v1 表示第一個 API 版本
          │   │   ├── auth.proto
          │   │   └── auth.yaml
          │   ├── auth
          │   │   └── auth.go // service 的具體實現(xiàn)
          │   ├── wechat
          │   └── main.go // 鑒權(quán) gRPC server
          ├── gateway // gRPC-Gateway,反向代理到各個 gRPC Server
          │   └── main.go
          ├── gen.sh // 根據(jù) `auth.proto` 生成代碼的命令
          └── go.mod

          領(lǐng)域(auth.proto)定義

          syntax = "proto3";
          package auth.v1;
          option go_package="server/auth/api/gen/v1;authpb";

          // 客戶端發(fā)送一個 code
          message LoginRequest {
          string code = 1;
          }

          // 開發(fā)者服務器返回一個自定義登錄態(tài)(token)
          message LoginResponse {
          string access_token = 1;
          int32 expires_in = 2; // 按 oauth2 約定走
          }

          service AuthService {
          rpc Login (LoginRequest) returns (LoginResponse);
          }

          使用 gRPC-Gateway 暴露 RESTful JSON API

          auth.yaml 定義

          type: google.api.Service
          config_version: 3

          http:
          rules:
          - selector: auth.v1.AuthService.Login
          post: /v1/auth/login
          body: "*"

          根據(jù)配置生成代碼

          使用 gen.sh 生成 gRPC-Gateway 相關(guān)代碼

          PROTO_PATH=./auth/api
          GO_OUT_PATH=./auth/api/gen/v1

          protoc -I=$PROTO_PATH --go_out=paths=source_relative:$GO_OUT_PATH auth.proto
          protoc -I=$PROTO_PATH --go-grpc_out=paths=source_relative:$GO_OUT_PATH auth.proto
          protoc -I=$PROTO_PATH --grpc-gateway_out=paths=source_relative,grpc_api_configuration=$PROTO_PATH/auth.yaml:$GO_OUT_PATH auth.proto

          運行:

          sh gen.sh

          成功后,會生成 auth.pb.go,auth_grpc.pb.go,auth.pb.gw.go 文件,代碼結(jié)構(gòu)如下:

          ├── auth
          │   ├── api
          │   ├── ├── gen
          │   ├── ├── ├── v1
          │   ├── ├── ├── ├── auth.pb.go // 生成的 golang 相關(guān)的 protobuf 代碼
          │   ├── ├── ├── ├── auth_grpc.pb.go // 生成 golang 相關(guān)的 gRPC Server 代碼
          │   ├── ├── ├── ├── auth.pb.gw.go // 生成 golang 相關(guān)的 gRPC-Gateway 代碼
          │   │   ├── auth.proto
          │   │   └── auth.yaml
          │   ├── auth
          │   │   └── auth.go
          │   ├── wechat
          │   └── main.go
          ├── gateway
          │   └── main.go
          ├── gen.sh
          └── go.mod

          整理一下包:

          go mod tidy

          初步實現(xiàn) Auth gRPC Service Server

          實現(xiàn) AuthServiceServer 接口

          我們查看生成 auth_grpc.pb.go 代碼,找到 AuthServiceServer 定義:

          ……
          // AuthServiceServer is the server API for AuthService service.
          // All implementations must embed UnimplementedAuthServiceServer
          // for forward compatibility
          type AuthServiceServer interface {
          Login(context.Context, *LoginRequest) (*LoginResponse, error)
          mustEmbedUnimplementedAuthServiceServer()
          }
          ……

          我們在 auth/auth/auth.go 進行它的實現(xiàn):

          關(guān)鍵代碼解讀:

          // 定義 Service 結(jié)構(gòu)體
          type Service struct {
          Logger *zap.Logger
          OpenIDResolver OpenIDResolver
          authpb.UnimplementedAuthServiceServer
          }
          // 這里作為使用者來說做一個抽象
          // 定義與微信第三方服務器通信的接口
          type OpenIDResolver interface {
          Resolve(code string) (string, error)
          }
          // 具體的方法實現(xiàn)
          func (s *Service) Login(c context.Context, req *authpb.LoginRequest) (*authpb.LoginResponse, error) {
          s.Logger.Info("received code",
          zap.String("code", req.Code))
          // 調(diào)用微信服務器,拿到用戶的唯一標識 openId
          openID, err := s.OpenIDResolver.Resolve(req.Code)
          if err != nil {
          return nil, status.Errorf(codes.Unavailable,
          "cannot resolve openid: %v", err)
          }
          // 調(diào)試代碼,先這樣寫
          return &authpb.LoginResponse{
          AccessToken: "token for open id " + openID,
          ExpiresIn: 7200,
          }, nil
          }

          這里有一個非常重要的編程理念,用好可以事半功倍。接口定義由使用者定義而不是實現(xiàn)者,如這里的 OpenIDResolver 接口。

          實現(xiàn) OpenIDResolver 接口

          這里用到了社區(qū)的一個第三方庫,這里主要用來完成開發(fā)者服務器向微信服務器換取 用戶唯一標識 OpenID 、 用戶在微信開放平臺帳號下的唯一標識 UnionID(若當前小程序已綁定到微信開放平臺帳號) 和 會話密鑰 session_key。當然,不用這個庫,自己寫也挺簡單。

          go get -u github.com/medivhzhan/weapp/v2

          我們在 auth/wechat/wechat.go 進行它的實現(xiàn):

          關(guān)鍵代碼解讀:

          // 相同的 Service 實現(xiàn)套路再來一遍
          // AppID & AppSecret 要可配置,是從外面?zhèn)鬟M來的
          type Service struct {
          AppID string
          AppSecret string
          }
          func (s *Service) Resolve(code string) (string, error) {
          resp, err := weapp.Login(s.AppID, s.AppSecret, code)
          if err != nil {
          return "", fmt.Errorf("weapp.Login: %v", err)
          }
          if err = resp.GetResponseError(); err != nil {
          return "", fmt.Errorf("weapp response error: %v", err)
          }
          return resp.OpenID, nil
          }

          配置 Auth Service gRPC Server

          auth/main.go

          func main() {
          logger, err := zap.NewDevelopment()
          if err != nil {
          log.Fatalf("cannot create logger: %v", err)
          }
          // 配置服務器監(jiān)聽端口
          lis, err := net.Listen("tcp", ":8081")
          if err != nil {
          logger.Fatal("cannot listen", zap.Error(err))
          }

          // 新建 gRPC server
          s := grpc.NewServer()
          // 配置具體 Service
          authpb.RegisterAuthServiceServer(s, &auth.Service{
          OpenIDResolver: &wechat.Service{
          AppID: "your-app-id",
          AppSecret: "your-app-secret",
          },
          Logger: logger,
          })
          // 對外開始服務
          err = s.Serve(lis)
          if err != nil {
          logger.Fatal("cannot server", zap.Error(err))
          }
          }

          初步實現(xiàn) API Gateway

          gateway/main.go

          // 創(chuàng)建一個可取消的上下文(如:請求發(fā)到一半可隨時取消)
          c := context.Background()
          c, cancel := context.WithCancel(c)
          defer cancel()

          mux := runtime.NewServeMux(runtime.WithMarshalerOption(
          runtime.MIMEWildcard,
          &runtime.JSONPb{
          MarshalOptions: protojson.MarshalOptions{
          UseEnumNumbers: true, // 枚舉字段的值使用數(shù)字
          UseProtoNames: true,
          // 傳給 clients 的 json key 使用下劃線 `_`
          // AccessToken string `protobuf:"bytes,1,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"`
          // 這里說明應使用 access_token
          },
          UnmarshalOptions: protojson.UnmarshalOptions{
          DiscardUnknown: true, // 忽略 client 發(fā)送的不存在的 poroto 字段
          },
          },
          ))
          err := authpb.RegisterAuthServiceHandlerFromEndpoint(
          c,
          mux,
          "localhost:8081",
          []grpc.DialOption{grpc.WithInsecure()},
          )
          if err != nil {
          log.Fatalf("cannot register auth service: %v", err)
          }

          err = http.ListenAndServe(":8080", mux)
          if err != nil {
          log.Fatalf("cannot listen and server: %v", err)
          }

          測試

          // 發(fā)送 res.code 到后臺換取 openId, sessionKey, unionId
          wx.request({
          url: "http://localhost:8080/v1/auth/login",
          method: "POST",
          data: { code: res.code },
          success: console.log,
          fail: console.error,
          })

          Refs

          • Demo: go-grpc-gateway-v2-microservice

            • https://github.com/Hacker-Linner/go-grpc-gateway-v2-microservice

          • gRPC-Gateway

            • https://github.com/grpc-ecosystem/grpc-gateway

          • gRPC-Gateway Docs

            • https://grpc-ecosystem.github.io/grpc-gateway


          我是為少
          微信:uuhells123
          公眾號:黑客下午茶
          加我微信(互相學習交流),關(guān)注公眾號(獲取更多學習資料~)





          K8S 進階訓練營


           點擊屏末  | 即刻學習
          瀏覽 12
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  大鸡吧日视频在线观看 | 亚洲视频在线视频观看 | 黄色在线网站 | 粉嫩小泬BBBB免费看-百度 | www.97色 |