From 51e0b517e4880d3037c4f99a86642989f24ba839 Mon Sep 17 00:00:00 2001 From: Guoqs <123456@jsowell.com> Date: Thu, 18 Jun 2026 17:02:54 +0800 Subject: [PATCH] =?UTF-8?q?bugfix=20=E7=BE=BD=E4=BF=A1=E9=A2=84=E7=BA=A6?= =?UTF-8?q?=E5=85=85=E7=94=B5=E7=8B=AC=E7=AB=8B=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补齐 0xB2 协议字段,新增羽信预约 DTO、service 和接口。 羽信预约改为先确认桩端成功再落库,取消/删除前先取消桩端预约。 原预约逻辑不再兼容羽信主板,保持旧链路独立。 --- .../uniapp/customer/PersonPileController.java | 123 ++++++ .../YuxinReservationChargingCommand.java | 68 ++++ .../pile/dto/YuxinReservationChargingDTO.java | 81 ++++ .../pile/service/PileRemoteService.java | 15 +- .../pile/service/YKCPushCommandService.java | 5 + .../YuxinReservationChargingService.java | 17 + .../impl/PileReservationInfoServiceImpl.java | 150 +------ .../impl/YKCPushCommandServiceImpl.java | 137 ++++--- .../YuxinReservationChargingServiceImpl.java | 384 ++++++++++++++++++ 9 files changed, 776 insertions(+), 204 deletions(-) create mode 100644 jsowell-pile/src/main/java/com/jsowell/pile/domain/ykcCommond/YuxinReservationChargingCommand.java create mode 100644 jsowell-pile/src/main/java/com/jsowell/pile/dto/YuxinReservationChargingDTO.java create mode 100644 jsowell-pile/src/main/java/com/jsowell/pile/service/YuxinReservationChargingService.java create mode 100644 jsowell-pile/src/main/java/com/jsowell/pile/service/impl/YuxinReservationChargingServiceImpl.java diff --git a/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/PersonPileController.java b/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/PersonPileController.java index a36186559..64abb1a5c 100644 --- a/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/PersonPileController.java +++ b/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/PersonPileController.java @@ -17,6 +17,7 @@ import com.jsowell.pile.service.PileBasicInfoService; import com.jsowell.pile.service.PileFirmwareInfoService; import com.jsowell.pile.service.PileMerchantInfoService; import com.jsowell.pile.service.PileReservationInfoService; +import com.jsowell.pile.service.YuxinReservationChargingService; import com.jsowell.pile.vo.PileReservationInfoVO; import com.jsowell.pile.vo.uniapp.customer.PersonPileConnectorSumInfoVO; import com.jsowell.pile.vo.uniapp.customer.PersonPileRealTimeVO; @@ -55,6 +56,9 @@ public class PersonPileController extends BaseController { @Autowired private PileReservationInfoService pileReservationInfoService; + @Autowired + private YuxinReservationChargingService yuxinReservationChargingService; + @Autowired private PileFirmwareInfoService pileFirmwareInfoService; @@ -508,6 +512,125 @@ public class PersonPileController extends BaseController { return response; } + /** + * 羽信主板添加预约充电 + * http://localhost:8080/uniapp/personalPile/yuxin/createReserved + */ + @PostMapping("/yuxin/createReserved") + public RestApiResponse createYuxinReserved(HttpServletRequest request, @RequestBody YuxinReservationChargingDTO dto) { + RestApiResponse response = null; + try { + String memberId = getMemberIdByAuthorization(request); + dto.setMemberId(memberId); + int reservedId = yuxinReservationChargingService.createReservation(dto); + response = new RestApiResponse<>(ImmutableMap.of("reservedId", reservedId)); + } catch (BusinessException e) { + logger.error("羽信添加预约充电error, params:{}", dto, e); + response = new RestApiResponse<>(e.getCode(), e.getMessage()); + } catch (Exception e) { + logger.error("羽信添加预约充电error, params:{}", dto, e); + response = new RestApiResponse<>(ReturnCodeEnum.CODE_CREATE_RESERVED_ERROR); + } + logger.info("羽信添加预约充电params:{}, result:{}", dto, response); + return response; + } + + /** + * 羽信主板修改预约充电 + * http://localhost:8080/uniapp/personalPile/yuxin/updateReservation + */ + @PostMapping("/yuxin/updateReservation") + public RestApiResponse updateYuxinReservation(HttpServletRequest request, @RequestBody YuxinReservationChargingDTO dto) { + RestApiResponse response = null; + try { + String memberId = getMemberIdByAuthorization(request); + dto.setMemberId(memberId); + int i = yuxinReservationChargingService.updateReservation(dto); + if (i > 0) { + response = new RestApiResponse<>(); + } else { + response = new RestApiResponse<>(ReturnCodeEnum.CODE_UPDATE_RESERVED_STATUS_ERROR.getValue(), ReturnCodeEnum.CODE_UPDATE_RESERVED_STATUS_ERROR.getLabel() + ": 充电桩返回修改失败"); + } + } catch (BusinessException e) { + logger.error("羽信修改预约充电error, params:{}", dto, e); + response = new RestApiResponse<>(e.getCode(), e.getMessage()); + } catch (Exception e) { + logger.error("羽信修改预约充电error, params:{}", dto, e); + response = new RestApiResponse<>(ReturnCodeEnum.CODE_UPDATE_RESERVED_STATUS_ERROR); + } + logger.info("羽信修改预约充电params:{}, result:{}", dto, response); + return response; + } + + /** + * 羽信主板取消预约充电 + * http://localhost:8080/uniapp/personalPile/yuxin/cancelReservation + */ + @PostMapping("/yuxin/cancelReservation") + public RestApiResponse cancelYuxinReservation(HttpServletRequest request, @RequestBody YuxinReservationChargingDTO dto) { + RestApiResponse response = null; + try { + String memberId = getMemberIdByAuthorization(request); + dto.setMemberId(memberId); + yuxinReservationChargingService.cancelReservation(dto); + response = new RestApiResponse<>(); + } catch (BusinessException e) { + logger.error("羽信取消预约充电error, params:{}", dto, e); + response = new RestApiResponse<>(e.getCode(), e.getMessage()); + } catch (Exception e) { + logger.error("羽信取消预约充电error, params:{}", dto, e); + response = new RestApiResponse<>(ReturnCodeEnum.CODE_UPDATE_RESERVED_STATUS_ERROR); + } + logger.info("羽信取消预约充电params:{}, result:{}", dto, response); + return response; + } + + /** + * 羽信主板删除预约 + * http://localhost:8080/uniapp/personalPile/yuxin/deleteReservation + */ + @PostMapping("/yuxin/deleteReservation") + public RestApiResponse deleteYuxinReservation(HttpServletRequest request, @RequestBody YuxinReservationChargingDTO dto) { + RestApiResponse response = null; + try { + String memberId = getMemberIdByAuthorization(request); + dto.setMemberId(memberId); + yuxinReservationChargingService.deleteReservation(dto); + response = new RestApiResponse<>(); + } catch (BusinessException e) { + logger.error("羽信删除预约error, params:{}", dto, e); + response = new RestApiResponse<>(e.getCode(), e.getMessage()); + } catch (Exception e) { + logger.error("羽信删除预约error, params:{}", dto, e); + response = new RestApiResponse<>(ReturnCodeEnum.CODE_UPDATE_RESERVED_STATUS_ERROR); + } + logger.info("羽信删除预约params:{}, result:{}", dto, response); + return response; + } + + /** + * 羽信主板查询预约信息 + * http://localhost:8080/uniapp/personalPile/yuxin/queryReservationInfo + */ + @PostMapping("/yuxin/queryReservationInfo") + public RestApiResponse queryYuxinReservationInfo(HttpServletRequest request, @RequestBody YuxinReservationChargingDTO dto) { + RestApiResponse response = null; + try { + String memberId = getMemberIdByAuthorization(request); + dto.setMemberId(memberId); + PileReservationInfoVO vo = yuxinReservationChargingService.queryReservationInfo(dto); + response = new RestApiResponse<>(vo); + } catch (BusinessException e) { + logger.error("羽信查询预约状态error, params:{}", dto, e); + response = new RestApiResponse<>(e.getCode(), e.getMessage()); + } catch (Exception e) { + logger.error("羽信查询预约状态error, params:{}", dto, e); + response = new RestApiResponse<>(ReturnCodeEnum.CODE_QUERY_RESERVATION_STATUS_ERROR); + } + logger.info("羽信查询预约状态params:{}, result:{}", dto, JSON.toJSONString(response)); + return response; + } + /** * 保存蓝牙充电记录 * http://localhost:8080/uniapp/personalPile/saveBluetoothChargingRecord diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/domain/ykcCommond/YuxinReservationChargingCommand.java b/jsowell-pile/src/main/java/com/jsowell/pile/domain/ykcCommond/YuxinReservationChargingCommand.java new file mode 100644 index 000000000..592fcecce --- /dev/null +++ b/jsowell-pile/src/main/java/com/jsowell/pile/domain/ykcCommond/YuxinReservationChargingCommand.java @@ -0,0 +1,68 @@ +package com.jsowell.pile.domain.ykcCommond; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 羽信预约充电0xB2指令 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class YuxinReservationChargingCommand { + /** + * 交易流水号 + */ + private String transactionCode; + + /** + * 充电桩编号 + */ + private String pileSn; + + /** + * 枪号: 0-所有枪, 1-1号枪 + */ + private String connectorCode; + + /** + * 账户余额, 单位元, 保留2位小数 + */ + private BigDecimal accountBalance; + + /** + * 预约类型: 00-立即预约; 01-取消预约 + */ + private String reservationType; + + /** + * 充电策略: 0-充满为止; 1-时间控制; 2-金额控制; 3-电量控制 + */ + private Integer chargingStrategy; + + /** + * 充电参数, 根据充电策略表示电量/时间/金额 + */ + private BigDecimal chargingParam; + + /** + * 系统当前时间, CP56Time2a格式 + */ + private Date systemTime; + + /** + * 预约/定时启动时间, CP56Time2a格式 + */ + private Date reservedStartTime; + + /** + * 预约超时时间, 单位分钟 + */ + private Integer reservationTimeout; +} diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/dto/YuxinReservationChargingDTO.java b/jsowell-pile/src/main/java/com/jsowell/pile/dto/YuxinReservationChargingDTO.java new file mode 100644 index 000000000..abaa0cffd --- /dev/null +++ b/jsowell-pile/src/main/java/com/jsowell/pile/dto/YuxinReservationChargingDTO.java @@ -0,0 +1,81 @@ +package com.jsowell.pile.dto; + +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import java.math.BigDecimal; + +/** + * 羽信预约充电业务入参 + */ +@Getter +@Setter +public class YuxinReservationChargingDTO { + /** + * 会员id + */ + private String memberId; + + /** + * 预约id + */ + private String reservedId; + + /** + * 充电桩编号 + */ + private String pileSn; + + /** + * 充电桩枪口编号 + */ + private String pileConnectorCode; + + /** + * 开始时间 hh:mm:ss + */ + private String startTime; + + /** + * 结束时间 hh:mm:ss + */ + private String endTime; + + /** + * 充电策略: 0-充满为止; 1-时间控制; 2-金额控制; 3-电量控制 + */ + private Integer chargingStrategy; + + /** + * 充电参数, 默认0 + */ + private BigDecimal chargingParam; + + /** + * 预约超时时间, 单位分钟; 不传则按开始/结束时间计算 + */ + private Integer reservationTimeout; + + private Integer pageNo; + + private Integer pageSize; + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.JSON_STYLE) + .append("memberId", memberId) + .append("reservedId", reservedId) + .append("pileSn", pileSn) + .append("pileConnectorCode", pileConnectorCode) + .append("startTime", startTime) + .append("endTime", endTime) + .append("chargingStrategy", chargingStrategy) + .append("chargingParam", chargingParam) + .append("reservationTimeout", reservationTimeout) + .append("pageNo", pageNo) + .append("pageSize", pageSize) + .toString(); + } +} diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/PileRemoteService.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/PileRemoteService.java index 12afc531d..4005dd5c4 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/PileRemoteService.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/PileRemoteService.java @@ -514,8 +514,21 @@ public class PileRemoteService { * @return result: 1-成功; 0-失败 */ public String reservationCharging(ReservationChargingCommand command) { - String result = "0"; byte[] bytes = ykcPushCommandService.pushReservationChargingCommand(command); + return parseReservationChargingResponse(bytes); + } + + /** + * 羽信预约充电指令 + * @return result: 1-成功; 0-失败 + */ + public String yuxinReservationCharging(YuxinReservationChargingCommand command) { + byte[] bytes = ykcPushCommandService.pushYuxinReservationChargingCommand(command); + return parseReservationChargingResponse(bytes); + } + + private String parseReservationChargingResponse(byte[] bytes) { + String result = "0"; // 解析结果 if (Objects.isNull(bytes)) { result = "0"; diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/YKCPushCommandService.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/YKCPushCommandService.java index bb7348b73..3a5e5daa6 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/YKCPushCommandService.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/YKCPushCommandService.java @@ -86,6 +86,11 @@ public interface YKCPushCommandService { */ byte[] pushReservationChargingCommand(ReservationChargingCommand command); + /** + * 发送羽信预约充电命令 + */ + byte[] pushYuxinReservationChargingCommand(YuxinReservationChargingCommand command); + /** * 发送并充充电指令 * @param command diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/YuxinReservationChargingService.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/YuxinReservationChargingService.java new file mode 100644 index 000000000..84d23730a --- /dev/null +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/YuxinReservationChargingService.java @@ -0,0 +1,17 @@ +package com.jsowell.pile.service; + +import com.jsowell.pile.dto.YuxinReservationChargingDTO; +import com.jsowell.pile.vo.PileReservationInfoVO; + +public interface YuxinReservationChargingService { + + int createReservation(YuxinReservationChargingDTO dto); + + int updateReservation(YuxinReservationChargingDTO dto); + + void cancelReservation(YuxinReservationChargingDTO dto); + + void deleteReservation(YuxinReservationChargingDTO dto); + + PileReservationInfoVO queryReservationInfo(YuxinReservationChargingDTO dto); +} diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileReservationInfoServiceImpl.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileReservationInfoServiceImpl.java index 2826e7edf..5c7d9ccae 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileReservationInfoServiceImpl.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileReservationInfoServiceImpl.java @@ -9,14 +9,11 @@ import com.jsowell.common.constant.Constants; import com.jsowell.common.core.page.PageResponse; import com.jsowell.common.core.redis.RedisCache; import com.jsowell.common.enums.DelFlagEnum; -import com.jsowell.common.enums.ykc.PileMainboardManufacturerEnum; import com.jsowell.common.enums.ykc.ReturnCodeEnum; import com.jsowell.common.exception.BusinessException; import com.jsowell.common.util.DateUtils; -import com.jsowell.common.util.PileProgramVersionUtils; import com.jsowell.common.util.StringUtils; import com.jsowell.common.util.YKCUtils; -import com.jsowell.pile.domain.PileBasicInfo; import com.jsowell.pile.domain.PileMemberRelation; import com.jsowell.pile.domain.PileReservationInfo; import com.jsowell.pile.domain.ykcCommond.ReservationChargingCommand; @@ -312,8 +309,7 @@ public class PileReservationInfoServiceImpl implements PileReservationInfoServic @Override public int createReservation(CreateReservedDTO dto) { PileReservationInfo reservedInfo = selectByPileConnectorCode(dto.getPileConnectorCode()); - PileReservationInfo oldReservationInfo = reservedInfo == null ? null : copyReservationInfo(reservedInfo); - boolean activeBeforeUpdate = oldReservationInfo != null && StringUtils.equals(oldReservationInfo.getStatus(), Constants.ONE); + boolean activeBeforeUpdate = reservedInfo != null && StringUtils.equals(reservedInfo.getStatus(), Constants.ONE); if (reservedInfo == null) { reservedInfo = new PileReservationInfo(); reservedInfo.setCreateBy(dto.getMemberId()); @@ -348,17 +344,7 @@ public class PileReservationInfoServiceImpl implements PileReservationInfoServic reservedInfo.getStartTime(), reservedInfo.getEndTime(), reservedInfo.getId())) { throw new BusinessException(ReturnCodeEnum.CODE_UPDATE_RESERVED_STATUS_REFUSED); } - boolean yuxinMainboard = activeBeforeUpdate && isYuxinMainboard(reservedInfo.getPileSn()); - log.info("创建/覆盖预约充电下发决策, memberId:{}, activeBeforeUpdate:{}, yuxinMainboard:{}, oldReservation:{}, newReservation:{}", - dto.getMemberId(), activeBeforeUpdate, yuxinMainboard, - reservationLogText(oldReservationInfo), reservationLogText(reservedInfo)); - if (activeBeforeUpdate && yuxinMainboard) { - if (!replaceYuxinReservation(dto.getMemberId(), oldReservationInfo, reservedInfo)) { - throwReservationCommandFailed(); - } - } else { - sendReservationCommandAndAssertSuccess(reservedInfo, dto.getMemberId(), activeBeforeUpdate ? "03" : "01"); - } + sendReservationCommandAndAssertSuccess(reservedInfo, dto.getMemberId(), activeBeforeUpdate ? "03" : "01"); this.insertOrUpdateSelective(reservedInfo); return reservedInfo.getId(); } @@ -419,7 +405,6 @@ public class PileReservationInfoServiceImpl implements PileReservationInfoServic if (pileReservationInfo == null) { return 0; } - PileReservationInfo oldReservationInfo = copyReservationInfo(pileReservationInfo); String redisKey = CacheConstants.UPDATE_RESERVATION_INFO + pileReservationInfo.getPileConnectorCode(); @@ -471,17 +456,11 @@ public class PileReservationInfoServiceImpl implements PileReservationInfoServic 发送指令 */ if (sendFlag) { - boolean yuxinMainboard = isYuxinMainboard(pileReservationInfo.getPileSn()); - boolean replaceYuxinReservation = shouldReplaceYuxinReservation(operation, modifyReservationSetting, oldReservationInfo, yuxinMainboard); - log.info("修改预约充电下发决策, reservedId:{}, memberId:{}, operation:{}, sendFlag:{}, modifyReservationSetting:{}, yuxinMainboard:{}, replaceYuxinReservation:{}, oldReservation:{}, newReservation:{}", + log.info("修改预约充电下发决策, reservedId:{}, memberId:{}, operation:{}, sendFlag:{}, modifyReservationSetting:{}, pileSn:{}, pileConnectorCode:{}, status:{}, startTime:{}, endTime:{}", pileReservationInfo.getId(), dto.getMemberId(), operation, sendFlag, modifyReservationSetting, - yuxinMainboard, replaceYuxinReservation, - reservationLogText(oldReservationInfo), reservationLogText(pileReservationInfo)); - if (replaceYuxinReservation) { - sendResult = replaceYuxinReservation(dto.getMemberId(), oldReservationInfo, pileReservationInfo); - } else { - sendResult = sendReservationCommand(pileReservationInfo, dto.getMemberId(), operation); - } + pileReservationInfo.getPileSn(), pileReservationInfo.getPileConnectorCode(), pileReservationInfo.getStatus(), + pileReservationInfo.getStartTime(), pileReservationInfo.getEndTime()); + sendResult = sendReservationCommand(pileReservationInfo, dto.getMemberId(), operation); // 设置缓存1分钟, 如果更新数据库就删掉, 如果没有更新数据量缓存70秒 redisCache.setCacheObject(redisKey, JSON.toJSONString(pileReservationInfo), CacheConstants.cache_expire_time_70s); @@ -497,123 +476,6 @@ public class PileReservationInfoServiceImpl implements PileReservationInfoServic return 0; } - private boolean shouldReplaceYuxinReservation(String operation, boolean modifyReservationSetting, - PileReservationInfo oldReservationInfo, boolean yuxinMainboard) { - if (!modifyReservationSetting || !yuxinMainboard) { - return false; - } - if (StringUtils.equals(operation, "03")) { - return true; - } - return StringUtils.equals(operation, "01") - && oldReservationInfo != null - && StringUtils.equals(oldReservationInfo.getStatus(), Constants.ONE); - } - - private boolean replaceYuxinReservation(String memberId, PileReservationInfo oldReservationInfo, PileReservationInfo newReservationInfo) { - if (oldReservationInfo == null) { - log.error("羽信预约替换失败, 旧预约为空, memberId:{}, newReservation:{}", memberId, reservationLogText(newReservationInfo)); - return false; - } - log.info("羽信预约替换开始, memberId:{}, oldReservation:{}, newReservation:{}", - memberId, reservationLogText(oldReservationInfo), reservationLogText(newReservationInfo)); - boolean cancelOldResult = sendReservationCommand(oldReservationInfo, memberId, "02"); - log.info("羽信预约替换取消旧预约结果, reservedId:{}, pileConnectorCode:{}, result:{}", - oldReservationInfo.getId(), oldReservationInfo.getPileConnectorCode(), cancelOldResult); - if (!cancelOldResult) { - boolean oldReservationEnded = isReservationTimeEnded(oldReservationInfo); - if (!oldReservationEnded) { - return false; - } - log.warn("羽信预约替换取消旧预约失败, 但旧预约时间窗已过期, 继续下发新预约, memberId:{}, oldReservation:{}", - memberId, reservationLogText(oldReservationInfo)); - } - boolean createNewResult = sendReservationCommand(newReservationInfo, memberId, "01"); - log.info("羽信预约替换新预约结果, reservedId:{}, pileConnectorCode:{}, result:{}", - newReservationInfo.getId(), newReservationInfo.getPileConnectorCode(), createNewResult); - if (createNewResult) { - return true; - } - if (!cancelOldResult) { - log.error("羽信预约替换新预约失败, 旧预约取消失败但已判定过期, 不恢复旧预约, memberId:{}, oldReservation:{}, newReservation:{}", - memberId, reservationLogText(oldReservationInfo), reservationLogText(newReservationInfo)); - return false; - } - try { - boolean restoreOldResult = sendReservationCommand(oldReservationInfo, memberId, "01"); - log.info("羽信预约替换恢复旧预约结果, reservedId:{}, pileConnectorCode:{}, result:{}", - oldReservationInfo.getId(), oldReservationInfo.getPileConnectorCode(), restoreOldResult); - if (!restoreOldResult) { - log.error("羽信主板修改预约失败后恢复旧预约失败, reservedId:{}, pileConnectorCode:{}", - oldReservationInfo.getId(), oldReservationInfo.getPileConnectorCode()); - } - } catch (Exception e) { - log.error("羽信主板修改预约失败后恢复旧预约异常, reservedId:{}, pileConnectorCode:{}", - oldReservationInfo.getId(), oldReservationInfo.getPileConnectorCode(), e); - } - return false; - } - - private boolean isReservationTimeEnded(PileReservationInfo reservationInfo) { - if (reservationInfo == null || reservationInfo.getStartTime() == null || reservationInfo.getEndTime() == null) { - log.warn("羽信旧预约过期判断失败, 预约时间为空, reservation:{}", reservationLogText(reservationInfo)); - return false; - } - LocalTime now = LocalTime.now(); - LocalTime startTime = reservationInfo.getStartTime().toLocalTime(); - LocalTime endTime = reservationInfo.getEndTime().toLocalTime(); - boolean crossDay = endTime.isBefore(startTime); - boolean ended = !crossDay && !endTime.isAfter(now); - log.info("羽信旧预约过期判断, reservedId:{}, pileConnectorCode:{}, now:{}, startTime:{}, endTime:{}, crossDay:{}, ended:{}", - reservationInfo.getId(), reservationInfo.getPileConnectorCode(), now, startTime, endTime, crossDay, ended); - return ended; - } - - private boolean isYuxinMainboard(String pileSn) { - try { - PileBasicInfo pileBasicInfo = pileBasicInfoService.selectPileBasicInfoBySN(pileSn); - String programVersion = pileBasicInfo == null ? null : pileBasicInfo.getProgramVersion(); - PileMainboardManufacturerEnum manufacturer = PileProgramVersionUtils.getMainboardManufacturer(programVersion); - boolean yuxinMainboard = manufacturer == PileMainboardManufacturerEnum.YUXIN; - log.info("预约充电主板识别, pileSn:{}, programVersion:{}, manufacturer:{}, yuxinMainboard:{}", - pileSn, programVersion, manufacturer.getValue(), yuxinMainboard); - return yuxinMainboard; - } catch (Exception e) { - log.warn("判断羽信主板失败, pileSn:{}", pileSn, e); - return false; - } - } - - private String reservationLogText(PileReservationInfo reservationInfo) { - if (reservationInfo == null) { - return "null"; - } - return String.format("reservedId=%s,pileSn=%s,pileConnectorCode=%s,status=%s,reservationType=%s,verifyIdentity=%s,startTime=%s,endTime=%s", - reservationInfo.getId(), reservationInfo.getPileSn(), reservationInfo.getPileConnectorCode(), - reservationInfo.getStatus(), reservationInfo.getReservationType(), reservationInfo.getVerifyIdentity(), - reservationInfo.getStartTime(), reservationInfo.getEndTime()); - } - - private PileReservationInfo copyReservationInfo(PileReservationInfo source) { - PileReservationInfo target = new PileReservationInfo(); - target.setId(source.getId()); - target.setMemberId(source.getMemberId()); - target.setPileSn(source.getPileSn()); - target.setPileConnectorCode(source.getPileConnectorCode()); - target.setStatus(source.getStatus()); - target.setReservationType(source.getReservationType()); - target.setVerifyIdentity(source.getVerifyIdentity()); - target.setStartTime(source.getStartTime()); - target.setEndTime(source.getEndTime()); - target.setFreq(source.getFreq()); - target.setCreateBy(source.getCreateBy()); - target.setCreateTime(source.getCreateTime()); - target.setUpdateBy(source.getUpdateBy()); - target.setUpdateTime(source.getUpdateTime()); - target.setDelFlag(source.getDelFlag()); - return target; - } - /** * 个人桩停止充电 * @param dto diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/YKCPushCommandServiceImpl.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/YKCPushCommandServiceImpl.java index aa3d0e13e..ac0147843 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/YKCPushCommandServiceImpl.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/YKCPushCommandServiceImpl.java @@ -5,7 +5,6 @@ import com.google.common.primitives.Bytes; import com.jsowell.common.constant.Constants; import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode; import com.jsowell.common.enums.ykc.PileChannelEntity; -import com.jsowell.common.enums.ykc.PileMainboardManufacturerEnum; import com.jsowell.common.enums.ykc.ReturnCodeEnum; import com.jsowell.common.exception.BusinessException; import com.jsowell.common.protocol.SyncPromise; @@ -13,10 +12,8 @@ import com.jsowell.common.service.JcppService; import com.jsowell.common.util.*; import com.jsowell.common.util.Cp56Time2a.Cp56Time2aUtil; import com.jsowell.common.util.spring.SpringUtils; -import com.jsowell.pile.domain.PileBasicInfo; import com.jsowell.pile.domain.ykcCommond.*; import com.jsowell.pile.dto.SavePileMsgDTO; -import com.jsowell.pile.mapper.PileBasicInfoMapper; import com.jsowell.pile.service.*; import com.jsowell.pile.vo.web.BillingTemplateVO; import com.jsowell.pile.vo.web.PileModelInfoVO; @@ -33,8 +30,6 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; import java.math.BigDecimal; -import java.time.Duration; -import java.time.LocalDateTime; import java.time.LocalTime; import java.util.Date; import java.util.List; @@ -48,6 +43,11 @@ import java.util.concurrent.TimeUnit; @Service public class YKCPushCommandServiceImpl implements YKCPushCommandService { + private static final BigDecimal DEFAULT_YUXIN_ACCOUNT_BALANCE = new BigDecimal("999.99"); + private static final BigDecimal DEFAULT_YUXIN_CHARGING_PARAM = BigDecimal.ZERO; + private static final int DEFAULT_YUXIN_CHARGING_STRATEGY = 0; + private static final int DEFAULT_YUXIN_RESERVATION_TIMEOUT = 0; + @Autowired private PileBillingTemplateService pileBillingTemplateService; @@ -63,9 +63,6 @@ public class YKCPushCommandServiceImpl implements YKCPushCommandService { @Autowired private PileConnectorInfoService pileConnectorInfoService; - @Autowired - private PileBasicInfoMapper pileBasicInfoMapper; - @DubboReference private JcppService jcppService; @@ -768,20 +765,41 @@ public class YKCPushCommandServiceImpl implements YKCPushCommandService { @Override public byte[] pushReservationChargingCommand(ReservationChargingCommand command) { String pileSn = command.getPileSn(); - YKCFrameTypeCode frameTypeCode = getReservationChargingFrameType(pileSn); - byte[] msg = frameTypeCode == YKCFrameTypeCode.YUXIN_RESERVATION_CHARGING_SETUP_CODE - ? buildYuxinReservationChargingMsg(command) - : buildReservationChargingMsg(command); + byte[] msg = buildReservationChargingMsg(command); byte[] response; try { - response = this.supplySend(msg, pileSn, frameTypeCode); + response = this.supplySend(msg, pileSn, YKCFrameTypeCode.RESERVATION_CHARGING_SETUP_CODE); } catch (Exception e) { log.error("发送消息异常", e); response = null; } log.info("【=====平台下发指令=====】: 预约充电指令, 帧类型:{}, 交易流水号:{}, 桩编号:{}, 枪口号:{}, 操作(01-启动; 02-取消; 03-修改):{}, 身份验证:{}, 开始时间:{}, 结束时间:{}, 启动金额:{}", - YKCUtils.frameType2Str(frameTypeCode.getBytes()), command.getTransactionCode(), pileSn, command.getConnectorCode(), command.getOperation(), command.getVerifyIdentity(), DateUtils.formatDateTime(command.getReservedStartTime()), DateUtils.formatDateTime(command.getReservedEndTime()), command.getAmount()); + YKCUtils.frameType2Str(YKCFrameTypeCode.RESERVATION_CHARGING_SETUP_CODE.getBytes()), + command.getTransactionCode(), pileSn, command.getConnectorCode(), command.getOperation(), + command.getVerifyIdentity(), DateUtils.formatDateTime(command.getReservedStartTime()), + DateUtils.formatDateTime(command.getReservedEndTime()), command.getAmount()); + return response; + } + + @Override + public byte[] pushYuxinReservationChargingCommand(YuxinReservationChargingCommand command) { + String pileSn = command.getPileSn(); + byte[] msg = buildYuxinReservationChargingMsg(command); + byte[] response; + try { + response = this.supplySend(msg, pileSn, YKCFrameTypeCode.YUXIN_RESERVATION_CHARGING_SETUP_CODE); + } catch (Exception e) { + log.error("发送羽信预约充电消息异常", e); + response = null; + } + + log.info("【=====平台下发指令=====】: 羽信预约充电指令, 帧类型:{}, 交易流水号:{}, 桩编号:{}, 枪口号:{}, 账户余额:{}, 预约类型(00-立即预约;01-取消预约):{}, 充电策略:{}, 充电参数:{}, 系统时间:{}, 预约启动时间:{}, 预约超时时间:{}", + YKCUtils.frameType2Str(YKCFrameTypeCode.YUXIN_RESERVATION_CHARGING_SETUP_CODE.getBytes()), + command.getTransactionCode(), pileSn, command.getConnectorCode(), getYuxinAccountBalance(command), + command.getReservationType(), getYuxinChargingStrategy(command), getYuxinChargingParam(command), + DateUtils.formatDateTime(getYuxinSystemTime(command)), + DateUtils.formatDateTime(command.getReservedStartTime()), getYuxinReservationTimeout(command)); return response; } @@ -841,53 +859,51 @@ public class YKCPushCommandServiceImpl implements YKCPushCommandService { reservedStartTimeByteArr, reservedEndTimeByteArr, amountByteArr); } - private byte[] buildYuxinReservationChargingMsg(ReservationChargingCommand command) { + private byte[] buildYuxinReservationChargingMsg(YuxinReservationChargingCommand command) { + BigDecimal accountBalance = getYuxinAccountBalance(command); + int chargingStrategy = getYuxinChargingStrategy(command); + BigDecimal chargingParam = getYuxinChargingParam(command); + Date systemTime = getYuxinSystemTime(command); + int reservationTimeout = getYuxinReservationTimeout(command); byte[] transactionCodeArr = BytesUtil.ensureLengthPrependZero(BytesUtil.str2Bcd(command.getTransactionCode()), 16); byte[] pileSnByteArr = BytesUtil.ensureLengthPrependZero(BytesUtil.str2Bcd(command.getPileSn()), 7); byte[] connectorCodeByteArr = BytesUtil.intToBytes(parseDecimalByte(command.getConnectorCode(), "枪口号"), 1); - byte[] accountBalanceByteArr = YKCUtils.getPriceByte(getAmount(command).toString(), 2); - byte[] reservationTypeByteArr = BytesUtil.intToBytes(StringUtils.equals(command.getOperation(), "02") ? 0x01 : 0x00, 1); - byte[] chargingStrategyByteArr = BytesUtil.intToBytes(0x00, 1); - byte[] chargingParamByteArr = YKCUtils.getPriceByte(BigDecimal.ZERO.toString(), 2); - byte[] systemTimeByteArr = Cp56Time2aUtil.date2Hbyte(new Date()); - Date reservedStartDate = getYuxinReservedStartDate(command); - byte[] reservedStartTimeByteArr = Cp56Time2aUtil.date2Hbyte(reservedStartDate); - byte[] reservationTimeoutByteArr = BytesUtil.intToBytes(getYuxinReservationTimeout(command), 1); - log.info("羽信预约充电组包, pileSn:{}, connectorCode:{}, operation:{}, reservationType:{}, reservedStartTime:{}, reservedStartDate:{}, reservationTimeout:{}", - command.getPileSn(), command.getConnectorCode(), command.getOperation(), - BytesUtil.bin2HexStr(reservationTypeByteArr), command.getReservedStartTime(), - DateUtils.formatDateTime(reservedStartDate), getYuxinReservationTimeout(command)); + byte[] accountBalanceByteArr = YKCUtils.getPriceByte(accountBalance.toString(), 2); + byte[] reservationTypeByteArr = BytesUtil.intToBytes(parseByteCode(command.getReservationType(), "预约类型"), 1); + byte[] chargingStrategyByteArr = BytesUtil.intToBytes(chargingStrategy, 1); + byte[] chargingParamByteArr = YKCUtils.getPriceByte(chargingParam.toString(), 2); + byte[] systemTimeByteArr = Cp56Time2aUtil.date2Hbyte(systemTime); + byte[] reservedStartTimeByteArr = Cp56Time2aUtil.date2Hbyte(command.getReservedStartTime()); + byte[] reservationTimeoutByteArr = BytesUtil.intToBytes(reservationTimeout, 1); + log.info("羽信预约充电组包, pileSn:{}, connectorCode:{}, accountBalance:{}, reservationType:{}, chargingStrategy:{}, chargingParam:{}, systemTime:{}, reservedStartTime:{}, reservationTimeout:{}", + command.getPileSn(), command.getConnectorCode(), accountBalance, + BytesUtil.bin2HexStr(reservationTypeByteArr), chargingStrategy, chargingParam, + DateUtils.formatDateTime(systemTime), + DateUtils.formatDateTime(command.getReservedStartTime()), reservationTimeout); return Bytes.concat(transactionCodeArr, pileSnByteArr, connectorCodeByteArr, accountBalanceByteArr, reservationTypeByteArr, chargingStrategyByteArr, chargingParamByteArr, systemTimeByteArr, reservedStartTimeByteArr, reservationTimeoutByteArr); } - private BigDecimal getAmount(ReservationChargingCommand command) { - return command.getAmount() == null ? BigDecimal.ZERO : command.getAmount(); + private BigDecimal getYuxinAccountBalance(YuxinReservationChargingCommand command) { + return DEFAULT_YUXIN_ACCOUNT_BALANCE; } - private Date getYuxinReservedStartDate(ReservationChargingCommand command) { - LocalDateTime now = LocalDateTime.now(); - LocalTime reservedStartTime = command.getReservedStartTime(); - LocalDateTime reservedStartDateTime = now.toLocalDate().atTime(reservedStartTime); - if (StringUtils.equals(command.getOperation(), "02")) { - return DateUtils.localDateTime2Date(reservedStartDateTime); - } - if (!reservedStartDateTime.isAfter(now)) { - reservedStartDateTime = reservedStartDateTime.plusDays(1); - } - return DateUtils.localDateTime2Date(reservedStartDateTime); + private int getYuxinChargingStrategy(YuxinReservationChargingCommand command) { + return command.getChargingStrategy() == null ? DEFAULT_YUXIN_CHARGING_STRATEGY : command.getChargingStrategy(); } - private int getYuxinReservationTimeout(ReservationChargingCommand command) { - LocalTime reservedStartTime = command.getReservedStartTime(); - LocalTime reservedEndTime = command.getReservedEndTime(); - long timeout = Duration.between(reservedStartTime, reservedEndTime).toMinutes(); - if (timeout <= 0) { - timeout += TimeUnit.DAYS.toMinutes(1); - } - return (int) Math.min(timeout, 0xFF); + private BigDecimal getYuxinChargingParam(YuxinReservationChargingCommand command) { + return command.getChargingParam() == null ? DEFAULT_YUXIN_CHARGING_PARAM : command.getChargingParam(); + } + + private Date getYuxinSystemTime(YuxinReservationChargingCommand command) { + return command.getSystemTime() == null ? new Date() : command.getSystemTime(); + } + + private int getYuxinReservationTimeout(YuxinReservationChargingCommand command) { + return command.getReservationTimeout() == null ? DEFAULT_YUXIN_RESERVATION_TIMEOUT : command.getReservationTimeout(); } private int parseDecimalByte(String value, String fieldName) { @@ -898,18 +914,21 @@ public class YKCPushCommandServiceImpl implements YKCPushCommandService { return result; } - private YKCFrameTypeCode getReservationChargingFrameType(String pileSn) { - try { - PileBasicInfo pileBasicInfo = pileBasicInfoMapper.selectPileBasicInfoBySn(pileSn); - String programVersion = pileBasicInfo == null ? null : pileBasicInfo.getProgramVersion(); - PileMainboardManufacturerEnum manufacturer = PileProgramVersionUtils.getMainboardManufacturer(programVersion); - if (manufacturer == PileMainboardManufacturerEnum.YUXIN) { - return YKCFrameTypeCode.YUXIN_RESERVATION_CHARGING_SETUP_CODE; - } - } catch (Exception e) { - log.warn("查询主板厂家失败, 使用默认预约充电帧类型, pileSn:{}", pileSn, e); + private int parseByteCode(String value, String fieldName) { + if (StringUtils.isBlank(value)) { + throw new IllegalArgumentException(fieldName + "不能为空"); } - return YKCFrameTypeCode.RESERVATION_CHARGING_SETUP_CODE; + String normalized = value.trim(); + int result; + if (normalized.startsWith("0x") || normalized.startsWith("0X")) { + result = Integer.parseInt(normalized.substring(2), 16); + } else { + result = Integer.parseInt(normalized); + } + if (result < 0 || result > 0xFF) { + throw new IllegalArgumentException(fieldName + "超出1字节范围:" + value); + } + return result; } } diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/YuxinReservationChargingServiceImpl.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/YuxinReservationChargingServiceImpl.java new file mode 100644 index 000000000..b4bfca329 --- /dev/null +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/YuxinReservationChargingServiceImpl.java @@ -0,0 +1,384 @@ +package com.jsowell.pile.service.impl; + +import com.jsowell.common.constant.Constants; +import com.jsowell.common.enums.DelFlagEnum; +import com.jsowell.common.enums.ykc.PileMainboardManufacturerEnum; +import com.jsowell.common.enums.ykc.ReturnCodeEnum; +import com.jsowell.common.exception.BusinessException; +import com.jsowell.common.util.DateUtils; +import com.jsowell.common.util.PileProgramVersionUtils; +import com.jsowell.common.util.StringUtils; +import com.jsowell.common.util.YKCUtils; +import com.jsowell.pile.domain.PileBasicInfo; +import com.jsowell.pile.domain.PileReservationInfo; +import com.jsowell.pile.domain.ykcCommond.YuxinReservationChargingCommand; +import com.jsowell.pile.dto.YuxinReservationChargingDTO; +import com.jsowell.pile.mapper.PileReservationInfoMapper; +import com.jsowell.pile.service.PileBasicInfoService; +import com.jsowell.pile.service.PileRemoteService; +import com.jsowell.pile.service.YuxinReservationChargingService; +import com.jsowell.pile.vo.PileReservationInfoVO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.sql.Time; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Service +public class YuxinReservationChargingServiceImpl implements YuxinReservationChargingService { + + private static final String YUXIN_RESERVATION_TYPE_CREATE = "00"; + private static final String YUXIN_RESERVATION_TYPE_CANCEL = "01"; + private static final BigDecimal YUXIN_ACCOUNT_BALANCE = new BigDecimal("999.99"); + private static final BigDecimal DEFAULT_CHARGING_PARAM = BigDecimal.ZERO; + private static final int DEFAULT_CHARGING_STRATEGY = 0; + + @Resource + private PileReservationInfoMapper pileReservationInfoMapper; + + @Autowired + private PileRemoteService pileRemoteService; + + @Autowired + private PileBasicInfoService pileBasicInfoService; + + @Override + public int createReservation(YuxinReservationChargingDTO dto) { + validateCreateOrUpdateParam(dto); + normalizePileInfo(dto); + assertYuxinMainboard(dto.getPileSn()); + + PileReservationInfo currentReservation = pileReservationInfoMapper.selectByPileConnectorCode(dto.getPileConnectorCode()); + PileReservationInfo oldReservation = currentReservation == null ? null : copyReservationInfo(currentReservation); + boolean oldActive = oldReservation != null && StringUtils.equals(oldReservation.getStatus(), Constants.ONE); + + PileReservationInfo reservationInfo = currentReservation == null ? new PileReservationInfo() : currentReservation; + fillReservationInfo(reservationInfo, dto); + if (currentReservation == null) { + reservationInfo.setCreateBy(dto.getMemberId()); + } else { + reservationInfo.setUpdateBy(dto.getMemberId()); + } + + if (!isTimeSlotAvailable(dto.getMemberId(), dto.getPileSn(), reservationInfo.getStartTime(), + reservationInfo.getEndTime(), reservationInfo.getId())) { + throw new BusinessException(ReturnCodeEnum.CODE_UPDATE_RESERVED_STATUS_REFUSED); + } + if (oldActive) { + cancelOldReservationBeforeCreate(dto, oldReservation); + } + sendYuxinReservationCommandAndAssertSuccess(dto, reservationInfo, YUXIN_RESERVATION_TYPE_CREATE); + pileReservationInfoMapper.insertOrUpdateSelective(reservationInfo); + return reservationInfo.getId(); + } + + @Override + public int updateReservation(YuxinReservationChargingDTO dto) { + if (StringUtils.isBlank(dto.getReservedId())) { + throw new BusinessException(ReturnCodeEnum.CODE_PARAM_NOT_NULL_ERROR); + } + PileReservationInfo reservationInfo = pileReservationInfoMapper.selectByPrimaryKey(Integer.valueOf(dto.getReservedId())); + if (reservationInfo == null) { + return 0; + } + dto.setPileSn(StringUtils.defaultIfBlank(dto.getPileSn(), reservationInfo.getPileSn())); + dto.setPileConnectorCode(StringUtils.defaultIfBlank(dto.getPileConnectorCode(), reservationInfo.getPileConnectorCode())); + dto.setStartTime(StringUtils.defaultIfBlank(dto.getStartTime(), reservationInfo.getStartTime().toString())); + dto.setEndTime(StringUtils.defaultIfBlank(dto.getEndTime(), reservationInfo.getEndTime().toString())); + assertYuxinMainboard(dto.getPileSn()); + + PileReservationInfo oldReservation = copyReservationInfo(reservationInfo); + fillReservationInfo(reservationInfo, dto); + reservationInfo.setUpdateBy(dto.getMemberId()); + + if (!isTimeSlotAvailable(dto.getMemberId(), dto.getPileSn(), reservationInfo.getStartTime(), + reservationInfo.getEndTime(), reservationInfo.getId())) { + throw new BusinessException(ReturnCodeEnum.CODE_UPDATE_RESERVED_STATUS_REFUSED); + } + if (StringUtils.equals(oldReservation.getStatus(), Constants.ONE)) { + cancelOldReservationBeforeCreate(dto, oldReservation); + } + sendYuxinReservationCommandAndAssertSuccess(dto, reservationInfo, YUXIN_RESERVATION_TYPE_CREATE); + return pileReservationInfoMapper.insertOrUpdateSelective(reservationInfo); + } + + @Override + public void cancelReservation(YuxinReservationChargingDTO dto) { + if (StringUtils.isBlank(dto.getReservedId())) { + throw new BusinessException(ReturnCodeEnum.CODE_PARAM_NOT_NULL_ERROR); + } + PileReservationInfo reservationInfo = pileReservationInfoMapper.selectByPrimaryKey(Integer.valueOf(dto.getReservedId())); + if (reservationInfo == null) { + return; + } + assertYuxinMainboard(reservationInfo.getPileSn()); + boolean cancelSuccess = true; + if (StringUtils.equals(reservationInfo.getStatus(), Constants.ONE)) { + cancelSuccess = sendYuxinReservationCommand(dto, reservationInfo, YUXIN_RESERVATION_TYPE_CANCEL); + } + if (!cancelSuccess && !isReservationTimeEnded(reservationInfo)) { + throwReservationCommandFailed(); + } + if (!cancelSuccess) { + log.warn("羽信取消预约失败, 但预约时间窗已过期, 本地停用预约, memberId:{}, reservation:{}", + dto.getMemberId(), reservationLogText(reservationInfo)); + } + reservationInfo.setStatus(Constants.ZERO); + reservationInfo.setUpdateBy(dto.getMemberId()); + pileReservationInfoMapper.updateByPrimaryKeySelective(reservationInfo); + } + + @Override + public void deleteReservation(YuxinReservationChargingDTO dto) { + if (StringUtils.isBlank(dto.getReservedId())) { + throw new BusinessException(ReturnCodeEnum.CODE_PARAM_NOT_NULL_ERROR); + } + PileReservationInfo reservationInfo = pileReservationInfoMapper.selectByPrimaryKey(Integer.valueOf(dto.getReservedId())); + if (Objects.isNull(reservationInfo)) { + return; + } + assertYuxinMainboard(reservationInfo.getPileSn()); + boolean cancelSuccess = true; + if (StringUtils.equals(reservationInfo.getStatus(), Constants.ONE)) { + cancelSuccess = sendYuxinReservationCommand(dto, reservationInfo, YUXIN_RESERVATION_TYPE_CANCEL); + } + if (!cancelSuccess && !isReservationTimeEnded(reservationInfo)) { + throwReservationCommandFailed(); + } + reservationInfo.setStatus(Constants.ZERO); + reservationInfo.setDelFlag(DelFlagEnum.DELETE.getValue()); + reservationInfo.setUpdateBy(dto.getMemberId()); + pileReservationInfoMapper.updateByPrimaryKey(reservationInfo); + } + + @Override + public PileReservationInfoVO queryReservationInfo(YuxinReservationChargingDTO dto) { + if (StringUtils.isBlank(dto.getPileConnectorCode())) { + throw new BusinessException(ReturnCodeEnum.CODE_PARAM_NOT_NULL_ERROR); + } + PileReservationInfo reservationInfo = pileReservationInfoMapper.selectByPileConnectorCode(dto.getPileConnectorCode()); + if (reservationInfo == null) { + return null; + } + return PileReservationInfoVO.builder() + .reservedId(reservationInfo.getId() + "") + .pileSn(reservationInfo.getPileSn()) + .pileConnectorCode(reservationInfo.getPileConnectorCode()) + .startTime(reservationInfo.getStartTime().toString()) + .endTime(reservationInfo.getEndTime().toString()) + .verifyIdentity(reservationInfo.getVerifyIdentity()) + .status(reservationInfo.getStatus()) + .build(); + } + + private void fillReservationInfo(PileReservationInfo reservationInfo, YuxinReservationChargingDTO dto) { + reservationInfo.setMemberId(dto.getMemberId()); + reservationInfo.setPileSn(dto.getPileSn()); + reservationInfo.setPileConnectorCode(dto.getPileConnectorCode()); + reservationInfo.setStatus(Constants.ONE); + reservationInfo.setDelFlag(DelFlagEnum.NORMAL.getValue()); + reservationInfo.setReservationType("recurring"); + reservationInfo.setFreq("daily"); + reservationInfo.setVerifyIdentity(Constants.ZERO); + reservationInfo.setStartTime(parseSqlTime(dto.getStartTime())); + reservationInfo.setEndTime(parseSqlTime(dto.getEndTime())); + if (reservationInfo.getId() == null) { + reservationInfo.setCreateTime(DateUtils.getNowDate()); + } else { + reservationInfo.setUpdateTime(DateUtils.getNowDate()); + } + } + + private void cancelOldReservationBeforeCreate(YuxinReservationChargingDTO dto, PileReservationInfo oldReservation) { + boolean cancelSuccess = sendYuxinReservationCommand(dto, oldReservation, YUXIN_RESERVATION_TYPE_CANCEL); + if (!cancelSuccess && !isReservationTimeEnded(oldReservation)) { + throwReservationCommandFailed(); + } + if (!cancelSuccess) { + log.warn("羽信预约新时间前取消旧预约失败, 但旧预约时间窗已过期, 继续预约新时间, memberId:{}, oldReservation:{}", + dto.getMemberId(), reservationLogText(oldReservation)); + } + } + + private void sendYuxinReservationCommandAndAssertSuccess(YuxinReservationChargingDTO dto, + PileReservationInfo reservationInfo, + String yuxinReservationType) { + if (!sendYuxinReservationCommand(dto, reservationInfo, yuxinReservationType)) { + throwReservationCommandFailed(); + } + } + + private boolean sendYuxinReservationCommand(YuxinReservationChargingDTO dto, + PileReservationInfo reservationInfo, + String yuxinReservationType) { + YuxinReservationChargingCommand command = buildYuxinReservationChargingCommand(dto, reservationInfo, yuxinReservationType); + log.info("羽信预约充电指令下发, memberId:{}, reservedId:{}, reservationType:{}, reservation:{}", + dto.getMemberId(), reservationInfo.getId(), yuxinReservationType, reservationLogText(reservationInfo)); + String result = pileRemoteService.yuxinReservationCharging(command); + boolean success = StringUtils.equals(result, Constants.ONE); + log.info("羽信预约充电指令返回, memberId:{}, reservedId:{}, reservationType:{}, result:{}, success:{}", + dto.getMemberId(), reservationInfo.getId(), yuxinReservationType, result, success); + return success; + } + + private YuxinReservationChargingCommand buildYuxinReservationChargingCommand(YuxinReservationChargingDTO dto, + PileReservationInfo reservationInfo, + String yuxinReservationType) { + return YuxinReservationChargingCommand.builder() + .transactionCode(Constants.ILLEGAL_TRANSACTION_CODE) + .pileSn(reservationInfo.getPileSn()) + .connectorCode(getConnectorCode(reservationInfo)) + .accountBalance(YUXIN_ACCOUNT_BALANCE) + .reservationType(yuxinReservationType) + .chargingStrategy(dto.getChargingStrategy() == null ? DEFAULT_CHARGING_STRATEGY : dto.getChargingStrategy()) + .chargingParam(dto.getChargingParam() == null ? DEFAULT_CHARGING_PARAM : dto.getChargingParam()) + .systemTime(new Date()) + .reservedStartTime(getReservedStartDate(reservationInfo.getStartTime().toLocalTime(), yuxinReservationType)) + .reservationTimeout(getReservationTimeout(dto, reservationInfo, yuxinReservationType)) + .build(); + } + + private Date getReservedStartDate(LocalTime reservedStartTime, String yuxinReservationType) { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime reservedStartDateTime = now.toLocalDate().atTime(reservedStartTime); + if (StringUtils.equals(yuxinReservationType, YUXIN_RESERVATION_TYPE_CREATE) + && !reservedStartDateTime.isAfter(now)) { + reservedStartDateTime = reservedStartDateTime.plusDays(1); + } + return DateUtils.localDateTime2Date(reservedStartDateTime); + } + + private int getReservationTimeout(YuxinReservationChargingDTO dto, + PileReservationInfo reservationInfo, + String yuxinReservationType) { + if (StringUtils.equals(yuxinReservationType, YUXIN_RESERVATION_TYPE_CANCEL)) { + return 0; + } + if (dto.getReservationTimeout() != null) { + return Math.min(Math.max(dto.getReservationTimeout(), 0), 0xFF); + } + LocalTime startTime = reservationInfo.getStartTime().toLocalTime(); + LocalTime endTime = reservationInfo.getEndTime().toLocalTime(); + long timeout = Duration.between(startTime, endTime).toMinutes(); + if (timeout <= 0) { + timeout += TimeUnit.DAYS.toMinutes(1); + } + return (int) Math.min(timeout, 0xFF); + } + + private String getConnectorCode(PileReservationInfo reservationInfo) { + return reservationInfo.getPileConnectorCode().replace(reservationInfo.getPileSn(), ""); + } + + private void normalizePileInfo(YuxinReservationChargingDTO dto) { + if (StringUtils.isBlank(dto.getPileConnectorCode())) { + throw new BusinessException(ReturnCodeEnum.CODE_PARAM_NOT_NULL_ERROR); + } + if (StringUtils.isBlank(dto.getPileSn())) { + dto.setPileSn(YKCUtils.getPileSn(dto.getPileConnectorCode())); + } + } + + private void validateCreateOrUpdateParam(YuxinReservationChargingDTO dto) { + if (StringUtils.isBlank(dto.getMemberId()) + || StringUtils.isBlank(dto.getPileConnectorCode()) + || StringUtils.isBlank(dto.getStartTime()) + || StringUtils.isBlank(dto.getEndTime())) { + throw new BusinessException(ReturnCodeEnum.CODE_PARAM_NOT_NULL_ERROR); + } + } + + private Time parseSqlTime(String time) { + if (time.length() == 5) { + return Time.valueOf(time + ":00"); + } + return Time.valueOf(time); + } + + private boolean isTimeSlotAvailable(String memberId, String pileSn, Time startTime, Time endTime, Integer reservationId) { + List reservations = pileReservationInfoMapper.findByMemberIdAndPileSnAndStatus(memberId, pileSn, Constants.ONE); + LocalTime newStartTime = startTime.toLocalTime(); + LocalTime newEndTime = endTime.toLocalTime(); + for (PileReservationInfo res : reservations) { + if (reservationId != null && res.getId().equals(reservationId)) { + continue; + } + LocalTime existingStartTime = res.getStartTime().toLocalTime(); + LocalTime existingEndTime = res.getEndTime().toLocalTime(); + if (newStartTime.isBefore(existingEndTime) && newEndTime.isAfter(existingStartTime)) { + return false; + } + } + return true; + } + + private boolean isReservationTimeEnded(PileReservationInfo reservationInfo) { + if (reservationInfo == null || reservationInfo.getStartTime() == null || reservationInfo.getEndTime() == null) { + return false; + } + LocalTime now = LocalTime.now(); + LocalTime startTime = reservationInfo.getStartTime().toLocalTime(); + LocalTime endTime = reservationInfo.getEndTime().toLocalTime(); + boolean crossDay = endTime.isBefore(startTime); + boolean ended = crossDay + ? !now.isBefore(endTime) && now.isBefore(startTime) + : !endTime.isAfter(now); + log.info("羽信预约过期判断, reservedId:{}, pileConnectorCode:{}, now:{}, startTime:{}, endTime:{}, crossDay:{}, ended:{}", + reservationInfo.getId(), reservationInfo.getPileConnectorCode(), now, startTime, endTime, crossDay, ended); + return ended; + } + + private void assertYuxinMainboard(String pileSn) { + PileBasicInfo pileBasicInfo = pileBasicInfoService.selectPileBasicInfoBySN(pileSn); + String programVersion = pileBasicInfo == null ? null : pileBasicInfo.getProgramVersion(); + PileMainboardManufacturerEnum manufacturer = PileProgramVersionUtils.getMainboardManufacturer(programVersion); + if (manufacturer != PileMainboardManufacturerEnum.YUXIN) { + throw new BusinessException(ReturnCodeEnum.CODE_FAILED.getValue(), "当前充电桩不是羽信主板"); + } + } + + private void throwReservationCommandFailed() { + throw new BusinessException(ReturnCodeEnum.CODE_UPDATE_RESERVED_STATUS_ERROR.getValue(), + ReturnCodeEnum.CODE_UPDATE_RESERVED_STATUS_ERROR.getLabel() + ": 充电桩返回修改失败"); + } + + private String reservationLogText(PileReservationInfo reservationInfo) { + if (reservationInfo == null) { + return "null"; + } + return String.format("reservedId=%s,pileSn=%s,pileConnectorCode=%s,status=%s,reservationType=%s,verifyIdentity=%s,startTime=%s,endTime=%s", + reservationInfo.getId(), reservationInfo.getPileSn(), reservationInfo.getPileConnectorCode(), + reservationInfo.getStatus(), reservationInfo.getReservationType(), reservationInfo.getVerifyIdentity(), + reservationInfo.getStartTime(), reservationInfo.getEndTime()); + } + + private PileReservationInfo copyReservationInfo(PileReservationInfo source) { + PileReservationInfo target = new PileReservationInfo(); + target.setId(source.getId()); + target.setMemberId(source.getMemberId()); + target.setPileSn(source.getPileSn()); + target.setPileConnectorCode(source.getPileConnectorCode()); + target.setStatus(source.getStatus()); + target.setReservationType(source.getReservationType()); + target.setVerifyIdentity(source.getVerifyIdentity()); + target.setStartTime(source.getStartTime()); + target.setEndTime(source.getEndTime()); + target.setFreq(source.getFreq()); + target.setCreateBy(source.getCreateBy()); + target.setCreateTime(source.getCreateTime()); + target.setUpdateBy(source.getUpdateBy()); + target.setUpdateTime(source.getUpdateTime()); + target.setDelFlag(source.getDelFlag()); + return target; + } +}