fix: 修复竞态条件、重置逻辑、无匹配终态等关键问题

- 用 Prisma $transaction 实现 atomicUpdateRoom,防止并发写入覆盖
- 新增 POST /api/room/[id]/reset 端点,修复"再来一轮"按钮死循环
- 新增 swipeCounts 字段追踪滑动进度,检测"无人匹配"终态
- 着陆页 handleCreate 增加 res.ok 检查,防止跳转到无效房间
- 匹配或无匹配后停止轮询,减少无效请求
This commit is contained in:
2026-02-24 17:04:16 +08:00
parent d87d30ccc0
commit 77d15f29e3
11 changed files with 204 additions and 78 deletions
+33 -7
View File
@@ -5,6 +5,7 @@ export interface RoomData {
users: string[];
restaurants: Restaurant[];
likes: Record<string, string[]>;
swipeCounts: Record<string, number>;
match: string | null;
}
@@ -12,11 +13,22 @@ function generateRoomId(): string {
return String(Math.floor(1000 + Math.random() * 9000));
}
function normalize(raw: Partial<RoomData>): RoomData {
return {
users: raw.users ?? [],
restaurants: raw.restaurants ?? [],
likes: raw.likes ?? {},
swipeCounts: raw.swipeCounts ?? {},
match: raw.match ?? null,
};
}
export async function createRoom(restaurants: Restaurant[]): Promise<string> {
const data: RoomData = {
users: [],
restaurants,
likes: {},
swipeCounts: {},
match: null,
};
@@ -47,15 +59,29 @@ export async function getRoomData(
): Promise<RoomData | null> {
const room = await prisma.room.findUnique({ where: { id: roomId } });
if (!room) return null;
return JSON.parse(room.data) as RoomData;
return normalize(JSON.parse(room.data));
}
export async function updateRoomData(
/**
* Atomic read-modify-write within a Prisma transaction.
* Prevents race conditions when multiple users swipe concurrently.
*/
export async function atomicUpdateRoom(
roomId: string,
data: RoomData,
): Promise<void> {
await prisma.room.update({
where: { id: roomId },
data: { data: JSON.stringify(data) },
updater: (data: RoomData) => RoomData,
): Promise<RoomData | null> {
return prisma.$transaction(async (tx) => {
const room = await tx.room.findUnique({ where: { id: roomId } });
if (!room) return null;
const data = normalize(JSON.parse(room.data));
const updated = updater(data);
await tx.room.update({
where: { id: roomId },
data: { data: JSON.stringify(updated) },
});
return updated;
});
}