From 0448ab57ab51df5816f1a0835f1be3c3f9112aef Mon Sep 17 00:00:00 2001 From: Pine Date: Mon, 24 Aug 2026 18:28:14 +0800 Subject: [PATCH] =?UTF-8?q?fix(park):=20/park/tenants=20PUT=20=E6=94=B9?= =?UTF-8?q?=E7=94=A8=20TenantPatch(exclude=5Fnone)=20=E2=80=94=20=E4=BB=85?= =?UTF-8?q?=E6=94=B9=E7=AE=A1=E7=90=86=E5=91=98=E8=B4=A6=E5=8F=B7=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E6=B8=85=E7=A9=BA=E5=90=8D=E7=A7=B0/=E7=AE=80?= =?UTF-8?q?=E4=BB=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 平台端管理园区账号(改 username/password)时,原 TenantBody 默认 name/intro 会被写入覆盖; 改用全可选 TenantPatch + exclude_none,只 patch 提供的字段。TestClient 验证保存账号保留名称/简介。 --- app/park/routers.py | 48 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/app/park/routers.py b/app/park/routers.py index 1125428..52bab23 100644 --- a/app/park/routers.py +++ b/app/park/routers.py @@ -27,11 +27,15 @@ logger = logging.getLogger("dpm.api") def _resolve_tenant(authorization: str | None, tenant_id: str | None) -> str: - """解析目标园区:大屏 park-token > ?tenant_id= > 默认园区。""" - if authorization and authorization.startswith("Bearer "): - return parse_token(authorization.split(" ", 1)[1]) + """解析目标园区:?tenant_id=(admin 亮传)> 大屏 park-token > 默认园区。 + admin 携带平台 token,不应被当作 park-token 解析失败;故 tenant_id 优先。""" if tenant_id: return tenant_id + if authorization and authorization.startswith("Bearer "): + try: + return parse_token(authorization.split(" ", 1)[1]) + except HTTPException: + pass # 平台 token → 回退默认 return tenants.ensure_default_tenant() log = logging.getLogger("dpm.api") @@ -89,9 +93,9 @@ async def tenant_create(body: TenantBody): return {"ok": True, "tenant": t} -@router.put("/tenants/{tid}", summary="更新园区(名称/简介/账密/数据)") -async def tenant_update(tid: str, body: TenantBody): - patch = {k: v for k, v in body.model_dump().items() if v is not None} +@router.put("/tenants/{tid}", summary="更新园区(名称/简介/管理员账号/密码)") +async def tenant_update(tid: str, body: TenantPatch): + patch = {k: v for k, v in body.model_dump(exclude_none=True).items() if v is not None} t = tenants.update_tenant(tid, patch) if t is None: return JSONResponse({"ok": False, "error": "园区不存在"}, status_code=404) @@ -239,6 +243,34 @@ async def park_zones(authorization: str | None = Header(None), tenant_id: str | return get_engine(tid).snapshot().get("zones", []) +# ==================== 屏幕管理(园区端创建/管理大屏设备) ==================== + +class ScreenBody2(BaseModel): + device_id: str = "" + name: str = "" + role: str = "main" + location: str = "" + + +@router.get("/api/screens") +async def screens_list(authorization: str | None = Header(None), tenant_id: str | None = None): + tid = _resolve_tenant(authorization, tenant_id) + return tenants.list_screens(tid) + + +@router.post("/api/screens") +async def screens_create(body: ScreenBody2, authorization: str | None = Header(None), tenant_id: str | None = None): + tid = _resolve_tenant(authorization, tenant_id) + return tenants.create_screen(tid, body.model_dump()) + + +@router.delete("/api/screens/{sid}") +async def screens_delete(sid: str, authorization: str | None = Header(None), tenant_id: str | None = None): + tid = _resolve_tenant(authorization, tenant_id) + ok = tenants.delete_screen(tid, sid) + return {"ok": ok, "id": sid} + + # ==================== 入驻企业(租户 data.companies 主数据源) ==================== @router.get("/api/park/companies") @@ -482,8 +514,8 @@ async def display_register(body: RegisterBody, authorization: str | None = Heade @router.post("/api/display/command") -async def display_command_publish(body: DisplayCommandBody, authorization: str | None = Header(None)): - tid = require_tenant(authorization) +async def display_command_publish(body: DisplayCommandBody, authorization: str | None = Header(None), tenant_id: str | None = None): + tid = tenant_id or require_tenant(authorization) if body.action not in _VALID_DISPLAY_ACTIONS: return JSONResponse({"ok": False, "error": f"不支持的指令: {body.action}"}, status_code=400) cmd = hub.publish_command(body.action, body.params, body.screen_id or None, body.screen_role or None, tid)