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

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: Go + gRPC-Gateway(V2) 构建微服务实战系列,小程序登录鉴权服务:第二篇(内附开发 demo)

鉴权微服务数据持久化



使用 Docker 快速本地搭建 MongoDB 4.4.5 环境


拉取镜像


docker pull mongo:4.4.5
# ....
# Digest: sha256:67018ee2847d8c35e8c7aeba629795d091f93c93e23d3d60741fde74ed6858c4
# Status: Image is up to date for mongo:4.4.5
# docker.io/library/mongo:4.4.5


启动


docker run -p 27017:27017 -d mongo:4.4.5
docker ps
# e6e8e350e749 mongo:4.4.5 ... 0.0.0.0:27017->27017/tcp ...


OK,我们看到成功映射了容器端口(27017/tcp)到了本机的 :27017


MongoDB for VS Code


因为为少的开发环境是 VS Code,所以安装一下它(开发时,用它足够了)。


微信图片_20220611155852.png


使用 Playground 对 MongoDB 进行 CRUD


开发时,我们可以点击 Create New Playground 按钮,进行数据库相关的 CRUD 操作。


微信图片_20220611155855.png


初始化数据库和表


这里,数据库是grpc-gateway-auth,表是account


use('grpc-gateway-auth');
db.account.drop()
db.account.insertMany([
  {open_id: '123'},
  {open_id: '456'},
])
db.account.find()


微信图片_20220611155917.png


用户 OpenID 查询/插入业务逻辑(MongoDB 指令分析)


一句话描述:

  • account 集合中查找用户 open_id 是否存在,存在就直接返回当前记录,不存在就插入并返回当前插入的记录。

对应数据库操作指令就是如下:


db.account.findAndModify({
  query: {
    open_id: "abcdef"
  },
  update: {
    $setOnInsert: {
      _id: ObjectId("607132dcfbe32307260f728a"),
      open_id: "abcdef"
    }
  },
  upsert: true,
  new: true // 返回新插入的记录
})


注意:

  • upsert 设为 true。满足查询条件的记录存在时,不执行 $setOnInsert 中的操作。满足条件的记录不存在时,执行 $setOnInsert 操作。


编码实战



为微服务提供一个轻量级 DAO


具体源码放在(dao/mongo):


.......
.......
type Mongo struct {
  col      *mongo.Collection
  newObjID func() primitive.ObjectID
}
func NewMongo(db *mongo.Database) *Mongo {
    // 返回个引用出去,根据需要(测试时)外部可随时改 `col` 和 `newObjID` 值
  return &Mongo{
    col:      db.Collection("account"), // 给个初值
    newObjID: primitive.NewObjectID,
  }
}
.......
.......


编写具体的查询/插入业务逻辑


通过 OpenID 查询关联的账号 ID。具体源码放在(dao/mongo):


func (m *Mongo) ResolveAccountID(c context.Context, openID string) (string, error) {
  insertedID := m.newObjID()
  // 对标上面的查询/插入指令
  res := m.col.FindOneAndUpdate(c, bson.M{
    openIDField: openID,
  }, mgo.SetOnInsert(bson.M{
    mgo.IDField: insertedID, // mgo.IDField -> "_id",
    openIDField: openID, // openIDField -> "open_id"
  }), options.FindOneAndUpdate().
    SetUpsert(true).
    SetReturnDocument(options.After))
  if err := res.Err(); err != nil {
    return "", fmt.Errorf("cannot findOneAndUpdate: %v", err)
  }
  var row mgo.ObjID
  err := res.Decode(&row)
  if err != nil {
    return "", fmt.Errorf("cannot decode result: %v", err)
  }
  return row.ID.Hex(), nil
}


Go 操作容器搭建真实的持久化 Unit Tests 环境

单元测试期间,使用 Go 程序完成容器启动与销毁


具体源码放在(dao/mongo.go):


func RunWithMongoInDocker(m *testing.M, mongoURI *string) int {
  c, err := client.NewClientWithOpts()
  if err != nil {
    panic(err)
  }
  ctx := context.Background()
  resp, err := c.ContainerCreate(ctx, &container.Config{
    Image: image,
    ExposedPorts: nat.PortSet{
      containerPort: {},
    },
  }, &container.HostConfig{
    PortBindings: nat.PortMap{
      containerPort: []nat.PortBinding{
        {
          HostIP:   "0.0.0.0", // 127.0.0.1
          HostPort: "0", // 随机挑一个端口
        },
      },
    },
  }, nil, nil, "")
  if err != nil {
    panic(err)
  }
  containerID := resp.ID
  defer func() {
    err := c.ContainerRemove(ctx, containerID, types.ContainerRemoveOptions{Force: true})
    if err != nil {
      panic(err)
    }
  }()
  err = c.ContainerStart(ctx, containerID, types.ContainerStartOptions{})
  if err != nil {
    panic(err)
  }
  inspRes, err := c.ContainerInspect(ctx, containerID)
  if err != nil {
    panic(err)
  }
  hostPort := inspRes.NetworkSettings.Ports[containerPort][0]
  *mongoURI = fmt.Sprintf("mongodb://%s:%s", hostPort.HostIP, hostPort.HostPort)
  return m.Run()
}


编写表格驱动单元测试


具体源码放在(dao/mongo_test.go):


func TestResolveAccountID(t *testing.T) {
  c := context.Background()
  mc, err := mongo.Connect(c, options.Client().ApplyURI(mongoURI))
  if err != nil {
    t.Fatalf("cannot connect mongodb: %v", err)
  }
  m := NewMongo(mc.Database("grpc-gateway-auth"))
  // 初始化两条数据
  _, err = m.col.InsertMany(c, []interface{}{
    bson.M{
      mgo.IDField: mustObjID("606f12ff0ba74007267bfeee"),
      openIDField: "openid_1",
    },
    bson.M{
      mgo.IDField: mustObjID("606f12ff0ba74007267bfeef"),
      openIDField: "openid_2",
    },
  })
  if err != nil {
    t.Fatalf("cannot insert initial values: %v", err)
  }
    // 注意,我猛将 `newObjID` 生成的 ID 变成固定了~
  m.newObjID = func() primitive.ObjectID {
    return mustObjID("606f12ff0ba74007267bfef0")
  }
    // 定义表格测试 case
  cases := []struct {
    name   string
    openID string
    want   string
  }{
    {
      name:   "existing_user",
      openID: "openid_1",
      want:   "606f12ff0ba74007267bfeee",
    },
    {
      name:   "another_existing_user",
      openID: "openid_2",
      want:   "606f12ff0ba74007267bfeef",
    },
    {
      name:   "new_user",
      openID: "openid_3",
      want:   "606f12ff0ba74007267bfef0",
    },
  }
  for _, cc := range cases {
    t.Run(cc.name, func(t *testing.T) {
      id, err := m.ResolveAccountID(context.Background(), cc.openID)
      if err != nil {
        t.Errorf("failed resolve account id for %q: %v", cc.openID, err)
      }
      if id != cc.want {
        t.Errorf("resolve account id: want: %q; got: %q", cc.want, id)
      }
    })
  }
}
func mustObjID(hex string) primitive.ObjectID {
  objID, err := primitive.ObjectIDFromHex(hex)
  if err != nil {
    panic(err)
  }
  return objID
}
func TestMain(m *testing.M) {
  os.Exit(mongotesting.RunWithMongoInDocker(m, &mongoURI))
}


运行测试


我们点击测试函数(TestResolveAccountID)上方的 run test


微信图片_20220611155952.png


我们看到多出来一个 Mongo DB 容器。


联调



测试通过后,一般联调是没有问题的。

具体代码 auth/auth/auth.go


type Service struct {
  Mongo          *dao.Mongo // 肚子里多一个数据访问层
  Logger         *zap.Logger
  OpenIDResolver OpenIDResolver
  authpb.UnimplementedAuthServiceServer
}
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)
  }
  accountID, err := s.Mongo.ResolveAccountID(c, openID) // 查询/插入操作
  if err != nil {
    s.Logger.Error("cannot resolve account id", zap.Error(err))
    return nil, status.Error(codes.Internal, "")
  }
  return &authpb.LoginResponse{
    AccessToken: "token for open id " + accountID,
    ExpiresIn:   7200,
  }, nil
}


具体代码 auth/main.go


authpb.RegisterAuthServiceServer(s, &auth.Service{
  OpenIDResolver: &wechat.Service{
    AppID:     "your-app-id",
    AppSecret: "your-app-secret",
  },
  Mongo:  dao.NewMongo(mongoClient.Database("grpc-gateway-auth")),
  Logger: logger,
})


运行


Service:


go run auth/main.go


gRPC-Gateway:


