feat: add storage quota limit for S3/R2 channels

- Add quota counter tracking (usedMB, fileCount) per channel
- Filter channels by quota threshold in fetchUploadConfig()
- Update quota on upload (endUpload) and delete
- Add /api/manage/quota API for stats and recalculation
- Fix null check in delete when file record not found
- Fix R2 channel name from hardcoded to actual channel.name
This commit is contained in:
axibayuit
2025-12-29 19:55:20 +08:00
parent 2c83f9062d
commit 3d77585ec5
6 changed files with 275 additions and 4 deletions

View File

@@ -4,6 +4,45 @@ import { getPageConfig } from '../api/manage/sysConfig/page';
import { getOthersConfig } from '../api/manage/sysConfig/others';
import { getDatabase } from './databaseAdapter.js';
/**
* 根据容量限制过滤渠道
* @param {Object} db - 数据库实例
* @param {Array} channels - 渠道列表
* @returns {Array} 过滤后的渠道列表
*/
async function filterChannelsByQuota(db, channels) {
const result = [];
for (const channel of channels) {
// 未启用容量限制,直接通过
if (!channel.quota?.enabled || !channel.quota?.limitGB) {
result.push(channel);
continue;
}
try {
const quotaKey = `manage@quota@${channel.name}`;
const quotaData = await db.get(quotaKey);
const quota = quotaData ? JSON.parse(quotaData) : { usedMB: 0, fileCount: 0 };
const usedGB = quota.usedMB / 1024;
const limitGB = channel.quota.limitGB;
const threshold = channel.quota.threshold || 95;
// 未超过阈值,渠道可用
if ((usedGB / limitGB) * 100 < threshold) {
result.push(channel);
} else {
console.log(`Channel ${channel.name} quota exceeded: ${usedGB.toFixed(2)}GB / ${limitGB}GB (${threshold}% threshold)`);
}
} catch (error) {
console.error(`Failed to check quota for channel ${channel.name}:`, error);
// 检查失败时保守处理,允许使用该渠道
result.push(channel);
}
}
return result;
}
export async function fetchUploadConfig(env) {
try {
const db = getDatabase(env);
@@ -13,6 +52,10 @@ export async function fetchUploadConfig(env) {
settings.cfr2.channels = settings.cfr2.channels.filter((channel) => channel.enabled);
settings.s3.channels = settings.s3.channels.filter((channel) => channel.enabled);
// 根据容量限制过滤渠道(仅 R2 和 S3
settings.cfr2.channels = await filterChannelsByQuota(db, settings.cfr2.channels);
settings.s3.channels = await filterChannelsByQuota(db, settings.s3.channels);
return settings;
} catch (error) {
console.error('Failed to fetch upload config:', error);