point / database /db_manager.py
eithney
code ref
e74eb63
Raw
History Blame Contribute Delete
12.7 kB
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
数据库管理器 - 支持新的 data 数据结构
提供数据库操作的封装接口
"""
import sqlite3
import json
from pathlib import Path
from typing import Dict, List, Optional, Tuple
import logging
logger = logging.getLogger(__name__)
class DatabaseManager:
"""数据库管理器类"""
def __init__(self, db_path: str = 'books.db'):
"""
初始化数据库管理器
Args:
db_path: 数据库文件路径
"""
self.db_path = db_path
self.conn = None
self._connect()
def _connect(self):
"""连接数据库"""
try:
self.conn = sqlite3.connect(self.db_path, check_same_thread=False)
self.conn.row_factory = sqlite3.Row # 使用字典形式返回结果
logger.info(f"数据库连接成功: {self.db_path}")
except Exception as e:
logger.error(f"数据库连接失败: {e}")
raise
def close(self):
"""关闭数据库连接"""
if self.conn:
self.conn.close()
logger.info("数据库连接已关闭")
def _dict_from_row(self, row) -> Dict:
"""将SQLite Row对象转换为字典"""
if row is None:
return None
return dict(zip(row.keys(), row))
# ==================== 书籍相关操作 ====================
def get_all_books(self) -> List[Dict]:
"""
获取所有书籍列表
Returns:
书籍列表
"""
cursor = self.conn.cursor()
cursor.execute('''
SELECT
market_book_id, market_book_name, market_book_cover,
max_page, market_classify_name, grade_id, reel_id,
create_date, source_dir
FROM books
ORDER BY grade_id, reel_id, market_book_id
''')
books = []
for row in cursor.fetchall():
books.append(self._dict_from_row(row))
return books
def get_book_by_id(self, book_id: int) -> Optional[Dict]:
"""
根据ID获取书籍详情
Args:
book_id: 书籍ID
Returns:
书籍信息字典,如果不存在返回None
"""
cursor = self.conn.cursor()
cursor.execute('''
SELECT * FROM books WHERE market_book_id = ?
''', (book_id,))
row = cursor.fetchone()
return self._dict_from_row(row)
def get_books_by_grade(self, grade_id: int) -> List[Dict]:
"""
根据年级获取书籍列表
Args:
grade_id: 年级ID
Returns:
书籍列表
"""
cursor = self.conn.cursor()
cursor.execute('''
SELECT
market_book_id, market_book_name, market_book_cover,
max_page, market_classify_name, reel_id
FROM books
WHERE grade_id = ?
ORDER BY reel_id, market_book_id
''', (grade_id,))
books = []
for row in cursor.fetchall():
books.append(self._dict_from_row(row))
return books
# ==================== 页面相关操作 ====================
def get_book_pages(self, book_id: int) -> List[Dict]:
"""
获取书籍的所有页面列表(目录)
Args:
book_id: 书籍ID
Returns:
页面列表
"""
cursor = self.conn.cursor()
cursor.execute('''
SELECT page_id, page_number, origin_img_url, encrypt_img_url
FROM pages
WHERE book_id = ?
ORDER BY page_number
''', (book_id,))
pages = []
for row in cursor.fetchall():
pages.append(self._dict_from_row(row))
return pages
def get_page_by_number(self, book_id: int, page_number: int) -> Optional[Dict]:
"""
根据页码获取页面信息(不含片段)
Args:
book_id: 书籍ID
page_number: 页码
Returns:
页面信息字典
"""
cursor = self.conn.cursor()
cursor.execute('''
SELECT * FROM pages
WHERE book_id = ? AND page_number = ?
''', (book_id, page_number))
row = cursor.fetchone()
return self._dict_from_row(row)
def get_page_content(self, book_id: int, page_number: int) -> Optional[Dict]:
"""
获取页面的完整内容(含片段)
Args:
book_id: 书籍ID
page_number: 页码
Returns:
包含pieces的完整页面数据
"""
# 获取页面基本信息
page = self.get_page_by_number(book_id, page_number)
if not page:
return None
# 获取页面片段
cursor = self.conn.cursor()
cursor.execute('''
SELECT
piece_id, original, translation,
origin_sound_url, encrypt_sound_url, duration,
coordinate_x, coordinate_y, coordinate_width, coordinate_height,
is_evaluated, show_translation, rich_original, rich_translation
FROM pieces
WHERE page_id = ?
ORDER BY piece_order
''', (page['page_id'],))
pieces = []
for row in cursor.fetchall():
piece = self._dict_from_row(row)
# 构建coordinate对象
piece['coordinate'] = {
'x': piece.pop('coordinate_x'),
'y': piece.pop('coordinate_y'),
'width': piece.pop('coordinate_width'),
'height': piece.pop('coordinate_height')
}
pieces.append(piece)
# 将pieces添加到page中
page['pieces'] = pieces
page['piece_count'] = len(pieces)
return page
# ==================== 目录相关操作 ====================
def get_book_catalog(self, book_id: int) -> List[Dict]:
"""
获取书籍目录结构
Args:
book_id: 书籍ID
Returns:
目录列表(树形结构)
"""
cursor = self.conn.cursor()
cursor.execute('''
SELECT
catalog_id, catalog_name, catalog_name_cn,
start_page, end_page, thumbnail, parent_id, sort
FROM catalogs
WHERE book_id = ?
ORDER BY sort
''', (book_id,))
catalogs = []
for row in cursor.fetchall():
catalogs.append(self._dict_from_row(row))
# 构建树形结构
catalog_dict = {cat['catalog_id']: dict(cat, children=[]) for cat in catalogs}
tree = []
for catalog in catalogs:
cat_id = catalog['catalog_id']
parent_id = catalog['parent_id']
if parent_id == 0:
tree.append(catalog_dict[cat_id])
else:
if parent_id in catalog_dict:
catalog_dict[parent_id]['children'].append(catalog_dict[cat_id])
return tree
# ==================== 搜索相关操作 ====================
def search_content(self, book_id: int, keyword: str, limit: int = 20) -> List[Dict]:
"""
在书籍中搜索内容(使用FTS全文搜索)
Args:
book_id: 书籍ID
keyword: 搜索关键词
limit: 返回结果数量限制
Returns:
搜索结果列表
"""
cursor = self.conn.cursor()
# 使用FTS5全文搜索
sql = '''
SELECT
p.page_number,
pc.piece_id,
pc.original,
pc.translation,
pc.origin_sound_url
FROM pieces_fts
JOIN pieces pc ON pieces_fts.rowid = pc.piece_id
JOIN pages p ON pc.page_id = p.page_id
WHERE p.book_id = ?
AND pieces_fts MATCH ?
ORDER BY rank
LIMIT ?
'''
cursor.execute(sql, (book_id, keyword, limit))
results = []
for row in cursor.fetchall():
results.append(self._dict_from_row(row))
return results
def search_all_books(self, keyword: str, limit: int = 50) -> List[Dict]:
"""
在所有书籍中搜索内容
Args:
keyword: 搜索关键词
limit: 返回结果数量限制
Returns:
搜索结果列表(包含书籍信息)
"""
cursor = self.conn.cursor()
sql = '''
SELECT
b.market_book_id,
b.market_book_name,
p.page_number,
pc.piece_id,
pc.original,
pc.translation,
pc.origin_sound_url
FROM pieces_fts
JOIN pieces pc ON pieces_fts.rowid = pc.piece_id
JOIN pages p ON pc.page_id = p.page_id
JOIN books b ON p.book_id = b.market_book_id
WHERE pieces_fts MATCH ?
ORDER BY rank
LIMIT ?
'''
cursor.execute(sql, (keyword, limit))
results = []
for row in cursor.fetchall():
results.append(self._dict_from_row(row))
return results
# ==================== 统计相关操作 ====================
def get_book_statistics(self, book_id: int) -> Dict:
"""
获取书籍统计信息
Args:
book_id: 书籍ID
Returns:
统计信息字典
"""
cursor = self.conn.cursor()
# 获取页面数
cursor.execute('SELECT COUNT(*) FROM pages WHERE book_id = ?', (book_id,))
page_count = cursor.fetchone()[0]
# 获取片段数
cursor.execute('''
SELECT COUNT(*) FROM pieces
WHERE page_id IN (SELECT page_id FROM pages WHERE book_id = ?)
''', (book_id,))
piece_count = cursor.fetchone()[0]
# 获取有音频的片段数
cursor.execute('''
SELECT COUNT(*) FROM pieces
WHERE page_id IN (SELECT page_id FROM pages WHERE book_id = ?)
AND (origin_sound_url != '' OR encrypt_sound_url != '')
''', (book_id,))
audio_count = cursor.fetchone()[0]
# 获取目录项数
cursor.execute('SELECT COUNT(*) FROM catalogs WHERE book_id = ?', (book_id,))
catalog_count = cursor.fetchone()[0]
return {
'book_id': book_id,
'total_pages': page_count,
'total_pieces': piece_count,
'total_audio': audio_count,
'total_catalogs': catalog_count
}
def get_overall_statistics(self) -> Dict:
"""
获取整体统计信息
Returns:
整体统计信息字典
"""
cursor = self.conn.cursor()
cursor.execute('SELECT COUNT(*) FROM books')
book_count = cursor.fetchone()[0]
cursor.execute('SELECT COUNT(*) FROM pages')
page_count = cursor.fetchone()[0]
cursor.execute('SELECT COUNT(*) FROM pieces')
piece_count = cursor.fetchone()[0]
cursor.execute('SELECT COUNT(*) FROM catalogs')
catalog_count = cursor.fetchone()[0]
return {
'total_books': book_count,
'total_pages': page_count,
'total_pieces': piece_count,
'total_catalogs': catalog_count
}
# 全局数据库实例
_db_instance = None
def get_db_instance(db_path: str = 'books.db') -> DatabaseManager:
"""
获取数据库管理器单例
Args:
db_path: 数据库文件路径
Returns:
DatabaseManager实例
"""
global _db_instance
if _db_instance is None:
_db_instance = DatabaseManager(db_path)
return _db_instance
def close_db():
"""关闭全局数据库连接"""
global _db_instance
if _db_instance:
_db_instance.close()
_db_instance = None