杭州明婳科技 5dbbce34f5 docs: 同步 GUIDE/README 至 v1.4.0
补 v1.4.0 发布时漏更的文档(代码与契约已改,文档未跟上):
- GUIDE 13 定时任务:新增 WithCronTask App 集成模式 + pre-Init cron.AddTask 迁移警示。
- GUIDE 14 链路追踪:新增 WithTrace 生命周期集成 + OTel 进程全局多 App 注意。
- GUIDE 8.4 限流:新增 app.RateLimitRegistry() 多 App 计数隔离 + WithRedisClient。
- GUIDE 5.1 缓存 / 10.3 存储:注明 App 自动初始化(per-App redis 注入 / Close 收口)。
- README:新增 v1.4.0 更新日志条目;cron/缓存/限流段补 v1.4.0 说明。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 16:35:47 +08:00
2026-07-03 00:00:11 +08:00
2026-07-08 18:21:32 +08:00
2026-06-23 10:41:05 +08:00
2026-07-02 18:26:27 +08:00
2026-07-02 18:26:27 +08:00
2026-07-13 16:35:47 +08:00
2026-04-30 23:40:59 +08:00
2026-04-30 23:26:14 +08:00
2026-07-13 16:35:47 +08:00

xlgo Web Framework

Go Version Gin Version License

基于 Go + Gin 的后端开发框架。组件全部 Manager 化——既能像脚本一样用包级函数简单调用,又能像正经框架一样注入实例、跑多套、塞 mock 做单测。

为什么是 xlgo

多数 Gin 脚手架把数据库、Redis、缓存、JWT、日志做成包级全局单例:写小项目顺手,但一旦要做单元测试、同进程跑多套实例、或替换实现,就动弹不得。xlgo 把这些组件全部抽象成 XxxManager,同时保留包级便捷函数做 facade——简单用法零成本,复杂场景不封顶

// 简单用法:包级函数,跟单例脚手架一样直接
database.InitDB(cfg)
db := database.GetDB()

// 同一份代码,也能注入实例 / 跑多套 / 塞 mock
myDB := database.NewManager(cfg)
myDB.Open(ctx)                       // 独立实例,不受全局影响
database.SetDefaultManager(myDB)     // 提升为全局默认,并关闭旧默认 manager
cache.SetDefaultCacheManager(&cache.CacheManager{}) // 测试注入自定义 CacheManager

区别于一般 Gin 脚手架的几点

  • 组件可注入 — database / redis / cache / jwt / storage / logger 均为 Manager + 全局 facade 双轨,支持多实例与 mock 注入,而非写死的包级单例
  • 生产就绪内置/livez /readyz、Prometheus /metrics、主库探活自愈、请求级超时、优雅关闭(等待 in-flight goroutine)开箱即用,不用自己搭
  • 框架内零 Fatal — 错误一律返回由调用方处理,框架代码不 panic 杀进程
  • 默认轻量、按需启用New() 什么都不开,NewFullStack() 一键全开,中间任意组合;Without* 精细排除
  • 可插拔数据库方言 — GORM 方言注册表,内置 MySQL / PostgreSQL,可扩展 SQLite / ClickHouse 等

30 秒上手

package main

import (
	xlgo "github.com/EthanCodeCraft/xlgo-core"
	"github.com/EthanCodeCraft/xlgo-core/response"
	"github.com/gin-gonic/gin"
)

func main() {
	app := xlgo.New(
		xlgo.WithConfigPath("./config.yaml"),
		xlgo.WithLogger(),
		xlgo.WithHealthRoutes(),
	)
	app.GetRouter().GET("/", func(c *gin.Context) {
		response.Success(c, gin.H{"hello": "xlgo"})
	})
	_ = app.Run()
}

框架特性

架构与可注入性

  • 组件 Manager 化 - database / redis / cache / jwt / storage / logger 均为 XxxManager + 全局 facade 双轨,支持多实例与测试注入 mock
  • 可插拔数据库方言 - GORM 方言注册表,内置 MySQL / PostgreSQL,可注册 SQLite / ClickHouse 等任意驱动
  • 读写分离 - 主库 + 多副本,ReplicaPicker 策略可插拔(轮询 / 随机 / 自定义),context 路由强制主库
  • Lifecycle Hooks - OnInit / OnStart / OnReady / OnStop 生命周期钩子,可插拔启动/关闭逻辑

生产就绪

  • 健康探针 - /livez(存活)+ /readyz(就绪)+ /health(兼容),K8s probe 友好
  • Prometheus 指标 - /metrics 端点 + 请求计数 / 耗时 / 在飞数采集中间件
  • 依赖健康自愈 - 主库后台探活,连续失败标记不健康联动 503;从库失败临时剔除、恢复自动纳入
  • 请求级超时 - middleware.Timeout 级联取消下游 GORM/Redis
  • 优雅关闭 - App.Go 管理后台 goroutineShutdown 时 cancel + 等待退出(带超时)
  • 配置校验 - 启动期 Validate 拦截非法配置(端口/密钥/必填字段),错误前置

