修复:调整游客取号限流策略
问题:移动网络共享出口 IP 会造成游客取号被误限流。 实现:移除公开取号 IP 限制,改用项目总量与手机号 HMAC 限流,并支持 Retry-After 倒计时。
This commit is contained in:
@@ -27,7 +27,7 @@ function keyForIntent(ref: { current: WriteIntent | null }, fingerprint: string)
|
||||
}
|
||||
|
||||
function releaseIntentAfterDefinitiveError(ref: { current: WriteIntent | null }, error: unknown) {
|
||||
if (error instanceof ApiError && error.status >= 400 && error.status < 500) ref.current = null;
|
||||
if (error instanceof ApiError && error.status >= 400 && error.status < 500 && error.status !== 429) ref.current = null;
|
||||
}
|
||||
|
||||
function ticketStatusPath(response: CreateTicketResponse): string | null {
|
||||
@@ -49,8 +49,17 @@ export function VisitorTakeTicketForm() {
|
||||
const [confirmDuplicatePhone, setConfirmDuplicatePhone] = useState(false);
|
||||
const [createdTicket, setCreatedTicket] = useState<CreateTicketResponse | null>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [retryAfterSeconds, setRetryAfterSeconds] = useState(0);
|
||||
const createIntentRef = useRef<WriteIntent | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (retryAfterSeconds <= 0) return;
|
||||
const timer = window.setTimeout(() => {
|
||||
setRetryAfterSeconds((current) => Math.max(0, current - 1));
|
||||
}, 1000);
|
||||
return () => window.clearTimeout(timer);
|
||||
}, [retryAfterSeconds]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!projects.length) return;
|
||||
if (!projects.some((project) => project.id === selectedProjectId)) {
|
||||
@@ -91,6 +100,7 @@ export function VisitorTakeTicketForm() {
|
||||
|
||||
async function submitTicket(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
if (retryAfterSeconds > 0) return;
|
||||
if (!selectedProjectId) {
|
||||
setFormError("请选择要排队的项目");
|
||||
return;
|
||||
@@ -118,14 +128,21 @@ export function VisitorTakeTicketForm() {
|
||||
setCreatedTicket(response);
|
||||
setTicketForm({ ...initialTicket, party_size: minPartySize });
|
||||
setConfirmDuplicatePhone(false);
|
||||
setRetryAfterSeconds(0);
|
||||
} catch (caught) {
|
||||
if (caught instanceof ApiError && caught.code === "DUPLICATE_PHONE") {
|
||||
createIntentRef.current = null;
|
||||
setConfirmDuplicatePhone(true);
|
||||
setFormError(null);
|
||||
setRetryAfterSeconds(0);
|
||||
} else if (caught instanceof ApiError && caught.status === 429) {
|
||||
setConfirmDuplicatePhone(false);
|
||||
setFormError(null);
|
||||
setRetryAfterSeconds(caught.retryAfterSeconds ?? 60);
|
||||
} else {
|
||||
releaseIntentAfterDefinitiveError(createIntentRef, caught);
|
||||
setConfirmDuplicatePhone(false);
|
||||
setRetryAfterSeconds(0);
|
||||
setFormError(caught instanceof ApiError ? caught.message : "取号未提交,请检查当前数据后重试。");
|
||||
}
|
||||
} finally {
|
||||
@@ -247,9 +264,10 @@ export function VisitorTakeTicketForm() {
|
||||
</fieldset>
|
||||
</div>
|
||||
{confirmDuplicatePhone ? <FeedbackBanner tone="warning" title="该手机号已有活动号码">如需继续取号,请再次点击确认按钮。</FeedbackBanner> : null}
|
||||
{retryAfterSeconds > 0 ? <FeedbackBanner tone="warning" title={`请求过于频繁,请在 ${retryAfterSeconds} 秒后重试。`} /> : null}
|
||||
{formError ? <FeedbackBanner tone="danger" title={formError} /> : null}
|
||||
<button className="button button--primary button--wide" type="submit" disabled={busy || !selectedProjectId || !projects.length}>
|
||||
{busy ? "正在创建排队号码" : confirmDuplicatePhone ? "确认继续取号" : "创建排队号码"}
|
||||
<button className="button button--primary button--wide" type="submit" disabled={busy || retryAfterSeconds > 0 || !selectedProjectId || !projects.length}>
|
||||
{busy ? "正在创建排队号码" : retryAfterSeconds > 0 ? `请等待 ${retryAfterSeconds} 秒` : confirmDuplicatePhone ? "确认继续取号" : "创建排队号码"}
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user