// 1. AWS 签名辅助函数 (不变) async function hmacRaw(key, string) { const keyData = typeof key === "string" ? new TextEncoder().encode(key) : key; const cryptoKey = await crypto.subtle.importKey("raw", keyData, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]); return crypto.subtle.sign("HMAC", cryptoKey, new TextEncoder().encode(string)); } function toHex(buffer) { return Array.from(new Uint8Array(buffer)).map(b => b.toString(16).padStart(2, '0')).join(''); } async function sha256Hex(data) { const digest = await crypto.subtle.digest("SHA-256", typeof data === "string" ? new TextEncoder().encode(data) : data); return toHex(digest); } // 2. 带有密码锁和云端拉取逻辑的前端页面 const htmlPage = ` 极客指北 - 私人云盘

☁️ 私人云盘管理系统

📤

点击或拖拽文件到此处,支持批量上传

☁️ 云端文件记录
    `; // 3. 核心后端逻辑 (加入路由和 KV 读写) export default { async fetch(request, env) { const url = new URL(request.url); // 路由 1: 访问主页,返回 HTML if (request.method === 'GET' && url.pathname === '/') { return new Response(htmlPage, { headers: { 'Content-Type': 'text/html;charset=UTF-8' } }); } // 路由 2: 获取云端历史记录 API if (request.method === 'POST' && url.pathname === '/api/history') { try { const body = await request.json(); // 鉴权 if (body.password !== env.MY_PASSWORD) { return new Response(JSON.stringify({ success: false, error: 'Unauthorized' }), { status: 401 }); } // 从 KV 数据库读取记录 const history = await env.RECORDS.get("s3_history", { type: "json" }) || []; return new Response(JSON.stringify({ success: true, history: history }), { headers: { 'Content-Type': 'application/json' } }); } catch (e) { return new Response(JSON.stringify({ success: false, error: e.message }), { status: 500 }); } } // 路由 3: 处理文件上传 API if (request.method === 'POST' && url.pathname === '/api/upload') { try { const formData = await request.formData(); // 鉴权:拦截非法上传 if (formData.get('password') !== env.MY_PASSWORD) { return new Response(JSON.stringify({ success: false, error: '密码错误,拒绝上传' }), { status: 401 }); } const file = formData.get('file'); if (!file) throw new Error("没有找到文件"); // --- S3 原生上传流程开始 --- const fileExt = file.name.split('.').pop(); const randomStr = Math.random().toString(36).substring(2, 8); const filename = `${Date.now()}-${randomStr}.${fileExt}`; const endpoint = env.S3_ENDPOINT; const bucket = env.S3_BUCKET; const s3Url = new URL(`/${bucket}/${filename}`, endpoint); const host = s3Url.hostname; const date = new Date(); const amzDate = date.toISOString().replace(/[:-]|\.\d{3}/g, ""); const dateStamp = amzDate.substring(0, 8); const fileBuffer = await file.arrayBuffer(); const payloadHash = await sha256Hex(fileBuffer); const method = "PUT"; const canonicalUri = `/${bucket}/${filename}`; const canonicalQuerystring = ""; const contentType = file.type || "application/octet-stream"; const canonicalHeaders = `content-type:${contentType}\nhost:${host}\nx-amz-content-sha256:${payloadHash}\nx-amz-date:${amzDate}\n`; const signedHeaders = "content-type;host;x-amz-content-sha256;x-amz-date"; const canonicalRequest = `${method}\n${canonicalUri}\n${canonicalQuerystring}\n${canonicalHeaders}\n${signedHeaders}\n${payloadHash}`; const canonicalRequestHash = await sha256Hex(canonicalRequest); const algorithm = "AWS4-HMAC-SHA256"; const region = "auto"; const service = "s3"; const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`; const stringToSign = `${algorithm}\n${amzDate}\n${credentialScope}\n${canonicalRequestHash}`; const kDate = await hmacRaw(`AWS4${env.S3_SECRET_KEY}`, dateStamp); const kRegion = await hmacRaw(kDate, region); const kService = await hmacRaw(kRegion, service); const kSigning = await hmacRaw(kService, "aws4_request"); const signatureBuffer = await hmacRaw(kSigning, stringToSign); const signature = toHex(signatureBuffer); const authorizationHeader = `${algorithm} Credential=${env.S3_ACCESS_KEY}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`; const response = await fetch(s3Url.toString(), { method: "PUT", body: fileBuffer, headers: { "Authorization": authorizationHeader, "Content-Type": contentType, "x-amz-date": amzDate, "x-amz-content-sha256": payloadHash, "Content-Length": file.size.toString() } }); if (response.ok) { // --- 上传成功,将记录保存到 KV 数据库 --- const finalUrl = s3Url.toString(); const timeStr = new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }); // 先获取老数据 let history = await env.RECORDS.get("s3_history", { type: "json" }) || []; // 把新数据插到最前面 history.unshift({ name: file.name, url: finalUrl, time: timeStr }); // 为了防止数据库过大,最多保存最近的 100 条记录 if (history.length > 100) history.pop(); // 写回数据库 await env.RECORDS.put("s3_history", JSON.stringify(history)); return new Response(JSON.stringify({ success: true, url: finalUrl }), { headers: { 'Content-Type': 'application/json' } }); } else { const errorText = await response.text(); return new Response(JSON.stringify({ success: false, error: errorText }), { status: response.status }); } } catch (e) { return new Response(JSON.stringify({ success: false, error: e.message }), { status: 500 }); } } return new Response('Method Not Allowed', { status: 405 }); } };