mirror of
https://github.com/MarSeventh/CloudFlare-ImgBed.git
synced 2026-05-03 10:26:58 +00:00
- Add frontend direct upload to HuggingFace S3, bypassing CF Workers limits - Add /api/huggingface/getUploadUrl endpoint to get LFS upload URLs - Add /api/huggingface/commitUpload endpoint to commit file references - Support multipart upload for very large files - SHA256 computed in frontend to avoid CF Workers CPU timeout - Small files (<20MB) still use proxy upload through CF Workers
106 lines
3.8 KiB
JavaScript
106 lines
3.8 KiB
JavaScript
/**
|
|
* HuggingFace 大文件直传 API
|
|
*
|
|
* 流程:
|
|
* 1. 前端计算 SHA256 和文件样本
|
|
* 2. 前端调用此 API 获取 LFS 上传 URL
|
|
* 3. 前端直接上传到 HuggingFace S3
|
|
* 4. 前端调用 commitUpload API 提交文件引用
|
|
*
|
|
* 这样可以绕过 CF Workers 的 100MB 请求体限制和 CPU 时间限制
|
|
*/
|
|
|
|
import { HuggingFaceAPI } from '../../utils/huggingfaceAPI.js';
|
|
import { getUploadConfig } from '../../utils/sysConfig.js';
|
|
|
|
export async function onRequestPost(context) {
|
|
const { request, env } = context;
|
|
|
|
try {
|
|
// 验证认证码
|
|
const authCode = request.headers.get('authcode');
|
|
if (env.AUTH_CODE && authCode !== env.AUTH_CODE) {
|
|
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
|
|
status: 401,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
|
|
const body = await request.json();
|
|
const { fileSize, fileName, sha256, fileSample, channelName } = body;
|
|
|
|
if (!fileSize || !fileName || !sha256 || !fileSample) {
|
|
return new Response(JSON.stringify({
|
|
error: 'Missing required fields: fileSize, fileName, sha256, fileSample'
|
|
}), {
|
|
status: 400,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
|
|
// 获取 HuggingFace 配置
|
|
const uploadConfig = await getUploadConfig(env);
|
|
const hfSettings = uploadConfig.huggingface;
|
|
|
|
if (!hfSettings || !hfSettings.channels || hfSettings.channels.length === 0) {
|
|
return new Response(JSON.stringify({ error: 'No HuggingFace channel configured' }), {
|
|
status: 400,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
|
|
// 选择渠道
|
|
let hfChannel;
|
|
if (channelName) {
|
|
hfChannel = hfSettings.channels.find(c => c.name === channelName);
|
|
}
|
|
if (!hfChannel) {
|
|
hfChannel = hfSettings.loadBalance?.enabled
|
|
? hfSettings.channels[Math.floor(Math.random() * hfSettings.channels.length)]
|
|
: hfSettings.channels[0];
|
|
}
|
|
|
|
if (!hfChannel || !hfChannel.token || !hfChannel.repo) {
|
|
return new Response(JSON.stringify({ error: 'HuggingFace channel not properly configured' }), {
|
|
status: 400,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
|
|
// 构建文件路径
|
|
const now = new Date();
|
|
const yearMonth = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}`;
|
|
|
|
// 生成唯一文件名
|
|
const ext = fileName.includes('.') ? fileName.substring(fileName.lastIndexOf('.')) : '';
|
|
const uniqueId = crypto.randomUUID().replace(/-/g, '');
|
|
const fullId = uniqueId + ext;
|
|
const filePath = `images/${yearMonth}/${fullId}`;
|
|
|
|
// 获取 LFS 上传信息
|
|
const huggingfaceAPI = new HuggingFaceAPI(hfChannel.token, hfChannel.repo, hfChannel.isPrivate || false);
|
|
const uploadInfo = await huggingfaceAPI.getLfsUploadInfo(fileSize, filePath, sha256, fileSample);
|
|
|
|
// 返回上传信息
|
|
return new Response(JSON.stringify({
|
|
success: true,
|
|
fullId,
|
|
filePath,
|
|
channelName: hfChannel.name,
|
|
repo: hfChannel.repo,
|
|
isPrivate: hfChannel.isPrivate || false,
|
|
...uploadInfo
|
|
}), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('getUploadUrl error:', error.message);
|
|
return new Response(JSON.stringify({ error: error.message }), {
|
|
status: 500,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
}
|