← 返回文章列表

go-zero-demo

透過一個服務示例,實踐 go-zero 的介面、資料庫與微服務開發。

daily

專案設計

說明

專案是透過 zo-zero 框架實現一個簡單的使用者訂單系統,包括兩個微服務使用者服務和訂單服務,透過 API 介面能夠分別呼叫兩個服務的介面,使用者服務暴露 RPC 服務介面供訂單服務進行呼叫。下面截圖是我的操作過程

daily

daily

API

goctl指令

goctl api go -api user.api -dir . -style goZero  
goctl api go -api order.api -dir . -style goZero

User

syntax = "v1"

// 进行项目的简单说明
info (
    title:   "实现简单的用户服务"
    author:  "aorangehc"
    date:    "2025年 11 月 11 日"
    version: "v1"
)

// 注册需要用户名、密码、手机号码,要做唯一性检测
type RegisterReq {
    UserName   string `json:"username"`
    Password   string `json:"password"`
    Repassword string `json:"re_password"`
    Mobile     string `json:"mobile"`
    Gender     int    `json:"gender,options=0|1|2,default=0"` // male|female|unknown
    NickName   string `json:"nickname"`
}

type RegisterResp {
    success bool   `json:"success"` // 注册是否成功
    Message string `json:"message"` // 注册报错信息
    Id      int64  `json:"id"` // 成功注册返回的 id
}

type LoginReq {
    UserName string `json:"username"`
    Password string `json:"password"`
}

type LoginResp {
    Message      string `json:"message"`
    AccessToken  string `json:"accessToken"`
    AccessExpire int64  `json:"accessExpire"`
    RefreshAfter int64  `json:"refreshAfter"`
}

type UserInfoReq {
    Id int64 `json:"id"`
}

type UserInfoResp {
    Id       int64  `json:"id"`
    Token    string `json:"token"`
    UserName string `json:"username"`
    Mobile   string `json:"mobile"`
    Gender   int    `json:"gender,options=0|1|2,default=0"` // male|female|unknown
    NickName string `json:"nickname"`
    CreateAt string `json:"createAt"` // 注册时间
    UpdateAt string `json:"updateAt"` // 最后更新时间
}

// 用户信息更新
type UserUpdateReq {
    Id       int64  `json:"id"`
    UserName string `json:"username"`
    Password string `json:"password"`
    Mobile   string `json:"mobile"`
    Gender   int    `json:"gender,options=0|1|2,default=0"` // male|female|unknown
    NickName string `json:"nickname"`
}

type UserUpdateResp {
    success bool   `json:"success"`
    Message string `json:"message"` // 更新报错信息
}

// 定义用户服务的 Api
@server (
    prefix: api
)
service user-api {
    @handler register
    post /user/register (RegisterReq) returns (RegisterResp)

    @handler login
    post /user/login (LoginReq) returns (LoginResp)
}

@server (
    prefix: api
    jwt:    Auth
)
service user-api {
    @handler userInfo // 获取用户信息
    get /user/:id (UserInfoReq) returns (UserInfoResp)
}

Order

syntax = "v1"

info (
    title:   "订单服务简单 demo"
    author:  "aorangehc"
    date:    "2025年 11 月 11 日"
    version: "v1"
)

// 创建订单
type CreateReq {
    UserId  int64 `json:"userId"`
    GoodsId int64 `json:"goodsId"`
    Num     int64 `json:"num"`
    Amount  int64 `json:"amount"`
}

type CreateResp {
    OrderSn string `json:"orderSn"`
}

// 查看订单详情
type DetailReq {
    OrderSn string `json:"orderSn"`
}

type DetailResp {
    OrderSn  string `json:"orderSn"`
    UserId   int64  `json:"userId"`
    GoodsId  int64  `json:"goodsid"`
    Num      int64  `json:"num"`
    Amount   int64  `json:"amount"`
    Status   int64  `json:"status"`
    CreateAt string `json:"createAt"`
}

// 删除订单
type DeleteReq {
    userId  int64  `json:"userId"`
    OrderSn string `json:"orderSn"`
}

