v1.0.3 定位为 bug fix release,收口 v1.0.2 引入的破坏性清理并修复 4 个轻量 bug + 依赖复查 + 版本号治理 + 文档对齐。同时包含此前未提交 的 v1.0.2 全部工作。 v1.0.3 变更: Fixed: - generateJTI 忽略 rand.Read 错误(jwt)— 改为 (string, error) 并传播 - QueryBuilder.Page Count 受残留 Limit 截断(repository)— countDB 加 Limit(-1).Offset(-1) - OSS/本地存储文件名冲突(storage)— 新增 uniqueFilename 加 8 字节随机后缀 - 数据库重试策略对不可恢复错误无效(database)— 新增 isTransientDBError Dependencies: - go mod tidy 补全 postgres 方言传递依赖;安全补丁升级 x/crypto v0.49→v0.53、golang-jwt/jwt/v5 v5.2.1→v5.3.1、gorilla/websocket v1.5.1→v1.5.3 Removed: - Breaking: 清理 v1.0.2 兼容别名(InitMySQL* / driverName) - 死代码 database.DBResolver - 代码中 292 行"评分/理由"自夸注释(#26,23 个文件) Changed: - Breaking: 错误码体系重构 CodeSuccess 1→0、CodeFail 0→1,删除 CodeInvalidParams,加编译期防撞码 - database/mysql.go → manager.go;Logger 拆分三独立 core 修复 Tee 重复写入 - 版本号常量化:app.go 新增 const Version 作为唯一来源,CLI/脚手架模板引用之,不再散落字面量 Security: - CORS 中间件按 W3C 规范修复 Allow-Credentials / Vary: Origin / 非白名单不回显 Added: - console 包显式 level 控制(SetLevel/WithLevel/LevelSilent,atomic.Int32 并发安全) - 新增测试 jwt/repository/storage/database/router/middleware/app - 文档:新增 CHANGELOG.md、Version_v1.0.2_report.md;更新 README/GUIDE 顶部更新日志 - 文档对齐:删除对不存在的 config.DefaultManager() getter 的描述(保持 API 与文档一致) 详见 CHANGELOG.md 与 README.md 更新日志。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
42 KiB
xlgo 框架使用指南
目录
- 快速开始
- 配置管理
- 日志系统
- 数据库操作
- 缓存与 Redis
- 请求处理
- 响应与错误处理
- 中间件
- 认证与授权
- 文件存储
- 工具函数库
- 实时通信
- 定时任务
- 链路追踪
- 压缩解压
- 测试工具
- CLI 脚手架
- 最佳实践
1. 快速开始
环境要求:xlgo 需要 Go 1.25+。本项目作为新框架不背负旧版本兼容包袱,可直接使用 Go 1.25 的新特性。
1.1 安装框架
# 创建新项目
xlgo new myproject
# 或手动安装
go get github.com/EthanCodeCraft/xlgo-core
1.2 项目结构
myproject/
├── config.yaml # 配置文件
├── main.go # 入口文件
├── handler/ # 请求处理器
├── model/ # 数据模型
├── repository/ # 数据仓库
├── service/ # 业务逻辑
├── middleware/ # 自定义中间件
├── public/ # 静态文件
├── logs/ # 日志目录
└── go.mod
1.3 最简示例
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() {
// v1.0.2 起 xlgo.New 默认是轻量应用,不会自动初始化 MySQL/Redis/Storage,
// 也不会自动注册 /health 或 /swagger/* 路由。
// 通过 Option 显式启用所需组件即可。
app := xlgo.New(
xlgo.WithConfigPath("./config.yaml"),
xlgo.WithLogger(),
xlgo.WithDefaultRoutes(), // 同时启用 /health 与 /swagger/*
xlgo.WithMiddlewares(middleware.Logger(), middleware.CORS()),
)
// 通过 Engine 直接挂路由,或使用 xlgo.WithModules(...) 注册模块
app.GetRouter().GET("/hello", func(c *gin.Context) {
response.Success(c, gin.H{"message": "Hello World"})
})
if err := app.Run(); err != nil {
panic(err)
}
}
想要 batteries-included 体验,可改用
xlgo.NewFullStack(...)或xlgo.RunFullStack(...),它会一次性启用 Logger / MySQL / Redis / Storage / Wire / Health / Swagger / AutoMigrate 等组件。
优雅关闭特性:
- 监听系统信号(SIGINT、SIGTERM)
- 等待请求处理完成(最多 30 秒)
- 按顺序关闭各组件(限流器、数据库、Redis、日志)
2. 配置管理
2.1 配置文件结构
# config.yaml
app:
name: "我的应用"
site_name: "my_app" # 站点别名(重要:多项目共用Redis时区分)
version: "1.0.0"
env: "dev" # dev/test/prod
debug: true
base_url: "http://localhost:8080"
token_expire: 86400 # Token过期时间(秒)
server:
port: 8080
mode: development # development/production
database:
host: localhost
port: 3306
user: root
password: your_password
name: mydb
max_idle_conns: 10
max_open_conns: 100
redis:
host: localhost
port: 6379
password: ""
db: 0
jwt:
secret: your_jwt_secret_key
expire: 86400
log:
dir: ./logs
max_size: 100 # MB
max_backups: 30
max_age: 30 # 天
compress: true
2.2 使用配置
import "github.com/EthanCodeCraft/xlgo-core/config"
// 加载配置
cfg, err := config.Load("./config.yaml")
// 获取配置值
port := cfg.Server.Port
dbHost := cfg.Database.Host
// 获取站点别名(用于缓存键前缀)
siteName := cfg.GetSiteName()
// 判断环境
if cfg.IsProduction() {
gin.SetMode(gin.ReleaseMode)
}
// 热更新配置
config.LoadWithWatch("./config.yaml", func(newCfg *config.Config) {
// 配置变更时的回调
})
// 动态获取配置值
debug := config.GetBool("app.debug")
3. 日志系统
3.1 初始化日志
import "github.com/EthanCodeCraft/xlgo-core/logger"
// 初始化
logger.Init(cfg)
// 程序退出前同步
// v1.0.2 起 Sync 返回 error,可按需处理
_ = logger.Sync()
3.2 日志级别
// 基础日志
logger.Debug("调试信息")
logger.Info("普通信息")
logger.Warn("警告信息")
logger.Error("错误信息")
// 格式化日志
logger.Infof("用户 %s 登录成功", username)
logger.Warnf("请求失败: %v", err)
// 致命错误(会终止程序)—— 仅在业务进程入口(main 等)使用
// v1.0.2 起,框架内部已禁止使用 Fatal/Fatalf,初始化错误改为 error 返回
logger.Fatalf("数据库连接失败: %v", err)
// 结构化日志
logger.Info("用户登录",
zap.String("username", "admin"),
zap.Int("user_id", 123),
)
3.3 彩色控制台输出
console 包定位是开发期彩色 stdout 工具——跟 fmt.Println 同级,不写文件、不感知运行环境、不做任何隐式行为。
import "github.com/EthanCodeCraft/xlgo-core/console"
console.Debug("调试信息") // 青色
console.Info("普通信息") // 白色
console.Success("成功信息") // 绿色
console.Warn("警告信息") // 黄色
console.Error("错误信息") // 红色
// 自定义配置
c := console.New(
console.WithColor(true),
console.WithTime(true),
console.WithCaller(true), // skip 可选,默认 2
console.WithLevel(console.LevelInfo),
)
c.Debug("此条不会输出,已被 LevelInfo 过滤")
c.Info("自定义输出")
console vs logger:怎么选?
| 维度 | console |
logger |
|---|---|---|
| 定位 | 开发期肉眼调试 | 业务可观测性记录 |
| 输出目标 | stdout(彩色) | 文件 + stdout |
| 持久化 | ❌ | ✅ 滚动归档 |
| 结构化 | 文本 | JSON 字段 |
| 性能 | 一般 | zap 高性能 |
| 默认级别 | LevelDebug(全开) |
dev=Debug / prod=Info |
| 适用场景 | 临时打印、开发联调 | 用户登录、订单事件、审计日志 |
简单判断:
- 这条信息上线后想留 → 用
logger - 这条信息上线就该消失 → 用
console,并在 main 中显式切到高级别
上线前显式收紧 console 输出
func main() {
cfg, _ := config.Load("./config.yaml")
// 显式选择:生产期 console 只看 Warn / Error
if cfg.IsProduction() {
console.SetLevel(console.LevelWarn)
}
// 或者完全静默 console(业务全靠 logger)
// console.SetLevel(console.LevelSilent)
app := xlgo.New(...)
app.Run()
}
注意:xlgo 不会自动根据
app.env切换 console 级别——选择权完全在调用方。 我们认为隐式切换会带来"开发期看到的 / 生产期看到的"行为不一致,调试体验更糟。
4. 数据库操作
xlgo 基于 GORM,驱动由配置 database.driver 决定。v1.0.2 起 GORM 方言通过 可插拔注册表 管理:内置 mysql(默认)与 postgres(含 postgresql、pg 别名),应用可通过 database.RegisterDialect 接入任意 GORM 驱动;database.dsn 字段始终可用于手写连接串。
4.1 初始化数据库
import "github.com/EthanCodeCraft/xlgo-core/database"
// 初始化数据库(驱动由配置决定,等价于 database.DefaultManager.InitDB(cfg))
database.InitDB(cfg)
// 关闭全部连接(含从库)
defer database.CloseAll()
// 获取数据库实例
db := database.GetDB() // 主库
read := database.GetReadDB() // 从库(无从库时回退主库)
4.1.1 主从读写分离
// 从库 DSN 列表需与主库驱动匹配
database.InitDBWithReplicas(cfg, []string{
"root:pass@tcp(slave1:3306)/db",
"root:pass@tcp(slave2:3306)/db",
})
// 选择策略:默认 Random,可换成 RoundRobin
database.SetReplicaPicker(&database.RoundRobinPicker{})
// 强制使用主库(事务、需要实时数据的场景)
ctx := database.UseMaster(context.Background())
database.GetDBFromContext(ctx).Find(&users)
// 强制使用从库(报表查询)
ctx = database.UseReplica(context.Background())
database.GetDBFromContext(ctx).Find(&reports)
4.1.2 实例化 Manager
需要多套数据库连接(如平台库 + 租户库)时,database.NewManager(cfg) 创建独立管理器,互不影响:
mgr := database.NewManager(cfg)
if err := mgr.Open(context.Background()); err != nil {
return err
}
defer mgr.Close()
mgr.SetPicker(&database.RoundRobinPicker{})
db := mgr.FromContext(ctx)
4.1.3 注册自定义 GORM 驱动
database.RegisterDialect 一次注册即让 database.driver: <name> 生效,DSN 构建器同步登记到 config 包:
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.driver: sqlite 即可。SQL Server / ClickHouse / TiDB 等同理。
诊断接口:
database.RegisteredDialects()- 列出已注册驱动(含别名)database.LookupDialect(name)- 检查某驱动是否可用config.RegisteredDrivers()- 列出已登记的 DSN 构建器
未注册的驱动会回退到 MySQL 以保持向后兼容。
4.2 定义模型
import "github.com/EthanCodeCraft/xlgo-core/model"
type User struct {
model.BaseModel // 包含 ID, CreatedAt, UpdatedAt
Username string `gorm:"size:50;unique" json:"username"`
Email string `gorm:"size:100" json:"email"`
Password string `gorm:"size:255" json:"-"`
Status int `gorm:"default:1" json:"status"`
}
func (User) TableName() string {
return "users"
}
4.3 使用 Repository(扩展版)
import "github.com/EthanCodeCraft/xlgo-core/repository"
// 创建仓库
userRepo := repository.NewBaseRepo[model.User](database.GetDB())
// 基础 CRUD
user, err := userRepo.FindByID(ctx, 1)
users, err := userRepo.FindAll(ctx)
users, err := userRepo.FindByIDs(ctx, []uint{1, 2, 3})
err := userRepo.Create(ctx, &user)
err := userRepo.Update(ctx, &user)
err := userRepo.Delete(ctx, 1)
// 统计数量
count, err := userRepo.Count(ctx)
count, err := userRepo.CountWhere(ctx, "status = ?", 1)
// 分页查询(内置)
result, err := userRepo.FindPage(ctx, 1, 20)
// result.Items - 数据列表
// result.Total - 总数
// result.Page - 当前页
// result.PageSize - 每页数量
// 条件分页查询
result, err := userRepo.FindPageWhere(ctx, 1, 20, "status = ?", 1)
// 分页并排序
result, err := userRepo.FindPageOrdered(ctx, 1, 20, "created_at DESC")
// 条件查询
user, err := userRepo.FindOne(ctx, "email = ?", "test@example.com")
users, err := userRepo.FindWhere(ctx, "status = ?", 1)
users, err := userRepo.FindWhereOrdered(ctx, "status = ?", 1, []any{}, "created_at DESC")
// 排序查询
users, err := userRepo.FindOrdered(ctx, "created_at DESC", 10)
// 批量操作
err := userRepo.CreateBatch(ctx, users)
err := userRepo.UpdateBatch(ctx, []uint{1, 2}, "status", 2)
err := userRepo.DeleteBatch(ctx, []uint{1, 2, 3})
// 存在性检查
exists, err := userRepo.Exists(ctx, 1)
exists, err := userRepo.ExistsWhere(ctx, "email = ?", "test@example.com")
// 软删除恢复
err := userRepo.Restore(ctx, 1)
err := userRepo.RestoreBatch(ctx, []uint{1, 2})
// 查询已删除的记录
deletedUsers, err := userRepo.FindDeleted(ctx)
// 链式查询(灵活构建)
users, err := userRepo.NewQueryBuilder().
Where("status = ?", 1).
Where("role = ?", "admin").
Order("created_at DESC").
Limit(10).
Find(ctx)
// 链式分页查询
result, err := userRepo.NewQueryBuilder().
Where("status = ?", 1).
Order("created_at DESC").
Page(ctx, 1, 20)
// 事务支持
err := userRepo.WithTransaction(ctx, func(txRepo *repository.BaseRepo[model.User]) error {
// 创建用户
if err := txRepo.Create(ctx, &user); err != nil {
return err
}
// 更新关联数据
return txRepo.Update(ctx, &profile)
})
4.4 Model 基础模型
import "github.com/EthanCodeCraft/xlgo-core/model"
// BaseModel 包含 ID、CreatedAt、UpdatedAt、DeletedAt
type User struct {
model.BaseModel // 自动包含 ID、时间戳、软删除
Username string `gorm:"size:50;unique" json:"username"`
Email string `gorm:"size:100" json:"email"`
Status int `gorm:"default:1" json:"status"`
}
// BaseModel 字段说明
// ID uint - 主键
// CreatedAt time.Time - 创建时间
// UpdatedAt time.Time - 更新时间
// DeletedAt gorm.DeletedAt - 软删除时间(不返回给前端)
5. 缓存与 Redis
5.1 初始化缓存
import "github.com/EthanCodeCraft/xlgo-core/cache"
// 初始化(通常在应用启动时)
cache.Init()
// 获取缓存实例
c := cache.GetCache()
5.2 缓存操作
// 设置缓存
c.Set(ctx, "user:1", userData, 30*time.Minute)
// 获取缓存
var user User
if c.Get(ctx, "user:1", &user) {
// 缓存命中
}
// 删除缓存
c.Delete(ctx, "user:1")
// 批量删除(按模式)
c.DeleteByPattern(ctx, "user:*")
// 检查是否存在
exists := c.Exists(ctx, "user:1")
5.3 键名前缀管理(多站点共用 Redis)
import "github.com/EthanCodeCraft/xlgo-core/cache"
// 自动从配置读取 site_name 作为前缀
// config.yaml: app.site_name: "my_app"
cache.K("user:1") // → "cache:my_app:user:1"
cache.KTemp("token") // → "temp:my_app:token"
cache.KPerm("config") // → "perm:my_app:config"
cache.KLock("order:123") // → "lock:my_app:order:123"
cache.KCounter("visit") // → "counter:my_app:visit"
cache.KSession("sid") // → "session:my_app:sid"
// 使用带前缀的缓存
c.Set(ctx, cache.K("user:1"), userData, ttl)
c.Get(ctx, cache.K("user:1"), &user)
5.4 分布式锁(安全增强版)
使用 Lua 脚本 + UUID Token,只有锁的持有者才能释放锁:
// 安全加锁(返回 LockToken)
token, err := cache.NewLock(ctx, cache.KLock("pay:123"), 30*time.Second)
if token != nil {
defer cache.Unlock(ctx, token) // 只有持有者能释放
// 执行业务逻辑
}
// 带重试的锁
token, err := cache.TryLock(ctx, key, ttl, 100*time.Millisecond, 10)
if token != nil {
defer cache.Unlock(ctx, token)
}
// 自动管理锁(简单场景)
err := cache.WithLock(ctx, key, ttl, func() error {
return nil
})
// 自动续期锁(长任务场景)
err := cache.WithLockAutoExtend(ctx, key, 30*time.Second, 10*time.Second, func() error {
// 执行时间超过 TTL 时自动续期
return nil
})
// 续期锁(手动续期)
cache.ExtendLock(ctx, token, 30*time.Second)
// 检查锁是否被占用
locked, err := cache.IsLocked(ctx, key)
// 强制释放锁(管理场景)
cache.ForceUnlock(ctx, key)
安全特性:
- Lua 脚本保证原子性操作
- UUID Token 标识锁的持有者
- 只有持有者才能释放锁(防止误释放)
- 支持续期(长任务不会因锁过期而中断)
5.5 计数器
// 自增
n, err := cache.Incr(ctx, cache.KCounter("page_view"))
// 指定增量
n, err := cache.IncrBy(ctx, key, 10)
// 自减
n, err := cache.Decr(ctx, key)
// 设置过期时间
cache.SetExpire(ctx, key, 1*time.Hour)
// 获取剩余时间
ttl, err := cache.GetTTL(ctx, key)
6. 请求处理
6.1 参数获取
import "github.com/EthanCodeCraft/xlgo-core/handler"
// 类型安全的参数获取(推荐)
// Query参数
page := handler.QueryInt(c, "page", 1) // Query参数 → int
id := handler.QueryInt64(c, "id", 0) // Query参数 → int64
price := handler.QueryFloat64(c, "price", 0.0) // Query参数 → float64
enabled := handler.QueryBool(c, "enabled", false) // Query参数 → bool
// 路径参数(RESTful API)
id := handler.PathInt(c, "id", 0) // 路径参数 → int
id := handler.PathInt64(c, "id", 0) // 路径参数 → int64
id := handler.PathUint64(c, "id", 0) // 路径参数 → uint64
// 表单参数(POST提交)
count := handler.FormInt(c, "count", 0) // 表单参数 → int
id := handler.FormInt64(c, "id", 0) // 表单参数 → int64
id := handler.FormUint64(c, "id", 0) // 表单参数 → uint64
price := handler.FormFloat64(c, "price", 0.0) // 表单参数 → float64
enabled := handler.FormBool(c, "enabled", false) // 表单参数 → bool
name := handler.FormString(c, "name", "") // 表单参数 → string
// 分页参数
page, pageSize := handler.GetPage(c) // 默认 page=1, pageSize=20
// 路径ID
id, ok := handler.GetIDFromPath(c, "id")
// 绑定请求
var req LoginRequest
handler.BindJSON(c, &req)
6.2 请求验证
import "github.com/EthanCodeCraft/xlgo-core/validation"
type RegisterRequest struct {
Username string `json:"username" label:"用户名" validate:"required,min=3,max=20" msg_required:"用户名不能为空"`
Password string `json:"password" label:"密码" validate:"required,password" msg_required:"密码不能为空"`
Email string `json:"email" label:"邮箱" validate:"required,email" msg_required:"邮箱不能为空"`
Phone string `json:"phone" label:"手机号" validate:"omitempty,phone"`
}
// 绑定并验证
errors, ok := validation.ShouldBindAndValidate(c, &req)
if !ok {
response.Fail(c, errors.Error())
return
}
6.3 验证规则
| 规则 | 说明 |
|---|---|
required |
必填 |
min=3 |
最小长度 |
max=20 |
最大长度 |
email |
邮箱格式 |
phone |
手机号格式 |
password |
密码强度(至少 8 位,包含字母和数字) |
url |
URL 格式 |
ip |
IP 地址格式 |
7. 响应与错误处理
7.1 统一响应格式
{
"code": 1,
"msg": "操作成功",
"data": {},
"request_id": "abc123"
}
7.2 成功响应
import "github.com/EthanCodeCraft/xlgo-core/response"
// 基础成功响应
response.Success(c, gin.H{"user": user})
// 自定义消息
response.SuccessWithMsg(c, "注册成功", gin.H{"user_id": 123})
// 分页响应
response.Page(c, users, total, page, pageSize)
7.3 错误响应
// 基础失败
response.Fail(c, "参数错误")
// 自定义错误码
response.FailWithCode(c, response.CodeUserNotFound, "用户不存在")
// 使用预定义错误
response.FailWithError(c, response.ErrUserNotFound)
// 带详细信息
response.FailWithDetail(c, response.ErrPasswordWrong, "连续错误3次将锁定")
7.4 预定义错误码
注:参数错误等业务化错误码请由业务项目自行定义,框架不再内置
ErrInvalidParams。 推荐使用response.NewError(40001, "参数错误")在业务侧统一管理。
| 错误 | 码 | 说明 |
|---|---|---|
ErrUnauthorized |
401 | 未授权 |
ErrForbidden |
403 | 无权限访问 |
ErrNotFound |
404 | 资源不存在 |
ErrRateLimit |
429 | 请求过于频繁 |
ErrServerError |
500 | 服务器错误 |
ErrUserNotFound |
10001 | 用户不存在 |
ErrPasswordWrong |
10004 | 密码错误 |
7.5 特殊响应
// 文件下载
response.Download(c, "report.xlsx", fileData)
// HTML响应
response.HTML(c, "<html>...</html>")
// 页面跳转
response.Redirect(c, 302, "https://example.com")
8. 中间件
8.1 内置中间件
import "github.com/EthanCodeCraft/xlgo-core/middleware"
// 日志中间件(默认配置)
r.Use(middleware.Logger())
// API 专用日志(记录请求体)
r.Use(middleware.LoggerForAPI())
// 调试日志(最详细,记录请求体和响应体)
r.Use(middleware.LoggerForDebug())
// 最简日志(只记录基本信息)
r.Use(middleware.LoggerMinimal())
// 自定义日志配置
r.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
LogRequestBody: true, // 记录请求体
LogResponseBody: false, // 不记录响应体
MaxBodyLength: 2048, // 最大记录长度
SkipPaths: []string{"/health"}, // 跳过路径
SlowRequestThreshold: 500 * time.Millisecond, // 慢请求阈值
}))
// CORS跨域
r.Use(middleware.CORS())
// 请求ID(追踪)
r.Use(middleware.RequestID())
// Panic恢复
r.Use(middleware.Recover())
// 认证中间件
r.Use(middleware.AuthRequired())
// 自定义用户类型权限
r.Use(middleware.RequireUserTypes("tenant_admin", "platform_admin"))
// 自定义角色权限
r.Use(middleware.RequireRoles("owner", "manager"))
// 默认快捷权限(super_admin/admin/staff 只是默认常量)
r.Use(middleware.AdminRequired())
// CSRF防护
r.Use(middleware.CSRF())
8.2 CORS 配置(支持配置文件)
# config.yaml 添加 CORS 配置
cors:
allowed_origins:
- "https://example.com"
- "https://app.example.com"
- "*.example.com" # 支持通配符
allowed_methods:
- "GET"
- "POST"
- "PUT"
- "DELETE"
allowed_headers:
- "Content-Type"
- "Authorization"
allow_credentials: true
max_age: 86400 # 预检请求缓存时间(秒)
// 使用配置文件的 CORS
r.Use(middleware.CORS())
// 指定域名列表
r.Use(middleware.CORSWithOrigins([]string{
"https://example.com",
"https://app.example.com",
}))
// 允许所有来源(仅开发环境)
r.Use(middleware.CORSWithWildcard())
// API 专用 CORS(不允许凭证)
r.Use(middleware.CORSForAPI())
生产环境 CORS 配置建议:
- 必须配置具体的
allowed_origins - 不使用
*通配符 - 根据需要配置
allow_credentials
8.3 CSRF 防护
// Cookie模式(Web应用)
r.Use(middleware.CSRF())
// 双重提交Cookie模式(无状态)
r.Use(middleware.DoubleSubmitCookie())
// API模式
r.Use(middleware.CSRFForAPI())
// 获取Token
token := middleware.GetCSRFToken(c)
8.3 限流(支持 Redis 分布式限流)
// 初始化限流器
middleware.InitRateLimiters()
// 内存限流(单实例)
r.POST("/login", middleware.LoginRateLimit(), handler.Login) // 每分钟10次
r.POST("/upload", middleware.UploadRateLimit(), handler.Upload) // 每分钟20次
r.Use(middleware.APIRateLimit()) // 每分钟100次
// 自定义内存限流
r.Use(middleware.CustomRateLimit(50, time.Minute)) // 每分钟50次
// Redis 分布式限流(多实例共享)
r.Use(middleware.RedisRateLimit("api_limit", 100)) // 每分钟100次
r.Use(middleware.LoginRedisRateLimit()) // 登录限流
r.Use(middleware.APIRedisRateLimit()) // API限流
r.Use(middleware.UploadRedisRateLimit()) // 上传限流
// 自定义 Redis 限流
r.Use(middleware.CustomRedisRateLimit("custom", 50, time.Minute))
// 自定义标识限流(如按用户ID)
r.Use(middleware.RedisRateLimitWithIdentifier("user_limit", 100, func(c *gin.Context) string {
return fmt.Sprintf("user:%d", middleware.GetUserID(c))
}))
// 停止限流器(应用关闭时)
defer middleware.StopRateLimiters()
内存限流 vs Redis 限流:
- 内存限流:单实例使用,简单高效
- Redis 限流:多实例共享,滑动窗口算法,分布式场景必需
8.4 路由架构
框架提供灵活的路由系统,支持模块化、版本化 API 和中间件分组。
8.4.1 模块化路由
import "github.com/EthanCodeCraft/xlgo-core/router"
// 方式一:实现 Module 接口
type UserModule struct{}
func (m *UserModule) Name() string { return "user" }
func (m *UserModule) Register(r *gin.RouterGroup) {
r.GET("/users", listUsers)
r.POST("/users", createUser)
r.GET("/users/:id", getUser)
}
// 方式二:函数式注册
router.ModuleFunc(func(r *gin.RouterGroup) {
r.GET("/users", listUsers)
})
// 使用
app := xlgo.New(
xlgo.WithModules(&UserModule{}),
)
8.4.2 版本化 API
// 单版本
v1 := router.NewVersion("v1", "/api/v1")
v1.AddModuleFunc("user", func(r *gin.RouterGroup) {
r.GET("/users", listUsersV1)
})
// 多版本共存
v1 := router.NewVersion("v1", "/api/v1")
v1.AddModuleFunc("user", func(r *gin.RouterGroup) {
r.GET("/users", listUsersV1) // 旧版本
})
v2 := router.NewVersion("v2", "/api/v2")
v2.AddModuleFunc("user", func(r *gin.RouterGroup) {
r.GET("/users", listUsersV2) // 新版本
r.GET("/users/:id/profile", getProfile) // 新增功能
})
app := xlgo.New(xlgo.WithVersions(v1, v2))
// 结果:
// GET /api/v1/users -> listUsersV1
// GET /api/v2/users -> listUsersV2
8.4.3 版本级中间件
v1 := router.NewVersion("v1", "/api/v1",
middleware.CustomRateLimit(100, time.Minute), // 版本级限流
middleware.AuthRequired(), // 版本级认证
)
v1.AddModuleFunc("user", userRoutes)
// 该版本所有路由自动应用限流和认证
8.4.4 中间件分组
// 创建分组
authGroup := router.NewMiddlewareGroup("auth",
middleware.AuthRequired(),
middleware.RequireUserTypes("tenant_admin", "platform_admin"),
)
publicGroup := router.NewMiddlewareGroup("public",
middleware.CustomRateLimit(1000, time.Minute),
)
// 注册分组
registry := router.NewRegistry(engine)
registry.RegisterMiddlewareGroup(authGroup)
registry.RegisterMiddlewareGroup(publicGroup)
// 使用分组
userGroup := router.GroupWithMiddlewareGroup(engine, "/users", "auth")
userGroup.GET("/:id", getUser)
8.4.5 RESTful CRUD
registry.RegisterModuleFunc("product", func(r *gin.RouterGroup) {
rest := router.NewRESTful(r, "/products")
// 一行注册标准 CRUD
rest.CRUD(
listProducts, // GET /products
getProduct, // GET /products/:id
createProduct, // POST /products
updateProduct, // PUT /products/:id
deleteProduct, // DELETE /products/:id
)
})
// 部分 CRUD(只注册需要的)
rest := router.NewRESTful(r, "/articles")
rest.CRUD(listArticles, getArticle, createArticle, nil, nil)
8.4.6 全局注册方式
engine := gin.New()
router.Init(engine)
// 全局中间件
router.Use(middleware.CORS(), middleware.Logger())
// 注册模块
router.RegisterModule(&UserModule{})
router.RegisterModuleFunc("health", func(r *gin.RouterGroup) {
r.GET("/health", healthCheck)
})
// 注册版本
router.RegisterVersion(router.NewVersion("v1", "/api/v1"))
// 应用路由
router.Apply()
xlgo.StartServer(engine, 8080)
8.4.7 默认路由
v1.0.2 起,xlgo.New() 默认是 轻量应用,不会自动注册任何默认路由。
按需通过下列 Option 显式启用:
xlgo.New(
xlgo.WithHealthRoutes(), // 注册 /health
xlgo.WithSwaggerRoutes(), // 注册 /swagger/*any
// 或一步到位:
xlgo.WithDefaultRoutes(), // 同时注册 /health 与 /swagger/*any
)
也可以使用 xlgo.NewFullStack(...) / xlgo.RunFullStack(...) 启用全部默认组件。
生产环境建议关闭 Swagger,仅保留
WithHealthRoutes(),避免文档接口意外暴露。
9. 认证与授权
9.1 JWT 使用(黑名单优化版)
使用 JTI(JWT ID)替代完整 Token 存储黑名单,大幅节省 Redis 内存:
import "github.com/EthanCodeCraft/xlgo-core/jwt"
// 生成Token(自动包含唯一 JTI)
token, err := jwt.GenerateToken(userID, username, "admin", "admin")
// 解析Token
claims, err := jwt.ParseToken(tokenString)
// 使Token失效(使用 JTI,内存占用约 30 字节)
jwt.InvalidateToken(tokenString)
// 获取 Token 的 JTI
jti, err := jwt.GetJTI(tokenString)
// 直接通过 JTI 撤销 Token
jwt.InvalidateTokenByID(jti, expiryTime)
// 自定义过期时间
token, err := jwt.GenerateTokenWithCustomExpiry(userID, username, "admin", "admin", 3600)
// 刷新Token(旧Token自动加入黑名单)
newToken, err := jwt.RefreshToken(oldToken)
// 获取已过期Token的信息(不验证过期)
claims, err := jwt.GetClaimsFromToken(tokenString)
黑名单优化说明:
- 每个Token自动生成唯一JTI(约24字节)
- 黑名单键名:
jwt_bl:{jti}(而非完整Token) - 内存节省:从数百字节降到约30字节每条记录
9.2 获取用户信息
// 一次性取出全部认证信息(v1.0.2 推荐)
if user, ok := middleware.GetAuthUser(c); ok {
_ = user.UserID
_ = user.Username
_ = user.Role
_ = user.UserType
}
// 单字段便捷函数(与旧版兼容)
userID := middleware.GetUserID(c)
username := middleware.GetUsername(c)
userType := middleware.GetUserType(c) // super_admin/admin/staff 等,由业务定义
9.3 密码加密
import "github.com/EthanCodeCraft/xlgo-core/validation"
// 加密密码
hash, err := validation.HashPassword("password123")
// 验证密码
if validation.CheckPassword(hash, "password123") {
// 密码正确
}
// 验证并自动升级成本
match, needUpgrade, newHash, err := validation.CheckPasswordAndUpgrade(hash, password, 12)
10. 文件存储
10.1 本地存储
storage:
driver: local
local:
path: ./public
base_url: http://localhost:8080/public
10.2 阿里云 OSS
storage:
driver: oss
oss:
endpoint: oss-cn-hangzhou.aliyuncs.com
bucket: my-bucket
access_key_id: your_key
access_key_secret: your_secret
base_url: https://my-bucket.oss-cn-hangzhou.aliyuncs.com
10.3 使用存储
import "github.com/EthanCodeCraft/xlgo-core/storage"
// 初始化
storage.Init(&cfg.Storage)
// 上传文件(从请求中获取)
file, _ := c.FormFile("file")
path, err := storage.Upload(file, "images")
// 获取访问 URL
url := storage.GetURL(path)
// 上传字节数据
path, err := storage.UploadFromBytes(data, "avatar.jpg", "images")
// 删除文件
err := storage.Delete(path)
// 获取文件内容
data, err := storage.Get(path)
// 检查文件是否存在
if storage.Exists(path) {
// 文件存在
}
11. 工具函数库
11.1 随机数生成
import "github.com/EthanCodeCraft/xlgo-core/utils"
// 随机字符串(16位)
token := utils.RandString(16)
// 随机数字(6位验证码)
code := utils.RandDigit(6)
// 范围随机数
n := utils.RandInt(1, 100)
11.2 字符串处理
// 检查空白
if utils.IsBlank(str) { }
// 批量检查
if utils.IsAnyBlank(name, email, phone) { }
// 默认值
name := utils.DefaultIfBlank(name, "未知")
// Unicode长度(中文)
len := utils.StrLen("你好世界") // 4
// 截取子串(支持中文)
sub := utils.Substr("你好世界", 0, 2) // "你好"
// 不区分大小写比较
if utils.EqualsIgnoreCase("Admin", "admin") { }
11.3 时间日期
// 时间戳
now := utils.NowUnix() // 秒
now := utils.NowTimestamp() // 毫秒
// 时间戳转时间
t := utils.FromUnix(unix)
t := utils.FromTimestamp(ms)
// 格式化
s := utils.FormatDateTime(t) // "2006-01-02 15:04:05"
s := utils.FormatDate(t) // "2006-01-02"
// 获取当天开始/结束
start := utils.StartOfDay(t)
end := utils.EndOfDay(t)
// 获取当月开始/结束
start := utils.StartOfMonth(t)
end := utils.EndOfMonth(t)
11.4 类型转换
// 字符串转数字
n := utils.ToInt("123")
n := utils.ToIntDefault("abc", 0) // 失败返回默认值
n := utils.ToInt64("123")
f := utils.ToFloat64("3.14")
// 分页计算
totalPages := utils.CalcPageCount(100, 10) // 10页
offset := utils.CalcOffset(2, 20) // 20
11.5 文件操作
// 检查存在
utils.FileExists(path)
utils.DirExists(path)
// 确保目录存在
utils.EnsureDir(path)
// 读写文件
data, err := utils.ReadFile(path)
err := utils.WriteFile(path, data)
err := utils.AppendFile(path, []byte("\nnew line"))
// 复制文件
err := utils.CopyFile(dst, src)
// 获取文件大小
size, err := utils.FileSize(path)
11.6 格式验证
// 手机号
if utils.IsPhone("13812345678") { }
// 邮箱
if utils.IsEmail("test@example.com") { }
// IPv4
if utils.IsIPv4("192.168.1.1") { }
// 身份证
if utils.IsIDCard(id) { }
// 纯数字
if utils.IsNumeric(str) { }
// 字母数字
if utils.IsAlphanumeric(str) { }
11.7 HTTP 客户端(连接池优化版)
import "github.com/EthanCodeCraft/xlgo-core/utils"
// 创建客户端(Transport 在初始化时创建,连接池可复用)
client := utils.NewHTTPClient()
// 自定义配置
client = utils.NewHTTPClientWithConfig(utils.HTTPClientConfig{
Timeout: 30 * time.Second,
MaxIdleConns: 100, // 最大空闲连接数
MaxIdleConnsPerHost: 10, // 每主机最大空闲连接
IdleConnTimeout: 90 * time.Second, // 空闲连接超时
})
// 链式配置
client.SetTimeout(30 * time.Second)
client.SetHeader("Authorization", "Bearer xxx")
client.SetSkipTLS(false) // 生产环境建议设为 false
// GET请求
data, err := client.Get(url, map[string]string{"page": "1"})
// POST表单
data, err := client.Post(url, map[string]string{"name": "test"})
// POST JSON
data, err := client.PostJSON(url, map[string]any{"name": "test"})
// PUT请求
data, err := client.Put(url, map[string]any{"id": 1})
// DELETE请求
data, err := client.Delete(url)
// 上传文件
files := []utils.UploadFile{
{FieldName: "file", FilePath: "/path/to/file"},
}
data, err := client.Upload(url, files, params)
// 从字节数据上传
data, err := client.UploadFromBytes(url, "file", "image.jpg", fileData, params)
// 自定义请求
data, err := client.Request("PATCH", url, bodyData)
// 关闭客户端(释放连接池资源)
client.Close()
HTTPClient 特性:
- Transport 初始化时创建,连接池可复用
- 支持连接池参数配置
- 全局默认客户端:
utils.DefaultHTTPClient() - 快捷函数:
utils.HTTPGet/HTTPPost/HTTPPostJSON
11.8 UUID
// UUID v4
uuid := utils.UUID() // "550e8400-e29b-41d4-a716-446655440000"
// 短UUID(无横线)
uuid := utils.UUIDShort() // "550e8400e29b41d4a716446655440000"
// 验证UUID
if utils.UUIDValid(uuid) { }
11.9 函数评估表
| 分类 | 高分函数(⭐⭐⭐⭐⭐) | 说明 |
|---|---|---|
| 随机 | RandString/RandDigit |
sync.Pool 优化性能 |
| 字符串 | IsBlank/DefaultIfBlank/StrLen |
空值处理、Unicode支持 |
| 时间 | FormatDateTime/StartOfDay/EndOfMonth |
标准格式、边界计算 |
| 转换 | ToIntDefault/CalcPageCount/CalcOffset |
安全转换、分页计算 |
| 文件 | FileExists/DirExists/EnsureDir/CopyFile |
路径检查、目录创建 |
| 验证 | IsIPv4/IsNumeric/IsAlphanumeric |
格式验证 |
| URL | ParseURL/URLBuilder |
链式构建URL |
| 加密 | SHA256/Base64Encode/Base64URLEncode |
安全哈希、编码 |
| HTTP | NewHTTPClient/Get/PostJSON/Upload |
连接池、链式调用 |
| UUID | UUID/UUIDShort |
UUID生成 |
设计改进点:
| 改进 | 说明 |
|---|---|
| 性能优化 | RandString/RandDigit 使用 sync.Pool 复用随机源 |
| 类型安全 | 移除使用反射的函数,保持类型安全 |
| 式调用 | HTTPClient 和 URLBuilder 支持链式调用 |
| 零依赖 | 仅依赖 google/uuid,其余使用标准库 |
12. 实时通信
12.1 SSE 流式响应
import "github.com/EthanCodeCraft/xlgo-core/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)
}
// 手动写入
writer, _ := sse.NewSSEWriter(c)
writer.WriteEvent("message", "Hello")
writer.WriteMessage("World")
writer.WriteDone()
12.2 WebSocket
import "github.com/EthanCodeCraft/xlgo-core/ws"
// 简单使用
r.GET("/ws", ws.HandleFunc(func(conn *ws.Connection, message []byte) {
conn.SendText("收到: " + string(message))
}))
// 广播模式
hub := ws.NewHub()
go hub.Run()
// 注册连接
hub.Register(conn)
// 广播消息
hub.Broadcast([]byte("广播消息"))
// 获取连接数
count := hub.Count()
13. 定时任务
13.1 添加任务
import "github.com/EthanCodeCraft/xlgo-core/cron"
// 间隔执行(每5分钟)
cron.AddTask("cleanup", cron.Every(5*time.Minute), func(ctx context.Context) error {
return cleanupOldData()
})
// 每天固定时间(凌晨2点)
cron.AddTask("report", cron.Daily(2, 0), generateReport)
// 每周执行(周一上午10点)
cron.AddTask("weekly", cron.Weekly(time.Monday, 10, 0), weeklyTask)
// 简化 Cron(分钟+小时)
cron.AddTask("noon", cron.Cron("0", "12"), doSomething) // 每天12:00
// 完整 Cron 表达式(5字段)
cron.AddTask("complex", cron.ParseCron("*/15 * * * *"), doSomething) // 每15分钟
cron.AddTask("monthly", cron.ParseCron("0 0 1 * *"), doSomething) // 每月1号凌晨
cron.AddTask("workday", cron.ParseCron("0 9-17 * * 1-5"), doSomething) // 工作日9-17点
13.2 启动与停止
// 启动调度器
cron.Start()
// 停止调度器
cron.Stop()
// 获取调度器实例进行更多操作
scheduler := cron.GetScheduler()
scheduler.RemoveTask("cleanup") // 移除任务
tasks := scheduler.ListTasks() // 查看任务列表
scheduler.RunTask("report") // 立即执行任务
scheduler.DisableTask("cleanup") // 禁用任务
scheduler.EnableTask("cleanup") // 启用任务
14. 链路追踪
14.1 初始化
import "github.com/EthanCodeCraft/xlgo-core/trace"
trace.Init(trace.Config{
Enabled: true,
ServiceName: "my-service",
Endpoint: "localhost:4318",
ExporterType: "otlp-http",
SampleRatio: 1.0,
})
defer trace.Close(ctx)
14.2 使用中间件
// 自动追踪所有请求
r.Use(trace.Middleware("my-service"))
// 响应头自动添加 X-Trace-ID
14.3 业务追踪
// 创建子Span
ctx, span := trace.StartSpan(c, "db_query")
defer span.End()
// 记录错误
trace.RecordError(c, err)
// 添加属性
trace.SetAttribute(c, "user_id", 123)
// 获取TraceID
traceID := trace.GetTraceID(c)
15. 压缩解压
15.1 Gzip
import "github.com/EthanCodeCraft/xlgo-core/compress"
// 压缩数据
compressed, err := compress.GzipCompress(data)
// 解压数据
data, err := compress.GzipDecompress(compressed)
// 压缩文件
err := compress.GzipCompressFile("src.txt", "dst.gz")
// 解压文件
err := compress.GzipDecompressFile("src.gz", "dst.txt")
15.2 Zip
// 打包文件/目录
err := compress.Zip("archive.zip", []string{"file1.txt", "dir/"})
// 解压到目录
err := compress.Unzip("archive.zip", "./output")
16. 测试工具
16.1 API 测试
import "github.com/EthanCodeCraft/xlgo-core/test"
func TestUserAPI(t *testing.T) {
router := test.SetupRouter()
// POST请求
resp := test.POST(router, "/api/users").
WithJSON(map[string]any{"name": "test"}).
Execute()
resp.AssertOK(t)
resp.AssertCode(t, 1)
// GET请求
resp = test.GET(router, "/api/users/1").Execute()
resp.AssertOK(t)
resp.AssertJSONKeyExists(t, "data.id")
}
17. CLI 脚手架
17.1 创建项目
# 创建新项目
xlgo new myproject
# 指定模块名
xlgo new myproject --module github.com/company/myproject
17.2 生成代码
# 生成Handler
xlgo make handler user
# 生成Model
xlgo make model user
# 生成Repository
xlgo make repository user
# 生成Service
xlgo make service user
18. 最佳实践
18.1 项目分层
handler/ → 接收请求、参数验证、调用Service
service/ → 业务逻辑、事务管理
repository/ → 数据访问、CRUD操作
model/ → 数据模型定义
18.2 错误处理
func GetUser(c *gin.Context) {
id := handler.PathInt64(c, "id", 0)
if id == 0 {
// 参数错误码由业务侧定义;这里直接返回通用失败 + 自定义消息
response.Fail(c, "参数错误")
return
}
user, err := userService.GetByID(id)
if err != nil {
trace.RecordError(c, err)
response.FailWithError(c, response.ErrUserNotFound)
return
}
response.Success(c, user)
}
18.3 多站点配置
# A站点
app:
site_name: "site_a"
# B站点
app:
site_name: "site_b"
缓存自动隔离:
- site_a:
cache:site_a:user:1 - site_b:
cache:site_b:user:1
18.4 环境区分
if cfg.IsProduction() {
gin.SetMode(gin.ReleaseMode)
// 生产环境收紧 console 输出(仅保留 Warn / Error)
// 业务事件请使用 logger 包记录
console.SetLevel(console.LevelWarn)
}
附录:完整示例
用户登录接口
package handler
import (
"github.com/EthanCodeCraft/xlgo-core/cache"
"github.com/EthanCodeCraft/xlgo-core/response"
"github.com/EthanCodeCraft/xlgo-core/validation"
"github.com/gin-gonic/gin"
)
type LoginRequest struct {
Username string `json:"username" label:"用户名" validate:"required" msg_required:"用户名不能为空"`
Password string `json:"password" label:"密码" validate:"required,password" msg_required:"密码不能为空"`
}
func Login(c *gin.Context) {
var req LoginRequest
if !validation.ShouldBindAndValidate(c, &req) {
return
}
// 查询用户
user, err := userService.GetByUsername(req.Username)
if err != nil {
response.FailWithError(c, response.ErrUserNotFound)
return
}
// 验证密码
if !validation.CheckPassword(user.Password, req.Password) {
response.FailWithError(c, response.ErrPasswordWrong)
return
}
// 生成Token
token, _ := jwt.GenerateToken(user.ID, user.Username, "admin", "admin")
// 缓存用户信息
cache.GetCache().Set(c.Request.Context(),
cache.KSession(token),
user,
time.Duration(cfg.JWT.Expire)*time.Second)
response.Success(c, gin.H{
"token": token,
"user_id": user.ID,
"username": user.Username,
})
}
文档版本: v1.0.2 最后更新: 2026-06-20