This commit is contained in:
2026-06-18 21:16:02 +08:00
parent 9685d34187
commit 3a5a0d0028
36 changed files with 1566 additions and 66 deletions

View File

@@ -0,0 +1,185 @@
package services
import (
"context"
"sync"
"sync/atomic"
"time"
"tyapi-server/internal/domains/api/entities"
"tyapi-server/internal/domains/api/repositories"
"go.uber.org/zap"
)
type QueryWhitelistService interface {
ShouldReturnEmpty(ctx context.Context, userID, apiCode string, params map[string]interface{}) bool
InvalidateCache(userID, idCardHash string)
InvalidateAllCache()
}
// queryWhitelistSnapshot 全量 enabled 规则快照,按 id_card_hash 索引,热路径只读内存。
type queryWhitelistSnapshot struct {
byHash map[string][]*entities.QueryWhitelistEntry
}
type QueryWhitelistServiceImpl struct {
repo repositories.QueryWhitelistRepository
formConfigService FormConfigService
logger *zap.Logger
snapshot atomic.Pointer[queryWhitelistSnapshot]
snapshotMu sync.Mutex
// apiCode -> 是否要求身份证入参FormConfig 反射结果,进程内永久缓存)
identityAPICache sync.Map
}
func NewQueryWhitelistService(
repo repositories.QueryWhitelistRepository,
formConfigService FormConfigService,
logger *zap.Logger,
) QueryWhitelistService {
s := &QueryWhitelistServiceImpl{
repo: repo,
formConfigService: formConfigService,
logger: logger,
}
return s
}
// ShouldReturnEmpty 检查是否应返回「查询为空」。
// 热路径:入参提取 → API 类型缓存 → 内存快照匹配,不逐请求查库。
func (s *QueryWhitelistServiceImpl) ShouldReturnEmpty(
ctx context.Context,
userID, apiCode string,
params map[string]interface{},
) bool {
identity := ExtractIdentityParams(params)
if !identity.OK {
return false
}
if !s.requiresIdentityInput(ctx, apiCode) {
return false
}
idCardHash := HashIDCard(identity.IDCard)
entries, err := s.lookupEntries(ctx, userID, idCardHash)
if err != nil {
s.logger.Error("查询白名单快照失败", zap.Error(err), zap.String("user_id", userID))
return false
}
for _, entry := range entries {
if !entry.IsEnabled() {
continue
}
if !entry.MatchesAPICode(apiCode) {
continue
}
if !entry.MatchesName(identity.Name) {
continue
}
s.logger.Info("命中查询白名单",
zap.String("user_id", userID),
zap.String("api_code", apiCode),
zap.String("whitelist_id", entry.ID),
zap.Bool("is_global", entry.IsGlobal()),
)
return true
}
return false
}
func (s *QueryWhitelistServiceImpl) requiresIdentityInput(ctx context.Context, apiCode string) bool {
if s.formConfigService == nil {
return false
}
if cached, ok := s.identityAPICache.Load(apiCode); ok {
return cached.(bool)
}
result := s.formConfigService.RequiresIdentityInput(ctx, apiCode)
s.identityAPICache.Store(apiCode, result)
return result
}
func (s *QueryWhitelistServiceImpl) lookupEntries(ctx context.Context, userID, idCardHash string) ([]*entities.QueryWhitelistEntry, error) {
snap, err := s.getSnapshot(ctx)
if err != nil {
return nil, err
}
candidates := snap.byHash[idCardHash]
if len(candidates) == 0 {
return nil, nil
}
result := make([]*entities.QueryWhitelistEntry, 0, len(candidates))
for _, entry := range candidates {
if entry.UserID == userID || entry.UserID == entities.QueryWhitelistGlobalUserID {
result = append(result, entry)
}
}
return result, nil
}
func (s *QueryWhitelistServiceImpl) getSnapshot(ctx context.Context) (*queryWhitelistSnapshot, error) {
if snap := s.snapshot.Load(); snap != nil {
return snap, nil
}
return s.reloadSnapshot(ctx)
}
func (s *QueryWhitelistServiceImpl) reloadSnapshot(ctx context.Context) (*queryWhitelistSnapshot, error) {
s.snapshotMu.Lock()
defer s.snapshotMu.Unlock()
if snap := s.snapshot.Load(); snap != nil {
return snap, nil
}
entries, err := s.repo.FindAllEnabled(ctx)
if err != nil {
return nil, err
}
byHash := make(map[string][]*entities.QueryWhitelistEntry, len(entries))
for _, entry := range entries {
byHash[entry.IDCardHash] = append(byHash[entry.IDCardHash], entry)
}
snap := &queryWhitelistSnapshot{byHash: byHash}
s.snapshot.Store(snap)
s.logger.Info("查询白名单快照已加载", zap.Int("entries", len(entries)), zap.Int("hash_buckets", len(byHash)))
return snap, nil
}
// refreshSnapshotAsync 管理端变更后异步刷新,避免阻塞写请求。
func (s *QueryWhitelistServiceImpl) refreshSnapshotAsync() {
go func() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
s.snapshotMu.Lock()
defer s.snapshotMu.Unlock()
s.snapshot.Store(nil)
entries, err := s.repo.FindAllEnabled(ctx)
if err != nil {
s.logger.Error("刷新查询白名单快照失败", zap.Error(err))
return
}
byHash := make(map[string][]*entities.QueryWhitelistEntry, len(entries))
for _, entry := range entries {
byHash[entry.IDCardHash] = append(byHash[entry.IDCardHash], entry)
}
s.snapshot.Store(&queryWhitelistSnapshot{byHash: byHash})
s.logger.Info("查询白名单快照已刷新", zap.Int("entries", len(entries)))
}()
}
func (s *QueryWhitelistServiceImpl) InvalidateCache(_ string, _ string) {
s.refreshSnapshotAsync()
}
func (s *QueryWhitelistServiceImpl) InvalidateAllCache() {
s.refreshSnapshotAsync()
}