基础功能

  • 配置管理 - YAML + 环境变量覆盖 + 热更新,time.Duration 字符串解析("24h"
  • 缓存 - Redis 缓存,键前缀、TTL、SCAN 优化、分布式锁
  • 认证 - JWT 认证,Token 黑名单、刷新机制,HMAC 算法可选
  • 日志 - 分级日志(通用 / API / DB 三分流),lumberjack 轮转
  • 中间件 - CORS、限流(内存 + Redis 版)、请求日志、认证、CSRF、RequestID、Recover
  • 文件存储 - 本地 + 阿里云 OSS
  • 实时通信 - SSE 流式响应 + WebSocket
  • 定时任务 - 内置调度器(interval / daily / weekly / cron
  • 验证器 - 请求参数验证,自定义错误消息
  • 响应模式 - ModeBusiness(业务码,默认)/ ModeREST(按错误码映射 HTTP status)一键切换
  • CLI 工具 - 脚手架 + 代码生成(handler/repository/model/service

快速开始

环境要求xlgo 基于 Go 1.25+ 构建,请确保本地已安装 Go 1.25 或更高版本。

1. 安装

# 安装脚手架工具
go install github.com/EthanCodeCraft/xlgo-core/cmd/xlgo@latest

# 创建新项目
xlgo new myproject

# 进入目录
cd myproject

# 安装依赖
go mod tidy

2. 创建配置文件 config.yaml

server:
  host: ""                 # 绑定地址,空=监听所有接口;127.0.0.1=仅本机
  port: 8080
  mode: development
  read_timeout: 15s
  write_timeout: 30s
  idle_timeout: 60s
  shutdown_timeout: 30s
  response_mode: business  # business(默认,全200+业务码) 或 rest(按错误码映射HTTP status)

database:
  driver: mysql          # mysql(默认)或 postgres
  host: localhost
  port: 3306
  user: root
  password: your_password
  name: your_database
  max_idle_conns: 10
  max_open_conns: 100
  # dsn: "自定义连接字符串,设置后优先于上面的字段"

redis:
  host: localhost
  port: 6379
  password: ""
  db: 0

jwt:
  secret: your_jwt_secret_key_at_least_32_bytes  # ≥32 字节,否则启动期被 Validate 拦截
  expire: "24h"            # time.Duration,支持 "24h"/"30m" 等
  refresh_expire: "168h"   # 刷新 token 过期时间
  issuer: xlgo
  algorithm: HS256         # HS256(默认)/HS384/HS512

storage:
  driver: local
  local:
    path: ./public
    base_url: http://localhost:8080/public

log:
  dir: ./logs
  max_size: 100
  max_backups: 30
  max_age: 30
  compress: true

3. 运行项目

go run main.go

访问 http://localhost:8080/health 检查服务状态。

v1.0.2 起,xlgo.New() 默认是轻量应用,不会自动初始化 MySQL、Redis、Storage 或 Swagger。需要完整基础设施时请显式使用 WithMySQL()WithRedis()WithStorage()WithSwaggerRoutes(),或直接使用 xlgo.NewFullStack()

Without* 系列 Option(如 WithoutSwaggerRoutes)主要用于 NewFullStack / RunFullStack 启用全部组件后排除个别项,例如 xlgo.NewFullStack(xlgo.WithoutSwaggerRoutes()) 全组件但关闭 Swagger。xlgo.New() 本身已全关,无需再 Without

4. 模块路径与包名

xlgo 的 模块路径github.com/EthanCodeCraft/xlgo-core包名xlgo(二者不同是 Go 惯例,如 github.com/gin-gonic/gin → 包名 gin)。import 时用别名 xlgo 即可:

package main

import (
	xlgo "github.com/EthanCodeCraft/xlgo-core"
	"github.com/EthanCodeCraft/xlgo-core/middleware"
	"github.com/EthanCodeCraft/xlgo-core/response"
	"github.com/gin-gonic/gin"
)

func main() {
	app := xlgo.New(
		xlgo.WithConfigPath("./config.yaml"),
		xlgo.WithLogger(),
		xlgo.WithHealthRoutes(),
	)
	// 注册一个示例路由
	app.GetRouter().GET("/", func(c *gin.Context) {
		response.Success(c, gin.H{"message": "Hello xlgo!"})
	})
	// 启用 JWT 认证示例路由(需配合 WithMySQL/WithRedis 生成 token
	_ = middleware.RequireUserTypes("admin")

	_ = app.Run()
}

子包(config / database / logger / middleware ...)的包名与目录名一致,无需别名。


核心功能

配置管理

// 加载配置
cfg, err := config.Load("./config.yaml")

// 获取配置
cfg := config.Get()
serverPort := cfg.Server.Port

// 配置热更新
cfg, err := config.LoadWithWatch("./config.yaml", func(newCfg *config.Config) {
    log.Println("配置已更新")
})

// 注册配置变更回调
config.RegisterCallback(func(cfg *config.Config) {
    // 处理配置变更
})

// 手动重新加载
config.Reload()

数据库操作

xlgo 基于 GORM,主库与从库的驱动由配置 database.driver 决定,内置支持 mysql(默认)与 postgres,也可通过 database.dsn 使用任意自定义连接字符串。v1.0.2 起 GORM 方言通过 可插拔注册表 管理,应用可自行接入 SQLite、SQL Server、ClickHouse 等任意 GORM 驱动而无需修改框架。

// 初始化数据库(驱动由配置决定)
database.InitDB(cfg)
defer database.Close()

// 主从读写分离(从库 DSN 需与主库驱动匹配)
database.InitDBWithReplicas(cfg, []string{
    "root:pass@tcp(slave1:3306)/db",
    "root:pass@tcp(slave2:3306)/db",
})

// 读操作自动路由到从库
var users []model.User
database.GetReadDB().Find(&users)

// 强制使用主库
ctx := database.UseMaster(context.Background())
database.GetDBFromContext(ctx).Find(&users)

// 事务
database.Transaction(func(tx *gorm.DB) error {
    return tx.Create(&user).Error
})

// 健康检查
status := database.HealthCheck()

v1.0.2 起数据库状态由实例化的 database.Manager 管理,全局函数(GetDBGetReadDBCloseAll 等)作为默认 DefaultManager 的 facade 保留。可通过 database.NewManager(cfg) 创建独立管理器,并通过 ReplicaPicker 自定义从库选择策略:

// 独立管理器(不影响全局 DefaultManager
mgr := database.NewManager(cfg)
if err := mgr.Open(context.Background()); err != nil {
    return err
}
defer mgr.Close()

// 从库选择策略:轮询(默认随机)
database.SetReplicaPicker(&database.RoundRobinPicker{})

database.SetDefaultManager(m) 表示把新 manager 接管为全局默认,并关闭被替换的旧 manager,避免连接池泄漏。需要失败回滚或延迟释放旧资源的初始化流程,请使用 database.SwapDefaultManager(m) 并自行决定何时关闭返回的旧 manager。

注册自定义 GORM 方言

通过 database.RegisterDialect 一次注册即可让 database.driver: <name> 生效,DSN 构建器会同步登记到 config 包,因此 cfg.Database.DSN() 也会识别新驱动:

import (
    "github.com/EthanCodeCraft/xlgo-core/config"
    "github.com/EthanCodeCraft/xlgo-core/database"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

func init() {
    database.RegisterDialect(database.DialectSpec{
        Name:      "sqlite",
        Aliases:   []string{"sqlite3"},
        Dialector: func(dsn string) gorm.Dialector { return sqlite.Open(dsn) },
        DSN:       func(c *config.DatabaseConfig) string { return c.Name }, // Name 当作文件路径
    })
}

诊断接口:database.RegisteredDialects() 返回当前已注册的驱动名(含别名),database.LookupDialect(name) 用于检查某个驱动是否已就绪。未注册的驱动会回退到 MySQL 以保持向后兼容。

Repository 泛型 CRUD

// 创建仓库
userRepo := repository.NewBaseRepo[model.User](database.GetDB())

// 基础 CRUD
user, err := userRepo.FindByID(ctx, 1)
err := userRepo.Create(ctx, &user)
err := userRepo.Update(ctx, &user)
err := userRepo.Delete(ctx, 1)

// 分页查询
result, err := userRepo.FindPage(ctx, 1, 20)
// result.Items, result.Total, result.Page, result.PageSize

// 条件查询
users, err := userRepo.FindWhere(ctx, "status = ?", 1)

// 链式查询
users, err := userRepo.NewQueryBuilder().
    Where("status = ?", 1).
    Order("created_at DESC").
    Limit(10).
    Find(ctx)

// 批量操作
err := userRepo.CreateBatch(ctx, users)
err := userRepo.DeleteBatch(ctx, []uint{1, 2, 3})

// 事务支持
err := userRepo.WithTransaction(ctx, func(txRepo *repository.BaseRepo[model.User]) error {
    return txRepo.Create(ctx, &user)
})

Redis 缓存

v1.4.0App 经 WithRedis() 启用时自动用 App 的 Redis 客户端初始化缓存(多 App 隔离,不串越)。下方 cache.Init() 用于 standalone。

// 初始化缓存
cache.Init()

// 使用缓存
ctx := context.Background()
cacheService := cache.GetCache()

// 设置缓存
cacheService.Set(ctx, "user:1", user, 10*time.Minute)

// 获取缓存(命中返回 true,未命中返回 false,nil,后端错误返回 err
var user User
if hit, err := cacheService.Get(ctx, "user:1", &user); err != nil {
    return err
} else if hit {
    // 缓存命中
}
if exists, err := cacheService.Exists(ctx, "user:1"); err != nil {
    return err
} else if exists {
    // key 存在
}

// 删除缓存
cacheService.Delete(ctx, "user:1")

// 按模式删除(使用 SCAN 优化)
cacheService.DeleteByPattern(ctx, "user:*")

JWT 认证

// 生成 Token(自动包含 JTI
token, err := jwt.GenerateToken(userID, username, "admin", "admin")

// 解析 Token
// ParseToken 默认 fail-closed:黑名单后端不可检查时拒绝 Token(需 Redis
claims, err := jwt.ParseToken(tokenString)

// 需显式 fail-open(仅无 Redis 或低安全场景)用:
claims, err = jwt.ParseTokenWithBlacklistPolicy(tokenString, jwt.BlacklistFailOpen)

// 使 Token 失效(使用 JTI,高效)
jwt.InvalidateToken(tokenString)

// 获取 Token 的 JTI
jti, err := jwt.GetJTI(tokenString)

JWT 黑名单优化:使用 JTIJWT ID)替代完整 Token 存储,大幅节省 Redis 内存。

分布式锁

// 安全加锁(返回 LockToken
token, err := cache.NewLock(ctx, cache.KLock("order:123"), 30*time.Second)
if token != nil {
    defer cache.Unlock(ctx, token) // 只有持有者能释放
    // 执行业务逻辑
}

// 续期锁(长任务场景)
cache.ExtendLock(ctx, token, 30*time.Second)

// 自动续期执行
err := cache.WithLockAutoExtend(ctx, key, 30*time.Second, 10*time.Second, func(ctx context.Context) error {
    // 长任务自动续期
    return nil
})

分布式锁安全特性:使用 Lua 脚本 + UUID Token,只有锁的持有者才能释放。

Redis 分布式限流

v1.4.0:多 App 走各自 Redis 用 middleware.NewRedisRateLimiter(..., middleware.WithRedisClient(app.RedisClient()))

// 内存限流(单实例)
r.Use(middleware.CustomRateLimit(100, time.Minute))

// Redis 分布式限流(多实例共享)
r.Use(middleware.RedisRateLimit("api_limit", 100))
r.Use(middleware.LoginRedisRateLimit())  // 登录限流
r.Use(middleware.APIRedisRateLimit())    // API 限流

请求验证

type LoginRequest struct {
    Username string `json:"username" label:"用户名" validate:"required,username" msg_required:"请输入用户名"`
    Password string `json:"password" label:"密码" validate:"required,password" error:"密码格式不正确"`
    Phone    string `json:"phone" label:"手机号" validate:"omitempty,phone" msg_phone:"请输入正确的手机号"`
}

// 绑定并验证
errors, ok := validation.ShouldBindAndValidate(c, &req)
if !ok {
    response.Fail(c, errors.FirstMessage())
    return
}

验证器支持的自定义标签

  • label:"中文名" - 字段显示名称
  • error:"通用错误消息" - 所有验证失败时显示
  • msg_required:"必填项" - 特定规则的错误消息
  • msg_min:"最少5个字符" - 针对 min 规则的错误消息

统一错误码

// 使用预定义错误
response.FailWithError(c, response.ErrUserNotFound)

// 带详细信息
response.FailWithDetail(c, response.ErrPasswordWrong, "连续错误3次将锁定账户")

// 自定义错误
err := response.NewError(10001, "自定义错误")

预定义错误码

  • 用户模块:ErrUserNotFound, ErrPasswordWrong, ErrTokenExpired
  • 文件模块:ErrFileTooLarge, ErrFileTypeInvalid
  • 数据模块:ErrDataNotFound, ErrDataConflict

文件上传

// 上传文件
file, err := c.FormFile("file")
path, err := storage.Upload(file, "images")
url := storage.GetURL(path)

// 上传字节数组
path, err := storage.UploadFromBytes(data, "filename.jpg", "images")

// 删除文件
err := storage.Delete(path)

// 获取文件内容
data, err := storage.Get(path)

// 检查文件是否存在(不存在 false,nil;后端错误返回 err
ok, err := storage.Exists(path)
if err != nil {
    return err
}

SSE 流式响应

// AI 对话场景
func ChatHandler(c *gin.Context) {
    ch := make(chan string)
    go func() {
        defer close(ch)
        for _, chunk := range aiResponse {
            ch <- chunk
        }
    }()
    sse.StreamChunks(c, ch)
}

// 带消息 ID 的流式响应
sse.StreamWithID(c, "msg_123", ch)

WebSocket

// 简单使用
r.GET("/ws", ws.HandleFunc(func(conn *ws.Connection, message []byte) {
    conn.SendText("收到: " + string(message))
}))

// 广播模式
hub := ws.NewHub()
go hub.Run()
hub.Broadcast([]byte("广播消息"))

定时任务

v1.4.0App 持专属调度器(实例隔离)。App 管理的任务用 xlgo.WithCronTask(name, schedule, handler) 注册(蕴含启用 cronInit 时注册+启动,Shutdown 时停止)。下方包级 cron.AddTask/cron.Start 仅用于 standalone(无 App)或 Init 之后;Init 之前调包级 cron.AddTask 会注册到 init 默认 调度器、被 App swap 丢弃。app.Scheduler() 供动态注册与管理。

// 每隔 5 分钟执行
cron.AddTask("cleanup", cron.Every(5*time.Minute), func(ctx context.Context) error {
    return cleanupOldData()
})

// 每天凌晨 2 点执行
cron.AddTask("daily_report", cron.Daily(2, 0), generateReport)

// 每周一上午 9 点执行
cron.AddTask("weekly", cron.Weekly(time.Monday, 9, 0), weeklyTask)

// 完整 Cron 表达式
cron.AddTask("every15min", cron.ParseCron("*/15 * * * *"), doSomething)
cron.AddTask("monthly", cron.ParseCron("0 0 1 * *"), doSomething) // 每月1号

// ParseCron 对非法表达式 fail-fast panic;动态输入请用 ParseCronStrict 处理 error。
// 如需旧版“非法表达式回退为每分钟”的兼容语义,请显式使用 ParseCronOrDefault。

// 启动调度器
cron.Start()
defer cron.Stop()

CSRF 防护

// 基本使用
r.Use(middleware.CSRF())

// 获取 Token(返回给前端)
token := middleware.GetCSRFToken(c)

// 跳过指定路径
r.Use(middleware.CSRFWithSkip([]string{"/api/webhook"}))

// API 模式(双重提交 Cookie
r.Use(middleware.DoubleSubmitCookie())

密码加密

// 加密密码
hash, err := validation.HashPassword("password123")

// 验证密码
if validation.CheckPassword(hash, "password123") {
    // 密码正确
}

// 带成本升级的验证
match, needUpgrade, newHash, err := validation.CheckPasswordAndUpgrade(hash, password, 12)

中间件

认证中间件

// JWT 认证(必须登录)
r.Use(middleware.AuthRequired())

// 自定义用户类型权限
r.Use(middleware.RequireUserTypes("tenant_admin", "platform_admin"))

// 自定义角色权限
r.Use(middleware.RequireRoles("owner", "manager"))

// 自定义复杂权限判断
r.Use(middleware.RequireAuth(func(user middleware.AuthUser, c *gin.Context) bool {
    return user.UserType == "merchant" && user.Role == "owner"
}))

// 默认快捷方法(super_admin/admin/staff 只是框架默认常量)
r.Use(middleware.AdminRequired())
r.Use(middleware.SuperAdminRequired())
r.Use(middleware.StaffRequired())
r.Use(middleware.AnyUserRequired())

// 获取用户信息
user, ok := middleware.GetAuthUser(c)
userID := middleware.GetUserID(c)
username := middleware.GetUsername(c)
role := middleware.GetRole(c)
userType := middleware.GetUserType(c)

限流中间件

v1.4.0App Shutdown 自动 registry.Stop()(不再调全局 StopRateLimiters,避免误停其他 App)。多 App per-App 计数隔离用 app.RateLimitRegistry().LoginRateLimit() 等 App-bound 中间件。

// 登录限流(每分钟 10 次)
r.POST("/login", middleware.LoginRateLimit(), func(c *gin.Context) {
    response.Success(c, gin.H{"token": "..."})
})

// 上传限流(每分钟 20 次)
r.POST("/upload", middleware.UploadRateLimit(), func(c *gin.Context) {
    response.Success(c, gin.H{"file": "..."})
})

// 自定义限流
r.Use(middleware.CustomRateLimit(50, time.Minute))

// 停止限流器(应用关闭时)
defer middleware.StopRateLimiters()

响应格式

框架使用统一的响应格式:

{
  "code": 1,
  "msg": "操作成功",
  "data": {},
  "request_id": "abc123"
}
// 成功响应
response.Success(c, data)
response.SuccessWithMsg(c, "自定义消息", data)

// 失败响应
response.Fail(c, "错误消息")

// 分页响应
response.Page(c, list, total, page, pageSize)

// 错误响应
response.Unauthorized(c, "请先登录")
response.NotFound(c, "资源不存在")
response.ServerError(c, "服务器错误")
response.RateLimit(c)

// 显式指定 HTTP status(不受 Mode 影响)
response.Custom(c, http.StatusBadRequest, response.CodeFail, "参数错误", nil)

响应模式v1.1.0):默认 ModeBusiness,所有响应 HTTP 200 + 业务码(兼容存量)。切换 ModeREST 后,失败响应按错误码映射对应 HTTP status401/404/429/500...),body 仍带业务码,便于 APM / Prometheus / 网关按 status 区分异常:

response.SetMode(response.ModeREST)            // 代码切换
// 或在 config.yaml 配置:server.response_mode: rest

CLI 工具

# 创建新项目
xlgo new myproject

# 创建新项目并指定模块路径
xlgo new myproject --module github.com/myorg/myproject

# 生成代码
xlgo make handler user    # 创建 handler/user.go
xlgo make repository user # 创建 repository/user_repository.go
xlgo make model user      # 创建 model/user.go
xlgo make service user    # 创建 service/user_service.go

# 显示版本
xlgo version

测试

框架提供完整的测试工具包:

func TestUserAPI(t *testing.T) {
    router := test.SetupRouter()

    // 创建请求
    resp := test.POST(router, "/api/v1/users").
        WithJSON(map[string]any{"name": "test"}).
        WithToken("xxx").
        Execute()

    // 断言
    resp.AssertOK(t)

    // 解析响应
    var result map[string]any
    resp.ParseJSON(&result)
}

目录结构

xlgo/
├── app.go              # 应用入口
├── cache/
│   ├── cache.go        # 缓存服务
│   ├── keybuilder.go   # 键名前缀管理
│   └── lock.go         # 分布式锁
├── cmd/
│   └── xlgo/           # CLI 脚手架
├── compress/
│   └── compress.go     # Gzip/Zip 压缩解压
├── config/
│   ├── config.go       # 配置管理(支持热更新、time.Duration 解析)
│   └── validate.go     # 配置校验(启动期拦截非法配置)
├── console/
│   └── console.go      # 彩色控制台输出
├── cron/
│   └── cron.go         # 定时任务调度
├── database/
│   ├── manager.go     # 数据库管理器(主从、Picker、探活自愈、Init/Close/HealthCheck
│   ├── dialect.go     # GORM 方言注册表(mysql / postgres,可扩展)
│   └── redis.go       # Redis 连接管理器(RedisManager
├── handler/
│   └── handler.go      # 基础处理器(类型安全参数获取)
├── jwt/
│   └── jwt.go          # JWT 工具
├── logger/
│   ├── logger.go       # 日志实现
│   └── field.go        # 日志字段工具
├── middleware/
│   ├── auth.go         # JWT 认证中间件
│   ├── cors.go         # CORS 跨域中间件
│   ├── csrf.go         # CSRF 防护中间件
│   ├── logger.go       # 请求日志中间件
│   ├── metrics.go      # Prometheus 指标中间件
│   ├── ratelimit.go    # 限流中间件
│   ├── requestid.go    # 请求ID中间件
│   ├── recover.go      # Panic恢复中间件
│   └── timeout.go      # 请求级超时中间件
├── model/
│   └── base.go         # 基础模型
├── repository/
│   └── repository.go   # 基础仓库(泛型CRUD
├── response/
│   ├── response.go     # 统一响应格式
│   ├── mode.go         # 响应模式开关(business / REST
│   └── error.go        # 统一错误码
├── router/
│   ├── router.go       # 路由注册中心(模块化/版本化、livez/readyz/health
│   └── metrics.go      # Prometheus /metrics 端点
├── sse/
│   └── sse.go          # SSE 流式响应
├── storage/
│   └── storage.go      # 文件存储(本地+OSS)
├── test/
│   └── test.go         # 测试工具包
├── trace/
│   └── trace.go        # 链路追踪
├── utils/
│   ├── random.go       # 随机数生成
│   ├── strings.go      # 字符串处理
│   ├── datetime.go     # 时间日期
│   ├── convert.go      # 类型转换
│   ├── file.go         # 文件操作
│   ├── url.go          # URL处理
│   ├── validator.go    # 格式验证
│   ├── crypto.go       # 加密编码
│   ├── http.go         # HTTP客户端
│   └── uuid.go         # UUID生成
├── validation/
│   ├── validator.go    # 请求验证器
│   ├── password.go     # 密码强度验证
│   └── hash.go         # 密码加密
└── ws/
    └── ws.go           # WebSocket 支持

部署指南

Docker 部署

FROM golang:1.25-alpine AS builder

WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o server .

FROM alpine:latest
RUN apk --no-cache add ca-certificates tzdata
WORKDIR /app
COPY --from=builder /app/server .
COPY --from=builder /app/config.yaml .
RUN mkdir -p /app/public /app/logs
EXPOSE 8080
CMD ["./server", "-config", "./config.yaml"]
docker build -t xlgo-app:latest .
docker run -d -p 8080:8080 xlgo-app:latest

更新日志

完整变更历史见 CHANGELOG.md

v1.4.0 (2026-07-12)

instance+facade 一致性收尾cron/storage/cache/ratelimit 实例化(App 持 per-App 实例 + SwapDefault* + 包级 facade 代理,照 database.Manager/jwt.Manager 模式),消除单进程多 App 互相污染;trace 纳入 App 生命周期(WithTrace);删除 WithoutWire 空函数。go vet + go build + go test -race ./... 全绿。完整变更见 CHANGELOG.md

主要破坏性变更(升级前必读):

  • xlgo.WithoutWire() 删除v1.1.0 wire 包删除后遗留的空 no-op,直接删除调用即可(无行为变化)。
  • cron.AddTaskApp.Init 之前注册失效App 持专属调度器,pre-Init 包级 cron.AddTask 注册到 init 默认调度器、被 App swap 丢弃。改用 xlgo.WithCronTask(name, schedule, handler)(或 Init 后 app.Scheduler().AddTask)。
  • middleware.StopRateLimiters 语义收窄:仅停默认 RegistryApp Shutdownapp 自己的 registry.Stop(),不再误停其他 App 限流器。
  • commitReplacedResources 不再关闭 previousDB/previousRedis/loggerSnapshot:多 App 下 previous 可能是另一 App 的活跃资源(致 redis: client is closed / logger writer 误杀)。单 App 无影响(init 默认无资源)。
  • cache/ratelimit redis 注入jwt 模型):cache.NewRedisCacheWithRedis/CacheManager.InitWithRedismiddleware.WithRedisClient

