Files
pixiv/backend/database/watchlist-database.js

188 lines
5.6 KiB
JavaScript

const { defaultLogger } = require('../utils/logger');
const crypto = require('crypto');
const logger = defaultLogger.child('WatchlistDatabase');
/**
* 数据库版本的待看名单
* 提供与JSON版本相同的接口,但使用MySQL数据库存储
*/
class WatchlistDatabase {
constructor(databaseManager) {
this.db = databaseManager;
this.loaded = false;
this.tableName = 'watchlist_items';
}
/**
* 初始化数据库表
*/
async init() {
try {
// 如果表不存在则创建
if (!(await this.db.tableExists(this.tableName))) {
const createSQL = `
CREATE TABLE ${this.tableName} (
id VARCHAR(64) PRIMARY KEY,
title VARCHAR(255) NOT NULL,
url TEXT NOT NULL,
url_hash CHAR(64) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uniq_url_hash (url_hash)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='待看名单表';
`;
await this.db.createTable(this.tableName, createSQL);
}
this.loaded = true;
logger.info('待看名单数据库表初始化完成');
return { success: true };
} catch (error) {
logger.error('初始化待看名单数据库失败:', error);
throw error;
}
}
/**
* 获取所有待看项目
*/
async getAllItems() {
try {
const result = await this.db.query(`
SELECT id, title, url, created_at, updated_at
FROM ${this.tableName}
ORDER BY created_at ASC
`);
return result.data.map(row => ({
id: row.id.toString(),
title: row.title,
url: row.url,
createdAt: (row.created_at instanceof Date) ? row.created_at.toISOString() : row.created_at,
updatedAt: (row.updated_at instanceof Date) ? row.updated_at.toISOString() : row.updated_at
}));
} catch (error) {
logger.error('获取待看项目失败(数据库):', error);
throw error;
}
}
/**
* 添加或更新待看项目(按URL唯一)
*/
async addItem(item) {
try {
const { url, title } = item;
if (!url) throw new Error('URL是必填项');
const urlHash = crypto.createHash('sha256').update(url).digest('hex');
// 先查是否存在
const exists = await this.db.select(this.tableName, { url_hash: urlHash });
if (exists.data && exists.data.length > 0) {
// 更新标题
await this.db.update(this.tableName, { title }, { url_hash: urlHash });
} else {
const id = (item.id?.toString()) || Date.now().toString();
await this.db.insert(this.tableName, {
id,
title: title || '待看页面',
url,
url_hash: urlHash
});
}
return await this.getAllItems();
} catch (error) {
logger.error('添加待看项目失败(数据库):', error);
throw error;
}
}
/**
* 删除待看项目
*/
async removeItem(id) {
try {
const result = await this.db.delete(this.tableName, { id });
if (result.affectedRows === 0) {
throw new Error('待看项目不存在');
}
return await this.getAllItems();
} catch (error) {
logger.error('删除待看项目失败(数据库):', error);
throw error;
}
}
/**
* 更新待看项目
*/
async updateItem(id, updates) {
try {
const allowed = {};
if (typeof updates.title === 'string') allowed.title = updates.title;
if (typeof updates.url === 'string') {
allowed.url = updates.url;
allowed.url_hash = crypto.createHash('sha256').update(updates.url).digest('hex');
}
if (Object.keys(allowed).length === 0) {
// 没有可更新字段则返回原项
const rows = await this.db.select(this.tableName, { id });
if (!rows.data || rows.data.length === 0) throw new Error('待看项目不存在');
const row = rows.data[0];
return {
id: row.id.toString(),
title: row.title,
url: row.url,
createdAt: (row.created_at instanceof Date) ? row.created_at.toISOString() : row.created_at,
updatedAt: (row.updated_at instanceof Date) ? row.updated_at.toISOString() : row.updated_at
};
}
await this.db.update(this.tableName, allowed, { id });
const rows = await this.db.select(this.tableName, { id });
const row = rows.data[0];
return {
id: row.id.toString(),
title: row.title,
url: row.url,
createdAt: (row.created_at instanceof Date) ? row.created_at.toISOString() : row.created_at,
updatedAt: (row.updated_at instanceof Date) ? row.updated_at.toISOString() : row.updated_at
};
} catch (error) {
logger.error('更新待看项目失败(数据库):', error);
throw error;
}
}
/**
* 清空表
*/
async clearAll() {
await this.db.query(`DELETE FROM ${this.tableName}`);
}
/**
* 批量插入
*/
async batchInsert(items) {
const rows = items.map(i => {
const url = i.url;
const url_hash = url ? crypto.createHash('sha256').update(url).digest('hex') : null;
return {
id: i.id?.toString() || Date.now().toString(),
title: i.title || '待看页面',
url,
url_hash
};
});
return await this.db.batchInsert(this.tableName, rows);
}
/**
* 获取已有URL集合
*/
async getExistingUrlSet() {
const result = await this.db.query(`SELECT url FROM ${this.tableName}`);
return new Set(result.data.map(r => r.url));
}
}
module.exports = WatchlistDatabase;