"""企微机器人路由 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 并主动发送回复。 """ # 校验回调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) 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(): """重定向到企微授权页面。""" from urllib.parse import quote corp_id = _get_wecom_config("WECOM_CORP_ID") # 优先使用 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}" 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: 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 # 重定向到前端(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: 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")