257 lines
7.9 KiB
Markdown
257 lines
7.9 KiB
Markdown
# 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事件发布正常。 |