框架 pkg 包 utils 工具

Qi Framework

基于 Gin 的轻量级 Go Web 框架
统一响应 · 自动绑定 · 泛型路由 · 优雅关机

$ go get github.com/tokmz/qi@latest

特性概览

基于 Gin

继承 Gin 的高性能和稳定性,无缝兼容 Gin 生态

统一响应

标准化 JSON 响应格式,自动注入 TraceID

自动绑定

根据 Content-Type 和 HTTP 方法自动绑定请求参数

泛型路由

Go 泛型简化路由处理,减少 70% 样板代码

错误处理

统一错误码和 HTTP 状态码映射

链路追踪

内置 TraceID 支持,OpenTelemetry 集成

Options 模式

灵活的函数选项配置方式

优雅关机

内置信号监听和生命周期回调

封装设计

Context 包装器提供清晰的 API 边界

内置 Recovery

默认启用 panic 恢复,防止服务崩溃

国际化

内置 i18n 支持,JSON 翻译、变量替换、复数形式

丰富中间件

CORS、限流、Gzip、超时控制、链路追踪

快速开始

基础用法

package main

import "github.com/tokmz/qi"

func main() {
    // 创建 Engine(New() 默认包含 Recovery,Default() 额外添加 Logger)
    engine := qi.Default()
    r := engine.Router()

    // 基础路由
    r.GET("/ping", func(c *qi.Context) {
        c.Success("pong")
    })

    // 手动绑定参数(绑定失败时自动响应错误)
    r.POST("/user", func(c *qi.Context) {
        var req CreateUserReq
        if err := c.BindJSON(&req); err != nil {
            return  // 绑定失败已自动响应错误,直接 return 即可
        }
        c.Success(&UserResp{ID: 1, Name: req.Name})
    })

    // 启动服务器(支持优雅关机)
    engine.Run(":8080")
}

使用 Options 配置

import (
    "time"
    "github.com/tokmz/qi"
    "github.com/gin-gonic/gin"
)

func main() {
    engine := qi.New(
        qi.WithMode(gin.ReleaseMode),
        qi.WithAddr(":8080"),
        qi.WithReadTimeout(15 * time.Second),
        qi.WithWriteTimeout(15 * time.Second),
        qi.WithShutdownTimeout(30 * time.Second),
        qi.WithBeforeShutdown(func() {
            log.Println("清理资源...")
        }),
        qi.WithAfterShutdown(func() {
            log.Println("关机完成")
        }),
        qi.WithTrustedProxies("127.0.0.1"),
    )

    r := engine.Router()
    r.GET("/ping", func(c *qi.Context) {
        c.Success("pong")
    })

    if err := engine.Run(); err != nil {
        log.Fatal(err)
    }
}

泛型路由

Qi 利用 Go 泛型提供三种路由函数,自动处理参数绑定和响应,减少约 70% 的样板代码。

Handle[Req, Resp] — 有请求有响应

qi.Handle[CreateUserReq, UserResp](r.POST, "/user",
    func(c *qi.Context, req *CreateUserReq) (*UserResp, error) {
        // req 已自动绑定,响应自动处理
        return &UserResp{ID: 1, Name: req.Name}, nil
    })

Handle0[Req] — 有请求无响应

qi.Handle0[DeleteUserReq](r.DELETE, "/user/:id",
    func(c *qi.Context, req *DeleteUserReq) error {
        // 自动绑定 URI 参数
        return deleteUser(req.ID)
    })

HandleOnly[Resp] — 无请求有响应

qi.HandleOnly[InfoResp](r.GET, "/info",
    func(c *qi.Context) (*InfoResp, error) {
        return &InfoResp{Version: "1.0.0"}, nil
    })

泛型路由 + 中间件

qi.Handle[CreateUserReq, UserResp](r.POST, "/admin/user",
    createUserHandler,
    authMiddleware,      // 第一个中间件
    adminMiddleware,     // 第二个中间件
)

路由与中间件

路由组

engine := qi.Default()
r := engine.Router()

// 路由组中间件
v1 := r.Group("/api/v1")
v1.Use(authMiddleware)

qi.Handle[LoginReq, TokenResp](v1.POST, "/login", loginHandler)

中间件注册方式

// 1. 全局中间件
engine.Use(traceMiddleware)

// 2. 路由组中间件
v1 := r.Group("/api/v1")
v1.Use(authMiddleware)

// 3. 单个路由中间件(不需要路由组)
r.GET("/admin/dashboard", dashboardHandler, authMiddleware, adminMiddleware)

// 4. 泛型路由中间件
qi.Handle[Req, Resp](r.POST, "/user", handler, middleware1, middleware2)

执行顺序

全局中间件 (engine.Use)
路由组中间件 (group.Use)
路由中间件 (variadic args)
Handler 函数

