Go + gRPC-Gateway(V2) 构建微服务实战系列,小程序登录鉴权服务:第一篇(内附开发 demo)

本文涉及的产品
任务调度 XXL-JOB 版免费试用,400 元额度,开发版规格
云原生网关 MSE Higress,422元/月
注册配置 MSE Nacos/ZooKeeper,118元/月
简介: Go + gRPC-Gateway(V2) 构建微服务实战系列,小程序登录鉴权服务:第一篇(内附开发 demo)

简介



小程序可以通过微信官方提供的登录能力方便地获取微信提供的用户身份标识,快速建立小程序内的用户体系。


系列


  1. 云原生 API 网关,gRPC-Gateway V2 初探


业务流程


微信图片_20220611155250.jpg


  • 官方开发接入文档


初始化项目


开发环境


为少 的本地开发环境


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 名字。


go-grpc-gateway-v2-microservice


├── 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 文件,代码结构如下:


├── 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 定义:


……
// 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,
})


微信图片_20220611155359.png

相关文章
|
4天前
|
NoSQL MongoDB 微服务
微服务——MongoDB实战演练——文章评论的基本增删改查
本节介绍了文章评论的基本增删改查功能实现。首先,在`cn.itcast.article.dao`包下创建数据访问接口`CommentRepository`,继承`MongoRepository`以支持MongoDB操作。接着,在`cn.itcast.article.service`包下创建业务逻辑类`CommentService`,通过注入`CommentRepository`实现保存、更新、删除及查询评论的功能。最后,新建Junit测试类`CommentServiceTest`,对保存和查询功能进行测试,并展示测试结果截图,验证功能的正确性。
17 2
|
4天前
|
NoSQL Java MongoDB
微服务——MongoDB实战演练——文章评论实体类的编写
本节主要介绍文章评论实体类的编写,创建了包`cn.itcast.article.po`用于存放实体类。具体实现中,`Comment`类通过`@Document`注解映射到MongoDB的`comment`集合,包含主键、内容、发布时间、用户ID、昵称等属性,并通过`@Indexed`和`@CompoundIndex`注解添加单字段及复合索引,以提升查询效率。同时提供了Mongo命令示例,便于理解和操作。
16 2
|
4天前
|
NoSQL 测试技术 MongoDB
微服务——MongoDB实战演练——MongoTemplate实现评论点赞
本节介绍如何使用MongoTemplate实现评论点赞功能。传统方法通过查询整个文档并更新所有字段,效率较低。为优化性能,采用MongoTemplate对特定字段直接操作。代码中展示了如何利用`Query`和`Update`对象构建更新逻辑,通过`update.inc("likenum")`实现点赞数递增。测试用例验证了功能的正确性,确保点赞数成功加1。
14 0
|
4天前
|
NoSQL 测试技术 MongoDB
微服务——MongoDB实战演练——根据上级ID查询文章评论的分页列表
本节介绍如何根据上级ID查询文章评论的分页列表,主要包括以下内容:(1)在CommentRepository中新增`findByParentid`方法,用于按父ID查询子评论分页列表;(2)在CommentService中新增`findCommentListPageByParentid`方法,封装分页逻辑;(3)提供JUnit测试用例,验证功能正确性;(4)使用Compass插入测试数据并执行测试,展示查询结果。通过这些步骤,实现对评论的高效分页查询。
18 0
|
4天前
|
NoSQL MongoDB 微服务
微服务——MongoDB实战演练——文章微服务模块搭建
本节介绍文章微服务模块的搭建过程,主要包括以下步骤:(1)创建项目工程 *article*,并在 *pom.xml* 中引入依赖;(2)配置 *application.yml* 文件;(3)创建启动类 *cn.itcast.article.ArticleApplication*;(4)启动项目,确保控制台无错误提示。通过以上步骤,完成文章微服务模块的基础构建与验证。
13 0
|
2月前
|
移动开发 小程序
thinkphp+uniapp开发的多端商城系统源码/H5/小程序/APP支持DIY模板直播分销
thinkphp+uniapp开发的多端商城系统源码/H5/小程序/APP支持DIY模板直播分销
66 0
|
4月前
|
小程序 前端开发 JavaScript
在线课堂+工具组件小程序uniapp移动端源码
在线课堂+工具组件小程序uniapp移动端源码
106 0
在线课堂+工具组件小程序uniapp移动端源码
|
5月前
|
移动开发 小程序 数据可视化
基于npm CLI脚手架的uniapp项目创建、运行与打包全攻略(微信小程序、H5、APP全覆盖)
基于npm CLI脚手架的uniapp项目创建、运行与打包全攻略(微信小程序、H5、APP全覆盖)
718 3
|
5月前
|
小程序 API
微信小程序更新提醒uniapp
在小程序开发中,版本更新至关重要。本方案利用 `uni-app` 的 `uni.getUpdateManager()` API 在启动时检测版本更新,提示用户并提供立即更新选项,自动下载更新内容,并在更新完成后重启小程序以应用新版本。适用于微信小程序,确保用户始终使用最新版本。以下是实现步骤: ### 实现步骤 1. **创建更新方法**:在 `App.vue` 中创建 `updateApp` 方法用于检查小程序是否有新版本。 2. **测试**:添加编译模式并选择成功状态进行模拟测试。
124 0
微信小程序更新提醒uniapp
|
7月前
|
小程序 前端开发 Java
SpringBoot+uniapp+uview打造H5+小程序+APP入门学习的聊天小项目
JavaDog Chat v1.0.0 是一款基于 SpringBoot、MybatisPlus 和 uniapp 的简易聊天软件,兼容 H5、小程序和 APP,提供丰富的注释和简洁代码,适合初学者。主要功能包括登录注册、消息发送、好友管理及群组交流。
175 0
SpringBoot+uniapp+uview打造H5+小程序+APP入门学习的聊天小项目