feat(rpm): RPM 限流模块优化
P0: - rpm_override 嵌入 Auth Cache Snapshot,消除每请求 DB 查询 (snapshot v6→v7) - 429 RPM 响应返回 Retry-After 头(当前分钟剩余秒数) P1: - ClearAll 按钮直连 DELETE API,带 loading 防重复 - 新增 GET /admin/users/:id/rpm-status 管理员 RPM 用量查询端点 优化: - checkRPM 从级联互斥改为并行取最严,user.rpm_limit 作为全局硬上限始终生效 - Override/Group 变更后自动失效 auth cache - fail-open 语义不变,Redis 故障不阻塞业务
This commit is contained in:
112
backend/internal/service/admin_service_rpm_status_test.go
Normal file
112
backend/internal/service/admin_service_rpm_status_test.go
Normal file
@@ -0,0 +1,112 @@
|
||||
//go:build unit
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type rpmStatusUserRepoStub struct {
|
||||
UserRepository
|
||||
user *User
|
||||
}
|
||||
|
||||
func (s *rpmStatusUserRepoStub) GetByID(_ context.Context, _ int64) (*User, error) {
|
||||
return s.user, nil
|
||||
}
|
||||
|
||||
type rpmStatusAPIKeyRepoStub struct {
|
||||
APIKeyRepository
|
||||
keys []APIKey
|
||||
}
|
||||
|
||||
func (s *rpmStatusAPIKeyRepoStub) ListByUserID(_ context.Context, _ int64, _ pagination.PaginationParams, _ APIKeyListFilters) ([]APIKey, *pagination.PaginationResult, error) {
|
||||
return s.keys, &pagination.PaginationResult{Total: int64(len(s.keys))}, nil
|
||||
}
|
||||
|
||||
type rpmStatusGroupRepoStub struct {
|
||||
GroupRepository
|
||||
groups map[int64]*Group
|
||||
}
|
||||
|
||||
func (s *rpmStatusGroupRepoStub) GetByIDLite(_ context.Context, id int64) (*Group, error) {
|
||||
return s.groups[id], nil
|
||||
}
|
||||
|
||||
type rpmStatusRateRepoStub struct {
|
||||
UserGroupRateRepository
|
||||
overrides map[int64]*int
|
||||
}
|
||||
|
||||
func (s *rpmStatusRateRepoStub) GetRPMOverrideByUserAndGroup(_ context.Context, _, groupID int64) (*int, error) {
|
||||
return s.overrides[groupID], nil
|
||||
}
|
||||
|
||||
type rpmStatusCacheStub struct {
|
||||
UserRPMCache
|
||||
userUsed int
|
||||
groupUsed map[int64]int
|
||||
}
|
||||
|
||||
func (s *rpmStatusCacheStub) IncrementUserGroupRPM(context.Context, int64, int64) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (s *rpmStatusCacheStub) IncrementUserRPM(context.Context, int64) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (s *rpmStatusCacheStub) GetUserGroupRPM(_ context.Context, _, groupID int64) (int, error) {
|
||||
return s.groupUsed[groupID], nil
|
||||
}
|
||||
|
||||
func (s *rpmStatusCacheStub) GetUserRPM(context.Context, int64) (int, error) {
|
||||
return s.userUsed, nil
|
||||
}
|
||||
|
||||
func TestAdminService_GetUserRPMStatus_AggregatesUserAndGroupLimits(t *testing.T) {
|
||||
groupOneID := int64(1)
|
||||
groupTwoID := int64(2)
|
||||
override := 7
|
||||
svc := &adminServiceImpl{
|
||||
userRepo: &rpmStatusUserRepoStub{user: &User{
|
||||
ID: 42,
|
||||
RPMLimit: 20,
|
||||
}},
|
||||
apiKeyRepo: &rpmStatusAPIKeyRepoStub{keys: []APIKey{
|
||||
{ID: 100, UserID: 42, GroupID: &groupTwoID},
|
||||
{ID: 101, UserID: 42, GroupID: &groupOneID},
|
||||
{ID: 102, UserID: 42, GroupID: &groupTwoID},
|
||||
{ID: 103, UserID: 42},
|
||||
}},
|
||||
groupRepo: &rpmStatusGroupRepoStub{groups: map[int64]*Group{
|
||||
groupOneID: {ID: groupOneID, Name: "group-one", RPMLimit: 10},
|
||||
groupTwoID: {ID: groupTwoID, Name: "group-two", RPMLimit: 60},
|
||||
}},
|
||||
userGroupRateRepo: &rpmStatusRateRepoStub{overrides: map[int64]*int{
|
||||
groupTwoID: &override,
|
||||
}},
|
||||
userRPMCache: &rpmStatusCacheStub{
|
||||
userUsed: 5,
|
||||
groupUsed: map[int64]int{
|
||||
groupOneID: 3,
|
||||
groupTwoID: 4,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
status, err := svc.GetUserRPMStatus(context.Background(), 42)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &UserRPMStatus{
|
||||
UserRPMUsed: 5,
|
||||
UserRPMLimit: 20,
|
||||
PerGroup: []UserGroupRPMStatus{
|
||||
{GroupID: groupOneID, GroupName: "group-one", Used: 3, Limit: 10, Source: "group"},
|
||||
{GroupID: groupTwoID, GroupName: "group-two", Used: 4, Limit: 7, Source: "override"},
|
||||
},
|
||||
}, status)
|
||||
}
|
||||
Reference in New Issue
Block a user