新增:WithTrace/WithCronTaskapp.Scheduler()/Cache()/RedisClient()/RateLimitRegistry()cron/storage/cache/middlewareSwapDefault*config.TraceConfigmiddleware.RateLimitRegistry + App-bound 限流中间件;storage.StorageManager.Close()

升级:go get github.com/EthanCodeCraft/xlgo-core@v1.4.0⚠️ 含破坏性变更,详见 CHANGELOG 迁移说明)

v1.2.0 (2026-07-04)

破坏性版本4 轮对抗性评审(deepseek / GLM / Claude / 终审)收口的全部 CRITICAL/HIGH/MEDIUM 修复 + 主线A 包级可变全局并发治理统一。go vet + go build + go test -race ./... 全绿。完整变更见 CHANGELOG.md

主要破坏性变更(升级前必读):

  • 包级可变全局一律 atomic.Pointer(主线A):database.DefaultRedis/database.DefaultManager/storage.DefaultStorage/validation.Validator 由裸指针改为 atomic.Pointer[T]。直接读字段调方法需改用 facade(InitRedis/GetDB/ValidateStruct 等)或 .Load()database.DefaultManager = xdatabase.SetDefaultManager(x)
  • jwt.DefaultJWT 包级变量删除GetDefaultJWT() / SetDefaultJWTManager()
  • repository.FindWhereOrdered/FindPageWhereOrdered 签名args []anyargs ...anyorder 前置于 query
  • response.ResponseData/RequestIDomitempty:两字段在所有响应中恒存在。
  • cache.IsLocked/GetLockTTL/ForceUnlock Redis 不可用改返 ErrRedisNotReady(锁操作正确性相关)。
  • config.Load() 返回深拷贝:新增 (*Config).Clone()Get() 仍返只读指针(热路径零分配)。
  • handler.GetPagepage 上限 10000utils.EqualsIgnoreCasestrings.EqualFoldutils.ReadFile 去 TOCTOU 前置检查(错误改 errors.Is(err, os.ErrNotExist))。

