Files
xlgo-core/database/dialect.go
T
杭州明婳科技 dcfd24b624 release: v1.0.3 bug fix release
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>
2026-06-22 21:40:10 +08:00

140 lines
4.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package database
import (
"fmt"
"strings"
"sync"
"github.com/EthanCodeCraft/xlgo-core/config"
"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
// 内置驱动常量(更多驱动可通过 RegisterDialect 扩展)
const (
DriverMySQL = config.DriverMySQL
DriverPostgres = config.DriverPostgres
)
// DialectorFactory 根据 DSN 返回 GORM Dialector
type DialectorFactory func(dsn string) gorm.Dialector
// DialectSpec 描述一种数据库方言:如何建立连接 + 如何拼接 DSN
type DialectSpec struct {
// Name 驱动主名称(如 "mysql"、"postgres"、"sqlite"),大小写不敏感
Name string
// Aliases 驱动别名(如 postgres 的 "postgresql"、"pg"
Aliases []string
// Dialector 由 DSN 构造 GORM Dialector
Dialector DialectorFactory
// DSN 由 DatabaseConfig 拼接连接字符串。可选——
// 不提供时使用 cfg.MySQLDSN() 兜底(适合自定义驱动通过 CustomDSN 指定连接串的场景)
DSN config.DSNBuilder
}
var (
dialectsMu sync.RWMutex
dialects = map[string]DialectorFactory{}
)
// RegisterDialect 注册一种数据库方言。
// 同时把 DSN 构建器登记到 config 包,使 cfg.Database.DSN() 也能识别新驱动。
// 已注册的同名驱动会被覆盖。
//
// 用法示例(接入 SQLite):
//
// import "gorm.io/driver/sqlite"
//
// database.RegisterDialect(database.DialectSpec{
// Name: "sqlite",
// Dialector: func(dsn string) gorm.Dialector { return sqlite.Open(dsn) },
// DSN: func(c *config.DatabaseConfig) string { return c.Name }, // 文件路径
// })
func RegisterDialect(spec DialectSpec) {
if spec.Dialector == nil || strings.TrimSpace(spec.Name) == "" {
return
}
dialectsMu.Lock()
for _, n := range append([]string{spec.Name}, spec.Aliases...) {
key := normalizeDriver(n)
if key != "" {
dialects[key] = spec.Dialector
}
}
dialectsMu.Unlock()
if spec.DSN != nil {
config.RegisterDSNBuilder(spec.Name, spec.DSN, spec.Aliases...)
}
}
// LookupDialect 查找已注册的 Dialector 工厂
func LookupDialect(driver string) (DialectorFactory, bool) {
key := normalizeDriver(driver)
dialectsMu.RLock()
defer dialectsMu.RUnlock()
f, ok := dialects[key]
return f, ok
}
// RegisteredDialects 返回所有已注册的驱动名(用于诊断)
func RegisteredDialects() []string {
dialectsMu.RLock()
defer dialectsMu.RUnlock()
names := make([]string, 0, len(dialects))
for k := range dialects {
names = append(names, k)
}
return names
}
// Dialector 根据配置返回 GORM Dialector。
// 驱动由 cfg.Database.Driver 决定,未指定或未注册时按 MySQL 兜底(向后兼容)。
func Dialector(cfg *config.Config) gorm.Dialector {
return dialectorForDSN(cfg.Database.Driver, cfg.Database.DSN())
}
// dialectorForDSN 根据驱动名和 DSN 返回 Dialector
func dialectorForDSN(driver, dsn string) gorm.Dialector {
if f, ok := LookupDialect(driver); ok {
return f(dsn)
}
// 未注册时回退到 MySQL,与 config.DSN() 的回退保持一致
return mysql.Open(dsn)
}
// normalizeDriver 规范化驱动名(小写、去空白)
func normalizeDriver(name string) string {
return strings.ToLower(strings.TrimSpace(name))
}
// driverDescription 返回带别名提示的驱动描述(用于错误信息和日志)
func driverDescription(driver string) string {
key := normalizeDriver(driver)
if key == "" {
return DriverMySQL + " (default)"
}
if _, ok := LookupDialect(key); ok {
return key
}
return fmt.Sprintf("%s (unregistered, fallback=%s)", key, DriverMySQL)
}
func init() {
// 内置 MySQL
RegisterDialect(DialectSpec{
Name: DriverMySQL,
Dialector: func(dsn string) gorm.Dialector { return mysql.Open(dsn) },
DSN: func(c *config.DatabaseConfig) string { return c.MySQLDSN() },
})
// 内置 PostgreSQL
RegisterDialect(DialectSpec{
Name: DriverPostgres,
Aliases: []string{"postgresql", "pg"},
Dialector: func(dsn string) gorm.Dialector { return postgres.Open(dsn) },
DSN: func(c *config.DatabaseConfig) string { return c.PostgresDSN() },
})
}