自定义中间件示例

func traceMiddleware(c *qi.Context) {
    traceID := c.GetHeader("X-Trace-ID")
    if traceID == "" {
        traceID = generateTraceID()
    }
    qi.SetContextTraceID(c, traceID)
    c.Header("X-Trace-ID", traceID)
    c.Next()
}

自动绑定

Qi 根据 HTTP 方法和 Content-Type 自动选择绑定策略:

HTTP 方法绑定策略
GET / DELETEShouldBindQuery + ShouldBindUri
POST / PUT / PATCHShouldBind(根据 Content-Type 自动选择)+ ShouldBindUri
其他方法ShouldBind(自动检测)

Content-Type 映射:

Content-Type绑定方式
application/jsonJSON
application/xmlXML
application/x-www-form-urlencodedForm
multipart/form-dataMultipart Form

绑定方法(自动响应错误)

所有 Bind* 方法在失败时自动响应 400 错误,只需判断 err != nilreturn

// BindJSON - 绑定 JSON 请求体
if err := c.BindJSON(&req); err != nil {
    return  // 已自动响应 400 错误
}

// BindQuery - 绑定 URL 查询参数
if err := c.BindQuery(&req); err != nil {
    return
}

// BindURI - 绑定路径参数
if err := c.BindURI(&req); err != nil {
    return
}

// BindHeader - 绑定请求头
if err := c.BindHeader(&req); err != nil {
    return
}

// Bind - 根据 Content-Type 自动选择
if err := c.Bind(&req); err != nil {
    return
}

请求结构体示例

// JSON 请求
type CreateUserReq struct {
    Name  string `json:"name" binding:"required"`
    Email string `json:"email" binding:"required,email"`
}

// Form 请求
type LoginReq struct {
    Username string `form:"username" binding:"required"`
    Password string `form:"password" binding:"required"`
}

// 文件上传
type UploadReq struct {
    File *multipart.FileHeader `form:"file" binding:"required"`
}

// URI 参数
type GetUserReq struct {
    ID int64 `uri:"id" binding:"required,min=1"`
}

响应格式

标准响应

{
  "code": 200,
  "data": {...},
  "message": "success",
  "trace_id": "xxx"
}

分页响应

{
  "code": 200,
  "data": {
    "list": [...],
    "total": 100
  },
  "message": "success"
}

响应方法

方法说明
c.Success(data)成功响应
c.SuccessWithMessage(data, msg)成功响应(自定义消息)
c.Nil()成功响应(无数据)
c.Fail(code, message)失败响应
c.RespondError(err)错误响应(自动映射 HTTP 状态码)
c.Page(list, total)分页响应

分页用法

// 方式 1:使用 Context.Page(推荐)
r.GET("/users", func(c *qi.Context) {
    users := []User{...}
    c.Page(users, 100)
})

// 方式 2:使用 NewPageResp
resp := qi.NewPageResp(users, 100)
c.Success(resp)

// 方式 3:使用 PageData
resp := qi.PageData(users, 100)
c.JSON(200, resp)

错误处理

错误类型

type Error struct {
    Code     int    // 业务错误码 (1000-9999)
    HttpCode int    // HTTP 状态码
    Message  string // 错误消息
    Err      error  // 原始错误
}

预定义错误

错误业务码HTTP 状态码说明
ErrServer1000500服务器错误
ErrBadRequest1001400请求参数错误
ErrUnauthorized1002401未授权
ErrForbidden1003403禁止访问
ErrNotFound1004404资源不存在

使用方式

import "github.com/tokmz/qi/pkg/errors"

// 使用预定义错误
return nil, errors.ErrBadRequest.WithMessage("用户名不能为空")

// 自定义错误
return nil, errors.New(2001, 403, "禁止访问", nil)

// 包装原始错误
return nil, errors.ErrServer.WithError(err)
提示:Handler 返回 error 后,RespondError() 会自动检查是否为 *errors.Error 类型,并映射到对应的 HTTP 状态码。

国际化 (i18n)

Qi 内置国际化支持,通过 WithI18n 配置即可启用。

创建翻译文件

// locales/zh-CN.json
{
    "hello": "你好 {{.Name}}",
    "user": {
        "login": "登录",
        "logout": "退出登录"
    }
}

// locales/en-US.json
{
    "hello": "Hello {{.Name}}",
    "user": {
        "login": "Login",
        "logout": "Logout"
    }
}

启用 i18n

import "github.com/tokmz/qi/pkg/i18n"

engine := qi.New(
    qi.WithI18n(&i18n.Config{
        Dir:             "./locales",
        DefaultLanguage: "zh-CN",
        Languages:       []string{"zh-CN", "en-US"},
    }),
)

框架自动初始化翻译器并注册语言检测中间件。