升级:go get github.com/EthanCodeCraft/xlgo-core@v1.2.0⚠️ 含破坏性变更,详见 CHANGELOG 迁移说明)

v1.1.1 (2026-06-23)

v1.1.0 的补丁发布:补 ServerConfig.Host 绑定地址、面向用户文案统一中文、修正 README 过时/错误描述(含会致启动失败的配置示例)。

  • ServerConfig.Host - server.host 控制监听地址,空=所有接口(兼容),127.0.0.1=仅本机,内网 IP=绑定指定网卡
  • 文案统一中文 - recover/logger/metrics 等面向用户文案统一中文(保留协议字段名、Prometheus metric Name、MySQL 错误串匹配等英文)
  • README 修正 - 删 wire 段、修配置示例(secret≥32 字节 + expire 改 Duration)、补 v1.1.0 新文件、重写首段介绍

升级:go get github.com/EthanCodeCraft/xlgo-core@v1.1.1(无破坏性变更)

v1.1.0 (2026-06-23)

本版本定位为 HA & Manager 化 release:高可用与生产就绪改进 + 组件 Manager 化。含少量破坏性变更,升级前请阅读 CHANGELOG 升级说明

  • 组件 Manager 化 - storage/cache/redis/jwt/logger 五组件新增 XxxManager + SetDefaultXxxManager,包级 facade 保留兼容,支持多实例与测试注入
  • Lifecycle Hooks - WithHook(Hook{OnInit/OnStart/OnReady/OnStop}) 生命周期钩子
  • App.Go - 受管理的后台 goroutineShutdown 时 cancel + 等待退出
  • Server 配置化 - server 新增 timeout/TLS/unix_socket/response_mode 等字段
  • JWTConfig time.Duration - jwt.expire"24h",新增 issuer/algorithm
  • Config Validate - 启动期校验配置(端口/密钥/必填字段),错误前置
  • response REST 模式 - SetMode(ModeREST) 按错误码映射 HTTP status
  • livez/readyz - K8s 友好的存活性/就绪性探针
  • Prometheus metrics - /metrics + middleware.Metrics() 采集请求指标
  • 请求级 Timeout - middleware.Timeout(d) 级联取消下游
  • 依赖健康自愈 - 主库探活 + replica 健康剔除,/readyz 联动 503
  • RequestID 默认装入 - 每个响应/panic 日志都带 request_id

