#!/usr/bin/env python3 """仅刷新 5 个业务角色的菜单授权(不删角色、不动用户绑定)。 用于修复「角色只勾 F 按钮、缺少 C 页面 :list」导致前端侧栏为空的问题。 用法: cd backend && ./venv/bin/python scripts/refresh_business_role_menus.py --env-file .env.production ./venv/bin/python scripts/refresh_business_role_menus.py --dry-run --env-file .env.production """ from __future__ import annotations import argparse import os import sys from datetime import datetime from pathlib import Path from sqlalchemy import create_engine, text BACKEND_ROOT = Path(__file__).resolve().parents[1] if str(BACKEND_ROOT) not in sys.path: sys.path.insert(0, str(BACKEND_ROOT)) from scripts.setup_business_roles import ( # noqa: E402 MODULE_MENU_ROOTS, NEW_ROLES, TENANT_NO, collect_subtree_menu_ids, grant_module_menus, perm_menu_ids, ) def load_settings_env(env_file: str) -> None: if env_file: os.environ["ENV_FILE"] = env_file from dotenv import load_dotenv path = Path(env_file) if env_file else BACKEND_ROOT / ".env.production" if not path.is_absolute(): path = BACKEND_ROOT / path if path.is_file(): load_dotenv(path, override=True) def load_runtime_settings(): from app.core.config import Settings return Settings() def verify_user_menu_codes(engine, user_no: str) -> None: from sqlalchemy.orm import Session from app.services.web_auth_roles import iter_user_menu_perm_codes with Session(engine) as db: row = db.execute( text( """ SELECT id FROM sys_user WHERE tenant_no = :tenant AND user_no = :user_no AND del_flag = 0 LIMIT 1 """ ), {"tenant": TENANT_NO, "user_no": user_no}, ).first() if not row: print(f" [verify] 用户不存在: {user_no}") return uid = str(row[0]) codes = sorted(iter_user_menu_perm_codes(db, uid, TENANT_NO)) list_codes = [c for c in codes if c.endswith(":list")] print(f" [verify] {user_no}: 共 {len(codes)} 个 perms,其中 :list {len(list_codes)} 个") if list_codes: print(f" 示例: {', '.join(list_codes[:5])}...") else: print(" 警告: 无任何 :list 权限,前端侧栏仍会为空") def main() -> int: parser = argparse.ArgumentParser(description="刷新业务角色菜单授权") parser.add_argument("--env-file", default=".env.production") parser.add_argument("--dry-run", action="store_true") parser.add_argument("--verify-user", default="方金强", help="刷新后校验该用户的 menuCodes") args = parser.parse_args() load_settings_env(args.env_file) settings = load_runtime_settings() engine = create_engine(settings.DATABASE_URL, pool_pre_ping=True) print("=== refresh_business_role_menus ===") print(f"模式: {'DRY-RUN' if args.dry_run else 'EXECUTE'}") print(f"租户: {TENANT_NO}") print() with engine.begin() as conn: subtree = collect_subtree_menu_ids(conn, MODULE_MENU_ROOTS, TENANT_NO) grant_ids = perm_menu_ids(conn, subtree, TENANT_NO) print(f"模块子树 {len(subtree)} 项,带 perms 菜单 {len(grant_ids)} 项") for role_name, role_id in NEW_ROLES.items(): cnt = grant_module_menus(conn, role_id, grant_ids, TENANT_NO, args.dry_run) print(f" {role_name}: {cnt} 项") if not args.dry_run and args.verify_user: print() print("校验 menuCodes:") verify_user_menu_codes(engine, args.verify_user) print("完成.") return 0 if __name__ == "__main__": raise SystemExit(main())