修复:调整游客取号限流策略

问题:移动网络共享出口 IP 会造成游客取号被误限流。

实现:移除公开取号 IP 限制,改用项目总量与手机号 HMAC 限流,并支持 Retry-After 倒计时。
This commit is contained in:
2026-07-31 22:56:33 +08:00
parent 3f4a9bf398
commit c2a5281534
17 changed files with 357 additions and 73 deletions

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
@@ -154,6 +155,43 @@ func TestPublicCreateTicketIgnoresDuplicateFromEndedSession(t *testing.T) {
if afterCallRecorder.Code != http.StatusCreated {
t.Fatalf("after-call status = %d, want 201; body = %s", afterCallRecorder.Code, afterCallRecorder.Body.String())
}
server.publicTicketProjectLimiter = newQueryLimiter(server.now, 10, time.Minute)
server.publicTicketPhoneLimiter = newQueryLimiter(server.now, 1, time.Minute)
var replayBody, replayKey string
for index := 0; index < 6; index++ {
body := fmt.Sprintf(`{"phone":"1390000%04d","party_size":1}`, index)
key := "same-mobile-ip-" + uuid.NewString()
recorder := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodPost, "/api/public/projects/"+projectID+"/tickets", strings.NewReader(body))
request.RemoteAddr = "198.51.100.27:42000"
request.Header.Set("Idempotency-Key", key)
server.Handler().ServeHTTP(recorder, request)
if recorder.Code != http.StatusCreated {
t.Fatalf("same-IP distinct phone %d status = %d, want 201; body = %s", index, recorder.Code, recorder.Body.String())
}
if index == 0 {
replayBody, replayKey = body, key
}
}
replayRecorder := httptest.NewRecorder()
replayRequest := httptest.NewRequest(http.MethodPost, "/api/public/projects/"+projectID+"/tickets", strings.NewReader(replayBody))
replayRequest.RemoteAddr = "198.51.100.27:42000"
replayRequest.Header.Set("Idempotency-Key", replayKey)
server.Handler().ServeHTTP(replayRecorder, replayRequest)
if replayRecorder.Code != http.StatusCreated {
t.Fatalf("idempotent replay status = %d, want 201; body = %s", replayRecorder.Code, replayRecorder.Body.String())
}
limitedRecorder := httptest.NewRecorder()
limitedRequest := httptest.NewRequest(http.MethodPost, "/api/public/projects/"+projectID+"/tickets", strings.NewReader(replayBody))
limitedRequest.RemoteAddr = "203.0.113.44:42000"
limitedRequest.Header.Set("Idempotency-Key", "new-intent-"+uuid.NewString())
server.Handler().ServeHTTP(limitedRecorder, limitedRequest)
if limitedRecorder.Code != http.StatusTooManyRequests {
t.Fatalf("same project/phone new intent status = %d, want 429; body = %s", limitedRecorder.Code, limitedRecorder.Body.String())
}
}
func TestInternalPhoneLookupUsesLatestActiveSessionPerProject(t *testing.T) {