Skip to content

Commit 75cdadf

Browse files
committed
fix: 编辑器图片表单上传
1 parent 83776ee commit 75cdadf

1 file changed

Lines changed: 57 additions & 12 deletions

File tree

app/editor/EditorPageClient.tsx

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,17 @@ import Link from "next/link";
1313
import type { Session } from "next-auth";
1414
import { buildDocsNewUrl } from "@/lib/github";
1515
import {
16-
FILENAME_PATTERN,
16+
MAX_SLUG_LENGTH,
1717
ensureMarkdownExtension,
18+
sanitizeSlug,
1819
stripMarkdownExtension,
20+
validateSlug,
1921
} from "@/lib/submission";
22+
import {
23+
MAX_IMAGE_UPLOAD_BYTES,
24+
isAllowedImageContentType,
25+
isExtensionAllowedForContentType,
26+
} from "@/lib/uploads";
2027

2128
interface EditorPageClientProps {
2229
session: Session;
@@ -58,6 +65,8 @@ function buildFrontmatter({
5865
return lines.join("\n");
5966
}
6067

68+
const MAX_IMAGE_SIZE_MB = MAX_IMAGE_UPLOAD_BYTES / (1024 * 1024);
69+
6170
/**
6271
* 编辑器页面客户端组件
6372
* 包含表单、编辑器和发布按钮
@@ -82,6 +91,24 @@ export function EditorPageClient({ session }: EditorPageClientProps) {
8291
file: File,
8392
articleSlug: string,
8493
): Promise<{ blobUrl: string; publicUrl: string }> => {
94+
const contentType = file.type.toLowerCase();
95+
96+
if (!validateSlug(articleSlug)) {
97+
throw new Error("文章 slug 不合法,请检查文件名。");
98+
}
99+
100+
if (!isAllowedImageContentType(contentType)) {
101+
throw new Error("仅支持上传 jpg/png/gif/webp 图片。");
102+
}
103+
104+
if (!isExtensionAllowedForContentType(file.name, contentType)) {
105+
throw new Error("文件扩展名与图片类型不匹配。");
106+
}
107+
108+
if (file.size <= 0 || file.size > MAX_IMAGE_UPLOAD_BYTES) {
109+
throw new Error(`图片大小需在 0 - ${MAX_IMAGE_SIZE_MB}MB 之间。`);
110+
}
111+
85112
// 1. 获取预签名 URL
86113
const response = await fetch("/api/upload", {
87114
method: "POST",
@@ -90,8 +117,9 @@ export function EditorPageClient({ session }: EditorPageClientProps) {
90117
},
91118
body: JSON.stringify({
92119
filename: file.name,
93-
contentType: file.type,
120+
contentType,
94121
articleSlug,
122+
fileSize: file.size,
95123
}),
96124
});
97125

@@ -100,15 +128,26 @@ export function EditorPageClient({ session }: EditorPageClientProps) {
100128
throw new Error(error.error || "获取上传链接失败");
101129
}
102130

103-
const { uploadUrl, publicUrl } = await response.json();
131+
const { uploadUrl, publicUrl, fields } = (await response.json()) as {
132+
uploadUrl: string;
133+
publicUrl: string;
134+
fields: Record<string, string>;
135+
};
136+
137+
if (!uploadUrl || !fields) {
138+
throw new Error("上传参数缺失,请稍后重试。");
139+
}
104140

105141
// 2. 上传文件到 R2
142+
const formData = new FormData();
143+
Object.entries(fields).forEach(([field, value]) => {
144+
formData.append(field, value);
145+
});
146+
formData.append("file", file);
147+
106148
const uploadResponse = await fetch(uploadUrl, {
107-
method: "PUT",
108-
headers: {
109-
"Content-Type": file.type,
110-
},
111-
body: file,
149+
method: "POST",
150+
body: formData,
112151
});
113152

114153
if (!uploadResponse.ok) {
@@ -139,8 +178,16 @@ export function EditorPageClient({ session }: EditorPageClientProps) {
139178

140179
const normalizedFilename = ensureMarkdownExtension(filename);
141180
const filenameBase = stripMarkdownExtension(normalizedFilename);
142-
if (!filenameBase || !FILENAME_PATTERN.test(filenameBase)) {
143-
alert("文件名仅支持英文、数字、连字符或下划线,并需以字母或数字开头。");
181+
const articleSlug = sanitizeSlug(filenameBase);
182+
183+
if (
184+
!articleSlug ||
185+
!validateSlug(articleSlug) ||
186+
articleSlug !== filenameBase
187+
) {
188+
alert(
189+
`文件名仅支持英文、数字、连字符或下划线,并需以字母或数字开头且不超过 ${MAX_SLUG_LENGTH} 个字符。`,
190+
);
144191
return;
145192
}
146193

@@ -166,9 +213,7 @@ export function EditorPageClient({ session }: EditorPageClientProps) {
166213
console.log("文件名:", normalizedFilename);
167214
console.log("投稿目录:", destinationPath);
168215
console.log("图片数量:", imageCount);
169-
170216
let finalMarkdown = markdown;
171-
const articleSlug = filenameBase;
172217

173218
// 如果有图片,上传到 R2 并替换 URL
174219
const editorHandle = editorRef.current;

0 commit comments

Comments
 (0)