fix: 服务端验证强化 — 房间ID/坐标/swipe/盲盒竞态/空格
- #15: 房间 ID 扩展为 6 位字母数字,createRoom 用 P2002 重试替代 find-then-create - #16: 盲盒编辑/删除改用 updateMany/deleteMany 原子操作,防止 TOCTOU - #17: lat/lng 用 Number.isFinite + 范围校验 (-90~90, -180~180) - #18: swipe action 必须为 'like' 或 'pass' - #19: user PUT 的 JSON.parse(preferences) 加 try/catch - #26: requireString 拒绝纯空格字符串
This commit is contained in:
+19
-14
@@ -1,4 +1,5 @@
|
||||
import { prisma } from "./prisma";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { Restaurant, SceneType } from "@/types";
|
||||
|
||||
const ROOM_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
||||
@@ -15,8 +16,14 @@ export interface RoomData {
|
||||
scene: SceneType;
|
||||
}
|
||||
|
||||
const ROOM_ID_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
||||
|
||||
function generateRoomId(): string {
|
||||
return String(Math.floor(1000 + Math.random() * 9000));
|
||||
let id = "";
|
||||
for (let i = 0; i < 6; i++) {
|
||||
id += ROOM_ID_CHARS[Math.floor(Math.random() * ROOM_ID_CHARS.length)];
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
function normalize(raw: Partial<RoomData>): RoomData {
|
||||
@@ -69,26 +76,24 @@ export async function createRoom(restaurants: Restaurant[], creatorId: string, s
|
||||
};
|
||||
|
||||
const expiresAt = new Date(Date.now() + ROOM_TTL_MS);
|
||||
let roomId: string;
|
||||
let attempts = 0;
|
||||
const payload = JSON.stringify(data);
|
||||
|
||||
while (attempts < 20) {
|
||||
roomId = generateRoomId();
|
||||
const existing = await prisma.room.findUnique({ where: { id: roomId } });
|
||||
if (!existing) {
|
||||
for (let attempts = 0; attempts < 20; attempts++) {
|
||||
const roomId = generateRoomId();
|
||||
try {
|
||||
await prisma.room.create({
|
||||
data: { id: roomId, data: JSON.stringify(data), expiresAt },
|
||||
data: { id: roomId, data: payload, expiresAt },
|
||||
});
|
||||
return roomId;
|
||||
} catch (e) {
|
||||
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2002") {
|
||||
continue;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
attempts++;
|
||||
}
|
||||
|
||||
roomId = generateRoomId() + String(Date.now()).slice(-2);
|
||||
await prisma.room.create({
|
||||
data: { id: roomId, data: JSON.stringify(data), expiresAt },
|
||||
});
|
||||
return roomId;
|
||||
throw new Error("无法生成唯一房间号,请稍后重试");
|
||||
}
|
||||
|
||||
export async function getRoomData(
|
||||
|
||||
@@ -43,7 +43,7 @@ export function validateRoomName(raw: unknown, fallback = "我们的周末"): st
|
||||
}
|
||||
|
||||
export function requireString(value: unknown, fieldName: string): string {
|
||||
if (!value || typeof value !== "string") {
|
||||
if (!value || typeof value !== "string" || !value.trim()) {
|
||||
throw new ApiError(`${fieldName}不能为空`);
|
||||
}
|
||||
return value;
|
||||
|
||||
Reference in New Issue
Block a user