后端 (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→入库→上传→校验)
59 lines
2.2 KiB
Python
59 lines
2.2 KiB
Python
"""基本 sanity 测试 — 不依赖具体 DB 连接,只验模块能 load"""
|
||
import os
|
||
import sys
|
||
|
||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||
|
||
|
||
def test_imports():
|
||
"""所有模块能 import 不报错"""
|
||
from db import models, database
|
||
from db.models import Book, BookDocument, DocumentFile
|
||
from services import book_service, document_service, file_service, rustfs_service
|
||
from services.wordparse import cleaner, post, render, convert
|
||
print("✓ 所有模块 import OK")
|
||
|
||
# Book 是 mongo helper class(非 ORM)
|
||
assert hasattr(Book, 'to_mongo')
|
||
assert hasattr(Book, 'from_mongo')
|
||
print("✓ Book 是 mongo helper class")
|
||
|
||
# mongo ↔ dict 对称
|
||
b = Book(id=1, name='test', zip_path='x', zip_sha256='abc', zip_size=100)
|
||
m = b.to_mongo()
|
||
assert m['_id'] == 1 and m['name'] == 'test'
|
||
b2 = Book.from_mongo(m)
|
||
assert b2.id == 1 and b2.name == 'test'
|
||
print("✓ Book to_mongo/from_mongo 对称")
|
||
|
||
# doc_id 派生
|
||
doc_id = BookDocument.make_doc_id(1, "docx/chapter1.docx")
|
||
assert len(doc_id) == 16
|
||
assert doc_id == BookDocument.make_doc_id(1, "docx/chapter1.docx")
|
||
print(f"✓ doc_id 幂等:{doc_id}")
|
||
|
||
# doc_type 推断
|
||
from services.document_service import _infer_doc_type
|
||
assert _infer_doc_type("课时作业1.docx") == "课时作业"
|
||
assert _infer_doc_type("周末提升训练.docx") == "周末提升"
|
||
assert _infer_doc_type("阶段质量评估(一).docx") == "阶段质量评估"
|
||
assert _infer_doc_type("期末质量评估.docx") == "综合评估"
|
||
assert _infer_doc_type("random.docx") == "unknown"
|
||
print("✓ doc_type 推断正确")
|
||
|
||
# cleaner 文本修整
|
||
from services.wordparse.cleaner import _fix_text
|
||
out = _fix_text('<a w="210mm" h="25.4"/>')
|
||
assert '0' in out and 'mm' not in out
|
||
print(f"✓ cleaner 修单位:{out}")
|
||
|
||
# rustfs key 模板
|
||
from services.rustfs_service import build_key
|
||
key = build_key(123, "5b3a2f1e9c0d4b7a", "html")
|
||
assert key == "book/123/docs/5b3a2f1e9c0d4b7a/output.html"
|
||
print(f"✓ rustfs key 模板:{key}")
|
||
|
||
|
||
if __name__ == '__main__':
|
||
test_imports()
|
||
print("\n✅ 全部测试通过") |