完善目录功能

This commit is contained in:
MarSeventh
2025-03-07 00:24:31 +08:00
parent 030020e12a
commit e3baafe24b
23 changed files with 225 additions and 24 deletions

View File

@@ -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

Binary file not shown.

View File

@@ -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}`;

View File

@@ -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@"));

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

View File

@@ -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;

View File

@@ -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;

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

Binary file not shown.

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

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

Binary file not shown.

File diff suppressed because one or more lines are too long

BIN
js/app.d162089f.js.map.gz Normal file

Binary file not shown.