Files
group_fqcd_jr/tools/grant_promotion_operator.py
2026-09-14 01:07:43 +08:00

155 lines
5.5 KiB
Python

"""给指定运营账号授予推介材料生成权限。"""
from __future__ import annotations
import argparse
import asyncio
import sys
from datetime import UTC, datetime, timedelta
from pathlib import Path
from sqlalchemy import text
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from app.core.contracts import RequestContext # noqa: E402
from app.infrastructure.db import SessionFactory, engine # noqa: E402
from app.service.identity_service import IdentityService # noqa: E402
PROMOTION_ROLE_CODE = "promotion_operator"
PROMOTION_ROLE_NAME = "推广材料运营"
DEFAULT_USER_ID = 9006
GRANTED_CODES = ("promotion:read", "promotion:write")
async def apply(user_id: int, *, dry_run: bool) -> int:
now = datetime.now(UTC).replace(tzinfo=None)
assigned_at = now - timedelta(seconds=5)
async with SessionFactory() as session, session.begin():
user = (
await session.execute(
text(
"SELECT id, username, status FROM sys_user WHERE id=:user_id"
),
{"user_id": user_id},
)
).mappings().first()
if user is None:
raise SystemExit(f"未找到用户 id={user_id}")
if user["status"] != "正常":
raise SystemExit(f"用户 id={user_id} 未启用,拒绝授权")
role_id = await session.scalar(
text("SELECT id FROM sys_role WHERE role_code=:code"),
{"code": PROMOTION_ROLE_CODE},
)
permission_rows = (
await session.execute(
text(
"SELECT permission_code, id FROM sys_permission "
"WHERE permission_code IN ('promotion:read', 'promotion:write')"
)
)
).all()
permission_ids = {str(code): int(permission_id) for code, permission_id in permission_rows}
missing = [code for code in GRANTED_CODES if code not in permission_ids]
if missing:
raise SystemExit(f"数据库缺少权限码,请先运行权限种子:{missing}")
print(f"用户:id={user_id} username={user['username']}")
print(
f"角色:{PROMOTION_ROLE_CODE} "
f"{'已存在 id=' + str(role_id) if role_id else '将新建'}"
)
print(f"授权范围:{', '.join(GRANTED_CODES)}")
if dry_run:
print("[dry-run] 未写入任何数据")
return 0
if role_id is None:
await session.execute(
text(
"""
INSERT INTO sys_role
(role_code, role_name, status, created_at, updated_at)
VALUES (:code, :name, 'active', :now, :now)
"""
),
{"code": PROMOTION_ROLE_CODE, "name": PROMOTION_ROLE_NAME, "now": now},
)
role_id = await session.scalar(
text("SELECT id FROM sys_role WHERE role_code=:code"),
{"code": PROMOTION_ROLE_CODE},
)
role_id = int(role_id)
for code in GRANTED_CODES:
exists = await session.scalar(
text(
"""
SELECT COUNT(*) FROM sys_role_permission
WHERE role_id=:role_id AND permission_id=:permission_id
"""
),
{"role_id": role_id, "permission_id": permission_ids[code]},
)
if not exists:
await session.execute(
text(
"""
INSERT INTO sys_role_permission (role_id, permission_id, created_at)
VALUES (:role_id, :permission_id, :now)
"""
),
{
"role_id": role_id,
"permission_id": permission_ids[code],
"now": now,
},
)
await session.execute(
text(
"""
INSERT INTO sys_user_role (user_id, role_id, assigned_at)
VALUES (:user_id, :role_id, :assigned_at)
ON DUPLICATE KEY UPDATE assigned_at=:assigned_at
"""
),
{"user_id": user_id, "role_id": role_id, "assigned_at": assigned_at},
)
return await verify(user_id)
async def verify(user_id: int) -> int:
context = await IdentityService().resolve(
RequestContext(user_id=str(user_id), trace_id="grant-promotion-operator")
)
expected = set(GRANTED_CODES)
actual = set(context.permissions)
print(f"真实解析角色:{sorted(context.roles)}")
print(f"真实解析权限:{sorted(actual)}")
missing = expected - actual
if missing:
raise SystemExit(f"授权验证失败,缺少权限:{sorted(missing)}")
print("推介材料生成权限已生效:promotion:read、promotion:write")
return 0
async def main() -> int:
parser = argparse.ArgumentParser(description="授予运营账号推介材料生成权限")
parser.add_argument("--user-id", type=int, default=DEFAULT_USER_ID)
parser.add_argument("--dry-run", action="store_true")
args = parser.parse_args()
try:
return await apply(args.user_id, dry_run=args.dry_run)
finally:
await engine.dispose()
if __name__ == "__main__":
sys.exit(asyncio.run(main()))