优化房间点赞同步为增量更新策略
This commit is contained in:
@@ -211,12 +211,14 @@
|
|||||||
- 证据:
|
- 证据:
|
||||||
- `src/lib/planQueries.ts` 已由“逐条房间查询”改为“单次查询带房间关系”。
|
- `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`
|
- `src/lib/roomRepository.ts` 已移除 likes 全删全建逻辑,改为差量同步。
|
||||||
- 建议:
|
|
||||||
- 按增量 diff 更新(新增/删除差集)替代全量重建;
|
|
||||||
- 对高频路径(swipe/undo)优先优化。
|
|
||||||
|
|
||||||
### R3 请求参数契约不统一(前端仍大量发送已废弃 `userId`)
|
### R3 请求参数契约不统一(前端仍大量发送已废弃 `userId`)
|
||||||
- 证据:
|
- 证据:
|
||||||
|
|||||||
@@ -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([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
+68
-11
@@ -16,8 +16,56 @@ export interface RoomData {
|
|||||||
scene: SceneType;
|
scene: SceneType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface RoomLikeRow {
|
||||||
|
userId: string;
|
||||||
|
restaurantId: string;
|
||||||
|
}
|
||||||
|
|
||||||
const ROOM_ID_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
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 {
|
function generateRoomId(): string {
|
||||||
let id = "";
|
let id = "";
|
||||||
for (let i = 0; i < 6; i++) {
|
for (let i = 0; i < 6; i++) {
|
||||||
@@ -181,18 +229,27 @@ export async function atomicUpdateRoom(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync likes: rebuild from updated data
|
// Sync likes incrementally to avoid full delete + rebuild on every change.
|
||||||
if (JSON.stringify(current.likes) !== JSON.stringify(updated.likes)) {
|
const { toCreate, toDelete } = diffRoomLikes(current.likes, updated.likes);
|
||||||
await tx.roomLike.deleteMany({ where: { roomId } });
|
if (toDelete.length > 0) {
|
||||||
const likeRows: { roomId: string; userId: string; restaurantId: string }[] = [];
|
await tx.roomLike.deleteMany({
|
||||||
for (const [restaurantId, userIds] of Object.entries(updated.likes)) {
|
where: {
|
||||||
for (const userId of userIds) {
|
roomId,
|
||||||
likeRows.push({ roomId, userId, restaurantId });
|
OR: toDelete.map((row) => ({
|
||||||
}
|
userId: row.userId,
|
||||||
}
|
restaurantId: row.restaurantId,
|
||||||
if (likeRows.length > 0) {
|
})),
|
||||||
await tx.roomLike.createMany({ data: likeRows });
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
if (toCreate.length > 0) {
|
||||||
|
await tx.roomLike.createMany({
|
||||||
|
data: toCreate.map((row) => ({
|
||||||
|
roomId,
|
||||||
|
userId: row.userId,
|
||||||
|
restaurantId: row.restaurantId,
|
||||||
|
})),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync swipes
|
// Sync swipes
|
||||||
|
|||||||
Reference in New Issue
Block a user