type DeleteResp {
    success bool   `json:"success"`
    Message string `json:"message"`
}

service order-api {
    @handler create
    post /api/order/create (CreateReq) returns (CreateResp)

    @handler detail
    get /api/order/:orderSn (DetailReq) returns (DetailResp)

    @handler delete
    delete /api/order/delete (DeleteReq) returns (DeleteResp)
}

RPC

goctl指令

goctl rpc protoc user.proto --go_out=. --go-grpc_out=. --zrpc_out=.

User

// 暴露一个 user 服务对外接口,获取用户信息

syntax = "proto3";

option go_package = "./user";

// 定义请求结构体
message GetUserInfoRequest {
    int64 user_id = 1; // 用户ID
}

// 定义响应结构体
message GetUserInfoResponse {
    int64 user_id = 1; // 用户ID
    string user_name = 2; // 用户名
    int64 gender = 3; // 用户性别
}

// 定义 UserService 服务
service UserService {
    // 获取用户信息
    rpc GetUserInfo(GetUserInfoRequest) returns (GetUserInfoResponse); 
}

MySQL

goctl指令

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

User

CREATE TABLE user (
    id bigint AUTO_INCREMENT,
    name varchar(255) NULL COMMENT '用户名',
    password varchar(255) NOT NULL DEFAULT '' COMMENT '密码',
    mobile varchar(255) NOT NULL DEFAULT '' COMMENT '手机号码',
    gender char(10) NOT NULL DEFAULT 'male' COMMENT '性别,male|female|unknown',
    nickname varchar(255) NULL DEFAULT '' COMMENT '昵称',
    type tinyint(1) NULL DEFAULT 0 COMMENT '用户类型, 0:普通用户,1:VIP用户,用于测试golang关键字',
    create_at timestamp NULL,
    update_at timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE mobile_index (mobile),
    UNIQUE name_index (name),
    PRIMARY KEY (id)
) ENGINE = InnoDB COLLATE utf8mb4_general_ci COMMENT '用户表';

Order

CREATE TABLE `order` (
    `order_sn` VARCHAR(64) NOT NULL COMMENT '订单号',
    `user_id` BIGINT NOT NULL DEFAULT 0 COMMENT '用户ID',
    `goods_id` BIGINT NOT NULL DEFAULT 0 COMMENT '商品ID',
    `num` BIGINT NOT NULL DEFAULT 0 COMMENT '数量',
    `amount` BIGINT NOT NULL DEFAULT 0 COMMENT '金额',
    `status` INT NOT NULL DEFAULT 0 COMMENT '状态',
    `create_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `delete_at` DATETIME DEFAULT NULL COMMENT '删除时间',
    PRIMARY KEY (`order_sn`)
) ENGINE=InnoDB COLLATE utf8mb4_general_ci COMMENT='订单表';

MySQL配置和model操作

已經完成了專案結構的初步搭建,下一步我們進行資料庫的配置,這裡我們從頭開始,使用 docker 建立表。下面是相關的指令。

建立 MySQL 資料庫和表

# 使用 docker 拉取 docekr 的镜像
docker pull mysql:8.0 

# 使用镜像创建容器
docker run -d --name testMySQl \ # 容器名称
  -e MYSQL_ROOT_PASSWORD=123456 \ # 数据库密码
  -e MYSQL_DATABASE=goframework \ # 数据库名称
  -p 3307:3306 \ # 容器内 3306 端口映射到 3307,因为我的 3306 被占用了
  -v $PWD/mysql-data:/var/lib/mysql \ # 将存储映射到外部
  mysql:8.0 --default-authentication-plugin=mysql_native_password

# 进入容器
docker exec -it testMySQl /bin/sh

# 进入数据库
mysql -uroot -p123456

# 看一下已有的数据库
show databases;

# 创建一个数据库
create database user_order;

# 使用数据库
use user_order;;

# 看一下表
show tables;

# 创建一个用户表
CREATE TABLE `user` (
    id bigint AUTO_INCREMENT,
    name varchar(255) NULL COMMENT '用户名',
    password varchar(255) NOT NULL DEFAULT '' COMMENT '密码',
    mobile varchar(255) NOT NULL DEFAULT '' COMMENT '手机号码',
    gender char(10) NOT NULL DEFAULT 'male' COMMENT '性别,male|female|unknown',
    nickname varchar(255) NULL DEFAULT '' COMMENT '昵称',
    type tinyint(1) NULL DEFAULT 0 COMMENT '用户类型, 0:普通用户,1:VIP用户,用于测试golang关键字',
    create_at timestamp NULL,
    update_at timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE mobile_index (mobile),
    UNIQUE name_index (name),
    PRIMARY KEY (id)
) ENGINE = InnoDB COLLATE utf8mb4_general_ci COMMENT '用户表';

# 创建订单表
CREATE TABLE `order` (
    `order_sn` VARCHAR(64) NOT NULL COMMENT '订单号',
    `user_id` BIGINT NOT NULL DEFAULT 0 COMMENT '用户ID',
    `goods_id` BIGINT NOT NULL DEFAULT 0 COMMENT '商品ID',
    `num` BIGINT NOT NULL DEFAULT 0 COMMENT '数量',
    `amount` BIGINT NOT NULL DEFAULT 0 COMMENT '金额',
    `status` INT NOT NULL DEFAULT 0 COMMENT '状态',
    `create_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `delete_at` DATETIME DEFAULT NULL COMMENT '删除时间',
    PRIMARY KEY (`order_sn`)
) ENGINE=InnoDB COLLATE utf8mb4_general_ci COMMENT='订单表';

