fix(P2): 修复4个P2轻微问题

P2-01: 通配符scope安全风险 (scope_auth.go)
- 添加hasWildcardScope()函数检测通配符scope
- 添加logWildcardScopeAccess()函数记录审计日志
- 在RequireScope/RequireAllScopes/RequireAnyScope中间件中调用审计日志

P2-02: isSamePayload比较字段不完整 (audit_service.go)
- 添加ActionDetail字段比较
- 添加ResultMessage字段比较
- 添加Extensions字段比较
- 添加compareExtensions()辅助函数

P2-03: regexp.MustCompile可能panic (sanitizer.go)
- 添加compileRegex()安全编译函数替代MustCompile
- 处理编译错误,避免panic

P2-04: StrategyRoundRobin未实现 (router.go)
- 添加selectByRoundRobin()方法
- 添加roundRobinCounter原子计数器
- 使用atomic.AddUint64实现线程安全的轮询

P2-05: 错误信息泄露内部细节 - 已在MED-09中处理,跳过
This commit is contained in:
Your Name
2026-04-03 09:39:32 +08:00
parent 732c97f85b
commit b2d32be14f
8 changed files with 289 additions and 19 deletions

View File

@@ -5,6 +5,7 @@ import (
"math"
"math/rand"
"sync"
"sync/atomic"
"time"
"lijiaoqiao/gateway/internal/adapter"
@@ -36,10 +37,11 @@ type ProviderHealth struct {
// Router 路由器
type Router struct {
providers map[string]adapter.ProviderAdapter
health map[string]*ProviderHealth
strategy LoadBalancerStrategy
mu sync.RWMutex
providers map[string]adapter.ProviderAdapter
health map[string]*ProviderHealth
strategy LoadBalancerStrategy
mu sync.RWMutex
roundRobinCounter uint64 // RoundRobin策略的原子计数器
}
// NewRouter 创建路由器
@@ -87,6 +89,8 @@ func (r *Router) SelectProvider(ctx context.Context, model string) (adapter.Prov
switch r.strategy {
case StrategyLatency:
return r.selectByLatency(candidates)
case StrategyRoundRobin:
return r.selectByRoundRobin(candidates)
case StrategyWeighted:
return r.selectByWeight(candidates)
case StrategyAvailability:
@@ -121,6 +125,16 @@ func (r *Router) isProviderAvailable(name, model string) bool {
return false
}
func (r *Router) selectByRoundRobin(candidates []string) (adapter.ProviderAdapter, error) {
if len(candidates) == 0 {
return nil, gwerror.NewGatewayError(gwerror.ROUTER_NO_PROVIDER_AVAILABLE, "no available provider")
}
// 使用原子操作进行轮询选择
index := atomic.AddUint64(&r.roundRobinCounter, 1) - 1
return r.providers[candidates[index%uint64(len(candidates))]], nil
}
func (r *Router) selectByLatency(candidates []string) (adapter.ProviderAdapter, error) {
var bestProvider adapter.ProviderAdapter
var minLatency int64 = math.MaxInt64

View File

@@ -0,0 +1,51 @@
package router
import (
"context"
"testing"
)
// TestP2_04_StrategyRoundRobin_NotImplemented 验证RoundRobin策略是否真正实现
// P2-04: StrategyRoundRobin定义了但走default分支
func TestP2_04_StrategyRoundRobin_NotImplemented(t *testing.T) {
// 创建3个provider都设置不同的延迟
// 如果走latency策略延迟最低的会被持续选中
// 如果走RoundRobin策略应该轮询选择
r := NewRouter(StrategyRoundRobin)
prov1 := &mockProvider{name: "p1", models: []string{"gpt-4"}, healthy: true}
prov2 := &mockProvider{name: "p2", models: []string{"gpt-4"}, healthy: true}
prov3 := &mockProvider{name: "p3", models: []string{"gpt-4"}, healthy: true}
r.RegisterProvider("p1", prov1)
r.RegisterProvider("p2", prov2)
r.RegisterProvider("p3", prov3)
// 设置不同的延迟 - p1延迟最低
r.health["p1"].LatencyMs = 10
r.health["p2"].LatencyMs = 20
r.health["p3"].LatencyMs = 30
// 选择100次统计每个provider被选中的次数
counts := map[string]int{"p1": 0, "p2": 0, "p3": 0}
const iterations = 99 // 99能被3整除
for i := 0; i < iterations; i++ {
selected, err := r.SelectProvider(context.Background(), "gpt-4")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
counts[selected.ProviderName()]++
}
t.Logf("Selection counts with different latencies: p1=%d, p2=%d, p3=%d", counts["p1"], counts["p2"], counts["p3"])
// 如果走latency策略p1应该几乎100%被选中
// 如果走RoundRobin应该约33% each
// 严格检查如果p1被选中了超过50次说明走的是latency策略而不是round_robin
if counts["p1"] > iterations/2 {
t.Errorf("RoundRobin strategy appears to NOT be implemented. p1 was selected %d/%d times (%.1f%%), which indicates latency-based selection is being used instead.",
counts["p1"], iterations, float64(counts["p1"])*100/float64(iterations))
}
}