升级:go get github.com/EthanCodeCraft/xlgo-core@v1.1.0⚠️ 含破坏性变更,见升级说明)

v1.0.4 (2026-06-22)

本版本定位为 DX & Docs release:开发体验与文档改进,无破坏性 API 变更。

  • CLI 多模板 - xlgo new --template minimal/api/fullstack,支持轻量 HTTP / 标准业务 / 全组件三种脚手架
  • examples/ 目录 - 新增 examples/minimal(无依赖可跑)与 examples/fullmysql+redis+jwt+user CRUD)可运行示例
  • 模块路径文档 - 明确说明 module path xlgo-core 与 package name xlgo 的关系,补完整 import 示例(保留 -corexlgo 多产品系列)
  • Without 定位* - 文档化 Without* Option 主要用于 NewFullStack 后排除单项
  • 文档结构 - 规划文档归档到 docs/plans/,新增 docs/README.md 索引

升级:go get github.com/EthanCodeCraft/xlgo-core@v1.0.4(无破坏性变更)

v1.0.3 (2026-06-22)

本版本定位为 bug fix release:收口 v1.0.2 引入的破坏性清理,并修复 4 个轻量 bug + 依赖复查。完整说明见 CHANGELOG.md#unreleased

🐛 Fixed — JWT JTI 生成忽略 rand.Read 错误

