video-flow-toon/src/routes/project/addVisualManual.ts
2026-03-29 21:18:30 +08:00

103 lines
3.4 KiB
TypeScript

import express from "express";
import u from "@/utils";
import { error, success } from "@/lib/responseFormat";
import fs from "fs";
import path from "path";
import { validateFields } from "@/middleware/middleware";
import { z } from "zod";
const router = express.Router();
// 编辑视觉手册
export default router.post(
"/",
validateFields({
name: z.string(),
images: z.array(z.string()),
data: z.array(
z.object({
label: z.string(),
value: z.string(),
data: z.string(),
}),
),
}),
async (req, res) => {
try {
const { name, images, data } = req.body as {
name: string;
images: string[];
data: { label: string; value: string; data: string }[];
};
if (/^\d+$/.test(name)) {
res.status(400).send(error("名称不能为纯数字"));
return;
}
const mainPath = u.getPath(["skills", "art_prompts", name]);
if (fs.existsSync(mainPath)) {
return res.status(400).send(error("请勿填写重复名称的视觉手册"));
}
// 字段映射表(与 getVisualManual 保持一致)
const DATA_MAP: { value: string; subDir?: string }[] = [
{ value: "README" },
{ value: "prefix" },
{ value: "art_character", subDir: "art_prompt" },
{ value: "art_character_derivative", subDir: "art_prompt" },
{ value: "art_prop", subDir: "art_prompt" },
{ value: "art_prop_derivative", subDir: "art_prompt" },
{ value: "art_scene", subDir: "art_prompt" },
{ value: "art_scene_derivative", subDir: "art_prompt" },
{ value: "art_storyboard", subDir: "art_prompt" },
{ value: "art_storyboard_video", subDir: "art_prompt" },
{ value: "director_planning", subDir: "driector_skills" },
{ value: "director_storyboard_table", subDir: "driector_skills" },
];
// 根据 DATA_MAP 构建 value -> subDir 的映射
const SUB_DIR_MAP = new Map(DATA_MAP.map(({ value, subDir }) => [value, subDir ?? ""]));
// 合法的 value 值集合,用于校验
const VALID_KEYS = new Set(DATA_MAP.map(({ value }) => value));
for (const item of data) {
if (!VALID_KEYS.has(item.value)) continue;
const subDir = SUB_DIR_MAP.get(item.value)!;
const dirArr = subDir ? [mainPath, subDir] : [mainPath];
const filePath = u.getPath([...dirArr, `${item.value}.md`]);
const fileDir = path.dirname(filePath);
// 目录不存在时递归创建
if (!fs.existsSync(fileDir)) {
fs.mkdirSync(fileDir, { recursive: true });
}
fs.writeFileSync(filePath, item.data, "utf-8");
}
const ossImagesDir = u.getPath(["oss", name]);
let existingFiles: string[] = [];
try {
const allFiles = fs.readdirSync(ossImagesDir);
existingFiles = allFiles.filter((f) => /\.(png|jpe?g|gif|webp|svg)$/i.test(f));
} catch {}
const retainedFileNames = new Set(images.filter((item) => item.startsWith("http")).map((url) => path.basename(new URL(url).pathname)));
for (const file of existingFiles) {
if (!retainedFileNames.has(file)) {
await u.oss.deleteFile(`${name}/${file}`);
}
}
for (const item of images) {
if (!item.startsWith("http")) await u.oss.writeFile(`${name}/${u.uuid()}.jpg`, item);
}
res.status(200).send(success());
} catch (err) {
res.status(500).send({ error: String(err) });
}
},
);