feat(api-key): 增加 API Key 上次使用时间并补齐测试

This commit is contained in:
yangjianbo
2026-02-22 22:07:17 +08:00
parent 1fae8d086d
commit 7be1195281
29 changed files with 1067 additions and 16 deletions

View File

@@ -5,6 +5,8 @@ import (
"crypto/rand"
"encoding/hex"
"fmt"
"strconv"
"sync"
"time"
"github.com/Wei-Shaw/sub2api/internal/config"
@@ -32,6 +34,7 @@ var (
const (
apiKeyMaxErrorsPerHour = 20
apiKeyLastUsedMinTouch = 30 * time.Second
)
type APIKeyRepository interface {
@@ -58,6 +61,7 @@ type APIKeyRepository interface {
// Quota methods
IncrementQuotaUsed(ctx context.Context, id int64, amount float64) (float64, error)
UpdateLastUsed(ctx context.Context, id int64, usedAt time.Time) error
}
// APIKeyCache defines cache operations for API key service
@@ -125,6 +129,8 @@ type APIKeyService struct {
authCacheL1 *ristretto.Cache
authCfg apiKeyAuthCacheConfig
authGroup singleflight.Group
lastUsedTouchL1 sync.Map // keyID -> time.Time
lastUsedTouchSF singleflight.Group
}
// NewAPIKeyService 创建API Key服务实例
@@ -527,6 +533,7 @@ func (s *APIKeyService) Delete(ctx context.Context, id int64, userID int64) erro
if err := s.apiKeyRepo.Delete(ctx, id); err != nil {
return fmt.Errorf("delete api key: %w", err)
}
s.lastUsedTouchL1.Delete(id)
return nil
}
@@ -558,6 +565,37 @@ func (s *APIKeyService) ValidateKey(ctx context.Context, key string) (*APIKey, *
return apiKey, user, nil
}
// TouchLastUsed 通过防抖更新 api_keys.last_used_at减少高频写放大。
// 该操作为尽力而为,不应阻塞主请求链路。
func (s *APIKeyService) TouchLastUsed(ctx context.Context, keyID int64) error {
if keyID <= 0 {
return nil
}
now := time.Now()
if v, ok := s.lastUsedTouchL1.Load(keyID); ok {
if last, ok := v.(time.Time); ok && now.Sub(last) < apiKeyLastUsedMinTouch {
return nil
}
}
_, err, _ := s.lastUsedTouchSF.Do(strconv.FormatInt(keyID, 10), func() (any, error) {
latest := time.Now()
if v, ok := s.lastUsedTouchL1.Load(keyID); ok {
if last, ok := v.(time.Time); ok && latest.Sub(last) < apiKeyLastUsedMinTouch {
return nil, nil
}
}
if err := s.apiKeyRepo.UpdateLastUsed(ctx, keyID, latest); err != nil {
return nil, fmt.Errorf("touch api key last used: %w", err)
}
s.lastUsedTouchL1.Store(keyID, latest)
return nil, nil
})
return err
}
// IncrementUsage 增加API Key使用次数可选用于统计
func (s *APIKeyService) IncrementUsage(ctx context.Context, keyID int64) error {
// 使用Redis计数器