From c99662cfeccd8b837f75830a9fda2691411c052d Mon Sep 17 00:00:00 2001 From: PineHomePC Date: Mon, 24 Aug 2026 09:35:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=9A=E5=8A=A1=E5=B1=82=20services?= =?UTF-8?q?=20=E6=8A=BD=E5=8F=96=E5=AE=8C=E6=88=90=EF=BC=88=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E5=8F=B0/=E7=94=A8=E6=88=B7=E7=AE=A1=E7=90=86?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DashboardService:OPC 工作台聚合(统计/进行中任务/智能体建议/月度收入/消息) - UserAdminService:建号+默认身份联动、角色分配提权防护(action:role.grant_perm 仅超管) - rbac_opc dashboard / rbac_admin create_user+set_role 接线 - services 层覆盖全部核心业务域,路由全瘦身 --- app/api/routers/rbac_admin.py | 25 +++++--------- app/api/routers/rbac_opc.py | 46 ++----------------------- app/services/dashboard_service.py | 39 +++++++++++++++++++++ app/services/user_admin_service.py | 55 ++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 61 deletions(-) create mode 100644 app/services/dashboard_service.py create mode 100644 app/services/user_admin_service.py diff --git a/app/api/routers/rbac_admin.py b/app/api/routers/rbac_admin.py index f65d247..5a48db3 100644 --- a/app/api/routers/rbac_admin.py +++ b/app/api/routers/rbac_admin.py @@ -50,22 +50,12 @@ async def create_user( db: Database = Depends(get_db), actor: dict = Depends(require_permission("action:user.assign_role")), ): - if not req.username.strip() or not req.password.strip(): - raise HTTPException(status_code=400, detail="账号与密码必填") - if await db.users.get_by_username(req.username) is not None: - raise HTTPException(status_code=400, detail="账号已存在") - await _validate_role_assignment(db, actor, req.role, req.sub_role) - user = await db.users.create( - req.username, req.password, nickname=req.nickname, - role=req.role, sub_role=req.sub_role, org_id=req.org_id, region_id=req.region_id, - ) - # 自动创建该账号的默认端口身份 - await db.identities.create(user["id"], port=_port_for_role(req.role), role=req.role, - sub_role=req.sub_role, org_id=req.org_id, region_id=req.region_id, - name=req.nickname or req.username) + from ...services.user_admin_service import UserAdminService + + user = await UserAdminService(db).create_user(req, actor) await write_audit(db, action="user.create", resource="user", resource_id=user["id"], detail=f"role={req.role}", user=actor, request=request) - return await db.users.to_profile(user) + return user def _port_for_role(role: str) -> str: @@ -94,11 +84,12 @@ async def set_user_role( db: Database = Depends(get_db), actor: dict = Depends(require_permission("action:user.assign_role")), ): + from ...services.user_admin_service import UserAdminService + if await db.users.get_by_id(user_id) is None: raise HTTPException(status_code=404, detail="User not found") - await _validate_role_assignment(db, actor, req.role, req.sub_role) - updated = await db.users.set_role( - user_id, req.role, req.sub_role, req.org_id, req.region_id, + updated = await UserAdminService(db).set_user_role( + actor, user_id, req.role, req.sub_role, req.org_id, req.region_id, ) await write_audit( db, action="role.assign", resource="user", resource_id=user_id, diff --git a/app/api/routers/rbac_opc.py b/app/api/routers/rbac_opc.py index 1e76f57..0497fd5 100644 --- a/app/api/routers/rbac_opc.py +++ b/app/api/routers/rbac_opc.py @@ -23,51 +23,9 @@ async def opc_dashboard( user: dict = Depends(require_roles("opc_member")), ): """返回当前 OPC 用户的工作台聚合数据(统计/进行中任务/智能体建议/月度收入/最新消息)。""" - uid = user["id"] + from ...services.dashboard_service import DashboardService - profile = await db.opc_profiles.get(uid) - task_counts = await db.opc_tasks.counts(uid) - opc_tasks = await db.opc_tasks.list_by_user(uid) - - # 进行中任务:进行中/紧急优先,取前 3 条 - active_tasks = [ - t for t in opc_tasks if t["status"] in ("in_progress", "urgent") - ][:3] - - # 智能体建议(由服务端依据真实数据生成) - policy_count = len( - [c for c in await db.content.list(ctype="policy", status="published")] - ) - agent_tips = [ - { - "title": "政策匹配提醒", - "desc": f"{policy_count} 项新政策与您高度匹配", - "type": "policy", - }, - { - "title": "报税截止提醒", - "desc": "本月报税截止 7月31日", - "type": "tax", - }, - { - "title": "技能提升推荐", - "desc": "推荐参加 UI 设计研修班", - "type": "skill", - }, - ] - - return { - "stats": { - "inProgressTasks": task_counts["in_progress"], - "completedTasks": task_counts["completed"], - "totalEarnings": await db.finance.total_income(uid), - "creditScore": (profile or {}).get("credit_score", 80), - }, - "activeTasks": active_tasks, - "agentTips": agent_tips, - "monthlyIncome": await db.finance.monthly_income(uid), - "recentMessages": await db.messages.recent(uid, limit=4), - } + return await DashboardService(db).opc_dashboard(user["id"]) # ── OPC 子页面(全部由服务端提供标准数据)───────────────────────────────── diff --git a/app/services/dashboard_service.py b/app/services/dashboard_service.py new file mode 100644 index 0000000..adf058c --- /dev/null +++ b/app/services/dashboard_service.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +"""业务层 · 工作台聚合服务(各端口 dashboard)。""" +from __future__ import annotations + +from ..infrastructure.repositories import Database + + +class DashboardService: + """OPC 工作台聚合:统计/进行中任务/智能体建议/月度收入/最新消息。""" + + def __init__(self, db: Database): + self.db = db + + async def opc_dashboard(self, uid: str) -> dict: + profile = await self.db.opc_profiles.get(uid) + task_counts = await self.db.opc_tasks.counts(uid) + opc_tasks = await self.db.opc_tasks.list_by_user(uid) + active_tasks = [t for t in opc_tasks if t["status"] in ("in_progress", "urgent")][:3] + + policy_count = len( + [c for c in await self.db.content.list(ctype="policy", status="published")] + ) + agent_tips = [ + {"title": "政策匹配提醒", "desc": f"{policy_count} 项新政策与您高度匹配", "type": "policy"}, + {"title": "报税截止提醒", "desc": "本月报税截止 7月31日", "type": "tax"}, + {"title": "技能提升推荐", "desc": "推荐参加 UI 设计研修班", "type": "skill"}, + ] + return { + "stats": { + "inProgressTasks": task_counts["in_progress"], + "completedTasks": task_counts["completed"], + "totalEarnings": await self.db.finance.total_income(uid), + "creditScore": (profile or {}).get("credit_score", 80), + }, + "activeTasks": active_tasks, + "agentTips": agent_tips, + "monthlyIncome": await self.db.finance.monthly_income(uid), + "recentMessages": await self.db.messages.recent(uid, limit=4), + } diff --git a/app/services/user_admin_service.py b/app/services/user_admin_service.py new file mode 100644 index 0000000..727ba63 --- /dev/null +++ b/app/services/user_admin_service.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +"""业务层 · 用户管理服务(角色分配提权防护 + 建号联动默认身份)。""" +from __future__ import annotations + +from fastapi import HTTPException + +from ..infrastructure.repositories import Database + +# 业务角色 → 端口映射(与领域规则一致) +_PORT_FOR_ROLE = { + "opc_member": "opc", "carrier": "carrier", "enterprise": "enterprise", + "provider": "provider", "government": "government", "operator": "operator", + "investor": "investor", "developer": "developer", +} + + +class UserAdminService: + """运营端账号/角色管理。""" + + def __init__(self, db: Database): + self.db = db + + async def _validate_role_assignment(self, actor: dict, role: str, sub_role: str | None) -> None: + """白名单 + 提权防护:拒绝非法角色组合,及无权者创建/提升特权角色。""" + key = role if not sub_role else f"{role}|{sub_role}" + if not await self.db.roles.role_exists(role): + raise HTTPException(status_code=400, detail=f"无效角色: {role}") + if sub_role and not await self.db.roles.role_exists(f"{role}|{sub_role}"): + raise HTTPException(status_code=400, detail=f"无效角色组合: {key}") + if "action:role.grant_perm" in await self.db.roles.permissions_for(role, sub_role) \ + and "action:role.grant_perm" not in actor.get("permissions", []): + raise HTTPException(status_code=403, detail="无权创建/分配特权角色(仅超级管理员)") + + async def create_user(self, req, actor: dict) -> dict: + """创建账号 + 自动创建默认端口身份(含提权防护)。""" + if not req.username.strip() or not req.password.strip(): + raise HTTPException(status_code=400, detail="账号与密码必填") + if await self.db.users.get_by_username(req.username) is not None: + raise HTTPException(status_code=400, detail="账号已存在") + await self._validate_role_assignment(actor, req.role, req.sub_role) + user = await self.db.users.create( + req.username, req.password, nickname=req.nickname, + role=req.role, sub_role=req.sub_role, org_id=req.org_id, region_id=req.region_id, + ) + await self.db.identities.create( + user["id"], port=_PORT_FOR_ROLE.get(req.role, "opc"), role=req.role, + sub_role=req.sub_role, org_id=req.org_id, region_id=req.region_id, + name=req.nickname or req.username, + ) + return await self.db.users.to_profile(user) + + async def set_user_role(self, actor: dict, user_id: str, role: str, sub_role: str, org_id: str, region_id: str) -> dict: + """分配角色(含提权防护,同步主身份并失效旧令牌)。""" + await self._validate_role_assignment(actor, role, sub_role) + return await self.db.users.set_role(user_id, role, sub_role, org_id, region_id)