feat: 每日热点问题共享库(定时生成+管理员手动更新+点击埋点)
CI / 后端测试 (push) Has been cancelled
CI / 前端构建 (push) Has been cancelled

数据库:
- daily_topics表:每天每场景一条热点问题记录
- sample_clicks表:记录用户点击示例问题行为

后端:
- DailyTopicStore: CRUD + 埋点 + 点击统计
- generate_daily_topics: AI生成4场景各25-30个问题
- 定时任务:每天凌晨3点自动生成(后台线程轮询)
- GET /topics/daily: 获取当天热点(公开)
- POST /topics/click: 点击埋点
- GET /admin/daily-topics: 管理端查看
- POST /admin/daily-topics: 管理员手动更新
- POST /admin/daily-topics/generate: 手动触发AI生成
- GET /admin/sample-clicks: 点击统计

前端:
- ChatPanel挂载时取当天热点,无则用硬编码兜底
- 点击示例问题时记录埋点
- AdminDailyTopics管理页面:查看/编辑/手动生成
- 路由 /admin/daily-topics + 导航配置

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
selfrelease
2026-08-31 17:04:33 +08:00
co-authored by Devin
parent 35cc6e54ea
commit a7ef530841
9 changed files with 638 additions and 3 deletions
+109
View File
@@ -18,6 +18,7 @@ from bossai import (
ExpertStore, ConsultationStore, RatingStore,
DocumentGenerator, RegulationUpdateManager, FactFinder,
ChatStore, redact,
DailyTopicStore, generate_daily_topics,
)
# ===== 日志脱敏 =====
@@ -90,12 +91,50 @@ consultation_store = ConsultationStore()
rating_store = RatingStore()
chat_store = ChatStore()
metrics = Metrics()
daily_topic_store = DailyTopicStore()
try:
kimi = KimiClient()
except RuntimeError:
kimi = None
# ===== 每日热点问题定时任务(每天凌晨3点) =====
def _daily_topics_scheduler():
"""后台线程:每天凌晨3点自动生成热点问题。
使用轮询方式实现,避免引入 APScheduler 依赖。
AI 不可用时跳过,保留已有数据。
"""
import threading
from datetime import datetime, time as dt_time
def run():
while True:
now = datetime.now()
# 计算到下一个凌晨3点的秒数
target = datetime.combine(now.date(), dt_time(3, 0))
if now >= target:
# 今天3点已过,等明天
from datetime import timedelta
target = target + timedelta(days=1)
wait_seconds = (target - now).total_seconds()
threading.Event().wait(timeout=wait_seconds)
# 执行生成
if kimi:
try:
logging.info("开始生成每日热点问题...")
results = generate_daily_topics(kimi, daily_topic_store)
logging.info(f"每日热点问题生成完成: {results}")
except Exception as e:
logging.warning(f"每日热点问题生成失败: {type(e).__name__}: {e}")
thread = threading.Thread(target=run, daemon=True, name="daily-topics-scheduler")
thread.start()
_daily_topics_scheduler()
# ===== 请求计时 + 日志脱敏中间件 =====
@app.middleware("http")
async def metrics_middleware(request: Request, call_next):
@@ -2959,3 +2998,73 @@ def update_execution_result(matter_id: int, body: dict,
return {"updated": True}
finally:
conn.close()
# ===== 每日热点问题库 =====
@app.get("/topics/daily")
def get_daily_topics(scene: str):
"""获取当天热点问题(公开接口,无需登录)。
无当天数据时返回空数组,前端用硬编码兜底。
"""
if scene not in DailyTopicStore.SCENES:
raise HTTPException(400, f"场景应为 {DailyTopicStore.SCENES} 之一")
return {"scene": scene, "questions": daily_topic_store.get_today(scene)}
@app.post("/topics/click")
def record_sample_click(body: dict, authorization: str | None = Header(default=None)):
"""记录用户点击示例问题(埋点)。"""
scene = body.get("scene", "")
question = body.get("question", "")
if scene not in DailyTopicStore.SCENES or not question:
raise HTTPException(400, "缺少 scene 或 question")
phone = get_current_phone(authorization)
daily_topic_store.record_click(scene, question, phone)
return {"recorded": True}
# ===== 管理端:热点问题管理 =====
@app.get("/admin/daily-topics")
def admin_get_daily_topics(date: str | None = None):
"""管理端:获取指定日期(默认今天)所有场景的热点问题。"""
from datetime import date as date_cls
target = date or str(date_cls.today())
return {"results": daily_topic_store.get_by_date(target)}
class DailyTopicUpdateRequest(BaseModel):
"""管理员手动更新热点问题。"""
scene: str
questions: list[str]
date: str | None = None # 指定日期,默认今天
@app.post("/admin/daily-topics")
def admin_update_daily_topics(req: DailyTopicUpdateRequest):
"""管理端:手动更新某场景的热点问题。"""
if req.scene not in DailyTopicStore.SCENES:
raise HTTPException(400, f"场景应为 {DailyTopicStore.SCENES} 之一")
if not req.questions:
raise HTTPException(400, "问题列表不能为空")
result = daily_topic_store.upsert(
req.scene, req.questions, source="admin", date_str=req.date,
)
return {"updated": True, "topic": result}
@app.post("/admin/daily-topics/generate")
def admin_generate_daily_topics():
"""管理端:手动触发 AI 生成当天热点问题(4个场景)。"""
if not kimi:
raise HTTPException(503, "AI 服务不可用,请检查 KIMI_API_KEY 配置")
results = generate_daily_topics(kimi, daily_topic_store)
return {"generated": True, "results": results}
@app.get("/admin/sample-clicks")
def admin_sample_click_stats(days: int = 14):
"""管理端:获取示例问题点击统计。"""
return {"results": daily_topic_store.click_stats(days)}