创建充电(订单)记录的5种场景


创建充电(订单)记录的5种场景

HChargeRecsManager.createChargeRecs 方法被调用。

createChargeRecs 接口共有 5个调用入口,对应 4种业务场景。以下是详细梳理:

HChargeRecsManager.createChargeRecs()
│
├─ 1️⃣ APP启动充电命令 → NotificationStartChargeCommand4DbListener
│                        ↓
│                   HChargeOrderServiceImpl.createChargeOrder4StartCharge()
│
├─ 2️⃣ 启充结果-成功(新增) → NotificationStartChargeResultListener
│                           ↓
│                      NotificationStartChargeResultHandler.chargingOrder() 
│                      [记录不存在时新增]
│
├─ 3️⃣ 启充结果-失败(新增) → NotificationStartChargeResultListener
│                           ↓
│                      NotificationStartChargeResultHandler.handleStartChargeFail()
│                      [记录不存在时新增]
│
├─ 4️⃣ 充电订单完成(新增) → NotificationChargeOrderInfoListener
│                          ↓
│                     NotificationChargeOrderInfoHandler.chargeOrderInfoResult()
│                     [记录不存在时新增]
│
└─ 5️⃣ 历史数据迁移      → ReceiveChargeOrderInfoListener
                          ↓
                     NotificationChargeOrderInfoHandler.doCreateChargeOrder4Migrate()

1️⃣ APP启动充电命令创建

调用链路:

Kafka Topic: GEP_SUEZ_SERVICE_START_CHARGE_ORDER (启动充电命令)
    ↓
NotificationStartChargeCommand4DbListener.onMessage() [第42行]
    ↓
HChargeOrderServiceImpl.createChargeOrder4StartCharge() [第68行]
    ↓
hChargeRecsManager.createChargeRecs(entity)

核心代码: HChargeOrderServiceImpl.java:68

@Override
public void createChargeOrder4StartCharge(CreateChargeOrderRequest request) {
    // 1. 构建实体
    HChargeRecsEntity entity = buildChargeRecsEntity(request);
    
    // 2. 设置状态为"启动中"
    entity.setStatus(hChargeRecsStateService.startCharge());
    
    // 3. 生成平台订单号
    entity.setOrderNo(idService.defaultOrderNo());
    
    // 4. 创建充电记录
    boolean insertResult = hChargeRecsManager.createChargeRecs(entity);
}

触发条件:

  • APP下发启动充电指令时
  • Kafka消息主题: GEP_SUEZ_SERVICE_START_CHARGE_ORDER

特点:

  • 主动创建: 在启充命令下发时就创建记录
  • 初始状态: 状态为"启动中" (RECS_STARTING)
  • 仅APP模式: 只处理APP充电场景

2️⃣ 启充结果-成功时新增

调用链路:

Kafka Topic: GEP_DEVICE4H_NOTIFY_START_CHARGE_RESULT (启充结果上报)
    ↓
NotificationStartChargeResultListener.onMessage() [第35行]
    ↓
NotificationStartChargeResultHandler.handle()
    ↓
NotificationStartChargeResultHandler.chargingOrder() [第190行]
    ↓
hChargeRecsManager.createChargeRecs(newEntity) [记录不存在时]

核心代码: NotificationStartChargeResultHandler.java:190

private void chargingOrder(StartChargeResultModel model, EquipmentOwnerInfo equipmentOwnerInfo) {
    // 1. 查询是否已存在记录
    Optional<HChargeRecsEntity> hChargeRecsOpt = 
        hChargeRecsManager.getLatestHChargeRecsByStartChargeSeq(
            model.getDeviceSN(), model.getProviderNo(), model.getBizOrderId(), null);
    
    if (hChargeRecsOpt.isPresent()) {
        // 存在则更新
        // ... update logic
    } else {
        // 不存在:新增
        newEntity.setOrderNo(idService.defaultOrderNo());
        newEntity.setStatus(hChargeRecsStateService.startSuccess());
        
        // 补充用户信息
        if (StringUtils.isBlank(newEntity.getChannelId())) {
            newEntity.setChannelId(SourceTypeKeyEnum.getChannelIdBySourceTypeKey(
                equipmentOwnerInfo.getSourceTypeKey()));
        }
        if (StringUtils.isBlank(newEntity.getUserId())) {
            newEntity.setUserId(equipmentOwnerInfo.getUserId());
        }
        
        // 创建充电记录
        boolean createResult = hChargeRecsManager.createChargeRecs(newEntity);
    }
}

触发条件:

  • IoT设备上报启充成功结果
  • Kafka消息主题: GEP_DEVICE4H_NOTIFY_START_CHARGE_RESULT
  • 数据库中不存在该充电流水号记录时

特点:

  • 被动创建: 等待IoT设备上报结果后创建
  • 成功状态: 状态为"启动成功" (RECS_CHARGING)
  • 多模式支持: APP、刷卡、即插即充、关闭鉴权
  • 先查后写: 避免重复创建

3️⃣ 启充结果-失败时新增

调用链路:

Kafka Topic: GEP_DEVICE4H_NOTIFY_START_CHARGE_RESULT (启充结果上报)
    ↓
