refresh_business_role_menus.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. #!/usr/bin/env python3
  2. """仅刷新 5 个业务角色的菜单授权(不删角色、不动用户绑定)。
  3. 用于修复「角色只勾 F 按钮、缺少 C 页面 :list」导致前端侧栏为空的问题。
  4. 用法:
  5. cd backend && ./venv/bin/python scripts/refresh_business_role_menus.py --env-file .env.production
  6. ./venv/bin/python scripts/refresh_business_role_menus.py --dry-run --env-file .env.production
  7. """
  8. from __future__ import annotations
  9. import argparse
  10. import os
  11. import sys
  12. from datetime import datetime
  13. from pathlib import Path
  14. from sqlalchemy import create_engine, text
  15. BACKEND_ROOT = Path(__file__).resolve().parents[1]
  16. if str(BACKEND_ROOT) not in sys.path:
  17. sys.path.insert(0, str(BACKEND_ROOT))
  18. from scripts.setup_business_roles import ( # noqa: E402
  19. MODULE_MENU_ROOTS,
  20. NEW_ROLES,
  21. TENANT_NO,
  22. collect_subtree_menu_ids,
  23. grant_module_menus,
  24. perm_menu_ids,
  25. )
  26. def load_settings_env(env_file: str) -> None:
  27. if env_file:
  28. os.environ["ENV_FILE"] = env_file
  29. from dotenv import load_dotenv
  30. path = Path(env_file) if env_file else BACKEND_ROOT / ".env.production"
  31. if not path.is_absolute():
  32. path = BACKEND_ROOT / path
  33. if path.is_file():
  34. load_dotenv(path, override=True)
  35. def load_runtime_settings():
  36. from app.core.config import Settings
  37. return Settings()
  38. def verify_user_menu_codes(engine, user_no: str) -> None:
  39. from sqlalchemy.orm import Session
  40. from app.services.web_auth_roles import iter_user_menu_perm_codes
  41. with Session(engine) as db:
  42. row = db.execute(
  43. text(
  44. """
  45. SELECT id FROM sys_user
  46. WHERE tenant_no = :tenant AND user_no = :user_no AND del_flag = 0
  47. LIMIT 1
  48. """
  49. ),
  50. {"tenant": TENANT_NO, "user_no": user_no},
  51. ).first()
  52. if not row:
  53. print(f" [verify] 用户不存在: {user_no}")
  54. return
  55. uid = str(row[0])
  56. codes = sorted(iter_user_menu_perm_codes(db, uid, TENANT_NO))
  57. list_codes = [c for c in codes if c.endswith(":list")]
  58. print(f" [verify] {user_no}: 共 {len(codes)} 个 perms,其中 :list {len(list_codes)} 个")
  59. if list_codes:
  60. print(f" 示例: {', '.join(list_codes[:5])}...")
  61. else:
  62. print(" 警告: 无任何 :list 权限,前端侧栏仍会为空")
  63. def main() -> int:
  64. parser = argparse.ArgumentParser(description="刷新业务角色菜单授权")
  65. parser.add_argument("--env-file", default=".env.production")
  66. parser.add_argument("--dry-run", action="store_true")
  67. parser.add_argument("--verify-user", default="方金强", help="刷新后校验该用户的 menuCodes")
  68. args = parser.parse_args()
  69. load_settings_env(args.env_file)
  70. settings = load_runtime_settings()
  71. engine = create_engine(settings.DATABASE_URL, pool_pre_ping=True)
  72. print("=== refresh_business_role_menus ===")
  73. print(f"模式: {'DRY-RUN' if args.dry_run else 'EXECUTE'}")
  74. print(f"租户: {TENANT_NO}")
  75. print()
  76. with engine.begin() as conn:
  77. subtree = collect_subtree_menu_ids(conn, MODULE_MENU_ROOTS, TENANT_NO)
  78. grant_ids = perm_menu_ids(conn, subtree, TENANT_NO)
  79. print(f"模块子树 {len(subtree)} 项,带 perms 菜单 {len(grant_ids)} 项")
  80. for role_name, role_id in NEW_ROLES.items():
  81. cnt = grant_module_menus(conn, role_id, grant_ids, TENANT_NO, args.dry_run)
  82. print(f" {role_name}: {cnt} 项")
  83. if not args.dry_run and args.verify_user:
  84. print()
  85. print("校验 menuCodes:")
  86. verify_user_menu_codes(engine, args.verify_user)
  87. print("完成.")
  88. return 0
  89. if __name__ == "__main__":
  90. raise SystemExit(main())