QDAirPortBackend0122/doc/guide/fix_bug_details.md
2026-01-22 13:19:47 +08:00

257 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Bug修复详细记录
## ADXP的Docker容器无法关闭的问题
### 问题描述
**现象**
- 在 Ubuntu24.04 系统中部署的 ADXP 的 Docker 容器,启动后无法用 docker compose down 关闭
- 错误提示Error response from daemon: cannot stop container: adxp-adapter: permission denied
**错误原因**
- 由于apparmor软件的行为导致
**修复方法**
```sh
sudo apt purge --auto-remove apparmor
sudo service docker restart
docker system prune --all --volumes
```
** 其他可能的方案 **
```sh
sudo systemctl restart docker.socket docker.service
sudo docker image rm -f $(sudo docker image ls -q)
```
## 航班进出港通知定时任务不执行问题
### 问题描述
**现象**
- 新增了航班进出港接口实现,但在日志中没有看到任何访问信息
- 服务端没有收到任何请求
- 其他所有定时任务接口都工作正常,只有航班进出港接口完全没有启动
**预期行为**
- 定时任务应该每1秒执行一次
- 应该有日志输出显示API调用和数据处理过程
- 应该发布WebSocket事件
### 问题分析过程
#### 1. 初步排查 - 代码逻辑对比
对比正常工作的接口和不工作的接口实现:
**正常工作的接口**
```java
// 航空器数据采集
@Scheduled(fixedRateString = "${data.collector.interval}")
public void collectAircraftData() {
List<Aircraft> newAircrafts = dataCollectorDao.collectAircraftData(airportAircraftEndpoint, airportBaseUrl);
// ...
}
// 车辆数据采集
@Scheduled(fixedRateString = "${data.collector.interval}")
public void collectVehicleData() {
List<AirportVehicle> vehicles = dataCollectorDao.collectVehicleData(airportVehicleEndpoint, airportBaseUrl);
// ...
}
```
**不工作的接口**
```java
// 航班进出港通知采集
@Scheduled(fixedRateString = "${data.collector.flight-notification.interval}")
public void collectFlightNotificationData() {
List<FlightNotificationDTO> notifications = dataCollectorDao.getFlightNotifications(flightNotificationEndpoint, airportBaseUrl);
// ...
}
```
**发现差异**:参数传递模式相同,代码逻辑相同,唯一差异在配置键名。
#### 2. 配置分析 - Spring属性绑定问题
检查所有定时任务的配置键名:
**正常工作的配置**
- `${data.collector.interval}`
- `${data.collector.route.interval}`
- `${data.collector.detection.interval:1000}`
**不工作的配置**
- `${data.collector.flight-notification.interval}`
**关键发现**:连字符在嵌套属性中的问题!
#### 3. 配置文件结构分析
```yaml
data:
collector:
interval: 250 # ✅ 工作正常
route: # ✅ 工作正常
interval: 5000
detection: # ✅ 工作正常
interval: 1000
flight-notification: # ❌ 不工作
interval: 1000
```
**根本原因**Spring Boot中@Value/@Scheduled表达式与@ConfigurationProperties对连字符的处理规则不同。
### Spring Boot配置绑定的两套规则
#### @ConfigurationProperties - 支持relaxed binding宽松绑定
```java
@ConfigurationProperties(prefix = "my.app")
public class MyProperties {
private String firstName; // 可以绑定 first-name
}
```
配置文件:
```yaml
my:
app:
first-name: "John" # ✅ 支持连字符会自动转换为firstName
```
#### @Value/@Scheduled - 直接属性解析,连字符限制
```java
@Scheduled(fixedRateString = "${my.app.first-name.interval}") // ❌ 可能失败
@Value("${my.app.first-name.interval}") // ❌ 可能失败
```
**差异原因**
- @ConfigurationProperties使用Spring的relaxed binding机制会自动转换kebab-case到camelCase
- @Value和@Scheduled直接使用Environment.getProperty(),需要精确匹配属性路径
- 当属性路径中包含连字符时,可能影响属性解析器的路径分割逻辑
### 解决方案
#### 1. 修改配置文件
将连字符键名改为无连字符形式:
```yaml
# 修改前(不工作)
flight-notification:
interval: 1000
# 修改后(正常工作)
flightnotification:
interval: 1000
```
#### 2. 修改代码配置引用
```java
// 修改前
@Scheduled(fixedRateString = "${data.collector.flight-notification.interval:1000}")
// 修改后
@Scheduled(fixedRateString = "${data.collector.flightnotification.interval:1000}")
```
### 验证方法
#### 1. 添加调试日志验证定时任务执行
```java
@Scheduled(fixedRateString = "${data.collector.flightnotification.interval:1000}")
public void collectFlightNotificationData() {
log.info("🔥 定时任务启动检查: 航班进出港通知采集任务执行中...");
// ...
}
```
#### 2. 运行集成测试验证
测试结果显示调试日志成功输出:
```
🔥 定时任务启动检查: 航班进出港通知采集任务执行中...
✈️ 成功获取航班进出港通知数据,数量: 2
✈️ 采集到 2 条航班进出港通知
🛬 处理航班进出港通知: 航班号=CA8901, 类型=OUT, 跑道=35, 机位=201
📡 发布航班进出港通知WebSocket事件: 航班号=CA8901, 事件类型=TAKEOFF
✅ 航班进出港通知数据处理完成,处理数量: 2
```
### 最终修复文件
**修改的文件**
1. `/qaup-admin/src/main/resources/application.yml` - 配置键名修改
2. `/qaup-collision/src/main/java/com/qaup/collision/datacollector/service/DataCollectorService.java` - 配置引用修改
**测试文件**
- 集成测试验证功能正常工作
- 所有相关单元测试通过
### 经验教训
#### 1. Spring Boot配置最佳实践
- **避免在嵌套配置中使用连字符**虽然YAML支持连字符但Spring Boot的属性绑定机制在处理嵌套连字符属性时可能存在问题
- **优先使用驼峰命名或无连字符命名**:如`flightNotification`或`flightnotification`
- **为配置属性提供默认值**:使用`${key:defaultValue}`模式避免配置缺失导致的启动失败
#### 2. 连字符配置的使用场景
**推荐使用连字符的场景**
- @ConfigurationProperties的类属性绑定
- 简单的顶级配置属性
**避免使用连字符的场景**
- @Value注解中的嵌套属性路径
- @Scheduled注解中的配置引用
- 复杂的多级嵌套属性路径
#### 3. 问题排查方法论
1. **对比分析法**:对比正常工作和异常功能的实现差异
2. **逐层排查**:从代码逻辑 → 配置文件 → Spring机制
3. **添加调试日志**:在关键位置添加日志验证执行流程
4. **配置验证**:重点检查配置键名的命名规范
#### 4. 测试策略
- **不要仅依赖手动调用测试**集成测试手动调用方法无法发现Spring调度问题
- **需要真实的Spring Boot环境测试**只有在完整的Spring上下文中才能发现配置绑定问题
- **添加专门的调试日志**:临时添加明显的调试标识快速验证修复效果
### 相关技术点
#### Spring Boot属性绑定机制
Spring Boot使用`@ConfigurationProperties`和Environment抽象来绑定配置属性。对于嵌套属性
- **支持的格式**`data.collector.interval`、`data.collector.route.interval`
- **可能有问题的格式**`data.collector.flight-notification.interval`
- **推荐格式**`data.collector.flightnotification.interval`
#### @Scheduled注解机制
`@Scheduled`注解在Spring容器启动时进行解析
1. Spring扫描所有带有`@Scheduled`的方法
2. 解析`fixedRateString`中的配置属性引用
3. 如果配置属性解析失败,该定时任务不会被注册
4. 不会抛出异常,只是静默跳过
这就是为什么其他接口正常工作,而配置有问题的接口完全没有日志输出的原因。
### 状态
-**问题已解决**
-**根本原因已确认**
-**修复方案已验证**
-**相关测试已通过**
**最终结果**航班进出港通知定时任务现在每1秒正常执行日志输出正常WebSocket事件发布正常。