You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
181 lines
6.1 KiB
181 lines
6.1 KiB
"""企业微信工具模块。
|
|
|
|
提供企业微信 API 的封装,支持发送消息、查询用户信息、群消息发送等功能。
|
|
包含 access_token 的自动获取和缓存机制。
|
|
"""
|
|
import httpx
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__) # 当前模块的日志记录器
|
|
|
|
_WECOM_ACCESS_TOKEN: dict = {"token": None, "expires_at": 0} # 企业微信 access_token 缓存
|
|
|
|
|
|
def _get_access_token(corp_id: str, app_secret: str) -> str | None:
|
|
"""获取或刷新企业微信 access_token。
|
|
|
|
优先使用缓存中的 token,如果已过期或不存在则重新请求。
|
|
Token 过期前 5 分钟会自动刷新。
|
|
|
|
Args:
|
|
corp_id: 企业微信 CorpID。
|
|
app_secret: 企业微信应用 Secret。
|
|
|
|
Returns:
|
|
str | None: 有效的 access_token,获取失败返回 None。
|
|
"""
|
|
if not corp_id or not app_secret:
|
|
logger.warning("WECOM_CORP_ID 或 WECOM_APP_SECRET 未配置,无法发送企微通知")
|
|
return None
|
|
|
|
import time
|
|
now = time.time()
|
|
if _WECOM_ACCESS_TOKEN["token"] and _WECOM_ACCESS_TOKEN["expires_at"] > now + 60:
|
|
return _WECOM_ACCESS_TOKEN["token"]
|
|
|
|
try:
|
|
url = f"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={corp_id}&corpsecret={app_secret}"
|
|
resp = httpx.get(url, timeout=10)
|
|
data = resp.json()
|
|
if data.get("errcode") == 0:
|
|
_WECOM_ACCESS_TOKEN["token"] = data["access_token"]
|
|
_WECOM_ACCESS_TOKEN["expires_at"] = now + data.get("expires_in", 7200) - 300 # 提前 5 分钟过期
|
|
return _WECOM_ACCESS_TOKEN["token"]
|
|
else:
|
|
logger.error(f"获取企微 token 失败: {data}")
|
|
return None
|
|
except Exception as e:
|
|
logger.error(f"请求企微 token 异常: {e}")
|
|
return None
|
|
|
|
|
|
def _get_config():
|
|
"""从全局配置中获取企业微信 CorpID 和 AppSecret。
|
|
|
|
Returns:
|
|
tuple: (corp_id, app_secret) 元组。
|
|
"""
|
|
from config import settings
|
|
return settings.WECOM_CORP_ID, settings.WECOM_APP_SECRET
|
|
|
|
|
|
def send_notification(to_user: str, message: str, msg_type: str = "text") -> str:
|
|
"""向指定企业微信用户发送通知消息。
|
|
|
|
支持文本和文本卡片两种消息类型。
|
|
|
|
Args:
|
|
to_user: 接收消息的企业微信用户 ID。
|
|
message: 消息内容。
|
|
msg_type: 消息类型,支持 text/textcard。
|
|
|
|
Returns:
|
|
str: 发送结果描述信息。
|
|
"""
|
|
corp_id, app_secret = _get_config()
|
|
token = _get_access_token(corp_id, app_secret)
|
|
if not token:
|
|
return "企业微信通知发送失败: 未配置 WECOM_CORP_ID/WECOM_APP_SECRET 或获取 access_token 失败"
|
|
|
|
try:
|
|
url = f"https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={token}"
|
|
|
|
if msg_type == "textcard":
|
|
body = {
|
|
"touser": to_user,
|
|
"msgtype": "textcard",
|
|
"agentid": 0,
|
|
"textcard": {
|
|
"title": "企业AI平台通知",
|
|
"description": message,
|
|
"url": "",
|
|
},
|
|
}
|
|
else:
|
|
body = {
|
|
"touser": to_user,
|
|
"msgtype": "text",
|
|
"agentid": 0,
|
|
"text": {"content": message},
|
|
}
|
|
|
|
resp = httpx.post(url, json=body, timeout=10)
|
|
data = resp.json()
|
|
if data.get("errcode") == 0:
|
|
return f"企业微信通知已成功发送至 {to_user}"
|
|
else:
|
|
logger.error(f"企微消息发送失败: {data}")
|
|
return f"企业微信通知发送失败: {data.get('errmsg', '未知错误')}"
|
|
except Exception as e:
|
|
logger.error(f"企微消息发送异常: {e}")
|
|
return f"企业微信通知发送失败: {e}"
|
|
|
|
|
|
def query_wecom_user(user_id: str) -> str:
|
|
"""查询企业微信用户的详细信息。
|
|
|
|
Args:
|
|
user_id: 企业微信用户 ID。
|
|
|
|
Returns:
|
|
str: 用户信息描述或错误信息。
|
|
"""
|
|
corp_id, app_secret = _get_config()
|
|
token = _get_access_token(corp_id, app_secret)
|
|
if not token:
|
|
return "企业微信用户查询失败: 未配置或 access_token 获取失败"
|
|
|
|
try:
|
|
url = f"https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token={token}&userid={user_id}"
|
|
resp = httpx.get(url, timeout=10)
|
|
data = resp.json()
|
|
if data.get("errcode") == 0:
|
|
user = data
|
|
return f"用户 {user.get('name', user_id)} - 部门: {user.get('department', [])} - 职位: {user.get('position', '未知')}"
|
|
else:
|
|
return f"企业微信用户查询失败: {data.get('errmsg', '未知错误')}"
|
|
except Exception as e:
|
|
return f"企业微信用户查询失败: {e}"
|
|
|
|
|
|
def send_wecom_group_message(message: str, group_id: str | None = None, msg_type: str = "text") -> str:
|
|
"""向企业微信群发送消息。
|
|
|
|
支持文本和 Markdown 两种消息格式。
|
|
|
|
Args:
|
|
message: 消息内容。
|
|
group_id: 企业微信群聊 ID。
|
|
msg_type: 消息类型,支持 text/markdown。
|
|
|
|
Returns:
|
|
str: 发送结果描述信息。
|
|
"""
|
|
corp_id, app_secret = _get_config()
|
|
token = _get_access_token(corp_id, app_secret)
|
|
if not token:
|
|
return "企业微信群消息发送失败: 未配置或 access_token 获取失败"
|
|
|
|
try:
|
|
url = f"https://qyapi.weixin.qq.com/cgi-bin/appchat/send?access_token={token}"
|
|
|
|
body = {
|
|
"chatid": group_id,
|
|
"msgtype": msg_type,
|
|
}
|
|
if msg_type == "text":
|
|
body["text"] = {"content": message}
|
|
elif msg_type == "markdown":
|
|
body["markdown"] = {"content": message}
|
|
|
|
resp = httpx.post(url, json=body, timeout=10)
|
|
data = resp.json()
|
|
if data.get("errcode") == 0:
|
|
return f"企业微信群消息已成功发送至群 {group_id}"
|
|
else:
|
|
return f"企业微信群消息发送失败: {data.get('errmsg', '未知错误')}"
|
|
except Exception as e:
|
|
return f"企业微信群消息发送失败: {e}"
|
|
|
|
|
|
__all__ = ["send_notification", "query_wecom_user", "send_wecom_group_message"]
|
|
|