daily

專案連線資料庫

  • 先修改配置檔案,在配置中增加資料庫相關配置
root:123456@tcp(127.0.0.1:3307)/user_order?charset=utf8mb4&parseTime=True&loc=Local
├─┘└──┘└┘└────┘└──┘└─────┘└───────┘└───────┘└────┘
 用户名 密码  协议    主机地址    端口   数据库名    字符集        时间解析     时区
  • 然後修改 yaml檔案中的配置資訊,寫入具體配置

  • 在 servicecontext 中增加資料庫相關配置,並連線資料庫表

  • 在註冊邏輯中進行實現,增加資料寫入邏輯

之後進行測試,資料寫入

daily

daily

註冊

之前我們實現了簡單的資料庫操作,現在開始我們實現一個簡單的註冊功能

  • 引數校驗(校驗引數合法性,判斷一下是不是空,還有兩次密碼認證—)

  • 資料唯一性確認(檢查資料庫中是否存在已有資料)

  • 加密加鹽(使用 md5 進行加密加鹽)

// 加密加盐
    h := md5.New()
    h.Write([]byte(req.Password))
    h.Write(secret)
    passwordStr := hex.EncodeToString(h.Sum(nil))

下面是效果驗證

daily

daily

daily

快取

go-zero快取

首先講一下快取,簡單來說快取就是把下次可能用到的資料先存下來,這樣下次需要的時候就不用再慢慢去找,能有效提升載入速度和使用者體驗。

在我們日常生活中使用瀏覽器載入網頁一般都會進行快取,所以一般第一次載入都會很慢,我之前遇到過一個情況,部署在 GitHub Page 的網頁會一直快取著第一次載入的資料,很奇怪,要在瀏覽器進行單獨設定後才會解決這個問題

在電商場景或者是應用場景下,會將讀得特別多、計算成本高、不經常變化的資料或者是需要快速響應的資料進行快取,既是為了降低成本,也是為了提升效率。

快取最常用的資料庫就是 Redis,Redis 也在逐漸迭代,適配更多的快取場景。

在 go-zero中,可以透過”-c”引數設定快取的使用,我們在目前的使用者訂單系統進行簡單的快取實現,首先使用下面指令,生成快取相關程式碼

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

下面是有無快取程式碼的對比

