feat: 2026-07-11 全量更新 - 代办集成+会议室预定+知识迭代修复+UI统一+Bug修复

== 已部署上线 (9项) ==
- 代办事项真实数据源集成 (企微审批API 8bug修复链)
- H5/坐席端 Logo样式统一+绿色背景
- 视频引导页修复 (localStorage key v2)
- 坐席端 v9 Vue版本修复 (ElMessage._context)
- 截图按钮 v10 修复 (getDisplayMedia user gesture)
- 扫码样式恢复+H5扫码登录跳转修复
- H5截图快捷键提示

== 代码完成待部署 (3项) ==
- 知识迭代3Bug修复 (#8 POST端点/#7 MERGE幂等/#6 过期检查)
- 会议室预定-小鱼易联终端 (40文件, 40/40测试通过)
- IT资产升级审批推送 (asset_service.py)

== 需求文档 (2项) ==
- 坐席端AI辅助消息框-PRD (4项新功能确认)
- 坐席端布局优化建议 v2.0 (7天计划)

== 新增文档 ==
- 日报-2026-07-11.md
- 知识迭代Bug修复报告-20260711.md
- 会议室预定-部署指南.md
- CHANGELOG.md 更新

== 测试 ==
- test_todo_integration.py: 40/40
- test_meetingroom.py: 40/40
- test_bugfix_ki_suggestions.py: 21/21
This commit is contained in:
Simon
2026-07-11 23:13:10 +08:00
parent 3d152fc8eb
commit bea288e414
928 changed files with 85169 additions and 54205 deletions
@@ -0,0 +1,129 @@
# =============================================================================
# 企微IT智能服务台 — 复杂场景重构 暂停超时清理定时任务
# =============================================================================
# 说明:后台定时任务,扫描 paused 状态且超过 24 小时未恢复的会话,
# 自动标记为 closedclosed_by = "system(timeout)"),
# 并清理 Redis 恢复点与暂停会话集合,推送超时关闭 WS 事件。
#
# 调用方式:
# 1. FastAPI lifespan 中 asyncio.create_task(TimeoutCleaner(...).run_scheduled())
# 2. 或外部调度器定期调用 run_once()
# =============================================================================
from __future__ import annotations
import asyncio
import logging
from datetime import datetime, timedelta, timezone
from typing import Any
from sqlalchemy import select
from app.constants import (
PAUSE_TIMEOUT_HOURS,
REDIS_KEY_PAUSED_SESSIONS,
REDIS_KEY_RESUME_POINT,
)
from app.models.automation import AutoSession
from app.services.automation.progress_publisher import publish_timeout_closed
logger = logging.getLogger(__name__)
class TimeoutCleaner:
"""暂停超时清理器。
扫描 paused 状态的会话,超过 PAUSE_TIMEOUT_HOURS(默认 24 小时)
未恢复的会话自动关闭,并推送 WS 通知。
"""
def __init__(self, db_factory: Any, redis: Any = None):
"""初始化。
Args:
db_factory: 异步 DB 会话工厂(如 app.database._get_session_factory()
redis: Redis 客户端(可选,用于清理恢复点)
"""
self.db_factory = db_factory
self.redis = redis
async def run_once(self) -> int:
"""执行一次扫描,返回关闭的会话数。
Returns:
int: 本次扫描关闭的会话数量
"""
closed_count = 0
async with self.db_factory() as db:
# 查询超时的 paused 会话(paused_at < cutoff 隐含 NOT NULL
cutoff = datetime.now(timezone.utc) - timedelta(hours=PAUSE_TIMEOUT_HOURS)
stmt = select(AutoSession).where(
AutoSession.status == "paused",
AutoSession.paused_at < cutoff,
)
sessions = list((await db.execute(stmt)).scalars().all())
if not sessions:
return 0
for session in sessions:
try:
# 标记关闭
session.status = "closed"
session.closed_by = "system(timeout)"
closed_at = datetime.now(timezone.utc)
# 清理 Redis 恢复点
if self.redis:
await self._cleanup_redis(session.id, session.employee_id)
await db.flush()
closed_count += 1
# 推送 WS 超时关闭事件
await publish_timeout_closed(
session_id=session.id,
closed_at=closed_at.isoformat(),
reason=f"暂停超过 {PAUSE_TIMEOUT_HOURS} 小时未恢复,已自动关闭",
)
logger.info(
f"超时关闭会话: session={session.id} "
f"paused_at={session.paused_at} closed_at={closed_at}"
)
except Exception as e: # noqa: BLE001
logger.error(f"超时关闭会话失败 session={session.id}: {e}")
await db.commit()
if closed_count > 0:
logger.info(f"超时清理完成: 共关闭 {closed_count} 个暂停会话")
return closed_count
async def run_scheduled(self, interval: int = 3600) -> None:
"""定时扫描(每小时一次)。
Args:
interval: 扫描间隔(秒),默认 3600 = 1 小时
"""
logger.info(f"启动暂停超时清理定时任务,间隔 {interval}")
while True:
try:
await self.run_once()
except Exception as e: # noqa: BLE001
logger.error(f"超时清理定时任务异常: {e}")
await asyncio.sleep(interval)
async def _cleanup_redis(self, session_id: str, employee_id: str) -> None:
"""清理 Redis 中的恢复点和暂停会话集合。"""
if not self.redis:
return
try:
# 删除恢复点
resume_key = REDIS_KEY_RESUME_POINT.format(session_id=session_id)
await self.redis.delete(resume_key)
# 从暂停会话集合中移除
paused_key = REDIS_KEY_PAUSED_SESSIONS.format(employee_id=employee_id)
await self.redis.srem(paused_key, session_id)
except Exception as e: # noqa: BLE001
logger.warning(f"Redis 清理失败 session={session_id}: {e}")