feat: 业务层 services 抽取完成(工作台/用户管理)

- DashboardService:OPC 工作台聚合(统计/进行中任务/智能体建议/月度收入/消息)
- UserAdminService:建号+默认身份联动、角色分配提权防护(action:role.grant_perm 仅超管)
- rbac_opc dashboard / rbac_admin create_user+set_role 接线
- services 层覆盖全部核心业务域,路由全瘦身
This commit is contained in:
2026-08-24 09:35:08 +08:00
parent 681067f84b
commit c99662cfec
4 changed files with 104 additions and 61 deletions
+8 -17
View File
@@ -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,
+2 -44
View File
@@ -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 子页面(全部由服务端提供标准数据)─────────────────────────────────
+39
View File
@@ -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),
}
+55
View File
@@ -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)