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

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
+7 -5
View File
@@ -211,12 +211,14 @@
- 证据:
- `src/lib/planQueries.ts` 已由“逐条房间查询”改为“单次查询带房间关系”。
### R2 `atomicUpdateRoom` 对 likes 的“全删全建”策略成本较高
### R2 `atomicUpdateRoom` 对 likes 的“全删全建”策略成本较高【已完成】
- 修复状态:✅ 已完成(2026-03-03
- 修复内容:
-`roomRepository` 中新增 `diffRoomLikes`,按差集计算 `toCreate/toDelete`
- `atomicUpdateRoom` 改为 likes 增量更新(`deleteMany + createMany` 仅处理变更项),替代全量重建;
- 补充 `src/lib/roomRepository.test.ts` 验证增量 diff 行为与去重逻辑。
- 证据:
- `src/lib/roomRepository.ts:185-195`
- 建议:
- 按增量 diff 更新(新增/删除差集)替代全量重建;
- 对高频路径(swipe/undo)优先优化。
- `src/lib/roomRepository.ts` 已移除 likes 全删全建逻辑,改为差量同步。
### R3 请求参数契约不统一(前端仍大量发送已废弃 `userId`)
- 证据:
+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