v1.0.3
同义可失败 API 收敛 + 注释/文档一致性修复 + 前序评审收口。
API 收敛(
gpt_clear_function.md计划):按"同一能力只保留一个主入口、框架不替上层吞错、安全路径默认 fail-closed"原则,收敛 cache/jwt/storage/ratelimit 四个包的历史双轨 API。自定义CacheService/Storage实现需同步更新签名(编译失败即迁移信号)。注释/文档一致性(
glm_note_report.md报告):A/B/C 三档共 23 项注释/文档修复,无运行逻辑变更。前序评审收口:M13 cron panic、M1 App 生命周期状态机、M3 logger 生命周期临界区;config 模块评审(H-config-1 Set/viper 同源、M-config-1 回调 panic 隔离、M-config-2 DB SSL/TLS、M-config-3 App 关闭停 watcher、M-config-4 Clone 守卫);database 模块评审(H-db-1 后台探活/启动 ping 经 pingWithTimeout 3s 约束)。
项目处于初级阶段,无下游用户,放心引入破坏性变更。
go vet+go build+go test -race ./...全绿。
Breaking ⚠️
-
PostgresDSN 默认 sslmode 由
disable改为prefer(M-config-2):原PostgresDSN硬编码sslmode=disable,生产 DB 流量明文。新增DatabaseConfig.SSLMode字段,空值默认prefer(优先加密、失败回退明文),可显式配置disable/allow/prefer/require/verify-ca/verify-full(非法值在Validate阶段报错)。依赖明文 Postgres 连接的下游若因prefer回退行为受影响,请显式设置ssl_mode: disable。MySQL 不受影响(用TLS/TLSRootCA)。 -
cache 写操作与计数器/原始 Redis helper 在 Redis 未初始化时返回
ErrRedisNotReady:Set/Delete/DeleteByPattern/Incr/IncrBy/Decr/GetTTL/SetExpire/GetRaw/SetRaw旧行为会静默返回成功或零值,调用方容易误判缓存写入、计数器更新或过期时间设置已经生效;现在统一显式返回错误。公共接口签名不变,但依赖“未启用 Redis 时当作成功”的下游需要改为忽略errors.Is(err, cache.ErrRedisNotReady)或显式启用 Redis。 -
cache.WithLock/cache.WithLockAutoExtend未获取到锁时返回ErrLockNotAcquired:旧行为返回nil并跳过业务函数,调用方无法区分“业务执行成功”和“根本没有执行”。同时锁 TTL 小于 1ms、续期/重试间隔非正会返回显式错误,避免 Redis PX=0 或time.NewTicker(0)崩溃。 -
cache.WithLock/cache.WithLockAutoExtend的业务函数签名改为func(context.Context) error:旧签名func() error无法强制业务函数接收取消信号,容易在请求取消/超时后继续访问 DB/HTTP 等下游资源;现在框架会把调用方 ctx 传入业务函数。nil 业务函数返回新增ErrLockFuncNil。 -
test.Request.Execute()改为返回*test.Response:旧返回值是*httptest.ResponseRecorder,与文档示例中的resp.AssertOK(t)/resp.ParseJSON(...)不一致;现在Execute()返回带断言和 JSON 解析方法的包装类型。需要原始 recorder 的调用方改用新增ExecuteRecorder()。 -
config.Get()/(*config.Manager).Get()改为返回配置副本:旧行为暴露内部*Config,调用方修改返回值会污染全局配置并可能与热重载并发读写竞态;现在返回深拷贝。需要动态替换配置的测试或工具代码请改用config.Set(cfg)。 -
config.Set()/(*config.Manager).Set()现在返回error:非 nil 配置会先执行Validate(),非法配置不会覆盖旧配置,并返回ErrInvalidConfig包装错误。旧代码可以继续忽略返回值,但建议测试和启动路径显式检查。 -
config.GetViper()/(*config.Manager).GetViper()改为返回 viper 快照:旧行为暴露内部可变*viper.Viper;现在修改返回对象不会影响全局配置。常规读取请使用GetString/GetInt/GetBool/GetStringMap。 -
未知数据库 driver 不再静默回退 MySQL:
config.DatabaseConfig.DSN()对非空但未注册的driver返回空字符串,database.Dialector返回初始化即失败的 Dialector;空driver仍保持默认 MySQL。下游若使用自定义数据库驱动,需先通过database.RegisterDialect或config.RegisterDSNBuilder注册。 -
database.InitDB/(*database.Manager).InitDB/InitDBWithReplicas必须显式传入context.Context:旧 API 无法取消初始化过程中的 Ping 与重试等待,shutdown 或启动失败回滚时可能长时间卡住。现在调用方必须传入生命周期 ctx;普通测试或一次性脚本可使用context.Background(),App 初始化会使用 App root ctx。 -
database.SetDefaultManager/database.SetDefaultRedisManager会关闭被替换的旧 manager:旧行为只做 atomic 替换,直接调用会遗留旧 DB/Redis 连接池。现在普通 Set 表示“接管全局默认资源并释放旧资源”;需要失败回滚或延迟释放旧资源的初始化流程请改用新增database.SwapDefaultManager/database.SwapDefaultRedisManager。 -
jwt.ParseToken开始校验 issuer,RefreshToken使用jwt.refresh_expire:签发者与当前配置不一致的 token 会被拒绝;刷新后的 token 过期时间优先使用refresh_expire,未配置时回退expire。GenerateTokenWithCustomExpiry现在拒绝非正过期时间,InvalidateTokenByID("")返回ErrEmptyJTI。 -
App.Init()由sync.Once改为生命周期状态机(app.go,M1):5 态stateCreated/Initializing/Initialized/Stopping/Stopped+lifecycleMu(RWMutex) +initMu(Mutex)。Shutdown后或Init失败后再调Init()返回新增导出错误xlgo.ErrAppClosed(原sync.Once"多次调用返回首次结果"语义不再适用——已关闭的 App 不可再 Init,需新建 App)。 -
App.Go()在 Shutdown 开始或 Init 失败后为 no-op(app.go,M1):state >= stateStopping时拒绝wg.Add直接返回,避免与Shutdown的wg.Wait竞争sync.WaitGroup契约(Add 须 happen-before Wait)。依赖"Shutdown 后仍可 Go"的下游需改用独立 goroutine。 -
生命周期 hook 不允许重入调用
Init/Shutdown/Run(app.go,M1):initMu非重入,hook(OnInit/OnStart/OnReady/OnStop)内调用会自锁死锁。需在 hook 内触发关闭应改用信号通道由主流程处理。 -
xlgo.WithConfig(cfg)改为快照语义并在Init时校验(app.go,M1):传入配置会深拷贝到 App 私有快照,调用方后续修改原cfg不再影响 App;非法配置在Init返回中文校验错误。依赖“修改原 cfg 指针动态影响 App”的下游需改为重新创建 App 或使用配置管理器。 -
logger.DefaultLogger = m直接赋值不再驱动包级 facade(logger/logger.go,M3):为消除SetDefaultLogManager()与包级 facade 并发读取默认 manager 的裸全局指针竞态,facade 改为读取内部 atomic 快照。logger.DefaultLogger仍保持*LogManager类型,旧的logger.DefaultLogger.Init/Close/SetLevel/GetLevel直接调用仍可用;替换默认 manager 请使用logger.SetDefaultLogManager(m)。 -
logger.Init拒绝明显非法日志配置(logger/logger.go,M3):空日志目录、负数MaxSize/MaxBackups/MaxAge现在直接返回错误。依赖零值日志配置启动WithLogger()的下游需显式设置Log.Dir与非负轮转参数。 -
限流器非法配置改为 fail-fast(middleware/ratelimit.go,M8):
NewRateLimiter/NewRedisRateLimiter/NewRedisRateLimiterFailClosed现在对rate <= 0或window <= 0直接 panic,避免零值窗口/零值配额静默产生不确定限流语义。下游应在配置加载阶段校验限流参数。 -
handler.BindJSON默认限制 JSON body 为 1MiB(handler/handler.go,M6):防止入口层无上限读取请求体导致 OOM。需要更大 JSON 的接口请改用handler.BindJSONWithMaxBytes(c, req, maxBytes)显式声明上限。 -
cron 非法任务配置改为 fail-fast(cron/cron.go,M13):
AddTask拒绝 nil schedule / nil handler;Every(<=0)、Daily/Weekly越界时间、非法 weekday 会 panic,避免静默生成不推进或归一化跑偏的调度。 -
cron.ParseCron非法表达式改为 fail-fast panic(cron/cron.go,M13):旧行为会把非法表达式静默回退为每分钟执行,容易让拼写错误变成高频任务。动态输入请用ParseCronStrict处理 error;确实需要旧回退语义时改用新增ParseCronOrDefault。 -
repository 查询保护默认开启(repository/repository.go,M5/N5):
FindAll默认最多返回DefaultFindAllLimit=1000条;明确需要全表扫描时改用FindAllUnbounded。FindPage*/QueryBuilder.Page会归一化page/pageSize并限制MaxPageSize=100、MaxPage=10000。Find*Ordered/QueryBuilder.Order只接受简单字段排序(如created_at DESC, id ASC),复杂表达式/raw SQL 会返回ErrUnsafeOrder;UpdateBatch字段名不合法返回ErrUnsafeField。 -
同义可失败 API 收敛:错误不再吞并(cache/jwt/storage/ratelimit):按"同一能力只保留一个主入口、框架不替上层吞错、安全路径默认 fail-closed"原则,收敛历史双轨 API。详见下述分项;自定义
CacheService/Storage实现需同步更新签名(编译失败即迁移信号)。-
cache:
CacheService.Get/Exists改为(bool, error)--命中(true,nil)、未命中(false,nil)、Redis 未就绪/命令错误/反序列化失败返回(false,err)。删除cache.GetE/cache.ExistsE/CacheGetter/CacheExistChecker/redisCache.GetE/redisCache.ExistsE(不保留 deprecated wrapper)。新增包级cache.Get(ctx,key,dest) (bool,error)/cache.Exists(ctx,key) (bool,error)。GetWithPrefix改为(bool,error)。迁移:hit, err := cache.Get(ctx, key, &v); if err != nil { return err }; if !hit { /* miss */ }。 -
jwt:
TokenBlacklist.IsBlacklisted改为(bool, error),删除IsBlacklistedE。IsTokenRevoked改为(bool, error)。ParseToken默认从 fail-open 改为 fail-closed--黑名单后端不可检查时返回ErrBlacklistUnavailable拒绝该 Token(无 Redis 部署不再支持可靠撤销)。删除ParseTokenFailClosed(主 API 已 fail-closed)。保留ParseTokenWithBlacklistPolicy(token, policy)+BlacklistPolicy/BlacklistFailOpen/BlacklistFailClosed供显式 fail-open(仅无 Redis 或低安全场景)。迁移:无 Redis 部署需启用 Redis,或显式jwt.ParseTokenWithBlacklistPolicy(token, jwt.BlacklistFailOpen)。 -
storage:
Storage.Exists改为(bool, error)--存在(true,nil)、不存在(false,nil)、未初始化/路径非法/穿越/后端错误返回(false,err)。LocalStorage.Exists区分os.IsNotExist(not found)与其他os.Stat错误;OSSStorage.Exists区分 OSS 404/NoSuchKey(not found)与鉴权/网络错误。包级storage.Exists同步签名,未初始化返回ErrStorageNotInitialized。新增ErrReadTooLarge:LocalStorage.Get/OSSStorage.Get读取超maxReadBytes上限原误用ErrInvalidPath(路径无效语义不贴切),改为ErrReadTooLarge。迁移:ok, err := storage.Exists(p); if err != nil { return err }; if !ok { /* not found */ }。 -
ratelimit:Redis 限流器策略收敛为配置型 API。
NewRedisRateLimiter(keyPrefix, rate, window, opts ...RedisRateLimiterOption)新增可变参数,WithFailClosed(true)替代原NewRedisRateLimiterFailClosed。RedisRateLimit/CustomRedisRateLimit/RedisRateLimitWithIdentifier同步加opts参数。删除NewRedisRateLimiterFailClosed/RedisRateLimitFailClosed/CustomRedisRateLimitFailClosed。UploadRedisRateLimit由 fail-open 改为 fail-closed(上传属资源敏感操作,Redis 故障时拒绝以防限流静默失效)。迁移:middleware.RedisRateLimit("k", 100, middleware.WithFailClosed(true))替代原RedisRateLimitFailClosed("k", 100)。
-
Security 🔒
- MySQL 连接支持 TLS(M-config-2):
DatabaseConfig.TLS为 true 时MySQLDSN追加tls=true(go-sql-driver/mysql v1.7.0 内置安全语义:系统根 CA + ServerName 自动取自 Host + 证书校验,无需注册)。配合DatabaseConfig.TLSRootCA(PEM 路径)可指定私有 CA/自签证书,由database包在InitDB时RegisterTLSConfig注册命名配置(config.MySQLTLSConfigName);CA 不可读或非 PEM 时 fail-fast,不静默回退明文。 - Postgres 连接支持 sslmode 配置(M-config-2):见 Breaking 项,默认
prefer优先加密。
Fixed 🐛
-
config
Set(cfg)与 viper 视图同源修复(H-config-1):Set原只更新类型化视图m.cfg,m.v(viper)停留旧值,导致Get()与GetString/GetInt/GetBool/GetViper返回不同世界(违反 C1 单一配置源)。现在Set用 mapstructure 将*Config重建为不含AutomaticEnv的 viper 视图,保证 Get 与 GetString 同源。 -
config 热重载回调 panic 隔离(M-config-1):单个
onChange回调 panic 原会传播致 watcher 泄漏、后续热更新静默失效。现在每个回调独立recover(标准库log记录 + 堆栈),不阻断后续回调、不杀 watcher。 -
App.Shutdown 停止 configManager watcher(M-config-3):
closeResources末尾新增configManager.StopWatcher(),用户对 App 的 configManager 调LoadWithWatch/StartWatcher后由 Shutdown 统一收口,避免关闭后遗留监听 goroutine(违反 C7)。 -
config
Clone切片字段覆盖守卫(M-config-4):新增反射测试枚举Config所有切片/map 字段,断言Clone深拷贝;新增切片字段未同步 fixture 或Clone时测试失败,形成机械守卫。 -
config
watchLoop增加 ctx 逃生通道(L-config-2):原仅靠w.Events关闭退出,与"for 消费循环须 ctx.Done"红线有张力。StopWatcher改为 cancel ctx + Close watcher 双重退出。 -
config
Validate连接池交叉校验(L-config-3):MaxOpenConns>0时MaxIdleConns>MaxOpenConns视为配置错误。 -
config 哨兵错误改用
errors.New(L-config-4);DSN()去除冗余 TrimSpace(L-config-5);DSN/MySQLDSN/PostgresDSN/Addrnil receiver 防御(L-config-6)。 -
database 后台探活/启动 ping 经 pingWithTimeout 3s 约束(H-db-1,M11 修复不完整):
pingWithTimeout原只加到包级HealthCheck(),未覆盖方法(*Manager).HealthCheck(被后台探活probeOncemaster 与/health端点共用)、probeOnce从库 ping、InitDB/InitDBWithReplicas启动 ping。挂起 DB(连接活但不响应)下这些路径的PingContext无 ctx deadline 无限阻塞,致探活 goroutine 阻塞、#21 自愈冻结、IsHealthy()缓存失真、启动卡死。现 4 路径统一经pingWithTimeout(healthCheckTimeout=3s,尊重 ctx 自带更短 deadline)。另发现并修复 gorm.Open 的 automatic ping(gorm.go:204,ConnPool 为*sql.DB时调pinger.Ping()无超时)在 pingWithTimeout 之前就无限阻塞--initDB/InitDBWithReplicas的 gormConfig 加DisableAutomaticPing: true,框架用 pingWithTimeout 自管启动 ping。Redis 侧HealthCheck经 clientReadTimeout(3s) 约束(行为验证)。新增manager_hdb1_internal_test.go(hungDriver 回归 pingWithTimeout/m.HealthCheck/probeOnce replica/initDB/InitDBWithReplicas 5 路径)、redis_hdb1_internal_test.go(挂起 Redis 回归 HealthCheck)、app_hdb1_test.go(App 级端到端:Init 挂起 DB 有界失败 + Shutdown 正常)。 -
M9 JWT issuer / refresh expiry 契约修复:
ParseToken、InvalidateToken、GetClaimsFromToken统一按当前配置校验 issuer;RefreshToken不再忽略refresh_expire;空 JTI 不再写入永不命中的jwt_bl:黑名单键;新增ParseTokenFailClosed/ParseTokenWithBlacklistPolicy/TokenBlacklist.IsBlacklistedE,默认ParseToken仍保持黑名单检查 fail-open 兼容语义,安全敏感路由可显式选择 fail-closed;解析侧配置错误现在可通过errors.Is区分ErrEmptySecret/ErrUnsupportedAlgorithm;InvalidateToken使用不校验时序的解析路径,允许提前吊销nbf在未来的外部 token。 -
M10 分布式锁参数与取消传播修复:锁 TTL 统一校验到 Redis 毫秒粒度;
TryLock的非正 retry interval 不再 busy-loop;WithLockAutoExtend的非正 extend interval 不再触发 goroutine panic;UnlockByKey在 Redis 未初始化时与ForceUnlock一样返回ErrRedisNotReady;WithLock/WithLockAutoExtend现在把调用方 ctx 传入业务函数,避免取消后业务函数继续运行。 -
M10 cache 剩余错误语义收口:新增
cache.GetE/cache.ExistsE与可选CacheGetter/CacheExistChecker,让调用方能区分 cache miss、Redis/backend 故障和反序列化错误;保留旧Get/Existsbool-only 兼容方法但记录后端错误;KeyBuilder现在忽略 nil option,WithPrefix/WithSeparator/WithCacheType直接作用于 nil builder 时 no-op,避免扩展配置路径 panic。 -
M2 config 热重载生命周期修复:
StopWatcher会等待已触发的 reload/回调结束;包级Load/LoadWithWatch只有在新配置成功加载并启动 watcher 后才替换默认 manager,失败时保留旧 watcher;SetDefaultManager会停止旧 manager 的 watcher,避免全局置换后遗留 goroutine;数据库配置出现字段时会校验 driver/host/name/port,未知 driver 不再静默回退 MySQL,database.Dialector对未知 driver fail-closed;MySQL DSN 转义用户名/库名,Postgres DSN 统一转义字符串字段。 -
M4 database nil 边界修复:
InitDB(ctx, nil)/InitDBWithReplicas(ctx, nil, ...)/InitRedis(nil)现在返回中文错误,不再空指针 panic;UseMaster(nil)/UseReplica(nil)/GetDBFromContext(nil)/WithTx(nil, ...)/TxFromContext(nil)/TransactionWithContext(nil, ...)/ReadQuery(nil, ...)/WriteQuery(nil, ...)/ Redis health check 会把 nil context 归一化为context.Background(),避免异常调用路径触发 panic;Dialector(nil)安全回退到 MySQL 空 DSN。 -
M4 database 全局置换资源释放修复:
SetDefaultManager/SetDefaultRedisManager替换全局默认 manager 时会关闭旧 DB/Redis manager,避免包级默认资源反复置换后连接池泄漏;App 初始化改用SwapDefaultManager/SwapDefaultRedisManager暂存旧资源,保证失败回滚仍能恢复旧默认资源。 -
M4 database 初始化生命周期修复:DB 初始化与主从库初始化统一接收 ctx,主库/从库 Ping 使用
PingContext,重试等待改为select ctx.Done()/time.After;Manager用生命周期锁串行化 Init/Close,避免 shutdown 与运行期重建交错;运行期重建从库后会立即重建健康标记,探活循环也能在发现健康标记缺失时自愈;包级HealthCheck()固定读取一次默认 manager 快照。 -
M11 SSE 换行注入修复:
WriteEvent拒绝带 CR/LF 的 event 名,WriteMessage/WriteEvent的 data 按 SSE 多行格式逐行输出,避免用户数据伪造额外event:/id:字段。 -
M15 utils/validation 资源与错误边界修复:
HTTPClient.Upload改为流式 multipart 上传,不再把文件请求体完整缓存在内存中;AppendFile/CopyFile返回写侧Close错误;CheckPasswordAndUpgrade归一化非法targetCost,避免异常配置触发超高 bcrypt cost;ValidateStruct(nil)直接返回 nil。 -
M15 utils 转换语义收口:新增
utils.ToIntE/utils.ToInt64E返回解析错误;旧ToInt/ToInt64保留“失败返回 0”的兼容行为,调用方在 0 有业务含义时应迁移到严格变体。 -
M16 测试工具、脚手架与示例闭环修复:
MockDB/MockCache/MockStorage改为并发安全;MockCache与MockStorage.UploadFromBytes复制字节切片,避免调用方修改污染内部状态;MockStorage拒绝 nil 文件与超过 32MiB 的输入,避免测试 helper 被误用成无上限内存缓冲;xlgo make对资源名做显式标识符校验,非法名称(路径穿越、连字符、数字开头等)直接返回中文错误,不再静默转义后生成不可预期代码;examples/full启动时初始化alice/secret,登录校验 bcrypt 哈希,创建用户也保存哈希,避免示例首次运行无法登录或传播不验密/明文密码模式;README/GUIDE 限流示例不再引用不存在的handler.Login/handler.Upload。 -
M16 GUIDE/test API 不一致修复:GUIDE 测试示例不再调用不存在的
AssertCode/AssertJSONKeyExists,统一改用现有AssertJSONContains,避免照文档编写测试直接编译失败。 -
M12 storage/compress 安全边界修复:本地上传写侧
Close错误会通过返回值暴露并清理残片;OSSGetSignedURL统一经过 object key 净化;UnzipWithOptions解析目标绝对路径失败时 fail-closed。 -
M8 middleware 边界收口:
AuthRequired的Authorizationscheme 改为大小写不敏感,符合 Bearer 语义;LoggerConfig.SkipPathPrefixes与SimpleLogger静态路径跳过改为路径边界匹配(/api仅匹配/api和/api/...,不再误跳过/api2)。 -
M13 cron handler panic 未 recover 崩进程(cron/cron.go):
RunTask与checkAndRun调度 goroutine 统一经新增executeTask(t)边界recover,panic 转为 error(含debug.Stack调用栈)记入task.LastError并向上返回,不再终止进程。外侧defer wg.Done()/running守卫释放不受影响(recover 在边界内完成)。顺带修复RunTask手动路径此前只更LastRun/RunCount、不记LastError的子问题(现与调度路径一致)。 -
M6 response/handler 入口防护(response/error.go,response/response.go,handler/handler.go):
FailWithError(nil)/FailWithDetail(nil, ...)回退统一服务器错误响应,不再 nil deref panic;新增response.DownloadReader支持大文件/对象存储流式下载,旧Download/DownloadWithContentType保持兼容并复用同一响应头逻辑。 -
M14 trace 剩余记录补齐(trace/trace.go):
SampleRatio拒绝 NaN/越界值;Init/Close使用操作超时并在初始化失败时回滚 provider;RecordError/RecordErrorToSpan对 nil 输入 no-op;Middleware(serviceName)写入service.nameattribute;X-Trace-ID不再输出全零 TraceID。 -
M7 health/readiness 探活超时(router/router.go):
HealthCheck新增Timeout字段,默认每个依赖检查 2s 超时;超时项返回"timeout"并使/health//readyz返回 503。单个 check 同时最多一个执行中,panic 会 recover 为错误,避免 k8s/LB/监控探活被挂死依赖无限卡住或无限堆积 goroutine。 -
M13 cron 剩余边界收口(cron/cron.go):Stop 后再次 Start 会重建调度器 context,手动和调度执行不再收到已取消 ctx;Start/Stop 生命周期串行化,避免 Stop 等待期间重新 Start 触发 WaitGroup Add/Wait 交错。
-
M5 repository 安全边界收口(repository/repository.go):nil ctx 统一按
context.Background()处理;FindByIDs(nil)返回非 nil 空切片;NewQueryBuilder复用 nil DB 明确 panic;批量空 ids 写操作 no-op;默认FindAll加上限并新增FindAllUnbounded;排序/字段名白名单避免便捷 API 误接 raw SQL。 -
M1 App 生命周期三类缺陷统一治理(app.go):
- Init 失败无资源回滚 → 新增
failAfterInit:markStopping → cancel rootCtx → wg.Wait(10s) → cron 5s 显式超时停止 →closeResources幂等关闭 db/redis/logger,回滚错误errors.Join进initErr不吞;先停 goroutine 再关资源,避免"关 DB 时探活 goroutine 仍在用"的竞态。 - App.Go 与 wg.Wait race →
lifecycleMu.RLock包住wg.Add,Shutdown持写锁翻stateStopping,保证 Add happens-before Wait。 - Shutdown 非幂等/非并发安全 →
shutdownOnce保证doShutdown单次执行,并发调用者返回同一shutdownErr。 - OnStop 语义与超时 → 仅
Init曾成功(wasInitialized)时在doShutdown开头执行;Init 失败/未 Init 时 HTTP 从未启动,OnStop 跳过。OnStop 现在受server.shutdown_timeout同一预算约束,阻塞 hook 不再无限拖住 Shutdown。 - 资源所有权 → App 只关闭自己成功初始化过的 logger/db/redis/cron,避免一个 Init 失败的新 App 关闭同进程既有全局资源。
- 复审补强:资源替换事务边界 → App 初始化 logger/db/redis 时先创建 App-owned manager 并保存旧默认 manager 快照;OnInit 或后续步骤失败时恢复旧默认 manager 并关闭新资源,完整成功后才释放旧资源,避免“新 App Init 失败”破坏同进程既有全局 logger/db/redis。
- 复审补强:health/probing 绑定 App-owned manager → App 注册的 MySQL/Redis health check 与 DB probing 使用本 App 持有的 manager,不随后续全局默认 manager 替换漂移。
- OnReady 早于真实监听成功 →
StartServer改为同步net.Listen且 TLS 证书装配成功后再启动Serve与执行 OnReady;监听/TLS 失败直接返回,不触发 ready 副作用。 server.unix_socket不可用 → 非空unix_socket现在走net.Listen("unix", path)+http.Server.Serve,不再把 socket path 误传给 TCPListenAndServe。Init/Shutdown经initMu串行化 doInit/doShutdown 长段,杜绝并发改资源。
- Init 失败无资源回滚 → 新增
-
M3 logger 生命周期与全局 manager 并发治理(logger/logger.go):
LogManager.Init()锁外写m.level→Init在通过局部配置校验后持m.mu完成建目录、构造与发布新 logger,m.level只在同一临界区更新;包级Logger/fileWriters发布另由globalMu串行化,避免多个LogManager实例用各自实例锁保护同一包级状态。DefaultLogger裸全局指针 → 保留导出变量兼容旧代码,新增内部 atomic 默认 manager 快照与GetDefaultLogManager();包级Init/Close/Sync/SetLevel/GetLevel全部经 atomic 读取当前 manager。- stale manager 关闭当前 logger → 每次发布全局 logger 分配 generation,只有拥有当前 generation 的
LogManager.Close()才能关闭当前全局 logger/writer。 - 旧 writer 关闭顺序错误 →
Init先 atomic 发布新 logger,再关闭旧 lumberjack writer,避免替换窗口内包级读路径拿到指向已关闭 writer 的旧 logger。 Close()与Init()生命周期互相覆盖 →Close在同一 manager 锁内先快照旧 logger/writer 并发布 Nop,再执行 Sync/Close;关闭错误通过errors.Join聚合返回,不再静默吞掉 lumberjackClose错误。
-
M8 middleware/CSRF 与限流边界治理(middleware/csrf.go,middleware/ratelimit.go):
- CSRF JSON body 读取无上限 → 从 body 提取
_csrf时使用http.MaxBytesReader,默认上限 1MiB(CSRFConfig.MaxBodyBytes可调),超限返回 HTTP 413,避免 pre-auth OOM;仍使用ShouldBindBodyWith保留下游重复读取 body 的能力。 - CSRF cookie SameSite 配置未真正写入 →
CSRF()与DoubleSubmitCookie()设置 cookie 前显式SetSameSite,默认 Lax。 - CSRF 局部配置丢默认 cookie 行为 →
CSRFConfig归一化补齐Path、SameSite、MaxAge、FormField、MaxBodyBytes等默认值,只覆盖单个字段时不再意外丢失默认 cookie 约束。 - CSRF session cookie 显式配置 → 新增
CSRFConfig.SessionCookie,需要会话 Cookie 时设置为true;为保持CSRFWithConfig(CSRFConfig{})默认 1 小时语义,MaxAge=0且未设置SessionCookie时仍回退默认值。 - CSRF TokenLength 负数 panic/退化 →
TokenLength <= 0统一回退默认长度,避免配置错误导致make([]byte, negative)panic。 - CSRF skip path 前缀误跳过 →
CSRFWithSkip([]string{"/api"})仅跳过/api与/api/...,不再误跳过/apix。 - API CSRF token map 只校验时清理过期 token →
GenerateAPIToken颁发新 token 前同步清理过期项,避免长期只发不验场景内存增长。 RateLimit(nil)panic → 改为 fail-closed 返回 HTTP 503 +CodeServiceUnavailable,避免未初始化限流器导致请求路径 nil deref。RedisRateLimitWithIdentifiernil 回调 panic →identifierFunc == nil时回退ClientIP(),并保留空字符串回退逻辑。
- CSRF JSON body 读取无上限 → 从 body 提取
-
gosec database 告警收口(database/manager.go,database/redis.go):
RoundRobinPicker改为 mutex 保护的有界int计数器,消除 G115 整数转换告警;RandomPicker改用crypto/rand选择从库,消除 G404 弱随机源告警,随机源失败时安全回退到首个从库;Redis 初始化 ping 失败时不再静默吞掉client.Close()错误。 -
复审补强:DB/Redis 重建路径资源释放(database/manager.go,database/redis.go):Redis 重复初始化会关闭旧 client;DB 重建/重试路径的旧连接池关闭失败不再静默丢弃,会记录 warning 供排查。
-
gosec Close 错误收口(utils/http.go,compress/compress.go):HTTP multipart 上传循环中的本地文件
Close()错误不再静默吞掉;gzip/zip 解压输出文件关闭失败会通过errors.Join返回,并触发残留目标文件清理。 -
gosec 兼容性/误报标注收口(cmd/xlgo,utils/http.go,utils/crypto.go,utils/file.go,compress/compress.go):为已存在输入校验或明确调用方契约的路径、客户端请求 cookie、非安全用途 checksum、压缩源路径遍历、兼容模式 HTTP 请求添加精确
#nosec理由;NewSSRFSafeHTTPClient/BlockPrivateNetworks仍是处理不可信 URL 的推荐入口。 -
示例参数解析错误处理(examples/full/main.go):示例用户详情接口现在检查
fmt.Sscanf错误,非法用户 ID 返回失败响应,不再静默使用零值。