feat: enhance download traffic quota lifecycle controls

This commit is contained in:
2026-02-17 17:19:25 +08:00
parent 2629237f9e
commit 7687397954
5 changed files with 635 additions and 53 deletions

View File

@@ -501,6 +501,24 @@ const UserDB = {
* @private
*/
_validateFieldValue(fieldName, value) {
const NULLABLE_STRING_FIELDS = new Set([
'oss_provider',
'oss_region',
'oss_access_key_id',
'oss_access_key_secret',
'oss_bucket',
'oss_endpoint',
'upload_api_key',
'verification_token',
'verification_expires_at',
'storage_permission',
'current_storage_type',
'download_traffic_quota_expires_at',
'download_traffic_reset_cycle',
'download_traffic_last_reset_at',
'theme_preference'
]);
// 字段类型白名单(根据数据库表结构定义)
const FIELD_TYPES = {
// 文本类型字段
@@ -518,6 +536,9 @@ const UserDB = {
'verification_expires_at': 'string',
'storage_permission': 'string',
'current_storage_type': 'string',
'download_traffic_quota_expires_at': 'string',
'download_traffic_reset_cycle': 'string',
'download_traffic_last_reset_at': 'string',
'theme_preference': 'string',
// 数值类型字段
@@ -542,6 +563,9 @@ const UserDB = {
// 检查类型匹配
if (expectedType === 'string') {
if (value === null) {
return NULLABLE_STRING_FIELDS.has(fieldName);
}
return typeof value === 'string';
} else if (expectedType === 'number') {
// 允许数值或可转换为数值的字符串
@@ -593,6 +617,9 @@ const UserDB = {
'oss_storage_quota': 'oss_storage_quota',
'download_traffic_quota': 'download_traffic_quota',
'download_traffic_used': 'download_traffic_used',
'download_traffic_quota_expires_at': 'download_traffic_quota_expires_at',
'download_traffic_reset_cycle': 'download_traffic_reset_cycle',
'download_traffic_last_reset_at': 'download_traffic_last_reset_at',
// 偏好设置
'theme_preference': 'theme_preference'
@@ -676,6 +703,9 @@ const UserDB = {
'oss_storage_quota': 'oss_storage_quota',
'download_traffic_quota': 'download_traffic_quota',
'download_traffic_used': 'download_traffic_used',
'download_traffic_quota_expires_at': 'download_traffic_quota_expires_at',
'download_traffic_reset_cycle': 'download_traffic_reset_cycle',
'download_traffic_last_reset_at': 'download_traffic_last_reset_at',
// 偏好设置
'theme_preference': 'theme_preference'
@@ -726,6 +756,9 @@ const UserDB = {
'upload_api_key': 'string', 'verification_token': 'string',
'verification_expires_at': 'string', 'storage_permission': 'string',
'current_storage_type': 'string', 'theme_preference': 'string',
'download_traffic_quota_expires_at': 'string',
'download_traffic_reset_cycle': 'string',
'download_traffic_last_reset_at': 'string',
'is_admin': 'number', 'is_active': 'number', 'is_banned': 'number',
'has_oss_config': 'number', 'is_verified': 'number',
'local_storage_quota': 'number', 'local_storage_used': 'number',
@@ -1298,6 +1331,9 @@ function migrateDownloadTrafficFields() {
const columns = db.prepare("PRAGMA table_info(users)").all();
const hasDownloadTrafficQuota = columns.some(col => col.name === 'download_traffic_quota');
const hasDownloadTrafficUsed = columns.some(col => col.name === 'download_traffic_used');
const hasDownloadTrafficQuotaExpiresAt = columns.some(col => col.name === 'download_traffic_quota_expires_at');
const hasDownloadTrafficResetCycle = columns.some(col => col.name === 'download_traffic_reset_cycle');
const hasDownloadTrafficLastResetAt = columns.some(col => col.name === 'download_traffic_last_reset_at');
if (!hasDownloadTrafficQuota) {
console.log('[数据库迁移] 添加 download_traffic_quota 字段...');
@@ -1311,6 +1347,24 @@ function migrateDownloadTrafficFields() {
console.log('[数据库迁移] ✓ download_traffic_used 字段已添加');
}
if (!hasDownloadTrafficQuotaExpiresAt) {
console.log('[数据库迁移] 添加 download_traffic_quota_expires_at 字段...');
db.exec('ALTER TABLE users ADD COLUMN download_traffic_quota_expires_at DATETIME DEFAULT NULL');
console.log('[数据库迁移] ✓ download_traffic_quota_expires_at 字段已添加');
}
if (!hasDownloadTrafficResetCycle) {
console.log('[数据库迁移] 添加 download_traffic_reset_cycle 字段...');
db.exec("ALTER TABLE users ADD COLUMN download_traffic_reset_cycle TEXT DEFAULT 'none'");
console.log('[数据库迁移] ✓ download_traffic_reset_cycle 字段已添加');
}
if (!hasDownloadTrafficLastResetAt) {
console.log('[数据库迁移] 添加 download_traffic_last_reset_at 字段...');
db.exec('ALTER TABLE users ADD COLUMN download_traffic_last_reset_at DATETIME DEFAULT NULL');
console.log('[数据库迁移] ✓ download_traffic_last_reset_at 字段已添加');
}
// 统一策略download_traffic_quota <= 0 表示不限流量
const quotaBackfillResult = db.prepare(`
UPDATE users
@@ -1341,6 +1395,27 @@ function migrateDownloadTrafficFields() {
if (usedCapResult.changes > 0) {
console.log(`[数据库迁移] ✓ 下载流量已用值已按配额校准: ${usedCapResult.changes} 条记录`);
}
const resetCycleBackfillResult = db.prepare(`
UPDATE users
SET download_traffic_reset_cycle = 'none'
WHERE download_traffic_reset_cycle IS NULL
OR download_traffic_reset_cycle NOT IN ('none', 'daily', 'weekly', 'monthly')
`).run();
if (resetCycleBackfillResult.changes > 0) {
console.log(`[数据库迁移] ✓ 下载流量重置周期已回填: ${resetCycleBackfillResult.changes} 条记录`);
}
const clearExpiryForUnlimitedResult = db.prepare(`
UPDATE users
SET download_traffic_quota_expires_at = NULL
WHERE download_traffic_quota <= 0
`).run();
if (clearExpiryForUnlimitedResult.changes > 0) {
console.log(`[数据库迁移] ✓ 不限流量用户已清理到期时间: ${clearExpiryForUnlimitedResult.changes} 条记录`);
}
} catch (error) {
console.error('[数据库迁移] 下载流量字段迁移失败:', error);
}