go-zero學習之註冊登入

charliecen發表於2021-10-13

最近公司可能用到微服務,同事們推薦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 協議》,轉載必須註明作者和本文連結

相關文章