generateJTI() 丢弃 crypto/rand.Read 的 error,失败时会基于全零字节生成 JTI,导致所有 token 的 JTI 相同、黑名单机制失效。改为 (string, error) 并在 GenerateToken / GenerateTokenWithCustomExpiry 传播错误。

🐛 Fixed — QueryBuilder.Page 统计行数被残留 Limit 截断

Page() 复制查询做 Count 时未清除残留 Limit/Offset,调用方先 .Limit(n).Page(...) 会让 Count 被包成子查询,返回 total 被截断为 ≤ n。countDB 改为 .Limit(-1).Offset(-1) 清除残留条件。

🐛 Fixed — OSS / 本地存储文件名冲突

4 处上传路径仅用 time.Now().UnixNano() 命名,同纳秒并发上传会撞名覆盖。新增 uniqueFilename(now, ext)<unixNano>-<8字节crypto/rand hex>.<ext>),4 处统一改用。

🐛 Fixed — 数据库重试策略对不可恢复错误无效

InitDB 对认证失败(Access denied)、未知数据库(Unknown database)、非法 DSN、未注册驱动等配置类错误也退避重试 5 次,白白延迟 31 秒。新增 isTransientDBError,这类错误首次出现即返回;网络类错误仍正常重试。

📦 Dependencies — go mod tidy + 安全补丁升级

  • go mod tidy 补全 postgres 方言传递依赖(jackc/pgx 家族、golang.org/x/sync),gorm.io/driver/postgres 提升为直接依赖
  • 安全补丁升级(无 API 变更):golang.org/x/crypto v0.49→v0.53、golang-jwt/jwt/v5 v5.2.1→v5.3.1、gorilla/websocket v1.5.1→v1.5.3
  • 暂缓升级(留待 v1.0.4 / v1.1):gin / validator / gorm / aliyun-oss-go-sdk v2→v3(major 破坏性)等跨版本升级

