feat: add user download traffic reports and restore OSS direct downloads

This commit is contained in:
2026-02-17 17:36:26 +08:00
parent 7687397954
commit 3a22b88f23
4 changed files with 600 additions and 8 deletions

View File

@@ -365,6 +365,21 @@ function initDatabase() {
)
`);
// 下载流量日统计表(用于用户侧报表)
db.exec(`
CREATE TABLE IF NOT EXISTS user_download_traffic_daily (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
date_key TEXT NOT NULL, -- YYYY-MM-DD本地时区
bytes_used INTEGER NOT NULL DEFAULT 0,
download_count INTEGER NOT NULL DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE(user_id, date_key),
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
)
`);
// 日志表索引
db.exec(`
CREATE INDEX IF NOT EXISTS idx_logs_created_at ON system_logs(created_at);
@@ -377,6 +392,9 @@ function initDatabase() {
-- 优势:快速查询用户最近的活动记录,支持时间范围过滤
-- 使用场景:用户活动历史、审计日志查询
CREATE INDEX IF NOT EXISTS idx_logs_user_created ON system_logs(user_id, created_at);
-- 下载流量报表索引(按用户+日期查询)
CREATE INDEX IF NOT EXISTS idx_download_traffic_user_date ON user_download_traffic_daily(user_id, date_key);
`);
console.log('[数据库性能优化] ✓ 日志表复合索引已创建');
@@ -1421,6 +1439,104 @@ function migrateDownloadTrafficFields() {
}
}
// 下载流量报表(按天聚合)
const DownloadTrafficReportDB = {
normalizeDays(days, defaultDays = 30, maxDays = 365) {
const parsed = Number(days);
if (!Number.isFinite(parsed) || parsed <= 0) {
return defaultDays;
}
return Math.min(maxDays, Math.max(1, Math.floor(parsed)));
},
formatDateKey(date = new Date()) {
const target = date instanceof Date ? date : new Date(date);
if (Number.isNaN(target.getTime())) {
return null;
}
const year = target.getFullYear();
const month = String(target.getMonth() + 1).padStart(2, '0');
const day = String(target.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
},
addUsage(userId, bytesUsed, downloadCount = 1, date = new Date()) {
const uid = Number(userId);
const bytes = Math.floor(Number(bytesUsed));
const count = Math.floor(Number(downloadCount));
const dateKey = this.formatDateKey(date);
if (!Number.isFinite(uid) || uid <= 0 || !Number.isFinite(bytes) || bytes <= 0 || !dateKey) {
return null;
}
const normalizedCount = Number.isFinite(count) && count > 0 ? count : 1;
return db.prepare(`
INSERT INTO user_download_traffic_daily (user_id, date_key, bytes_used, download_count, created_at, updated_at)
VALUES (?, ?, ?, ?, datetime('now', 'localtime'), datetime('now', 'localtime'))
ON CONFLICT(user_id, date_key)
DO UPDATE SET
bytes_used = user_download_traffic_daily.bytes_used + excluded.bytes_used,
download_count = user_download_traffic_daily.download_count + excluded.download_count,
updated_at = datetime('now', 'localtime')
`).run(uid, dateKey, bytes, normalizedCount);
},
getDailyUsage(userId, days = 30) {
const uid = Number(userId);
if (!Number.isFinite(uid) || uid <= 0) {
return [];
}
const safeDays = this.normalizeDays(days);
const offset = `-${safeDays - 1} days`;
return db.prepare(`
SELECT
date_key,
bytes_used,
download_count
FROM user_download_traffic_daily
WHERE user_id = ?
AND date_key >= date('now', 'localtime', ?)
ORDER BY date_key ASC
`).all(uid, offset);
},
getPeriodSummary(userId, days = null) {
const uid = Number(userId);
if (!Number.isFinite(uid) || uid <= 0) {
return { bytes_used: 0, download_count: 0 };
}
if (days === null || days === undefined) {
const row = db.prepare(`
SELECT
COALESCE(SUM(bytes_used), 0) AS bytes_used,
COALESCE(SUM(download_count), 0) AS download_count
FROM user_download_traffic_daily
WHERE user_id = ?
`).get(uid);
return row || { bytes_used: 0, download_count: 0 };
}
const safeDays = this.normalizeDays(days);
const offset = `-${safeDays - 1} days`;
const row = db.prepare(`
SELECT
COALESCE(SUM(bytes_used), 0) AS bytes_used,
COALESCE(SUM(download_count), 0) AS download_count
FROM user_download_traffic_daily
WHERE user_id = ?
AND date_key >= date('now', 'localtime', ?)
`).get(uid, offset);
return row || { bytes_used: 0, download_count: 0 };
}
};
// 系统日志操作
const SystemLogDB = {
// 日志级别常量
@@ -1616,6 +1732,7 @@ module.exports = {
SettingsDB,
VerificationDB,
PasswordResetTokenDB,
DownloadTrafficReportDB,
SystemLogDB,
TransactionDB,
WalManager