preface
We will show you a go zero micro service example in detail through a series of articles. The whole series is divided into ten articles, and the directory structure is as follows:
- Environment construction
- Service splitting
- User services (this article)
- Product service
- Order service
- Payment services
- RPC service Auth authentication
- Service monitoring
- Link tracking
- Distributed transaction
This series is expected to take you to quickly develop a mall system using go zero in the Docker environment on the machine, so that you can quickly start microservices.
Complete sample code: https://github.com/nivin-studio/go-zero-mall
First, mobile phone computer and the computer terminal are not synchronized with the official account of WeChat public.
3 User Service (user)
- Enter the service workspace
$ cd mall/service/user
3.1 generate user model
- Create sql file
$ vim model/user.sql
- Writing sql files
CREATE TABLE `user` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL DEFAULT '' COMMENT 'User name', `gender` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT 'User gender', `mobile` varchar(255) NOT NULL DEFAULT '' COMMENT 'Subscriber telephone', `password` varchar(255) NOT NULL DEFAULT '' COMMENT 'User password', `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 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
- Writing api files
type ( // User login LoginRequest { Mobile string `json:"mobile"` Password string `json:"password"` } LoginResponse { AccessToken string `json:"accessToken"` AccessExpire int64 `json:"accessExpire"` } // User login // User registration 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"` } // User registration // User information UserInfoResponse { Id int64 `json:"id"` Name string `json:"name"` Gender int64 `json:"gender"` Mobile string `json:"mobile"` } // User information ) 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 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"; // User login message LoginRequest { string Mobile = 1; string Password = 2; } message LoginResponse { int64 Id = 1; string Name = 2; int64 Gender = 3; string Mobile = 4; } // User login // User registration 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; } // User registration // User information message UserInfoRequest { int64 Id = 1; } message UserInfoResponse { int64 Id = 1; string Name = 2; int64 Gender = 3; string Mobile = 4; } // User information service User { rpc Login(LoginRequest) returns(LoginResponse); rpc Register(RegisterRequest) returns(RegisterResponse); rpc UserInfo(UserInfoRequest) returns(UserInfoResponse); }
- Run template generation command
$ goctl rpc proto -src ./rpc/user.proto -dir ./rpc
-
Add and download dependent packages
Go back to the root directory of the mall project and execute the following command:
$ go mod tidy
3.4 writing user rpc service
3.4.1 modify configuration file
- Modify user Yaml profile
$ 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 and 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
- Add Mysql service configuration and instantiate CacheRedis 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 }
- Register the dependency of the 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 directory common. This tool method is mainly used for password encryption.
$ 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 judge whether the registered mobile phone number has been registered, the mobile phone number has not been registered, 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) { // Judge whether the mobile phone number has been registered _, err := l.svcCtx.UserModel.FindOneByMobile(in.Mobile) if err == nil { return nil, status.Error(100, "The user already exists") } 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
The user login process determines whether the user is a registered user by querying the mobile phone number. If it is a registered user, the password entered by the user needs to be encrypted and compared with the user encryption password 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) { // Query whether the user exists res, err := l.svcCtx.UserModel.FindOneByMobile(in.Mobile) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "user does not exist") } return nil, status.Error(500, err.Error()) } // Determine whether the password is correct password := cryptx.PasswordEncrypt(l.svcCtx.Config.Salt, in.Password) if password != res.Password { return nil, status.Error(100, "Password error") } return &user.LoginResponse{ Id: res.Id, Name: res.Name, Gender: res.Gender, Mobile: res.Mobile, }, nil }
3.4.5 adding 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) { // Query whether the user exists res, err := l.svcCtx.UserModel.FindOne(in.Id) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "user does not exist") } 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 writing user api service
3.5.1 modify configuration file
- Modify user Yaml profile
$ vim api/etc/user.yaml
- Modify the service address, the port number is 0.0.0.0:8000, the Mysql service configuration, the CacheRedis service configuration, and the Auth authentication 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 dependency
- 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 }
- Register service context user rpc dependency
$ 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
-
Add JWT tool
Create a new jwtx tool library in the root directory common to generate user token s.
$ 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
The user rpc service is called for login verification. After successful login, the user information is used to generate the corresponding token and the validity period of the 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 adding 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 }
Through l.ctx Value ("uid") can obtain the customized parameters in jwt token
3.6 start user rpc service
Tip: start the service 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 user api service
Tip: start the service 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
https://gitee.com/kevwan/go-zero
Welcome to go zero and star support us!
Wechat communication group
Focus on the "micro service practice" official account and click on the exchange group to get the community community's two-dimensional code.