From f7d65f286f122034403a8cea582f9ec8ee109368 Mon Sep 17 00:00:00 2001 From: Tian jianyong <11429339@qq.com> Date: Mon, 20 Oct 2025 14:10:05 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=A4=9A=E4=BD=99=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- adxp-adapter/IMPLEMENTATION_SUMMARY.md | 226 --------- tools/README_SOAP.md | 150 ------ tools/mock_adxp.py | 674 ------------------------- 3 files changed, 1050 deletions(-) delete mode 100644 adxp-adapter/IMPLEMENTATION_SUMMARY.md delete mode 100644 tools/README_SOAP.md delete mode 100644 tools/mock_adxp.py diff --git a/adxp-adapter/IMPLEMENTATION_SUMMARY.md b/adxp-adapter/IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index f6f49d1a..00000000 --- a/adxp-adapter/IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,226 +0,0 @@ -# ADXP SDK Adapter 实施总结 - -## 完成时间 - -2025-10-15 - -## 问题描述 - -ADXP SDK (adxp-client-2.6.9.jar) 依赖 JDK 8 内部类 `com.sun.xml.internal.*`,无法在 Java 11+ 环境运行。主应用使用 Spring Boot 3.x + Java 21,存在兼容性冲突。 - -## 解决方案 - -创建独立的 JDK 8 适配器服务,将 SDK 封装为 REST API,主应用通过 HTTP 调用。 - -## 项目结构 - -``` -adxp-adapter/ -├── src/main/java/com/qaup/adxp/adapter/ -│ ├── AdxpAdapterApplication.java # 主应用 -│ ├── controller/ -│ │ └── AdxpController.java # REST API -│ ├── service/ -│ │ └── AdxpSdkService.java # SDK 封装 -│ └── dto/ -│ ├── LoginRequest.java -│ ├── LoginResponse.java -│ ├── FlightMessage.java -│ └── MessageResponse.java -├── src/main/resources/ -│ ├── application.yml # 配置文件 -│ └── application-prod.yml -├── libs/ -│ ├── adxp-client-2.6.9.jar # SDK JAR -│ └── mq.allclient-9.0.jar # IBM MQ JAR -├── pom.xml # Maven 配置 (JDK 8, Spring Boot 2.7.18) -├── Dockerfile # Docker 镜像 -├── docker-compose.yml # Docker Compose -├── README.md # 详细文档 -├── QUICKSTART.md # 快速开始 -└── IMPLEMENTATION_SUMMARY.md # 本文件 -``` - -## 实现的功能 - -### 1. REST API 端点 - -| 端点 | 方法 | 说明 | -|------|------|------| -| `/api/adxp/login` | POST | 登录数据中台 | -| `/api/adxp/messages` | GET | 接收航班消息 | -| `/api/adxp/logout` | POST | 登出 | -| `/api/adxp/health` | GET | 健康检查 | - -### 2. 主应用集成 - -创建 `AdxpFlightServiceHttpClient`,通过 HTTP 调用适配器服务: -- 自动登录和 session 管理 -- 消息解析(ARR/AXOT/RUNWAY/CRAFTSEAT) -- 错误处理和重连 - -### 3. 配置管理 - -支持环境变量和配置文件: -```yaml -# 主应用 -data.collector.adxp-adapter: - host: localhost - port: 8086 - -# 适配器 -adxp: - host: ${ADXP_HOST} - port: ${ADXP_PORT} -``` - -### 4. Docker 支持 - -- Dockerfile: 基于 openjdk:8-jdk-alpine -- docker-compose.yml: 一键部署 -- 健康检查: 自动监控服务状态 - -## 技术栈 - -| 组件 | 技术 | 版本 | -|------|------|------| -| 适配器服务 | Spring Boot | 2.7.18 | -| Java 版本 | JDK | 8 | -| 主应用 | Spring Boot | 3.5.3 | -| Java 版本 | JDK | 21 | -| 通信协议 | HTTP/REST | - | -| 容器化 | Docker | - | - -## 文件清单 - -### 新增文件 - -``` -adxp-adapter/ -├── pom.xml ✓ 创建 -├── Dockerfile ✓ 创建 -├── docker-compose.yml ✓ 创建 -├── README.md ✓ 创建 -├── QUICKSTART.md ✓ 创建 -├── IMPLEMENTATION_SUMMARY.md ✓ 创建 -├── src/main/java/com/qaup/adxp/adapter/ -│ ├── AdxpAdapterApplication.java ✓ 创建 -│ ├── controller/AdxpController.java ✓ 创建 -│ ├── service/AdxpSdkService.java ✓ 创建 -│ └── dto/ -│ ├── LoginRequest.java ✓ 创建 -│ ├── LoginResponse.java ✓ 创建 -│ ├── FlightMessage.java ✓ 创建 -│ └── MessageResponse.java ✓ 创建 -├── src/main/resources/ -│ ├── application.yml ✓ 创建 -│ └── application-prod.yml ✓ 创建 -└── libs/ - ├── adxp-client-2.6.9.jar ✓ 复制 - └── mq.allclient-9.0.jar ✓ 复制 -``` - -### 修改文件 - -``` -qaup-collision/src/main/java/com/qaup/collision/datacollector/ -├── sdk/AdxpFlightServiceHttpClient.java ✓ 创建 -└── config/FlightSdkProperties.java ✓ 修改(添加 useHttp) - -qaup-admin/src/main/resources/ -└── application-dev.yml ✓ 修改(适配器配置精简) -``` - -## 使用方式 - -### 开发环境 - -1. 启动 Mock 服务器 -```bash -python3 tools/mock_adxp.py --auto --interval 10 -``` - -2. 启动适配器服务 -```bash -cd adxp-adapter -mvn spring-boot:run -``` - -3. 启动主应用 -```bash -cd qaup-admin -mvn spring-boot:run -Dspring-boot.run.profiles=dev,druid -``` - -### 生产环境 - -使用 Docker Compose: -```bash -cd adxp-adapter -export ADXP_HOST=10.10.10.100 -export ADXP_PORT=7001 -docker-compose up -d -``` - -## 优势 - -1. ✅ **兼容性隔离**: SDK 在 JDK 8 环境运行,主应用使用 Java 21 -2. ✅ **架构清晰**: 适配器独立部署,职责明确 -3. ✅ **易于维护**: SDK 升级只需修改适配器 -4. ✅ **技术自由**: 主应用可使用最新 Java 特性 -5. ✅ **容错能力**: 适配器故障不影响主应用其他功能 -6. ✅ **水平扩展**: 支持多个主应用实例共享适配器 -7. ✅ **性能影响小**: HTTP 延迟 < 5ms(局域网) - -## 测试结果 - -- ✅ 适配器服务编译通过 -- ✅ 所有 REST API 端点就绪 -- ✅ 主应用 HTTP 客户端集成完成 -- ✅ Docker 镜像构建成功 -- ✅ 配置管理完善 - -## 下一步 - -### 立即测试(推荐) - -```bash -# Terminal 1: Mock 服务器 -cd tools && python3 mock_adxp.py --auto --interval 10 - -# Terminal 2: 适配器服务 -cd adxp-adapter && mvn clean package && java -jar target/adxp-adapter.jar - -# Terminal 3: 主应用 -cd qaup-admin && mvn spring-boot:run -Dspring-boot.run.profiles=dev,druid -``` - -### 生产部署 - -1. 部署适配器到生产服务器 -2. 配置主应用连接适配器 -3. 添加监控和告警 -4. 压力测试 - -### 可选优化 - -1. 添加 API 认证(API Key/JWT) -2. 集成 Prometheus metrics -3. 添加缓存层 -4. 实现连接池 - -## 估算工作量 - -- ✅ 设计方案: 30分钟 -- ✅ 创建项目: 1小时 -- ✅ 实现代码: 1.5小时 -- ✅ 集成主应用: 30分钟 -- ✅ Docker化: 30分钟 -- ✅ 文档编写: 30分钟 - -**总计: 4小时** - -## 联系人 - -实施者: AI Assistant -日期: 2025-10-15 diff --git a/tools/README_SOAP.md b/tools/README_SOAP.md deleted file mode 100644 index 28cf6cf5..00000000 --- a/tools/README_SOAP.md +++ /dev/null @@ -1,150 +0,0 @@ -# ADXP SOAP Mock Server 使用说明 - -## 概述 - -`mock_adxp_soap.py` 是数据中台的 SOAP WebService 模拟服务器,用于本地开发测试 SDK 集成。 - -## 安装依赖 - -```bash -cd tools -pip3 install -r requirements-soap.txt -``` - -或直接安装: -```bash -pip3 install spyne lxml -``` - -## 启动服务器 - -### 基本启动 -```bash -python3 mock_adxp_soap.py -``` - -### 启用自动推送(每10秒生成一批样例消息) -```bash -python3 mock_adxp_soap.py --auto --interval 10 -``` - -### 自定义地址和端口 -```bash -python3 mock_adxp_soap.py --host 0.0.0.0 --port 8086 --auto -``` - -## 接口说明 - -### WSDL 地址 -``` -http://localhost:8086/adxp?wsdl -``` - -### SOAP 操作 - -#### 1. login(username, password) -用户登录 - -**测试账号:** -- 用户名: `dianxin` -- 密码: `dianxin@123` - -**返回:** -```xml - - true - 200 - 登录成功 - -``` - -#### 2. receiveMessage() -接收消息队列中的所有消息(接收后清空) - -**返回:** -```xml - - - - ADXP_NAOMS_O_DYN_ARR - UPDATE - - - - ... - - -``` - -## 自动推送消息 - -启用 `--auto` 后,服务器每隔指定秒数自动生成以下样例消息: - -1. **MU5123 到达** (ARR + RUNWAY + CRAFTSEAT) - - 降落时间: 实时 - - 跑道: 35L - - 机位: 138 - -2. **CA1234 离港** (AXOT + RUNWAY + CRAFTSEAT) - - 撤轮挡时间: 实时 - - 跑道: 17 - - 机位: 201 - -## 配置后端应用 - -确保 `application-dev.yml` 中配置正确: - -```yaml -data: - collector: - adxp-adapter: - host: localhost - port: 8086 - username: dianxin - password: dianxin@123 - reconnect-delay-millis: 3000 -``` - -或通过环境变量: -```bash -export ADXP_SDK_ENABLED=true -export ADXP_SDK_HOST=localhost -export ADXP_SDK_PORT=8086 -``` - -## 测试流程 - -1. **启动 SOAP mock 服务器:** - ```bash - cd tools - python3 mock_adxp_soap.py --auto --interval 10 - ``` - -2. **启动后端应用:** - ```bash - cd qaup-admin - mvn spring-boot:run -Dspring-boot.run.profiles=dev - ``` - -3. **验证数据:** - - 查看后端日志,确认 SDK 登录成功 - - 打开前端页面,查看航班通知实时更新 - - 每10秒应该收到 MU5123 和 CA1234 的更新 - -## 故障排查 - -### 登录失败 803 -- 检查 mock 服务器是否启动 -- 检查用户名密码是否为 `dianxin/dianxin@123` -- 查看 mock 服务器日志 - -### 接收不到消息 -- 确认启用了 `--auto` 自动推送 -- 检查后端 `adxp-adapter` 配置是否正确 -- 查看 DataCollectorService 日志 - -### 依赖安装失败 -```bash -# 使用国内镜像 -pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple spyne lxml -``` diff --git a/tools/mock_adxp.py b/tools/mock_adxp.py deleted file mode 100644 index 5537ddbf..00000000 --- a/tools/mock_adxp.py +++ /dev/null @@ -1,674 +0,0 @@ -#!/usr/bin/env python3 -"""ADXP 数据中台 SOAP WebService 模拟服务(纯标准库实现) - -提供 SOAP WebService 接口,模拟真实数据中台,支持 SDK 客户端连接。 - -启动示例: - python3 mock_adxp.py - python3 mock_adxp.py --auto --interval 10 - -SOAP 接口: - POST /adxp SOAP 服务端点 - GET /adxp?wsdl 获取 WSDL 描述 - -支持操作: - - login(username, password) 用户登录 - - receiveMessage() 接收消息 - -运行该脚本无需额外第三方依赖。 -""" - -import argparse -import logging -import socketserver -import threading -import time -import xml.etree.ElementTree as ET -from datetime import datetime -from http.server import BaseHTTPRequestHandler -from typing import List, Tuple, Optional - -# 导入统一的日志配置 -import sys -import os -sys.path.append(os.path.dirname(os.path.abspath(__file__))) -from logging_config import setup_logger - -# 设置当前模块的日志记录器 -logger = setup_logger( - name='mock_adxp', - log_file='logs/mock_adxp.log', - max_bytes=10*1024*1024, # 10MB - backup_count=5 -) - -# 服务代码 -SERVICE_CODES = { - "ARR": "ADXP_NAOMS_O_DYN_ARR", - "AXOT": "ADXP_NAOMS_O_CDM_AXOT", - "RUNWAY": "ADXP_NAOMS_O_CDM_RUNWAY", - "CRAFTSEAT": "ADXP_NAOMS_O_DYN_CRAFTSEAT", -} - -# SOAP 命名空间 -NS_SOAP = "http://schemas.xmlsoap.org/soap/envelope/" -NS_LOGIN = "http://LoginService" -NS_MESSAGE = "http://MessageService" - - -def now_timestamp(fmt="%Y%m%d%H%M%S"): - """获取当前时间戳""" - return datetime.now().strftime(fmt) - - -def build_biz_key(flight_no, movement, event_time): - """构建 BizKey""" - marker = "A" if movement == "ARR" else "D" - return f"{flight_no}-{marker}-{event_time}" - - -def wrap_message(service_code, body): - """包装消息(匹配真实数据中台格式)""" - session_id = now_timestamp("%Y%m%d%H%M%S%f")[:-3] # 精确到毫秒 - return ( - '' - '' - '' - f'{service_code}' - '1.0' - 'ADXP' - 'NAOMS' - '' - '' - '' - f'{session_id}' - f'{session_id}' - '' - '' - f'{body}' - '' - '' - ) - - -def build_arrival_message(flight_no, landing_time=None): - """构建到达消息(匹配真实格式)""" - event_time = landing_time or now_timestamp() - biz_key = build_biz_key(flight_no, "ARR", event_time) - flight_id = abs(hash(biz_key)) % 10000000 # 生成模拟 FlightId - body = ( - '' - f'{flight_id}' - f'{biz_key}' - 'TAO' - 'ARR' - f'{event_time}' - '' - ) - xml = wrap_message(SERVICE_CODES["ARR"], body) - return SERVICE_CODES["ARR"], "UPDATE", xml - - -def build_departure_message(flight_no, offblock_time=None): - """构建离港AXOT消息(匹配真实CDM格式)""" - event_time_min = (offblock_time or now_timestamp())[:12] # AXOT精确到分钟 yyyyMMddHHmm - event_time = offblock_time or now_timestamp() - biz_key = build_biz_key(flight_no, "DEP", event_time) - body = ( - '' - 'HCDM' - f' {biz_key}' # 注意BizKey有空格前缀 - f'{event_time_min}' - '' - ) - xml = wrap_message(SERVICE_CODES["AXOT"], body) - return SERVICE_CODES["AXOT"], "UPDATE", xml - - -def build_runway_message(flight_no, movement, runway, event_time=None): - """构建跑道分配消息(匹配真实CDM格式)""" - evt_time = event_time or now_timestamp() - biz_key = build_biz_key(flight_no, movement, evt_time) - body = ( - '' - f'{biz_key}' - f'{movement}' - f'{runway}' - '' - ) - xml = wrap_message(SERVICE_CODES["RUNWAY"], body) - return SERVICE_CODES["RUNWAY"], "UPDATE", xml - - -def build_craftseat_message(flight_no, movement, seat, event_time=None): - """构建机位分配消息(匹配真实DYN格式)""" - evt_time = event_time or now_timestamp() - biz_key = build_biz_key(flight_no, movement, evt_time) - flight_id = abs(hash(biz_key)) % 10000000 - body = ( - '' - f'{flight_id}' - f'{biz_key}' - '' - '' - f'{seat}' - f'{evt_time}' - f'{evt_time}' - f'{evt_time}' - f'{evt_time}' - '' - '' - '' - ) - xml = wrap_message(SERVICE_CODES["CRAFTSEAT"], body) - return SERVICE_CODES["CRAFTSEAT"], "UPDATE", xml - - -# ==================== 消息队列 ==================== - -class MessageQueue: - """线程安全的消息队列""" - - def __init__(self): - self.messages = [] # List of (service_code, action_code, xml) - self.lock = threading.Lock() - self.auto_push_enabled = False - self.auto_push_thread = None - self.auto_push_interval = 10 - - def add_message(self, service_code, action_code, xml): - with self.lock: - self.messages.append((service_code, action_code, xml)) - logger.info(f"Added message: {service_code}") - - def get_messages(self): - """获取并清空所有消息""" - with self.lock: - msgs = self.messages.copy() - self.messages.clear() - return msgs - - def start_auto_push(self, interval=10): - if self.auto_push_enabled: - return - self.auto_push_enabled = True - self.auto_push_interval = interval - self.auto_push_thread = threading.Thread(target=self._auto_push_loop, daemon=True) - self.auto_push_thread.start() - logger.info(f"Auto-push started with interval {interval}s") - - def stop_auto_push(self): - self.auto_push_enabled = False - if self.auto_push_thread: - self.auto_push_thread.join(timeout=2) - logger.info("Auto-push stopped") - - def _auto_push_loop(self): - while self.auto_push_enabled: - try: - self._generate_sample_messages() - time.sleep(self.auto_push_interval) - except Exception as e: - logger.error(f"Auto-push error: {e}") - - def _generate_sample_messages(self): - event_time = now_timestamp() - - # MU5123 到达 - self.add_message(*build_arrival_message("MU5123", event_time)) - self.add_message(*build_runway_message("MU5123", "ARR", "35L", event_time)) - self.add_message(*build_craftseat_message("MU5123", "ARR", "138", event_time)) - - # CA1234 离港 - self.add_message(*build_departure_message("CA1234", event_time)) - self.add_message(*build_runway_message("CA1234", "DEP", "17", event_time)) - self.add_message(*build_craftseat_message("CA1234", "DEP", "201", event_time)) - - logger.info(f"Generated 6 sample messages at {event_time}") - - -# 全局消息队列 -message_queue = MessageQueue() - -# 全局 token 存储(用户名 -> token) -active_tokens = {} - - -# ==================== SOAP 处理 ==================== - -def create_soap_response(body_content): - """创建 SOAP 响应""" - # 使用字符串拼接方式创建 SOAP 响应,避免命名空间问题 - body_xml = ET.tostring(body_content, encoding='utf-8').decode('utf-8') - - soap_response = f''' - - - {body_xml} - -''' - - return soap_response.encode('utf-8') - - -def create_login_response(success, code, message): - """创建登录响应(完全匹配真实数据中台格式 - 直接返回 LoginResult)""" - # 直接创建 LoginResult 元素,不需要 loginResponse 包装 - result = ET.Element('LoginResult') - - success_elem = ET.SubElement(result, 'success') - success_elem.text = 'TRUE' if success else 'FALSE' # 全大写 - - code_elem = ET.SubElement(result, 'code') - code_elem.text = str(code) - - # message 字段返回 token(成功时)或错误信息(失败时) - message_elem = ET.SubElement(result, 'message') - message_elem.text = message if message else '' - - return create_soap_response(result) - - -def create_receive_message_response(messages): - """创建接收消息响应(匹配 SDK 期望的 receiveMessageResponse)""" - import uuid - - # 创建 SOAP 操作响应元素(SDK 期望带命名空间) - response = ET.Element('{' + NS_MESSAGE + '}receiveMessageResponse') - result = ET.SubElement(response, 'MessageResult') - - success_elem = ET.SubElement(result, 'success') - success_elem.text = 'TRUE' - - code_elem = ET.SubElement(result, 'code') - code_elem.text = '0' - - guid_elem = ET.SubElement(result, 'guid') - guid_elem.text = str(uuid.uuid4()).replace('-', '') - - # MessageList - 大写 M!SDK JAXB 要求,每个 Msg 包含 Head 和 Body - msg_list = ET.SubElement(result, 'MessageList') - for service_code, action_code, xml_content in messages: - msg_elem = ET.SubElement(msg_list, 'Msg') # 注意:Msg 首字母大写 - - # 解析 xml_content,提取 Head 和 Body 元素 - try: - import xml.etree.ElementTree as ET_parse - msg_root = ET_parse.fromstring(xml_content) - # 找到 Head 和 Body 元素并添加到 Msg 中 - for child in msg_root: - if child.tag in ('Head', 'Body'): - msg_elem.append(child) - except Exception as e: - logger.error(f"解析消息 XML 失败: {e}") - - return create_soap_response(response) - - -def parse_soap_request(xml_data): - """解析 SOAP 请求(支持 Header 中的 token 验证)""" - try: - root = ET.fromstring(xml_data) - - # 查找 Header(可能包含 username 和 token) - header = root.find('.//{' + NS_SOAP + '}Header') - auth_info = {} - if header is not None: - # 尝试提取 username 和 token(可能在不同命名空间) - for elem in header.iter(): - if elem.tag.endswith('username') and elem.text: - auth_info['username'] = elem.text - elif elem.tag.endswith('token') and elem.text: - auth_info['token'] = elem.text - - # 查找 Body - body = root.find('.//{' + NS_SOAP + '}Body') - if body is None: - return None, None - - # 查找 login 操作 - login_elem = body.find('.//{' + NS_LOGIN + '}login') - if login_elem is not None: - username_elem = login_elem.find('.//{' + NS_LOGIN + '}username') - password_elem = login_elem.find('.//{' + NS_LOGIN + '}password') - # 也尝试无命名空间的元素 - if username_elem is None: - username_elem = login_elem.find('.//username') - if password_elem is None: - password_elem = login_elem.find('.//password') - - params = { - 'username': username_elem.text if username_elem is not None else None, - 'password': password_elem.text if password_elem is not None else None - } - params.update(auth_info) # 添加 Header 中的认证信息 - return 'login', params - - # 查找 receiveMessage 操作(可能在 LoginService 或 MessageService 命名空间) - receive_elem = body.find('.//{' + NS_LOGIN + '}receiveMessage') - if receive_elem is None: - receive_elem = body.find('.//{' + NS_MESSAGE + '}receiveMessage') - if receive_elem is not None: - return 'receiveMessage', auth_info # 返回 Header 中的认证信息 - - # 查找 getInterval 操作(HeartbeatService) - # 尝试查找任何命名空间的 getInterval - for elem in body.iter(): - if elem.tag.endswith('getInterval'): - return 'getInterval', auth_info - - return None, None - except Exception as e: - logger.error(f"Parse SOAP request error: {e}", exc_info=True) - return None, None - - -def handle_login(username, password): - """处理登录,生成并返回 token""" - logger.info(f"Login request: username={username}") - - # 检查用户名和密码是否为None - if username is None or password is None: - logger.warning("❌ Login failed: username or password is None") - return create_login_response(False, 803, "用户名或密码不能为空") - - if username == "dianxin" and password == "dianxin@123": - # 生成 token(使用 UUID,32位十六进制) - import uuid - token = str(uuid.uuid4()).replace('-', '') - active_tokens[username] = token - logger.info(f"✅ Login successful: username={username}, token={token[:16] if token else ''}...") - return create_login_response(True, 0, token) # message 字段返回 token - else: - logger.warning(f"❌ Login failed: username={username}") - return create_login_response(False, 803, "用户名或密码错误") - - -def handle_receive_message(auth_info=None): - """处理接收消息(无验证,直接返回)""" - # 完全跳过 token 验证,直接返回消息 - messages = message_queue.get_messages() - # 只在有消息时记录日志,减少噪音 - if len(messages) > 0: - logger.info(f"✅ receiveMessage returned {len(messages)} messages") - return create_receive_message_response(messages) - - -def handle_get_interval(): - """处理 getInterval 请求(HeartbeatService)""" - # 返回心跳间隔(毫秒),SDK 用这个来检查连接是否正常 - response = ET.Element('getIntervalResponse') - interval = ET.SubElement(response, 'return') - interval.text = '60000' # 60 秒心跳间隔 - return create_soap_response(response) - - -# ==================== WSDL 定义 ==================== - -WSDL_TEMPLATE = ''' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -''' - - -# ==================== HTTP 处理器 ==================== - -class SOAPRequestHandler(BaseHTTPRequestHandler): - """SOAP 请求处理器""" - - def log_message(self, format, *args): - """自定义日志 - 禁用 HTTP 请求日志减少噪音""" - pass # 不记录每个 HTTP 请求 - - def do_GET(self): - """处理 GET 请求(WSDL)""" - # 支持多个服务路径 - if any(self.path.startswith(p) for p in ['/LoginService', '/MessageService', '/HeartbeatService', '/adxp']): - if '?wsdl' in self.path.lower(): - # 动态生成 WSDL,替换端点 URL - host = self.headers.get('Host', 'localhost:8086') - - # 根据路径确定服务名 - if '/MessageService' in self.path: - service_name = 'MessageService' - elif '/HeartbeatService' in self.path: - service_name = 'HeartbeatService' - else: - service_name = 'LoginService' - - endpoint_url = f"http://{host}/{service_name}" - wsdl_content = WSDL_TEMPLATE.replace('{{ENDPOINT_URL}}', endpoint_url) - wsdl_content = wsdl_content.replace('LoginService', service_name) - - self.send_response(200) - self.send_header('Content-Type', 'text/xml; charset=utf-8') - self.end_headers() - self.wfile.write(wsdl_content.encode('utf-8')) - else: - self.send_response(200) - self.send_header('Content-Type', 'text/html; charset=utf-8') - self.end_headers() - html = ''' -ADXP Mock Service - -

ADXP SOAP WebService Mock Server

-

WSDL: /LoginService?wsdl

-

Endpoint: POST /LoginService

-''' - self.wfile.write(html.encode('utf-8')) - else: - self.send_error(404) - - def do_POST(self): - """处理 POST 请求(SOAP)""" - # 支持多个服务路径 - valid_paths = ['/LoginService', '/MessageService', '/HeartbeatService', '/adxp'] - if not any(self.path.startswith(p) for p in valid_paths): - self.send_error(404) - return - - # 读取请求体 - content_length = int(self.headers.get('Content-Length', 0)) - body = self.rfile.read(content_length) - - # 打印原始请求(调试用) - logger.debug("=" * 60) - logger.debug("Received SOAP Request:") - logger.debug(body.decode('utf-8', errors='ignore')) - logger.debug("=" * 60) - - # 解析 SOAP 请求 - operation, params = parse_soap_request(body) - - if operation == 'login': - # 确保 params 不为 None 且包含必要的字段 - if params is None: - logger.error("Login request has no parameters") - response = create_login_response(False, 801, "登录参数错误") - else: - # 确保username和password不为None - username = params.get('username') if params.get('username') is not None else "" - password = params.get('password') if params.get('password') is not None else "" - response = handle_login(username, password) - elif operation == 'receiveMessage': - # 确保 params 不为 None - response = handle_receive_message(params if params is not None else {}) # 传入 auth_info - elif operation == 'getInterval': - response = handle_get_interval() - else: - logger.error(f"Unknown operation: {operation}") - logger.error(f"Request body: {body.decode('utf-8', errors='ignore')}") - self.send_error(400, "Unknown operation") - return - - # 打印响应(调试用) - logger.debug("Sending SOAP Response:") - logger.debug(response.decode('utf-8', errors='ignore')) - logger.debug("=" * 60) - - # 发送响应 - self.send_response(200) - self.send_header('Content-Type', 'text/xml; charset=utf-8') - self.send_header('Content-Length', str(len(response))) - self.end_headers() - self.wfile.write(response) - - -class ThreadedHTTPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): - """支持多线程的 HTTP 服务器""" - allow_reuse_address = True - daemon_threads = True - - -# ==================== 主程序 ==================== - -def main(): - parser = argparse.ArgumentParser(description="ADXP SOAP WebService Mock Server") - parser.add_argument("--host", default="0.0.0.0", help="Server host (default: 0.0.0.0)") - parser.add_argument("--port", type=int, default=8086, help="Server port (default: 8086)") - parser.add_argument("--auto", action="store_true", help="Enable auto-push on startup") - parser.add_argument("--interval", type=int, default=10, help="Auto-push interval in seconds (default: 10)") - args = parser.parse_args() - - # 启动自动推送 - if args.auto: - message_queue.start_auto_push(args.interval) - - # 启动服务器 - server = ThreadedHTTPServer((args.host, args.port), SOAPRequestHandler) - - logger.info("=" * 70) - logger.info("ADXP SOAP WebService Mock Server") - logger.info("=" * 70) - logger.info(f"WSDL: http://{args.host}:{args.port}/LoginService?wsdl") - logger.info(f"Endpoint: http://{args.host}:{args.port}/LoginService") - logger.info(f"Auto-push: {'ENABLED' if args.auto else 'DISABLED'}") - if args.auto: - logger.info(f"Auto-push interval: {args.interval}s") - logger.info(f"Credentials: dianxin / dianxin@123") - logger.info("=" * 70) - - try: - server.serve_forever() - except KeyboardInterrupt: - logger.info("Shutting down...") - message_queue.stop_auto_push() - server.shutdown() - - -if __name__ == "__main__": - main()