feat(园区端): 园区状态(禁用/启用)+联级删除 — 禁用禁止登录,删除一并删资源

park_tenants 增 status 列(migration)、set_status、/park/tenants/{id}/status 端点;
/ park/auth/login 对被禁用园区返回 403;delete_tenant 联级删 企业/知识库/屏幕+引擎缓存;
_summary 含 status。TestClient:禁用→登录403、启用→200、删除→资源清零。
This commit is contained in:
Pine
2026-08-24 19:05:16 +08:00
parent 6458d5eb98
commit 88882553a9
2 changed files with 36 additions and 4 deletions
+14
View File
@@ -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):
+22 -4
View File
@@ -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: