188 lines
5.6 KiB
JavaScript
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; |