语言检测优先级:Query(lang) > X-Language Header > Accept-Language Header > 默认语言

在路由中使用

r.GET("/hello", func(c *qi.Context) {
    msg := c.T("hello", "Name", "Alice")
    c.Success(msg)
})

// 泛型路由
qi.Handle[HelloReq, HelloResp](r.POST, "/hello",
    func(c *qi.Context, req *HelloReq) (*HelloResp, error) {
        msg := c.T("hello", "Name", req.Name)
        return &HelloResp{Message: msg}, nil
    })

复数形式

// 翻译文件: {"item_one": "{{.Count}} item", "item_other": "{{.Count}} items"}
c.Tn("item_one", "item_other", 1)  // "1 item"
c.Tn("item_one", "item_other", 5)  // "5 items"

获取翻译器实例

t := engine.Translator()
t.Preload("ja-JP")
t.HasKey("hello")
语言回退:当请求的语言中找不到翻译键时,自动回退到默认语言。如果默认语言也找不到,返回 key 本身。

优雅关机

Qi 内置优雅关机支持,自动监听 SIGINTSIGTERM 信号。

engine := qi.New(
    qi.WithShutdownTimeout(30 * time.Second),
    qi.WithBeforeShutdown(func() {
        log.Println("关闭数据库连接...")
        db.Close()
    }),
    qi.WithAfterShutdown(func() {
        log.Println("清理完成")
    }),
)

// Run 会阻塞直到收到关机信号
if err := engine.Run(":8080"); err != nil {
    log.Fatal(err)
}

手动关机

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

if err := engine.Shutdown(ctx); err != nil {
    log.Printf("关机失败: %v", err)
}

中间件

核心中间件(qi 包内置)

中间件说明启用方式
Recoverypanic 恢复qi.New() 默认启用
Logger请求日志qi.Default() 默认启用

扩展中间件(middleware 包)

中间件说明
middleware.Tracing()OpenTelemetry 链路追踪
middleware.CORS()跨域资源共享
middleware.RateLimiter()令牌桶限流
middleware.Timeout()请求超时控制
middleware.Gzip()响应压缩
注意:i18n 中间件已内置到框架中,通过 qi.WithI18n() 配置即可自动注册,无需手动添加。

推荐注册顺序

e := qi.Default() // 内置 Recovery + Logger

// 1. 链路追踪(最先,创建根 Span + 生成 TraceID)
e.Use(middleware.Tracing())
// 2. CORS(在业务逻辑之前处理跨域预检)
e.Use(middleware.CORS())
// 3. 限流(在业务处理之前拦截超限请求)
e.Use(middleware.RateLimiter())
// 4. 超时控制
e.Use(middleware.Timeout())
// 5. Gzip 压缩
e.Use(middleware.Gzip())
// i18n 中间件通过 WithI18n 配置自动注册

配置选项

完整 Options 列表

Option说明默认值
WithMode(mode)运行模式gin.DebugMode
WithAddr(addr)监听地址:8080
WithReadTimeout(d)读取超时10s
WithWriteTimeout(d)写入超时10s
WithIdleTimeout(d)空闲超时60s
WithMaxHeaderBytes(n)最大请求头1MB
WithShutdownTimeout(d)关机超时10s
WithBeforeShutdown(fn)关机前回调nil
WithAfterShutdown(fn)关机后回调nil
WithTrustedProxies(p...)信任的代理nil
WithMaxMultipartMemory(n)Multipart 内存32MB
WithI18n(cfg)国际化配置nil(不启用)

完整配置示例

engine := qi.New(
    qi.WithMode(gin.ReleaseMode),
    qi.WithAddr(":8080"),
    qi.WithReadTimeout(10 * time.Second),
    qi.WithWriteTimeout(10 * time.Second),
    qi.WithIdleTimeout(60 * time.Second),
    qi.WithMaxHeaderBytes(1 << 20),
    qi.WithShutdownTimeout(10 * time.Second),
    qi.WithBeforeShutdown(func() { /* cleanup */ }),
    qi.WithAfterShutdown(func() { /* finalize */ }),
    qi.WithTrustedProxies("127.0.0.1"),
    qi.WithMaxMultipartMemory(32 << 20),
    qi.WithI18n(&i18n.Config{
        Dir:             "./locales",
        DefaultLanguage: "zh-CN",
        Languages:       []string{"zh-CN", "en-US"},
    }),
)

API 参考

Engine API

创建 Engine

// New 创建一个新的 Engine 实例(包含 Recovery 中间件)
func New(opts ...Option) *Engine

// Default 创建带有 Logger + Recovery 中间件的 Engine
func Default(opts ...Option) *Engine

Engine 方法