// 有缓存
func (m *defaultUserModel) FindOneByMobile(ctx context.Context, mobile string) (*User, error) {
    userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, mobile)
    var resp User
    err := m.QueryRowIndexCtx(ctx, &resp, userMobileKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v any) (i any, e error) {
        query := fmt.Sprintf("select %s from %s where `mobile` = ? limit 1", userRows, m.table)
        if err := conn.QueryRowCtx(ctx, &resp, query, mobile); err != nil {
            return nil, err
        }
        return resp.Id, nil
    }, m.queryPrimary)
    switch err {
    case nil:
        return &resp, nil
    case sqlc.ErrNotFound:
        return nil, ErrNotFound
    default:
        return nil, err
    }
}

//无缓存
func (m *defaultUserModel) FindOneByMobile(ctx context.Context, mobile string) (*User, error) {
    var resp User
    query := fmt.Sprintf("select %s from %s where `mobile` = ? limit 1", userRows, m.table)
    err := m.conn.QueryRowCtx(ctx, &resp, query, mobile)
    switch err {
    case nil:
        return &resp, nil
    case sqlx.ErrNotFound:
        return nil, ErrNotFound
    default:
        return nil, err
    }
}

有了快取需求之後,我們就要配置快取資料庫了

  • 先看生成的帶快取的usermodel.go檔案並深入檢視資料結構的設計,我們能發現需要的新版的配置型別是cache.CacheConf,深入發現需要的引數在RedisConf中,這是我們需要在 yaml 檔案中設定的內容。
// NewUserModel returns a model for the database table.
func NewUserModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) UserModel {
    return &customUserModel{
        defaultUserModel: newUserModel(conn, c, opts...),
    }
}


type (
    // A ClusterConf is the config of a redis cluster that used as cache.
    ClusterConf []NodeConf

    // A NodeConf is the config of a redis node that used as cache.
    NodeConf struct {
        redis.RedisConf
        Weight int `json:",default=100"`
    }
)

type (
    // A RedisConf is a redis config.
    RedisConf struct {
        Host     string
        Type     string `json:",default=node,options=node|cluster"`
        User     string `json:",optional"`
        Pass     string `json:",optional"`
        Tls      bool   `json:",optional"`
        NonBlock bool   `json:",default=true"`
        // PingTimeout is the timeout for ping redis.
        PingTimeout time.Duration `json:",default=1s"`
    }

    // A RedisKeyConf is a redis config with key.
    RedisKeyConf struct {
        RedisConf
        Key string
    }
)
  • 然後修改 config.go和 yaml 檔案我們簡單配置

  • 然後就結束了,剩下的框架都給你實現了。

docker使用 redis

# 拉取镜像
docker pull redis:8
# 创建容器
docker run -d --name testRedis -p 6379:6379 redis:8
# 进入容器
docker exec -it testRedis redis-cli 
# 看一下keys
keys cache:user:name:*
# 退出
exit

測試

透過結合 postman測試發現會逐漸將搜尋加入快取

daily

這只是一個簡單的使用,實際 redis的功能和應用範圍非常廣泛

日誌

在 config預設的結構體中嵌入了 go-zero的log配置項,下面是他的完整設計,我們在 yaml檔案中建立 Log並進行下面引數的配置

# 配置
Log:
  ServiceName: user-api-srv
  Mode: file
  Encoding: json
  Path: logs
  Level: info
  Stat: true
