From 88882553a991ee923a2da47472849a65582d0ba4 Mon Sep 17 00:00:00 2001 From: Pine Date: Mon, 24 Aug 2026 19:05:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=9B=AD=E5=8C=BA=E7=AB=AF):=20=E5=9B=AD?= =?UTF-8?q?=E5=8C=BA=E7=8A=B6=E6=80=81(=E7=A6=81=E7=94=A8/=E5=90=AF?= =?UTF-8?q?=E7=94=A8)+=E8=81=94=E7=BA=A7=E5=88=A0=E9=99=A4=20=E2=80=94=20?= =?UTF-8?q?=E7=A6=81=E7=94=A8=E7=A6=81=E6=AD=A2=E7=99=BB=E5=BD=95=EF=BC=8C?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E4=B8=80=E5=B9=B6=E5=88=A0=E8=B5=84=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit park_tenants 增 status 列(migration)、set_status、/park/tenants/{id}/status 端点; / park/auth/login 对被禁用园区返回 403;delete_tenant 联级删 企业/知识库/屏幕+引擎缓存; _summary 含 status。TestClient:禁用→登录403、启用→200、删除→资源清零。 --- app/park/routers.py | 14 ++++++++++++++ app/park/tenants.py | 26 ++++++++++++++++++++++---- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/app/park/routers.py b/app/park/routers.py index 1d4ba68..6f692bb 100644 --- a/app/park/routers.py +++ b/app/park/routers.py @@ -80,6 +80,8 @@ async def park_login(body: LoginBody): t = tenants.find_by_admin(body.username.strip()) if not t: return JSONResponse({"ok": False, "error": "该账号未绑定任何园区管理员"}, status_code=401) + if t.get("status") == "disabled": + return JSONResponse({"ok": False, "error": "该园区已禁用,禁止登录使用"}, status_code=403) finally: await db.close() return {"ok": True, "tenant_id": t["id"], "name": t["name"], "intro": t["intro"], "token": create_token(t["id"])} @@ -142,6 +144,18 @@ async def tenant_unbind(tid: str): return {"ok": ok, "tenant_id": tid} +class StatusBody(BaseModel): + status: str # active | disabled + + +@router.post("/tenants/{tid}/status", summary="禁用/启用园区(不删,禁用禁止登录)") +async def tenant_status(tid: str, body: StatusBody): + ok = tenants.set_status(tid, body.status) + if not ok: + return JSONResponse({"ok": False, "error": "无效状态或园区不存在"}, status_code=400) + return {"ok": True, "tenant_id": tid, "status": body.status} + + # ==================== 数据模型 ==================== class SettingsBody(BaseModel): diff --git a/app/park/tenants.py b/app/park/tenants.py index b3edb41..a65c1e9 100644 --- a/app/park/tenants.py +++ b/app/park/tenants.py @@ -34,6 +34,7 @@ CREATE TABLE IF NOT EXISTS park_tenants ( salt TEXT, password_hash TEXT, admin_username TEXT DEFAULT '', -- 绑定的平台账号(园区管理员,园区端登录用) + status TEXT DEFAULT 'active', -- active | disabled(禁用则不删、禁止登录/使用) data_json TEXT, agent_json TEXT, created_at TEXT @@ -86,6 +87,8 @@ def init_db(): cols = [r["name"] for r in conn.execute("PRAGMA table_info(park_tenants)").fetchall()] if "admin_username" not in cols: conn.execute("ALTER TABLE park_tenants ADD COLUMN admin_username TEXT DEFAULT ''") + if "status" not in cols: + conn.execute("ALTER TABLE park_tenants ADD COLUMN status TEXT DEFAULT 'active'") conn.commit() conn.close() ensure_default_tenant() @@ -118,6 +121,7 @@ def _row_to_tenant(row) -> dict: "intro": json.loads(row["intro_json"] or "[]"), "auth": {"username": row["username"], "salt": row["salt"], "password_hash": row["password_hash"]}, "admin_username": row["admin_username"] or "", + "status": row["status"] if "status" in row.keys() else "active", "data": json.loads(row["data_json"] or "{}"), "agent": json.loads(row["agent_json"] or "{}"), } @@ -222,15 +226,29 @@ def update_tenant(tenant_id: str, patch: dict) -> dict | None: def delete_tenant(tenant_id: str) -> bool: + """联级删除园区:租户行 + 全部资源(企业/知识库/屏幕/引擎)。""" conn = _conn() cur = conn.execute("DELETE FROM park_tenants WHERE id=?", (tenant_id,)) conn.execute("DELETE FROM park_companies WHERE tenant_id=?", (tenant_id,)) conn.execute("DELETE FROM park_kb_docs WHERE tenant_id=?", (tenant_id,)) + conn.execute("DELETE FROM park_screens WHERE tenant_id=?", (tenant_id,)) + for cid in [c["id"] for c in conn.execute("SELECT id FROM park_tenants").fetchall()]: + pass # 引擎缓存后续统一清理 conn.commit() conn.close() - refresh_engine(tenant_id) # 淘汰缓存引擎 from .sim_engine import _engines - _engines.pop(tenant_id, None) + _engines.pop(tenant_id, None) # 淘汰缓存引擎 + return cur.rowcount > 0 + + +def set_status(tenant_id: str, status: str) -> bool: + """禁用/启用园区(disabled 不删除,但禁止登录/使用)。""" + if status not in ("active", "disabled"): + return False + conn = _conn() + cur = conn.execute("UPDATE park_tenants SET status=? WHERE id=?", (status, tenant_id)) + conn.commit() + conn.close() return cur.rowcount > 0 @@ -266,8 +284,8 @@ def ensure_default_tenant() -> str: return _DEFAULT_TENANT_ID -def _summary(tid: str, name: str, intro: list, username: str, admin_username: str = "") -> dict: - return {"id": tid, "name": name, "intro": intro, "username": username, "admin_username": admin_username} +def _summary(tid: str, name: str, intro: list, username: str, admin_username: str = "", status: str = "active") -> dict: + return {"id": tid, "name": name, "intro": intro, "username": username, "admin_username": admin_username, "status": status} def _seed_companies(tenant_id: str, companies: list) -> None: