带你十天轻松搞定 Go 微服务系列(三)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 带你十天轻松搞定 Go 微服务系列(三)

序言

我们通过一个系列文章跟大家详细展示一个 go-zero 微服务示例,整个系列分十篇文章,目录结构如下:

  1. 环境搭建:带你十天轻松搞定 Go 微服务系列(一)
  2. 服务拆分:带你十天轻松搞定 Go 微服务系列(二)
  3. 用户服务(本文)
  4. 产品服务
  5. 订单服务
  6. 支付服务
  7. RPC 服务 Auth 验证
  8. 服务监控
  9. 链路追踪
  10. 分布式事务

期望通过本系列带你在本机利用 Docker 环境利用 go-zero 快速开发一个商城系统,让你快速上手微服务。

完整示例代码:https://github.com/nivin-studio/go-zero-mall

首先,我们来更新一下上篇文章中的服务拆分图片,由于微信公众号手机和电脑端不同步,导致美化的图片没有跟大家见面,特此补上,如图:

3 用户服务(user)

  • 进入服务工作区
$ cd mall/service/user

3.1 生成 user model 模型

  • 创建 sql 文件
$ vim model/user.sql
  • 编写 sql 文件
CREATE TABLE `user` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255)  NOT NULL DEFAULT '' COMMENT '用户姓名',
  `gender` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '用户性别',
  `mobile` varchar(255)  NOT NULL DEFAULT '' COMMENT '用户电话',
  `password` varchar(255)  NOT NULL DEFAULT '' COMMENT '用户密码',
  `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_mobile_unique` (`mobile`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4;
  • 运行模板生成命令
$ goctl model mysql ddl -src ./model/user.sql -dir ./model -c

3.2 生成 user api 服务

  • 创建 api 文件
$ vim api/user.api
  • 编写 api 文件
type (
  // 用户登录
  LoginRequest {
    Mobile   string `json:"mobile"`
    Password string `json:"password"`
  }
  LoginResponse {
    AccessToken  string `json:"accessToken"`
    AccessExpire int64  `json:"accessExpire"`
  }
  // 用户登录
  // 用户注册
  RegisterRequest {
    Name     string `json:"name"`
    Gender   int64  `json:"gender"`
    Mobile   string `json:"mobile"`
    Password string `json:"password"`
  }
  RegisterResponse {
    Id     int64  `json:"id"`
    Name   string `json:"name"`
    Gender int64  `json:"gender"`
    Mobile string `json:"mobile"`
  }
  // 用户注册
  // 用户信息
  UserInfoResponse {
    Id     int64  `json:"id"`
    Name   string `json:"name"`
    Gender int64  `json:"gender"`
    Mobile string `json:"mobile"`
  }
  // 用户信息
)
service User {
  @handler Login
  post /api/user/login(LoginRequest) returns (LoginResponse)
  
  @handler Register
  post /api/user/register(RegisterRequest) returns (RegisterResponse)
}
@server(
  jwt: Auth
)
service User {
  @handler UserInfo
  post /api/user/userinfo() returns (UserInfoResponse)
}
  • 运行模板生成命令
$ goctl api go -api ./api/user.api -dir ./api

3.3 生成 user rpc 服务

  • 创建 proto 文件
$ vim rpc/user.proto
  • 编写 proto 文件
syntax = "proto3";
package userclient;
option go_package = "user";
// 用户登录
message LoginRequest {
    string Mobile = 1;
    string Password = 2;
}
message LoginResponse {
    int64 Id = 1;
    string Name = 2;
    int64 Gender = 3;
    string Mobile = 4;
}
// 用户登录
// 用户注册
message RegisterRequest {
    string Name = 1;
    int64 Gender = 2;
    string Mobile = 3;
    string Password = 4;
}
message RegisterResponse {
    int64 Id = 1;
    string Name = 2;
    int64 Gender = 3;
    string Mobile = 4;
}
// 用户注册
// 用户信息
message UserInfoRequest {
    int64 Id = 1;
}
message UserInfoResponse {
    int64 Id = 1;
    string Name = 2;
    int64 Gender = 3;
    string Mobile = 4;
}
// 用户信息
service User {
    rpc Login(LoginRequest) returns(LoginResponse);
    rpc Register(RegisterRequest) returns(RegisterResponse);
    rpc UserInfo(UserInfoRequest) returns(UserInfoResponse);
}
  • 运行模板生成命令
$ goctl rpc proto -src ./rpc/user.proto -dir ./rpc
  • 添加下载依赖包
    回到 mall 项目根目录执行如下命令:
$ go mod tidy

3.4 编写 user rpc 服务

3.4.1 修改配置文件

  • 修改 user.yaml 配置文件
$ vim rpc/etc/user.yaml
  • 修改服务监听地址,端口号为0.0.0.0:9000,Etcd 服务配置,Mysql 服务配置,CacheRedis 服务配置
Name: user.rpc
ListenOn: 0.0.0.0:9000
Etcd:
  Hosts:
  - etcd:2379
  Key: user.rpc
Mysql:
  DataSource: root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
CacheRedis:
- Host: redis:6379
  Type: node
  Pass:

3.4.2 添加 user model 依赖

  • 添加 Mysql 服务配置,CacheRedis 服务配置的实例化
$ vim rpc/internal/config/config.go
package config
import (
  "github.com/tal-tech/go-zero/core/stores/cache"
  "github.com/tal-tech/go-zero/zrpc"
)
type Config struct {
  zrpc.RpcServerConf
  Mysql struct {
    DataSource string
  }
  
  CacheRedis cache.CacheConf
}
  • 注册服务上下文 user model 的依赖
$ vim rpc/internal/svc/servicecontext.go
package svc
import (
  "mall/service/user/model"
  "mall/service/user/rpc/internal/config"
  "github.com/tal-tech/go-zero/core/stores/sqlx"
)
type ServiceContext struct {
  Config config.Config
    
  UserModel model.UserModel
}
func NewServiceContext(c config.Config) *ServiceContext {
  conn := sqlx.NewMysql(c.Mysql.DataSource)
  return &ServiceContext{
    Config:    c,
    UserModel: model.NewUserModel(conn, c.CacheRedis),
  }
}

3.4.3 添加用户注册逻辑 Register

  • 添加密码加密工具
    在根目录 common 新建 crypt 工具库,此工具方法主要用于密码的加密处理。
$ vim common/cryptx/crypt.go
package cryptx
import (
  "fmt"
  "golang.org/x/crypto/scrypt"
)
func PasswordEncrypt(salt, password string) string {
  dk, _ := scrypt.Key([]byte(password), []byte(salt), 32768, 8, 1, 32)
  return fmt.Sprintf("%x", string(dk))
}
  • 添加密码加密 Salt 配置
$ vim rpc/etc/user.yaml
Name: user.rpc
ListenOn: 0.0.0.0:9000
...
Salt: HWVOFkGgPTryzICwd7qnJaZR9KQ2i8xe
$ vim rpc/internal/config/config.go
package config
import (
  "github.com/tal-tech/go-zero/core/stores/cache"
  "github.com/tal-tech/go-zero/zrpc"
)
type Config struct {
  ...
  Salt string
}
  • 添加用户注册逻辑
    用户注册流程,先判断注册手机号是否已经被注册,手机号未被注册,将用户信息写入数据库,用户密码需要进行加密存储。
$ vim rpc/internal/logic/registerlogic.go
package logic
import (
  "context"
  "mall/common/cryptx"
  "mall/service/user/model"
  "mall/service/user/rpc/internal/svc"
  "mall/service/user/rpc/user"
  "github.com/tal-tech/go-zero/core/logx"
  "google.golang.org/grpc/status"
)
type RegisterLogic struct {
  ctx    context.Context
  svcCtx *svc.ServiceContext
  logx.Logger
}
func NewRegisterLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RegisterLogic {
  return &RegisterLogic{
    ctx:    ctx,
    svcCtx: svcCtx,
    Logger: logx.WithContext(ctx),
  }
}
func (l *RegisterLogic) Register(in *user.RegisterRequest) (*user.RegisterResponse, error) {
  // 判断手机号是否已经注册
  _, err := l.svcCtx.UserModel.FindOneByMobile(in.Mobile)
  if err == nil {
    return nil, status.Error(100, "该用户已存在")
  }
  if err == model.ErrNotFound {
    newUser := model.User{
      Name:     in.Name,
      Gender:   in.Gender,
      Mobile:   in.Mobile,
      Password: cryptx.PasswordEncrypt(l.svcCtx.Config.Salt, in.Password),
    }
    res, err := l.svcCtx.UserModel.Insert(&newUser)
    if err != nil {
      return nil, status.Error(500, err.Error())
    }
    newUser.Id, err = res.LastInsertId()
    if err != nil {
      return nil, status.Error(500, err.Error())
    }
    return &user.RegisterResponse{
      Id:     newUser.Id,
      Name:   newUser.Name,
      Gender: newUser.Gender,
      Mobile: newUser.Mobile,
    }, nil
  }
  return nil, status.Error(500, err.Error())
}

3.4.4 添加用户登录逻辑 Login

用户登录流程,通过手机号查询判断用户是否是注册用户,如果是注册用户,需要将用户输入的密码进行加密与数据库中用户加密密码进行对比验证。

$ vim rpc/internal/logic/loginlogic.go
package logic
import (
  "context"
  "mall/common/cryptx"
  "mall/service/user/model"
  "mall/service/user/rpc/internal/svc"
  "mall/service/user/rpc/user"
  "github.com/tal-tech/go-zero/core/logx"
  "google.golang.org/grpc/status"
)
type LoginLogic struct {
  ctx    context.Context
  svcCtx *svc.ServiceContext
  logx.Logger
}
func NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginLogic {
  return &LoginLogic{
    ctx:    ctx,
    svcCtx: svcCtx,
    Logger: logx.WithContext(ctx),
  }
}
func (l *LoginLogic) Login(in *user.LoginRequest) (*user.LoginResponse, error) {
  // 查询用户是否存在
  res, err := l.svcCtx.UserModel.FindOneByMobile(in.Mobile)
  if err != nil {
    if err == model.ErrNotFound {
      return nil, status.Error(100, "用户不存在")
    }
    return nil, status.Error(500, err.Error())
  }
  // 判断密码是否正确
  password := cryptx.PasswordEncrypt(l.svcCtx.Config.Salt, in.Password)
  if password != res.Password {
    return nil, status.Error(100, "密码错误")
  }
  return &user.LoginResponse{
    Id:     res.Id,
    Name:   res.Name,
    Gender: res.Gender,
    Mobile: res.Mobile,
  }, nil
}

3.4.5 添加用户信息逻辑 UserInfo

$ vim rpc/internal/logic/userinfologic.go
package logic
import (
  "context"
  "mall/service/user/model"
  "mall/service/user/rpc/internal/svc"
  "mall/service/user/rpc/user"
  "github.com/tal-tech/go-zero/core/logx"
  "google.golang.org/grpc/status"
)
type UserInfoLogic struct {
  ctx    context.Context
  svcCtx *svc.ServiceContext
  logx.Logger
}
func NewUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UserInfoLogic {
  return &UserInfoLogic{
    ctx:    ctx,
    svcCtx: svcCtx,
    Logger: logx.WithContext(ctx),
  }
}
func (l *UserInfoLogic) UserInfo(in *user.UserInfoRequest) (*user.UserInfoResponse, error) {
  // 查询用户是否存在
  res, err := l.svcCtx.UserModel.FindOne(in.Id)
  if err != nil {
    if err == model.ErrNotFound {
      return nil, status.Error(100, "用户不存在")
    }
    return nil, status.Error(500, err.Error())
  }
  return &user.UserInfoResponse{
    Id:     res.Id,
    Name:   res.Name,
    Gender: res.Gender,
    Mobile: res.Mobile,
  }, nil
}

3.5 编写 user api 服务

3.5.1 修改配置文件

  • 修改 user.yaml 配置文件
$ vim api/etc/user.yaml
  • 修改服务地址,端口号为0.0.0.0:8000,Mysql 服务配置,CacheRedis 服务配置,Auth 验证配置
Name: User
Host: 0.0.0.0
Port: 8000
Mysql:
  DataSource: root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
CacheRedis:
- Host: redis:6379
  Pass:
  Type: node
Auth:
  AccessSecret: uOvKLmVfztaXGpNYd4Z0I1SiT7MweJhl
  AccessExpire: 86400

3.5.2 添加 user rpc 依赖

  • 添加 user rpc 服务配置
$ vim api/etc/user.yaml
Name: User
Host: 0.0.0.0
Port: 8000
...
UserRpc:
  Etcd:
    Hosts:
    - etcd:2379
    Key: user.rpc
  • 添加 user rpc 服务配置的实例化
$ vim api/internal/config/config.go
package config
import (
  "github.com/tal-tech/go-zero/rest"
  "github.com/tal-tech/go-zero/zrpc"
)
type Config struct {
  rest.RestConf
  Auth struct {
    AccessSecret string
    AccessExpire int64
  }
  UserRpc zrpc.RpcClientConf
}
  • 注册服务上下文 user rpc 的依赖
$ vim api/internal/svc/servicecontext.go
package svc
import (
  "mall/service/user/api/internal/config"
  "mall/service/user/rpc/userclient"
  "github.com/tal-tech/go-zero/zrpc"
)
type ServiceContext struct {
  Config config.Config
    
  UserRpc userclient.User
}
func NewServiceContext(c config.Config) *ServiceContext {
  return &ServiceContext{
    Config:  c,
    UserRpc: userclient.NewUser(zrpc.MustNewClient(c.UserRpc)),
  }
}

3.5.3  添加用户注册逻辑 Register

$ vim api/internal/logic/registerlogic.go
package logic
import (
  "context"
  "mall/service/user/api/internal/svc"
  "mall/service/user/api/internal/types"
  "mall/service/user/rpc/userclient"
  "github.com/tal-tech/go-zero/core/logx"
)
type RegisterLogic struct {
  logx.Logger
  ctx    context.Context
  svcCtx *svc.ServiceContext
}
func NewRegisterLogic(ctx context.Context, svcCtx *svc.ServiceContext) RegisterLogic {
  return RegisterLogic{
    Logger: logx.WithContext(ctx),
    ctx:    ctx,
    svcCtx: svcCtx,
  }
}
func (l *RegisterLogic) Register(req types.RegisterRequest) (resp *types.RegisterResponse, err error) {
  res, err := l.svcCtx.UserRpc.Register(l.ctx, &userclient.RegisterRequest{
    Name:     req.Name,
    Gender:   req.Gender,
    Mobile:   req.Mobile,
    Password: req.Password,
  })
  if err != nil {
    return nil, err
  }
  return &types.RegisterResponse{
    Id:     res.Id,
    Name:   res.Name,
    Gender: res.Gender,
    Mobile: res.Mobile,
  }, nil
}

3.5.4  添加用户登录逻辑 Login

  • 添加 JWT 工具
    在根目录 common 新建 jwtx 工具库,用于生成用户 token
$ vim common/jwtx/jwt.go
package jwtx
import "github.com/golang-jwt/jwt"
func GetToken(secretKey string, iat, seconds, uid int64) (string, error) {
  claims := make(jwt.MapClaims)
  claims["exp"] = iat + seconds
  claims["iat"] = iat
  claims["uid"] = uid
  token := jwt.New(jwt.SigningMethodHS256)
  token.Claims = claims
  return token.SignedString([]byte(secretKey))
}
  • 添加用户登录逻辑
    通过调用 user rpc 服务进行登录验证,登录成功后,使用用户信息生成对应的 token 以及 token 的有效期。
$ vim api/internal/logic/loginlogic.go
package logic
import (
  "context"
  "time"
  "mall/common/jwtx"
  "mall/service/user/api/internal/svc"
  "mall/service/user/api/internal/types"
  "mall/service/user/rpc/userclient"
  "github.com/tal-tech/go-zero/core/logx"
)
type LoginLogic struct {
  logx.Logger
  ctx    context.Context
  svcCtx *svc.ServiceContext
}
func NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) LoginLogic {
  return LoginLogic{
    Logger: logx.WithContext(ctx),
    ctx:    ctx,
    svcCtx: svcCtx,
  }
}
func (l *LoginLogic) Login(req types.LoginRequest) (resp *types.LoginResponse, err error) {
  res, err := l.svcCtx.UserRpc.Login(l.ctx, &userclient.LoginRequest{
    Mobile:   req.Mobile,
    Password: req.Password,
  })
  if err != nil {
    return nil, err
  }
  now := time.Now().Unix()
  accessExpire := l.svcCtx.Config.Auth.AccessExpire
  accessToken, err := jwtx.GetToken(l.svcCtx.Config.Auth.AccessSecret, now, accessExpire, res.Id)
  if err != nil {
    return nil, err
  }
  return &types.LoginResponse{
    AccessToken:  accessToken,
    AccessExpire: now + accessExpire,
  }, nil
}

3.5.5  添加用户信息逻辑 UserInfo

$ vim api/internal/logic/userinfologic.go
package logic
import (
  "context"
  "encoding/json"
  "mall/service/user/api/internal/svc"
  "mall/service/user/api/internal/types"
  "mall/service/user/rpc/userclient"
  "github.com/tal-tech/go-zero/core/logx"
)
type UserInfoLogic struct {
  logx.Logger
  ctx    context.Context
  svcCtx *svc.ServiceContext
}
func NewUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) UserInfoLogic {
  return UserInfoLogic{
    Logger: logx.WithContext(ctx),
    ctx:    ctx,
    svcCtx: svcCtx,
  }
}
func (l *UserInfoLogic) UserInfo() (resp *types.UserInfoResponse, err error) {
  uid, _ := l.ctx.Value("uid").(json.Number).Int64()
  res, err := l.svcCtx.UserRpc.UserInfo(l.ctx, &userclient.UserInfoRequest{
    Id: uid,
  })
  if err != nil {
    return nil, err
  }
  return &types.UserInfoResponse{
    Id:     res.Id,
    Name:   res.Name,
    Gender: res.Gender,
    Mobile: res.Mobile,
  }, nil
}

通过 l.ctx.Value("uid") 可获取 jwt token 中自定义的参数

