darkit/gin 是一个基于 gin-gonic/gin 的增强版 Go Web 框架。它保留 Gin 的高性能与生态兼容性,在其上增加了增强型 Context、可组合的 Engine 配置、认证子系统、丰富中间件、Chi 风格正则路由、自动注册路由,以及一组可直接复用的基础能力包。
darkit/gin 适合希望继续使用 Gin 编程模型,但又希望获得更多"开箱即用"能力的项目:
Engine、Router、ContextContext 与 Router,减少样板代码github.com/darkit/gin ├── engine.go # Engine 聚合与运行入口 ├── context.go # 核心增强 Context ├── context_*.go # Context 的分能力扩展 ├── router.go # Router 增强 ├── regex_router.go # Chi 风格正则路由 ├── auto_register.go # 控制器自动注册 ├── options.go # OptionFunc 配置模式 ├── binding/ # 绑定类型导出 ├── auth/ # 认证子系统 ├── middleware/ # 中间件生态 (30+) ├── pkg/ # 基础能力工具包 │ ├── cache/ # 缓存抽象与实现 │ ├── circuitbreaker/ # 熔断控制 │ ├── concurrency/ # 并发工具 │ ├── diagnostic/ # 诊断辅助 │ ├── export/ # 导出能力 │ ├── image/ # 图片处理 │ ├── lifecycle/ # 生命周期管理 │ ├── logger/ # 日志抽象 │ ├── mail/ # 邮件发送 │ ├── mask/ # 数据脱敏 │ ├── retry/ # 重试工具 │ ├── routes/ # 路由辅助 │ ├── sms/ # 短信能力 │ ├── static/ # 静态资源 │ ├── swagger/ # Swagger/OpenAPI │ ├── validator/ # 校验辅助 │ └── websocket/ # WebSocket 能力 ├── docs/ # 参考文档 └── examples/ # 示例代码
engine.go 与 options.go 提供以下核心能力:
New(opts ...OptionFunc):创建增强引擎Default(opts ...OptionFunc):创建默认引擎,自动挂载 RequestID、Recovery、Logger 中间件logger.NewNoop() - 空日志器cache.NewMemoryCache() - 内存缓存lifecycle.NewManager() - 生命周期管理器middleware.NewRegistry() - 中间件注册表OnStart()、OnShutdown()、OnStopped()sync.Pool 复用增强型 Contextcontext.go 提供丰富的请求/响应处理能力:
// 与上游 gin 一致的单一来源取值
id := c.Param("id")
name := c.Query("name")
email := c.PostForm("email")
pageText := c.DefaultQuery("page", "1")
status := c.DefaultPostForm("status", "draft")
// 本项目增强的聚合取值:按 路径参数 -> query -> form 的顺序读取
keyword := c.Input("keyword", "")
page := c.ParamInt("page", 1)
debug := c.ParamBool("debug", false)
// 带错误返回的聚合解析
id, err := c.ParamIntE("id")
if err != nil {
c.BadRequest("invalid id")
return
}
说明:
c.Param("id") 仅表示路径参数,行为与上游 gin.Context.Param 一致c.Query(...)、c.PostForm(...)、c.DefaultQuery(...)、c.DefaultPostForm(...) 也保持上游用法c.Input(key, def...) 是本项目增强入口,用于统一读取路径参数、query 和 formc.ParamInt/ParamInt64/ParamFloat/ParamBool 这类增强 helper 内部同样基于 Input(...),语义是“聚合输入解析”,不是“仅路径参数解析”// 成功响应
c.Success(data) // 200 OK
c.Created(data) // 201 Created
c.Accepted(data) // 202 Accepted
c.NoContent() // 204 No Content
// 错误响应
c.BadRequest("message") // 400
c.Unauthorized("message") // 401
c.Forbidden("message") // 403
c.NotFound("message") // 404
c.Conflict("message") // 409
c.ValidationError(errors) // 422
c.InternalError("message") // 500
c.TooManyRequests() // 429
// 解析分页参数
page, perPage := c.ParsePagination()
// 分页响应
c.Paginated(data, page, perPage, total)
// 游标分页
cursor, limit := c.ParseCursorPagination()
c.CursorPaginated(data, cursor, limit, hasMore)
// Server-Sent Events
c.BeginSSE()
c.SSE(event, data)
c.SSEHeartbeat()
// NDJSON
c.BeginNDJSON()
c.StreamNDJSON(obj)
ip := c.GetIP() ua := c.GetUserAgent() requestID := c.RequestID() traceID := c.TraceID() spanID := c.SpanID() token := c.GetBearerToken()
router.go 提供增强型路由能力:
// 增强型处理器签名
type HandlerFunc func(*Context)
// REST 资源路由
r.Resource("users", userController)
r.CRUD("articles", articleController)
// 版本化 API
v1 := r.Version("1")
v1.GET("/users", listUsers)
// 健康检查
r.HealthCheck()
r.Liveness()
r.Readiness()
// 静态资源
r.Static("/static", "./public")
r.Assets("/assets", "./public")
r.Site("/app", "./dist")
e.FallbackSite("./dist")
r.StaticFile("/favicon.ico", "./public/favicon.ico")
r.EmbedFS("/static", embedFS, "dist")
说明:
Static* / Embed* 与上游 Gin 一样,直接注册普通路由Assets* 用于“静态资源目录”语义,文件未命中时不会自动回退到站点首页Site* 用于“前端站点”语义,支持 index.html、history fallback 与自定义 404.htmlFallbackSite* 通过 NoRoute 受控兜底,不会抢占普通路由;优先级为“普通路由 -> regex 路由 -> 受控静态挂载 -> 用户 NoRoute”支持 /{param} 和 /{param:regex} 模式的路由:
// 直接在路由中使用 Chi 风格模式
r.GET("/users/{id:[0-9]+}", handler)
r.GET("/posts/{slug}", handler)
r.GET("/files/*path", handler)
// 高级控制
rx := app.RegexRouter()
rx.NotFound(func(c *Context) {
c.JSON(404, gin.H{"error": "not found"})
})
根据控制器方法名自动推断 HTTP 方法与路由路径:
type UserController struct{}
func (u *UserController) GetUsers(c *gin.Context) { /* ... */ }
func (u *UserController) GetUser(c *gin.Context) { /* ... */ }
func (u *UserController) CreateUser(c *gin.Context) { /* ... */ }
func (u *UserController) UpdateUser(c *gin.Context) { /* ... */ }
func (u *UserController) DeleteUser(c *gin.Context) { /* ... */ }
// 自动注册
r.AutoRegister(&UserController{}, gin.WithPrefix("/api"))
// GET /api/users
// GET /api/users/:id
// POST /api/users
// PUT /api/users/:id
// DELETE /api/users/:id
1.25+go get github.com/darkit/gin@latest
package main
import (
"log"
gin "github.com/darkit/gin"
)
func main() {
app := gin.Default()
r := app.Router()
r.GET("/ping", func(c *gin.Context) {
c.Success(gin.H{
"message": "pong",
"request_id": c.RequestID(),
})
})
if err := app.Run(); err != nil {
log.Fatal(err)
}
}
package main
import (
"log"
"time"
gin "github.com/darkit/gin"
"github.com/darkit/gin/middleware"
)
func main() {
app := gin.New(
gin.WithAddr(":9090"),
gin.WithReadTimeout(10*time.Second),
gin.WithWriteTimeout(10*time.Second),
gin.WithGracefulShutdown(15*time.Second),
gin.Production(),
)
r := app.Router()
r.Use(
middleware.CORS(),
middleware.Secure(),
)
v1 := r.Version("1")
v1.HealthCheck()
v1.GET("/users/:id", func(c *gin.Context) {
id, err := c.ParamIntE("id")
if err != nil {
c.BadRequest("invalid id")
return
}
c.Success(gin.H{"id": id})
})
if err := app.Run(); err != nil {
log.Fatal(err)
}
}
┌──────────────────────────────────────────────┐ │ Application Handlers / Controllers │ <- 用户代码 ├──────────────────────────────────────────────┤ │ Router / RegexRouter / AutoRegister │ <- 路由层 ├──────────────────────────────────────────────┤ │ Enhanced Context │ <- Context 层 ├──────────────────────────────────────────────┤ │ Engine + OptionFunc + Middleware Registry │ <- Engine 层 ├──────────────────────────────────────────────┤ │ auth/* + middleware/* + pkg/* │ <- 能力层 └──────────────────────────────────────────────┘
| 层级 | 组件 | 职责 |
|---|---|---|
| 应用层 | Handlers | 业务逻辑处理 |
| 路由层 | Router, RegexRouter, AutoRegister | 路由注册与匹配 |
| Context 层 | Enhanced Context | 参数解析、响应封装 |
| Engine 层 | Engine, OptionFunc, MiddlewareRegistry | 组件聚合、生命周期管理 |
| 能力层 | auth, middleware, pkg | 认证、中间件、基础设施 |
app := gin.New(
gin.WithAddr(":8080"),
gin.WithReadTimeout(10*time.Second),
gin.WithWriteTimeout(10*time.Second),
gin.WithGracefulShutdown(30*time.Second),
)
// 开发环境
app := gin.New(gin.Development())
// 生产环境
app := gin.New(gin.Production())
app := gin.New(
gin.WithTrustedProxies([]string{"127.0.0.1", "10.0.0.0/8"}),
)
app := gin.New( gin.WithLogger(customLogger), gin.WithCache(customCache), )
app := gin.New(
gin.WithUploadDir("./uploads"),
gin.WithMaxFileSize(10<<20), // 10MB
gin.WithMaxMultipartMemory(32<<20), // 32MB
gin.WithAllowedExts("jpg", "png", "pdf"),
)
app := gin.New(
gin.WithAuth(auth.AuthConfig{
Secret: "your-secret-key",
Expiry: 24 * time.Hour,
TokenStyle: auth.TokenStyleJWT,
}),
)
app := gin.New(
gin.EnableSwagger(swagger.SwaggerConfig{
Title: "Example API",
Description: "API Documentation",
Version: "v1.0.0",
}),
)
r.Resource("users", userController)
自动注册以下路由:
GET /users - 列表POST /users - 创建GET /users/:id - 详情PUT /users/:id - 更新PATCH /users/:id - 部分更新DELETE /users/:id - 删除// 直接使用 Chi 风格 pattern
app.GET("/orders/{id:[0-9]+}", func(c *gin.Context) {
c.Success(gin.H{"order_id": c.Param("id")})
})
type UserController struct{}
func (u *UserController) GetProfile(c *gin.Context) {
c.Success(gin.H{"ok": true})
}
func main() {
app := gin.Default()
r := app.Router()
r.AutoRegister(&UserController{}, gin.WithPrefix("/api/users"))
}
框架提供 30+ 生产级中间件:
| 中间件 | 说明 |
|---|---|
RequestID | 请求 ID 生成与传递 |
Recovery | Panic 恢复 |
Logger | 访问日志 |
OTel | OpenTelemetry 接入 |
| 中间件 | 说明 |
|---|---|
CORS | 跨域资源共享 |
Secure | 安全响应头 |
RateLimit | 访问限流 |
SignatureVerify | 签名校验 |
Throttle | 并发节流 |
| 中间件 | 说明 |
|---|---|
Cache | 响应缓存 |
Compress | 压缩传输 |
Timeout | 请求超时控制 |
| 中间件 | 说明 |
|---|---|
Idempotent | 幂等控制 |
Interceptor | 请求/响应拦截 |
支持直接使用 Chi/标准库风格的中间件:
import chimw "github.com/go-chi/chi/v5/middleware"
r.Use(chimw.RequestID)
r.Use(chimw.Logger)
r.Use(chimw.Recoverer)
详细文档请参考 middleware/README.md。
auth 模块提供完整的认证、授权、会话管理能力:
e := ginx.New(
ginx.WithAuth(auth.AuthConfig{
Secret: "replace-me",
Expiry: 24 * time.Hour,
TokenStyle: auth.TokenStyleJWT,
}),
)
e.POST("/login", func(c *ginx.Context) {
token, err := c.Auth().Login("user-1001", "web")
if err != nil {
c.InternalError(err.Error())
return
}
c.Success(ginx.H{"token": token})
})
func handleProfile(c *gin.Context) {
if err := c.Auth().CheckLogin(); err != nil {
c.Unauthorized(err.Error())
return
}
loginID, _ := c.Auth().LoginID()
if err := c.Auth().CheckAnyPermission("user:read", "profile:read"); err != nil {
c.Forbidden(err.Error())
return
}
c.Success(gin.H{"login_id": loginID})
}
cfg := auth.DefaultAuthConfig()
mgr := auth.NewManager(auth.NewMemoryStorage(), &cfg)
auth.SetGlobalManager(mgr)
defer auth.CloseGlobalManager()
token, _ := auth.Login("user-1001", "web")
ok := auth.IsLogin(token)
auth.NewMemoryStorage() - 适用于开发/测试auth.NewRedisStorage(redisURL) - 适用于生产环境详细文档请参考 auth/README.md 和 auth/DESIGN.md。
c := cache.NewMemoryCache(
cache.WithMaxSize(1000),
cache.WithDefaultTTL(5*time.Minute),
)
// 使用接口
c.Set(ctx, "key", value, time.Minute)
val, _ := c.Get(ctx, "key")
mgr := lifecycle.NewManager()
mgr.SetShutdownTimeout(10 * time.Second)
mgr.OnStart(func(ctx context.Context) error {
return nil // 启动任务
})
mgr.OnShutdown(func(ctx context.Context) error {
return db.Close() // 清理任务
})
mgr.Run(server, nil)
type MyLogger struct{}
func (l *MyLogger) Debug(msg string, args ...any) {}
func (l *MyLogger) Info(msg string, args ...any) {}
func (l *MyLogger) Warn(msg string, args ...any) {}
func (l *MyLogger) Error(msg string, args ...any) {}
func (l *MyLogger) WithContext(ctx context.Context) logger.Logger { return l }
func (l *MyLogger) WithFields(fields map[string]any) logger.Logger { return l }
app := gin.New()
app.WithLogger(&MyLogger{})
ws, err := c.UpgradeWebSocket("user-1")
defer ws.Close()
go ws.StartPingPong()
for {
msg, err := ws.ReadText()
if err != nil {
break
}
ws.WriteText("echo: " + msg)
}
// CSV 导出
err := export.CSV(w, data, export.CSVConfig{
Headers: []string{"ID", "Name", "Email"},
Fields: []string{"id", "name", "email"},
})
// Excel 导出
err := export.Excel(w, data, export.ExcelConfig{
SheetName: "Users",
})
// 阿里云 SMS
aliyun := sms.NewAliyunProvider(sms.AliyunConfig{
AccessKey: "your-access-key",
AccessSecret: "your-access-secret",
SignName: "YourSignName",
})
err := aliyun.Send(ctx, "13800138000", "SMS_123456", map[string]string{"code": "1234"})
详细文档请参考各子包的 README.md。
// 创建引擎
app := gin.New(opts ...OptionFunc)
app := gin.Default(opts ...OptionFunc)
// 启动服务
err := app.Run() // 监听默认 :8080
err := app.Run(":9090") // 监听指定地址
// 优雅关闭
err := app.Shutdown(ctx)
// 生命周期钩子
app.OnStart(hooks ...HookFunc)
app.OnShutdown(hooks ...HookFunc)
app.OnStopped(hooks ...HookFunc)
// 获取路由器
r := app.Router()
rx := app.RegexRouter()
// 与上游 gin 一致的单一来源取值
c.Param(key string) string
c.Query(key string) string
c.DefaultQuery(key, defaultValue string) string
c.PostForm(key string) string
c.DefaultPostForm(key, defaultValue string) string
// 本项目增强的聚合取值
c.Input(key string, defaults ...string) string
c.ParamInt(key string, defaults ...int) int
c.ParamInt64(key string, defaults ...int64) int64
c.ParamFloat(key string, defaults ...float64) float64
c.ParamBool(key string, defaults ...bool) bool
c.ParamIntE(key string) (int, error)
c.ParamInt64E(key string) (int64, error)
c.ParamFloatE(key string) (float64, error)
c.ParamBoolE(key string) (bool, error)
// 响应方法
c.Success(data any)
c.Created(data any)
c.Accepted(data any)
c.NoContent()
c.BadRequest(message string)
c.Unauthorized(message string)
c.Forbidden(message string)
c.NotFound(message string)
c.Conflict(message string)
c.ValidationError(errors any)
c.InternalError(message string)
c.TooManyRequests()
c.Error(code int, message string)
// 分页
c.ParsePagination() (page, perPage int)
c.Paginated(data any, page, perPage, total int)
c.ParseCursorPagination() (cursor string, limit int)
c.CursorPaginated(data any, cursor string, limit int, hasMore bool)
// 流式响应
c.BeginSSE()
c.SSE(event string, data any)
c.SSEHeartbeat()
c.BeginNDJSON()
c.StreamNDJSON(obj any)
// 请求信息
c.GetIP() string
c.GetUserAgent() string
c.RequestID() string
c.TraceID() string
c.SpanID() string
c.GetBearerToken() string
c.IsAjax() bool
c.IsJSON() bool
c.IsSecure() bool
// WebSocket
c.UpgradeWebSocket(userID string, opts ...WSOption) (*websocket.WebSocket, error)
// 获取组件
c.Logger() logger.Logger
c.Cache() cache.Cache
c.Auth() *auth.AuthContext
// HTTP 方法
r.GET(path string, handlers ...HandlerFunc)
r.POST(path string, handlers ...HandlerFunc)
r.PUT(path string, handlers ...HandlerFunc)
r.PATCH(path string, handlers ...HandlerFunc)
r.DELETE(path string, handlers ...HandlerFunc)
r.HEAD(path string, handlers ...HandlerFunc)
r.OPTIONS(path string, handlers ...HandlerFunc)
r.Any(path string, handlers ...HandlerFunc)
r.Match(methods []string, path string, handlers ...HandlerFunc)
// 分组
group := r.Group(prefix string, middleware ...HandlerFunc)
// 资源路由
r.Resource(name string, ctrl ResourceController, opts ...ResourceOption)
r.CRUD(name string, ctrl ResourceController)
// 版本化
v := r.Version(v string) *Router
r.VersionedAPI(version string, setup func(*Router))
// 健康检查
r.HealthCheck(paths ...string)
r.Liveness(path ...string)
r.Readiness(checks ...ProbeCheck)
r.Startup(checks ...ProbeCheck)
// 静态资源
r.Static(relativePath string, root string)
r.StaticFile(relativePath string, filepath string)
r.StaticFS(relativePath string, sys http.FileSystem)
r.Assets(relativePath string, root string, opts ...static.Option)
r.AssetsFS(relativePath string, sys http.FileSystem, opts ...static.Option)
r.Site(relativePath string, root string, opts ...static.Option)
r.SiteFS(relativePath string, sys http.FileSystem, opts ...static.Option)
r.SiteZip(relativePath string, zipPath string, opts ...static.Option)
r.SiteEmbeddedZip(relativePath string, archive fs.FS, archivePath string, opts ...static.Option)
r.EmbedFS(relativePath string, fsys embed.FS, subPath ...string)
r.EmbedFile(relativePath string, file embed.FS, filePath string)
e.FallbackSite(root string, opts ...static.Option)
e.FallbackSiteFS(sys http.FileSystem, opts ...static.Option)
e.FallbackSiteZip(zipPath string, opts ...static.Option)
e.FallbackSiteEmbeddedZip(archive fs.FS, archivePath string, opts ...static.Option)
// 自动注册
r.AutoRegister(controller any, opts ...OptionFunc)
r.WithPrefix(prefix string) OptionFunc
r.WithMiddleware(mw ...HandlerFunc) OptionFunc
详细 API 文档请参考 docs/api-reference.md。
框架提供了路由基准测试文件 routing_bench_test.go,用于对比不同路由场景的性能:
go test -run '^$' -bench '^BenchmarkRouting$|^BenchmarkRoutingParallel$' -benchmem -benchtime=1s .
测试场景包括:
该模块并不是替代 Gin 的"重写版",而是基于 Gin 的增强层:
github.com/gin-gonic/gin如果你已经熟悉 Gin,这个模块的上手成本会很低。
gin.Default() + Router() 开始WithAuth(),并在生产环境切换到 Redis 存储WithLogger()、WithCache()、Production() 与中间件栈GET/POST/Match/Any 中使用 chi 风格 pattern;仅在需要高级控制时再使用 RegexRouter()本项目基于 MIT 许可证开源。详细信息请参考 LICENSE 文件。