fix(org): 机构/企业成员统一返回格式(补齐姓名、头像)

- _normalize_org_member 统一 user_id/name/username/avatar/role/is_admin
- 机构成员无头像时批量从 users 表补查
This commit is contained in:
Pine
2026-09-13 11:14:20 +08:00
parent 000e3b5cb7
commit 76351cc91a
+66 -6
View File
@@ -6,11 +6,14 @@ import logging
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from sqlalchemy import select
from ..dependencies import get_db, get_user_organizations, get_user_primary_org
from ..schemas.org import OrgMemberAdd
from ...rbac import require_roles
from ...infrastructure.repositories import Database
from ...infrastructure.oss import resolve_url
from ...infrastructure.models import User
from ...park import tenants as tnt
log = logging.getLogger("rbac.org")
@@ -18,6 +21,28 @@ log = logging.getLogger("rbac.org")
router = APIRouter(tags=["org"])
async def _normalize_org_member(raw: dict) -> dict:
"""统一机构/企业成员返回格式,确保前端 OrgMember 类型字段齐全。
前端期望字段:user_id, name, username, avatar, role, is_admin
企业成员(tnt.company_members)返回:id, username, nickname, avatar, role, is_admin
机构成员(db.org_members.list_for_org)返回:user_id, username, nickname, role, is_admin(无avatar
"""
user_id = str(raw.get("user_id") or raw.get("id") or "")
username = raw.get("username") or ""
nickname = raw.get("nickname") or ""
name = nickname or username or user_id
avatar = raw.get("avatar") or ""
return {
"user_id": user_id,
"name": name,
"username": username,
"avatar": resolve_url(avatar),
"role": raw.get("role") or "",
"is_admin": bool(raw.get("is_admin", False)),
}
@router.get("/org/me", summary="当前组织信息")
async def org_me(
db: Database = Depends(get_db),
@@ -75,20 +100,55 @@ async def org_members(
# 叠加制权限:operator 全量;本 org 的载体机构管理员;本企业的企业成员(org_id 亦可能是 company_id
caps = user.get("capabilities") or []
if "operator" in caps:
pass
log.info(f"[org_members] operator access, org_id={org_id}, user_id={user['id']}")
elif await db.org_members.is_admin(org_id, user["id"]):
pass
log.info(f"[org_members] org admin access, org_id={org_id}, user_id={user['id']}")
elif await db.company_members.get(user["id"], org_id):
pass
log.info(f"[org_members] company member access, org_id={org_id}, user_id={user['id']}")
else:
log.warning(f"[org_members] 403 forbidden, org_id={org_id}, user_id={user['id']}, caps={caps}")
raise HTTPException(status_code=403, detail="无权查看该组织成员")
# org_id 可能是 organization_members.org_id(载体机构),也可能被前端当作 company_id 传入:
# 优先返回企业成员(命中企业时),否则回退到机构成员。
company_members = await tnt.company_members(org_id)
try:
company_members = await tnt.company_members(org_id)
log.info(f"[org_members] tnt.company_members returned {len(company_members)} members for org_id={org_id}")
except Exception as e:
log.error(f"[org_members] tnt.company_members failed for org_id={org_id}: {e}", exc_info=True)
company_members = []
if company_members:
return {"items": company_members}
return {"items": await db.org_members.list_for_org(org_id)}
items = [await _normalize_org_member(m) for m in company_members]
log.info(f"[org_members] returning {len(items)} company members for org_id={org_id}")
return {"items": items}
try:
org_members_list = await db.org_members.list_for_org(org_id)
log.info(f"[org_members] db.org_members.list_for_org returned {len(org_members_list)} members for org_id={org_id}")
except Exception as e:
log.error(f"[org_members] db.org_members.list_for_org failed for org_id={org_id}: {e}", exc_info=True)
org_members_list = []
# 机构成员没有 avatar 字段,批量从 users 表补查
user_ids = [str(m.get("user_id") or "") for m in org_members_list if m.get("user_id")]
avatar_map = {}
if user_ids:
try:
users = (await db.session.execute(
select(User).where(User.id.in_(user_ids))
)).scalars().all()
avatar_map = {u.id: (u.avatar or "") for u in users}
except Exception as e:
log.error(f"[org_members] failed to fetch avatars: {e}", exc_info=True)
avatar_map = {}
for m in org_members_list:
uid = str(m.get("user_id") or "")
if uid and not m.get("avatar"):
m["avatar"] = avatar_map.get(uid, "")
items = [await _normalize_org_member(m) for m in org_members_list]
log.info(f"[org_members] returning {len(items)} org members for org_id={org_id}")
return {"items": items}
@router.post("/org/{org_id}/members", summary="添加机构成员(子账号)")