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

"""企业微信工具模块。
提供企业微信 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"]