后端:
1.阶段 6 CP4/CP5 目录收口与共享边界纯化
- 将 backend 根目录收口为 services、client、gateway、cmd、shared 五个一级目录
- 收拢 bootstrap、inits、infra/kafka、infra/outbox、conv、respond、pkg、middleware,移除根目录旧实现与空目录
- 将 utils 下沉到 services/userauth/internal/auth,将 logic 下沉到 services/schedule/core/planning
- 将迁移期 runtime 桥接实现统一收拢到 services/runtime/{conv,dao,eventsvc,model},删除 shared/legacy 与未再被 import 的旧 service 实现
- 将 gateway/shared/respond 收口为 HTTP/Gin 错误写回适配,shared/respond 仅保留共享错误语义与状态映射
- 将 HTTP IdempotencyMiddleware 与 RateLimitMiddleware 收口到 gateway/middleware
- 将 GormCachePlugin 下沉到 shared/infra/gormcache,将共享 RateLimiter 下沉到 shared/infra/ratelimit,将 agent token budget 下沉到 services/agent/shared
- 删除 InitEino 兼容壳,收缩 cmd/internal/coreinit 仅保留旧组合壳残留域初始化语义
- 更新微服务迁移计划与桌面 checklist,补齐 CP4/CP5 当前切流点、目录终态与验证结果
- 完成 go test ./...、git diff --check 与最终真实 smoke;health、register/login、task/create+get、schedule/today、task-class/list、memory/items、agent chat/meta/timeline/context-stats 全部 200,SSE 合并结果为 CP5_OK 且 [DONE] 只有 1 个
65 lines
1.8 KiB
Go
65 lines
1.8 KiB
Go
package ratelimit
|
||
|
||
import (
|
||
"context"
|
||
"time"
|
||
|
||
"github.com/go-redis/redis/v8"
|
||
)
|
||
|
||
var tokenBucketScript = redis.NewScript(`-- KEYS[1]: 限流标识 (如 rate_limit:user_123)
|
||
-- ARGV[1]: 令牌桶最大容量 (Capacity)
|
||
-- ARGV[2]: 令牌填充速率 (Tokens per second)
|
||
-- ARGV[3]: 当前时间戳 (Current Unix timestamp in seconds)
|
||
-- ARGV[4]: 请求需要的令牌数 (通常为 1)
|
||
|
||
local bucket_info = redis.call("HMGET", KEYS[1], "last_tokens", "last_refreshed")
|
||
local last_tokens = tonumber(bucket_info[1])
|
||
local last_refreshed = tonumber(bucket_info[2])
|
||
|
||
local capacity = tonumber(ARGV[1])
|
||
local rate = tonumber(ARGV[2])
|
||
local now = tonumber(ARGV[3])
|
||
local requested = tonumber(ARGV[4])
|
||
|
||
-- 如果是首次访问,初始化桶
|
||
if last_tokens == nil then
|
||
last_tokens = capacity
|
||
last_refreshed = now
|
||
end
|
||
|
||
-- 💡 核心逻辑:计算这段时间新产生的令牌
|
||
local delta = math.max(0, now - last_refreshed)
|
||
local new_tokens = math.min(capacity, last_tokens + (delta * rate))
|
||
|
||
local allowed = false
|
||
if new_tokens >= requested then
|
||
new_tokens = new_tokens - requested
|
||
allowed = true
|
||
end
|
||
|
||
-- 更新 Redis 状态
|
||
redis.call("HMSET", KEYS[1], "last_tokens", new_tokens, "last_refreshed", now)
|
||
-- 设置过期时间(比如 1 小时没人访问就删掉,省内存)
|
||
redis.call("EXPIRE", KEYS[1], 3600)
|
||
|
||
return allowed and 1 or 0`)
|
||
|
||
type RateLimiter struct {
|
||
client *redis.Client
|
||
}
|
||
|
||
func NewRateLimiter(client *redis.Client) *RateLimiter {
|
||
return &RateLimiter{client: client}
|
||
}
|
||
|
||
func (r *RateLimiter) Allow(ctx context.Context, key string, capacity, rate int) (bool, error) {
|
||
// 传参:Key, 容量, 速率, 当前时间, 请求数
|
||
res, err := tokenBucketScript.Run(ctx, r.client, []string{key},
|
||
capacity, rate, time.Now().Unix(), 1).Int()
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
return res == 1, nil
|
||
}
|