diff --git a/jsowell-admin/src/main/java/com/jsowell/api/uniapp/business/BusinessStationInfoController.java b/jsowell-admin/src/main/java/com/jsowell/api/uniapp/business/BusinessStationInfoController.java index f78eca58a..787852b98 100644 --- a/jsowell-admin/src/main/java/com/jsowell/api/uniapp/business/BusinessStationInfoController.java +++ b/jsowell-admin/src/main/java/com/jsowell/api/uniapp/business/BusinessStationInfoController.java @@ -5,10 +5,12 @@ import com.google.common.collect.ImmutableMap; import com.jsowell.common.core.controller.BaseController; import com.jsowell.common.exception.BusinessException; import com.jsowell.common.response.RestApiResponse; +import com.jsowell.pile.dto.business.QueryStationWithConnectorStatusDTO; import com.jsowell.pile.dto.business.StationBusinessAnalyzeInfoDTO; import com.jsowell.pile.dto.business.StationStatisticsInfoDTO; import com.jsowell.pile.service.OrderBasicInfoService; import com.jsowell.pile.service.PileStationInfoService; +import com.jsowell.common.core.page.PageResponse; import com.jsowell.pile.vo.uniapp.business.StationBusinessAnalyzeInfoVO; import com.jsowell.pile.vo.uniapp.business.StationStatisticsInfosVO; import org.springframework.beans.factory.annotation.Autowired; @@ -151,4 +153,24 @@ public class BusinessStationInfoController extends BaseController { return response; } + /** + * 运营端小程序查询站点及充电枪状态 + * 支持通过站点ID或站点名称查询,不传则查询全部站点 + * @param dto 查询条件 + * @return 站点列表及充电枪状态统计 + */ + @PostMapping("/queryStationWithConnectorStatus") + public RestApiResponse queryStationWithConnectorStatus(@RequestBody QueryStationWithConnectorStatusDTO dto) { + RestApiResponse response = null; + try { + PageResponse pageResponse = pileStationInfoService.queryStationWithConnectorStatus(dto); + response = new RestApiResponse<>(pageResponse); + } catch (Exception e) { + logger.error("查询站点及充电枪状态 error", e); + response = new RestApiResponse<>(e); + } + logger.info("查询站点及充电枪状态 params:{}, result:{}", JSONObject.toJSONString(dto), response); + return response; + } + } diff --git a/jsowell-common/src/main/java/com/jsowell/common/constant/CacheConstants.java b/jsowell-common/src/main/java/com/jsowell/common/constant/CacheConstants.java index efc2c6011..94b2e2495 100644 --- a/jsowell-common/src/main/java/com/jsowell/common/constant/CacheConstants.java +++ b/jsowell-common/src/main/java/com/jsowell/common/constant/CacheConstants.java @@ -335,6 +335,11 @@ public class CacheConstants { */ public static final String PILE_HARDWARE_FAULT = "pile_hardware_fault:"; + /** + * 充电枪状态变更时间戳(用于计算状态持续时长) + */ + public static final String PILE_CONNECTOR_STATUS_CHANGE_TIME = "pile_connector_status_change_time:"; + /** * 充电桩sn生成 key */ diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/dto/business/QueryStationWithConnectorStatusDTO.java b/jsowell-pile/src/main/java/com/jsowell/pile/dto/business/QueryStationWithConnectorStatusDTO.java new file mode 100644 index 000000000..558ce2d2c --- /dev/null +++ b/jsowell-pile/src/main/java/com/jsowell/pile/dto/business/QueryStationWithConnectorStatusDTO.java @@ -0,0 +1,39 @@ +package com.jsowell.pile.dto.business; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 运营端小程序查询站点及充电枪状态DTO + * + * @author Auto + * @Date 2024/12/19 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class QueryStationWithConnectorStatusDTO { + /** + * 站点id(可选) + */ + private String stationId; + + /** + * 站点名称(可选,支持模糊查询) + */ + private String stationName; + + /** + * 页码(默认1) + */ + private int pageNum; + + /** + * 每页条数(默认10) + */ + private int pageSize; +} + diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/PileStationInfoService.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/PileStationInfoService.java index 4f006ed75..505686b68 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/PileStationInfoService.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/PileStationInfoService.java @@ -6,6 +6,7 @@ import com.jsowell.pile.dto.FastCreateStationDTO; import com.jsowell.pile.dto.QueryStationDTO; import com.jsowell.pile.dto.QueryStationInfoDTO; import com.jsowell.pile.dto.amap.GetStationInfoDTO; +import com.jsowell.pile.dto.business.QueryStationWithConnectorStatusDTO; import com.jsowell.pile.dto.business.StationBusinessAnalyzeInfoDTO; import com.jsowell.pile.dto.business.StationStatisticsInfoDTO; import com.jsowell.pile.dto.lutongyunting.BindParkingPlatformDTO; @@ -14,6 +15,7 @@ import com.jsowell.pile.vo.base.ThirdPartyStationInfoVO; import com.jsowell.pile.vo.ningxiajiaotou.NXJTStationInfoVO; import com.jsowell.pile.vo.uniapp.business.StationBusinessAnalyzeInfoVO; import com.jsowell.pile.vo.uniapp.business.StationStatisticsInfosVO; +import com.jsowell.pile.vo.uniapp.business.StationWithConnectorStatusVO; import com.jsowell.pile.vo.web.PileStationVO; import com.jsowell.pile.vo.web.StationSelectVO; @@ -215,4 +217,11 @@ public interface PileStationInfoService { * @return */ PageResponse queryUserFrequentedStation(QueryStationDTO dto); + + /** + * 运营端小程序查询站点及充电枪状态 + * @param dto 查询条件 + * @return 站点列表及充电枪状态统计 + */ + PageResponse queryStationWithConnectorStatus(QueryStationWithConnectorStatusDTO dto); } diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileConnectorInfoServiceImpl.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileConnectorInfoServiceImpl.java index 945554c51..b40d07787 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileConnectorInfoServiceImpl.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileConnectorInfoServiceImpl.java @@ -773,6 +773,23 @@ public class PileConnectorInfoServiceImpl implements PileConnectorInfoService { deleteRedisByPileSnOrPileConnectorCode(pileSn, pileConnectorCode); redisCache.setCacheObject(redisKey, status, CacheConstants.cache_expire_time_6m); + // 记录状态变更时间戳(用于计算状态持续时长) + // 只记录需要计算时长的状态:故障、离线、占用(未充电)、挂起(预约锁定) + if (StringUtils.equals(status, PileConnectorDataBaseStatusEnum.FAULT.getValue()) + || StringUtils.equals(status, PileConnectorDataBaseStatusEnum.OFF_NETWORK.getValue()) + || StringUtils.equals(status, PileConnectorDataBaseStatusEnum.OCCUPIED_NOT_CHARGED.getValue()) + || StringUtils.equals(status, PileConnectorDataBaseStatusEnum.OCCUPIED_RESERVED_LOCK.getValue())) { + String statusChangeTimeKey = CacheConstants.PILE_CONNECTOR_STATUS_CHANGE_TIME + pileConnectorCode; + String now = DateUtils.getDateTime(); + redisCache.setCacheObject(statusChangeTimeKey, now, CacheConstants.cache_expire_time_30d); + } else { + // 状态变更为空闲或充电中时,删除状态变更时间戳 + String statusChangeTimeKey = CacheConstants.PILE_CONNECTOR_STATUS_CHANGE_TIME + pileConnectorCode; + if (redisCache.hasKey(statusChangeTimeKey)) { + redisCache.deleteObject(statusChangeTimeKey); + } + } + // 异步放缓存 CompletableFuture.runAsync(() -> statusChange(pileConnectorCode), executor); } diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileStationInfoServiceImpl.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileStationInfoServiceImpl.java index 445a7540c..9d8e00693 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileStationInfoServiceImpl.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileStationInfoServiceImpl.java @@ -16,11 +16,13 @@ import com.jsowell.common.enums.ykc.ReturnCodeEnum; import com.jsowell.common.exception.BusinessException; import com.jsowell.common.util.*; import com.jsowell.common.util.ip.AddressUtils; +import com.jsowell.common.core.domain.ykc.RealTimeMonitorData; import com.jsowell.pile.domain.*; import com.jsowell.pile.dto.FastCreateStationDTO; import com.jsowell.pile.dto.QueryStationDTO; import com.jsowell.pile.dto.QueryStationInfoDTO; import com.jsowell.pile.dto.amap.GetStationInfoDTO; +import com.jsowell.pile.dto.business.QueryStationWithConnectorStatusDTO; import com.jsowell.pile.dto.business.StationBusinessAnalyzeInfoDTO; import com.jsowell.pile.dto.business.StationStatisticsInfoDTO; import com.jsowell.pile.dto.lutongyunting.BindParkingPlatformDTO; @@ -35,6 +37,7 @@ import com.jsowell.pile.vo.uniapp.business.BusinessOrderDetailInfoVO; import com.jsowell.pile.vo.uniapp.business.StationBusinessAnalyzeInfoVO; import com.jsowell.pile.vo.uniapp.business.StationOrderQuantityInfoVO; import com.jsowell.pile.vo.uniapp.business.StationStatisticsInfosVO; +import com.jsowell.pile.vo.uniapp.business.StationWithConnectorStatusVO; import com.jsowell.pile.vo.uniapp.customer.CurrentTimePriceDetails; import com.jsowell.pile.vo.web.BillingTemplateVO; import com.jsowell.pile.vo.web.PileStationVO; @@ -1623,4 +1626,165 @@ public class PileStationInfoServiceImpl implements PileStationInfoService { } return uniAppQueryStationInfoListV2(dto); } + + /** + * 运营端小程序查询站点及充电枪状态 + * @param dto 查询条件 + * @return 站点列表及充电枪状态统计 + */ + @Override + public PageResponse queryStationWithConnectorStatus(QueryStationWithConnectorStatusDTO dto) { + // 获取当前登录账号的运营商权限 + List merchantInfoVOList = UserUtils.getMerchantInfoVOList(); + if (CollectionUtils.isEmpty(merchantInfoVOList)) { + throw new BusinessException(ReturnCodeEnum.CODE_SELECT_INFO_IS_NULL); + } + List merchantIds = merchantInfoVOList.stream() + .map(MerchantInfoVO::getMerchantId) + .collect(Collectors.toList()); + + // 根据权限查询站点列表 + List allStations = getStationInfosByMerchantIds(merchantIds); + if (CollectionUtils.isEmpty(allStations)) { + throw new BusinessException(ReturnCodeEnum.CODE_SELECT_INFO_IS_NULL); + } + + // 根据站点ID或站点名称过滤 + List filteredStations = allStations; + if (StringUtils.isNotBlank(dto.getStationId())) { + filteredStations = allStations.stream() + .filter(s -> String.valueOf(s.getId()).equals(dto.getStationId())) + .collect(Collectors.toList()); + } else if (StringUtils.isNotBlank(dto.getStationName())) { + filteredStations = allStations.stream() + .filter(s -> s.getStationName() != null && s.getStationName().contains(dto.getStationName())) + .collect(Collectors.toList()); + } + + if (CollectionUtils.isEmpty(filteredStations)) { + throw new BusinessException(ReturnCodeEnum.CODE_SELECT_INFO_IS_NULL); + } + + // 分页处理 + int pageNum = dto.getPageNum() == 0 ? 1 : dto.getPageNum(); + int pageSize = dto.getPageSize() == 0 ? 10 : dto.getPageSize(); + PageHelper.startPage(pageNum, pageSize); + + // 构建返回结果 + List resultList = new ArrayList<>(); + for (PileStationInfo station : filteredStations) { + String stationId = String.valueOf(station.getId()); + + // 查询站点下的所有充电枪 + List connectorList = pileConnectorInfoService.getUniAppConnectorList(Long.parseLong(stationId)); + + // 统计各状态数量并计算时长 + int totalNum = connectorList.size(); + int chargingNum = 0; // 充电中 + int freeNum = 0; // 空闲 + int occupiedNum = 0; // 占用(未充电) + int hangingNum = 0; // 挂起(预约锁定) + int offlineNum = 0; // 离线 + int faultNum = 0; // 故障 + + String now = DateUtils.getDateTime(); + for (ConnectorInfoVO connector : connectorList) { + String status = connector.getConnectorStatus(); + String pileConnectorCode = connector.getPileConnectorCode(); + + // 计算时长 + if (StringUtils.equals(PileConnectorDataBaseStatusEnum.OCCUPIED_CHARGING.getValue(), status)) { + // 充电中:从实时数据中获取剩余时间 + chargingNum++; + OrderBasicInfo order = orderBasicInfoService.queryChargingByPileConnectorCode(pileConnectorCode); + if (order != null) { + List chargingRealTimeData = orderBasicInfoService.getChargingRealTimeData(order.getTransactionCode()); + if (CollectionUtils.isNotEmpty(chargingRealTimeData)) { + RealTimeMonitorData realTimeMonitorData = chargingRealTimeData.get(0); + connector.setTimeRemaining(realTimeMonitorData.getTimeRemaining()); // 剩余时长(分钟) + } + } + } else if (StringUtils.equals(PileConnectorDataBaseStatusEnum.FAULT.getValue(), status)) { + // 故障:从Redis中获取状态变更时间戳,计算从状态变更到现在的时间 + faultNum++; + String statusChangeTimeKey = CacheConstants.PILE_CONNECTOR_STATUS_CHANGE_TIME + pileConnectorCode; + String statusChangeTimeStr = redisCache.getCacheObject(statusChangeTimeKey); + if (StringUtils.isNotBlank(statusChangeTimeStr)) { + try { + long duration = DateUtils.intervalTime(statusChangeTimeStr, now); + connector.setDuration(String.valueOf(duration)); + } catch (Exception e) { + log.warn("计算状态持续时长失败, pileConnectorCode:{}, statusChangeTimeStr:{}", pileConnectorCode, statusChangeTimeStr, e); + } + } + } else if (StringUtils.equals(PileConnectorDataBaseStatusEnum.OFF_NETWORK.getValue(), status)) { + // 离线:从Redis中获取状态变更时间戳,计算从状态变更到现在的时间 + offlineNum++; + String statusChangeTimeKey = CacheConstants.PILE_CONNECTOR_STATUS_CHANGE_TIME + pileConnectorCode; + String statusChangeTimeStr = redisCache.getCacheObject(statusChangeTimeKey); + if (StringUtils.isNotBlank(statusChangeTimeStr)) { + try { + long duration = DateUtils.intervalTime(statusChangeTimeStr, now); + connector.setDuration(String.valueOf(duration)); + } catch (Exception e) { + log.warn("计算状态持续时长失败, pileConnectorCode:{}, statusChangeTimeStr:{}", pileConnectorCode, statusChangeTimeStr, e); + } + } + } else if (StringUtils.equals(PileConnectorDataBaseStatusEnum.OCCUPIED_NOT_CHARGED.getValue(), status)) { + // 占用(未充电):从Redis中获取状态变更时间戳,计算从状态变更到现在的时间 + occupiedNum++; + String statusChangeTimeKey = CacheConstants.PILE_CONNECTOR_STATUS_CHANGE_TIME + pileConnectorCode; + String statusChangeTimeStr = redisCache.getCacheObject(statusChangeTimeKey); + if (StringUtils.isNotBlank(statusChangeTimeStr)) { + try { + long duration = DateUtils.intervalTime(statusChangeTimeStr, now); + connector.setDuration(String.valueOf(duration)); + } catch (Exception e) { + log.warn("计算状态持续时长失败, pileConnectorCode:{}, statusChangeTimeStr:{}", pileConnectorCode, statusChangeTimeStr, e); + } + } + } else if (StringUtils.equals(PileConnectorDataBaseStatusEnum.OCCUPIED_RESERVED_LOCK.getValue(), status)) { + // 挂起(预约锁定):从Redis中获取状态变更时间戳,计算从状态变更到现在的时间 + hangingNum++; + String statusChangeTimeKey = CacheConstants.PILE_CONNECTOR_STATUS_CHANGE_TIME + pileConnectorCode; + String statusChangeTimeStr = redisCache.getCacheObject(statusChangeTimeKey); + if (StringUtils.isNotBlank(statusChangeTimeStr)) { + try { + long duration = DateUtils.intervalTime(statusChangeTimeStr, now); + connector.setDuration(String.valueOf(duration)); + } catch (Exception e) { + log.warn("计算状态持续时长失败, pileConnectorCode:{}, statusChangeTimeStr:{}", pileConnectorCode, statusChangeTimeStr, e); + } + } + } else if (StringUtils.equals(PileConnectorDataBaseStatusEnum.FREE.getValue(), status)) { + freeNum++; + } + } + + StationWithConnectorStatusVO vo = StationWithConnectorStatusVO.builder() + .stationId(stationId) + .stationName(station.getStationName()) + .connectorNum(totalNum) + .chargingConnectorNum(chargingNum) + .freeConnectorNum(freeNum) + .occupiedConnectorNum(occupiedNum) + .hangingConnectorNum(hangingNum) + .offlineConnectorNum(offlineNum) + .faultConnectorNum(faultNum) + .connectorList(connectorList) + .build(); + resultList.add(vo); + } + + + // 构建分页响应 + PageInfo pageInfo = new PageInfo<>(resultList); + return PageResponse.builder() + .pageNum(pageNum) + .pageSize(pageSize) + .total(pageInfo.getTotal()) + .pages(pageInfo.getPages()) + .list(resultList) + .build(); + } } diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/vo/base/ConnectorInfoVO.java b/jsowell-pile/src/main/java/com/jsowell/pile/vo/base/ConnectorInfoVO.java index 5e4fa27ef..ae1fc5692 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/vo/base/ConnectorInfoVO.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/vo/base/ConnectorInfoVO.java @@ -66,4 +66,18 @@ public class ConnectorInfoVO { */ private String ratedVoltage; + /** + * 状态更新时间(用于计算时长) + */ + private String updateTime; + + /** + * 剩余时长(充电中状态,单位:分钟) + */ + private String timeRemaining; + + /** + * 状态持续时长(故障/离线/占用/挂起状态,单位:分钟) + */ + private String duration; } diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/vo/uniapp/business/StationWithConnectorStatusVO.java b/jsowell-pile/src/main/java/com/jsowell/pile/vo/uniapp/business/StationWithConnectorStatusVO.java new file mode 100644 index 000000000..0815a8fa3 --- /dev/null +++ b/jsowell-pile/src/main/java/com/jsowell/pile/vo/uniapp/business/StationWithConnectorStatusVO.java @@ -0,0 +1,72 @@ +package com.jsowell.pile.vo.uniapp.business; + +import com.jsowell.pile.vo.base.ConnectorInfoVO; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 运营端小程序站点及充电枪状态VO + * + * @author Auto + * @Date 2024/12/19 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class StationWithConnectorStatusVO { + /** + * 站点id + */ + private String stationId; + + /** + * 站点名称 + */ + private String stationName; + + /** + * 总枪口数量 + */ + private Integer connectorNum; + + /** + * 充电中枪口数量 + */ + private Integer chargingConnectorNum; + + /** + * 空闲枪口数量 + */ + private Integer freeConnectorNum; + + /** + * 挂起枪口数量 + */ + private Integer hangingConnectorNum; + + /** + * 占用中枪口数量 + */ + private Integer occupiedConnectorNum; + + /** + * 离线枪口数量 + */ + private Integer offlineConnectorNum; + + /** + * 故障枪口数量 + */ + private Integer faultConnectorNum; + + /** + * 充电枪列表 + */ + private List connectorList; +} +