feat: Gateway store 桩换真实 GORM/Postgres + go-redis (含自动迁移与优雅降级)
第 2 层网关持久层落地,遵守 sundynix_ 表名前缀 + AutoMigrate 约定。 - store: GORM(NamingStrategy 前缀 sundynix_/单数) → User=sundynix_user, Task=sundynix_task 启动 AutoMigrate;go-redis/v9 滑动窗口限流(Incr+Expire,按 IP) - 优雅降级:连不上库则 warn 继续(不 fatal),保证无 Docker 的 make demo 仍跑通 - handler: SubmitTask 持久化任务(best-effort),Billing 真实读库返回 tasks_submitted - main: OpenPostgres/OpenRedis 读 POSTGRES_DSN/REDIS_ADDR 环境变量 - 验证: 4 模块 build ✓;e2e 3 测试 PASS;live 双路径(真实库持久化 + 坏DSN降级)实测通过 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,56 @@
|
||||
package store
|
||||
|
||||
import "log"
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// Redis 持有 CacheDB 连接(Session / Rate Limit)。
|
||||
type Redis struct{ /* *redis.Client */ }
|
||||
|
||||
// MustOpenRedis 建立 Redis 连接,失败即退出。
|
||||
func MustOpenRedis() *Redis {
|
||||
// TODO: redis.NewClient(opts)
|
||||
log.Println("[store] redis connected (stub)")
|
||||
return &Redis{}
|
||||
// rdb 为 nil 表示降级模式(连接失败时放行,不限流)。
|
||||
type Redis struct {
|
||||
rdb *redis.Client
|
||||
}
|
||||
|
||||
// OpenRedis 连接 CacheDB。连接失败不 fatal:返回降级实例(限流放行)。
|
||||
func OpenRedis(addr string) *Redis {
|
||||
rdb := redis.NewClient(&redis.Options{Addr: addr})
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
if err := rdb.Ping(ctx).Err(); err != nil {
|
||||
log.Printf("[store] redis 不可用,降级运行(不限流): %v", err)
|
||||
_ = rdb.Close()
|
||||
return &Redis{}
|
||||
}
|
||||
log.Println("[store] redis connected")
|
||||
return &Redis{rdb: rdb}
|
||||
}
|
||||
|
||||
// Enabled 报告是否处于真实限流模式。
|
||||
func (r *Redis) Enabled() bool { return r.rdb != nil }
|
||||
|
||||
// Allow 滑动窗口计数限流:在 window 内对 key 累加,超过 limit 即拒绝。
|
||||
// 降级模式(rdb==nil)始终放行。
|
||||
func (r *Redis) Allow(ctx context.Context, key string, limit int64, window time.Duration) (bool, error) {
|
||||
if r.rdb == nil {
|
||||
return true, nil
|
||||
}
|
||||
rk := "sundynix:ratelimit:" + key
|
||||
n, err := r.rdb.Incr(ctx, rk).Result()
|
||||
if err != nil {
|
||||
return true, err // 限流后端故障时放行,不阻断业务
|
||||
}
|
||||
if n == 1 {
|
||||
_ = r.rdb.Expire(ctx, rk, window).Err() // 首次计数设置窗口过期
|
||||
}
|
||||
return n <= limit, nil
|
||||
}
|
||||
|
||||
// Close 释放底层连接。
|
||||
func (r *Redis) Close() {
|
||||
if r.rdb != nil {
|
||||
_ = r.rdb.Close()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user