baodan/api/insurance/wecom/routes.py

111 lines
4.4 KiB
Python
Raw Normal View History

"""企微机器人路由
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
2026-07-12 14:17:18 +08:00
群聊/私聊立即返回 "success"异步调 BaoDan 并主动发送回复
"""
# 校验回调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)
2026-07-12 14:17:18 +08:00
if wecom_service.handle_message_for_private(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():
"""重定向到企微授权页面。"""
2026-07-12 14:17:18 +08:00
from urllib.parse import quote
corp_id = _get_wecom_config("WECOM_CORP_ID")
2026-07-12 14:17:18 +08:00
# 优先使用 BASE_URL 环境变量Docker/Vite 代理下 request.host_url 是内部地址)
base_url = _get_wecom_config("BASE_URL", "").rstrip("/")
if not base_url:
base_url = request.host_url.rstrip("/")
redirect_uri = f"{base_url}/insurance/wecom/oauth/callback"
auth_url = (
f"https://open.weixin.qq.com/connect/oauth2/authorize"
f"?appid={corp_id}"
2026-07-12 14:17:18 +08:00
f"&redirect_uri={quote(redirect_uri, safe='')}"
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:
2026-07-12 14:17:18 +08:00
frontend_url = _get_wecom_config("FRONTEND_URL", "").rstrip("/")
if frontend_url:
return redirect(f"{frontend_url}/login?error=auth_failed")
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
2026-07-12 14:17:18 +08:00
# 重定向到前端Docker 中后端和前端端口不同)
frontend_url = _get_wecom_config("FRONTEND_URL", "").rstrip("/")
if frontend_url:
return redirect(f"{frontend_url}/?token={token}&user={quote(user_json)}")
return redirect(f"/?token={token}&user={quote(user_json)}")
else:
2026-07-12 14:17:18 +08:00
frontend_url = _get_wecom_config("FRONTEND_URL", "").rstrip("/")
if frontend_url:
return redirect(f"{frontend_url}/login?error=auth_failed")
return redirect("/login?error=auth_failed")