NotificationStartChargeResultListener.onMessage() [第35行]
    ↓
NotificationStartChargeResultHandler.handle()
    ↓
NotificationStartChargeResultHandler.handleStartChargeFail() [第320行]
    ↓
hChargeRecsManager.createChargeRecs(newEntity) [记录不存在时]

核心代码: NotificationStartChargeResultHandler.java:320

private void handleStartChargeFail(StartChargeResultModel model, EquipmentOwnerInfo equipmentOwnerInfo) {
    // 1. 构建失败实体
    HChargeRecsEntity newEntity = buildHChargeRecsEntityFromStartChargeResultFail(model, equipmentOwnerInfo);
    
    // 2. 查询是否已存在
    Optional<HChargeRecsEntity> hChargeRecsOpt = 
        hChargeRecsManager.getLatestHChargeRecsByStartChargeSeq(...);
    
    if (hChargeRecsOpt.isPresent()) {
        // 存在则更新状态为"已关闭"
        // ... update logic
    } else {
        // 不存在:新增
        newEntity.setOrderNo(idService.defaultOrderNo());
        newEntity.setStatus(hChargeRecsStateService.closeOrder());  // 已关闭状态
        
        // 补充用户信息
        if (StringUtils.isBlank(newEntity.getUserId())) {
            newEntity.setUserId(equipmentOwnerInfo.getUserId());
        }
        if (StringUtils.isBlank(newEntity.getChannelId())) {
            newEntity.setChannelId(SourceTypeKeyEnum.getChannelIdBySourceTypeKey(
                equipmentOwnerInfo.getSourceTypeKey()));
        }
        
        // 创建充电记录
        boolean createResult = hChargeRecsManager.createChargeRecs(newEntity);
    }
}

触发条件:

  • IoT设备上报启充失败结果
  • Kafka消息主题: GEP_DEVICE4H_NOTIFY_START_CHARGE_RESULT
  • 数据库中不存在该充电流水号记录时

特点:

  • 失败记录: 记录启充失败的订单
  • 关闭状态: 状态为"已关闭" (RECS_CLOSED)
  • 包含失败原因: 记录失败原因字段
  • 兜底机制: 即使失败也保留记录便于追溯

4️⃣ 充电订单完成时新增

调用链路:

Kafka Topic: GEP_DEVICE4H_NOTIFY_CHARGE_ORDER_INFO (充电订单完成上报)
    ↓
NotificationChargeOrderInfoListener.onMessage() [第35行]
    ↓
NotificationChargeOrderInfoHandler.handle()
    ↓
NotificationChargeOrderInfoHandler.chargeOrderInfoResult() [第165行]
    ↓
hChargeRecsManager.createChargeRecs(newOne) [记录不存在时]

核心代码: NotificationChargeOrderInfoHandler.java:165

private void chargeOrderInfoResult(ChargeOrderInfoModel model, EquipmentOwnerInfo equipmentOwnerInfo) {
    // 1. 构建订单实体
    HChargeRecsEntity newOne = buildHChargeRecsEntity(model, equipmentOwnerInfo);
    
    // 2. 查询是否已存在
    Optional<HChargeRecsEntity> hChargeRecsEntityOption = 
        hChargeRecsManager.getLatestHChargeRecsByStartChargeSeq(
            model.getDeviceSN(), model.getProviderNo(), model.getBizOrderId(), null);
    
    if (hChargeRecsEntityOption.isPresent()) {
        // 存在则更新(补全结束时间、电量等)
        // ... update logic
    } else {
        // 不存在:新增
        newOne.setOrderNo(idService.defaultOrderNo());
        newOne.setRemark(ORDER_COMPLETE_REMARK);  // "订单完成"
        newOne.setStatus(hChargeRecsStateService.completeOrder());  // 已完成状态
        
        // 补充用户信息
        if (StringUtils.isBlank(newOne.getUserId())) {
            newOne.setUserId(equipmentOwnerInfo.getUserId());
        }
        if (StringUtils.isBlank(newOne.getChannelId())) {
            newOne.setChannelId(SourceTypeKeyEnum.getChannelIdBySourceTypeKey(
                equipmentOwnerInfo.getSourceTypeKey()));
        }
        
        // 创建充电记录
        boolean createResult = hChargeRecsManager.createChargeRecs(newOne);
    }
}

触发条件:

  • IoT设备上报充电订单完成事件
  • Kafka消息主题: GEP_DEVICE4H_NOTIFY_CHARGE_ORDER_INFO
  • 数据库中不存在该充电流水号记录时(异常情况下的兜底)

特点:

  • 完成状态: 状态为"已完成" (RECS_COMPLETE)
  • 完整数据: 包含开始时间、结束时间、电量等完整信息
  • 异常兜底: 正常情况下应该先有启充记录,这里是兜底逻辑
  • 刷卡特殊处理: 刷卡充电会设置卡号和桩主标识

5️⃣ 历史数据迁移创建

调用链路:

Kafka Topic: HCHARGE_ORDER (历史订单同步)
    ↓
ReceiveChargeOrderInfoListener.onMessage() [第51行]
    ↓
