Преамбула
Мы подробно покажем вам пример микросервиса с нуля в серии статей. Вся серия разделена на десять статей. Структура каталогов выглядит следующим образом:
- Строительство окружающей среды
- разделение обслуживания
- Пользовательские службы (эта статья)
- продукт сервис
- Заказать услугу
- платежный сервис
- Проверка подлинности службы RPC
- сервисный мониторинг
- отслеживание ссылок
- Распределенная транзакция
Я надеюсь, что с помощью этой серии вы сможете быстро разработать систему торгового центра, используя 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...
адрес проекта
Добро пожаловатьgo-zeroа такжеstarПоддерживать нас!
Группа обмена WeChat
Сфокусируйся на"Практика микросервисов』Общедоступный номер и нажмитегруппа обменаПолучите QR-код группы сообщества.