Files
wecom_it_smart_desk/backend/app/api/todo_items.py
T

206 lines
7.1 KiB
Python
Raw Normal View History

# =============================================================================
# 企微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}中操作",
}
)