deepseek修改版

This commit is contained in:
杭州明婳科技
2026-07-03 00:00:11 +08:00
parent 0f13292a46
commit a74ea0c2f4
22 changed files with 378 additions and 195 deletions
+18 -9
View File
@@ -49,7 +49,10 @@ func (p *RoundRobinPicker) Pick(replicas []*gorm.DB) *gorm.DB {
return replicas[int(n-1)%len(replicas)]
}
// RandomPicker 随机选择从库
// RandomPicker 随机选择从库
//
// D2 注释:rand.Intn 自 Go 1.20 起(go.dev/issue/54899)内部使用 per-goroutine
// 随机源,并发安全无需额外同步。本模块 go.mod 声明 go 1.25.0,满足此要求。
type RandomPicker struct{}
// Pick 随机选择一个从库
@@ -69,11 +72,11 @@ type Manager struct {
mu sync.Mutex
// #21 健康自愈
healthy atomic.Bool // 主库是否健康
replicaHealthy []atomic.Bool // 每个从库的健康标记,索引与 replicas 对齐
probeFailures int // 主库连续探活失败次数
probeMu sync.Mutex // 保护 probeFailures
replicaHealthSet bool // replicaHealthy 是否已按 replicas 长度初始化
healthy atomic.Bool // 主库是否健康
replicaHealthy []atomic.Bool // 每个从库的健康标记,索引与 replicas 对齐
probeFailures int // 主库连续探活失败次数
probeMu sync.Mutex // 保护 probeFailures
replicaHealthSet bool // replicaHealthy 是否已按 replicas 长度初始化
}
// NewManager 创建数据库管理器
@@ -434,18 +437,24 @@ func (m *Manager) InitDB(cfg *config.Config) error {
// isTransientDBError 判断数据库连接错误是否值得重试。
// 认证失败、未知数据库、非法 DSN/驱动等属于配置类错误,重试无意义,直接返回更友好。
// D5 修复:覆盖 MySQL 和 PostgreSQL 常见非瞬态错误。
func isTransientDBError(err error) bool {
if err == nil {
return true
}
msg := err.Error()
nonTransient := []string{
"Access denied", // MySQL 认证失败(用户名/密码错误)
"authentication plugin", // MySQL 认证插件不支持
"Unknown database", // MySQL 目标库不存在
// MySQL
"Access denied", // 认证失败(用户名/密码错误)
"authentication plugin", // 认证插件不支持
"Unknown database", // 目标库不存在
"invalid DSN", // DSN 语法错误
"unknown driver", // 驱动未注册
"unsupported driver", // 驱动不支持
// PostgreSQLD5 修复)
"password authentication failed", // pg 密码错误
"database", "does not exist", // pg 目标库不存在(消息含"database ... does not exist"
"no pg_hba.conf entry", // pg_hba.conf 拒绝
}
for _, sub := range nonTransient {
if strings.Contains(msg, sub) {
+34 -12
View File
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"sync"
"time"
"github.com/EthanCodeCraft/xlgo-core/config"
"github.com/EthanCodeCraft/xlgo-core/logger"
@@ -12,9 +13,22 @@ import (
"go.uber.org/zap"
)
// RedisClient 全局 Redis 客户端(兼容 facade由 RedisManager.Init 同步维护)。
// 保留供存量代码直接访问;新代码建议用 GetRedis() 或持有 *RedisManager 实例。
var RedisClient *redis.Client
// redisClient 内部 Redis 客户端引用(由 RedisManager.Init/Close 同步维护)。
//
// 已废弃对外暴露:所有外部代码请使用 GetRedis() 或持有 *RedisManager 实例。
// 测试如需注入 mock Redis,请用 SetDefaultRedisManager 替换 DefaultRedis。
//
// 仅包内 Init/Close 在持有 m.mu 时写入;外部直接读取存在竞态风险。
var redisClient *redis.Client
// SetTestRedisClient 供测试注入 miniredis 等 mock 客户端。
// 返回旧客户端引用以便测试清理时恢复。生产代码严禁调用。
// 注意:仅在测试环境单 goroutine 使用,不持有锁保护。
func SetTestRedisClient(c *redis.Client) *redis.Client {
old := redisClient
redisClient = c
return old
}
// RedisManager Redis 连接管理器(#10)。照 database.Manager 模式:
// 实例化 + DefaultRedis 全局默认 + 包级 facade 代理,支持多实例与测试注入。
@@ -44,20 +58,24 @@ func (m *RedisManager) Init(cfg *config.Config) error {
defer m.mu.Unlock()
client := redis.NewClient(&redis.Options{
Addr: cfg.Redis.Addr(),
Password: cfg.Redis.Password,
DB: cfg.Redis.DB,
Addr: cfg.Redis.Addr(),
Password: cfg.Redis.Password,
DB: cfg.Redis.DB,
DialTimeout: 5 * time.Second, // D7 修复:连接超时
ReadTimeout: 3 * time.Second, // D7 修复:读超时
WriteTimeout: 3 * time.Second, // D7 修复:写超时
})
ctx := context.Background()
if err := client.Ping(ctx).Err(); err != nil {
pingCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := client.Ping(pingCtx).Err(); err != nil {
client.Close()
return fmt.Errorf("Redis 连接失败: %w", err)
}
m.cfg = cfg
m.client = client
RedisClient = client // 同步兼容 facade
redisClient = client // 内部同步
logger.Info("Redis 连接成功", zap.String("addr", cfg.Redis.Addr()))
return nil
}
@@ -72,7 +90,7 @@ func (m *RedisManager) Close() error {
}
err := m.client.Close()
m.client = nil
RedisClient = nil
redisClient = nil
return err
}
@@ -111,7 +129,11 @@ func HealthCheckRedis(ctx context.Context) error {
return DefaultRedis.HealthCheck(ctx)
}
// GetRedis 获取 Redis 客户端
// GetRedis 获取 Redis 客户端
// 优先返回 DefaultRedis 实例化的客户端;若未初始化则回退到内部客户端(测试注入路径)。
func GetRedis() *redis.Client {
return DefaultRedis.Client()
if c := DefaultRedis.Client(); c != nil {
return c
}
return redisClient
}