type (
    // A LogConf is a logging config.
    LogConf struct {
        // ServiceName represents the service name.
        ServiceName string `json:",optional"`
        // Mode represents the logging mode, default is `console`.
        // console: log to console.
        // file: log to file.
        // volume: used in k8s, prepend the hostname to the log file name.
        Mode string `json:",default=console,options=[console,file,volume]"`
        // Encoding represents the encoding type, default is `json`.
        // json: json encoding.
        // plain: plain text encoding, typically used in development.
        Encoding string `json:",default=json,options=[json,plain]"`
        // TimeFormat represents the time format, default is `2006-01-02T15:04:05.000Z07:00`.
        TimeFormat string `json:",optional"`
        // Path represents the log file path, default is `logs`.
        Path string `json:",default=logs"`
        // Level represents the log level, default is `info`.
        Level string `json:",default=info,options=[debug,info,error,severe]"`
        // MaxContentLength represents the max content bytes, default is no limit.
        MaxContentLength uint32 `json:",optional"`
        // Compress represents whether to compress the log file, default is `false`.
        Compress bool `json:",optional"`
        // Stat represents whether to log statistics, default is `true`.
        Stat bool `json:",default=true"`
        // KeepDays represents how many days the log files will be kept. Default to keep all files.
        // Only take effect when Mode is `file` or `volume`, both work when Rotation is `daily` or `size`.
        KeepDays int `json:",optional"`
        // StackCooldownMillis represents the cooldown time for stack logging, default is 100ms.
        StackCooldownMillis int `json:",default=100"`
        // MaxBackups represents how many backup log files will be kept. 0 means all files will be kept forever.
        // Only take effect when RotationRuleType is `size`.
        // Even though `MaxBackups` sets 0, log files will still be removed
        // if the `KeepDays` limitation is reached.
        MaxBackups int `json:",default=0"`
        // MaxSize represents how much space the writing log file takes up. 0 means no limit. The unit is `MB`.
        // Only take effect when RotationRuleType is `size`
        MaxSize int `json:",default=0"`
        // Rotation represents the type of log rotation rule. Default is `daily`.
        // daily: daily rotation.
        // size: size limited rotation.
        Rotation string `json:",default=daily,options=[daily,size]"`
        // FileTimeFormat represents the time format for file name, default is `2006-01-02T15:04:05.000Z07:00`.
        FileTimeFormat string `json:",optional"`
        // FieldKeys represents the field keys.
        FieldKeys fieldKeyConf `json:",optional"`
    }

    fieldKeyConf struct {
        // CallerKey represents the caller key.
        CallerKey string `json:",default=caller"`
        // ContentKey represents the content key.
        ContentKey string `json:",default=content"`
        // DurationKey represents the duration key.
        DurationKey string `json:",default=duration"`
        // LevelKey represents the level key.
        LevelKey string `json:",default=level"`
        // SpanKey represents the span key.
        SpanKey string `json:",default=span"`
        // TimestampKey represents the timestamp key.
        TimestampKey string `json:",default=@timestamp"`
        // TraceKey represents the trace key.
        TraceKey string `json:",default=trace"`
        // TruncatedKey represents the truncated key.
        TruncatedKey string `json:",default=truncated"`
    }
)

可以使用下面的介面,進行日誌的記錄

type Logger interface {
    Debug(...any)
    Debugf(string, ...any)
    Debugv(any)
    Debugw(string, ...LogField)
    Error(...any)
    Errorf(string, ...any)
    Errorv(any)
    Errorw(string, ...LogField)
    Info(...any)
    Infof(string, ...any)
    Infov(any)
    Infow(string, ...LogField)
    Slow(...any)
    Slowf(string, ...any)
    Slowv(any)
    Sloww(string, ...LogField)
    WithCallerSkip(skip int) Logger
    WithContext(ctx context.Context) Logger
    WithDuration(d time.Duration) Logger
    WithFields(fields ...LogField) Logger
}

簡單測試

daily

login 和userInfo介面

主要進行這兩個檔案中邏輯的完善

daily

在專案中進行簡單實現,具體參照專案程式碼,點選直達

下面是測試:

daily

daily

daily

不過專案中還有很多錯誤,比如 id的寫入出現問題,涉及到資料庫配置的問題。

JWT 鑑權

JWT的主要流程

使用者詳情介面需要登陸之後才能進行訪問

  1. 使用者登入成功之後->JSON Web Token(JWT)->前端會把token儲存起來,每次後端呼叫的時候都會帶上,繼續驗證。

  2. 後端需要鑑權介面,通常是做成中介軟體的形式進行鑑權,對請求頭中的token進行解析

  3. 解析成功就是登入使用者

  4. 解析失敗就是未登入使用者或者是失效使用者

  5. Refresh token

