mirror of
https://github.com/ZSCGR/CloudFlare-ImgBed.git
synced 2026-08-13 04:03:42 +08:00
Merge pull request #643 from jeroldtsao/feat/concurrent-batch-image-delete
feat(manage): add concurrent batch image deletion
This commit is contained in:
@@ -27,6 +27,7 @@ import * as apiManageCusConfigBlockipList from '../../functions/api/manage/cusCo
|
|||||||
import * as apiManageCusConfigFiles from '../../functions/api/manage/cusConfig/files.js';
|
import * as apiManageCusConfigFiles from '../../functions/api/manage/cusConfig/files.js';
|
||||||
import * as apiManageCusConfigList from '../../functions/api/manage/cusConfig/list.js';
|
import * as apiManageCusConfigList from '../../functions/api/manage/cusConfig/list.js';
|
||||||
import * as apiManageCusConfigWhiteip from '../../functions/api/manage/cusConfig/whiteip.js';
|
import * as apiManageCusConfigWhiteip from '../../functions/api/manage/cusConfig/whiteip.js';
|
||||||
|
import * as apiManageDeleteBatch from '../../functions/api/manage/delete/batch.js';
|
||||||
import * as apiManageSysConfigOthers from '../../functions/api/manage/sysConfig/others.js';
|
import * as apiManageSysConfigOthers from '../../functions/api/manage/sysConfig/others.js';
|
||||||
import * as apiManageSysConfigPage from '../../functions/api/manage/sysConfig/page.js';
|
import * as apiManageSysConfigPage from '../../functions/api/manage/sysConfig/page.js';
|
||||||
import * as apiManageSysConfigSecurity from '../../functions/api/manage/sysConfig/security.js';
|
import * as apiManageSysConfigSecurity from '../../functions/api/manage/sysConfig/security.js';
|
||||||
@@ -77,6 +78,7 @@ const routes = [
|
|||||||
{ path: '/api/manage/cusConfig/files', module: apiManageCusConfigFiles, middlewares: [mw_api, mw_api_manage] },
|
{ path: '/api/manage/cusConfig/files', module: apiManageCusConfigFiles, middlewares: [mw_api, mw_api_manage] },
|
||||||
{ path: '/api/manage/cusConfig/list', module: apiManageCusConfigList, middlewares: [mw_api, mw_api_manage] },
|
{ path: '/api/manage/cusConfig/list', module: apiManageCusConfigList, middlewares: [mw_api, mw_api_manage] },
|
||||||
{ path: '/api/manage/cusConfig/whiteip', module: apiManageCusConfigWhiteip, middlewares: [mw_api, mw_api_manage] },
|
{ path: '/api/manage/cusConfig/whiteip', module: apiManageCusConfigWhiteip, middlewares: [mw_api, mw_api_manage] },
|
||||||
|
{ path: '/api/manage/delete/batch', module: apiManageDeleteBatch, middlewares: [mw_api, mw_api_manage] },
|
||||||
{ path: '/api/manage/sysConfig/others', module: apiManageSysConfigOthers, middlewares: [mw_api, mw_api_manage] },
|
{ path: '/api/manage/sysConfig/others', module: apiManageSysConfigOthers, middlewares: [mw_api, mw_api_manage] },
|
||||||
{ path: '/api/manage/sysConfig/page', module: apiManageSysConfigPage, middlewares: [mw_api, mw_api_manage] },
|
{ path: '/api/manage/sysConfig/page', module: apiManageSysConfigPage, middlewares: [mw_api, mw_api_manage] },
|
||||||
{ path: '/api/manage/sysConfig/security', module: apiManageSysConfigSecurity, middlewares: [mw_api, mw_api_manage] },
|
{ path: '/api/manage/sysConfig/security', module: apiManageSysConfigSecurity, middlewares: [mw_api, mw_api_manage] },
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ export async function onRequest(context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 删除单个文件的核心函数
|
// 删除单个文件的核心函数
|
||||||
async function deleteFile(env, fileId, cdnUrl, url) {
|
export async function deleteFile(env, fileId, cdnUrl, url) {
|
||||||
try {
|
try {
|
||||||
// 读取图片信息
|
// 读取图片信息
|
||||||
const db = getDatabase(env);
|
const db = getDatabase(env);
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { batchRemoveFilesFromIndex } from '../../../utils/indexManager.js';
|
||||||
|
import { mapConcurrent, normalizeBatchFileIds } from '../../../utils/deleteBatch.js';
|
||||||
|
import { deleteFile } from './[[path]].js';
|
||||||
|
|
||||||
|
const MAX_BATCH_SIZE = 500;
|
||||||
|
const DELETE_CONCURRENCY = 10;
|
||||||
|
|
||||||
|
const corsHeaders = {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
||||||
|
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||||
|
'Access-Control-Max-Age': '86400',
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function onRequestPost(context) {
|
||||||
|
const payload = await context.request.json();
|
||||||
|
const fileIds = normalizeBatchFileIds(payload?.fileIds, MAX_BATCH_SIZE);
|
||||||
|
if (fileIds.length === 0) {
|
||||||
|
return jsonResponse({ success: false, error: 'fileIds is required' }, 400);
|
||||||
|
}
|
||||||
|
const url = new URL(context.request.url);
|
||||||
|
const results = await mapConcurrent(fileIds, DELETE_CONCURRENCY, async (fileId) => {
|
||||||
|
const cdnUrl = `${url.origin}/file/${fileId.split('/').map(encodeURIComponent).join('/')}`;
|
||||||
|
const success = await deleteFile(context.env, fileId, cdnUrl, url);
|
||||||
|
return { fileId, success, error: success ? '' : 'Delete file failed' };
|
||||||
|
});
|
||||||
|
|
||||||
|
const deleted = results.filter((item) => item.success).map((item) => item.fileId);
|
||||||
|
const failed = results.filter((item) => !item.success).map(({ fileId, error }) => ({ fileId, error }));
|
||||||
|
if (deleted.length > 0) {
|
||||||
|
context.waitUntil(batchRemoveFilesFromIndex(context, deleted));
|
||||||
|
}
|
||||||
|
return jsonResponse({ success: failed.length === 0, deleted, failed });
|
||||||
|
} catch (error) {
|
||||||
|
return jsonResponse({ success: false, error: error.message }, 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function jsonResponse(payload, status = 200) {
|
||||||
|
return new Response(JSON.stringify(payload), {
|
||||||
|
status,
|
||||||
|
headers: { 'Content-Type': 'application/json', ...corsHeaders },
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
export function normalizeBatchFileIds(values, maxBatchSize) {
|
||||||
|
if (!Array.isArray(values)) return [];
|
||||||
|
const normalized = [...new Set(values
|
||||||
|
.map((value) => String(value || '').trim().replace(/^\/+|\/+$/g, ''))
|
||||||
|
.filter(Boolean))];
|
||||||
|
if (normalized.length > maxBatchSize) {
|
||||||
|
throw new Error(`A maximum of ${maxBatchSize} files can be deleted at once`);
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function mapConcurrent(values, concurrency, operation) {
|
||||||
|
const results = new Array(values.length);
|
||||||
|
let nextIndex = 0;
|
||||||
|
const workers = Array.from({ length: Math.min(concurrency, values.length) }, async () => {
|
||||||
|
while (nextIndex < values.length) {
|
||||||
|
const index = nextIndex++;
|
||||||
|
results[index] = await operation(values[index]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await Promise.all(workers);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user