go-zero学习之注册登录

最近公司可能用到微服务,同事们推荐go-zero,然后自己实践操作了下。记录下来学习过程。

关于go-zero介绍,请看这里,不多介绍了。

微服务是将一个大系统拆分成多个子系统,每个子系统拥有独立的存储,如用户系统,订单系统,商品管理系统等等。

这里我们只测试下用户系统和订单系统

service # 服务目录
    └───user    # 子系统
        ├───api # http服务访问,业务实现
        └───rpc # rpc服务,为其它子系统提供数据访问
        └───model # model访问持久化数据层,生成CURD

model生成代码

创建sql
CREATE TABLE `user` (
    `id` BIGINT NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(255)  NOT NULL DEFAULT '' COMMENT '用户名称',
    `password` VARCHAR(255)  NOT NULL DEFAULT '' COMMENT '用户密码',
    `age` TINYINT(3) NOT NULL DEFAULT 0 COMMENT '年龄',
    `gender` CHAR(5)  NOT NULL COMMENT '男|女|未公开',
    `create_time` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
    `update_time` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    UNIQUE KEY `name_unique` (`name`)
) ENGINE=INNODB  DEFAULT CHARSET=utf8mb4 ;
生成代码

官网上有三种生成方式,这里只用到第一种。

# 进入目录
> cd .\service\user\model
# 执行代码
> goctl.exe model mysql ddl -src .\user.sql -dir .
Done.
命令详解
    goctl.exe   命令名称
    model       生成model代码
    mysql       数据库类型
    ddl         指定数据源为ddl文件生成model代码
    src         指定源
    user.sql    sql文件
    dir         指定生成目录
    .           当前目录

执行后,生成两个文件usermodel.go, vars.go

注意:如果代码中存在包无法加载的情况,请到项目根目录执行以下命令

# 初始化
❯ go mod init
go: creating new go.mod: module go-zero-demo1
go: to add module requirements and sums:
        go mod tidy

# 加载依赖
❯ go mod tidy
go: finding module for package github.com/tal-tech/go-zero/tools/goctl/model/sql/builderx
go: finding module for package github.com/tal-tech/go-zero/core/stores/sqlc
go: finding module for package github.com/tal-tech/go-zero/core/stores/sqlx
go: finding module for package github.com/tal-tech/go-zero/core/stringx
go: found github.com/tal-tech/go-zero/core/stores/sqlc in github.com/tal-tech/go-zero v1.2.1
go: found github.com/tal-tech/go-zero/core/stores/sqlx in github.com/tal-tech/go-zero v1.2.1
go: found github.com/tal-tech/go-zero/core/stringx in github.com/tal-tech/go-zero v1.2.1
go: found github.com/tal-tech/go-zero/tools/goctl/model/sql/builderx in github.com/tal-tech/go-zero v1.2.1

接口编写

编写api
> vim .\service\user\api\user.api
syntax = "v1"

info(
    title: "用户管理"
    desc: "用户管理"
    author: "charlie"
    email: "cenhuqing@163.com"
    version: "1.0"
)

type (
    RegisterReq {
        Username string `json:"username"`
        Password string `json:"password"`
        Age     int     `json:"age"`
        Gender    string    `json:"gender"`
    }

    RegisterResp {
        Msg string `json:"msg"`
    }

    LoginReq {
        Username string `json:"username"`
        Password string `json:"password"`
    }

    LoginResp {
        Username string `json:"Username"`
        Age      int    `json:"age"`
        Gender   string `json:"gender"`
    }
)

// 用户接口
service user-api {
    // 注册
    @handler signIn
    // 请求方式, 路由地址, (请求数据), (响应数据)
    post /user/register (RegisterReq) returns (RegisterResp)
    // 登录
    @handler getUser
    post /user/login (LoginReq) returns(LoginResp)
}
生成接口代码
❯ goctl.exe api go -api user.api -dir .
Done.
命令详解
    goctl.exe   命令名称
    api         生成api代码
    go          文件类型
    api         指定源
    user.api    api文件
    dir         指定生成目录
    .           当前目录
