记录清理机制
This commit is contained in:
@@ -437,6 +437,7 @@ dockge/komodo 等 docker compose UI 也有自动更新功能
|
|||||||
| VIDEOINFO_CACHE_MINUTES | 私人影库视频信息在内存中的缓存时长(分钟) | 正整数 | 1440(1天) |
|
| VIDEOINFO_CACHE_MINUTES | 私人影库视频信息在内存中的缓存时长(分钟) | 正整数 | 1440(1天) |
|
||||||
| NEXT_PUBLIC_ENABLE_SOURCE_SEARCH | 是否开启源站寻片功能 | true/false | true |
|
| NEXT_PUBLIC_ENABLE_SOURCE_SEARCH | 是否开启源站寻片功能 | true/false | true |
|
||||||
| MAX_PLAY_RECORDS_PER_USER | 单个用户播放记录清理阈值(超过此数量将自动清理旧记录) | 正整数 | 100 |
|
| MAX_PLAY_RECORDS_PER_USER | 单个用户播放记录清理阈值(超过此数量将自动清理旧记录) | 正整数 | 100 |
|
||||||
|
| MAX_MANGA_HISTORY_PER_USER | 单个用户漫画阅读历史保留上限 | 正整数 | 100 |
|
||||||
| INIT_CONFIG | 初始配置(JSON 格式,包含 api_site、custom_category、lives 等) | JSON 字符串 | (空) |
|
| INIT_CONFIG | 初始配置(JSON 格式,包含 api_site、custom_category、lives 等) | JSON 字符串 | (空) |
|
||||||
| CONFIG_SUBSCRIPTION_URL | 配置订阅 URL(Base58 编码的配置文件地址,优先级高于 INIT_CONFIG) | URL | (空) |
|
| CONFIG_SUBSCRIPTION_URL | 配置订阅 URL(Base58 编码的配置文件地址,优先级高于 INIT_CONFIG) | URL | (空) |
|
||||||
| TMDB_API_KEY | TMDB API 密钥 | 任意字符串 | (空) |
|
| TMDB_API_KEY | TMDB API 密钥 | 任意字符串 | (空) |
|
||||||
|
|||||||
+2
-1
@@ -2068,13 +2068,14 @@ export class D1Storage implements IStorage {
|
|||||||
async cleanupOldMangaReadRecords(userName: string): Promise<void> {
|
async cleanupOldMangaReadRecords(userName: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const maxRecords = parseInt(process.env.MAX_MANGA_HISTORY_PER_USER || '100', 10);
|
const maxRecords = parseInt(process.env.MAX_MANGA_HISTORY_PER_USER || '100', 10);
|
||||||
|
const threshold = maxRecords + 10;
|
||||||
const countResult = await this.db
|
const countResult = await this.db
|
||||||
.prepare('SELECT COUNT(*) as count FROM manga_read_records WHERE username = ?')
|
.prepare('SELECT COUNT(*) as count FROM manga_read_records WHERE username = ?')
|
||||||
.bind(userName)
|
.bind(userName)
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
const count = Number(countResult?.count || 0);
|
const count = Number(countResult?.count || 0);
|
||||||
if (count <= maxRecords) return;
|
if (count <= threshold) return;
|
||||||
|
|
||||||
await this.db
|
await this.db
|
||||||
.prepare(`
|
.prepare(`
|
||||||
|
|||||||
+19
-4
@@ -95,6 +95,8 @@ const PLAY_RECORDS_KEY = 'moontv_play_records';
|
|||||||
const FAVORITES_KEY = 'moontv_favorites';
|
const FAVORITES_KEY = 'moontv_favorites';
|
||||||
const MANGA_SHELF_KEY = 'moontv_manga_shelf';
|
const MANGA_SHELF_KEY = 'moontv_manga_shelf';
|
||||||
const MANGA_HISTORY_KEY = 'moontv_manga_history';
|
const MANGA_HISTORY_KEY = 'moontv_manga_history';
|
||||||
|
const DEFAULT_MAX_MANGA_HISTORY_RECORDS = 100;
|
||||||
|
const DEFAULT_MAX_MANGA_HISTORY_THRESHOLD = DEFAULT_MAX_MANGA_HISTORY_RECORDS + 10;
|
||||||
const SEARCH_HISTORY_KEY = 'moontv_search_history';
|
const SEARCH_HISTORY_KEY = 'moontv_search_history';
|
||||||
const MUSIC_PLAY_RECORDS_KEY = 'moontv_music_play_records';
|
const MUSIC_PLAY_RECORDS_KEY = 'moontv_music_play_records';
|
||||||
|
|
||||||
@@ -1701,6 +1703,17 @@ export async function clearAllMangaShelf(): Promise<void> {
|
|||||||
window.dispatchEvent(new CustomEvent('mangaShelfUpdated', { detail: {} }));
|
window.dispatchEvent(new CustomEvent('mangaShelfUpdated', { detail: {} }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function trimMangaReadRecords(records: Record<string, MangaReadRecord>): Record<string, MangaReadRecord> {
|
||||||
|
const entries = Object.entries(records);
|
||||||
|
if (entries.length <= DEFAULT_MAX_MANGA_HISTORY_THRESHOLD) return records;
|
||||||
|
|
||||||
|
return Object.fromEntries(
|
||||||
|
entries
|
||||||
|
.sort(([, a], [, b]) => b.saveTime - a.saveTime)
|
||||||
|
.slice(0, DEFAULT_MAX_MANGA_HISTORY_RECORDS)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export async function getAllMangaReadRecords(): Promise<Record<string, MangaReadRecord>> {
|
export async function getAllMangaReadRecords(): Promise<Record<string, MangaReadRecord>> {
|
||||||
if (typeof window === 'undefined') return {};
|
if (typeof window === 'undefined') return {};
|
||||||
|
|
||||||
@@ -1748,8 +1761,9 @@ export async function saveMangaReadRecord(sourceId: string, mangaId: string, rec
|
|||||||
if (STORAGE_TYPE !== 'localstorage') {
|
if (STORAGE_TYPE !== 'localstorage') {
|
||||||
const cached = cacheManager.getCachedMangaReadRecords() || {};
|
const cached = cacheManager.getCachedMangaReadRecords() || {};
|
||||||
cached[key] = record;
|
cached[key] = record;
|
||||||
cacheManager.cacheMangaReadRecords(cached);
|
const trimmedRecords = trimMangaReadRecords(cached);
|
||||||
window.dispatchEvent(new CustomEvent('mangaHistoryUpdated', { detail: cached }));
|
cacheManager.cacheMangaReadRecords(trimmedRecords);
|
||||||
|
window.dispatchEvent(new CustomEvent('mangaHistoryUpdated', { detail: trimmedRecords }));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fetchWithAuth('/api/manga/history', {
|
await fetchWithAuth('/api/manga/history', {
|
||||||
@@ -1766,8 +1780,9 @@ export async function saveMangaReadRecord(sourceId: string, mangaId: string, rec
|
|||||||
|
|
||||||
const allRecords = await getAllMangaReadRecords();
|
const allRecords = await getAllMangaReadRecords();
|
||||||
allRecords[key] = record;
|
allRecords[key] = record;
|
||||||
localStorage.setItem(MANGA_HISTORY_KEY, JSON.stringify(allRecords));
|
const trimmedRecords = trimMangaReadRecords(allRecords);
|
||||||
window.dispatchEvent(new CustomEvent('mangaHistoryUpdated', { detail: allRecords }));
|
localStorage.setItem(MANGA_HISTORY_KEY, JSON.stringify(trimmedRecords));
|
||||||
|
window.dispatchEvent(new CustomEvent('mangaHistoryUpdated', { detail: trimmedRecords }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteMangaReadRecord(sourceId: string, mangaId: string): Promise<void> {
|
export async function deleteMangaReadRecord(sourceId: string, mangaId: string): Promise<void> {
|
||||||
|
|||||||
@@ -2037,13 +2037,14 @@ export class PostgresStorage implements IStorage {
|
|||||||
async cleanupOldMangaReadRecords(userName: string): Promise<void> {
|
async cleanupOldMangaReadRecords(userName: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const maxRecords = parseInt(process.env.MAX_MANGA_HISTORY_PER_USER || '100', 10);
|
const maxRecords = parseInt(process.env.MAX_MANGA_HISTORY_PER_USER || '100', 10);
|
||||||
|
const threshold = maxRecords + 10;
|
||||||
const countResult = await this.db
|
const countResult = await this.db
|
||||||
.prepare('SELECT COUNT(*) as count FROM manga_read_records WHERE username = $1')
|
.prepare('SELECT COUNT(*) as count FROM manga_read_records WHERE username = $1')
|
||||||
.bind(userName)
|
.bind(userName)
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
const count = Number(countResult?.count || 0);
|
const count = Number(countResult?.count || 0);
|
||||||
if (count <= maxRecords) return;
|
if (count <= threshold) return;
|
||||||
|
|
||||||
await this.db
|
await this.db
|
||||||
.prepare(`
|
.prepare(`
|
||||||
|
|||||||
@@ -1557,6 +1557,8 @@ export abstract class BaseRedisStorage implements IStorage {
|
|||||||
async cleanupOldMangaReadRecords(userName: string): Promise<void> {
|
async cleanupOldMangaReadRecords(userName: string): Promise<void> {
|
||||||
const records = await this.getAllMangaReadRecords(userName);
|
const records = await this.getAllMangaReadRecords(userName);
|
||||||
const maxRecords = parseInt(process.env.MAX_MANGA_HISTORY_PER_USER || '100', 10);
|
const maxRecords = parseInt(process.env.MAX_MANGA_HISTORY_PER_USER || '100', 10);
|
||||||
|
const threshold = maxRecords + 10;
|
||||||
|
if (Object.keys(records).length <= threshold) return;
|
||||||
const keys = Object.entries(records)
|
const keys = Object.entries(records)
|
||||||
.sort(([, a], [, b]) => b.saveTime - a.saveTime)
|
.sort(([, a], [, b]) => b.saveTime - a.saveTime)
|
||||||
.slice(maxRecords)
|
.slice(maxRecords)
|
||||||
|
|||||||
Reference in New Issue
Block a user