diff --git a/MAINTENANCE.md b/MAINTENANCE.md index 09417d8..c672b90 100644 --- a/MAINTENANCE.md +++ b/MAINTENANCE.md @@ -1,6 +1,6 @@ # 维护手册 -新时空教务管家长期维护时,目标是每次更新都能做到可检查、可部署、可回滚,并且不误改业务数据。 +新时空教务管理系统长期维护时,目标是每次更新都能做到可检查、可部署、可回滚,并且不误改业务数据。 ## 日常更新流程 @@ -49,7 +49,7 @@ ## Gitea 自动备份 -新时空教务管家推荐把 Gitea SSH 仓库配置为 `origin`,并用 `post-commit` hook 在每次提交后自动推送当前分支。 +新时空教务管理系统推荐把 Gitea SSH 仓库配置为 `origin`,并用 `post-commit` hook 在每次提交后自动推送当前分支。 首次配置: @@ -120,7 +120,7 @@ git push origin HEAD:<当前分支> ## 课程小结迁移维护 - VPS 是 `classnotes.txt` 和 `学生课时账户.md` 的唯一正式写入方。 -- 本机 `com.xsk.records.sync` 常驻同步迁移后应停止,避免旧本地数据覆盖 VPS。 +- 本机 `com.xsk.education-management.sync` 常驻同步迁移后应停止,避免旧本地数据覆盖 VPS。 - 本机课程小结采集脚本用 `XSK_INGEST_URL` 和 `XSK_INGEST_TOKEN` 推送批次;失败批次保存在本机 `推送失败队列/`。 - 管理后台的“课程小结审核”处理低置信或冲突小结;“操作记录”追踪接收、自动入账、重复、失败、审核批准和驳回。 - 历史小结导入使用 `scripts/import_course_summaries.py`,历史 `classnotes缺失.txt` 只生成审核任务,不自动扣课时。 diff --git a/Makefile b/Makefile index cb4fdb2..f8ac2e9 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ build: docker compose build up: - docker compose up -d + docker compose up -d --remove-orphans ps: docker compose ps @@ -38,7 +38,7 @@ deploy: check smoke $(MAKE) --no-print-directory ps logs: - docker compose logs --tail=120 xsk-records-web + docker compose logs --tail=120 xsk-education-management install-gitea-backup: python3 scripts/install_gitea_backup_hook.py diff --git a/README.md b/README.md index 3fd22e6..c4d1fa6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 新时空教务管家 +# 新时空教务管理系统 这是一个面向新时空教务业务的综合管理系统。迁移后 VPS 是正式业务数据主机,负责保存 `classnotes.txt`、`学生课时账户.md`、课程小结库、审核任务和操作记录;本机只保留微信群聊天记录采集/识别,并把课程小结批量推送到 VPS。 @@ -7,13 +7,13 @@ - `app/`:FastAPI 后端和内置前端页面。 - `MAINTENANCE.md`:日常检查、部署、数据保护和回滚流程。 - `Makefile`:常用维护命令入口。 -- `scripts/deploy_to_vps.py`:部署教务管家服务到 VPS。 +- `scripts/deploy_to_vps.py`:部署新时空教务管理系统到 VPS。 - `scripts/import_course_summaries.py`:一次性导入历史课程小结 Markdown。 - `scripts/sync_to_vps.py`:旧版正式数据同步脚本,迁移后不要继续常驻运行。 - `scripts/smoke_test.js`:轻量前端行为烟测。 - `scripts/install_gitea_backup_hook.py`:安装提交后自动推送到 Gitea 的 Git hook。 - `scripts/install_launch_agent.py`:安装 Mac 开机常驻同步任务。 -- `launchd/com.xsk.records.sync.plist.template`:LaunchAgent 模板。 +- `launchd/com.xsk.education-management.sync.plist.template`:LaunchAgent 模板。 ## 后端结构 @@ -32,7 +32,7 @@ 推荐先配置 SSH key。若临时使用密码,可通过环境变量传入,不要写入仓库文件。 ```bash -cd /Users/yangdawei/Desktop/新时空业务源数据/tools/xsk-records-web +cd /Users/yangdawei/Desktop/新时空业务源数据/tools/xsk-education-management XSK_USE_SSHPASS=1 \ XSK_SSH_PASSWORD='填写SSH密码' \ XSK_WEB_PASSWORD='填写网页访问密码' \ @@ -104,7 +104,7 @@ python3 /Users/yangdawei/Desktop/新时空业务源数据/新时空课程记录 ```bash cd /root/新时空数据/app -docker compose exec xsk-records-web python scripts/import_course_summaries.py \ +docker compose exec xsk-education-management python scripts/import_course_summaries.py \ --source /data/import/课程小结采集 \ --target /data/course_summaries \ --state /data/course_summary_state.json \ @@ -160,21 +160,21 @@ python3 scripts/install_launch_agent.py --use-sshpass 日志位置: ```text -~/Library/Logs/xsk-records-web/sync.log -~/Library/Logs/xsk-records-web/sync.err.log +~/Library/Logs/xsk-education-management/sync.log +~/Library/Logs/xsk-education-management/sync.err.log ``` 查看任务: ```bash -launchctl list | grep com.xsk.records.sync +launchctl list | grep com.xsk.education-management.sync ``` 卸载任务: ```bash -launchctl unload ~/Library/LaunchAgents/com.xsk.records.sync.plist -rm ~/Library/LaunchAgents/com.xsk.records.sync.plist +launchctl unload ~/Library/LaunchAgents/com.xsk.education-management.sync.plist +rm ~/Library/LaunchAgents/com.xsk.education-management.sync.plist ``` ## 更换网页访问密码 diff --git a/app/auth.py b/app/auth.py index b7ef547..1f6adda 100644 --- a/app/auth.py +++ b/app/auth.py @@ -56,7 +56,7 @@ def is_records_authenticated( request, RECORDS_SESSION_COOKIE, BASIC_AUTH_PASSWORD, - b"xsk-records-web-session-v1", + b"xsk-education-management-records-session-v1", ) or has_valid_basic_auth(credentials, BASIC_AUTH_PASSWORD) diff --git a/app/config.py b/app/config.py index 58257e2..646e099 100644 --- a/app/config.py +++ b/app/config.py @@ -20,7 +20,7 @@ ACCOUNTS_AUTH_PASSWORD = os.getenv("ACCOUNTS_AUTH_PASSWORD") or os.getenv("ACCOU ADMIN_AUTH_PASSWORD = os.getenv("ADMIN_AUTH_PASSWORD") or ACCOUNTS_AUTH_PASSWORD INGEST_AUTH_TOKEN = os.getenv("INGEST_AUTH_TOKEN", "") -RECORDS_SESSION_COOKIE = "xsk_records_session" +RECORDS_SESSION_COOKIE = "xsk_education_management_records_session" ACCOUNTS_SESSION_COOKIE = "xsk_accounts_session" ADMIN_SESSION_COOKIE = "xsk_admin_session" SESSION_MAX_AGE = 60 * 60 * 24 * 30 diff --git a/app/main.py b/app/main.py index 77745d5..e2b53f2 100644 --- a/app/main.py +++ b/app/main.py @@ -6,7 +6,7 @@ from fastapi.responses import JSONResponse from .routers import accounts, admin, health, ingest, pages, records -app = FastAPI(title="新时空教务管家", version="1.0.0") +app = FastAPI(title="新时空教务管理系统", version="1.0.0") @app.exception_handler(ValueError) diff --git a/app/routers/pages.py b/app/routers/pages.py index ca29525..a0bfc4b 100644 --- a/app/routers/pages.py +++ b/app/routers/pages.py @@ -241,7 +241,7 @@ def login_page( return RedirectResponse(url=next_path, status_code=status.HTTP_303_SEE_OTHER) return HTMLResponse( render_login_page( - title="新时空教务管家", + title="新时空教务管理系统", action="/login", next_path=next_path, has_error=has_error, @@ -260,7 +260,7 @@ async def login_submit(request: Request): response = RedirectResponse(url=next_path, status_code=status.HTTP_303_SEE_OTHER) response.set_cookie( RECORDS_SESSION_COOKIE, - session_token(records_password, b"xsk-records-web-session-v1"), + session_token(records_password, b"xsk-education-management-records-session-v1"), max_age=SESSION_MAX_AGE, httponly=True, samesite="lax", diff --git a/app/static/index.html b/app/static/index.html index 40576d6..f7251f5 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -3,13 +3,13 @@ - 新时空教务管家 + 新时空教务管理系统
-

