Вам понадобится десять дней, чтобы легко получить серию микросервисов Go (3)

Микросервисы Go
Вам понадобится десять дней, чтобы легко получить серию микросервисов Go (3)

Преамбула

Мы подробно покажем вам пример микросервиса с нуля в серии статей. Вся серия разделена на десять статей. Структура каталогов выглядит следующим образом:

  1. Строительство окружающей среды
  2. разделение обслуживания
  3. Пользовательские службы (эта статья)
  4. продукт сервис
  5. Заказать услугу
  6. платежный сервис
  7. Проверка подлинности службы RPC
  8. сервисный мониторинг
  9. отслеживание ссылок
  10. Распределенная транзакция

Я надеюсь, что с помощью этой серии вы сможете быстро разработать систему торгового центра, используя go-zero в среде Docker на локальном компьютере, чтобы вы могли быстро приступить к работе с микросервисами.

Полный пример кода:GitHub.com/Вы Вин-Ученик…

Прежде всего, давайте обновим разделенное изображение службы в предыдущей статье.Из-за рассинхронизации мобильного телефона и компьютера с общедоступной учетной записью WeChat улучшенное изображение не было видно с вами, поэтому оно добавляется, как показано на фигура:

3 Пользовательская служба (пользователь)

  • Войдите в рабочее пространство службы
$ cd mall/service/user

3.1 Создание модели пользовательской модели

  • создать 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 Создание пользовательского API-сервиса

  • создать файл апи
$ vim api/user.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 Создание пользовательского сервиса rpc

  • Создать прото файл
$ vim rpc/user.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 Запись пользовательского сервиса 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 Добавление зависимости модели пользователя

  • Добавить к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 Добавить логику регистрации пользователя Регистрация

  • Добавить инструмент шифрования паролей

    в корневом каталоге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 Добавить логику входа пользователя

В процессе входа пользователя номер мобильного телефона используется для проверки того, является ли пользователь зарегистрированным пользователем.Если это зарегистрированный пользователь, введенный пользователем пароль необходимо зашифровать и сравнить с зашифрованным паролем пользователя в база данных.

$ 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 Написать пользовательский 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 Добавление пользовательской зависимости 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 Добавить логику регистрации пользователя Регистрация

$ 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 Добавить логику входа пользователя

  • Добавить к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

3.6 Запуск пользовательской службы 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 Запустите службу пользовательского API

Совет: Запуск службы должен быть вgolangначать в контейнере

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

адрес проекта

GitHub.com/ноль микро/…

Добро пожаловатьgo-zeroа такжеstarПоддерживать нас!

Группа обмена WeChat

Сфокусируйся на"Практика микросервисов』Общедоступный номер и нажмитегруппа обменаПолучите QR-код группы сообщества.