3.6 启动 user rpc 服务

提示:启动服务需要在 golang 容器中启动

$ cd mall/service/user/rpc
$ go run user.go -f etc/user.yaml
Starting rpc server at 127.0.0.1:9000...

3.7 启动 user api 服务

提示:启动服务需要在 golang 容器中启动

$ cd mall/service/user/api
$ go run user.go -f etc/user.yaml
Starting server at 0.0.0.0:8000...

项目地址

https://github.com/zeromicro/go-zero

相关文章
|
29天前
|
JavaScript Java Go
探索Go语言在微服务架构中的优势
在微服务架构的浪潮中,Go语言以其简洁、高效和并发处理能力脱颖而出。本文将深入探讨Go语言在构建微服务时的性能优势,包括其在内存管理、网络编程、并发模型以及工具链支持方面的特点。通过对比其他流行语言,我们将揭示Go语言如何成为微服务架构中的一股清流。
118 53
|
3月前
|
Go API Docker
热门go与微服务15
热门go与微服务15
33 2
|
4月前
|
消息中间件 人工智能 供应链
go-zero 微服务实战系列(二、服务拆分)
go-zero 微服务实战系列(二、服务拆分)
|
26天前
|
监控 Go API
Go语言在微服务架构中的应用实践
在微服务架构的浪潮中,Go语言以其简洁、高效和并发处理能力脱颖而出,成为构建微服务的理想选择。本文将探讨Go语言在微服务架构中的应用实践,包括Go语言的特性如何适应微服务架构的需求,以及在实际开发中如何利用Go语言的特性来提高服务的性能和可维护性。我们将通过一个具体的案例分析,展示Go语言在微服务开发中的优势,并讨论在实际应用中可能遇到的挑战和解决方案。
|
27天前
|
Go 数据处理 API
Go语言在微服务架构中的应用与优势
本文摘要采用问答形式,以期提供更直接的信息获取方式。 Q1: 为什么选择Go语言进行微服务开发? A1: Go语言的并发模型、简洁的语法和高效的编译速度使其成为微服务架构的理想选择。 Q2: Go语言在微服务架构中有哪些优势? A2: 主要优势包括高性能、高并发处理能力、简洁的代码和强大的标准库。 Q3: 文章将如何展示Go语言在微服务中的应用? A3: 通过对比其他语言和展示Go语言在实际项目中的应用案例,来说明其在微服务架构中的优势。
|
2月前
|
Cloud Native Go API
Go语言在微服务架构中的创新应用与实践
本文深入探讨了Go语言在构建高效、可扩展的微服务架构中的应用。Go语言以其轻量级协程(goroutine)和强大的并发处理能力,成为微服务开发的首选语言之一。通过实际案例分析,本文展示了如何利用Go语言的特性优化微服务的设计与实现,提高系统的响应速度和稳定性。文章还讨论了Go语言在微服务生态中的角色,以及面临的挑战和未来发展趋势。
|
2月前
|
运维 Go 开发者
Go语言在微服务架构中的应用与优势
本文深入探讨了Go语言在构建微服务架构中的独特优势和实际应用。通过分析Go语言的核心特性,如简洁的语法、高效的并发处理能力以及强大的标准库支持,我们揭示了为何Go成为开发高性能微服务的首选语言。文章还详细介绍了Go语言在微服务架构中的几个关键应用场景,包括服务间通信、容器化部署和自动化运维等,旨在为读者提供实用的技术指导和启发。
|
2月前
|
负载均衡 Go API
探索Go语言在微服务架构中的应用与优势
在这篇技术性文章中,我们将深入探讨Go语言(又称为Golang)在构建微服务架构时的独特优势。文章将通过对比分析Go语言与其他主流编程语言,展示Go在并发处理、性能优化、以及开发效率上的优势。同时,我们将通过一个实际的微服务案例,详细说明如何利用Go语言构建高效、可扩展的微服务系统。
|
2月前
|
安全 Go 云计算
探索Go语言在微服务架构中的应用与优势
在本文中,我们将深入探讨Go语言(又称为Golang)在构建微服务架构中的独特优势。文章将分析Go语言的并发模型、简洁的语法以及高效的编译速度,以及这些特性如何使其成为微服务架构的理想选择。我们将通过一个简单的微服务示例,展示Go语言在实际开发中的表现,并讨论其在性能和可维护性方面的优势。
|
2月前
|
消息中间件 监控 Go
Go语言在微服务架构中的优势与实践
【10月更文挑战第10天】Go语言在微服务架构中的优势与实践