生成的目录结构
├───etc         # 配置文件目录
├───user.api    # api描述文件
├───user.go     # main函数入口文件
└───internal    
    ├───config  # 声明配置类型
    ├───handler # 路由和handle转发
    ├───logic   # 业务逻辑
    ├───svc     # 依赖资源池
    └───types   # 请求和响应的结构体,自动生成。不建议编辑

编写逻辑

添加yaml配置
> vim .\user\api\etc\user-api.yaml
Name: user-api
Host: 0.0.0.0
Port: 8888
Mysql:
    # 用户名:密码@协议(IP:PORT)/DBNAME?...
  DataSource: root:root@tcp(172.15.0.11:3306)/go_zero?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
CacheRedis:
  - Host: 172.15.0.11:6379
    Pass: root
    Type: node
添加配置声明
> vim .\user\api\internal\config\config.go
package config

import (
    "github.com/tal-tech/go-zero/core/stores/cache"
    "github.com/tal-tech/go-zero/rest"
)

type Config struct {
    rest.RestConf
    Mysql struct{
        DataSource string
    }
    CacheRedis cache.CacheConf
}
完善服务依赖
> vim .\user\api\internal\svc\servicecontext.go

package svc

import (
    "github.com/tal-tech/go-zero/core/stores/sqlx"
    "go-zero-demo1/service/user/api/internal/config"
    "go-zero-demo1/service/user/model"
)

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),
    }
}
填充注册业务逻辑
> vim .\user\api\internal\logic\signinlogic.go

package logic

import (
    "context"
    "errors"
    "go-zero-demo1/service/user/model"
    "strings"
    "time"

    "go-zero-demo1/service/user/api/internal/svc"
    "go-zero-demo1/service/user/api/internal/types"

    "github.com/tal-tech/go-zero/core/logx"
)

type SignInLogic struct {
    logx.Logger
    ctx    context.Context
    svcCtx *svc.ServiceContext
}

func NewSignInLogic(ctx context.Context, svcCtx *svc.ServiceContext) SignInLogic {
    return SignInLogic{
        Logger: logx.WithContext(ctx),
        ctx:    ctx,
        svcCtx: svcCtx,
    }
}

func (l *SignInLogic) SignIn(req types.RegisterReq) (*types.RegisterResp, error) {
    // todo: add your logic here and delete this line
    if len(strings.TrimSpace(req.Username)) == 0 || len(strings.TrimSpace(req.Password)) == 0 {
        return nil, errors.New("用户名和密码不为空")
    }

    // 插入数据
    _, err = l.svcCtx.UserModel.Insert(model.User{
        Name: req.Username,
        Password: req.Password,
        Age: int64(req.Age),
        Gender: req.Gender,
        CreateTime: time.Now(),
        UpdateTime: time.Now(),
    })
    if err != nil {
        return nil, err
    }

    return &types.RegisterResp{
        Msg: "用户注册成功",
    }, nil
}

启动服务
❯ go run user.go -f .\etc\user-api.yaml
Starting server at 0.0.0.0:8888...
注册登录
❯ curl -i -X POST http://localhost:8888/user/register -H 'content-type: application/json' -d '{"username":"charlie", "password":"123456","age":30,"gender":"男"}'
HTTP/1.1 200 OK
Content-Type: application/json
X-Trace-Id: d054b56b5e3b06b5
Date: Tue, 12 Oct 2021 06:06:34 GMT
Content-Length: 28

{"msg":"用户注册成功"}

下面就可以写登录业务了,这里登录用到jwt,所以先需要生成token

jwt认证

介绍

