Go + gRPC-Gateway(V2) 構建微服務實戰系列,小程序登陸鑑權服務:第一篇(內附開發 demo)

簡介

小程序能夠經過微信官方提供的登陸能力方便地獲取微信提供的用戶身份標識,快速創建小程序內的用戶體系。html

系列

  1. 雲原生 API 網關,gRPC-Gateway V2 初探

業務流程

初始化項目

開發環境

爲少 的本地開發環境git

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

初始代碼結構

使用 go mod init server 初始化 Go 項目,這裏(demo)我直接採用 server 做爲當前 module 名字。github

go-grpc-gateway-v2-microservicegolang

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

領域(auth.proto)定義

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

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

// 開發者服務器返回一個自定義登陸態(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: "*"

根據配置生成代碼

使用 gen.sh 生成 gRPC-Gateway 相關代碼

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.goauth_grpc.pb.goauth.pb.gw.go 文件,代碼結構以下:json

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

整理一下包:小程序

go mod tidy

初步實現 Auth gRPC Service Server

實現 AuthServiceServer 接口

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

……
// 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 進行它的實現:服務器

關鍵代碼解讀:微信

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

這裏有一個很是重要的編程理念,用好能夠事半功倍。接口定義由使用者定義而不是實現者,如這裏的 OpenIDResolver 接口。

實現 OpenIDResolver 接口

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

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

咱們在 auth/wechat/wechat.go 進行它的實現:

關鍵代碼解讀:

// 相同的 Service 實現套路再來一遍
// AppID & AppSecret 要可配置,是從外面傳進來的
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)
	}
    // 配置服務器監聽端口
	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))   
	}
}

初步實現 API Gateway

gateway/main.go

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

mux := runtime.NewServeMux(runtime.WithMarshalerOption(
	runtime.MIMEWildcard,
	&runtime.JSONPb{
		MarshalOptions: protojson.MarshalOptions{
			UseEnumNumbers: true, // 枚舉字段的值使用數字
			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 發送的不存在的 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)
}

測試

// 發送 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

我是爲少
微信:uuhells123
公衆號:黑客下午茶
加我微信(互相學習交流),關注公衆號(獲取更多學習資料~)
相關文章
相關標籤/搜索