删除多余文件

This commit is contained in:
Tian jianyong 2025-10-20 14:10:05 +08:00
parent b0d481bdff
commit f7d65f286f
3 changed files with 0 additions and 1050 deletions

View File

@ -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

View File

@ -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
<LoginResult>
<success>true</success>
<code>200</code>
<message>登录成功</message>
</LoginResult>
```
#### 2. receiveMessage()
接收消息队列中的所有消息(接收后清空)
**返回:**
```xml
<MessageResult>
<messageList>
<msg>
<serviceCode>ADXP_NAOMS_O_DYN_ARR</serviceCode>
<actionCode>UPDATE</actionCode>
</msg>
</messageList>
<messageStringList>
<item><?xml version="1.0"?>...</item>
</messageStringList>
</MessageResult>
```
## 自动推送消息
启用 `--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
```

View File

@ -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 (
'<?xml version="1.0" encoding="UTF-8"?>'
'<Msg>'
'<Head>'
f'<Svc_ServiceCode>{service_code}</Svc_ServiceCode>'
'<Svc_Version>1.0</Svc_Version>'
'<Svc_Sender_Org>ADXP</Svc_Sender_Org>'
'<Svc_Sender>NAOMS</Svc_Sender>'
'<Svc_Receiver_Org></Svc_Receiver_Org>'
'<Svc_Receiver></Svc_Receiver>'
'<Svc_SerialNumber></Svc_SerialNumber>'
f'<Svc_SessionId>{session_id}</Svc_SessionId>'
f'<Svc_SendTimeStamp>{session_id}</Svc_SendTimeStamp>'
'</Head>'
'<Body>'
f'{body}'
'</Body>'
'</Msg>'
)
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 = (
'<DynFlight>'
f'<FlightId>{flight_id}</FlightId>'
f'<BizKey>{biz_key}</BizKey>'
'<AirportIATA>TAO</AirportIATA>'
'<FLIGHTSTATUS>ARR</FLIGHTSTATUS>'
f'<RealLanding>{event_time}</RealLanding>'
'</DynFlight>'
)
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 = (
'<Flight>'
'<SourceKey>HCDM</SourceKey>'
f'<BizKey> {biz_key}</BizKey>' # 注意BizKey有空格前缀
f'<AXOT>{event_time_min}</AXOT>'
'</Flight>'
)
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 = (
'<CdmRunway>'
f'<BizKey>{biz_key}</BizKey>'
f'<Movement>{movement}</Movement>'
f'<Runway>{runway}</Runway>'
'</CdmRunway>'
)
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 = (
'<DynFlight>'
f'<FlightId>{flight_id}</FlightId>'
f'<BizKey>{biz_key}</BizKey>'
'<CraftseatList>'
'<Craftseat>'
f'<Code>{seat}</Code>'
f'<PlanStart>{evt_time}</PlanStart>'
f'<PlanEnd>{evt_time}</PlanEnd>'
f'<RealStart>{evt_time}</RealStart>'
f'<RealEnd>{evt_time}</RealEnd>'
'</Craftseat>'
'</CraftseatList>'
'</DynFlight>'
)
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'''<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="{NS_SOAP}">
<soapenv:Body>
{body_xml}
</soapenv:Body>
</soapenv:Envelope>'''
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 - 大写 MSDK 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使用 UUID32位十六进制
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 = '''<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://LoginService"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://LoginService"
name="LoginService">
<wsdl:types>
<xsd:schema targetNamespace="http://LoginService" elementFormDefault="qualified">
<xsd:element name="login">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="username" type="xsd:string"/>
<xsd:element name="password" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="loginResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="return" type="tns:loginResult"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="loginResult">
<xsd:sequence>
<xsd:element name="success" type="xsd:boolean"/>
<xsd:element name="code" type="xsd:string"/>
<xsd:element name="message" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="receiveMessage">
<xsd:complexType>
<xsd:sequence/>
</xsd:complexType>
</xsd:element>
<xsd:element name="receiveMessageResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="return" type="tns:messageResult"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="messageResult">
<xsd:sequence>
<xsd:element name="messageList" type="tns:messageList"/>
<xsd:element name="messageStringList" type="xsd:string" maxOccurs="unbounded" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="messageList">
<xsd:sequence>
<xsd:element name="msg" type="tns:msgType" maxOccurs="unbounded" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="msgType">
<xsd:sequence>
<xsd:element name="serviceCode" type="xsd:string"/>
<xsd:element name="actionCode" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
</wsdl:types>
<wsdl:message name="login">
<wsdl:part name="parameters" element="tns:login"/>
</wsdl:message>
<wsdl:message name="loginResponse">
<wsdl:part name="parameters" element="tns:loginResponse"/>
</wsdl:message>
<wsdl:message name="receiveMessage">
<wsdl:part name="parameters" element="tns:receiveMessage"/>
</wsdl:message>
<wsdl:message name="receiveMessageResponse">
<wsdl:part name="parameters" element="tns:receiveMessageResponse"/>
</wsdl:message>
<wsdl:portType name="LoginService">
<wsdl:operation name="login">
<wsdl:input message="tns:login"/>
<wsdl:output message="tns:loginResponse"/>
</wsdl:operation>
<wsdl:operation name="receiveMessage">
<wsdl:input message="tns:receiveMessage"/>
<wsdl:output message="tns:receiveMessageResponse"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="LoginServiceSoapBinding" type="tns:LoginService">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="login">
<soap:operation soapAction=""/>
<wsdl:input><soap:body use="literal"/></wsdl:input>
<wsdl:output><soap:body use="literal"/></wsdl:output>
</wsdl:operation>
<wsdl:operation name="receiveMessage">
<soap:operation soapAction=""/>
<wsdl:input><soap:body use="literal"/></wsdl:input>
<wsdl:output><soap:body use="literal"/></wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="LoginServiceHttpService">
<wsdl:port name="LoginServiceHttpPort" binding="tns:LoginServiceSoapBinding">
<soap:address location="{{ENDPOINT_URL}}"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>'''
# ==================== 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 = '''<!DOCTYPE html>
<html><head><title>ADXP Mock Service</title></head>
<body>
<h1>ADXP SOAP WebService Mock Server</h1>
<p>WSDL: <a href="/LoginService?wsdl">/LoginService?wsdl</a></p>
<p>Endpoint: POST /LoginService</p>
</body></html>'''
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()