NotificationChargeOrderInfoHandler.handleOrderFromMigrate()
    ↓
NotificationChargeOrderInfoHandler.doCreateChargeOrder4Migrate() [第296行]
    ↓
hChargeRecsManager.createChargeRecs(newOne)

核心代码: NotificationChargeOrderInfoHandler.java:296

private void doCreateChargeOrder4Migrate(MigrateChargeOrderInfoDTO chargeOrderInfoDTO) {
    // 1. 构建迁移实体
    HChargeRecsEntity newOne = buildHChargeRecsEntity(chargeOrderInfoDTO);
    
    try {
        // 2. 直接创建(不检查是否存在,由上层控制)
        boolean createChargeRecsResult = hChargeRecsManager.createChargeRecs(newOne);
        log.info("createChargeRecs result={},equipmentId={},startChargeSeq={}",
                createChargeRecsResult, newOne.getEquipmentId(), newOne.getStartChargeSeq());
    } catch (Exception e) {
        log.error("createChargeOrder4Migrate exception, newEntity={}", JSON.toJSONString(newOne), e);
    }
}

上层调用逻辑: NotificationChargeOrderInfoHandler.java:270-290

// 非强制更新:只创建不存在的
private void handleOrderWithoutForceFromMigrate(List<MigrateChargeOrderInfoDTO> chargeOrderInfoDTOList) {
    chargeOrderInfoDTOList.forEach(chargeOrderInfo -> {
        Optional<HChargeRecsEntity> hChargeRecsEntityOption =
            hChargeRecsManager.getLatestHChargeRecsByStartChargeSeq(
                chargeOrderInfo.getEquipmentId(), null,
                chargeOrderInfo.getStartChargeSeq(), chargeOrderInfo.getStatus());
        
        // 不存在才创建
        if (!hChargeRecsEntityOption.isPresent()) {
            doCreateChargeOrder4Migrate(chargeOrderInfo);
        }
    });
}

// 强制更新:覆盖已存在的
private void handleOrderWithForceFromMigrate(List<MigrateChargeOrderInfoDTO> chargeOrderInfoDTOList) {
    chargeOrderInfoDTOList.forEach(chargeOrderInfo -> {
        Optional<HChargeRecsEntity> hChargeRecsEntityOption = ...;
        
        if (hChargeRecsEntityOption.isPresent()) {
            // 存在则更新
            doUpdateChargeOrder4Migrate(hChargeRecsEntityOption.get(), chargeOrderInfo);
        } else {
            // 不存在则创建
            doCreateChargeOrder4Migrate(chargeOrderInfo);
        }
    });
}

触发条件:

  • 从其他系统迁移历史充电订单数据
  • Kafka消息主题: HCHARGE_ORDER
  • 消息模型: SyncChargeOrderInfoModel

特点:

  • 批量迁移: 支持批量导入历史数据
  • 灵活策略: 支持强制更新和非强制更新两种模式
  • 完整字段: 包含用户昵称、手机号、头像等完整信息
  • 异常隔离: 单条失败不影响其他数据迁移

对比总结表

方式触发场景Kafka Topic订单状态适用充电模式是否先查后写典型场景
1. APP启动命令APP下发启充指令GEP_SUEZ_SERVICE_START_CHARGE_ORDER启动中仅APP❌ 直接创建正常APP充电流程
2. 启充成功IoT上报启充成功GEP_DEVICE4H_NOTIFY_START_CHARGE_RESULT充电中全部模式✅ 先查后写非APP模式或APP兜底
3. 启充失败IoT上报启充失败GEP_DEVICE4H_NOTIFY_START_CHARGE_RESULT已关闭全部模式✅ 先查后写记录失败订单
4. 订单完成IoT上报订单完成GEP_DEVICE4H_NOTIFY_CHARGE_ORDER_INFO已完成全部模式✅ 先查后写异常兜底场景
5. 数据迁移历史数据同步HCHARGE_ORDER原状态全部模式⚠️ 上层控制系统迁移/数据导入

关键设计要点

1. 幂等性保障

  • 方式2/3/4都采用先查后写策略,通过 startChargeSeq(充电流水号)判断是否存在
  • 避免重复创建同一笔订单

2. 状态流转

启动中(1) → 充电中(2) → 已完成(3)
         ↘ 已关闭(4)

3. 数据来源差异

  • 方式1: 从APP请求参数获取用户信息
  • 方式2/3/4: 从缓存 equipmentOwnerAndSharerCacheService 补充用户信息
  • 方式5: 从迁移数据直接获取完整信息

4. 订单号生成

所有方式都通过 idService.defaultOrderNo() 生成平台订单号,确保唯一性

5. ID生成策略

HChargeRecsManagerImpl 内部使用雪花算法生成主键ID:

if (Objects.isNull(entity.getId())) {
    entity.setId(Long.valueOf(idService.defaultIdWithSnowFlake()));
}

这套设计实现了充电订单从启充→充电→完成的全生命周期管理,同时支持异常兜底和历史数据迁移,保证了数据的完整性和一致性。

SpringBoot
JAVA-技能点
知识点