新时空教务管家

+

新时空教务管理系统

正在读取数据状态

diff --git a/docker-compose.yml b/docker-compose.yml index 449464b..61063c2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,10 +1,10 @@ services: - xsk-records-web: + xsk-education-management: build: context: . args: PYTHON_IMAGE: ${PYTHON_IMAGE:-python:3.12-slim} - container_name: xsk-records-web + container_name: xsk-education-management restart: unless-stopped env_file: - .env diff --git a/launchd/com.xsk.records.sync.plist.template b/launchd/com.xsk.education-management.sync.plist.template similarity index 69% rename from launchd/com.xsk.records.sync.plist.template rename to launchd/com.xsk.education-management.sync.plist.template index 6a33518..18e83fd 100644 --- a/launchd/com.xsk.records.sync.plist.template +++ b/launchd/com.xsk.education-management.sync.plist.template @@ -3,20 +3,20 @@ Label - com.xsk.records.sync + com.xsk.education-management.sync ProgramArguments /usr/bin/python3 - /Users/yangdawei/Desktop/新时空业务源数据/tools/xsk-records-web/scripts/sync_to_vps.py + /Users/yangdawei/Desktop/新时空业务源数据/tools/xsk-education-management/scripts/sync_to_vps.py RunAtLoad KeepAlive StandardOutPath - /Users/yangdawei/Library/Logs/xsk-records-web/sync.log + /Users/yangdawei/Library/Logs/xsk-education-management/sync.log StandardErrorPath - /Users/yangdawei/Library/Logs/xsk-records-web/sync.err.log + /Users/yangdawei/Library/Logs/xsk-education-management/sync.err.log EnvironmentVariables PATH diff --git a/scripts/deploy_to_vps.py b/scripts/deploy_to_vps.py index 61fa9d3..4389c3b 100755 --- a/scripts/deploy_to_vps.py +++ b/scripts/deploy_to_vps.py @@ -19,7 +19,7 @@ DEFAULT_IDENTITY_FILE = Path.home() / ".ssh" / "xsk_records_vps_ed25519" def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser(description="部署新时空课程记录查询网页到 VPS") + parser = argparse.ArgumentParser(description="部署新时空教务管理系统到 VPS") parser.add_argument("--remote-host", default=os.getenv("XSK_REMOTE_HOST", DEFAULT_REMOTE_HOST)) parser.add_argument("--remote-user", default=os.getenv("XSK_REMOTE_USER", DEFAULT_REMOTE_USER)) parser.add_argument("--remote-port", type=int, default=int(os.getenv("XSK_REMOTE_PORT", str(DEFAULT_REMOTE_PORT)))) @@ -27,7 +27,7 @@ def parse_args() -> argparse.Namespace: parser.add_argument( "--identity-file", default=os.getenv("XSK_IDENTITY_FILE", str(DEFAULT_IDENTITY_FILE) if DEFAULT_IDENTITY_FILE.exists() else ""), - help="SSH 私钥路径;默认使用 ~/.ssh/xsk_records_vps_ed25519(如果存在)", + help="SSH 私钥路径;默认兼容使用历史文件 ~/.ssh/xsk_records_vps_ed25519(如果存在)", ) parser.add_argument("--app-port", default=os.getenv("XSK_APP_PORT", "18080")) parser.add_argument( @@ -137,7 +137,7 @@ def main() -> int: run(ssh_command(args) + [f"mkdir -p {shlex.quote(app_dir)} {shlex.quote(data_dir)}"]) run(rsync_command(args) + [f"{PROJECT_DIR}/", f"{base_remote(args)}:{app_dir}/"]) run(rsync_command(args) + [str(env_file), f"{base_remote(args)}:{app_dir}/.env"]) - run(ssh_command(args) + [f"cd {shlex.quote(app_dir)} && docker compose up -d --build"]) + run(ssh_command(args) + [f"cd {shlex.quote(app_dir)} && docker compose up -d --build --remove-orphans"]) print(f"部署完成: http://{args.remote_host}:{args.app_port}/") finally: env_file.unlink(missing_ok=True) diff --git a/scripts/install_launch_agent.py b/scripts/install_launch_agent.py index 741eebc..152b6f1 100755 --- a/scripts/install_launch_agent.py +++ b/scripts/install_launch_agent.py @@ -9,13 +9,15 @@ import subprocess PROJECT_DIR = Path(__file__).resolve().parents[1] -LABEL = "com.xsk.records.sync" +LABEL = "com.xsk.education-management.sync" +OLD_LABEL = "com.xsk.records.sync" PLIST_PATH = Path.home() / "Library" / "LaunchAgents" / f"{LABEL}.plist" -LOG_DIR = Path.home() / "Library" / "Logs" / "xsk-records-web" +OLD_PLIST_PATH = Path.home() / "Library" / "LaunchAgents" / f"{OLD_LABEL}.plist" +LOG_DIR = Path.home() / "Library" / "Logs" / "xsk-education-management" def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser(description="安装新时空课程记录同步 LaunchAgent") + parser = argparse.ArgumentParser(description="安装新时空教务管理系统同步 LaunchAgent") parser.add_argument("--use-sshpass", action="store_true", default=os.getenv("XSK_USE_SSHPASS", "") == "1") parser.add_argument("--ssh-password", default=os.getenv("XSK_SSH_PASSWORD")) return parser.parse_args() @@ -50,6 +52,8 @@ def main() -> int: with PLIST_PATH.open("wb") as handle: plistlib.dump(plist, handle) + subprocess.run(["launchctl", "unload", str(OLD_PLIST_PATH)], check=False, capture_output=True) + OLD_PLIST_PATH.unlink(missing_ok=True) subprocess.run(["launchctl", "unload", str(PLIST_PATH)], check=False, capture_output=True) subprocess.run(["launchctl", "load", str(PLIST_PATH)], check=True) print(f"已安装并启动 LaunchAgent: {PLIST_PATH}") diff --git a/scripts/sync_to_vps.py b/scripts/sync_to_vps.py index 99ffc9e..78271e6 100755 --- a/scripts/sync_to_vps.py +++ b/scripts/sync_to_vps.py @@ -45,7 +45,7 @@ def parse_args() -> argparse.Namespace: parser.add_argument( "--identity-file", default=os.getenv("XSK_IDENTITY_FILE", str(DEFAULT_IDENTITY_FILE) if DEFAULT_IDENTITY_FILE.exists() else ""), - help="SSH 私钥路径;默认使用 ~/.ssh/xsk_records_vps_ed25519(如果存在)", + help="SSH 私钥路径;默认兼容使用历史文件 ~/.ssh/xsk_records_vps_ed25519(如果存在)", ) parser.add_argument( "--use-sshpass", @@ -161,7 +161,7 @@ def current_signatures(local_dir: Path) -> dict[str, tuple[int, int]]: def watch(config: Config) -> None: - log("启动课程记录同步监听") + log("启动新时空教务管理系统数据同步监听") signatures: dict[str, tuple[int, int]] = {} while True: try: