181 lines
5.6 KiB
JavaScript
Executable File
181 lines
5.6 KiB
JavaScript
Executable File
export default {
|
||
async fetch(request, env) {
|
||
const url = new URL(request.url);
|
||
const path = url.pathname.split('/');
|
||
|
||
// 处理CORS
|
||
if (request.method === 'OPTIONS') {
|
||
return new Response(null, {
|
||
headers: {
|
||
'Access-Control-Allow-Origin': '*',
|
||
'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',
|
||
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
|
||
}
|
||
});
|
||
}
|
||
|
||
// 文件上传
|
||
if (path[1] === 'upload' && request.method === 'POST') {
|
||
try {
|
||
const formData = await request.formData();
|
||
const file = formData.get('file');
|
||
const password = formData.get('password') || null;
|
||
const expiresAt = formData.get('expires') ? new Date(formData.get('expires')) : null;
|
||
|
||
if (!file) {
|
||
return new Response(JSON.stringify({ error: 'No file provided' }), {
|
||
status: 400,
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Access-Control-Allow-Origin': '*'
|
||
}
|
||
});
|
||
}
|
||
|
||
// 生成唯一文件ID
|
||
const fileId = crypto.randomUUID();
|
||
|
||
// 上传文件到R2,将 FILES_BUCKET 改为 file_share_bucket
|
||
await env.file_share_bucket.put(fileId, file);
|
||
|
||
// 存储文件元数据到D1
|
||
await env.DB.prepare(
|
||
'INSERT INTO files (id, name, type, size, password, created_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?)'
|
||
).bind(
|
||
fileId,
|
||
file.name,
|
||
file.type,
|
||
file.size,
|
||
password,
|
||
new Date().toISOString(),
|
||
expiresAt ? expiresAt.toISOString() : null
|
||
).run();
|
||
|
||
return new Response(JSON.stringify({
|
||
success: true,
|
||
fileId: fileId,
|
||
url: `${url.origin}/file/${fileId}`
|
||
}), {
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Access-Control-Allow-Origin': '*'
|
||
}
|
||
});
|
||
} catch (error) {
|
||
return new Response(JSON.stringify({ error: error.message }), {
|
||
status: 500,
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Access-Control-Allow-Origin': '*'
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
// 文件下载
|
||
if (path[1] === 'file' && path[2]) {
|
||
const fileId = path[2];
|
||
const password = url.searchParams.get('password') || null;
|
||
|
||
try {
|
||
// 获取文件元数据
|
||
const fileInfo = await env.DB.prepare(
|
||
'SELECT * FROM files WHERE id = ?'
|
||
).bind(fileId).first();
|
||
|
||
if (!fileInfo) {
|
||
return new Response(JSON.stringify({ error: 'File not found' }), {
|
||
status: 404,
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Access-Control-Allow-Origin': '*'
|
||
}
|
||
});
|
||
}
|
||
|
||
// 检查文件是否过期
|
||
if (fileInfo.expires_at && new Date(fileInfo.expires_at) < new Date()) {
|
||
return new Response(JSON.stringify({ error: 'File has expired' }), {
|
||
status: 410,
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Access-Control-Allow-Origin': '*'
|
||
}
|
||
});
|
||
}
|
||
|
||
// 检查密码
|
||
if (fileInfo.password && fileInfo.password !== password) {
|
||
return new Response(JSON.stringify({ error: 'Invalid password' }), {
|
||
status: 401,
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Access-Control-Allow-Origin': '*'
|
||
}
|
||
});
|
||
}
|
||
|
||
// 从R2获取文件,将 FILES_BUCKET 改为 file_share_bucket
|
||
const file = await env.file_share_bucket.get(fileId);
|
||
|
||
if (!file) {
|
||
return new Response(JSON.stringify({ error: 'File not found in storage' }), {
|
||
status: 404,
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Access-Control-Allow-Origin': '*'
|
||
}
|
||
});
|
||
}
|
||
|
||
// 更新下载计数
|
||
await env.DB.prepare(
|
||
'UPDATE files SET downloads = downloads + 1 WHERE id = ?'
|
||
).bind(fileId).run();
|
||
|
||
// 返回文件
|
||
return new Response(file.body, {
|
||
headers: {
|
||
'Content-Type': file.httpMetadata.contentType || 'application/octet-stream',
|
||
'Content-Disposition': `attachment; filename="${fileInfo.name}"`,
|
||
'Access-Control-Allow-Origin': '*'
|
||
}
|
||
});
|
||
} catch (error) {
|
||
return new Response(JSON.stringify({ error: error.message }), {
|
||
status: 500,
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Access-Control-Allow-Origin': '*'
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
// 修改默认响应
|
||
const apiDoc = {
|
||
message: "\u26a0\ufe0f \u8fd9\u662fAPI\u4e13\u7528\u7aef\u53e3\uff0c\u7981\u6b62\u76f4\u63a5\u8bbf\u95ee",
|
||
description: "\u8fd9\u662f\u4e00\u4e2a\u6587\u4ef6\u5b58\u50a8\u548c\u5206\u4eab\u7cfb\u7edf\u7684API\u670d\u52a1",
|
||
frontend: "<你的前端地址>",
|
||
api_endpoints: {
|
||
upload: {
|
||
method: "POST",
|
||
path: "/upload",
|
||
description: "\u4e0a\u4f20\u6587\u4ef6"
|
||
},
|
||
download: {
|
||
method: "GET",
|
||
path: "/file/:fileId",
|
||
description: "\u4e0b\u8f7d\u6587\u4ef6"
|
||
}
|
||
}
|
||
};
|
||
|
||
return new Response(JSON.stringify(apiDoc, null, 2), {
|
||
headers: {
|
||
'Content-Type': 'application/json; charset=utf-8',
|
||
'Access-Control-Allow-Origin': '*'
|
||
}
|
||
});
|
||
}
|
||
}; |