feat(auth): 账号类型统一为四种(运营方/载体方/OPC/服务方) + 关闭子角色 + 权限逻辑集中

- 新增 app/domain/account_types.py(唯一来源): account_type(role)→operator/carrier/opc/service,
  非运营非OPC一律→service(预留服务商/甲方企业); permission_role(类型)→权限角色码; label()
- _issue_token/_profile_for 统一走账号类型: 权限=permissions_for(permission_role(atype)),
  子角色置None(停用); 资料暴露 account_type/account_type_label
- 端口角色(role)仅保留做内部路由守卫兼容, 对外鉴权用四种账号类型
This commit is contained in:
Pine
2026-08-26 13:03:44 +08:00
parent cabc49eda9
commit af6f01c5a9
2 changed files with 63 additions and 8 deletions
+13 -8
View File
@@ -39,6 +39,7 @@ from ..schemas.auth import (
)
from ...infrastructure.repositories import Database
from ...services import sms, wechat
from ...domain.account_types import account_type, permission_role, label as account_type_label
router = APIRouter(prefix="/auth", tags=["auth"])
@@ -144,17 +145,19 @@ async def _issue_token(
否则回退到 ``users.role``(单角色/中性令牌)。
"""
role = (identity or user).get("role", "opc_member")
sub_role = (identity or user).get("sub_role")
# 账号类型(四种)统一解析;子角色已停用(置 None)
atype = account_type(role)
sub_role = None
org_id = (identity or user).get("org_id")
region_id = (identity or user).get("region_id")
identity_id = identity.get("id") if identity else None
perms = await db.roles.permissions_for(role, sub_role)
perms = await db.roles.permissions_for(permission_role(atype), None)
scope_ids = await db.regions.visible_region_ids(region_id)
scope_level = await db.regions.level(region_id)
token_user = dict(user)
token_user.update(role=role, sub_role=sub_role, org_id=org_id, region_id=region_id)
token_user.update(account_type=atype, role=role, sub_role=None, org_id=org_id, region_id=region_id)
await db.users.mark_login(user["id"]) # 记录最近登录时间(每次签发令牌=一次活动)
return await db.tokens.create(
token_user,
@@ -173,7 +176,8 @@ async def _profile_for(
) -> dict:
"""按身份(或回退用户单角色)构建对外资料字典。"""
role = (identity or user).get("role", "opc_member")
sub_role = (identity or user).get("sub_role")
atype = account_type(role) # 四种账号类型(子角色已停用)
sub_role = None
org_id = (identity or user).get("org_id")
region_id = (identity or user).get("region_id")
profile = {f: user.get(f, "") for f in _PROFILE_FIELDS}
@@ -195,12 +199,13 @@ async def _profile_for(
profile["affiliation"] = user.get("affiliation", "independent")
profile["park_id"] = user.get("park_id", "")
profile["park_name"] = user.get("park_name", "")
profile["account_type"] = user.get("account_type", "opc_default")
profile["role"] = role
profile["sub_role"] = sub_role
profile["account_type"] = atype # 四种账号类型(operator/carrier/opc/service
profile["account_type_label"] = account_type_label(atype)
profile["role"] = role # 端口角色(兼容守卫)
profile["sub_role"] = None # 子角色已停用
profile["org_id"] = org_id
profile["region_id"] = region_id
profile["permissions"] = await db.roles.permissions_for(role, sub_role)
profile["permissions"] = await db.roles.permissions_for(permission_role(atype), None)
profile["scope_region_ids"] = await db.regions.visible_region_ids(region_id)
if identity:
profile["identity_id"] = identity["id"]
+50
View File
@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
"""账号类型(唯一来源)—— 权限/身份逻辑集中于此,不分散。
账号类型只四种:
operator 运营方 —— 平台运营
carrier 载体方 —— 创业载体/园区运营方
opc OPC —— 超级个体
service 服务方 —— 其余全部(服务商/甲方企业/政务/投资/开发…… 统一归服务方,
为后期扩展 服务商/甲方企业 等预留)
子角色(sub_role)已停用:权限一律按账号类型解析,不再读子角色。
端口角色(role, 如 opc_member/provider/enterprise...) 仅作内部路由守卫兼容,
对外暴露/鉴权的「账号类型」统一用本模块。
"""
from __future__ import annotations
ACCOUNT_TYPES = ("operator", "carrier", "opc", "service")
def account_type(role: str | None) -> str:
"""把任意(端口)角色归一为四种账号类型之一。非运营/非OPC → service。"""
r = (role or "").strip().lower()
if r in ("operator", "operator_internal", "op_admin", "admin"):
return "operator"
if r in ("carrier", "park", "park_staff", "carrier_staff"):
return "carrier"
if r in ("opc_member", "opc", "opc_certified", "opc_independent"):
return "opc"
# provider / enterprise / government / investor / ... 全部归 服务方
return "service"
def permission_role(atype: str) -> str:
"""账号类型 → 用于查权限表( roles/<code> )的角色码。service 用 provider 基座。"""
return {
"operator": "operator",
"carrier": "carrier",
"opc": "opc_member",
"service": "provider",
}.get(atype, "provider")
def label(atype: str) -> str:
"""账号类型中文展示名。"""
return {
"operator": "运营方",
"carrier": "载体方",
"opc": "OPC",
"service": "服务方",
}.get(atype, "服务方")