方法说明
Use(middlewares ...HandlerFunc)注册全局中间件
Group(path, middlewares...) *RouterGroup创建路由组
Router() *RouterGroup返回根路由组
Translator() i18n.Translator返回 i18n 翻译器(未启用返回 nil)
Run(addr ...string) error启动 HTTP 服务器(支持优雅关机)
RunTLS(addr, cert, key) error启动 HTTPS 服务器
Shutdown(ctx) error手动关闭服务器

RouterGroup API

路由方法

方法说明
GET(path, handler, mw...)注册 GET 路由
POST(path, handler, mw...)注册 POST 路由
PUT(path, handler, mw...)注册 PUT 路由
DELETE(path, handler, mw...)注册 DELETE 路由
PATCH(path, handler, mw...)注册 PATCH 路由
HEAD(path, handler, mw...)注册 HEAD 路由
OPTIONS(path, handler, mw...)注册 OPTIONS 路由
Any(path, handler, mw...)注册所有 HTTP 方法
Group(path, mw...) *RouterGroup创建子路由组
Use(middlewares...)注册中间件到路由组

静态文件

方法说明
Static(path, root)静态文件目录服务
StaticFile(path, filepath)单个静态文件服务
StaticFS(path, fs)静态文件系统服务

泛型路由函数

// Handle 有请求参数,有响应数据
func Handle[Req, Resp any](register RouteRegister, path string,
    handler func(*Context, *Req) (*Resp, error), middlewares ...HandlerFunc)

// Handle0 有请求参数,无响应数据
func Handle0[Req any](register RouteRegister, path string,
    handler func(*Context, *Req) error, middlewares ...HandlerFunc)

// HandleOnly 无请求参数,有响应数据
func HandleOnly[Resp any](register RouteRegister, path string,
    handler func(*Context) (*Resp, error), middlewares ...HandlerFunc)

Context API

请求信息

方法说明
Request() *http.Request底层 Request
Writer() gin.ResponseWriter底层 ResponseWriter
Param(key) string路径参数
FullPath() string路由模板路径
Query(key) stringURL 查询参数
DefaultQuery(key, def) string查询参数(带默认值)
GetQuery(key) (string, bool)查询参数(返回是否存在)
PostForm(key) stringPOST 表单参数
DefaultPostForm(key, def) string表单参数(带默认值)
GetPostForm(key) (string, bool)表单参数(返回是否存在)
ClientIP() string客户端 IP
ContentType() stringContent-Type
GetHeader(key) string请求头

参数绑定(自动响应错误)

方法说明
Bind(obj) error自动选择绑定方式
BindJSON(obj) error绑定 JSON
BindQuery(obj) error绑定查询参数
BindURI(obj) error绑定路径参数
BindHeader(obj) error绑定请求头

参数绑定(不自动响应)

方法说明
ShouldBind(obj) error自动选择绑定方式
ShouldBindJSON(obj) error绑定 JSON
ShouldBindQuery(obj) error绑定查询参数
ShouldBindUri(obj) error绑定路径参数
ShouldBindHeader(obj) error绑定请求头

响应方法

方法说明
Success(data)成功响应
SuccessWithMessage(data, msg)成功响应(自定义消息)
Nil()成功响应(无数据)
Fail(code, message)失败响应
RespondError(err)错误响应
Page(list, total)分页响应
JSON(code, obj)发送 JSON
Header(key, value)设置响应头

国际化

方法说明
T(key, args...) string获取翻译(支持变量替换)
Tn(key, plural, n, args...) string获取翻译(支持复数形式)

上下文操作

方法说明
Set(key, value)设置键值对
Get(key) (any, bool)获取键值对
GetString(key) string获取字符串值
GetInt(key) int获取整数值
GetInt64(key) int64获取 int64 值
GetBool(key) bool获取布尔值
GetFloat64(key) float64获取 float64 值

中间件控制

方法说明
Next()执行下一个中间件
Abort()中止请求处理
AbortWithStatus(code)中止并设置状态码
AbortWithStatusJSON(code, obj)中止并返回 JSON
IsAborted() bool检查是否已中止

Context 传递

方法说明
RequestContext() context.Context返回标准库 Context(自动注入 TraceID/UID/Language)
SetRequestContext(ctx)更新 Request 的 Context

上下文辅助函数

函数说明
GetContextTraceID(ctx) string获取 TraceID
SetContextTraceID(ctx, id)设置 TraceID
GetContextUid(ctx) int64获取用户 UID
SetContextUid(ctx, uid)设置用户 UID
GetContextLanguage(ctx) string获取语言
SetContextLanguage(ctx, lang)设置语言
GetTraceIDFromContext(ctx) string从 context.Context 获取 TraceID
GetUidFromContext(ctx) int64从 context.Context 获取 UID
GetLanguageFromContext(ctx) string从 context.Context 获取 Language