帶你十天輕松搞定 Go 微服務系列(三)
序言
我們通過一個系列文章跟大家詳細展示一個 go-zero 微服務示例,整個系列分十篇文章,目錄結構如下:
環(huán)境搭建:帶你十天輕松搞定 Go 微服務系列(一) 服務拆分:帶你十天輕松搞定 Go 微服務系列(二) 用戶服務(本文) 產品服務 訂單服務 支付服務 RPC 服務 Auth 驗證 服務監(jiān)控 鏈路追蹤 分布式事務
期望通過本系列帶你在本機利用 Docker 環(huán)境利用 go-zero 快速開發(fā)一個商城系統(tǒng),讓你快速上手微服務。
完整示例代碼:https://github.com/nivin-studio/go-zero-mall
首先,我們來更新一下上篇文章中的服務拆分圖片,由于微信公眾號手機和電腦端不同步,導致美化的圖片沒有跟大家見面,特此補上,如圖:

3 用戶服務(user)
進入服務工作區(qū)
$?cd?mall/service/user
3.1 生成 user model 模型
創(chuàng)建 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 服務
創(chuàng)建 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 服務
創(chuàng)建 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項目根目錄執(zhí)行如下命令:
$?go?mod?tidy
3.4 編寫 user rpc 服務
3.4.1 修改配置文件
修改 user.yaml 配置文件
$?vim?rpc/etc/user.yaml
修改服務監(jiān)聽地址,端口號為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
}
添加用戶注冊邏輯
用戶注冊流程,先判斷注冊手機號是否已經被注冊,手機號未被注冊,將用戶信息寫入數(shù)據(jù)庫,用戶密碼需要進行加密存儲。
$?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
用戶登錄流程,通過手機號查詢判斷用戶是否是注冊用戶,如果是注冊用戶,需要將用戶輸入的密碼進行加密與數(shù)據(jù)庫中用戶加密密碼進行對比驗證。
$?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 中自定義的參數(shù)
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
歡迎使用 go-zero 并 star 支持我們!
推薦閱讀
評論
圖片
表情
