From af6f01c5a909017dd25996e6acb5070184d35a5e Mon Sep 17 00:00:00 2001 From: Pine Date: Wed, 26 Aug 2026 13:03:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(auth):=20=E8=B4=A6=E5=8F=B7=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E7=BB=9F=E4=B8=80=E4=B8=BA=E5=9B=9B=E7=A7=8D(?= =?UTF-8?q?=E8=BF=90=E8=90=A5=E6=96=B9/=E8=BD=BD=E4=BD=93=E6=96=B9/OPC/?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E6=96=B9)=20+=20=E5=85=B3=E9=97=AD=E5=AD=90?= =?UTF-8?q?=E8=A7=92=E8=89=B2=20+=20=E6=9D=83=E9=99=90=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E9=9B=86=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 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)仅保留做内部路由守卫兼容, 对外鉴权用四种账号类型 --- app/api/routers/auth.py | 21 ++++++++++------ app/domain/account_types.py | 50 +++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 8 deletions(-) create mode 100644 app/domain/account_types.py diff --git a/app/api/routers/auth.py b/app/api/routers/auth.py index 5709a88..22c9d47 100644 --- a/app/api/routers/auth.py +++ b/app/api/routers/auth.py @@ -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"] diff --git a/app/domain/account_types.py b/app/domain/account_types.py new file mode 100644 index 0000000..6d9c9d8 --- /dev/null +++ b/app/domain/account_types.py @@ -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/ )的角色码。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, "服务方")