⚠️ Breaking — 清理 v1.0.2 兼容别名(database 包)

// ❌ 移除
database.InitMySQL(cfg)
database.InitMySQLWithReplicas(cfg, replicas)
(*Manager).InitMySQL / InitMySQLWithReplicas

// ✅ 改用(v1.0.2 起就是正式 API,驱动由 cfg.Database.Driver 决定)
database.InitDB(cfg)
database.InitDBWithReplicas(cfg, replicas)

xlgo 仍是早期框架,趁此一次彻底清理 v1.0.2 临时保留的别名,避免长期累积技术债。

🗑️ Removed — 删除死代码 database.DBResolver

database.DBResolver.BeforeQuery 从未被注册到 GORM callback chain,属于纯死代码。文档曾暗示的"自动读写分离"实际从未生效——读写分离一直依赖业务侧显式调用 database.UseMaster(ctx) / database.UseReplica(ctx)

需要 callback 级自动路由的用户请直接接入官方 gorm.io/plugin/dbresolver

🔄 Changed — 文件重命名 database/mysql.go → database/manager.go

文件内容已与 MySQL 解耦(v1.0.2 引入可插拔方言注册表后),继续叫 mysql.go 误导。导入路径无变化,公开 API 全部保留。

Added — console 包显式 level 控制

console 包新增显式级别屏蔽能力:

console.SetLevel(console.LevelWarn)    // 只看 Warn / Error
console.SetLevel(console.LevelSilent)  // 完全静默

定位明确console 是开发期彩色 stdout 工具(跟 fmt.Println 同级),不写文件、不感知环境。业务可观测信息请使用 logger。框架不会自动根据 app.env 切换级别——选择权完全在调用方,避免"dev / prod 行为不一致"的隐式陷阱。

完整对比表见 GUIDE.md §3.3

🐛 Fixed — Logger 重复写入修复

修复通用 LoggerapiCoredbCore 都 Tee 进来导致每条日志写三份的 bug。

  • Logger(通用)→ logs/app.log + console
  • APILog()logs/api.log + console
  • DBLog()logs/database.log + console
  • 互不串扰,磁盘体积砍掉 2/3

新增logger.Close() 显式关闭文件句柄,App.Shutdown 已自动调用;Init(nil) 改为返回 error 而非 panic;生产默认级别从 Warn 调整为 Info

升级注意:日志目录会新增 logs/app.log 文件(之前通用日志被串写进了 api.log/database.log),运维采集脚本如有需要请补上。

🔒 Security — CORS 中间件修复

修复 CORS 中间件多个安全与规范遵守问题:

  • Access-Control-Allow-Credentials 永远是 true — 旧实现 if/else 两个分支都设了 "true",导致即使配置 AllowCredentials=false 也会发送凭证头
  • * + credentials: true 的规范违规 — 同时发送会被浏览器拒绝;修复后此场景回显具体 Origin
  • 缺失 Vary: Origin — 防止 CDN / 网关把 A 用户的 CORS 响应缓存给 B 用户
  • 非白名单 Origin 不再被回显,防反射型 CORS 漏洞

升级影响:如果你之前依赖"默认允许凭证"的隐式行为,需要在配置里显式启用:

cors:
  allowed_origins: ["https://your-frontend.example"]
  allow_credentials: true

⚠️ Breaking — 错误码体系重构

修复 CodeSuccessCodeInvalidParams 撞码的生产级 bug(两者都等于 1,导致业务错误响应被前端误判为成功)。

数值变更

常量 旧值 新值
response.CodeSuccess 1 0
response.CodeFail 0 1