JSON Web令牌(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑而独立的方法,用于在各方之间安全地将信息作为JSON对象传输。由于此信息是经过数字签名的,因此可以被验证和信任。可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对对JWT进行签名。
注意:关于详细的介绍,可以看官网

配置参数
vim .\user\api\etc\user-api.yaml

Jwt:
  AccessExpire: 3600    # 生成token的有效期,时间单位为秒
  AccessSecret: charlie # 生成token的密钥
声明参数类型
vim .\user\api\internal\config\config.go

package config

import (
    "github.com/tal-tech/go-zero/core/stores/cache"
    "github.com/tal-tech/go-zero/rest"
)

type Config struct {
    rest.RestConf
    Mysql struct{
        DataSource string
    }
    CacheRedis cache.CacheConf
    Jwt struct{
        AccessExpire string
        AccessSecret string
    }
}
修改api接口

登录需要获取token,所以返回需要添加token信息

> .\user\api\user.api

LoginResp {
        Username string `json:"Username"`
        Age      int    `json:"age"`
        Gender   string `json:"gender"`
        Token    string `json:"token"`
        ExpireTime   int64 `json:"expire_time"`
        RefreshAfter  int64 `json:"refreshAfter"`
    }

重新生成代码

❯ goctl.exe api go -api user.api -dir .
etc/user-api.yaml exists, ignored generation
internal/config/config.go exists, ignored generation
user.go exists, ignored generation
internal/svc/servicecontext.go exists, ignored generation
internal/handler/signinhandler.go exists, ignored generation
internal/handler/getuserhandler.go exists, ignored generation
internal/logic/signinlogic.go exists, ignored generation
internal/logic/getuserlogic.go exists, ignored generation
Done.
填充登录业务逻辑
> vim .\user\api\internal\logic\getuserlogic.go

package logic

import (
    "context"
    "errors"
    "fmt"
    "github.com/dgrijalva/jwt-go"
    "github.com/tal-tech/go-zero/core/stores/sqlx"
    "strings"
    "time"

    "go-zero-demo1/service/user/api/internal/svc"
    "go-zero-demo1/service/user/api/internal/types"

    "github.com/tal-tech/go-zero/core/logx"
)

type GetUserLogic struct {
    logx.Logger
    ctx    context.Context
    svcCtx *svc.ServiceContext
}

func NewGetUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) GetUserLogic {
    return GetUserLogic{
        Logger: logx.WithContext(ctx),
        ctx:    ctx,
        svcCtx: svcCtx,
    }
}

func (l *GetUserLogic) GetUser(req types.LoginReq) (*types.LoginResp, error) {
    // todo: add your logic here and delete this line
    if len(strings.TrimSpace(req.Username)) == 0 || len(strings.TrimSpace(req.Password)) == 0 {
        return nil, errors.New("用户名和密码不为空")
    }
    userInfo, err := l.svcCtx.UserModel.FindOneByName(req.Username)
    fmt.Println(userInfo)
    switch err {
    case nil:
    case sqlx.ErrNotFound:
        return nil, errors.New("用户不存在")
    default:
        return nil, err
    }
    if req.Password != userInfo.Password {
        return nil, errors.New("密码错误")
    }

    // jwt
    now := time.Now().Unix()
    accessExpire := l.svcCtx.Config.Jwt.AccessExpire
    accessSecret := l.svcCtx.Config.Jwt.AccessSecret
    token, err := l.getJwtToken(accessSecret,
        now,
        accessExpire,
        userInfo.Id)
    if err != nil {
        return nil, err
    }

    return &types.LoginResp{
        Username: userInfo.Name,
        Age: int(userInfo.Age),
        Gender: userInfo.Gender,
        Token: token,
        ExpireTime: now + accessExpire,
        RefreshAfter: now + accessExpire / 2,
    }, nil
}

// 获取token
func (l *GetUserLogic) getJwtToken(key string, iat, seconds, userId int64) (string, error) {
    claims := make(jwt.MapClaims)
    claims["exp"] = iat + seconds
    claims["iat"] = iat
    claims["userId"] = userId
    token := jwt.New(jwt.SigningMethodHS256)
    token.Claims = claims
    return token.SignedString([]byte(key))
}
登录
❯ curl -i -X POST http://localhost:8888/user/login -H 'content-type: application/json' -d '{"username":"charlie", "password":"123456"}'
HTTP/1.1 200 OK
Content-Type: application/json
X-Trace-Id: 6379fd19805b0101
Date: Wed, 13 Oct 2021 02:07:07 GMT
Content-Length: 251

{"Username":"charlie","age":30,"gender":"男","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MzQwOTQ0MjcsImlhdCI6MTYzNDA5MDgyNywidXNlcklkIjoxfQ.kwskqpz_VLZmyU_XDJ2C68xjOI0lsGyjmUWf3YYDKuA","expire_time":1634094427,"refreshAfter":1634092627}

到此本文结束

本作品采用《CC 协议》,转载必须注明作者和本文链接
charliecen
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!