2026-06-14 16:49:18 +08:00
|
|
|
|
# =============================================================================
|
|
|
|
|
|
# 企微IT智能服务台 — 待办事项 API
|
|
|
|
|
|
# =============================================================================
|
2026-07-11 23:13:10 +08:00
|
|
|
|
# 说明:提供待办事项的查询接口
|
2026-06-14 16:49:18 +08:00
|
|
|
|
# 接口列表:
|
2026-07-11 23:13:10 +08:00
|
|
|
|
# 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。
|
2026-06-14 16:49:18 +08:00
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
2026-07-11 23:13:10 +08:00
|
|
|
|
import logging
|
|
|
|
|
|
from typing import Optional
|
2026-06-14 16:49:18 +08:00
|
|
|
|
|
2026-07-11 23:13:10 +08:00
|
|
|
|
from fastapi import APIRouter, Depends, Query
|
2026-06-14 16:49:18 +08:00
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
|
2026-07-11 23:13:10 +08:00
|
|
|
|
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__)
|
2026-06-14 16:49:18 +08:00
|
|
|
|
|
|
|
|
|
|
# 创建路由器
|
|
|
|
|
|
router = APIRouter(prefix="/todo-items", tags=["待办事项"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --------------------------------------------------------------------------
|
2026-07-11 23:13:10 +08:00
|
|
|
|
# 请求 Schema
|
2026-06-14 16:49:18 +08:00
|
|
|
|
# --------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
class TodoStatusUpdateRequest(BaseModel):
|
2026-07-11 23:13:10 +08:00
|
|
|
|
"""更新待办状态请求 Schema。
|
|
|
|
|
|
|
|
|
|
|
|
⚠️ 仅展示模式:当前版本不支持在服务台内直接操作审批/工单状态,
|
|
|
|
|
|
代办状态更新需跳转到原系统(企微审批 / ITSM)操作。
|
|
|
|
|
|
"""
|
2026-06-14 16:49:18 +08:00
|
|
|
|
status: str = Field(..., description="新状态: pending/processing/resolved")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --------------------------------------------------------------------------
|
2026-07-11 23:13:10 +08:00
|
|
|
|
# 辅助函数
|
2026-06-14 16:49:18 +08:00
|
|
|
|
# --------------------------------------------------------------------------
|
|
|
|
|
|
|
2026-07-11 23:13:10 +08:00
|
|
|
|
def _get_redis():
|
|
|
|
|
|
"""获取 Redis 客户端实例。"""
|
|
|
|
|
|
return settings.create_redis_client()
|
2026-06-14 16:49:18 +08:00
|
|
|
|
|
2026-07-11 23:13:10 +08:00
|
|
|
|
|
|
|
|
|
|
async def _close_redis(redis_client) -> None:
|
|
|
|
|
|
"""安全关闭 Redis 客户端连接。"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
await redis_client.close()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
2026-06-14 16:49:18 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --------------------------------------------------------------------------
|
|
|
|
|
|
# API 接口
|
|
|
|
|
|
# --------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("")
|
|
|
|
|
|
async def list_todo_items(
|
2026-07-11 23:13:10 +08:00
|
|
|
|
type: Optional[str] = Query(None, description="按类型过滤: approval/ticket"),
|
|
|
|
|
|
_force: Optional[int] = Query(None, description="强制刷新(1=跳过缓存)"),
|
|
|
|
|
|
agent: Agent = Depends(get_current_agent),
|
2026-06-14 16:49:18 +08:00
|
|
|
|
):
|
|
|
|
|
|
"""获取当前坐席待办列表。
|
|
|
|
|
|
|
2026-07-11 23:13:10 +08:00
|
|
|
|
聚合企微审批和 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}}
|
2026-06-14 16:49:18 +08:00
|
|
|
|
"""
|
2026-07-11 23:13:10 +08:00
|
|
|
|
redis_client = _get_redis()
|
|
|
|
|
|
try:
|
|
|
|
|
|
aggregator = TodoAggregatorService(redis_client)
|
2026-06-14 16:49:18 +08:00
|
|
|
|
|
2026-07-11 23:13:10 +08:00
|
|
|
|
# 强制刷新:先清除缓存
|
|
|
|
|
|
if _force == 1:
|
|
|
|
|
|
await aggregator._invalidate_cache(agent.user_id)
|
2026-06-14 16:49:18 +08:00
|
|
|
|
|
2026-07-11 23:13:10 +08:00
|
|
|
|
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)
|
2026-06-14 16:49:18 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/{item_id}")
|
2026-07-11 23:13:10 +08:00
|
|
|
|
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)
|
2026-06-14 16:49:18 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.put("/{item_id}/status")
|
2026-07-11 23:13:10 +08:00
|
|
|
|
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: 提示用户跳转到原系统操作
|
|
|
|
|
|
"""
|
2026-06-14 16:49:18 +08:00
|
|
|
|
# 校验状态值
|
|
|
|
|
|
valid_statuses = {"pending", "processing", "resolved"}
|
|
|
|
|
|
if request.status not in valid_statuses:
|
2026-07-11 23:13:10 +08:00
|
|
|
|
raise AppException(
|
|
|
|
|
|
code=1001,
|
|
|
|
|
|
message=f"无效的状态值: {request.status},合法值为: {valid_statuses}",
|
2026-06-14 16:49:18 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-07-11 23:13:10 +08:00
|
|
|
|
# 解析类型前缀,提供对应的跳转提示
|
|
|
|
|
|
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 = "原系统"
|
2026-06-14 16:49:18 +08:00
|
|
|
|
|
2026-07-11 23:13:10 +08:00
|
|
|
|
return success_response(
|
|
|
|
|
|
data={
|
|
|
|
|
|
"item_id": item_id,
|
|
|
|
|
|
"status": request.status,
|
|
|
|
|
|
"mode": "display_only",
|
|
|
|
|
|
"message": f"当前为仅展示模式,请在{system_name}中操作",
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|