优化房间点赞同步为增量更新策略

This commit is contained in:
2026-03-03 13:07:22 +08:00
parent 325b7b5742
commit 4a5ed3b25a
3 changed files with 134 additions and 17 deletions
+58
View File
@@ -0,0 +1,58 @@
import { describe, it, expect } from "vitest";
import { diffRoomLikes } from "@/lib/roomRepository";
describe("diffRoomLikes", () => {
it("returns incremental create/delete changes", () => {
const current = {
r1: ["u1", "u2"],
r2: ["u3"],
};
const updated = {
r1: ["u1", "u3"],
r3: ["u2"],
};
const result = diffRoomLikes(current, updated);
expect(result.toDelete).toEqual(
expect.arrayContaining([
{ userId: "u2", restaurantId: "r1" },
{ userId: "u3", restaurantId: "r2" },
]),
);
expect(result.toCreate).toEqual(
expect.arrayContaining([
{ userId: "u3", restaurantId: "r1" },
{ userId: "u2", restaurantId: "r3" },
]),
);
});
it("deduplicates repeated user likes", () => {
const current = {
r1: ["u1", "u1", "u1"],
};
const updated = {
r1: ["u1"],
};
const result = diffRoomLikes(current, updated);
expect(result.toCreate).toEqual([]);
expect(result.toDelete).toEqual([]);
});
it("returns empty arrays when likes are unchanged", () => {
const current = {
r1: ["u1", "u2"],
};
const updated = {
r1: ["u1", "u2"],
};
const result = diffRoomLikes(current, updated);
expect(result.toCreate).toEqual([]);
expect(result.toDelete).toEqual([]);
});
});
+69 -12
View File
@@ -16,8 +16,56 @@ export interface RoomData {
scene: SceneType;
}
interface RoomLikeRow {
userId: string;
restaurantId: string;
}
const ROOM_ID_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
function normalizeLikeRows(likes: Record<string, string[]>): RoomLikeRow[] {
const rows: RoomLikeRow[] = [];
const seen = new Set<string>();
for (const [restaurantId, userIds] of Object.entries(likes)) {
for (const userId of userIds) {
const key = `${userId}::${restaurantId}`;
if (seen.has(key)) continue;
seen.add(key);
rows.push({ userId, restaurantId });
}
}
return rows;
}
export function diffRoomLikes(
currentLikes: Record<string, string[]>,
updatedLikes: Record<string, string[]>,
): { toCreate: RoomLikeRow[]; toDelete: RoomLikeRow[] } {
const currentRows = normalizeLikeRows(currentLikes);
const updatedRows = normalizeLikeRows(updatedLikes);
const currentMap = new Map(
currentRows.map((row) => [`${row.userId}::${row.restaurantId}`, row]),
);
const updatedMap = new Map(
updatedRows.map((row) => [`${row.userId}::${row.restaurantId}`, row]),
);
const toCreate: RoomLikeRow[] = [];
const toDelete: RoomLikeRow[] = [];
for (const [key, row] of updatedMap) {
if (!currentMap.has(key)) toCreate.push(row);
}
for (const [key, row] of currentMap) {
if (!updatedMap.has(key)) toDelete.push(row);
}
return { toCreate, toDelete };
}
function generateRoomId(): string {
let id = "";
for (let i = 0; i < 6; i++) {
@@ -181,18 +229,27 @@ export async function atomicUpdateRoom(
});
}
// Sync likes: rebuild from updated data
if (JSON.stringify(current.likes) !== JSON.stringify(updated.likes)) {
await tx.roomLike.deleteMany({ where: { roomId } });
const likeRows: { roomId: string; userId: string; restaurantId: string }[] = [];
for (const [restaurantId, userIds] of Object.entries(updated.likes)) {
for (const userId of userIds) {
likeRows.push({ roomId, userId, restaurantId });
}
}
if (likeRows.length > 0) {
await tx.roomLike.createMany({ data: likeRows });
}
// Sync likes incrementally to avoid full delete + rebuild on every change.
const { toCreate, toDelete } = diffRoomLikes(current.likes, updated.likes);
if (toDelete.length > 0) {
await tx.roomLike.deleteMany({
where: {
roomId,
OR: toDelete.map((row) => ({
userId: row.userId,
restaurantId: row.restaurantId,
})),
},
});
}
if (toCreate.length > 0) {
await tx.roomLike.createMany({
data: toCreate.map((row) => ({
roomId,
userId: row.userId,
restaurantId: row.restaurantId,
})),
});
}
// Sync swipes