小程序能夠經過微信官方提供的登陸能力方便地獲取微信提供的用戶身份標識,快速創建小程序內的用戶體系。html
爲少
的本地開發環境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-microservice
golang
├── 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
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); }
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.go
,auth_grpc.pb.go
,auth.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
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/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)) } }
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, })
我是爲少 微信:uuhells123 公衆號:黑客下午茶 加我微信(互相學習交流),關注公衆號(獲取更多學習資料~)