批次4死代码大扫除: 删triage三件套(H5零挂载)+ /approval/keywords端点 + scheduler.py孤儿模块 + 3个.bak文件 + get_reply_stream(~96行)

This commit is contained in:
Simon
2026-07-18 10:22:07 +08:00
parent 6a5f01ff90
commit b997a683ce
11 changed files with 28 additions and 3238 deletions
+3 -15
View File
@@ -983,18 +983,6 @@ async def urge_approval(
return success_response(data=result)
@router.get("/approval/keywords")
async def get_approval_keywords():
"""获取所有审批关键词(用于前端关键词检测)"""
keywords = []
for template in APPROVAL_TEMPLATES.values():
for kw in template["keywords"]:
keywords.append({
"keyword": kw,
"template_id": template["id"],
"template_name": template["name"],
"type": template["type"],
})
return success_response(data=keywords)
# v4.0 批次4GET /approval/keywords 端点已删除
# (前端 getApprovalKeywords 调用已随 v3.0 ApprovalCardModal 重构移除,
# 仅剩 .bak 备份文件引用;关键词匹配现由 ApprovalMatcher 后端统一处理)
File diff suppressed because it is too large Load Diff
+3 -96
View File
@@ -231,102 +231,9 @@ class AIService:
# --------------------------------------------------------------------------
# 流式调用:SSE 流式返回(供 WebSocket 推送给前端)
# --------------------------------------------------------------------------
async def get_reply_stream(
self,
message: str,
conversation_id: Optional[str] = None,
user_id: Optional[str] = None,
) -> AsyncGenerator[Dict[str, Any], None]:
"""调用 Dify API 获取流式 AI 回复(SSE),逐块 yield 给调用方。
Yields:
Dict: {"delta": str, "finished": bool, "conversation_id": str, "hit": bool|None}
- 流式中间块:{"delta": 增量, "finished": False, "hit": None}
- 终态块:{"delta": "", "finished": True, "hit": 命中判断}
实现:
- stream=True 走 SSE,解析 data: {...} 行,逐块 yield delta
- 流结束后用完整内容整体判断 hit_check_knowledge_hit
容错:若 Dify 不支持流式 / 超时 / 非 SSE 格式,catch 后 fallback 到
get_reply 非流式,yield 一次完整内容(前端退化为"整段到达"
功能不破,仅无逐字动画)。
"""
payload = {
"model": "Chat",
"messages": [{"role": "user", "content": message}],
"stream": True,
"temperature": 0.1,
}
if conversation_id:
payload["conversation_id"] = conversation_id
if user_id:
payload["user"] = user_id
try:
client = await self._get_client()
full_parts: list = []
dify_conv_id = conversation_id or ""
async with client.stream("POST", self.api_url, json=payload) as response:
response.raise_for_status()
async for line in response.aiter_lines():
if not line:
continue
line = line.strip()
if not line.startswith("data:"):
continue
data = line[5:].strip()
if data == "[DONE]":
break
try:
chunk = json.loads(data)
except json.JSONDecodeError:
continue
# OpenAI / Dify SSE 格式:choices[0].delta.content
try:
delta = chunk["choices"][0]["delta"].get("content", "")
except (KeyError, IndexError, TypeError):
delta = ""
if delta:
full_parts.append(delta)
yield {
"delta": delta,
"finished": False,
"conversation_id": dify_conv_id,
"hit": None,
}
# Dify 可能在流式块里给出 conversation_id
cid = chunk.get("conversation_id")
if cid:
dify_conv_id = cid
# 流结束:用完整内容判断命中
full_content = "".join(full_parts)
hit = self._check_knowledge_hit(full_content) if full_content else False
yield {
"delta": "",
"finished": True,
"conversation_id": dify_conv_id,
"hit": hit,
}
except Exception as e:
# 流式不可用(dify2openai 不支持 / 超时 / 非 SSE),回退非流式
logger.warning(f"Dify 流式失败,回退非流式: {e}")
try:
result = await self.get_reply(message, conversation_id, user_id)
yield {
"delta": result["content"],
"finished": True,
"conversation_id": result["conversation_id"],
"hit": result["hit"],
}
except Exception as e2:
logger.error(f"Dify 流式与非流式均失败: {e2}")
yield {
"delta": "⚠️ AI 服务异常,请输入「IT」转人工或稍后重试。",
"finished": True,
"conversation_id": conversation_id or "",
"hit": False,
}
# v4.0 批次4get_reply_stream 已删除(~96 行)
# v2.0 起 AI 回复改为 blocking + JSON 结构化(get_structured_reply),
# 流式 SSE 路径零调用,属死代码。
# --------------------------------------------------------------------------
# 结构化调用:blocking 模式,返回解析后的 JSON {text, action, options}
-149
View File
@@ -1,149 +0,0 @@
# -*- coding: utf-8 -*-
"""
资产推荐定时任务
功能:
1. 每日运维提醒推送(L2
2. 画像缓存预热
3. 新员工欢迎推送(L3
"""
import asyncio
import logging
from datetime import datetime
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger
logger = logging.getLogger(__name__)
# 定时任务调度器实例
# v4.0 P1-7 修复:AsyncIOSScheduler → AsyncIOScheduler(原拼写错误,import 即 NameError
# 注意:本模块当前无人 importmain.py 使用自己的 _scheduler),批次 4 候选删除
scheduler = AsyncIOScheduler()
def setup_scheduled_tasks():
"""配置定时任务"""
# 每日 9:00 运维提醒
scheduler.add_job(
daily_maintenance_push,
trigger=CronTrigger(hour=9, minute=0),
id='daily_maintenance_push',
name='每日运维提醒推送',
replace_existing=True
)
# 每小时画像缓存刷新
scheduler.add_job(
hourly_profile_sync,
trigger=CronTrigger(minute=0),
id='hourly_profile_sync',
name='每小时员工画像同步',
replace_existing=True
)
# 每天 8:55 检查新员工
scheduler.add_job(
check_new_employees,
trigger=CronTrigger(hour=8, minute=55),
id='check_new_employees',
name='新员工欢迎检查',
replace_existing=True
)
logger.info(f"[Scheduler] 已配置 {len(scheduler.get_jobs())} 个定时任务")
async def daily_maintenance_push():
"""每日运维提醒推送"""
logger.info("[Scheduler] 开始执行每日运维提醒推送")
from app.services.asset_recommend_service import get_asset_recommend_service
from app.services.employee_profile_service import get_employee_profile_service
asset_service = get_asset_recommend_service()
profile_service = get_employee_profile_service()
# 获取全部员工(分页)
# TODO: 实现分页获取员工列表
# page = 1
# while True:
# employees = await get_employees_paginated(page, 100)
# if not employees:
# break
#
# for emp in employees:
# try:
# profile = await profile_service.get_profile(emp.id)
# profile_dict = {
# 'huorong_version': profile.huorong_version,
# 'huorong_virusdb_date': profile.huorong_virusdb_date,
# 'huorong_offline_days': profile.huorong_offline_days,
# 'unionsoft_patches_missing': profile.unionsoft_patches_missing,
# 'unionsoft_violations': profile.unionsoft_violations,
# }
# l2_recs = asset_service.match_profile_triggers(profile_dict)
#
# if l2_recs:
# ws_msg = asset_service.build_ws_message(l2_recs)
# from app.services.ws_manager import manager as ws_manager
# await ws_manager.broadcast_to_employees([emp.id], ws_msg)
#
# except Exception as e:
# logger.error(f"[Scheduler] 推送失败: {emp.id}, {e}")
#
# page += 1
logger.info("[Scheduler] 每日运维提醒推送完成 (TODO: 实现员工列表获取)")
async def hourly_profile_sync():
"""每小时同步员工画像缓存"""
logger.info("[Scheduler] 开始同步员工画像缓存")
try:
profile_service = get_employee_profile_service()
deleted = await profile_service.clear_expired_cache()
logger.info(f"[Scheduler] 画像缓存同步完成,清理 {deleted}")
except Exception as e:
logger.error(f"[Scheduler] 画像缓存同步失败: {e}")
async def check_new_employees():
"""检查新员工并发送欢迎"""
logger.info("[Scheduler] 检查新员工")
# TODO: 实现新员工检测逻辑
# 获取过去 24 小时入职的员工
# new_employees = await get_new_employees(days=1)
#
# for emp in new_employees:
# asset_service = get_asset_recommend_service()
# role_recs = asset_service.get_by_role('new_employee')
#
# if role_recs:
# ws_msg = asset_service.build_ws_message(role_recs)
# from app.services.ws_manager import manager as ws_manager
# await ws_manager.broadcast_to_employees([emp.id], ws_msg)
logger.info("[Scheduler] 新员工检查完成 (TODO: 实现)")
def start_scheduler():
"""启动定时任务调度器"""
if not scheduler.running:
setup_scheduled_tasks()
scheduler.start()
logger.info("定时任务调度器已启动")
def stop_scheduler():
"""停止定时任务调度器"""
if scheduler.running:
scheduler.shutdown()
logger.info("定时任务调度器已停止")