Files
Neo-ZQYY/apps/backend/app/routers/member_retention_clue.py
Neo 6f8f12314f feat: 累积功能变更 — 聊天集成、租户管理、小程序更新、ETL 增强、迁移脚本
包含多个会话的累积代码变更:
- 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>
2026-04-06 00:03:48 +08:00

104 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
维客线索路由。
- POST /api/retention-clue — 提交维客线索UPSERT
- GET /api/retention-clue/{member_id} — 查询某会员的全部维客线索
- DELETE /api/retention-clue/{clue_id} — 删除单条线索
"""
import logging
from fastapi import APIRouter, HTTPException, status
from app.database import get_connection
from app.schemas.member_retention_clue import RetentionClueSubmit, RetentionClueOut
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api", tags=["维客线索"])
@router.post("/retention-clue")
async def submit_retention_clue(body: RetentionClueSubmit):
"""
提交维客线索INSERT
同一会员可有多条不同大类的线索。
"""
sql = """
INSERT INTO member_retention_clue
(member_id, category, summary, detail,
recorded_by_assistant_id, recorded_by_name, site_id, source)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
RETURNING id
"""
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(sql, (
body.member_id,
body.category.value,
body.summary,
body.detail,
body.recorded_by_assistant_id,
body.recorded_by_name,
body.site_id,
body.source.value,
))
row = cur.fetchone()
conn.commit()
return {"status": "ok", "id": row[0] if row else None}
except Exception:
conn.rollback()
logger.exception("维客线索写入失败: member_id=%s", body.member_id)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="线索提交失败,请稍后重试",
)
finally:
conn.close()
@router.get("/retention-clue/{member_id}", response_model=list[RetentionClueOut])
async def get_retention_clues(member_id: int, site_id: int):
"""查询某会员的全部维客线索,按录入时间倒序。"""
sql = """
SELECT id, member_id, category, summary, detail,
recorded_by_assistant_id, recorded_by_name, recorded_at, site_id, source
FROM member_retention_clue
WHERE member_id = %s AND site_id = %s
AND is_hidden = false
ORDER BY recorded_at DESC
"""
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(sql, (member_id, site_id))
rows = cur.fetchall()
cols = [d[0] for d in cur.description]
return [dict(zip(cols, r)) for r in rows]
finally:
conn.close()
@router.delete("/retention-clue/{clue_id}")
async def delete_retention_clue(clue_id: int):
"""删除单条维客线索。"""
sql = "DELETE FROM member_retention_clue WHERE id = %s"
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(sql, (clue_id,))
if cur.rowcount == 0:
raise HTTPException(status_code=404, detail="线索不存在")
conn.commit()
return {"status": "ok"}
except HTTPException:
raise
except Exception:
conn.rollback()
logger.exception("维客线索删除失败: id=%s", clue_id)
raise HTTPException(status_code=500, detail="删除失败")
finally:
conn.close()