Files
john e76fa586f1 Add word-book practice, iOS app shell, and fix embedded WebView blank screen.
Ship dual-track learning (daily accumulation vs textbook),沪教/商务词书 APIs and UI, native iOS wrapper with bundled H5, and production book import on deploy.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 21:09:27 +08:00

107 lines
3.4 KiB
Python

"""将 backend/data/books/*.json 导入 word_books / word_book_entries 表。"""
from __future__ import annotations
import json
import sys
from pathlib import Path
BACKEND_DIR = Path(__file__).resolve().parents[1]
if str(BACKEND_DIR) not in sys.path:
sys.path.insert(0, str(BACKEND_DIR))
from database import SessionLocal # noqa: E402
from models import WordBook, WordBookEntry # noqa: E402
from services.book_service import DEPRECATED_SLUGS # noqa: E402
BOOKS_DIR = BACKEND_DIR / "data" / "books"
def upsert_book(session, data: dict) -> WordBook:
book = session.query(WordBook).filter(WordBook.slug == data["slug"]).first()
if not book:
book = WordBook(slug=data["slug"])
session.add(book)
book.title = data["title"]
book.description = data.get("description")
book.level = data.get("level", "general")
book.word_count = data.get("word_count", 0)
book.unit_count = data.get("unit_count", 0)
book.sort_order = data.get("sort_order", 0)
book.daily_target = data.get("daily_target", 15)
book.master_required_count = data.get("master_required_count", 4)
book.weak_wrong_threshold = data.get("weak_wrong_threshold", 2)
book.learn_mode = data.get("learn_mode", "sequential")
book.is_published = 1
session.flush()
return book
def import_book_file(session, path: Path) -> tuple[str, int]:
data = json.loads(path.read_text(encoding="utf-8"))
book = upsert_book(session, data)
session.query(WordBookEntry).filter(WordBookEntry.book_id == book.id).delete(
synchronize_session=False
)
count = 0
for unit in data.get("units", []):
unit_no = unit.get("unit", 1)
unit_title = unit.get("title")
for w in unit.get("words", []):
entry = WordBookEntry(
book_id=book.id,
unit=unit_no,
unit_title=unit_title,
sort_index=w["sort_index"],
lemma_en=w["lemma_en"].strip().lower()[:128],
zh=w["zh"].strip()[:256],
phonetic=(w.get("phonetic") or "")[:128] or None,
example_en=w.get("example_en"),
example_cn=w.get("example_cn"),
)
session.add(entry)
count += 1
book.word_count = count
book.unit_count = len(data.get("units", []))
session.commit()
return book.slug, count
def import_all(books_dir: Path = BOOKS_DIR) -> None:
files = sorted(books_dir.glob("*.json"))
files = [
f
for f in files
if f.name != "manifest.json"
and f.stem not in DEPRECATED_SLUGS
]
if not files:
print(f"未找到词书 JSON: {books_dir}")
return
session = SessionLocal()
try:
for path in files:
slug, count = import_book_file(session, path)
print(f" 导入 {slug}: {count} 词条")
for slug in DEPRECATED_SLUGS:
book = session.query(WordBook).filter(WordBook.slug == slug).first()
if book and book.is_published:
book.is_published = 0
session.commit()
print(f" 下架旧词书 {slug}")
finally:
session.close()
def main() -> None:
import argparse
parser = argparse.ArgumentParser(description="导入词书 JSON 到数据库")
parser.add_argument("--dir", type=Path, default=BOOKS_DIR)
args = parser.parse_args()
import_all(args.dir)
if __name__ == "__main__":
main()