簡單實現和測試

藉助github.com/golang-jwt/jwt/v4實現 jwt 認證

還有一個比較重要的問題,jwt token存到哪裡了,存到專案的 context 中了,我們從 context 中就能取到值。

token := l.ctx.Value("userId")

生成 token

daily

測試沒加 token 和加入 token 之後的人一個結果,注意會超時,因為之前設定了 60 秒。

daily

daily

中介軟體

路由中介軟體:只給部分路由新增中介軟體,比如鑑權,效能檢測等等

全域性中介軟體:全部都要使用

也能透過閉包呼叫其他服務中的中介軟體

中介軟體定義

我們可以直接在 api檔案中進行定義,透過 middleware 中介軟體

syntax = "v1"

// 进行项目的简单说明
info (
    title:   "实现简单的用户服务"
    author:  "aorangehc"
    date:    "2025年 11 月 11 日"
    version: "v1"
)

// 注册需要用户名、密码、手机号码,要做唯一性检测
type RegisterReq {
    UserName   string `json:"username"`
    Password   string `json:"password"`
    Repassword string `json:"re_password"`
    Mobile     string `json:"mobile"`
    Gender     int    `json:"gender,options=0|1|2,default=0"` // male|female|unknown
    NickName   string `json:"nickname"`
}

type RegisterResp {
    success bool   `json:"success"` // 注册是否成功
    Message string `json:"message"` // 注册报错信息
    Id      int64  `json:"id"` // 成功注册返回的 id
}

type LoginReq {
    UserName string `json:"username"`
    Password string `json:"password"`
}

type LoginResp {
    Message      string `json:"message"`
    AccessToken  string `json:"accessToken"`
    AccessExpire int64  `json:"accessExpire"`
    RefreshAfter int64  `json:"refreshAfter"`
}

type UserInfoReq {
    Id int64 `json:"id"`
}

type UserInfoResp {
    Id       int64  `json:"id"`
    Token    string `json:"token"`
    UserName string `json:"username"`
    Mobile   string `json:"mobile"`
    Gender   int    `json:"gender,options=0|1|2,default=0"` // male|female|unknown
    NickName string `json:"nickname"`
    CreateAt string `json:"createAt"` // 注册时间
    UpdateAt string `json:"updateAt"` // 最后更新时间
}

// 用户信息更新
type UserUpdateReq {
    Id       int64  `json:"id"`
    UserName string `json:"username"`
    Password string `json:"password"`
    Mobile   string `json:"mobile"`
    Gender   int    `json:"gender,options=0|1|2,default=0"` // male|female|unknown
    NickName string `json:"nickname"`
}

type UserUpdateResp {
    success bool   `json:"success"`
    Message string `json:"message"` // 更新报错信息
}

// 定义用户服务的 Api
@server (
    prefix: api
)
service user-api {
    @handler register
    post /user/register (RegisterReq) returns (RegisterResp)

    @handler login
    post /user/login (LoginReq) returns (LoginResp)
}

@server (
    prefix:     api
    jwt:        Auth
    middleware: Cost // 耗时统计的一个中间件
)
service user-api {
    @handler userInfo // 获取用户信息
    get /user/:id (UserInfoReq) returns (UserInfoResp)

    @handler userUpdate // 更新用户信息
    post /user/update (UserUpdateReq) returns (UserUpdateResp)
}

Demo 和測試

更新完成 api檔案之後,透過 goctl更新專案,然後完善

GoZeroDemo/serviceDemo/user/api/internal/middleware/costMiddleware.go中的 Handle 方法
GoZeroDemo/serviceDemo/user/api/internal/svc/serviceContext.go中增加 Cost的注册

之後我們執行專案進行測試,列印出耗時

daily

daily

實現一個簡單的全域性中介軟體

daily

RPC服務和呼叫

RPC 服務實現流程

  • 編寫 rpc檔案

  • goctl生成專案檔案

  • 修改配置結構體和相關的檔案

  • 完善專案內容

    • 修改 context 檔案

    • 完善 RPC 業務邏輯

  • 測試,使用 postman 測試功能

