docs: 移动蓝绿部署指南到 troubleshooting 目录
This commit is contained in:
+233
-40
@@ -1,30 +1,41 @@
|
||||
# =============================================================================
|
||||
# IT智能服务台 — 审批流程 API
|
||||
# =============================================================================
|
||||
# 说明:提供审批模板管理和跳转链接生成
|
||||
# - 模板124(资源申请):跳转审批
|
||||
# - 模板122(设备申请):API提交
|
||||
# 说明:提供审批模板管理和API提交功能
|
||||
# - 模板详情获取
|
||||
# - API提交审批申请
|
||||
# - 审批状态回调处理
|
||||
# =============================================================================
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from pydantic import BaseModel
|
||||
import redis.asyncio as aioredis
|
||||
|
||||
from app.config import settings
|
||||
from app.utils.token_manager import ApprovalTokenManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
# =============================================================================
|
||||
# 审批模板配置(可配置化,后续可存入数据库)
|
||||
# =============================================================================
|
||||
# Redis客户端(依赖注入)
|
||||
async def get_redis() -> aioredis.Redis:
|
||||
"""获取Redis客户端依赖"""
|
||||
from app.main import redis_client
|
||||
return redis_client
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 企微审批模板配置(从环境变量读取)
|
||||
# 审批模板配置(从环境变量读取)
|
||||
# =============================================================================
|
||||
# 环境变量:
|
||||
# APPROVAL_TEMPLATE_RESOURCE - 资源申请模板ID
|
||||
# APPROVAL_TEMPLATE_DEVICE - 设备申请模板ID
|
||||
|
||||
import os
|
||||
|
||||
APPROVAL_TEMPLATE_RESOURCE = os.getenv("APPROVAL_TEMPLATE_RESOURCE", "")
|
||||
APPROVAL_TEMPLATE_DEVICE = os.getenv("APPROVAL_TEMPLATE_DEVICE", "")
|
||||
|
||||
@@ -35,7 +46,7 @@ if APPROVAL_TEMPLATE_RESOURCE:
|
||||
APPROVAL_TEMPLATES[APPROVAL_TEMPLATE_RESOURCE] = {
|
||||
"id": APPROVAL_TEMPLATE_RESOURCE,
|
||||
"name": "资源申请",
|
||||
"type": "jump", # 跳转审批
|
||||
"type": "jump",
|
||||
"keywords": ["申请资源", "要资源", "申请"],
|
||||
}
|
||||
|
||||
@@ -43,7 +54,7 @@ if APPROVAL_TEMPLATE_DEVICE:
|
||||
APPROVAL_TEMPLATES[APPROVAL_TEMPLATE_DEVICE] = {
|
||||
"id": APPROVAL_TEMPLATE_DEVICE,
|
||||
"name": "设备申请",
|
||||
"type": "api", # API提交
|
||||
"type": "api",
|
||||
"keywords": ["申请设备", "要设备", "电脑", "笔记本"],
|
||||
}
|
||||
|
||||
@@ -52,12 +63,11 @@ if APPROVAL_TEMPLATE_DEVICE:
|
||||
# Schema 定义
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class ApprovalTemplateResponse(BaseModel):
|
||||
"""审批模板响应"""
|
||||
id: str
|
||||
name: str
|
||||
type: str # "jump" 或 "api"
|
||||
type: str
|
||||
keywords: list[str]
|
||||
|
||||
|
||||
@@ -73,11 +83,19 @@ class ApprovalJumpResponse(BaseModel):
|
||||
template_name: str
|
||||
|
||||
|
||||
class ApprovalContentItem(BaseModel):
|
||||
"""审批表单控件内容"""
|
||||
control: str # 控件类型: Text, Textarea, Number, Money, Date, Selector, Contact, etc.
|
||||
id: str # 控件ID
|
||||
value: dict # 控件值
|
||||
|
||||
|
||||
class ApprovalSubmitRequest(BaseModel):
|
||||
"""API提交审批请求"""
|
||||
template_id: str
|
||||
employee_id: str
|
||||
content: dict # 审批内容
|
||||
employee_id: str # 申请人userid
|
||||
contents: list[ApprovalContentItem] # 表单内容
|
||||
use_template_approver: int = 1 # 1-使用模板预设流程
|
||||
|
||||
|
||||
class ApprovalSubmitResponse(BaseModel):
|
||||
@@ -86,11 +104,98 @@ class ApprovalSubmitResponse(BaseModel):
|
||||
template_name: str
|
||||
|
||||
|
||||
class ApprovalCallbackRequest(BaseModel):
|
||||
"""审批回调请求(XML解析后的模型)"""
|
||||
sp_no: str
|
||||
sp_name: str
|
||||
template_id: str
|
||||
apply_time: int
|
||||
applyer_userid: str
|
||||
sp_status: int # 1-审批中 2-已通过 3-已驳回 4-已撤销 6-通过后撤销 7-已删除 10-已支付
|
||||
status_change_event: int # 1-提单 2-同意 3-驳回 4-转审 5-催办 6-撤销 8-通过后撤销 10-添加备注
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 企微API调用辅助函数
|
||||
# =============================================================================
|
||||
|
||||
async def get_approval_token(redis: aioredis.Redis) -> str:
|
||||
"""获取审批应用access_token"""
|
||||
manager = ApprovalTokenManager(redis)
|
||||
return await manager.get_token()
|
||||
|
||||
|
||||
async def get_template_detail(access_token: str, template_id: str) -> dict:
|
||||
"""获取审批模板详情
|
||||
|
||||
对应企微API:
|
||||
POST https://qyapi.weixin.qq.com/cgi-bin/oa/gettemplatedetail
|
||||
|
||||
返回模板内的控件构成及控件ID
|
||||
"""
|
||||
url = "https://qyapi.weixin.qq.com/cgi-bin/oa/gettemplatedetail"
|
||||
params = {"access_token": access_token}
|
||||
|
||||
async with httpx.AsyncClient(timeout=httpx.Timeout(connect=10.0, read=30.0)) as client:
|
||||
response = await client.post(url, params=params, json={"template_id": template_id})
|
||||
result = response.json()
|
||||
|
||||
if result.get("errcode") != 0:
|
||||
logger.error(f"获取模板详情失败: {result.get('errmsg')}")
|
||||
raise Exception(f"获取模板详情失败: {result.get('errmsg')}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def submit_approval_api(
|
||||
access_token: str,
|
||||
template_id: str,
|
||||
creator_userid: str,
|
||||
contents: list[dict],
|
||||
use_template_approver: int = 1
|
||||
) -> dict:
|
||||
"""提交审批申请
|
||||
|
||||
对应企微API:
|
||||
POST https://qyapi.weixin.qq.com/cgi-bin/oa/applyevent
|
||||
|
||||
Args:
|
||||
access_token: 审批应用access_token
|
||||
template_id: 模板ID
|
||||
creator_userid: 申请人userid
|
||||
contents: 表单控件内容列表
|
||||
use_template_approver: 1-使用模板预设流程
|
||||
|
||||
Returns:
|
||||
{"sp_no": "审批单号"}
|
||||
"""
|
||||
url = "https://qyapi.weixin.qq.com/cgi-bin/oa/applyevent"
|
||||
params = {"access_token": access_token}
|
||||
|
||||
payload = {
|
||||
"creator_userid": creator_userid,
|
||||
"template_id": template_id,
|
||||
"use_template_approver": use_template_approver,
|
||||
"apply_data": {
|
||||
"contents": contents
|
||||
}
|
||||
}
|
||||
|
||||
async with httpx.AsyncClient(timeout=httpx.Timeout(connect=10.0, read=30.0)) as client:
|
||||
response = await client.post(url, params=params, json=payload)
|
||||
result = response.json()
|
||||
|
||||
if result.get("errcode") != 0:
|
||||
logger.error(f"提交审批失败: {result.get('errmsg')}")
|
||||
raise Exception(f"提交审批失败: {result.get('errmsg')}")
|
||||
|
||||
return {"sp_no": result.get("sp_no")}
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# API 端点
|
||||
# =============================================================================
|
||||
|
||||
|
||||
@router.get("/approval/templates", response_model=list[ApprovalTemplateResponse])
|
||||
async def get_approval_templates():
|
||||
"""获取所有审批模板列表"""
|
||||
@@ -102,27 +207,42 @@ async def get_approval_template(template_id: str):
|
||||
"""获取指定审批模板详情"""
|
||||
if template_id not in APPROVAL_TEMPLATES:
|
||||
from fastapi import HTTPException
|
||||
|
||||
raise HTTPException(status_code=404, detail="模板不存在")
|
||||
return APPROVAL_TEMPLATES[template_id]
|
||||
|
||||
|
||||
@router.get("/approval/templates/{template_id}/detail")
|
||||
async def get_template_full_detail(
|
||||
template_id: str,
|
||||
redis: aioredis.Redis = Depends(get_redis)
|
||||
):
|
||||
"""获取审批模板完整详情(控件结构)"""
|
||||
if template_id not in APPROVAL_TEMPLATES:
|
||||
from fastapi import HTTPException
|
||||
raise HTTPException(status_code=404, detail="模板不存在")
|
||||
|
||||
try:
|
||||
token = await get_approval_token(redis)
|
||||
detail = await get_template_detail(token, template_id)
|
||||
return detail
|
||||
except Exception as e:
|
||||
from fastapi import HTTPException
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/approval/jump", response_model=ApprovalJumpResponse)
|
||||
async def create_approval_jump(request: ApprovalJumpRequest):
|
||||
"""生成跳转审批链接(模板124跳转方式)"""
|
||||
"""生成跳转审批链接"""
|
||||
template = APPROVAL_TEMPLATES.get(request.template_id)
|
||||
if not template:
|
||||
from fastapi import HTTPException
|
||||
|
||||
raise HTTPException(status_code=404, detail="模板不存在")
|
||||
|
||||
if template["type"] != "jump":
|
||||
from fastapi import HTTPException
|
||||
|
||||
raise HTTPException(status_code=400, detail="该模板不支持跳转方式")
|
||||
|
||||
# 生成跳转URL(企微审批链接格式)
|
||||
# 实际URL需要根据企微配置生成
|
||||
jump_url = f"https://qyapi.weixin.qq.com/cgi-bin/oa/applyevent?access_token=TOKEN&template_id={request.template_id}"
|
||||
|
||||
return ApprovalJumpResponse(
|
||||
@@ -132,27 +252,102 @@ async def create_approval_jump(request: ApprovalJumpRequest):
|
||||
|
||||
|
||||
@router.post("/approval/submit", response_model=ApprovalSubmitResponse)
|
||||
async def submit_approval(request: ApprovalSubmitRequest):
|
||||
"""API提交审批(模板122 API方式)"""
|
||||
async def submit_approval(
|
||||
request: ApprovalSubmitRequest,
|
||||
redis: aioredis.Redis = Depends(get_redis)
|
||||
):
|
||||
"""API提交审批申请"""
|
||||
template = APPROVAL_TEMPLATES.get(request.template_id)
|
||||
if not template:
|
||||
from fastapi import HTTPException
|
||||
|
||||
raise HTTPException(status_code=404, detail="模板不存在")
|
||||
|
||||
if template["type"] != "api":
|
||||
from fastapi import HTTPException
|
||||
|
||||
raise HTTPException(status_code=400, detail="该模板不支持API提交")
|
||||
|
||||
# TODO: 调用企微API提交审批
|
||||
# 这里需要使用企微access_token调用审批API
|
||||
# 实际实现需要根据企微审批API文档
|
||||
try:
|
||||
# 1. 获取审批token
|
||||
token = await get_approval_token(redis)
|
||||
|
||||
return ApprovalSubmitResponse(
|
||||
sp_no=f"SP{request.template_id[:8]}", # 模拟审批单号
|
||||
template_name=template["name"],
|
||||
)
|
||||
# 2. 转换contents格式
|
||||
contents = [item.model_dump() for item in request.contents]
|
||||
|
||||
# 3. 提交审批
|
||||
result = await submit_approval_api(
|
||||
access_token=token,
|
||||
template_id=request.template_id,
|
||||
creator_userid=request.employee_id,
|
||||
contents=contents,
|
||||
use_template_approver=request.use_template_approver
|
||||
)
|
||||
|
||||
return ApprovalSubmitResponse(
|
||||
sp_no=result["sp_no"],
|
||||
template_name=template["name"],
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
from fastapi import HTTPException
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/approval/callback")
|
||||
async def approval_callback(
|
||||
sp_no: str = Query(...),
|
||||
sp_name: str = Query(...),
|
||||
template_id: str = Query(...),
|
||||
apply_time: int = Query(...),
|
||||
applyer_userid: str = Query(...),
|
||||
sp_status: int = Query(...),
|
||||
status_change_event: int = Query(...)
|
||||
):
|
||||
"""审批状态变化回调处理
|
||||
|
||||
对应企微审批回调事件: sys_approval_change
|
||||
|
||||
状态变化类型 (status_change_event):
|
||||
1 - 提单
|
||||
2 - 同意
|
||||
3 - 驳回
|
||||
4 - 转审
|
||||
5 - 催办
|
||||
6 - 撤销
|
||||
8 - 通过后撤销
|
||||
10 - 添加备注
|
||||
|
||||
审批单状态 (sp_status):
|
||||
1 - 审批中
|
||||
2 - 已通过
|
||||
3 - 已驳回
|
||||
4 - 已撤销
|
||||
6 - 通过后撤销
|
||||
7 - 已删除
|
||||
10 - 已支付
|
||||
"""
|
||||
logger.info(f"审批回调: sp_no={sp_no}, status={sp_status}, event={status_change_event}")
|
||||
|
||||
# TODO: 根据业务需求处理审批状态变化
|
||||
# 例如:
|
||||
# - 审批通过后,更新IT服务台待办状态
|
||||
# - 审批驳回后,通知申请人
|
||||
# - 审批撤销后,关闭相关工单
|
||||
|
||||
event_map = {
|
||||
1: "submitted",
|
||||
2: "approved",
|
||||
3: "rejected",
|
||||
4: "transferred",
|
||||
5: "reminded",
|
||||
6: "revoked",
|
||||
8: "revoked_after_approved",
|
||||
10: "commented"
|
||||
}
|
||||
|
||||
event_type = event_map.get(status_change_event, f"unknown_{status_change_event}")
|
||||
logger.info(f"审批事件类型: {event_type}")
|
||||
|
||||
return {"errcode": 0, "errmsg": "ok"}
|
||||
|
||||
|
||||
@router.get("/approval/keywords")
|
||||
@@ -161,12 +356,10 @@ async def get_approval_keywords():
|
||||
keywords = []
|
||||
for template in APPROVAL_TEMPLATES.values():
|
||||
for kw in template["keywords"]:
|
||||
keywords.append(
|
||||
{
|
||||
"keyword": kw,
|
||||
"template_id": template["id"],
|
||||
"template_name": template["name"],
|
||||
"type": template["type"],
|
||||
}
|
||||
)
|
||||
keywords.append({
|
||||
"keyword": kw,
|
||||
"template_id": template["id"],
|
||||
"template_name": template["name"],
|
||||
"type": template["type"],
|
||||
})
|
||||
return keywords
|
||||
|
||||
Reference in New Issue
Block a user