diff --git a/README.md b/README.md index 70c2087..80045e0 100644 --- a/README.md +++ b/README.md @@ -216,7 +216,8 @@ uv run python tools/import_fans_csv.py --csv E:\Downloads\history.csv --replace - `--replace` 会先删除该 mid 的全部 `up_stat`,再按 CSV 重建,适合用完整历史覆盖旧数据; - 如果数据库里已有晚于 CSV 末尾的记录,`--replace` 会拒绝执行;确认要删除时才加 `--allow-lossy-replace`;只想补齐 CSV 日期并保留后续采集时用 `--force`; - 页面的“日增”只在存在前一天数据时计算,跨多天不会标成日增; -- 写入前建议先备份 `data/app.db`,例如 `data/backup/app-pre-fans-YYYYMMDD.db`。 +- 写入前建议先备份 `data/app.db`,例如 `data/backup/app-pre-fans-YYYYMMDD.db`; +- 容器内运行时用 `--db /data/app.db` 或 `--config /config/config.json`,避免默认去找 `/app/config.json`。 ## 快速部署(Docker) diff --git a/tests/test_fans_csv.py b/tests/test_fans_csv.py index 3a5939e..8c72b77 100644 --- a/tests/test_fans_csv.py +++ b/tests/test_fans_csv.py @@ -12,6 +12,7 @@ sys.stdout.reconfigure(encoding="utf-8") from reaction_board.database import Database # noqa: E402 from reaction_board.repository import Repository # noqa: E402 from tools.import_fans_csv import ( # noqa: E402 + _database_path, import_fans_series, parse_fans_csv, ) @@ -104,6 +105,15 @@ def test_import_default_force_and_replace(): db.close() +def test_database_path_resolution(): + assert _database_path("missing.json", "x.db") == Path("x.db") + with tempfile.TemporaryDirectory() as tmp: + from tests.web_fixtures import write_config + + config = write_config(tmp) + assert _database_path(str(config), None) == Path(tmp) / "app.db" + + def test_replace_guard_for_newer_rows(): series = parse_fans_csv(CSV_TEXT) with tempfile.TemporaryDirectory() as tmp: @@ -147,6 +157,7 @@ def main_test(): test_parse_csv() test_parse_rejects_bad_values() test_import_default_force_and_replace() + test_database_path_resolution() test_replace_guard_for_newer_rows() test_replace_dry_run_counts_only() print("OK: 粉丝 CSV 解析/导入/替换/幂等测试全部通过") diff --git a/tools/import_fans_csv.py b/tools/import_fans_csv.py index a027dad..7c2f89f 100644 --- a/tools/import_fans_csv.py +++ b/tools/import_fans_csv.py @@ -224,7 +224,13 @@ def _database_path(config_path: str, override: str | None) -> Path: return Path(override) from reaction_board.config import ConfigStore - return ConfigStore(config_path, require_auth=False).get().storage.database_path + candidates = [Path(config_path), Path("/config/config.json")] + for candidate in candidates: + if candidate.exists(): + return ConfigStore(str(candidate), require_auth=False).get().storage.database_path + raise ValueError( + "找不到配置文件;请用 --db /data/app.db 或 --config /config/config.json" + ) def main(argv: Sequence[str] | None = None) -> int: