go-zero 注册登录功能实践

1,075 阅读2分钟

准备项目配置和生成文件

创建目录

mkdir user-login
cd user-login
go mod init user-login

创建userlogin.api

  • get:使用form
  • post: 使用json
type (
   RegisterRequest {
      Name     string `json:"name"`
      Email    string `json:"email"`
      Password string `json:"password"`
   }

   RegisterResponse {
      ID    int    `json:"id"`
      Name  string `json:"name"`
      Email string `json:"email"`
   }

   LoginRequest {
      Email    string `json:"email"`
      Password string `json:"password"`
   }

   LoginResponse {
      Token  string `json:"token"`
      Expire int64  `json:"expire"`
   }
)

service userlogin-api {
   @handler RegisterHandler
   post /api/register(RegisterRequest) returns (RegisterResponse);
   
   @handler LoginHandler
   post /api/login(LoginRequest) returns (LoginResponse);
}

type UserInfoResponse {
   ID    int64  `form:"id"`
   Name  string `form:"name"`
   Email string `form:"email"`
}

@server(
   jwt: Auth // Auth 与 userlogin/internal/config/config.go 中配置的 jwt 参数对应
)
service userlogin-api {
   @handler UserInfo
   get /api/userinfo returns (UserInfoResponse)
}

生成

goctl api go -api userlogin.api -dir . go mod tidy

如果你使用的是golang或idea,可以安装goctl插件,然后使用一键生成

image.png

修改配置

  • 文件路径:user-login/etc/userlogin-api.yaml
  • DataSource是你自己数据库的配置,记得修改
  • Salt是盐值,用于加密
  • Auth用于jwt鉴权
Name: userlogin-api
Host: 0.0.0.0
Port: 8888
Mysql:
  DataSource: root:root@tcp(127.0.0.1:3306)/gozerouserlogin?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai

CacheRedis:
  - Host: redis:6379
    Type: node

Salt: DWe7OZf6KPlnv7yy
Auth:
  AccessSecret: uOvKLmVfztaXGpNYd4Z0I1SiT7MweJhl
  AccessExpire: 86400
  • 文件路径:user-login/internal/config/config.go
type Config struct {
   rest.RestConf
   Mysql struct {
      DataSource string
   }

   CacheRedis cache.CacheConf
   Salt       string
   Auth       struct {
      AccessSecret string
      AccessExpire int64
   }
}

添加用户表

  • 路径:user-login/model/user.sql
CREATE TABLE `user` ( 
    `id` bigint unsigned NOT NULL AUTO_INCREMENT, 
    `name` varchar(255) NOT NULL DEFAULT '' COMMENT '用户姓名', 
    `email` 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_email_unique` (`email`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

生成model文件

goctl model mysql ddl -src="user-login/model/user.sql" -dir="user-login/model/" -c

修改user-login/internal/svc/servicecontext.go

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),
   }
}

编写业务逻辑代码

编写工具类

  • 路径: user-login/common/cryptx/cryptx.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))
}
  • 路径:go-zero/user-login/common/jwtx/jwt.go
package jwtx

import (
   "github.com/golang-jwt/jwt/v4"
)

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)//注意是 HS 不是 ES
   token.Claims = claims
   return token.SignedString([]byte(secretKey))//注意是Signed
}

注册

  • 路径:user-login/internal/logic/registerlogic.go
func (l *RegisterLogic) Register(req *types.RegisterRequest) (resp *types.RegisterResponse, err error) {
   // todo: add your logic here and delete this line

   _, err = l.svcCtx.UserModel.FindOneByEmail(l.ctx, req.Email)
   if err == nil {
      return nil, status.Error(100, "用户已存在")
   }

   if err != model.ErrNotFound {
      return nil, status.Error(100, err.Error())
   }
   newUser := model.User{
      Name:     req.Name,
      Email:    req.Email,
      Password: cryptx.PasswordEncrypt(l.svcCtx.Config.Salt, req.Password),
   }
   res, err := l.svcCtx.UserModel.Insert(l.ctx, &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 &types.RegisterResponse{
      ID:    int(newUser.Id),
      Name:  newUser.Name,
      Email: newUser.Email,
   }, nil
}

登录

  • 路径:user-login/internal/logic/loginlogic.go
func (l *LoginLogic) Login(req *types.LoginRequest) (resp *types.LoginResponse, err error) {
   res, err := l.svcCtx.UserModel.FindOneByEmail(l.ctx, req.Email)
   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, req.Password)
   if password != res.Password {
      return nil, status.Error(100, "密码错误")
   }
   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{
      Token:  accessToken,
      Expire: now + accessExpire,
   }, nil
}

使用Token查询用户信息

  • 路径:go-zero/user-login/internal/logic/userinfologic.go
func (l *UserInfoLogic) UserInfo() (resp *types.UserInfoResponse, err error) {
   // todo: add your logic here and delete this line
   uid, _ := l.ctx.Value("uid").(json.Number).Int64()
   one, err := l.svcCtx.UserModel.FindOne(l.ctx, uid)
   if err != nil {
      return nil, err
   }
   return &types.UserInfoResponse{
      ID:    one.Id,
      Name:  one.Name,
      Email: one.Email,
   }, nil
}

启动项目

  • 先启动redis
go run userlogin.go -f etc/userlogin-api.yaml

# Starting server at 0.0.0.0:8888...

注册功能

image.png

登录功能

image.png

鉴权 - 使用上面的token查询用户信息

image.png