新增解答题拍照判分:百炼 Qwen-VL 按评分点逐点判分,无 API Key 时演示判分回退;混合卷按真实分值加权计分;判分照片与评分点进错题本;补齐历史迁移缺口(0005 模板生效方式/0006 五张缺失表+grammar_progress point_id)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
4033abc21d
commit
f80a931ef0
@@ -0,0 +1,49 @@
|
||||
// xuchao: 本地开发照片回退——OSS 未配置时,解答题照片直传/查看走本地磁盘
|
||||
// 生产环境配置了 OSS 或 NODE_ENV=production 时,本路由一律 404
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { ossConfigured } from "@/lib/oss";
|
||||
|
||||
const BASE = path.join(process.cwd(), ".dev-uploads");
|
||||
|
||||
function disabled() {
|
||||
return ossConfigured() || process.env.NODE_ENV === "production";
|
||||
}
|
||||
|
||||
// xuchao: 键名白名单校验 + 防路径穿越
|
||||
function safePath(key: string): string | null {
|
||||
if (!/^exam-solve\/\d+\/[\w-]+\/q\d+-[0-9a-f]{16}\.jpg$/.test(key)) return null;
|
||||
const p = path.normalize(path.join(BASE, key));
|
||||
return p.startsWith(BASE + path.sep) ? p : null;
|
||||
}
|
||||
|
||||
// xuchao: 直传入口:multipart file + key
|
||||
export async function POST(req: NextRequest) {
|
||||
if (disabled()) return NextResponse.json({ error: "not available" }, { status: 404 });
|
||||
const form = await req.formData();
|
||||
const file = form.get("file");
|
||||
const key = form.get("key");
|
||||
if (!(file instanceof File) || typeof key !== "string") {
|
||||
return NextResponse.json({ error: "缺少文件或键名" }, { status: 400 });
|
||||
}
|
||||
const target = safePath(key);
|
||||
if (!target) return NextResponse.json({ error: "键名不合法" }, { status: 400 });
|
||||
fs.mkdirSync(path.dirname(target), { recursive: true });
|
||||
const buf = Buffer.from(await file.arrayBuffer());
|
||||
if (buf.byteLength > 8 * 1024 * 1024) {
|
||||
return NextResponse.json({ error: "照片过大" }, { status: 400 });
|
||||
}
|
||||
fs.writeFileSync(target, buf);
|
||||
return NextResponse.json({ ok: true, key });
|
||||
}
|
||||
|
||||
// xuchao: 查看入口:?key=xxx
|
||||
export async function GET(req: NextRequest) {
|
||||
if (disabled()) return NextResponse.json({ error: "not available" }, { status: 404 });
|
||||
const key = req.nextUrl.searchParams.get("key") ?? "";
|
||||
const target = safePath(key);
|
||||
if (!target || !fs.existsSync(target)) return NextResponse.json({ error: "不存在" }, { status: 404 });
|
||||
const buf = fs.readFileSync(target);
|
||||
return new NextResponse(buf, { headers: { "Content-Type": "image/jpeg" } });
|
||||
}
|
||||
Reference in New Issue
Block a user