daily

RPC 呼叫

在 go-zero中進行rpc呼叫基本要進行以下流程

  • 首先配置 yaml 檔案和config 檔案

  • 然後在 context 檔案中進行資料庫連線和 RPC 的配置

  • 之後再 logic 檔案中進行 RPC 的呼叫

下面是測試結果

建立訂單

daily

測試訂單刪除功能

daily

daily

獲取訂單資訊

daily

註冊中心-consul

原本使用 Etcd 作為 註冊中心,要改成 consul,docker 啟動 consul

  • 修改配置檔案 yaml 和 config.go,註釋掉原來的 etcd

  • 將服務註冊到 consul,在啟動服務的時候

  • 服務發現方也需要修改配置

之後啟動服務進行測試,測試成功

daily

daily

daily

RPC 呼叫傳遞 metadata

metadata(後設資料)= 快遞單(貼在包裹上的資訊:誰寄的、寄給誰、是否易碎)。可以包含:

  • 認證令牌 (Token): JWT Token,用來證明你是誰。

  • 鏈路追蹤 ID (Trace ID): 用來追蹤這個請求經過了哪幾個微服務(例如 x-request-id)。

  • 客戶端資訊: 比如 User-Agent,或者客戶端版本號。

  • 超時控制: 告訴服務端,如果 1 秒沒處理完就別處理了。

RPC 攔截器

攔截器 = 安檢員/分揀中心(在包裹真正到達收件人手裡之前,先檢查一下快遞單,或者對包裹做個消毒)

裝“上下文”資訊

它是一種鉤子機制(Hook)。它允許你在請求傳送出去之前(客戶端)或者請求被處理之前/之後(服務端),插入一段通用的邏輯程式碼。

它的核心目的是把通用邏輯從業務程式碼中剝離出來(AOP 面向切面程式設計的思想)。可以進行:

  • 身份認證: 在進入業務邏輯前,檢查 Metadata 裡有沒有 Token,Token 對不對。如果不對應,直接攔截退回,業務邏輯根本不用寫判斷程式碼。

  • 日誌記錄: 記錄“誰在什麼時間調了哪個介面,耗時多少”。

  • 錯誤處理: 統一捕獲異常,轉換成標準錯誤碼。

  • 限流/熔斷: 檢查請求是不是太多了,如果太多直接攔截。

客戶端攔截器

在 context 檔案中編寫攔截器,之後加到 rpc發現服務中

需要注意:

  • 什麼時候儲存metadata

  • 怎麼存

  • 攔截器怎麼透過 context 傳值

服務端攔截器

在啟動 RPC 服務之前進行攔截器的註冊

攔截器的業務邏輯實現

服務端怎麼從 metadata 中取到資料

測試

經過簡單測試,服務端收到來客戶端的後設資料並進行安正

daily

錯誤處理

可以自定義錯誤型別,輸出更加全面

  • 定義自定義格式錯誤

  • 業務程式碼中按需使用定義的錯誤

  • 告訴 go-zero框架我們自定義了錯誤型別要進行處理

下面是測試結果

daily

修改模板生成自定義程式碼

參照模板定製化 goctl template

模版的作用:生成專案程式碼,goctl指令生成程式碼的時候就是根據模板來生成程式碼的

修改模版的主要指令

goctl template clean # 用于删除持久化在本地的模板文件。
goctl template init # 用于初始化模板,会将模板文件存储到本地。
goctl template revert # 用于回滚某个分类下的指定的模板文件。
goctl template update # 用于更新某个分类下的所有模板文件。

這樣在指定目錄下就能看到.goctl資料夾,裡面儲存 tpl檔案,我們可以修改檔案內容,實現生成專案檔案的自動定義

參考資料

go-zero

七米老師課程

關於這篇文章的日期

舊日期 2025-01-11 與文章發布記錄不符,現按舊站可確認的首次發布日 2026-01-25 整理。實際創作日期尚無法確認。

原始日期: 2025-01-11 09:58:39 +0800