包含多个会话的累积代码变更: - backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔 - admin-web: ETL 状态页、任务管理、调度配置、登录优化 - miniprogram: 看板页面、聊天集成、UI 组件、导航更新 - etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强 - tenant-admin: 项目初始化 - db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8) - packages/shared: 枚举和工具函数更新 - tools: 数据库工具、报表生成、健康检查 - docs: PRD/架构/部署/合约文档更新 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
641 lines
22 KiB
Python
641 lines
22 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""P18 任务引擎运营看板 API
|
||
|
||
提供转移日志查看、待审核任务管理、参数配置等端点。
|
||
所有端点需要 JWT 认证;写操作仅限 super_admin。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
from datetime import date
|
||
|
||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||
from psycopg2.extras import RealDictCursor
|
||
|
||
from app.auth.dependencies import CurrentUser, get_current_user
|
||
from app.database import get_connection
|
||
from app.schemas.admin_task_engine import (
|
||
CandidateAssistant,
|
||
CandidateListResponse,
|
||
CloseRequest,
|
||
CloseResponse,
|
||
ConfigParam,
|
||
ConfigParamCreate,
|
||
ConfigParamList,
|
||
ConfigParamResponse,
|
||
ConfigParamUpdate,
|
||
PendingReviewItem,
|
||
PendingReviewPage,
|
||
ReassignRequest,
|
||
ReassignResponse,
|
||
TransferLogItem,
|
||
TransferLogPage,
|
||
)
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
router = APIRouter(prefix="/api/admin/task-engine", tags=["任务引擎管理"])
|
||
|
||
# ---- 任务类型中文映射 ----
|
||
TASK_TYPE_LABELS = {
|
||
"high_priority_recall": "高优先召回",
|
||
"priority_recall": "优先召回",
|
||
"follow_up_visit": "客户回访",
|
||
"relationship_building": "关系构建",
|
||
}
|
||
|
||
|
||
# ---- 权限辅助函数 ----
|
||
|
||
def _require_super_admin(user: CurrentUser) -> None:
|
||
"""写操作权限校验:仅超级管理员可执行。"""
|
||
if "super_admin" not in user.roles:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_403_FORBIDDEN,
|
||
detail="仅超级管理员可执行此操作",
|
||
)
|
||
|
||
|
||
def _filter_site_id(user: CurrentUser, query_site_id: int | None) -> int | None:
|
||
"""读操作门店过滤:门店管理员强制按自身 site_id 过滤。"""
|
||
if "super_admin" in user.roles:
|
||
return query_site_id
|
||
return user.site_id
|
||
|
||
|
||
# =====================================================================
|
||
# 1. 转移日志
|
||
# =====================================================================
|
||
|
||
@router.get("/transfer-log", response_model=TransferLogPage)
|
||
async def list_transfer_logs(
|
||
site_id: int | None = Query(None, description="门店 ID"),
|
||
from_date: date | None = Query(None, description="起始日期"),
|
||
to_date: date | None = Query(None, description="截止日期"),
|
||
assistant_id: int | None = Query(None, description="助教 ID"),
|
||
page: int = Query(1, ge=1),
|
||
page_size: int = Query(20, ge=1, le=100),
|
||
user: CurrentUser = Depends(get_current_user),
|
||
) -> TransferLogPage:
|
||
"""转移日志分页列表。"""
|
||
effective_site_id = _filter_site_id(user, site_id)
|
||
|
||
conn = get_connection()
|
||
try:
|
||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||
conditions = []
|
||
params: list = []
|
||
|
||
if effective_site_id is not None:
|
||
conditions.append("t.site_id = %s")
|
||
params.append(effective_site_id)
|
||
if from_date is not None:
|
||
conditions.append("t.created_at >= %s")
|
||
params.append(from_date)
|
||
if to_date is not None:
|
||
conditions.append("t.created_at < %s::date + interval '1 day'")
|
||
params.append(to_date)
|
||
if assistant_id is not None:
|
||
conditions.append("(t.from_assistant_id = %s OR t.to_assistant_id = %s)")
|
||
params.extend([assistant_id, assistant_id])
|
||
|
||
where_clause = " AND ".join(conditions) if conditions else "1=1"
|
||
|
||
# 总数
|
||
cur.execute(
|
||
f"SELECT count(*) AS cnt FROM biz.coach_task_transfer_log t WHERE {where_clause}",
|
||
params,
|
||
)
|
||
total = cur.fetchone()["cnt"]
|
||
|
||
# 分页数据
|
||
offset = (page - 1) * page_size
|
||
cur.execute(
|
||
f"""
|
||
SELECT t.*, s.site_name
|
||
FROM biz.coach_task_transfer_log t
|
||
LEFT JOIN biz.sites s ON s.site_id = t.site_id
|
||
WHERE {where_clause}
|
||
ORDER BY t.created_at DESC
|
||
LIMIT %s OFFSET %s
|
||
""",
|
||
params + [page_size, offset],
|
||
)
|
||
rows = cur.fetchall()
|
||
|
||
items = [TransferLogItem(**row) for row in rows]
|
||
return TransferLogPage(items=items, total=total)
|
||
except HTTPException:
|
||
raise
|
||
except Exception as exc:
|
||
logger.exception("查询转移日志失败")
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"查询转移日志失败: {str(exc)[:200]}",
|
||
)
|
||
finally:
|
||
conn.close()
|
||
|
||
|
||
@router.get("/transfer-log/{member_id}/history", response_model=list[TransferLogItem])
|
||
async def get_member_transfer_history(
|
||
member_id: int,
|
||
user: CurrentUser = Depends(get_current_user),
|
||
) -> list[TransferLogItem]:
|
||
"""某客户全部转移历史。"""
|
||
conn = get_connection()
|
||
try:
|
||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||
# 门店管理员只能看自己门店的记录
|
||
effective_site_id = _filter_site_id(user, None)
|
||
if effective_site_id is not None:
|
||
cur.execute(
|
||
"""
|
||
SELECT t.*, s.site_name
|
||
FROM biz.coach_task_transfer_log t
|
||
LEFT JOIN biz.sites s ON s.site_id = t.site_id
|
||
WHERE t.member_id = %s AND t.site_id = %s
|
||
ORDER BY t.created_at DESC
|
||
""",
|
||
[member_id, effective_site_id],
|
||
)
|
||
else:
|
||
cur.execute(
|
||
"""
|
||
SELECT t.*, s.site_name
|
||
FROM biz.coach_task_transfer_log t
|
||
LEFT JOIN biz.sites s ON s.site_id = t.site_id
|
||
WHERE t.member_id = %s
|
||
ORDER BY t.created_at DESC
|
||
""",
|
||
[member_id],
|
||
)
|
||
rows = cur.fetchall()
|
||
|
||
return [TransferLogItem(**row) for row in rows]
|
||
except HTTPException:
|
||
raise
|
||
except Exception as exc:
|
||
logger.exception("查询客户转移历史失败: member_id=%s", member_id)
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"查询客户转移历史失败: {str(exc)[:200]}",
|
||
)
|
||
finally:
|
||
conn.close()
|
||
|
||
|
||
# =====================================================================
|
||
# 2. 待审核任务
|
||
# =====================================================================
|
||
|
||
@router.get("/pending-review", response_model=PendingReviewPage)
|
||
async def list_pending_reviews(
|
||
site_id: int | None = Query(None, description="门店 ID"),
|
||
page: int = Query(1, ge=1),
|
||
page_size: int = Query(20, ge=1, le=100),
|
||
user: CurrentUser = Depends(get_current_user),
|
||
) -> PendingReviewPage:
|
||
"""待审核任务列表。"""
|
||
effective_site_id = _filter_site_id(user, site_id)
|
||
|
||
conn = get_connection()
|
||
try:
|
||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||
conditions = ["ct.status = 'pending_review'"]
|
||
params: list = []
|
||
|
||
if effective_site_id is not None:
|
||
conditions.append("ct.site_id = %s")
|
||
params.append(effective_site_id)
|
||
|
||
where_clause = " AND ".join(conditions)
|
||
|
||
# 总数
|
||
cur.execute(
|
||
f"SELECT count(*) AS cnt FROM biz.coach_tasks ct WHERE {where_clause}",
|
||
params,
|
||
)
|
||
total = cur.fetchone()["cnt"]
|
||
|
||
# 分页数据
|
||
offset = (page - 1) * page_size
|
||
cur.execute(
|
||
f"""
|
||
SELECT ct.*, s.site_name
|
||
FROM biz.coach_tasks ct
|
||
LEFT JOIN biz.sites s ON s.site_id = ct.site_id
|
||
WHERE {where_clause}
|
||
ORDER BY ct.created_at DESC
|
||
LIMIT %s OFFSET %s
|
||
""",
|
||
params + [page_size, offset],
|
||
)
|
||
rows = cur.fetchall()
|
||
|
||
items = []
|
||
for row in rows:
|
||
row["task_type_label"] = TASK_TYPE_LABELS.get(row.get("task_type", ""), "")
|
||
items.append(PendingReviewItem(**row))
|
||
|
||
return PendingReviewPage(items=items, total=total)
|
||
except HTTPException:
|
||
raise
|
||
except Exception as exc:
|
||
logger.exception("查询待审核任务失败")
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"查询待审核任务失败: {str(exc)[:200]}",
|
||
)
|
||
finally:
|
||
conn.close()
|
||
|
||
|
||
@router.post("/pending-review/{task_id}/reassign", response_model=ReassignResponse)
|
||
async def reassign_task(
|
||
task_id: int,
|
||
body: ReassignRequest,
|
||
user: CurrentUser = Depends(get_current_user),
|
||
) -> ReassignResponse:
|
||
"""重新分配待审核任务(仅超级管理员)。
|
||
|
||
逻辑:原任务 status → 'transferred',新建 active 任务,写 transfer_log。
|
||
"""
|
||
_require_super_admin(user)
|
||
|
||
conn = get_connection()
|
||
try:
|
||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||
# 查询原任务
|
||
cur.execute(
|
||
"SELECT * FROM biz.coach_tasks WHERE id = %s FOR UPDATE",
|
||
[task_id],
|
||
)
|
||
task = cur.fetchone()
|
||
if task is None:
|
||
raise HTTPException(status_code=404, detail="任务不存在")
|
||
if task["status"] != "pending_review":
|
||
raise HTTPException(status_code=400, detail="任务状态不是待审核,无法重新分配")
|
||
|
||
# 原任务标记为 transferred
|
||
cur.execute(
|
||
"UPDATE biz.coach_tasks SET status = 'transferred', updated_at = now() WHERE id = %s",
|
||
[task_id],
|
||
)
|
||
|
||
# 新建 active 任务
|
||
cur.execute(
|
||
"""
|
||
INSERT INTO biz.coach_tasks
|
||
(site_id, member_id, assistant_id, task_type, priority_score, status, created_at, updated_at)
|
||
VALUES (%s, %s, %s, %s, %s, 'active', now(), now())
|
||
RETURNING id
|
||
""",
|
||
[task["site_id"], task["member_id"], body.to_assistant_id,
|
||
task["task_type"], task.get("priority_score")],
|
||
)
|
||
new_task_id = cur.fetchone()["id"]
|
||
|
||
# 写转移日志
|
||
cur.execute(
|
||
"""
|
||
INSERT INTO biz.coach_task_transfer_log
|
||
(site_id, member_id, from_assistant_id, to_assistant_id,
|
||
transfer_reason, transfer_score, created_at)
|
||
VALUES (%s, %s, %s, %s, %s, %s, now())
|
||
""",
|
||
[task["site_id"], task["member_id"], task["assistant_id"],
|
||
body.to_assistant_id, "manual_reassign", task.get("priority_score")],
|
||
)
|
||
|
||
conn.commit()
|
||
return ReassignResponse(success=True, new_task_id=new_task_id)
|
||
except HTTPException:
|
||
conn.rollback()
|
||
raise
|
||
except Exception as exc:
|
||
conn.rollback()
|
||
logger.exception("重新分配任务失败: task_id=%s", task_id)
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"重新分配任务失败: {str(exc)[:200]}",
|
||
)
|
||
finally:
|
||
conn.close()
|
||
|
||
|
||
@router.post("/pending-review/{task_id}/close", response_model=CloseResponse)
|
||
async def close_task(
|
||
task_id: int,
|
||
body: CloseRequest,
|
||
user: CurrentUser = Depends(get_current_user),
|
||
) -> CloseResponse:
|
||
"""关闭待审核任务(仅超级管理员)。
|
||
|
||
逻辑:任务 status → 'inactive',记录 abandon_reason。
|
||
"""
|
||
_require_super_admin(user)
|
||
|
||
conn = get_connection()
|
||
try:
|
||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||
cur.execute(
|
||
"SELECT id, status FROM biz.coach_tasks WHERE id = %s FOR UPDATE",
|
||
[task_id],
|
||
)
|
||
task = cur.fetchone()
|
||
if task is None:
|
||
raise HTTPException(status_code=404, detail="任务不存在")
|
||
if task["status"] != "pending_review":
|
||
raise HTTPException(status_code=400, detail="任务状态不是待审核,无法关闭")
|
||
|
||
cur.execute(
|
||
"""
|
||
UPDATE biz.coach_tasks
|
||
SET status = 'inactive', abandon_reason = %s, updated_at = now()
|
||
WHERE id = %s
|
||
""",
|
||
[body.reason, task_id],
|
||
)
|
||
|
||
conn.commit()
|
||
return CloseResponse(success=True)
|
||
except HTTPException:
|
||
conn.rollback()
|
||
raise
|
||
except Exception as exc:
|
||
conn.rollback()
|
||
logger.exception("关闭任务失败: task_id=%s", task_id)
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"关闭任务失败: {str(exc)[:200]}",
|
||
)
|
||
finally:
|
||
conn.close()
|
||
|
||
|
||
# =====================================================================
|
||
# 3. 参数管理
|
||
# =====================================================================
|
||
|
||
# 权重参数 key 列表(联合校验用)
|
||
_WEIGHT_KEYS = {"w_rs", "w_ms", "w_ml"}
|
||
|
||
|
||
@router.get("/config", response_model=ConfigParamList)
|
||
async def list_config_params(
|
||
site_id: int | None = Query(None, description="门店 ID(不传则返回全部)"),
|
||
user: CurrentUser = Depends(get_current_user),
|
||
) -> ConfigParamList:
|
||
"""参数列表。"""
|
||
conn = get_connection()
|
||
try:
|
||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||
conditions: list[str] = []
|
||
params: list = []
|
||
|
||
if site_id is not None:
|
||
# 返回指定门店覆盖 + 全局默认
|
||
conditions.append("(p.site_id = %s OR p.site_id IS NULL)")
|
||
params.append(site_id)
|
||
|
||
where_clause = " AND ".join(conditions) if conditions else "1=1"
|
||
|
||
cur.execute(
|
||
f"""
|
||
SELECT p.*, s.site_name
|
||
FROM biz.cfg_task_generator_params p
|
||
LEFT JOIN biz.sites s ON s.site_id = p.site_id
|
||
WHERE {where_clause}
|
||
ORDER BY p.site_id NULLS FIRST, p.param_key
|
||
""",
|
||
params,
|
||
)
|
||
rows = cur.fetchall()
|
||
|
||
return ConfigParamList(params=[ConfigParam(**row) for row in rows])
|
||
except HTTPException:
|
||
raise
|
||
except Exception as exc:
|
||
logger.exception("查询参数配置失败")
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"查询参数配置失败: {str(exc)[:200]}",
|
||
)
|
||
finally:
|
||
conn.close()
|
||
|
||
|
||
@router.put("/config/{param_id}", response_model=ConfigParamResponse)
|
||
async def update_config_param(
|
||
param_id: int,
|
||
body: ConfigParamUpdate,
|
||
user: CurrentUser = Depends(get_current_user),
|
||
) -> ConfigParamResponse:
|
||
"""更新参数值(仅超级管理员)。
|
||
|
||
权重参数(w_rs / w_ms / w_ml)更新后会校验三者之和是否为 1.0。
|
||
"""
|
||
_require_super_admin(user)
|
||
|
||
conn = get_connection()
|
||
try:
|
||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||
# 查询当前参数
|
||
cur.execute(
|
||
"SELECT * FROM biz.cfg_task_generator_params WHERE id = %s FOR UPDATE",
|
||
[param_id],
|
||
)
|
||
param = cur.fetchone()
|
||
if param is None:
|
||
raise HTTPException(status_code=404, detail="参数不存在")
|
||
|
||
# 更新
|
||
cur.execute(
|
||
"""
|
||
UPDATE biz.cfg_task_generator_params
|
||
SET param_value = %s, updated_at = now()
|
||
WHERE id = %s
|
||
""",
|
||
[body.param_value, param_id],
|
||
)
|
||
|
||
# 权重参数联合校验:w_rs + w_ms + w_ml = 1.0
|
||
if param["param_key"] in _WEIGHT_KEYS:
|
||
cur.execute(
|
||
"""
|
||
SELECT param_key, param_value
|
||
FROM biz.cfg_task_generator_params
|
||
WHERE site_id IS NOT DISTINCT FROM %s
|
||
AND param_key = ANY(%s)
|
||
""",
|
||
[param["site_id"], list(_WEIGHT_KEYS)],
|
||
)
|
||
weight_rows = cur.fetchall()
|
||
weight_sum = sum(r["param_value"] for r in weight_rows)
|
||
if abs(weight_sum - 1.0) > 0.001:
|
||
conn.rollback()
|
||
raise HTTPException(
|
||
status_code=400,
|
||
detail=f"权重参数之和必须为 1.0,当前为 {weight_sum:.4f}",
|
||
)
|
||
|
||
conn.commit()
|
||
return ConfigParamResponse(success=True, id=param_id)
|
||
except HTTPException:
|
||
conn.rollback()
|
||
raise
|
||
except Exception as exc:
|
||
conn.rollback()
|
||
logger.exception("更新参数失败: param_id=%s", param_id)
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"更新参数失败: {str(exc)[:200]}",
|
||
)
|
||
finally:
|
||
conn.close()
|
||
|
||
|
||
@router.post("/config", response_model=ConfigParamResponse)
|
||
async def create_config_param(
|
||
body: ConfigParamCreate,
|
||
user: CurrentUser = Depends(get_current_user),
|
||
) -> ConfigParamResponse:
|
||
"""新增门店覆盖参数(仅超级管理员)。"""
|
||
_require_super_admin(user)
|
||
|
||
conn = get_connection()
|
||
try:
|
||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||
# 检查是否已存在同 site_id + param_key 的记录
|
||
cur.execute(
|
||
"""
|
||
SELECT id FROM biz.cfg_task_generator_params
|
||
WHERE site_id = %s AND param_key = %s
|
||
""",
|
||
[body.site_id, body.param_key],
|
||
)
|
||
if cur.fetchone() is not None:
|
||
raise HTTPException(
|
||
status_code=400,
|
||
detail=f"门店 {body.site_id} 已存在参数 {body.param_key} 的覆盖配置",
|
||
)
|
||
|
||
cur.execute(
|
||
"""
|
||
INSERT INTO biz.cfg_task_generator_params
|
||
(site_id, param_key, param_value, updated_at)
|
||
VALUES (%s, %s, %s, now())
|
||
RETURNING id
|
||
""",
|
||
[body.site_id, body.param_key, body.param_value],
|
||
)
|
||
new_id = cur.fetchone()["id"]
|
||
|
||
conn.commit()
|
||
return ConfigParamResponse(success=True, id=new_id)
|
||
except HTTPException:
|
||
conn.rollback()
|
||
raise
|
||
except Exception as exc:
|
||
conn.rollback()
|
||
logger.exception("新增参数失败")
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"新增参数失败: {str(exc)[:200]}",
|
||
)
|
||
finally:
|
||
conn.close()
|
||
|
||
|
||
@router.delete("/clear-all-tasks")
|
||
async def clear_all_tasks(
|
||
user: CurrentUser = Depends(get_current_user),
|
||
) -> dict:
|
||
"""【测试用】清空所有 coach_tasks 及关联数据(仅超级管理员)。
|
||
|
||
用于开发/测试阶段重置任务数据,让 task_generator 重新生成。
|
||
按外键依赖顺序删除:transfer_log → notes → history → tasks。
|
||
"""
|
||
_require_super_admin(user)
|
||
|
||
conn = get_connection()
|
||
try:
|
||
with conn.cursor() as cur:
|
||
# 按外键依赖顺序:先删引用表,再删主表
|
||
cur.execute("DELETE FROM biz.coach_task_transfer_log")
|
||
transfer_count = cur.rowcount
|
||
cur.execute("DELETE FROM biz.notes WHERE task_id IS NOT NULL")
|
||
notes_count = cur.rowcount
|
||
cur.execute("DELETE FROM biz.coach_task_history")
|
||
history_count = cur.rowcount
|
||
# coach_tasks 有自引用 FK,先清 parent_task_id 和 transferred_from
|
||
cur.execute("UPDATE biz.coach_tasks SET parent_task_id = NULL, transferred_from = NULL")
|
||
cur.execute("DELETE FROM biz.coach_tasks")
|
||
task_count = cur.rowcount
|
||
conn.commit()
|
||
return {
|
||
"success": True,
|
||
"message": f"已清空 {task_count} 条任务 + {history_count} 条历史 + {transfer_count} 条转移日志 + {notes_count} 条备注",
|
||
"deleted_tasks": task_count,
|
||
"deleted_history": history_count,
|
||
"deleted_transfers": transfer_count,
|
||
"deleted_notes": notes_count,
|
||
}
|
||
except Exception as exc:
|
||
conn.rollback()
|
||
logger.exception("清空任务失败")
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"清空任务失败: {str(exc)[:200]}",
|
||
)
|
||
finally:
|
||
conn.close()
|
||
|
||
|
||
@router.delete("/config/{param_id}", response_model=ConfigParamResponse)
|
||
async def delete_config_param(
|
||
param_id: int,
|
||
user: CurrentUser = Depends(get_current_user),
|
||
) -> ConfigParamResponse:
|
||
"""删除门店覆盖参数(仅超级管理员)。
|
||
|
||
不允许删除 site_id IS NULL 的全局默认参数。
|
||
"""
|
||
_require_super_admin(user)
|
||
|
||
conn = get_connection()
|
||
try:
|
||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||
cur.execute(
|
||
"SELECT id, site_id FROM biz.cfg_task_generator_params WHERE id = %s",
|
||
[param_id],
|
||
)
|
||
param = cur.fetchone()
|
||
if param is None:
|
||
raise HTTPException(status_code=404, detail="参数不存在")
|
||
if param["site_id"] is None:
|
||
raise HTTPException(status_code=400, detail="不允许删除全局默认参数")
|
||
|
||
cur.execute(
|
||
"DELETE FROM biz.cfg_task_generator_params WHERE id = %s",
|
||
[param_id],
|
||
)
|
||
|
||
conn.commit()
|
||
return ConfigParamResponse(success=True, id=param_id)
|
||
except HTTPException:
|
||
conn.rollback()
|
||
raise
|
||
except Exception as exc:
|
||
conn.rollback()
|
||
logger.exception("删除参数失败: param_id=%s", param_id)
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"删除参数失败: {str(exc)[:200]}",
|
||
)
|
||
finally:
|
||
conn.close()
|
||
|