From 9c7f18e0faf946c691aa7cd1ecbe95ee97e5143f Mon Sep 17 00:00:00 2001 From: kurihada Date: Thu, 26 Feb 2026 20:14:02 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=94=A8=E6=88=B7=E5=90=8D=E5=94=AF?= =?UTF-8?q?=E4=B8=80=E6=80=A7=E7=AB=9E=E6=80=81=E5=A4=84=E7=90=86=20+=20?= =?UTF-8?q?=E5=AF=86=E7=A0=81=E9=95=BF=E5=BA=A6=E4=B8=8A=E9=99=90=20+=20JS?= =?UTF-8?q?ON.parse=20=E5=AE=89=E5=85=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - #6: register/user PUT 捕获 P2002 返回 409,apiHandler 全局兜底 - #8: GET /api/user 的 JSON.parse(preferences) 加 try/catch 防崩溃 - #12: 密码校验加 128 字符上限防 DoS - #29: ApiError.name 设为 "ApiError" 便于调试 --- src/app/api/auth/register/route.ts | 35 +++++++++++++++++------------- src/app/api/user/route.ts | 35 ++++++++++++++++++++---------- src/lib/api.ts | 11 ++++++++++ src/lib/validation.ts | 3 +++ 4 files changed, 57 insertions(+), 27 deletions(-) diff --git a/src/app/api/auth/register/route.ts b/src/app/api/auth/register/route.ts index 9fbbfd1..ea8c7bd 100644 --- a/src/app/api/auth/register/route.ts +++ b/src/app/api/auth/register/route.ts @@ -1,5 +1,6 @@ import { NextResponse } from "next/server"; import { prisma } from "@/lib/prisma"; +import { Prisma } from "@prisma/client"; import bcrypt from "bcryptjs"; import { apiHandler, ApiError } from "@/lib/api"; import { validateUsername, validatePassword } from "@/lib/validation"; @@ -12,22 +13,26 @@ export const POST = apiHandler(async (req) => { const trimmedUsername = validateUsername(username); validatePassword(password); - const existing = await prisma.user.findUnique({ where: { username: trimmedUsername } }); - if (existing) throw new ApiError("用户名已被注册", 409); - const passwordHash = await bcrypt.hash(password, 10); - const user = await prisma.user.create({ - data: { - username: trimmedUsername, - passwordHash, - avatar: avatar || "🐱", - }, - }); + try { + const user = await prisma.user.create({ + data: { + username: trimmedUsername, + passwordHash, + avatar: avatar || "🐱", + }, + }); - return NextResponse.json({ - id: user.id, - username: user.username, - avatar: user.avatar, - }); + return NextResponse.json({ + id: user.id, + username: user.username, + avatar: user.avatar, + }); + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2002") { + throw new ApiError("用户名已被注册", 409); + } + throw e; + } }); diff --git a/src/app/api/user/route.ts b/src/app/api/user/route.ts index 6f07c9e..d9a0a2d 100644 --- a/src/app/api/user/route.ts +++ b/src/app/api/user/route.ts @@ -1,5 +1,6 @@ import { NextResponse } from "next/server"; import { prisma } from "@/lib/prisma"; +import { Prisma } from "@prisma/client"; import bcrypt from "bcryptjs"; import { apiHandler, ApiError, requireUserId, requireUser } from "@/lib/api"; import { validateUsername, validatePassword, validateEmail } from "@/lib/validation"; @@ -13,12 +14,15 @@ export const GET = apiHandler(async (req) => { const decisionCount = await prisma.decision.count({ where: { userId } }); + let preferences = {}; + try { preferences = JSON.parse(user.preferences); } catch { /* fallback */ } + return NextResponse.json({ id: user.id, username: user.username, avatar: user.avatar, email: user.email, - preferences: JSON.parse(user.preferences), + preferences, createdAt: user.createdAt.toISOString(), decisionCount, }); @@ -63,16 +67,23 @@ export const PUT = apiHandler(async (req) => { updateData.preferences = JSON.stringify(body.preferences); } - const user = await prisma.user.update({ - where: { id: userId }, - data: updateData, - }); + try { + const user = await prisma.user.update({ + where: { id: userId }, + data: updateData, + }); - return NextResponse.json({ - id: user.id, - username: user.username, - avatar: user.avatar, - email: user.email, - preferences: JSON.parse(user.preferences), - }); + return NextResponse.json({ + id: user.id, + username: user.username, + avatar: user.avatar, + email: user.email, + preferences: JSON.parse(user.preferences), + }); + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2002") { + throw new ApiError("用户名已被占用", 409); + } + throw e; + } }); diff --git a/src/lib/api.ts b/src/lib/api.ts index 33d1e46..f3879dc 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1,4 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; +import { Prisma } from "@prisma/client"; import { prisma } from "@/lib/prisma"; export class ApiError extends Error { @@ -7,6 +8,7 @@ export class ApiError extends Error { public status: number = 400, ) { super(message); + this.name = "ApiError"; } } @@ -45,6 +47,15 @@ export function apiHandler(handler: RouteHandler): RouteHandler { if (e instanceof ApiError) { return NextResponse.json({ error: e.message }, { status: e.status }); } + if ( + e instanceof Prisma.PrismaClientKnownRequestError && + e.code === "P2002" + ) { + return NextResponse.json( + { error: "该记录已存在或值已被占用" }, + { status: 409 }, + ); + } console.error(`[API ${req.method} ${req.nextUrl.pathname}]`, e); return NextResponse.json({ error: "操作失败" }, { status: 500 }); } diff --git a/src/lib/validation.ts b/src/lib/validation.ts index c9f5b5b..f400757 100644 --- a/src/lib/validation.ts +++ b/src/lib/validation.ts @@ -12,6 +12,9 @@ export function validatePassword(password: string, label = "密码"): void { if (password.length < 6) { throw new ApiError(`${label}至少 6 个字符`); } + if (password.length > 128) { + throw new ApiError(`${label}不能超过 128 个字符`); + } } export function validateEmail(email: string): void {