学习笔记(带你十天轻松搞定|学习笔记:带你十天轻松搞定 Go 微服务系列(三)- 用户服务)

1、学习课程 带你十天轻松搞定 Go 微服务系列(三)
2、生成 user model 模型 在昨天代码目录 xxx/gonivinck/code 下

$ cd mall/service/user # 进入mall/service/user文件夹 $ vim model/user.sql #创建 sql 直接 copy 下面语句就行了

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=InnoDBDEFAULT CHARSET=utf8mb4;

再运行模板生成命令
$ goctl model mysql ddl -src ./model/user.sql -dir ./model -c

有的人就会报错
Command 'goctl' not found, did you mean:command 'doctl' from snap doctl (v1.69.0+git2.8af78470)See 'snap info ' for additional versions.

学习笔记(带你十天轻松搞定|学习笔记:带你十天轻松搞定 Go 微服务系列(三)- 用户服务)
文章图片

解决方法:在宿主机未安装 goctl 是不能访问的使用的,我们进入 golang 容器使用就好了(后面有需要 goctl 都要进入容器使用 goctl )
1. 第一步
$ docker ps | grep golang#查找golang 容器信息

执行结果:
学习笔记(带你十天轻松搞定|学习笔记:带你十天轻松搞定 Go 微服务系列(三)- 用户服务)
文章图片

这里面列出所有在运行的容器信息,第一项就是CONTAINER ID,下面要用到
CONTAINER IDIMAGECOMMAND...PORTSNAMES

2. 第二步
$ docker exec -it container-id/bin/bash#进入容器 默认进入到/usr/src/code可用dir查看目录

这里的 container-id 就换成上面 CONTAINER ID 的信息,我这里是 13258ded6d62
执行结果:
学习笔记(带你十天轻松搞定|学习笔记:带你十天轻松搞定 Go 微服务系列(三)- 用户服务)
文章图片

3. 第三步
$ cd mall/service/user# 进入 mall/service/user $ goctl model mysql ddl -src ./model/user.sql -dir ./model -c#done

当出现 done,就证明执行成功了
执行结果:
学习笔记(带你十天轻松搞定|学习笔记:带你十天轻松搞定 Go 微服务系列(三)- 用户服务)
文章图片

3、生成 user api 服务流程 3.1 创建 api 文件
$ vim api/user.api