go run gateway/main.go
相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。   相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
相关文章
|
23小时前
|
运维 负载均衡 API
构建高效微服务架构的七大关键策略
【5月更文挑战第27天】 在当前企业级应用开发中,微服务架构已成为实现敏捷、可扩展和灵活部署的主流解决方案。本文将深入探讨构建和维护一个高效微服务系统的七个关键策略,包括服务划分原则、API网关设计、服务发现与注册、配置管理、熔断机制、分布式跟踪及持续集成和部署。这些策略不仅有助于提升系统的稳定性和弹性,还能确保在不断变化的业务需求面前,系统能够快速响应并保持高效的运行状态。
|
1天前
|
机器学习/深度学习 监控 持续交付
构建高效微服务架构:后端开发的新趋势探索深度学习在图像识别中的边界
【5月更文挑战第27天】随着业务需求的快速变化和市场竞争的激烈,企业需要更灵活、高效和可扩展的系统来支持其运营。微服务架构作为一种新兴的软件开发模式,已经成为后端开发领域的热门话题。本文将深入探讨微服务架构的概念、优势以及如何构建一个高效的微服务架构,帮助后端开发者更好地应对业务挑战。 【5月更文挑战第27天】 随着人工智能的不断进步,深度学习技术已经在图像识别领域取得了显著成就。本文将深入探讨深度学习模型在处理复杂图像数据时的挑战与机遇,分析现有技术的局限性,并提出潜在的改进方向。通过实验验证,我们将展示如何通过创新的网络架构、数据增强策略和损失函数设计来提升模型性能。本研究不仅为深度学习
|
1天前
|
监控 安全 数据管理
构建高效微服务架构的五大核心要素
【5月更文挑战第27天】在现代软件开发中,微服务架构以其灵活性、可扩展性和容错性而受到企业青睐。本文将深入探讨构建一个高效微服务架构所需的五大核心要素:服务划分策略、通信机制、数据管理、安全性和监控与日志系统。这些要素是确保微服务架构能够高效运行并适应快速变化需求的关键。我们将逐一解析每个要素的重要性及其实现方法,为后端开发者提供一套全面的指导原则和实践技巧。
|
1天前
|
消息中间件 缓存 数据库
构建高性能微服务架构:从理论到实践
【5月更文挑战第27天】在现代软件开发中,微服务架构已成为实现可扩展、灵活和容错系统的关键设计模式。本文将深入探讨如何构建一个高性能的微服务系统,包括关键的设计理念、技术选型以及性能优化策略。我们将通过分析真实案例,提供一套实用的指导原则和最佳实践,帮助开发者提升系统的响应速度和处理能力,从而满足不断变化的业务需求。
|
1天前
|
存储 监控 安全
构建高效微服务架构:后端开发的新范式
【5月更文挑战第27天】随着现代软件开发的复杂性日益增加,传统的单体应用架构面临着可扩展性和维护性的瓶颈。本文探讨了采用微服务架构作为解决方案的优势与挑战,并详细阐述了在设计、部署和优化微服务过程中的关键实践和技术考量。通过将大型应用程序拆分成一系列小型、自治的服务,开发团队可以更灵活地应对市场变化,提高系统的可靠性,同时促进技术创新。
|
1天前
|
消息中间件 监控 持续交付
构建高效微服务架构:后端开发的新范式
【5月更文挑战第27天】在现代软件开发领域,随着业务需求的不断复杂化和开发团队规模的增长,传统的单体应用架构逐渐显得笨重且难以维护。微服务架构作为解决这一问题的银弹,其设计理念是拆分大型应用程序为一系列小型、自治的服务单元,每个单元负责一个功能模块并独立运行在其各自的进程中。本文探讨了微服务架构的设计原则、技术选型、以及在实施过程中可能面临的挑战,并提供了一系列解决方案和最佳实践,旨在帮助后端开发人员构建更加灵活、可扩展且高效的系统。
|
1天前
|
消息中间件 持续交付 开发者
构建高效的微服务架构:后端开发的新范式
【5月更文挑战第27天】在现代软件开发中,微服务架构已经成为一种流行的设计模式,它通过将大型应用程序拆分成一组小型、松散耦合的服务来提供灵活性和可扩展性。本文将探讨微服务架构的关键概念、优势以及如何在实际项目中实现这种架构。我们将重点关注后端开发的挑战,包括服务的划分、通信、数据一致性和安全性等方面,并提供实用的解决方案和最佳实践。
|
1天前
|
监控 负载均衡 数据库
构建高效微服务架构:后端开发者的技术挑战与策略
【5月更文挑战第27天】 在数字化转型的浪潮中,微服务架构已成为软件开发的一大趋势。它通过拆分传统单体应用,实现服务的细粒度管理和独立部署,从而提高了系统的可扩展性和灵活性。然而,随之而来的是一系列技术挑战,包括服务治理、数据一致性、网络延迟等问题。本文将探讨这些挑战并提出相应的解决策略,以帮助后端开发者构建和维护一个高效的微服务系统。
|
1天前
|
监控 API 持续交付
构建高效微服务架构:后端开发的新范式
【5月更文挑战第27天】随着现代软件系统的复杂性日益增长,传统的单体应用架构已难以满足快速迭代和灵活部署的需求。微服务架构作为一种新兴的后端开发模式,以其高度模块化和独立部署的特性,为解决这一难题提供了有效的解决方案。本文将深入探讨微服务架构的设计原则、核心技术以及在实现中的最佳实践,旨在为后端开发人员提供构建和维护高效、可靠微服务系统的指导。
|
1天前
|
监控 Cloud Native 持续交付
构建未来:云原生架构在现代企业中的应用与挑战构建高效微服务架构:策略与实践
【5月更文挑战第27天】 随着数字化转型的深入,企业对技术的依赖日益增强。云原生技术以其灵活性、可扩展性和敏捷性成为推动企业IT架构现代化的关键力量。本文将探讨云原生架构的核心概念、实施策略以及在采纳过程中可能遇到的挑战。通过分析案例和最佳实践,旨在为读者提供如何在保持业务连续性的同时,利用云原生技术加速创新的见解。 【5月更文挑战第27天】 在当前软件开发的快速迭代和市场需求多变的背景下,微服务架构以其灵活性、可扩展性和容错性成为企业技术选型的热门。本文将探讨如何构建一个高效的微服务系统,包括关键的设计原则、常用的技术栈选择、以及实施过程中的最佳实践。我们将重点分析如何通过合理的服务划分、