182 lines
6.2 KiB
Python
182 lines
6.2 KiB
Python
|
|
# =============================================================================
|
|||
|
|
# 企微IT智能服务台 — 企微 JS-SDK 签名 API (v0.5.4 应急页用)
|
|||
|
|
# =============================================================================
|
|||
|
|
# 说明:提供前端 wx.config / wx.agentConfig 所需的鉴权签名。
|
|||
|
|
# 对应企微文档:https://developer.work.weixin.qq.com/document/path/90506
|
|||
|
|
#
|
|||
|
|
# 流程:
|
|||
|
|
# 1. 前端调 GET /api/wecom/jsapi-config?url=xxx 拿签名
|
|||
|
|
# 2. 后端用 jsapi_ticket + url 算 sha1 签名
|
|||
|
|
# 3. 前端用 wx.config({...}) 鉴权后,即可调企微 JS-SDK(如 wx.agentConfig)
|
|||
|
|
#
|
|||
|
|
# BC/DR 设计:不依赖 session/auth,公开访问(只返回签名,不返回敏感数据)
|
|||
|
|
# =============================================================================
|
|||
|
|
|
|||
|
|
import logging
|
|||
|
|
import secrets
|
|||
|
|
import time
|
|||
|
|
|
|||
|
|
from fastapi import APIRouter, Query
|
|||
|
|
|
|||
|
|
from app.config import settings
|
|||
|
|
from app.dependencies import get_shared_wecom_service
|
|||
|
|
from app.utils.response import AppException, success_response
|
|||
|
|
|
|||
|
|
logger = logging.getLogger(__name__)
|
|||
|
|
|
|||
|
|
router = APIRouter()
|
|||
|
|
|
|||
|
|
|
|||
|
|
@router.get("/wecom/jsapi-config")
|
|||
|
|
async def get_jsapi_config(
|
|||
|
|
url: str = Query(..., description="当前页面 URL(不含 # 及其后)"),
|
|||
|
|
):
|
|||
|
|
"""获取企微 JS-SDK 鉴权配置。
|
|||
|
|
|
|||
|
|
供前端 wx.config 和 wx.agentConfig 使用。
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
{
|
|||
|
|
"code": 0,
|
|||
|
|
"data": {
|
|||
|
|
"corp_id": "wwa8c87970b2011f41",
|
|||
|
|
"agent_id": "1000133",
|
|||
|
|
"timestamp": 1718500000,
|
|||
|
|
"nonce_str": "5K8264ILTKCH...",
|
|||
|
|
"signature": "f7c8e9..."
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
wecom_service = get_shared_wecom_service()
|
|||
|
|
|
|||
|
|
# 1. 获取 jsapi_ticket
|
|||
|
|
ticket = await wecom_service.get_jsapi_ticket()
|
|||
|
|
|
|||
|
|
# 2. 生成时间戳和随机串
|
|||
|
|
timestamp = int(time.time())
|
|||
|
|
nonce_str = secrets.token_hex(8) # 16 字符
|
|||
|
|
|
|||
|
|
# 3. 计算签名
|
|||
|
|
signature = wecom_service.generate_jsapi_signature(
|
|||
|
|
ticket=ticket,
|
|||
|
|
nonce_str=nonce_str,
|
|||
|
|
timestamp=timestamp,
|
|||
|
|
url=url,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
logger.info(
|
|||
|
|
f"生成 JS-SDK 签名: url={url[:80]}... timestamp={timestamp}"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
return success_response(
|
|||
|
|
{
|
|||
|
|
"corp_id": settings.wecom_corp_id,
|
|||
|
|
"agent_id": str(settings.wecom_agent_id),
|
|||
|
|
"timestamp": timestamp,
|
|||
|
|
"nonce_str": nonce_str,
|
|||
|
|
"signature": signature,
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"生成 JS-SDK 签名失败: {e}", exc_info=True)
|
|||
|
|
raise AppException(
|
|||
|
|
code=5001,
|
|||
|
|
message=f"生成 JS-SDK 签名失败: {str(e)}",
|
|||
|
|
) from e
|
|||
|
|
|
|||
|
|
|
|||
|
|
# =============================================================================
|
|||
|
|
# 应急页身份检测 (v0.5.4)
|
|||
|
|
# =============================================================================
|
|||
|
|
# 流程:
|
|||
|
|
# 1. 前端用 wx.agentConfig 拿到当前 userid
|
|||
|
|
# 2. 前端调 GET /api/wecom/check-role?userid=xxx
|
|||
|
|
# 3. 后端用企微通讯录 API 查 userid 是否在"IT支持-咨询坐席"标签里
|
|||
|
|
# 4. 返回 "user" 或 "agent"
|
|||
|
|
# =============================================================================
|
|||
|
|
|
|||
|
|
|
|||
|
|
@router.get("/wecom/check-role")
|
|||
|
|
async def check_emergency_role(
|
|||
|
|
userid: str = Query(..., description="企微 userid"),
|
|||
|
|
):
|
|||
|
|
"""检测当前账号在应急页场景下的角色。
|
|||
|
|
|
|||
|
|
实现方式(优先级递减):
|
|||
|
|
1. 企微通讯录标签检测(若配置 WECOM_AGENT_TAG_ID)
|
|||
|
|
2. 后台硬编码名单(若配置 WECOM_AGENT_USERIDS 环境变量)
|
|||
|
|
3. 默认 "user" (兜底)
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
userid: 企微 userid(从 wx.agentConfig 拿)
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
{
|
|||
|
|
"code": 0,
|
|||
|
|
"data": {
|
|||
|
|
"role": "user" | "agent",
|
|||
|
|
"userid": "...",
|
|||
|
|
"method": "tag" | "hardcoded" | "default"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
"""
|
|||
|
|
wecom_service = get_shared_wecom_service()
|
|||
|
|
|
|||
|
|
# 方式 1:企微标签检测
|
|||
|
|
tag_id = getattr(settings, "wecom_agent_tag_id", None)
|
|||
|
|
if tag_id:
|
|||
|
|
try:
|
|||
|
|
access_token = await wecom_service.get_access_token()
|
|||
|
|
url = f"https://qyapi.weixin.qq.com/cgi-bin/tag/get?access_token={access_token}&tagid={tag_id}"
|
|||
|
|
import httpx
|
|||
|
|
async with httpx.AsyncClient(timeout=5.0) as client:
|
|||
|
|
resp = await client.get(url)
|
|||
|
|
result = resp.json()
|
|||
|
|
|
|||
|
|
if result.get("errcode", 0) == 0:
|
|||
|
|
user_list = result.get("userlist", [])
|
|||
|
|
# userlist 元素可能是 str(老版)或 dict(新版带 name)
|
|||
|
|
user_ids = [
|
|||
|
|
u if isinstance(u, str) else u.get("userid", "")
|
|||
|
|
for u in user_list
|
|||
|
|
]
|
|||
|
|
if userid in user_ids:
|
|||
|
|
logger.info(f"标签检测: userid={userid} 是坐席")
|
|||
|
|
return success_response(
|
|||
|
|
{"role": "agent", "userid": userid, "method": "tag"}
|
|||
|
|
)
|
|||
|
|
else:
|
|||
|
|
logger.info(f"标签检测: userid={userid} 是员工")
|
|||
|
|
return success_response(
|
|||
|
|
{"role": "user", "userid": userid, "method": "tag"}
|
|||
|
|
)
|
|||
|
|
else:
|
|||
|
|
logger.warning(
|
|||
|
|
f"标签 API 失败: errcode={result.get('errcode')}, "
|
|||
|
|
f"errmsg={result.get('errmsg')}, 降级到硬编码"
|
|||
|
|
)
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.warning(f"标签检测失败(降级): {e}")
|
|||
|
|
|
|||
|
|
# 方式 2:硬编码名单
|
|||
|
|
hardcoded = getattr(settings, "wecom_agent_userids", None)
|
|||
|
|
if hardcoded:
|
|||
|
|
agent_ids = [x.strip() for x in hardcoded.split(",") if x.strip()]
|
|||
|
|
if userid in agent_ids:
|
|||
|
|
logger.info(f"硬编码名单: userid={userid} 是坐席")
|
|||
|
|
return success_response(
|
|||
|
|
{"role": "agent", "userid": userid, "method": "hardcoded"}
|
|||
|
|
)
|
|||
|
|
else:
|
|||
|
|
return success_response(
|
|||
|
|
{"role": "user", "userid": userid, "method": "hardcoded"}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 方式 3:默认 user
|
|||
|
|
logger.info(f"未配置检测方式, userid={userid} 默认 user")
|
|||
|
|
return success_response(
|
|||
|
|
{"role": "user", "userid": userid, "method": "default"}
|
|||
|
|
)
|