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

7.9 KiB
Raw Blame History

Bug修复详细记录

ADXP的Docker容器无法关闭的问题

问题描述

现象

  • 在 Ubuntu24.04 系统中部署的 ADXP 的 Docker 容器,启动后无法用 docker compose down 关闭
    • 错误提示Error response from daemon: cannot stop container: adxp-adapter: permission denied

错误原因

  • 由于apparmor软件的行为导致

修复方法

sudo apt purge --auto-remove apparmor
sudo service docker restart
docker system prune --all --volumes

** 其他可能的方案 **

sudo systemctl restart docker.socket docker.service
sudo docker image rm -f $(sudo docker image ls -q)

航班进出港通知定时任务不执行问题

问题描述

现象

  • 新增了航班进出港接口实现,但在日志中没有看到任何访问信息
  • 服务端没有收到任何请求
  • 其他所有定时任务接口都工作正常,只有航班进出港接口完全没有启动

预期行为

  • 定时任务应该每1秒执行一次
  • 应该有日志输出显示API调用和数据处理过程
  • 应该发布WebSocket事件

问题分析过程

1. 初步排查 - 代码逻辑对比

对比正常工作的接口和不工作的接口实现:

正常工作的接口

// 航空器数据采集
@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);
    // ...
}

不工作的接口

// 航班进出港通知采集
@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. 配置文件结构分析

data:
  collector:
    interval: 250                    # ✅ 工作正常
    route:                          # ✅ 工作正常
      interval: 5000
    detection:                      # ✅ 工作正常
      interval: 1000
    flight-notification:            # ❌ 不工作
      interval: 1000

根本原因Spring Boot中@Value/@Scheduled表达式与@ConfigurationProperties对连字符的处理规则不同。

Spring Boot配置绑定的两套规则

@ConfigurationProperties - 支持relaxed binding宽松绑定

@ConfigurationProperties(prefix = "my.app")
public class MyProperties {
    private String firstName;  // 可以绑定 first-name
}

配置文件:

my:
  app:
    first-name: "John"    # ✅ 支持连字符会自动转换为firstName

@Value/@Scheduled - 直接属性解析,连字符限制

@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. 修改配置文件

将连字符键名改为无连字符形式:

# 修改前(不工作)
flight-notification:
  interval: 1000

# 修改后(正常工作)
flightnotification:
  interval: 1000

2. 修改代码配置引用

// 修改前
@Scheduled(fixedRateString = "${data.collector.flight-notification.interval:1000}")

// 修改后
@Scheduled(fixedRateString = "${data.collector.flightnotification.interval:1000}")

验证方法

1. 添加调试日志验证定时任务执行

@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的属性绑定机制在处理嵌套连字符属性时可能存在问题
  • 优先使用驼峰命名或无连字符命名:如flightNotificationflightnotification
  • 为配置属性提供默认值:使用${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.intervaldata.collector.route.interval
  • 可能有问题的格式data.collector.flight-notification.interval
  • 推荐格式data.collector.flightnotification.interval

@Scheduled注解机制

@Scheduled注解在Spring容器启动时进行解析

  1. Spring扫描所有带有@Scheduled的方法
  2. 解析fixedRateString中的配置属性引用
  3. 如果配置属性解析失败,该定时任务不会被注册
  4. 不会抛出异常,只是静默跳过

这就是为什么其他接口正常工作,而配置有问题的接口完全没有日志输出的原因。

状态

  • 问题已解决
  • 根本原因已确认
  • 修复方案已验证
  • 相关测试已通过

最终结果航班进出港通知定时任务现在每1秒正常执行日志输出正常WebSocket事件发布正常。