Preamble
We will show you a go-zero microservice example in detail through a series of articles. The whole series is divided into ten articles. The directory structure is as follows:
- Environment construction
- service split
- User Services (this article)
- product service
- Order service
- payment service
- RPC Service Auth Authentication
- service monitoring
- link tracking
- Distributed transaction
I hope that through this series, you can quickly develop a mall system using go-zero in the Docker environment on the local machine, so that you can quickly get started with microservices.
Full sample code: https://github.com/nivin-studio/go-zero-mall
First of all, let's update the service split picture in the previous article. Since the mobile phone of the WeChat public account is not synchronized with the computer, the beautified picture has not been seen with you. It is hereby added, as shown in the figure:
3 User service (user)
- Enter the service workspace
$ cd mall/service/user
3.1 Generate user model model
- create sql file
$ vim model/user.sql
- write sql file
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;
- Run the template generation command
$ goctl model mysql ddl -src ./model/user.sql -dir ./model -c
3.2 Generate user api service
- create api file
$ vim api/user.api
- write api file
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)
}
- Run the template generation command
$ goctl api go -api ./api/user.api -dir ./api
3.3 Generate user rpc service
- Create proto file
$ vim rpc/user.proto
- write proto file
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);
}
- Run the template generation command
$ goctl rpc proto -src ./rpc/user.proto -dir ./rpc
Add download dependencies
Go back to the root directory of the
mall
project and execute the following command:
$ go mod tidy
3.4 Write user rpc service
3.4.1 Modify the configuration file
- Modify the user.yaml configuration file
$ vim rpc/etc/user.yaml
- Modify the service listening address, the port number is 0.0.0.0:9000,
Etcd
service configuration,Mysql
service configuration,CacheRedis
service configuration
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 Add user model dependency
- Added
Mysql
service configuration, instantiation ofCacheRedis
service configuration
$ 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
}
- Dependency of registered service context
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 Add user registration logic Register
Add password encryption tool
Create a new
crypt
tool library in the root directorycommon
. This tool method is mainly used for password encryption processing.
$ 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))
}
- Add password encryption
Salt
configuration
$ 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
}
Add user registration logic
In the user registration process, first determine whether the registered mobile phone number has been registered, and the mobile phone number has not been registered, then write the user information into the database, and the user password needs to be encrypted and stored.
$ 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 Add user login logic Login
In the user login process, the mobile phone number is used to check whether the user is a registered user. If it is a registered user, the password entered by the user needs to be encrypted and compared with the encrypted password of the user in the database.
$ 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 Add user information logic 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 Write user api service
3.5.1 Modify the configuration file
- Modify the user.yaml configuration file
$ vim api/etc/user.yaml
- Modify the service address, the port number is 0.0.0.0:8000,
Mysql
service configuration,CacheRedis
service configuration,Auth
verification configuration
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 Add user rpc dependencies
- Add
user rpc
service configuration
$ vim api/etc/user.yaml
Name: User
Host: 0.0.0.0
Port: 8000
......
UserRpc:
Etcd:
Hosts:
- etcd:2379
Key: user.rpc
- Add instantiation of
user rpc
service configuration
$ 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
}
- Dependency of registered service context
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 Add user registration logic 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 Add user login logic Login
Added
JWT
toolCreate a new
jwtx
tool library in the root directorycommon
to generate the usertoken
.
$ 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))
}
Add user login logic
By calling
user rpc
service for login verification, after successful login, use the user information to generate the corresponding validity period oftoken
andtoken
.
$ 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 Add user information logic 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
}
Custom parameters in jwt token can be obtained through l.ctx.Value("uid")
3.6 Start the user rpc service
Tip: Starting the service needs to be started in the golang
container
$ cd mall/service/user/rpc
$ go run user.go -f etc/user.yaml
Starting rpc server at 127.0.0.1:9000...
3.7 Start the user api service
Tip: Start the service needs to start in the golang
container
$ cd mall/service/user/api
$ go run user.go -f etc/user.yaml
Starting server at 0.0.0.0:8000...
project address
https://github.com/zeromicro/go-zero
Welcome to go-zero
and star to support us!
WeChat exchange group
Follow the official account of " Practice " and click exchange group to get the QR code of the community group.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。