# ============================================================================= # 企微IT智能服务台 — 待办事项 API # ============================================================================= # 说明:提供待办事项的查询接口 # 接口列表: # GET /api/todo-items — 获取当前坐席待办列表(聚合企微审批 + ITSM 工单) # GET /api/todo-items/{id} — 获取待办详情(按 ID 前缀路由到对应数据源) # PUT /api/todo-items/{id}/status — 更新待办状态(仅展示模式,不支持实际操作) # # 数据来源: # - 企微审批(getapprovaldata + getapprovaldetail) # - ITSM 运维平台(OpenAPI workitem/detail) # 通过 TodoAggregatorService 聚合,Redis 缓存 TTL 45s。 # ============================================================================= import logging from typing import Optional from fastapi import APIRouter, Depends, Query from pydantic import BaseModel, Field from app.api.agents import get_current_agent from app.config import settings from app.models.agent import Agent from app.services.todo_aggregator_service import TodoAggregatorService from app.utils.response import AppException, success_response logger = logging.getLogger(__name__) # 创建路由器 router = APIRouter(prefix="/todo-items", tags=["待办事项"]) # -------------------------------------------------------------------------- # 请求 Schema # -------------------------------------------------------------------------- class TodoStatusUpdateRequest(BaseModel): """更新待办状态请求 Schema。 ⚠️ 仅展示模式:当前版本不支持在服务台内直接操作审批/工单状态, 代办状态更新需跳转到原系统(企微审批 / ITSM)操作。 """ status: str = Field(..., description="新状态: pending/processing/resolved") # -------------------------------------------------------------------------- # 辅助函数 # -------------------------------------------------------------------------- def _get_redis(): """获取 Redis 客户端实例。""" return settings.create_redis_client() async def _close_redis(redis_client) -> None: """安全关闭 Redis 客户端连接。""" try: await redis_client.close() except Exception: pass # -------------------------------------------------------------------------- # API 接口 # -------------------------------------------------------------------------- @router.get("") async def list_todo_items( type: Optional[str] = Query(None, description="按类型过滤: approval/ticket"), _force: Optional[int] = Query(None, description="强制刷新(1=跳过缓存)"), agent: Agent = Depends(get_current_agent), ): """获取当前坐席待办列表。 聚合企微审批和 ITSM 工单两个数据源,返回统一格式的待办列表。 支持 type 参数按类型过滤,默认返回全部。 支持 _force=1 参数强制跳过缓存重新查询。 流程: 1. 从认证依赖获取当前坐席 (agent.user_id) 2. 如 _force=1,先清除该坐席的所有待办缓存 3. 调用 TodoAggregatorService.get_todo_list() - 先查 Redis 缓存(TTL 45s),命中直接返回 - 未命中则并行查询企微审批 + ITSM 工单 - 按优先级排序 urgent → high → normal 4. 返回 {items, total, cached} 格式 Args: type: 类型过滤(approval/ticket),不传则返回全部 _force: 强制刷新(1=跳过缓存,重新查询外部 API) agent: 当前坐席(通过认证依赖注入) Returns: Dict: 统一响应格式 {code:0, data:{items, total, cached}} """ redis_client = _get_redis() try: aggregator = TodoAggregatorService(redis_client) # 强制刷新:先清除缓存 if _force == 1: await aggregator._invalidate_cache(agent.user_id) result = await aggregator.get_todo_list( agent_userid=agent.user_id, todo_type=type, ) return success_response(data=result) except Exception as e: logger.error(f"获取待办列表失败: agent={agent.user_id}, error={e}", exc_info=True) return success_response(data={"items": [], "total": 0, "cached": False}) finally: await _close_redis(redis_client) @router.get("/{item_id}") async def get_todo_item( item_id: str, agent: Agent = Depends(get_current_agent), ): """获取待办事项详情。 从 item_id 解析类型前缀(approval:{sp_no} / ticket:{id}), 路由到对应数据源 Service 查询详情。 ID 格式约定: - 审批:approval:{sp_no}(如 approval:202607110001) - 工单:ticket:{process_instance_id}(如 ticket:12345) Args: item_id: 待办 ID(格式:{type}:{原始ID}) agent: 当前坐席(通过认证依赖注入) Returns: Dict: 统一响应格式 {code:0, data:{item: TodoItemData}} """ redis_client = _get_redis() try: aggregator = TodoAggregatorService(redis_client) item = await aggregator.get_todo_detail( agent_userid=agent.user_id, item_id=item_id, ) if item is None: raise AppException(code=1003, message=f"待办事项 {item_id} 不存在") return success_response(data=item) except AppException: raise except Exception as e: logger.error(f"获取待办详情失败: item_id={item_id}, error={e}", exc_info=True) raise AppException(code=1005, message=f"获取待办详情失败: {e}") finally: await _close_redis(redis_client) @router.put("/{item_id}/status") async def update_todo_item_status( item_id: str, request: TodoStatusUpdateRequest, agent: Agent = Depends(get_current_agent), ): """更新待办事项状态(仅展示模式)。 ⚠️ 当前版本为"仅展示模式":不支持在服务台内直接操作审批/工单状态。 代办状态更新需跳转到原系统操作: - 审批 → 在企微审批中操作 - 工单 → 在 ITSM 中操作 保留此接口用于前端兼容,实际不修改任何状态。 Args: item_id: 待办 ID request: 状态更新请求 agent: 当前坐席 Returns: Dict: 提示用户跳转到原系统操作 """ # 校验状态值 valid_statuses = {"pending", "processing", "resolved"} if request.status not in valid_statuses: raise AppException( code=1001, message=f"无效的状态值: {request.status},合法值为: {valid_statuses}", ) # 解析类型前缀,提供对应的跳转提示 todo_type = item_id.split(":", 1)[0] if ":" in item_id else "unknown" if todo_type == "approval": system_name = "企微审批" elif todo_type == "ticket": system_name = "ITSM 运维平台" else: system_name = "原系统" return success_response( data={ "item_id": item_id, "status": request.status, "mode": "display_only", "message": f"当前为仅展示模式,请在{system_name}中操作", } )