feat: 房间 24 小时自动过期,添加 TTL 清理机制

This commit is contained in:
2026-02-24 19:46:14 +08:00
parent b5fe3f6cc8
commit f6949a062f
3 changed files with 52 additions and 2 deletions
@@ -0,0 +1,20 @@
/*
Warnings:
- Added the required column `expiresAt` to the `Room` table without a default value. This is not possible if the table is not empty.
*/
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Room" (
"id" TEXT NOT NULL PRIMARY KEY,
"data" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"expiresAt" DATETIME NOT NULL
);
INSERT INTO "new_Room" ("createdAt", "data", "id", "expiresAt") SELECT "createdAt", "data", "id", "createdAt" FROM "Room";
DROP TABLE "Room";
ALTER TABLE "new_Room" RENAME TO "Room";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;
+1
View File
@@ -11,4 +11,5 @@ model Room {
id String @id id String @id
data String data String
createdAt DateTime @default(now()) createdAt DateTime @default(now())
expiresAt DateTime
} }
+31 -2
View File
@@ -1,6 +1,8 @@
import { prisma } from "./prisma"; import { prisma } from "./prisma";
import { Restaurant } from "@/types"; import { Restaurant } from "@/types";
const ROOM_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
export interface RoomData { export interface RoomData {
users: string[]; users: string[];
restaurants: Restaurant[]; restaurants: Restaurant[];
@@ -23,7 +25,29 @@ function normalize(raw: Partial<RoomData>): RoomData {
}; };
} }
let lastCleanup = 0;
const CLEANUP_INTERVAL_MS = 60 * 60 * 1000; // at most once per hour
async function cleanupExpiredRooms() {
const now = Date.now();
if (now - lastCleanup < CLEANUP_INTERVAL_MS) return;
lastCleanup = now;
try {
const { count } = await prisma.room.deleteMany({
where: { expiresAt: { lt: new Date() } },
});
if (count > 0) {
console.log(`Cleaned up ${count} expired room(s)`);
}
} catch (e) {
console.error("Room cleanup failed:", e);
}
}
export async function createRoom(restaurants: Restaurant[]): Promise<string> { export async function createRoom(restaurants: Restaurant[]): Promise<string> {
await cleanupExpiredRooms();
const data: RoomData = { const data: RoomData = {
users: [], users: [],
restaurants, restaurants,
@@ -32,6 +56,7 @@ export async function createRoom(restaurants: Restaurant[]): Promise<string> {
match: null, match: null,
}; };
const expiresAt = new Date(Date.now() + ROOM_TTL_MS);
let roomId: string; let roomId: string;
let attempts = 0; let attempts = 0;
@@ -40,7 +65,7 @@ export async function createRoom(restaurants: Restaurant[]): Promise<string> {
const existing = await prisma.room.findUnique({ where: { id: roomId } }); const existing = await prisma.room.findUnique({ where: { id: roomId } });
if (!existing) { if (!existing) {
await prisma.room.create({ await prisma.room.create({
data: { id: roomId, data: JSON.stringify(data) }, data: { id: roomId, data: JSON.stringify(data), expiresAt },
}); });
return roomId; return roomId;
} }
@@ -49,7 +74,7 @@ export async function createRoom(restaurants: Restaurant[]): Promise<string> {
roomId = generateRoomId() + String(Date.now()).slice(-2); roomId = generateRoomId() + String(Date.now()).slice(-2);
await prisma.room.create({ await prisma.room.create({
data: { id: roomId, data: JSON.stringify(data) }, data: { id: roomId, data: JSON.stringify(data), expiresAt },
}); });
return roomId; return roomId;
} }
@@ -59,6 +84,10 @@ export async function getRoomData(
): Promise<RoomData | null> { ): Promise<RoomData | null> {
const room = await prisma.room.findUnique({ where: { id: roomId } }); const room = await prisma.room.findUnique({ where: { id: roomId } });
if (!room) return null; if (!room) return null;
if (room.expiresAt < new Date()) {
prisma.room.delete({ where: { id: roomId } }).catch(() => {});
return null;
}
return normalize(JSON.parse(room.data)); return normalize(JSON.parse(room.data));
} }