3.2 编写 api 文件
type ( // 用户登录 LoginRequest { Mobilestring `json:"mobile"` Password string `json:"password"` } LoginResponse { AccessTokenstring `json:"accessToken"` AccessExpire int64`json:"accessExpire"` } // 用户登录// 用户注册 RegisterRequest { Namestring `json:"name"` Genderint64`json:"gender"` Mobilestring `json:"mobile"` Password string `json:"password"` } RegisterResponse { Idint64`json:"id"` Namestring `json:"name"` Gender int64`json:"gender"` Mobile string `json:"mobile"` } // 用户注册// 用户信息 UserInfoResponse { Idint64`json:"id"` Namestring `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) }

3.3 运行模板生成命令
$ goctl api go -api ./api/user.api -dir ./api

4、生成 user rpc 服务 创建 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 项目根目录执行如下命令:
$ go mod tidy

5、 编写 user rpc 服务 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:9000Etcd: Hosts: - etcd:2379 Key: user.rpcMysql: DataSource: root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghaiCacheRedis: - Host: redis:6379 Type: node

4.2 添加 user model 依赖 添加 Mysql 服务配置,CacheRedis 服务配置的实例化
$ vim rpc/internal/config/config.go

package configimport ( "github.com/zeromicro/go-zero/core/stores/cache" "github.com/zeromicro/go-zero/zrpc" )type Config struct { zrpc.RpcServerConfMysql struct { DataSource string }CacheRedis cache.CacheConf }

注册服务上下文 user model 的依赖
$ vim rpc/internal/svc/servicecontext.go

package svcimport ( "mall/service/user/model" "mall/service/user/rpc/internal/config""github.com/zeromicro/go-zero/core/stores/sqlx" )type ServiceContext struct { Config config.ConfigUserModel model.UserModel }func NewServiceContext(c config.Config) *ServiceContext { conn := sqlx.NewMysql(c.Mysql.DataSource) return &ServiceContext{ Config:c, UserModel: model.NewUserModel(conn, c.CacheRedis), } }

4.3 添加用户注册逻辑 Register 4.3.1 添加密码加密工具
在根目录 common 新建 crypt 工具库,此工具方法主要用于密码的加密处理。
$ mkdir cryptx $ cd cryptx $ vim crypt.go

package cryptximport ( "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)) }

4.3.2 添加密码加密 Salt 配置
$ vim rpc/etc/user.yaml

Name: user.rpc ListenOn: 0.0.0.0:9000...Salt: HWVOFkGgPTryzICwd7qnJaZR9KQ2i8xe

$ vim rpc/internal/config/config.go

package configimport ( "github.com/zeromicro/go-zero/core/stores/cache" "github.com/zeromicro/go-zero/zrpc" )type Config struct { ... Salt string }

4.3.3 添加用户注册逻辑
用户注册流程,先判断注册手机号是否已经被注册,手机号未被注册,将用户信息写入数据库,用户密码需要进行加密存储。
$ vim rpc/internal/logic/registerlogic.go

package logicimport ( "context""mall/common/cryptx" "mall/service/user/model" "mall/service/user/rpc/internal/svc" "mall/service/user/rpc/user""github.com/zeromicro/go-zero/core/logx" "google.golang.org/grpc/status" )type RegisterLogic struct { ctxcontext.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()) }

4.4 添加用户登录逻辑 Login 用户登录流程,通过手机号查询判断用户是否是注册用户,如果是注册用户,需要将用户输入的密码进行加密与数据库中用户加密密码进行对比验证。
$ vim rpc/internal/logic/loginlogic.go

package logicimport ( "context""mall/common/cryptx" "mall/service/user/model" "mall/service/user/rpc/internal/svc" "mall/service/user/rpc/user""github.com/zeromicro/go-zero/core/logx" "google.golang.org/grpc/status" )type LoginLogic struct { ctxcontext.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 }

4.5 添加用户信息逻辑 UserInfo
$ vim rpc/internal/logic/userinfologic.go

package logicimport ( "context""mall/service/user/model" "mall/service/user/rpc/internal/svc" "mall/service/user/rpc/user""github.com/zeromicro/go-zero/core/logx" "google.golang.org/grpc/status" )type UserInfoLogic struct { ctxcontext.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 }

6、编写 user api 服务 6.1 修改配置文件 修改 user.yaml 配置文件
$ vim api/etc/user.yaml

修改服务地址,端口号为0.0.0.0:8000,Mysql 服务配置,CacheRedis 服务配置,Auth 验证配置
Name: User Host: 0.0.0.0 Port: 8000Mysql: DataSource: root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghaiCacheRedis: - Host: redis:6379 Pass: Type: nodeAuth: AccessSecret: uOvKLmVfztaXGpNYd4Z0I1SiT7MweJhl AccessExpire: 86400

6.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 configimport ( "github.com/zeromicro/go-zero/rest" "github.com/zeromicro/go-zero/zrpc" )type Config struct { rest.RestConfAuth struct { AccessSecret string AccessExpire int64 }UserRpc zrpc.RpcClientConf }

注册服务上下文 user rpc 的依赖
$ vim api/internal/svc/servicecontext.go

package svcimport ( "mall/service/user/api/internal/config" "mall/service/user/rpc/userclient""github.com/zeromicro/go-zero/zrpc" )type ServiceContext struct { Config config.ConfigUserRpc userclient.User }func NewServiceContext(c config.Config) *ServiceContext { return &ServiceContext{ Config:c, UserRpc: userclient.NewUser(zrpc.MustNewClient(c.UserRpc)), } }

6.3 添加用户注册逻辑 Register
$ vim api/internal/logic/registerlogic.go

package logicimport ( "context""mall/service/user/api/internal/svc" "mall/service/user/api/internal/types" "mall/service/user/rpc/userclient""github.com/zeromicro/go-zero/core/logx" )type RegisterLogic struct { logx.Logger ctxcontext.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 }

6.4 添加用户登录逻辑 Login 6.4.1添加 JWT 工具
在根目录 common 新建 jwtx 工具库,用于生成用户 token。
$ vim common/jwtx/jwt.go

package jwtximport "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)) }

6.4.2 添加用户登录逻辑
通过调用 user rpc 服务进行登录验证,登录成功后,使用用户信息生成对应的 token 以及 token 的有效期。
$ vim api/internal/logic/loginlogic.go

package logicimport ( "context" "time""mall/common/jwtx" "mall/service/user/api/internal/svc" "mall/service/user/api/internal/types" "mall/service/user/rpc/userclient""github.com/zeromicro/go-zero/core/logx" )type LoginLogic struct { logx.Logger ctxcontext.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.AccessExpireaccessToken, 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 }

6.5 添加用户信息逻辑 UserInfo
$ vim api/internal/logic/userinfologic.go

package logicimport ( "context" "encoding/json""mall/service/user/api/internal/svc" "mall/service/user/api/internal/types" "mall/service/user/rpc/userclient""github.com/zeromicro/go-zero/core/logx" )type UserInfoLogic struct { logx.Logger ctxcontext.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 中自定义的参数
7、 启动 user rpc 服务 提示:启动服务需要在 golang 容器中启动
$ cd mall/service/user/rpc $ go run user.go -f etc/user.yaml

可能会报下面的错
internal/logic/loginlogic.go:6:3: package mall/common/cryptx imports golang.org/x/crypto/scrypt from implicitly required module; to add missing requirements, run: go get golang.org/x/crypto@v0.0.0-20210920023735-84f357641f63

学习笔记(带你十天轻松搞定|学习笔记:带你十天轻松搞定 Go 微服务系列(三)- 用户服务)
文章图片

这是包没导全,进入 mall 目录 go mod tidy 一下,再重新
$ go run user.go -f etc/user.yaml

看到 Starting rpc server at 127.0.0.1:9000... 就是启动成功了
学习笔记(带你十天轻松搞定|学习笔记:带你十天轻松搞定 Go 微服务系列(三)- 用户服务)
文章图片

8、 启动 user api 服务 提示:启动服务需要在 golang 容器中启动,且前面的 rpc 服务不能关闭,不然会报错,如下所示:
2022/03/07 12:57:18 {"@timestamp":"2022-03-07T12:57:18.642+08:00","level":"error","content":"discovbuilder.go:33 bad resolver state"} 2022/03/07 12:57:21 rpc dial: discov://etcd:2379/user.rpc, error: context deadline exceeded, make sure rpc service "user.rpc" is already started exit status 1

学习笔记(带你十天轻松搞定|学习笔记:带你十天轻松搞定 Go 微服务系列(三)- 用户服务)
文章图片

陆续输入如下命令:
$ cd mall/service/user/api $ go run user.go -f etc/user.yaml

看到 Starting server at 0.0.0.0:8000... 就是启动成功了
【学习笔记(带你十天轻松搞定|学习笔记:带你十天轻松搞定 Go 微服务系列(三)- 用户服务)】学习笔记(带你十天轻松搞定|学习笔记:带你十天轻松搞定 Go 微服务系列(三)- 用户服务)
文章图片

    推荐阅读