移除

  • response.CodeInvalidParams(与 CodeSuccess 撞码,且参数错误码应由业务自定义)
  • response.ErrInvalidParams

迁移指南

// ❌ 旧代码(编译失败)
response.FailWithError(c, response.ErrInvalidParams)

// ✅ 推荐:业务侧自定义错误码
var ErrInvalidParams = response.NewError(40001, "参数错误")
response.FailWithError(c, ErrInvalidParams)

// ✅ 或直接使用通用失败响应
response.Fail(c, "用户名格式错误")

前端if (resp.code === 1)if (resp.code === 0)

新增 _errorCodeUniquenessGuard 编译期防撞码 map,任何后续 Code* 常量重复都会在 go build 阶段直接报错。

详细迁移说明见 CHANGELOG.md

v1.0.2 (2026-06-20)

数据库

  • 可插拔方言注册表 - 新增 database.RegisterDialect / LookupDialect / RegisteredDialects,应用可一次注册即让 database.driver: <name> 生效,DSN 构建器同步登记到 config 包;内置 mysqlpostgres(含 postgresqlpg 别名),未注册驱动回退 MySQL
  • 多数据库驱动 - database.driver 支持 mysql(默认)与 postgres,新增 database.InitDB / InitDBWithReplicasv1.0.3 起原 InitMySQL* 别名已移除)
  • 数据库管理器 - 引入实例化 database.Manager 持有主从连接,提供 Master/Replica/FromContext/HealthCheck 等方法
  • 从库选择策略 - 新增 ReplicaPicker 接口与 RoundRobinPicker / RandomPicker 实现,可通过 SetReplicaPicker 自定义
  • 私有上下文键 - db_mode 字符串键替换为私有 dbModeContextKey{} 类型,避免上下文键名冲突
  • DSN 构建器注册表 - config 包新增 RegisterDSNBuilder / LookupDSNBuilder / RegisteredDriversDatabaseConfig.DSN() 改为查注册表,自定义 CustomDSN 优先

App 启动流程

  • 轻量默认 - xlgo.New() 默认不再初始化 MySQL / Redis / Storage,也不注册 /health/swagger/*;通过 Option 显式启用
  • 组件 Option - 新增 WithLogger / WithMySQL / WithRedis / WithStorage / WithHealthRoutes / WithSwaggerRoutes / WithDefaultRoutes / WithAutoMigrate 及对应 WithoutXxx 关闭项(注:WithWire 曾在此版本新增,已于 v1.1.0 随 wire 包删除而移除)
  • 迁移控制 - 新增 Migrator 类型与 WithMigrator / WithModels,注册时自动开启 WithAutoMigrateWithoutAutoMigrate 可显式关闭;不再强制调用空的 database.AutoMigrate()
  • batteries-included - 新增 WithFullStack / NewFullStack / RunFullStack,一键启用全部默认组件
  • 错误传播 - 框架初始化失败一律 return error,不再在框架内部直接 Fatalf 退出进程

权限中间件

  • 去业务化 - super_admin/admin/staff 调整为默认常量而非固定业务模型
  • 通用能力 - 新增 AuthUser 结构体、GetAuthUserRequireUserTypes / RequireRoles / RequireAuth,旧的 AdminRequired/SuperAdminRequired/StaffRequired/AnyUserRequired 改为基于通用能力实现

配置管理

  • 实例化 Manager - 新增 config.Manager,提供 Load / LoadWithWatch / Reload / RegisterCallback / Get / GetViper / Set 等方法,支持多实例与重复加载
  • App 持有 Manager - WithConfigPath 创建 App 私有 manager 并通过新增的 config.SetDefaultManager 推为全局默认,使 config.Get / GetString 等便捷函数仍然可用
  • 修复 - 修复 WithConfigPath 此前的空实现问题

健康检查与默认路由

  • 拆分注册 - router 包新增 RegisterHealthRoute / RegisterSwaggerRoutes,原 RegisterDefaultRoutes 改为组合调用
  • 检查项与状态 - /health 支持注册 HealthCheck,失败返回 HTTP 503 与 {"status":"error", "checks":{...}}
  • App 集成 - 启用 MySQL / Redis 时自动追加对应健康检查项,可通过 WithHealthCheck 注册自定义检查

资源关闭

  • Shutdown 修复 - 改为 database.CloseAll() 关闭主从连接,并使用 errors.Join 聚合限流器、日志、Redis 等组件的关闭错误
  • Storage 可选化 - 未初始化 Storage 时返回 ErrStorageNotInitialized,不再 nil panic

文档与 CLI

  • CLI 修复 - xlgo version 更新为 v1.0.2,脚手架模板改为依赖 github.com/EthanCodeCraft/xlgo-core 并在注释中提示 Swagger / MySQL / Redis / FullStack 的开启方式
  • GUIDE 同步 - 1.3 最简示例、3.2 日志注释、8.4.7 默认路由、9.2 用户信息获取均更新为 v1.0.2 行为
  • 历史日志修正 - 修复此前 README 中错误的 v2.0.0 / v2.1.0 更新日志表述

v1.0.1 (2026-04-30)

  • 新增工具函数库、彩色控制台输出、压缩解压、RequestID、Recover 中间件
  • 新增缓存键名前缀、分布式锁、计数器、Redis 分布式限流
  • 增强 JWT 黑名单、Repository、CORS、日志中间件和优雅关闭能力
  • 新增路由架构,支持模块化、版本化 API、中间件分组和 RESTful CRUD
  • 完善配置热更新、数据库读写分离、CSRF、SSE、WebSocket、定时任务、CLI、测试工具和统一错误码

v1.0.0 (2024-04)

  • 初始版本发布
  • 基础框架功能
  • 完整示例代码

许可证

MIT License - 详见 LICENSE 文件

S
Description
基于 Go 和 Gin、组件 Manager 化的轻量 Web 开发框架
Readme 864 KiB
Languages
Go 100%