Initial commit: book-mcp 教辅处理 MCP server + Arco Vue 管理端
后端 (book_mcp_server.py, 335 行, 15 个 MCP 工具):
- create_book / add_document / mark_html_ready / mark_json_ready / mark_failed
- update_book_status / soft_delete_book / retry_failed
- get_book / list_books / get_document / list_documents / get_document_url
- upload_to_rustfs / health
- 走 JSON-RPC + stateless_http + json_response(前端可直接调)
- launchd 守护 / 6 秒拉起(macOS)
- systemd 守护脚本(Linux/codelab)
存储:
- MongoDB (mongo:8.0 容器) - book/book_document/document_file 三表
- RustFS 对象存储(fs.code-lab.cn/scan/)- book/<id>/docs/<doc_id>/{docx,html,json}
- 幂等设计: zip_sha256 唯一 + (book_id+original_path) 唯一
前端 (Vue 3 + TS + Arco Design):
- 列表页 /book/list - 搜索 + 状态过滤 + 进度
- 详情页 /book/:id - 文档折叠 + 文件下载
- MCP 客户端 src/api/mcp.ts (JSON-RPC over fetch)
部署:
- deploy/install.sh (codelab 一键装 backend + systemd)
- deploy/install-frontend.sh (vite dev / 生产构建)
- docs/deploy.md (运维手册)
测试:
- tests/test_basic.py (sanity 7 项)
- tests/test_e2e.py (完整 e2e: docx→HTML→入库→上传→校验)
This commit is contained in:
98
db/database.py
Normal file
98
db/database.py
Normal file
@@ -0,0 +1,98 @@
|
||||
"""MongoDB 连接 + collection 管理
|
||||
|
||||
3 个 collection:
|
||||
book — 教辅
|
||||
book_document — 教辅文档
|
||||
document_file — 文档文件
|
||||
|
||||
设计思路:
|
||||
- _id 用业务 ID(doc_id 16 字符 sha1 / book_id 自增字符串 / file_id 自增字符串)
|
||||
- book_id 用 INT 自增(mongo 中用 find_one_and_update + $inc 实现)
|
||||
- 不强制 schema(mongo 灵活),但用 helper 函数做字段约束
|
||||
- 索引在 init_db() 时一次性建好
|
||||
"""
|
||||
import os
|
||||
import yaml
|
||||
from pymongo import MongoClient, ASCENDING, DESCENDING, ReturnDocument
|
||||
from pymongo.database import Database
|
||||
|
||||
_CONFIG_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'config.yaml')
|
||||
|
||||
_client: MongoClient | None = None
|
||||
|
||||
|
||||
def load_config() -> dict:
|
||||
with open(_CONFIG_PATH) as f:
|
||||
return yaml.safe_load(f)
|
||||
|
||||
|
||||
def get_client() -> MongoClient:
|
||||
global _client
|
||||
if _client is None:
|
||||
cfg = load_config()['database']
|
||||
_client = MongoClient(cfg['url'], serverSelectionTimeoutMS=5000)
|
||||
return _client
|
||||
|
||||
|
||||
def get_db() -> Database:
|
||||
cfg = load_config()['database']
|
||||
return get_client()[cfg['db_name']]
|
||||
|
||||
|
||||
def get_collection(name: str):
|
||||
return get_db()[name]
|
||||
|
||||
|
||||
# ---- collection 名常量 ----
|
||||
COL_BOOK = 'book'
|
||||
COL_DOC = 'book_document'
|
||||
COL_FILE = 'document_file'
|
||||
|
||||
# ---- counters collection(book_id / file_id 自增用)----
|
||||
COL_COUNTER = 'counters'
|
||||
|
||||
|
||||
# ---- 索引初始化 ----
|
||||
def init_db():
|
||||
"""建所有索引(幂等)"""
|
||||
db = get_db()
|
||||
|
||||
# book: zip_sha256 唯一 + (name, version, is_deleted) 唯一
|
||||
db[COL_BOOK].create_index([('zip_sha256', ASCENDING)], unique=True, name='uk_book_zip_sha')
|
||||
db[COL_BOOK].create_index([('name', ASCENDING), ('version', ASCENDING), ('is_deleted', ASCENDING)],
|
||||
unique=True, name='uk_book_name_version')
|
||||
db[COL_BOOK].create_index([('status', ASCENDING)], name='idx_book_status')
|
||||
db[COL_BOOK].create_index([('created_at', DESCENDING)], name='idx_book_created')
|
||||
|
||||
# book_document: _id 已唯一(doc_id 即 _id);额外索引仅用于查询加速
|
||||
db[COL_DOC].create_index([('book_id', ASCENDING), ('order_index', ASCENDING)], name='idx_book_order')
|
||||
db[COL_DOC].create_index([('status', ASCENDING)], name='idx_doc_status')
|
||||
|
||||
# document_file: (doc_id, file_type) 唯一 + (bucket, key)
|
||||
db[COL_FILE].create_index([('doc_id', ASCENDING), ('file_type', ASCENDING)],
|
||||
unique=True, name='uk_doc_type')
|
||||
db[COL_FILE].create_index([('object_bucket', ASCENDING), ('object_key', ASCENDING)],
|
||||
name='idx_bucket_key')
|
||||
|
||||
print(f"✓ MongoDB 已就绪(db: {db.name}, collections: {[COL_BOOK, COL_DOC, COL_FILE]})")
|
||||
print(f" ✓ 8 个索引创建完成")
|
||||
|
||||
|
||||
def health() -> bool:
|
||||
try:
|
||||
get_client().admin.command('ping')
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def next_id(name: str) -> int:
|
||||
"""原子自增 ID(mongo 没有 AUTO_INCREMENT,用 counters collection 模拟)"""
|
||||
db = get_db()
|
||||
r = db[COL_COUNTER].find_one_and_update(
|
||||
{'_id': name},
|
||||
{'$inc': {'seq': 1}},
|
||||
upsert=True,
|
||||
return_document=ReturnDocument.AFTER,
|
||||
)
|
||||
return int(r['seq'])
|
||||
Reference in New Issue
Block a user