mirror of
https://github.com/MarSeventh/CloudFlare-ImgBed.git
synced 2026-04-24 22:25:07 +00:00
完善目录功能
This commit is contained in:
@@ -905,11 +905,11 @@ Web端在登录页面输入你的**认证码**即可登录使用;API端需要
|
||||
29. :white_check_mark:~~进行删除、加入白名单、加入黑名单等操作时,自动清除CF CDN缓存,避免延迟生效~~(2024.12.11已完成)
|
||||
30. :white_check_mark:~~管理端批量选择时,记录用户选择的顺序~~(2024.12.20已完成)
|
||||
31. :memo:上传图片支持自定义上传路径,支持相册功能
|
||||
- 文件夹删除功能
|
||||
- ~~文件夹删除功能~~(2025.3.6已完成)
|
||||
- 文件位置移动功能
|
||||
- 管理端加载更多数据时鬼打墙问题修复
|
||||
- 管理端批量操作适配文件夹
|
||||
- 管理端分页逻辑调整
|
||||
- ~~管理端加载更多数据时鬼打墙问题修复~~(2025.3.6已完成)
|
||||
- ~~管理端批量操作适配文件夹~~(2025.3.6已完成)
|
||||
- ~~管理端分页逻辑调整~~(2025.3.6已完成)
|
||||
32. :white_check_mark:~~支持多个 Telegram Bot Token 负载均衡~~(2025.2.4已完成)
|
||||
33. :white_check_mark:~~管理端提供详细的设置信息和设置方式引导~~(2025.2.5已完成)
|
||||
34. :white_check_mark:~~Logo焕新、登录页面优化、设置提示项等多项展示效果优化~~(2025.2.2已完成)
|
||||
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
BIN
css/393.b006f9eb.css.gz
Normal file
BIN
css/393.b006f9eb.css.gz
Normal file
Binary file not shown.
@@ -12,8 +12,52 @@ export async function onRequest(context) {
|
||||
data, // arbitrary space for passing data between middlewares
|
||||
} = context;
|
||||
|
||||
// 组装 CDN URL
|
||||
const url = new URL(request.url);
|
||||
|
||||
// 读取folder参数,判断是否为文件夹删除请求
|
||||
const folder = url.searchParams.get('folder');
|
||||
if (folder === 'true') {
|
||||
try {
|
||||
// 调用list API获取指定目录下的所有文件
|
||||
const folderPath = params.path.join('/');
|
||||
|
||||
const listUrl = new URL(`${url.origin}/api/manage/list?count=-1&dir=${folderPath}`);
|
||||
const listRequest = new Request(listUrl, request);
|
||||
|
||||
const listResponse = await fetch(listRequest);
|
||||
const listData = await listResponse.json();
|
||||
|
||||
const files = listData.files;
|
||||
// 调用delete API删除文件夹下的所有文件
|
||||
for (const file of files) {
|
||||
const encodedFileName = encodeURIComponent(file.name);
|
||||
|
||||
const deleteUrl = new URL(`${url.origin}/api/manage/delete/${encodedFileName}`);
|
||||
const deleteRequest = new Request(deleteUrl, request);
|
||||
|
||||
await fetch(deleteRequest);
|
||||
}
|
||||
|
||||
// 调用delete API删除所有子文件夹
|
||||
const directories = listData.directories;
|
||||
for (const dir of directories) {
|
||||
const encodedDir = encodeURIComponent(dir);
|
||||
|
||||
const deleteUrl = new URL(`${url.origin}/api/manage/delete/${encodedDir}?folder=true`);
|
||||
const deleteRequest = new Request(deleteUrl, request);
|
||||
|
||||
await fetch(deleteRequest);
|
||||
}
|
||||
|
||||
// 返回成功信息
|
||||
return new Response('Folder Deleted');
|
||||
|
||||
} catch (e) {
|
||||
return new Response('Error: Delete Folder Failed', { status: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
// 组装 CDN URL
|
||||
const cdnPath = url.pathname.replace('/api/manage/delete/', '');
|
||||
const cdnUrl = `https://${url.hostname}/file/${cdnPath}`;
|
||||
|
||||
|
||||
@@ -6,8 +6,19 @@ export async function onRequest(context) {
|
||||
let count = parseInt(url.searchParams.get('count'), 10) || 50;
|
||||
let sum = url.searchParams.get('sum') || false;
|
||||
let dir = url.searchParams.get('dir') || ''; // 目录名
|
||||
// 相对路径
|
||||
if (dir.startsWith('/')) {
|
||||
dir = dir.substring(1);
|
||||
}
|
||||
if (dir !== '' && !dir.endsWith('/')) {
|
||||
dir += '/';
|
||||
}
|
||||
|
||||
let allRecords = await getAllRecords(env);
|
||||
let allRecords = await getAllRecords(env, dir);
|
||||
|
||||
allRecords.sort((a, b) => {
|
||||
return b.metadata.TimeStamp - a.metadata.TimeStamp;
|
||||
});
|
||||
|
||||
// 解析目录下的文件和子目录
|
||||
let filteredRecords = [];
|
||||
@@ -27,15 +38,15 @@ export async function onRequest(context) {
|
||||
filteredRecords.push(record);
|
||||
} else {
|
||||
// 该目录下的子文件夹
|
||||
subdirectories.add(parts[0]);
|
||||
if (dir === '' || dir.endsWith('/')) {
|
||||
subdirectories.add(dir + parts[0]);
|
||||
} else {
|
||||
subdirectories.add(dir + '/' + parts[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 按照 metadata 中的时间戳倒序排序
|
||||
filteredRecords.sort((a, b) => {
|
||||
return b.metadata.TimeStamp - a.metadata.TimeStamp;
|
||||
});
|
||||
|
||||
// sum 参数为 true 时,只返回数据总数
|
||||
if (count === -1 && sum === 'true') {
|
||||
@@ -67,13 +78,18 @@ export async function onRequest(context) {
|
||||
});
|
||||
}
|
||||
|
||||
async function getAllRecords(env) {
|
||||
async function getAllRecords(env, dir) {
|
||||
// 按前缀列出所有文件
|
||||
let allRecords = [];
|
||||
let cursor = null;
|
||||
|
||||
while (true) {
|
||||
const limit = 1000;
|
||||
const response = await env.img_url.list({ limit, cursor });
|
||||
const response = await env.img_url.list({
|
||||
prefix: dir,
|
||||
limit: limit,
|
||||
cursor: cursor
|
||||
});
|
||||
cursor = response.cursor;
|
||||
|
||||
const filteredRecords = response.keys.filter(item => !item.name.startsWith("manage@"));
|
||||
|
||||
141
functions/api/manage/move/[[path]].js
Normal file
141
functions/api/manage/move/[[path]].js
Normal file
@@ -0,0 +1,141 @@
|
||||
import { S3Client, DeleteObjectCommand } from "@aws-sdk/client-s3";
|
||||
import { purgeCFCache } from "../../../utils/purgeCache";
|
||||
|
||||
export async function onRequest(context) {
|
||||
// Contents of context object
|
||||
const {
|
||||
request, // same as existing Worker API
|
||||
env, // same as existing Worker API
|
||||
params, // if filename includes [id] or [[path]]
|
||||
waitUntil, // same as ctx.waitUntil in existing Worker API
|
||||
next, // used for middleware or to fetch assets
|
||||
data, // arbitrary space for passing data between middlewares
|
||||
} = context;
|
||||
|
||||
const url = new URL(request.url);
|
||||
|
||||
// 读取目标文件夹
|
||||
const dist = url.searchParams.get('dist')
|
||||
? url.searchParams.get('dist').replace(/^\/+/, '') // 移除开头的/
|
||||
.replace(/\/{2,}/g, '/') // 替换多个连续的/为单个/
|
||||
.replace(/\/$/, '') // 移除末尾的/
|
||||
: '';
|
||||
|
||||
// 读取folder参数,判断是否为文件夹删除请求
|
||||
const folder = url.searchParams.get('folder');
|
||||
if (folder === 'true') {
|
||||
try {
|
||||
// 调用list API获取指定目录下的所有文件
|
||||
const folderPath = params.path.join('/');
|
||||
|
||||
const listUrl = new URL(`${url.origin}/api/manage/list?count=-1&dir=${folderPath}`);
|
||||
const listRequest = new Request(listUrl, request);
|
||||
|
||||
const listResponse = await fetch(listRequest);
|
||||
const listData = await listResponse.json();
|
||||
|
||||
const files = listData.files;
|
||||
// 调用move API移动文件夹下的所有文件
|
||||
for (const file of files) {
|
||||
const encodedFileName = encodeURIComponent(file.name);
|
||||
|
||||
const moveUrl = new URL(`${url.origin}/api/manage/move/${encodedFileName}?dist=${dist}`);
|
||||
const moveRequest = new Request(moveUrl, request);
|
||||
|
||||
await fetch(moveRequest);
|
||||
}
|
||||
|
||||
const directories = listData.directories;
|
||||
// 调用move API移动所有子文件夹
|
||||
for (const dir of directories) {
|
||||
const encodedDir = encodeURIComponent(dir);
|
||||
|
||||
const moveUrl = new URL(`${url.origin}/api/manage/move/${encodedDir}?dist=${dist}&folder=true`);
|
||||
const moveRequest = new Request(moveUrl, request);
|
||||
|
||||
await fetch(moveRequest);
|
||||
}
|
||||
|
||||
// 返回成功信息
|
||||
return new Response('Folder Moved');
|
||||
|
||||
} catch (e) {
|
||||
return new Response('Error: Move Folder Failed', { status: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
// 组装 CDN URL
|
||||
const cdnPath = url.pathname.replace('/api/manage/move/', '');
|
||||
const cdnUrl = `https://${url.hostname}/file/${cdnPath}`;
|
||||
|
||||
// 从params中获取图片ID
|
||||
let fileId = '';
|
||||
try {
|
||||
// 解码params.path
|
||||
params.path = decodeURIComponent(params.path);
|
||||
// 从path中提取文件ID
|
||||
fileId = params.path.split(',').join('/');
|
||||
} catch (e) {
|
||||
return new Response('Error: Decode Image ID Failed', { status: 400 });
|
||||
}
|
||||
|
||||
// 读取文件名
|
||||
const fileKey = fileId.split('/').pop();
|
||||
const newFileId = dist === '' ? fileKey : `${dist}/${fileKey}`;
|
||||
|
||||
try {
|
||||
// 读取图片信息
|
||||
const img = await env.img_url.getWithMetadata(fileId);
|
||||
|
||||
// 如果是R2渠道的图片,需要移动R2中对应的图片
|
||||
if (img.metadata?.Channel === 'CloudflareR2') {
|
||||
const R2DataBase = env.img_r2;
|
||||
|
||||
// 获取原文件内容
|
||||
const object = await R2DataBase.get(fileId);
|
||||
if (!object) {
|
||||
return new Response('Error: R2 Object Not Found', { status: 404 });
|
||||
}
|
||||
|
||||
// 复制到新位置
|
||||
await R2DataBase.put(newFileId, object.body);
|
||||
|
||||
// 删除旧文件
|
||||
await R2DataBase.delete(fileId);
|
||||
}
|
||||
|
||||
|
||||
// 旧版 Telegram 渠道和 Telegraph 渠道不支持移动
|
||||
if (img.metadata?.Channel === 'Telegram' || img.metadata?.Channel === undefined) {
|
||||
return new Response('Error: Move Image Failed', { status: 400 });
|
||||
}
|
||||
|
||||
|
||||
// 其他渠道,直接修改KV中的id为newFileId
|
||||
await env.img_url.put(newFileId, img.value, { metadata: img.metadata });
|
||||
|
||||
// 删除原有图片
|
||||
await env.img_url.delete(fileId);
|
||||
|
||||
const info = JSON.stringify(fileId);
|
||||
|
||||
// 清除CDN缓存
|
||||
await purgeCFCache(env, cdnUrl);
|
||||
|
||||
// 清除api/randomFileList API缓存
|
||||
try {
|
||||
const cache = caches.default;
|
||||
// 通过写入一个max-age=0的response来清除缓存
|
||||
const nullResponse = new Response(null, {
|
||||
headers: { 'Cache-Control': 'max-age=0' },
|
||||
});
|
||||
await cache.put(`${url.origin}/api/randomFileList`, nullResponse);
|
||||
} catch (error) {
|
||||
console.error('Failed to clear cache:', error);
|
||||
}
|
||||
|
||||
return new Response(info);
|
||||
} catch (e) {
|
||||
return new Response('Error: Move Image Failed', { status: 400 });
|
||||
}
|
||||
}
|
||||
@@ -168,7 +168,7 @@ export async function onRequest(context) { // Contents of context object
|
||||
let TgFileID = ''; // Tg的file_id
|
||||
if (imgRecord.metadata?.Channel === 'Telegram') {
|
||||
// id为file_id + ext
|
||||
TgFileID = params.id.split('.')[0];
|
||||
TgFileID = fileId.split('.')[0];
|
||||
} else if (imgRecord.metadata?.Channel === 'TelegramNew') {
|
||||
// id为unique_id + file_name
|
||||
TgFileID = imgRecord.metadata?.TgFileId;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/logo.png"><title>Sanyue ImgHub</title><script defer="defer" src="/js/app.80a61b60.js"></script><link href="/css/app.9a1a6b51.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but sanyue_imghub doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html><style>/* 下拉菜单样式 */
|
||||
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/logo.png"><title>Sanyue ImgHub</title><script defer="defer" src="/js/app.d162089f.js"></script><link href="/css/app.9a1a6b51.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but sanyue_imghub doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html><style>/* 下拉菜单样式 */
|
||||
.el-dropdown__popper.el-popper {
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
|
||||
BIN
index.html.gz
BIN
index.html.gz
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
BIN
js/393.78f09ad0.js.gz
Normal file
BIN
js/393.78f09ad0.js.gz
Normal file
Binary file not shown.
1
js/393.78f09ad0.js.map
Normal file
1
js/393.78f09ad0.js.map
Normal file
File diff suppressed because one or more lines are too long
BIN
js/393.78f09ad0.js.map.gz
Normal file
BIN
js/393.78f09ad0.js.map.gz
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
BIN
js/app.d162089f.js.gz
Normal file
BIN
js/app.d162089f.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
BIN
js/app.d162089f.js.map.gz
Normal file
BIN
js/app.d162089f.js.map.gz
Normal file
Binary file not shown.
Reference in New Issue
Block a user