# 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 newAircrafts = dataCollectorDao.collectAircraftData(airportAircraftEndpoint, airportBaseUrl); // ... } // 车辆数据采集 @Scheduled(fixedRateString = "${data.collector.interval}") public void collectVehicleData() { List vehicles = dataCollectorDao.collectVehicleData(airportVehicleEndpoint, airportBaseUrl); // ... } ``` **不工作的接口**: ```java // 航班进出港通知采集 @Scheduled(fixedRateString = "${data.collector.flight-notification.interval}") public void collectFlightNotificationData() { List 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事件发布正常。