Giới thiệu
Ứng dụng nhỏ có thể sử dụng khả năng đăng nhập được cung cấp bởi WeChat để dễ dàng lấy thông tin nhận dạng người dùng từ WeChat, giúp xây dựng hệ thống người dùng bên trong ứng dụng nhanh chóng.
Mô tả Quy Trình Kinh Doanh
- Tài liệu hướng dẫn tích hợp của WeChat
Cấu Hình Dự Án Ban Đầu
Môi Trường Phát Triển
Môi trường phát triển cục bộ:
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
Kết Cấu Mã Nguồn Ban Đầu
Sử dụng go mod init service để khởi tạo dự án Go, tại đây tôi sử dụng tên module là service.
├── auth // Dịch vụ xác thực
│ ├── api
│ │ ├── gen
│ │ │ ├── v1 // Lưu mã nguồn đã tạo, v1 là phiên bản API đầu tiên
│ │ │ │ ├── auth.proto
│ │ │ │ └── auth.yaml
│ │ ├── auth
│ │ │ └── auth.go // Thực hiện dịch vụ cụ thể
│ ├── wechat
│ └── main.go // Server gRPC cho xác thực
├── gateway // gRPC-Gateway, proxy ngược đến các server gRPC
│ └── main.go
├── gen.sh // Lệnh tạo mã từ `auth.proto`
└── go.mod
Định Nghĩa Lĩnh Vực (auth.proto)
syntax = "proto3";
package auth.v1;
option go_package="service/auth/api/gen/v1;authpb";
// Khách hàng gửi một code
message LoginRequest {
string code = 1;
}
// Máy chủ trả về trạng thái đăng nhập tùy chỉnh (token)
message LoginResponse {
string access_token = 1;
int32 expires_in = 2; // Theo quy định của OAuth2
}
service AuthService {
rpc Login (LoginRequest) returns (LoginResponse);
}
Sử Dụng gRPC-Gateway Để Phơi Bày RESTful JSON API
Định Nghĩa auth.yaml
type: google.api.Service
config_version: 3
http:
rules:
- selector: auth.v1.AuthService.Login
post: /v1/auth/login
body: "*"
Tạo Mã Từ Cấu Hình
Sử Dụng gen.sh Để Tạo Mã Liên Quan Đến 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
Chạy lệnh:
sh gen.sh
Sau khi thành công, sẽ tạo ra các tệp auth.pb.go, auth_grpc.pb.go, auth.pb.gw.go với cấu trúc như sau:
├── auth
│ ├── api
│ │ ├── gen
│ │ │ ├── v1
│ │ │ │ ├── auth.pb.go // Mã protobuf liên quan đến Go
│ │ │ │ ├── auth_grpc.pb.go // Mã server gRPC liên quan đến Go
│ │ │ │ ├── auth.pb.gw.go // Mã gRPC-Gateway liên quan đến Go
│ │ │ │ ├── auth.proto
│ │ │ │ └── auth.yaml
│ │ ├── auth
│ │ │ └── auth.go
│ ├── wechat
│ └── main.go
├── gateway
│ └── main.go
├── gen.sh
└── go.mod
Tổ chức lại gói:
go mod tidy
Thực Hiện Ban Đầu Auth gRPC Service Server
Thực Hiện Giao Diện AuthServiceServer
Xem mã auth_grpc.pb.go để tìm định nghĩa AuthServiceServer:
……
type AuthServiceServer interface {
Login(context.Context, *LoginRequest) (*LoginResponse, error)
mustEmbedUnimplementedAuthServiceServer()
}
……
Thực hiện trong auth/auth/auth.go:
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, 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
}
Thực Hiện Giao Diện OpenIDResolver
Sử dụng thư viện bên thứ ba từ cộng đồng:
go get -u github.com/medivhzhan/weapp/v2
Thực hiện trong auth/wechat/wechat.go:
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
}
Cấu Hình gRPC Server Cho Dịch Vụ Auth
Trong 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))
}
s := grpc.NewServer()
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 serve", zap.Error(err))
}
}
Thực Hiện Ban Đầu API Gateway
Trong 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,
},
UnmarshalOptions: protojson.UnmarshalOptions{
DiscardUnknown: true,
},
},
))
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 serve: %v", err)
}
Thử nghiệm
wx.request({
url: "http://localhost:8080/v1/auth/login",
method: "POST",
data: { code: res.code },
success: console.log,
fail: console.error,
})