Files
wecom_it_smart_desk/backend/app/services/itsm_service.py
T

261 lines
9.0 KiB
Python
Raw Normal View History

# =============================================================================
# 企微IT智能服务台 — ITSM 运维平台待办数据源
# =============================================================================
# 说明:ITSM 一站式运维平台 OpenAPI 数据源实现。
# 通过 ITSMSigner 进行 SHA1 签名认证,调用 ITSM OpenAPI 获取工单待办。
#
# 当前状态:
# - get_todo_list(): ITSM 列表 API 尚未提供,返回空列表 + 日志告警
# - get_todo_detail(): 已实现工单详情查询(调 workitem/detail API
#
# 待 ITSM 列表 API 到位后,只需实现 get_todo_list() 内部逻辑即可。
# =============================================================================
import logging
from typing import Any, Dict, List, Optional
import httpx
import redis.asyncio as aioredis
from app.config import settings
from app.services.todo_source_service import TodoSourceService
from app.utils.itsm_signer import ITSMSigner
logger = logging.getLogger(__name__)
# ITSM OpenAPI 工单详情端点
ITSM_WORKITEM_DETAIL_PATH = "/openapi/v1/process/workitem/detail"
# ITSM API 成功响应码
ITSM_SUCCESS_CODE = 20000
# ITSM HTTP 请求超时
ITSM_TIMEOUT = httpx.Timeout(timeout=30.0, connect=10.0, read=30.0)
def _itsm_priority_to_todo(itsm_priority: Any) -> str:
"""将 ITSM 优先级映射到统一的 todo 优先级(urgent/high/normal)。
Args:
itsm_priority: ITSM 返回的优先级字段(可能是字符串或数字)
Returns:
str: 统一优先级 urgent/high/normal
"""
if itsm_priority is None:
return "normal"
priority_str = str(itsm_priority).strip().lower()
# 紧急 / urgent / 1
if priority_str in ("urgent", "紧急", "1", "critical", "p0", "p1"):
return "urgent"
# 高 / high / 2
if priority_str in ("high", "", "2", "p2"):
return "high"
return "normal"
def _itsm_status_to_todo(itsm_status: Any) -> str:
"""将 ITSM 状态映射到统一的 todo 状态(pending/processing/resolved)。
Args:
itsm_status: ITSM 返回的状态字段
Returns:
str: 统一状态 pending/processing/resolved
"""
if itsm_status is None:
return "pending"
status_str = str(itsm_status).strip().lower()
# 已完成/已关闭类
if status_str in ("resolved", "closed", "done", "完成", "已关闭", "已完成", "resolved", "3"):
return "resolved"
# 处理中
if status_str in ("processing", "in_progress", "处理中", "2"):
return "processing"
# 默认待处理
return "pending"
class ITSMService(TodoSourceService):
"""ITSM 运维平台待办数据源实现。
通过 ITSM OpenAPI 获取当前坐席的代办工单。
签名认证使用 ITSMSignerSHA1 签名),请求头携带 appId/timestamp/sign。
Attributes:
agent_userid: 当前坐席的企微 userid
redis: Redis 异步客户端(预留,后续 SSO 认证可能需要)
base_url: ITSM API 基址
app_id: ITSM OpenAPI app_id
app_secret: ITSM OpenAPI app_secret
"""
def __init__(self, agent_userid: str, redis: aioredis.Redis):
"""初始化 ITSM 数据源服务。
从 settings 读取 ITSM 配置。如果 itsm_app_id 为空,
所有方法将返回空结果并记日志告警。
Args:
agent_userid: 当前坐席的企微 userid
redis: Redis 异步客户端实例
"""
self.agent_userid = agent_userid
self.redis = redis
self.base_url = settings.itsm_base_url.rstrip("/")
self.app_id = settings.itsm_app_id
self.app_secret = settings.itsm_app_secret
# ------------------------------------------------------------------
# 公开接口
# ------------------------------------------------------------------
async def get_todo_list(self) -> List[Dict[str, Any]]:
"""获取 ITSM 代办工单列表。
⚠️ ITSM 列表 API 尚未提供,当前返回空列表 + 日志告警。
待 API 到位后实现列表查询逻辑。
Returns:
List[Dict[str, Any]]: 空列表(API 待实现)
"""
if not self.app_id:
logger.warning("ITSM app_id 未配置,代办列表返回空")
return []
logger.warning(
"ITSM 代办列表 API 尚未实现,返回空列表。"
"待 ITSM 平台方提供列表 API 端点后补充实现。"
)
return []
async def get_todo_detail(self, item_id: str) -> Optional[Dict[str, Any]]:
"""获取 ITSM 工单详情。
通过 ITSM OpenAPI workitem/detail 端点获取工单详情,
并映射为统一 TodoItemData 格式。
Args:
item_id: 工单的 process_instance_id(不含 "ticket:" 前缀)
Returns:
Optional[Dict[str, Any]]: TodoItemData 格式的工单详情
"""
if not self.app_id:
logger.warning("ITSM app_id 未配置,无法查询工单详情")
return None
try:
# 调用 ITSM workitem detail API
detail = await self._get_workitem_detail(item_id, self.agent_userid)
if not detail:
return None
return self._map_to_todo_item(detail)
except Exception as e:
logger.error(
f"获取 ITSM 工单详情失败: item_id={item_id}, error={e}",
exc_info=True,
)
return None
# ------------------------------------------------------------------
# 私有方法
# ------------------------------------------------------------------
async def _do_post(self, url: str, body: Dict[str, Any]) -> Optional[dict]:
"""发送带签名的 POST 请求到 ITSM API。
使用 ITSMSigner 生成签名请求头,发送 JSON POST 请求。
解析 ITSM 标准响应格式 {code: 20000, data: {...}, message: "..."}。
Args:
url: 完整的 ITSM API URL
body: 请求体(业务数据)
Returns:
Optional[dict]: ITSM 响应中的 data 字段,失败返回 None
"""
headers = ITSMSigner.get_headers(self.app_id, self.app_secret, body)
async with httpx.AsyncClient(timeout=ITSM_TIMEOUT) as client:
response = await client.post(url, json=body, headers=headers)
result = response.json()
code = result.get("code")
if code != ITSM_SUCCESS_CODE:
logger.error(
f"ITSM API 调用失败: url={url}, code={code}, "
f"message={result.get('message', '')}"
)
return None
return result.get("data")
async def _get_workitem_detail(
self, process_instance_id: str, executor: str
) -> Optional[dict]:
"""调用 ITSM 工单详情 API。
POST {base_url}/openapi/v1/process/workitem/detail
Args:
process_instance_id: 工单流程实例 ID
executor: 当前执行人(坐席 userid
Returns:
Optional[dict]: ITSM 返回的工单详情数据
"""
url = f"{self.base_url}{ITSM_WORKITEM_DETAIL_PATH}"
body = {
"process_instance_id": process_instance_id,
"executor": executor,
}
return await self._do_post(url, body)
def _map_to_todo_item(self, detail: dict) -> Dict[str, Any]:
"""将 ITSM 工单详情映射为统一 TodoItemData 格式。
映射规则参考系统设计文档 §8.2:
- id: "ticket:{process_instance_id}"
- type: "ticket"
- title: 工单标题
- priority: ITSM 优先级映射到 urgent/high/normal
- status: ITSM 状态映射到 pending/processing/resolved
Args:
detail: ITSM API 返回的工单详情
Returns:
Dict[str, Any]: TodoItemData 格式的待办事项
"""
process_instance_id = detail.get("process_instance_id", "")
title = detail.get("title", "")
itsm_priority = detail.get("priority", "normal")
itsm_status = detail.get("status", "pending")
creator = detail.get("creator", "")
executor = detail.get("executor", "")
created_at = detail.get("created_at", "")
updated_at = detail.get("updated_at", "")
return {
"id": f"ticket:{process_instance_id}",
"type": "ticket",
"title": title or "ITSM 工单",
"priority": _itsm_priority_to_todo(itsm_priority),
"description": {
"process_instance_id": process_instance_id,
"executor": executor,
"status": itsm_status,
"creator": creator,
"itsm_priority": itsm_priority,
"title": title,
},
"status": _itsm_status_to_todo(itsm_status),
"assigned_agent_id": self.agent_userid,
"corp_id": "",
"created_at": created_at,
"updated_at": updated_at,
}