94 lines
3.5 KiB
Python
94 lines
3.5 KiB
Python
|
|
"""企微机器人路由
|
|||
|
|
|
|||
|
|
GET /wecom/callback 企微 URL 验证
|
|||
|
|
POST /wecom/callback 企微消息接收
|
|||
|
|
GET /wecom/oauth 企微 OAuth 登录入口
|
|||
|
|
GET /wecom/oauth/callback 企微 OAuth 回调
|
|||
|
|
"""
|
|||
|
|
from flask import Blueprint, request, Response, redirect
|
|||
|
|
from insurance.config import get_config as _get_wecom_config
|
|||
|
|
from insurance.wecom.service import WeComService
|
|||
|
|
|
|||
|
|
wecom_bp = Blueprint("wecom", __name__)
|
|||
|
|
wecom_service = WeComService()
|
|||
|
|
|
|||
|
|
|
|||
|
|
@wecom_bp.route("/callback", methods=["GET"])
|
|||
|
|
def verify_callback():
|
|||
|
|
"""企微服务器 URL 验证(GET)。"""
|
|||
|
|
msg_signature = request.args.get("msg_signature", "")
|
|||
|
|
timestamp = request.args.get("timestamp", "")
|
|||
|
|
nonce = request.args.get("nonce", "")
|
|||
|
|
echostr = request.args.get("echostr", "")
|
|||
|
|
|
|||
|
|
result = wecom_service.verify_url(msg_signature, timestamp, nonce, echostr)
|
|||
|
|
return Response(result, content_type="text/plain")
|
|||
|
|
|
|||
|
|
|
|||
|
|
@wecom_bp.route("/callback", methods=["POST"])
|
|||
|
|
def receive_message():
|
|||
|
|
"""企微消息接收(POST)。
|
|||
|
|
|
|||
|
|
群聊:立即返回 "success",异步调 BaoDan 并主动发送到群(群聊不支持被动回复)。
|
|||
|
|
私聊:同步处理,返回被动回复 XML。
|
|||
|
|
"""
|
|||
|
|
# 校验回调IP白名单(Nginx透传 X-Forwarded-For)
|
|||
|
|
client_ip = request.headers.get("X-Forwarded-For", request.remote_addr or "").split(",")[0].strip()
|
|||
|
|
if not wecom_service.is_callback_ip(client_ip):
|
|||
|
|
print(f"[WECOM ERROR] 非法回调IP: {client_ip}", flush=True)
|
|||
|
|
return Response("", status=200)
|
|||
|
|
|
|||
|
|
msg_signature = request.args.get("msg_signature", "")
|
|||
|
|
timestamp = request.args.get("timestamp", "")
|
|||
|
|
nonce = request.args.get("nonce", "")
|
|||
|
|
encrypted_data = request.data
|
|||
|
|
|
|||
|
|
# 先判断是否群聊(群聊必须立即返回 "success",5秒内不回复会被企微重试)
|
|||
|
|
if wecom_service.handle_message_for_group(msg_signature, timestamp, nonce, encrypted_data):
|
|||
|
|
return Response("success", status=200)
|
|||
|
|
|
|||
|
|
# 私聊:同步处理,返回被动回复 XML
|
|||
|
|
reply_xml = wecom_service.handle_message(msg_signature, timestamp, nonce, encrypted_data)
|
|||
|
|
if reply_xml:
|
|||
|
|
return Response(reply_xml, content_type="application/xml", status=200)
|
|||
|
|
return Response("", status=200)
|
|||
|
|
|
|||
|
|
|
|||
|
|
@wecom_bp.route("/oauth", methods=["GET"])
|
|||
|
|
def oauth_login():
|
|||
|
|
"""重定向到企微授权页面。"""
|
|||
|
|
corp_id = _get_wecom_config("WECOM_CORP_ID")
|
|||
|
|
redirect_uri = f"{request.host_url}insurance/wecom/oauth/callback"
|
|||
|
|
auth_url = (
|
|||
|
|
f"https://open.weixin.qq.com/connect/oauth2/authorize"
|
|||
|
|
f"?appid={corp_id}"
|
|||
|
|
f"&redirect_uri={redirect_uri}"
|
|||
|
|
f"&response_type=code"
|
|||
|
|
f"&scope=snsapi_userinfo"
|
|||
|
|
f"&state=state"
|
|||
|
|
f"#wechat_redirect"
|
|||
|
|
)
|
|||
|
|
return redirect(auth_url)
|
|||
|
|
|
|||
|
|
|
|||
|
|
@wecom_bp.route("/oauth/callback", methods=["GET"])
|
|||
|
|
def oauth_callback():
|
|||
|
|
"""企微 OAuth 回调:code 换用户信息 -> 生成 JWT -> 重定向到前端。"""
|
|||
|
|
code = request.args.get("code", "")
|
|||
|
|
if not code:
|
|||
|
|
return redirect("/login?error=auth_failed")
|
|||
|
|
|
|||
|
|
from insurance.auth.service import AuthService
|
|||
|
|
auth_service = AuthService()
|
|||
|
|
result = auth_service.wework_login(code, "")
|
|||
|
|
|
|||
|
|
if result.get("code") == 0:
|
|||
|
|
token = result["data"]["token"]
|
|||
|
|
user = result["data"].get("user", {})
|
|||
|
|
import json
|
|||
|
|
user_json = json.dumps(user, ensure_ascii=False)
|
|||
|
|
from urllib.parse import quote
|
|||
|
|
return redirect(f"/?token={token}&user={quote(user_json)}")
|
|||
|
|
else:
|
|||
|
|
return redirect("/login?error=auth_failed")
|