统一盲盒请求契约并移除冗余 userId 参数

This commit is contained in:
2026-03-03 13:11:33 +08:00
parent 4a5ed3b25a
commit 532d8ff7ad
7 changed files with 30 additions and 34 deletions
+7 -5
View File
@@ -220,12 +220,14 @@
- 证据: - 证据:
- `src/lib/roomRepository.ts` 已移除 likes 全删全建逻辑,改为差量同步。 - `src/lib/roomRepository.ts` 已移除 likes 全删全建逻辑,改为差量同步。
### R3 请求参数契约不统一(前端仍大量发送已废弃 `userId`) ### R3 请求参数契约不统一(前端仍大量发送已废弃 `userId`)【已完成】
- 修复状态:✅ 已完成(2026-03-03
- 修复内容:
- 盲盒主链路前端请求已去除冗余 `userId``useBlindboxIdeas``useBlindboxDraw``useBlindboxPlan``useBlindboxRoom``blindbox/page`);
- `useBlindboxRooms` 改为基于登录态启用请求,接口统一使用鉴权 cookie,不再拼接 `userId` query
- 保留房间实时 SSE 的 `userId` 参数(用于已修复的成员校验链路),其余盲盒链路契约已统一。
- 证据: - 证据:
- 例如 `src/hooks/useBlindboxIdeas.ts:57/71/104/...``src/hooks/useBlindboxPlan.ts:50/68/...` - `rg` 检索显示盲盒前端链路已无 `?userId=``userId: profile.id` 传参。
- 建议:
- 明确“鉴权由 cookie/token 提供”,清理冗余 `userId` 参数;
- 用 Zod schema 统一约束(`src/lib/schemas/requests.ts` 目前未形成全链路使用)。
### R4 统一 API 调用层(减少重复 fetch + 错误处理分散) ### R4 统一 API 调用层(减少重复 fetch + 错误处理分散)
- 现状: - 现状:
+3 -4
View File
@@ -172,7 +172,7 @@ export default function BlindboxLobbyPage() {
const [showAuth, setShowAuth] = useState(false); const [showAuth, setShowAuth] = useState(false);
const toast = useToast(); const toast = useToast();
const { rooms: swrRooms, isLoading: swrLoading, isUnauthorized, error: swrError, mutate: mutateRooms } = useBlindboxRooms( const { rooms: swrRooms, isLoading: swrLoading, isUnauthorized, error: swrError, mutate: mutateRooms } = useBlindboxRooms(
loggedIn && profile ? profile.id : undefined, loggedIn,
); );
const rooms = swrRooms; const rooms = swrRooms;
const loading = !loggedIn ? false : swrLoading; const loading = !loggedIn ? false : swrLoading;
@@ -233,7 +233,7 @@ export default function BlindboxLobbyPage() {
const res = await fetch("/api/blindbox/room", { const res = await fetch("/api/blindbox/room", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ userId: profile.id, name: name || undefined }), body: JSON.stringify({ name: name || undefined }),
}); });
const data = await res.json(); const data = await res.json();
if (!res.ok) throw new Error(data.error); if (!res.ok) throw new Error(data.error);
@@ -254,7 +254,7 @@ export default function BlindboxLobbyPage() {
const res = await fetch("/api/blindbox/room/join", { const res = await fetch("/api/blindbox/room/join", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ userId: profile.id, code }), body: JSON.stringify({ code }),
}); });
const data = await res.json(); const data = await res.json();
if (!res.ok) throw new Error(data.error); if (!res.ok) throw new Error(data.error);
@@ -273,7 +273,6 @@ export default function BlindboxLobbyPage() {
const res = await fetch(`/api/blindbox/room/${room.code}`, { const res = await fetch(`/api/blindbox/room/${room.code}`, {
method: "DELETE", method: "DELETE",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ userId: profile.id }),
}); });
if (!res.ok) { if (!res.ok) {
const data = await res.json(); const data = await res.json();
+1 -1
View File
@@ -81,7 +81,7 @@ export function useBlindboxDraw(
const res = await fetch("/api/blindbox/draw", { const res = await fetch("/api/blindbox/draw", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ roomId: room.id, userId: profile.id }), body: JSON.stringify({ roomId: room.id }),
}); });
if (!res.ok) { if (!res.ok) {
+5 -5
View File
@@ -62,7 +62,7 @@ export function useBlindboxIdeas(room: RoomInfo | null, profile: UserProfile | n
const fetchIdeas = useCallback(async () => { const fetchIdeas = useCallback(async () => {
if (!room || !profile) return; if (!room || !profile) return;
try { try {
const res = await fetch(`/api/blindbox?roomId=${room.id}&userId=${profile.id}`); const res = await fetch(`/api/blindbox?roomId=${room.id}`);
if (res.ok) { if (res.ok) {
const data = await res.json(); const data = await res.json();
setPoolCount(data.poolCount ?? 0); setPoolCount(data.poolCount ?? 0);
@@ -76,7 +76,7 @@ export function useBlindboxIdeas(room: RoomInfo | null, profile: UserProfile | n
if (!room || !profile) return; if (!room || !profile) return;
setSuggestionsLoading(true); setSuggestionsLoading(true);
try { try {
const res = await fetch(`/api/blindbox/suggest?roomId=${room.id}&userId=${profile.id}`); const res = await fetch(`/api/blindbox/suggest?roomId=${room.id}`);
if (res.ok) { if (res.ok) {
const data = await res.json(); const data = await res.json();
if (data.suggestions?.length > 0) { if (data.suggestions?.length > 0) {
@@ -109,7 +109,7 @@ export function useBlindboxIdeas(room: RoomInfo | null, profile: UserProfile | n
const res = await fetch("/api/blindbox", { const res = await fetch("/api/blindbox", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ roomId: room.id, userId: profile.id, content: text }), body: JSON.stringify({ roomId: room.id, content: text }),
}); });
if (!res.ok) { if (!res.ok) {
const data = await res.json(); const data = await res.json();
@@ -156,7 +156,7 @@ export function useBlindboxIdeas(room: RoomInfo | null, profile: UserProfile | n
const res = await fetch("/api/blindbox", { const res = await fetch("/api/blindbox", {
method: "PUT", method: "PUT",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ideaId, userId: profile.id, content: trimmed }), body: JSON.stringify({ ideaId, content: trimmed }),
}); });
if (!res.ok) { if (!res.ok) {
const data = await res.json(); const data = await res.json();
@@ -188,7 +188,7 @@ export function useBlindboxIdeas(room: RoomInfo | null, profile: UserProfile | n
const res = await fetch("/api/blindbox", { const res = await fetch("/api/blindbox", {
method: "DELETE", method: "DELETE",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ideaId, userId: profile.id }), body: JSON.stringify({ ideaId }),
}); });
if (!res.ok) { if (!res.ok) {
const data = await res.json(); const data = await res.json();
+10 -14
View File
@@ -1,7 +1,6 @@
"use client"; "use client";
import { useState, useCallback, useRef, useEffect } from "react"; import { useState, useCallback, useRef, useEffect } from "react";
import { getCachedProfile } from "@/lib/userId";
import { useToast } from "@/hooks/useToast"; import { useToast } from "@/hooks/useToast";
import type { RoomInfo } from "@/hooks/useBlindboxRoom"; import type { RoomInfo } from "@/hooks/useBlindboxRoom";
import type { WeekendPlanData, UserProfile } from "@/types"; import type { WeekendPlanData, UserProfile } from "@/types";
@@ -47,7 +46,7 @@ export function useBlindboxPlan(
const fetchAcceptedPlan = useCallback(async () => { const fetchAcceptedPlan = useCallback(async () => {
if (!room || !profile) return; if (!room || !profile) return;
try { try {
const res = await fetch(`/api/blindbox/plan?mode=latest&roomId=${room.id}&userId=${profile.id}`); const res = await fetch(`/api/blindbox/plan?mode=latest&roomId=${room.id}`);
if (!res.ok) return; if (!res.ok) return;
const data = await res.json(); const data = await res.json();
if (data.plan) { if (data.plan) {
@@ -65,7 +64,7 @@ export function useBlindboxPlan(
if (!profile) return; if (!profile) return;
(async () => { (async () => {
try { try {
const res = await fetch(`/api/blindbox/plan?mode=pending&userId=${profile.id}`); const res = await fetch("/api/blindbox/plan?mode=pending");
if (!res.ok) return; if (!res.ok) return;
const data = await res.json(); const data = await res.json();
if (data.pending?.length) setPendingContracts(data.pending); if (data.pending?.length) setPendingContracts(data.pending);
@@ -93,13 +92,10 @@ export function useBlindboxPlan(
}); });
n.onclick = () => { window.focus(); n.close(); }; n.onclick = () => { window.focus(); n.close(); };
} }
const p = getCachedProfile(); fetch("/api/blindbox/plan?mode=pending")
if (p) { .then((r) => r.json())
fetch(`/api/blindbox/plan?mode=pending&userId=${p.id}`) .then((d) => { if (d.pending?.length) setPendingContracts(d.pending); })
.then((r) => r.json()) .catch((e) => { console.error("refreshPendingContracts failed:", e); });
.then((d) => { if (d.pending?.length) setPendingContracts(d.pending); })
.catch((e) => { console.error("refreshPendingContracts failed:", e); });
}
}, ms); }, ms);
return () => clearTimeout(timer); return () => clearTimeout(timer);
@@ -110,7 +106,7 @@ export function useBlindboxPlan(
setGenerating(true); setGenerating(true);
setPhase("planning"); setPhase("planning");
setPlanStatusMessages([PLAN_STATUS_STEPS[0]]); setPlanStatusMessages([PLAN_STATUS_STEPS[0]]);
const payload = { roomId: room.id, userId: profile.id, availableTime: timeConfig }; const payload = { roomId: room.id, availableTime: timeConfig };
const stepRef = { current: 0 }; const stepRef = { current: 0 };
const fallbackTimer = setInterval(() => { const fallbackTimer = setInterval(() => {
stepRef.current = (stepRef.current + 1) % PLAN_STATUS_STEPS.length; stepRef.current = (stepRef.current + 1) % PLAN_STATUS_STEPS.length;
@@ -195,7 +191,7 @@ export function useBlindboxPlan(
const res = await fetch("/api/blindbox/plan", { const res = await fetch("/api/blindbox/plan", {
method: "PATCH", method: "PATCH",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ planId, userId: profile.id, action: "update_plan", days: newDays }), body: JSON.stringify({ planId, action: "update_plan", days: newDays }),
}); });
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error || "保存失败"); if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error || "保存失败");
} catch (e) { } catch (e) {
@@ -212,7 +208,7 @@ export function useBlindboxPlan(
const res = await fetch("/api/blindbox/plan/refine", { const res = await fetch("/api/blindbox/plan/refine", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ userId: profile.id, instruction, days: planDays }), body: JSON.stringify({ instruction, days: planDays }),
}); });
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error || "AI 调整失败"); if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error || "AI 调整失败");
const data = await res.json(); const data = await res.json();
@@ -233,7 +229,7 @@ export function useBlindboxPlan(
const res = await fetch("/api/blindbox/plan", { const res = await fetch("/api/blindbox/plan", {
method: "PATCH", method: "PATCH",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ planId, userId: profile.id, action: "accept" }), body: JSON.stringify({ planId, action: "accept" }),
}); });
const data = await res.json().catch(() => ({})); const data = await res.json().catch(() => ({}));
if (!res.ok) { if (!res.ok) {
+2 -3
View File
@@ -77,7 +77,7 @@ export function useBlindboxRoom(code: string) {
const res = await fetch("/api/blindbox/room/join", { const res = await fetch("/api/blindbox/room/join", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ userId: profile.id, code }), body: JSON.stringify({ code }),
}); });
if (res.ok) { setIsMember(true); fetchRoom(); } if (res.ok) { setIsMember(true); fetchRoom(); }
} catch (e) { console.error("handleJoinRoom failed:", e); } } catch (e) { console.error("handleJoinRoom failed:", e); }
@@ -99,7 +99,7 @@ export function useBlindboxRoom(code: string) {
const patchRes = await fetch(`/api/blindbox/room/${room.code}`, { const patchRes = await fetch(`/api/blindbox/room/${room.code}`, {
method: "PATCH", method: "PATCH",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ userId: profile.id, city: cityName, address: addressLabel, lat, lng }), body: JSON.stringify({ city: cityName, address: addressLabel, lat, lng }),
}); });
if (!patchRes.ok) throw new Error("保存位置失败"); if (!patchRes.ok) throw new Error("保存位置失败");
setRoom((prev) => prev ? { ...prev, city: cityName, address: addressLabel, lat, lng } : prev); setRoom((prev) => prev ? { ...prev, city: cityName, address: addressLabel, lat, lng } : prev);
@@ -123,7 +123,6 @@ export function useBlindboxRoom(code: string) {
const res = await fetch(`/api/blindbox/room/${room.code}`, { const res = await fetch(`/api/blindbox/room/${room.code}`, {
method: "DELETE", method: "DELETE",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ userId: profile.id }),
}); });
if (!res.ok) { if (!res.ok) {
const data = await res.json(); const data = await res.json();
+2 -2
View File
@@ -18,9 +18,9 @@ interface RoomsResponse {
rooms: RoomSummary[]; rooms: RoomSummary[];
} }
export function useBlindboxRooms(userId: string | undefined) { export function useBlindboxRooms(enabled: boolean) {
const { data, error, isLoading, mutate } = useSWR<RoomsResponse>( const { data, error, isLoading, mutate } = useSWR<RoomsResponse>(
userId ? `/api/blindbox/rooms?userId=${userId}` : null, enabled ? "/api/blindbox/rooms" : null,
fetcher, fetcher,
{ {
revalidateOnFocus: true, revalidateOnFocus: true,