// 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 });
}
};