ChargingReceptionServiceImpl.java 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769
  1. package com.zsElectric.boot.charging.service.impl;
  2. import cn.hutool.core.util.ObjUtil;
  3. import cn.hutool.core.util.ObjectUtil;
  4. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  5. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  6. import com.fasterxml.jackson.databind.JsonNode;
  7. import com.fasterxml.jackson.databind.ObjectMapper;
  8. import com.zsElectric.boot.business.mapper.*;
  9. import com.zsElectric.boot.business.model.entity.ChargeOrderInfo;
  10. import com.zsElectric.boot.business.model.entity.PolicyFee;
  11. import com.zsElectric.boot.business.model.entity.UserAccount;
  12. import com.zsElectric.boot.business.model.entity.UserFirm;
  13. import com.zsElectric.boot.business.service.ChargeOrderInfoService;
  14. import com.zsElectric.boot.charging.entity.*;
  15. import com.zsElectric.boot.charging.mapper.ThirdPartyChargeStatusMapper;
  16. import com.zsElectric.boot.charging.mapper.ThirdPartyConnectorInfoMapper;
  17. import com.zsElectric.boot.charging.mapper.ThirdPartyPolicyInfoMapper;
  18. import com.zsElectric.boot.charging.service.ChargingBusinessService;
  19. import com.zsElectric.boot.charging.service.ChargingReceptionService;
  20. import com.zsElectric.boot.charging.vo.ChargeResponseVO;
  21. import com.zsElectric.boot.charging.vo.QueryStationStatusVO;
  22. import com.zsElectric.boot.common.constant.ConnectivityConstants;
  23. import com.zsElectric.boot.common.constant.SystemConstants;
  24. import com.zsElectric.boot.common.util.DateUtils;
  25. import com.zsElectric.boot.common.util.electric.ChargingUtil;
  26. import com.zsElectric.boot.common.util.electric.RequestParmsEntity;
  27. import com.zsElectric.boot.common.util.electric.ResponseParmsEntity;
  28. import com.zsElectric.boot.core.exception.BusinessException;
  29. import com.zsElectric.boot.system.mapper.DictItemMapper;
  30. import com.zsElectric.boot.system.model.entity.DictItem;
  31. import lombok.RequiredArgsConstructor;
  32. import lombok.extern.slf4j.Slf4j;
  33. import org.redisson.api.RLock;
  34. import org.redisson.api.RedissonClient;
  35. import org.springframework.stereotype.Service;
  36. import org.springframework.util.CollectionUtils;
  37. import java.math.BigDecimal;
  38. import java.math.RoundingMode;
  39. import java.time.LocalDateTime;
  40. import java.time.format.DateTimeFormatter;
  41. import java.util.HashMap;
  42. import java.util.List;
  43. import java.util.Map;
  44. import java.util.Objects;
  45. import java.util.concurrent.TimeUnit;
  46. import java.util.function.Consumer;
  47. import static com.zsElectric.boot.common.constant.ConnectivityConstants.FAIL_REASON_NONE;
  48. import static com.zsElectric.boot.common.constant.ConnectivityConstants.STATUS_OK;
  49. import static com.zsElectric.boot.common.util.HmacMD5Util.genSign;
  50. import static com.zsElectric.boot.common.util.HmacMD5Util.verify;
  51. /**
  52. * 第三方充电推送接收服务实现
  53. *
  54. * @author system
  55. * @since 2025-12-11
  56. */
  57. @Slf4j
  58. @Service
  59. @RequiredArgsConstructor
  60. public class ChargingReceptionServiceImpl implements ChargingReceptionService {
  61. private final ChargingUtil chargingUtil;
  62. private final ThirdPartyConnectorInfoMapper connectorInfoMapper;
  63. private final ThirdPartyChargeStatusMapper chargeStatusMapper;
  64. private final ObjectMapper objectMapper;
  65. private final ChargeOrderInfoService chargeOrderInfoService;
  66. private final RedissonClient redissonClient;
  67. private final UserAccountMapper userAccountMapper;
  68. private final UserFirmMapper userFirmMapper;
  69. private final PolicyFeeMapper policyFeeMapper;
  70. private final DictItemMapper dictItemMapper;
  71. private final ThirdPartyEquipmentInfoMapper thirdPartyEquipmentInfoMapper;
  72. private final ChargingBusinessService chargingBusinessService;
  73. private final ThirdPartyStationInfoMapper thirdPartyStationInfoMapper;
  74. private final ThirdPartyPolicyInfoMapper thirdPartyPolicyInfoMapper;
  75. /**
  76. * 熔断检查锁前缀
  77. */
  78. private static final String BREAK_CHECK_LOCK_KEY = "charging:break:check:";
  79. /**
  80. * 锁等待时间(秒)
  81. */
  82. private static final long LOCK_WAIT_TIME = 3;
  83. /**
  84. * 锁持有时间(秒)
  85. */
  86. private static final long LOCK_LEASE_TIME = 10;
  87. private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
  88. // ==================== 接口实现 ====================
  89. @Override
  90. public ResponseParmsEntity chargeResponse(RequestParmsEntity requestDTO) {
  91. log.info("接收推送启动充电结果请求参数:{}", requestDTO);
  92. return processStartChargeResultRequest(requestDTO);
  93. }
  94. @Override
  95. public ResponseParmsEntity chargeStatusResponse(RequestParmsEntity requestDTO) {
  96. log.info("接收推送充电状态请求参数:{}", requestDTO);
  97. return processChargeStatusRequest(requestDTO);
  98. }
  99. @Override
  100. public ResponseParmsEntity stopChargeResponse(RequestParmsEntity requestDTO) {
  101. log.info("接收推送停止充电结果请求参数:{}", requestDTO);
  102. return processStopChargeResultRequest(requestDTO);
  103. }
  104. @Override
  105. public ResponseParmsEntity chargeOrderResponse(RequestParmsEntity requestDTO) throws Exception {
  106. log.info("接收推送充电订单信息请求参数:{}", requestDTO);
  107. return processChargeRequest(requestDTO, jsonNode -> {
  108. log.debug("充电订单信息 - StartChargeSeq: {}", getTextValue(jsonNode, "StartChargeSeq"));
  109. });
  110. }
  111. @Override
  112. public ResponseParmsEntity stationStatus(RequestParmsEntity requestDTO) {
  113. log.info("接收设备状态变化推送请求参数:{}", requestDTO);
  114. return processStationStatusRequest(requestDTO);
  115. }
  116. // ==================== 公共处理方法 ====================
  117. /**
  118. * 通用充电请求处理模板
  119. */
  120. private ResponseParmsEntity processChargeRequest(RequestParmsEntity requestDTO, Consumer<JsonNode> businessHandler) {
  121. try {
  122. JsonNode jsonNode = verifyAndDecrypt(requestDTO);
  123. //查询订单
  124. String startChargeSeq = getTextValue(jsonNode, "StartChargeSeq");
  125. String stopReason = getTextValue(jsonNode, "StopReason");
  126. String endTime = getTextValue(jsonNode, "EndTime");
  127. String startTime = getTextValue(jsonNode, "StartTime");
  128. String totalPower = getTextValue(jsonNode, "TotalPower");
  129. String totalElecMoney = getTextValue(jsonNode, "TotalElecMoney");
  130. String totalMoney = getTextValue(jsonNode, "TotalMoney");
  131. String totalSeviceMoney = getTextValue(jsonNode, "TotalSeviceMoney");
  132. String connectorID = getTextValue(jsonNode, "ConnectorID");
  133. ChargeOrderInfo chargeOrderInfo = chargeOrderInfoService.getOne(new LambdaQueryWrapper<ChargeOrderInfo>()
  134. .eq(ChargeOrderInfo::getStartChargeSeq, startChargeSeq).last("LIMIT 1"));
  135. chargeOrderInfo.setStopReason(stopReason);
  136. chargeOrderInfo.setStartTime(startTime);
  137. chargeOrderInfo.setEndTime(endTime);
  138. chargeOrderInfo.setTotalCharge(new BigDecimal(totalPower));
  139. chargeOrderInfo.setThirdPartyTotalCost(new BigDecimal(totalMoney));
  140. chargeOrderInfo.setThirdPartyServerfee(new BigDecimal(totalSeviceMoney));
  141. chargeOrderInfo.setThirdPartyElecfee(new BigDecimal(totalElecMoney));
  142. ThirdPartyConnectorInfo thirdPartyConnectorInfo = connectorInfoMapper.selectOne(Wrappers.<ThirdPartyConnectorInfo>lambdaQuery()
  143. .eq(ThirdPartyConnectorInfo::getConnectorId, connectorID).last("LIMIT 1"));
  144. String stationId = thirdPartyConnectorInfo.getStationId();
  145. ThirdPartyStationInfo thirdPartyStationInfo = thirdPartyStationInfoMapper.selectOne(Wrappers.<ThirdPartyStationInfo>lambdaQuery()
  146. .eq(ThirdPartyStationInfo::getStationId, stationId).last("LIMIT 1"));
  147. if (ObjectUtil.isEmpty(thirdPartyConnectorInfo)) {
  148. log.error("thirdPartyConnectorInfo" +"为空===============================================");
  149. }
  150. //平台服务费
  151. BigDecimal serviceFee = BigDecimal.ZERO;
  152. JsonNode chargeDetails = jsonNode.get("ChargeDetails");
  153. if (ObjectUtil.isNotEmpty(chargeDetails)) {
  154. for (JsonNode node : chargeDetails) {
  155. //提取字段值
  156. String itemFlag = node.get("ItemFlag").asText();
  157. node.get("DetailPower").asText();
  158. BigDecimal detailPower = new BigDecimal(node.get("DetailPower").asText());
  159. PolicyFee policyFee = policyFeeMapper.selectOne(Wrappers.<PolicyFee>lambdaQuery()
  160. .eq(PolicyFee::getStationInfoId, thirdPartyStationInfo.getId())
  161. .eq(PolicyFee::getPeriodFlag, Integer.parseInt(itemFlag))
  162. .last("LIMIT 1"));
  163. if (ObjectUtil.isNotEmpty(policyFee)) {
  164. BigDecimal opFee = policyFee.getOpFee();
  165. log.info("策略费用:{}",opFee);
  166. serviceFee = serviceFee.add(opFee.multiply(detailPower));
  167. }
  168. }
  169. }
  170. log.info("计算后的平台服务费:{}", serviceFee);
  171. chargeOrderInfo.setRealServiceCost(serviceFee.setScale(2, RoundingMode.HALF_UP));
  172. //订单结算:平台实际收取金额 = 互联互通金额 + 中数电动金额(平台总服务费)
  173. chargeOrderInfo.setRealCost(chargeOrderInfo.getRealServiceCost().add(chargeOrderInfo.getThirdPartyTotalCost()));
  174. //订单状态->已完成
  175. chargeOrderInfo.setStatus(SystemConstants.STATUS_THREE);
  176. //计算充电时间
  177. chargeOrderInfo.setChargeTime(DateUtils.getDuration(chargeOrderInfo.getStartTime(), chargeOrderInfo.getEndTime()));
  178. //修改订单
  179. chargeOrderInfoService.updateById(chargeOrderInfo);
  180. //账户余额扣减(积分增加)
  181. log.info("执行账户余额扣减(积分增加)");
  182. chargeOrderInfoService.orderSettlement(chargeOrderInfo.getId());
  183. // 执行业务处理
  184. businessHandler.accept(jsonNode);
  185. // 构建响应
  186. return buildChargeResponse(getTextValue(jsonNode, "StartChargeSeq"));
  187. } catch (BusinessException e) {
  188. throw e;
  189. } catch (Exception e) {
  190. log.error("处理请求失败:{}", e.getMessage());
  191. throw new BusinessException("处理请求失败:" + e.getMessage(), e);
  192. }
  193. }
  194. /**
  195. * 处理启动充电结果推送请求
  196. * 数据格式:{"ConnectorID":"xxx","StartChargeSeq":"xxx","StartChargeSeqStat":2,"StartTime":"xxx"}
  197. */
  198. private ResponseParmsEntity processStartChargeResultRequest(RequestParmsEntity requestDTO) {
  199. try {
  200. JsonNode jsonNode = verifyAndDecrypt(requestDTO);
  201. // 启动充电结果业务处理
  202. String startChargeSeq = getTextValue(jsonNode, "StartChargeSeq");
  203. Integer startChargeSeqStat = getIntValue(jsonNode, "StartChargeSeqStat");
  204. String startTime = getTextValue(jsonNode, "StartTime");
  205. ChargeOrderInfo chargeOrderInfo = chargeOrderInfoService.getOne(new LambdaQueryWrapper<ChargeOrderInfo>()
  206. .eq(ChargeOrderInfo::getStartChargeSeq, startChargeSeq).last("LIMIT 1"));
  207. if (startChargeSeqStat != null) {
  208. switch (startChargeSeqStat) {
  209. case 1 -> log.info("启动中 - StartChargeSeq: {}", startChargeSeq);
  210. case 2 -> {
  211. log.info("充电中 - StartChargeSeq: {}", startChargeSeq);
  212. // 修改订单状态
  213. if (ObjectUtil.isNotEmpty(chargeOrderInfo)) {
  214. if (Objects.equals(chargeOrderInfo.getStatus(), SystemConstants.STATUS_ZERO)) {
  215. chargeOrderInfo.setStatus(SystemConstants.STATUS_ONE);
  216. chargeOrderInfo.setStartTime(startTime);
  217. chargeOrderInfoService.updateById(chargeOrderInfo);
  218. }
  219. }
  220. }
  221. case 3 -> log.info("停止中 - StartChargeSeq: {}", startChargeSeq);
  222. case 4 -> log.info("已结束 - StartChargeSeq: {}", startChargeSeq);
  223. case 5 -> log.info("未知 - StartChargeSeq: {}", startChargeSeq);
  224. default -> log.warn("未知状态 - StartChargeSeq: {}, Stat: {}", startChargeSeq, startChargeSeqStat);
  225. }
  226. }
  227. // 构建响应
  228. return buildChargeResponse(startChargeSeq);
  229. } catch (BusinessException e) {
  230. throw e;
  231. } catch (Exception e) {
  232. log.error("处理启动充电结果推送失败:{}", e.getMessage(), e);
  233. throw new BusinessException("处理启动充电结果推送失败:" + e.getMessage(), e);
  234. }
  235. }
  236. /**
  237. * 处理停止充电结果推送请求
  238. * 数据格式:{"ConnectorID":"xxx","FailReason":0,"StartChargeSeq":"xxx","StartChargeSeqStat":4,"SuccStat":0}
  239. */
  240. private ResponseParmsEntity processStopChargeResultRequest(RequestParmsEntity requestDTO) {
  241. try {
  242. JsonNode jsonNode = verifyAndDecrypt(requestDTO);
  243. // 停止充电结果业务处理
  244. String startChargeSeq = getTextValue(jsonNode, "StartChargeSeq");
  245. Integer startChargeSeqStat = getIntValue(jsonNode, "StartChargeSeqStat");
  246. Integer succStat = getIntValue(jsonNode, "SuccStat");
  247. Integer failReason = getIntValue(jsonNode, "FailReason");
  248. log.info("停止充电结果 - StartChargeSeq: {}, Stat: {}, SuccStat: {}, FailReason: {}",
  249. startChargeSeq, startChargeSeqStat, succStat, failReason);
  250. ChargeOrderInfo chargeOrderInfo = chargeOrderInfoService.getOne(new LambdaQueryWrapper<ChargeOrderInfo>()
  251. .eq(ChargeOrderInfo::getStartChargeSeq, startChargeSeq).last("LIMIT 1"));
  252. if (startChargeSeqStat != null && ObjectUtil.isNotEmpty(chargeOrderInfo)) {
  253. switch (startChargeSeqStat) {
  254. case 1 -> log.info("启动中 - StartChargeSeq: {}", startChargeSeq);
  255. case 2 -> log.info("充电中 - StartChargeSeq: {}", startChargeSeq);
  256. case 3 -> log.info("停止中 - StartChargeSeq: {}", startChargeSeq);
  257. case 4 -> {
  258. log.info("已结束 - StartChargeSeq: {}", startChargeSeq);
  259. // 修改订单状态为结算中
  260. if (Objects.equals(chargeOrderInfo.getStatus(), SystemConstants.STATUS_ONE) ||
  261. Objects.equals(chargeOrderInfo.getStatus(), SystemConstants.STATUS_THREE)) {
  262. chargeOrderInfo.setStatus(SystemConstants.STATUS_TWO);
  263. chargeOrderInfoService.updateById(chargeOrderInfo);
  264. log.info("更新订单状态为结算中 - orderId: {}", chargeOrderInfo.getId());
  265. }
  266. }
  267. case 5 -> log.info("未知 - StartChargeSeq: {}", startChargeSeq);
  268. default -> log.warn("未知状态 - StartChargeSeq: {}, Stat: {}", startChargeSeq, startChargeSeqStat);
  269. }
  270. }
  271. // 构建响应
  272. return buildChargeResponse(startChargeSeq);
  273. } catch (BusinessException e) {
  274. throw e;
  275. } catch (Exception e) {
  276. log.error("处理停止充电结果推送失败:{}", e.getMessage(), e);
  277. throw new BusinessException("处理停止充电结果推送失败:" + e.getMessage(), e);
  278. }
  279. }
  280. /**
  281. * 处理实时充电状态推送请求
  282. * 数据格式:{"ConnectorID":"xxx","ConnectorStatus":3,"TotalPower":1.95,"ElecMoney":1.89,...}
  283. */
  284. private ResponseParmsEntity processChargeStatusRequest(RequestParmsEntity requestDTO) {
  285. try {
  286. JsonNode jsonNode = verifyAndDecrypt(requestDTO);
  287. // 保存或更新充电状态
  288. saveOrUpdateChargeStatus(jsonNode);
  289. // 构建响应
  290. return buildChargeResponse(getTextValue(jsonNode, "StartChargeSeq"));
  291. } catch (BusinessException e) {
  292. throw e;
  293. } catch (Exception e) {
  294. log.error("处理充电状态推送失败:{}", e.getMessage(), e);
  295. throw new BusinessException("处理充电状态推送失败:" + e.getMessage(), e);
  296. }
  297. }
  298. /**
  299. * 处理设备状态变化推送请求
  300. */
  301. private ResponseParmsEntity processStationStatusRequest(RequestParmsEntity requestDTO) {
  302. try {
  303. String decryptData = verifyAndDecryptRaw(requestDTO);
  304. // 解析并更新设备状态
  305. QueryStationStatusVO stationStatusVO = objectMapper.readValue(decryptData, QueryStationStatusVO.class);
  306. updateConnectorStatus(stationStatusVO);
  307. // 构建响应
  308. return buildStatusResponse();
  309. } catch (BusinessException e) {
  310. throw e;
  311. } catch (Exception e) {
  312. log.error("处理设备状态推送失败:{}", e.getMessage());
  313. throw new BusinessException("处理设备状态推送失败:" + e.getMessage(), e);
  314. }
  315. }
  316. /**
  317. * 验签并解密请求数据
  318. */
  319. private JsonNode verifyAndDecrypt(RequestParmsEntity requestDTO) throws Exception {
  320. String decryptData = verifyAndDecryptRaw(requestDTO);
  321. return objectMapper.readTree(decryptData);
  322. }
  323. /**
  324. * 验签并解密请求数据(返回原始字符串)
  325. */
  326. private String verifyAndDecryptRaw(RequestParmsEntity requestDTO) throws Exception {
  327. String signData = requestDTO.getOperatorID() + requestDTO.getData() + requestDTO.getTimeStamp() + requestDTO.getSeq();
  328. if (!verify(signData, ConnectivityConstants.SIG_SECRET, requestDTO.getSig())) {
  329. log.error("数据验签失败");
  330. throw new BusinessException("数据验签失败");
  331. }
  332. String decryptData = chargingUtil.decryptData(requestDTO.getData());
  333. log.info("==================== 解密数据开始 ====================");
  334. log.info("操作员ID: {}", requestDTO.getOperatorID());
  335. log.info("解密后的数据:{}", decryptData);
  336. log.info("==================== 解密数据结束 ====================");
  337. return decryptData;
  338. }
  339. // ==================== 响应构建 ====================
  340. /**
  341. * 构建充电响应
  342. */
  343. private ResponseParmsEntity buildChargeResponse(String startChargeSeq) throws Exception {
  344. ChargeResponseVO chargeResponseVO = new ChargeResponseVO();
  345. chargeResponseVO.setStartChargeSeq(startChargeSeq);
  346. chargeResponseVO.setSuccStat(STATUS_OK);
  347. chargeResponseVO.setFailReason(FAIL_REASON_NONE);
  348. String encryptData = chargingUtil.encryptData(objectMapper.writeValueAsString(chargeResponseVO));
  349. String sign = genSign(STATUS_OK, "请求成功", encryptData, ConnectivityConstants.SIG_SECRET);
  350. ResponseParmsEntity response = new ResponseParmsEntity();
  351. response.setRet(STATUS_OK);
  352. response.setMsg("请求成功");
  353. response.setData(encryptData);
  354. response.setSig(sign);
  355. return response;
  356. }
  357. /**
  358. * 构建设备状态响应
  359. */
  360. private ResponseParmsEntity buildStatusResponse() throws Exception {
  361. Map<String, Integer> statusMap = new HashMap<>();
  362. statusMap.put("Status", 0);
  363. String encryptData = chargingUtil.encryptData(objectMapper.writeValueAsString(statusMap));
  364. String sign = genSign(STATUS_OK, "", encryptData, ConnectivityConstants.SIG_SECRET);
  365. ResponseParmsEntity response = new ResponseParmsEntity();
  366. response.setRet(STATUS_OK);
  367. response.setMsg("");
  368. response.setData(encryptData);
  369. response.setSig(sign);
  370. return response;
  371. }
  372. // ==================== 状态更新 ====================
  373. /**
  374. * 更新充电接口状态
  375. */
  376. private void updateConnectorStatus(QueryStationStatusVO stationStatusVO) {
  377. if (stationStatusVO == null) {
  378. return;
  379. }
  380. // 处理单个连接器状态推送格式:{"ConnectorStatusInfo":{"ConnectorID":"xxx","Status":2}}
  381. ConnectorStatusInfo singleConnector = stationStatusVO.getConnectorStatusInfo();
  382. if (singleConnector != null && singleConnector.getConnectorID() != null) {
  383. connectorInfoMapper.update(null, Wrappers.<ThirdPartyConnectorInfo>lambdaUpdate()
  384. .eq(ThirdPartyConnectorInfo::getConnectorId, singleConnector.getConnectorID())
  385. .set(ThirdPartyConnectorInfo::getStatus, singleConnector.getStatus())
  386. .set(ThirdPartyConnectorInfo::getUpdateTime, LocalDateTime.now()));
  387. log.info("更新充电接口状态(单个) - connectorId: {}, status: {}",
  388. singleConnector.getConnectorID(), singleConnector.getStatus());
  389. return;
  390. }
  391. // 处理批量连接器状态推送格式
  392. if (CollectionUtils.isEmpty(stationStatusVO.getStationStatusInfos())) {
  393. return;
  394. }
  395. for (StationStatusInfo stationStatusInfo : stationStatusVO.getStationStatusInfos()) {
  396. List<ConnectorStatusInfo> connectorStatusInfos = stationStatusInfo.getConnectorStatusInfos();
  397. if (CollectionUtils.isEmpty(connectorStatusInfos)) {
  398. continue;
  399. }
  400. for (ConnectorStatusInfo connectorStatus : connectorStatusInfos) {
  401. connectorInfoMapper.update(null, Wrappers.<ThirdPartyConnectorInfo>lambdaUpdate()
  402. .eq(ThirdPartyConnectorInfo::getConnectorId, connectorStatus.getConnectorID())
  403. .set(ThirdPartyConnectorInfo::getStatus, connectorStatus.getStatus())
  404. .set(ThirdPartyConnectorInfo::getUpdateTime, LocalDateTime.now()));
  405. log.debug("更新充电接口状态 - connectorId: {}, status: {}",
  406. connectorStatus.getConnectorID(), connectorStatus.getStatus());
  407. }
  408. }
  409. }
  410. /**
  411. * 保存或更新充电状态数据
  412. */
  413. private void saveOrUpdateChargeStatus(JsonNode jsonNode) {
  414. try {
  415. log.info("保存或更新充电状态数据 - StartChargeSeq: {}", jsonNode);
  416. String startChargeSeq = jsonNode.get("StartChargeSeq").asText();
  417. String connectorId = getTextValue(jsonNode, "ConnectorID");
  418. // 获取第三方推送的实时数据
  419. BigDecimal totalPower = getDecimalValue(jsonNode, "TotalPower"); // 实际充电度数
  420. BigDecimal totalMoney = getDecimalValue(jsonNode, "TotalMoney"); // 第三方总费用
  421. BigDecimal elecMoney = getDecimalValue(jsonNode, "ElecMoney"); // 第三方电费
  422. BigDecimal serviceMoney = getDecimalValue(jsonNode, "SeviceMoney"); // 第三方服务费
  423. // 修改订单状态并实时更新消费字段
  424. ChargeOrderInfo chargeOrderInfo = chargeOrderInfoService.getOne(new LambdaQueryWrapper<ChargeOrderInfo>()
  425. .eq(ChargeOrderInfo::getStartChargeSeq, startChargeSeq).last("limit 1"));
  426. if(ObjUtil.isNotEmpty(chargeOrderInfo)){
  427. Integer connectorStatus = getIntValue(jsonNode, "ConnectorStatus");
  428. // 计算平台实际收取金额(根据度数和平台价格策略计算)
  429. BigDecimal realCost = calculateRealCost(chargeOrderInfo, totalPower);
  430. if (Objects.equals(connectorStatus, SystemConstants.STATUS_THREE) && Objects.equals(chargeOrderInfo.getStatus(), SystemConstants.STATUS_ZERO)) {
  431. // 充电中
  432. log.info("充电中 - StartChargeSeq: {}", startChargeSeq);
  433. chargeOrderInfo.setStatus(SystemConstants.STATUS_ONE);
  434. }
  435. if (Objects.equals(connectorStatus, SystemConstants.STATUS_FOUR) && Objects.equals(chargeOrderInfo.getStatus(),
  436. SystemConstants.STATUS_ONE)) {
  437. // 结算中
  438. log.info("结算中 - StartChargeSeq: {}", startChargeSeq);
  439. chargeOrderInfo.setStatus(SystemConstants.STATUS_TWO);
  440. }
  441. chargeOrderInfoService.updateById(chargeOrderInfo);
  442. log.info("实时更新订单消费 - startChargeSeq: {}, totalPower: {}, realCost: {}",
  443. startChargeSeq, totalPower, realCost);
  444. }
  445. // 查询是否已存在该充电状态记录
  446. ThirdPartyChargeStatus existing = chargeStatusMapper.selectOne(
  447. Wrappers.<ThirdPartyChargeStatus>lambdaQuery()
  448. .eq(ThirdPartyChargeStatus::getStartChargeSeq, startChargeSeq)
  449. );
  450. ThirdPartyChargeStatus chargeStatus = (existing != null) ? existing : new ThirdPartyChargeStatus();
  451. // 设置字段值
  452. chargeStatus.setStartChargeSeq(startChargeSeq);
  453. chargeStatus.setConnectorId(connectorId);
  454. chargeStatus.setConnectorStatus(getIntValue(jsonNode, "ConnectorStatus"));
  455. chargeStatus.setStartChargeSeqStat(getIntValue(jsonNode, "StartChargeSeqStat"));
  456. chargeStatus.setStartTime(parseDateTime(getTextValue(jsonNode, "StartTime")));
  457. chargeStatus.setEndTime(parseDateTime(getTextValue(jsonNode, "EndTime")));
  458. chargeStatus.setTotalPower(totalPower);
  459. chargeStatus.setTotalMoney(totalMoney);
  460. chargeStatus.setElecMoney(elecMoney);
  461. chargeStatus.setServiceMoney(serviceMoney);
  462. chargeStatus.setSoc(getIntValue(jsonNode, "Soc"));
  463. chargeStatus.setVoltageA(getDecimalValue(jsonNode, "VoltageA"));
  464. chargeStatus.setVoltageB(getDecimalValue(jsonNode, "VoltageB"));
  465. chargeStatus.setVoltageC(getDecimalValue(jsonNode, "VoltageC"));
  466. chargeStatus.setCurrentA(getDecimalValue(jsonNode, "CurrentA"));
  467. chargeStatus.setCurrentB(getDecimalValue(jsonNode, "CurrentB"));
  468. chargeStatus.setCurrentC(getDecimalValue(jsonNode, "CurrentC"));
  469. // 兼容处理:如果有ChargeDetails字段,则存储为JSON字符串
  470. if (jsonNode.has("ChargeDetails") && !jsonNode.get("ChargeDetails").isNull()) {
  471. chargeStatus.setChargeDetails(jsonNode.get("ChargeDetails").toString());
  472. }
  473. if (existing != null) {
  474. chargeStatus.setUpdateTime(LocalDateTime.now());
  475. chargeStatusMapper.updateById(chargeStatus);
  476. log.info("更新充电状态成功 - startChargeSeq: {}", startChargeSeq);
  477. } else {
  478. chargeStatus.setCreateTime(LocalDateTime.now());
  479. chargeStatusMapper.insert(chargeStatus);
  480. log.info("新增充电状态成功 - startChargeSeq: {}", startChargeSeq);
  481. }
  482. // 熔断保护 - 余额不足判断
  483. isNeedBreak(chargeStatus, chargeOrderInfo);
  484. } catch (Exception e) {
  485. log.error("保存充电状态数据失败", e);
  486. }
  487. }
  488. /**
  489. * 计算平台实际收取金额
  490. * 根据充电度数 * 当前时段的平台价格策略计算(支持跨时段计费)
  491. */
  492. private BigDecimal calculateRealCost(ChargeOrderInfo chargeOrderInfo, BigDecimal totalPower) {
  493. if (totalPower == null || totalPower.compareTo(BigDecimal.ZERO) <= 0) {
  494. return BigDecimal.ZERO;
  495. }
  496. try {
  497. // 获取用户企业信息
  498. UserFirm userFirm = userFirmMapper.selectOne(Wrappers.<UserFirm>lambdaQuery()
  499. .eq(UserFirm::getUserId, chargeOrderInfo.getUserId())
  500. .eq(UserFirm::getIsDeleted, 0));
  501. // 获取设备信息
  502. ThirdPartyEquipmentInfo equipmentInfo = thirdPartyEquipmentInfoMapper.selectOne(
  503. Wrappers.<ThirdPartyEquipmentInfo>lambdaQuery()
  504. .eq(ThirdPartyEquipmentInfo::getEquipmentId, chargeOrderInfo.getEquipmentId())
  505. .eq(ThirdPartyEquipmentInfo::getIsDeleted, 0));
  506. if (equipmentInfo == null) {
  507. log.warn("未找到设备信息 - equipmentId: {}", chargeOrderInfo.getEquipmentId());
  508. return BigDecimal.ZERO;
  509. }
  510. // 获取站点信息
  511. ThirdPartyStationInfo stationInfo = thirdPartyStationInfoMapper.selectOne(
  512. Wrappers.<ThirdPartyStationInfo>lambdaQuery()
  513. .eq(ThirdPartyStationInfo::getStationId, equipmentInfo.getStationId())
  514. .eq(ThirdPartyStationInfo::getIsDeleted, 0));
  515. if (stationInfo == null) {
  516. log.warn("未找到站点信息 - stationId: {}", equipmentInfo.getStationId());
  517. return BigDecimal.ZERO;
  518. }
  519. // 获取当前时间,格式为 HHmmss
  520. String currentTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HHmmss"));
  521. // 根据当前时间查询当前时段的价格策略(核心:支持跨时段计费)
  522. ThirdPartyPolicyInfo currentPeriodPolicy = thirdPartyPolicyInfoMapper
  523. .selectCurrentPeriodPolicyByStation(stationInfo.getId(), currentTime);
  524. if (currentPeriodPolicy == null || currentPeriodPolicy.getPeriodFlag() == null) {
  525. log.warn("未找到当前时段价格策略 - stationInfoId: {}, currentTime: {}",
  526. stationInfo.getId(), currentTime);
  527. return BigDecimal.ZERO;
  528. }
  529. Integer currentPeriodFlag = currentPeriodPolicy.getPeriodFlag();
  530. log.info("当前时段 - stationInfoId: {}, currentTime: {}, periodFlag: {}",
  531. stationInfo.getId(), currentTime, currentPeriodFlag);
  532. // 查询当前时段对应的价格策略
  533. Integer salesType = (userFirm != null) ? 1 : 0; // 1-企业 0-平台
  534. Long firmId = (userFirm != null) ? userFirm.getFirmId() : null;
  535. PolicyFee policyFee = policyFeeMapper.selectOne(Wrappers.<PolicyFee>lambdaQuery()
  536. .eq(PolicyFee::getStationInfoId, stationInfo.getId())
  537. .eq(PolicyFee::getPeriodFlag, currentPeriodFlag)
  538. .eq(PolicyFee::getSalesType, salesType)
  539. .eq(salesType == 1, PolicyFee::getFirmId, firmId)
  540. .eq(PolicyFee::getIsDeleted, 0)
  541. .last("LIMIT 1"));
  542. if (policyFee == null) {
  543. log.warn("未找到当前时段的价格策略 - stationInfoId: {}, periodFlag: {}, salesType: {}",
  544. stationInfo.getId(), currentPeriodFlag, salesType);
  545. return BigDecimal.ZERO;
  546. }
  547. // 获取综合销售费作为单价
  548. BigDecimal unitPrice = policyFee.getCompSalesFee() != null ? policyFee.getCompSalesFee() : BigDecimal.ZERO;
  549. // 实际收取金额 = 充电度数 * 当前时段单价
  550. BigDecimal realCost = totalPower.multiply(unitPrice).setScale(2, BigDecimal.ROUND_HALF_UP);
  551. log.info("计算实际收取金额 - totalPower: {}, periodFlag: {}, unitPrice: {}, realCost: {}",
  552. totalPower, currentPeriodFlag, unitPrice, realCost);
  553. return realCost;
  554. } catch (Exception e) {
  555. log.error("计算平台实际收取金额失败", e);
  556. return BigDecimal.ZERO;
  557. }
  558. }
  559. // ==================== JSON解析工具方法 ====================
  560. private String getTextValue(JsonNode node, String fieldName) {
  561. JsonNode field = node.get(fieldName);
  562. return (field != null && !field.isNull()) ? field.asText() : null;
  563. }
  564. private Integer getIntValue(JsonNode node, String fieldName) {
  565. JsonNode field = node.get(fieldName);
  566. return (field != null && !field.isNull()) ? field.asInt() : null;
  567. }
  568. private BigDecimal getDecimalValue(JsonNode node, String fieldName) {
  569. JsonNode field = node.get(fieldName);
  570. return (field != null && !field.isNull()) ? new BigDecimal(field.asText()) : null;
  571. }
  572. private LocalDateTime parseDateTime(String dateTimeStr) {
  573. if (dateTimeStr == null || dateTimeStr.isEmpty()) {
  574. return null;
  575. }
  576. try {
  577. return LocalDateTime.parse(dateTimeStr, DATE_TIME_FORMATTER);
  578. } catch (Exception e) {
  579. log.warn("解析时间失败: {}", dateTimeStr);
  580. return null;
  581. }
  582. }
  583. /**
  584. * 根据充电订单号StartChargeSeq 获取订单信息判断是否需要熔断,提前跳枪
  585. * 使用分布式锁防止并发重复检查
  586. *
  587. * @param chargeStatus 充电状态
  588. * @param chargeOrderInfo 订单信息
  589. */
  590. private void isNeedBreak(ThirdPartyChargeStatus chargeStatus, ChargeOrderInfo chargeOrderInfo) {
  591. // 订单为空或不是充电中状态,不需要熔断检查
  592. if (chargeOrderInfo == null || !Objects.equals(chargeOrderInfo.getStatus(), SystemConstants.STATUS_ONE)) {
  593. return;
  594. }
  595. String lockKey = BREAK_CHECK_LOCK_KEY + chargeStatus.getStartChargeSeq();
  596. RLock lock = redissonClient.getLock(lockKey);
  597. try {
  598. // 尝试获取锁,最多等待3秒,持有锁最多10秒
  599. if (lock.tryLock(LOCK_WAIT_TIME, LOCK_LEASE_TIME, TimeUnit.SECONDS)) {
  600. try {
  601. // 获取安全阈值配置
  602. DictItem dictItem = dictItemMapper.selectOne(
  603. new LambdaQueryWrapper<DictItem>()
  604. .eq(DictItem::getDictCode, "up_recharge")
  605. .eq(DictItem::getStatus, 1)
  606. .last("LIMIT 1"));
  607. BigDecimal safetyThreshold = BigDecimal.ZERO;
  608. if (dictItem != null && dictItem.getValue() != null) {
  609. safetyThreshold = new BigDecimal(dictItem.getValue());
  610. }
  611. // 获取用户余额
  612. UserAccount userAccount = userAccountMapper.selectOne(Wrappers.<UserAccount>lambdaQuery()
  613. .eq(UserAccount::getUserId, chargeOrderInfo.getUserId())
  614. .eq(UserAccount::getIsDeleted, 0));
  615. if (userAccount == null) {
  616. log.warn("未找到用户账户信息 - userId: {}", chargeOrderInfo.getUserId());
  617. return;
  618. }
  619. BigDecimal balance = userAccount.getBalance() != null ? userAccount.getBalance() : BigDecimal.ZERO;
  620. // 获取当前实时消费金额
  621. BigDecimal realCost = chargeOrderInfo.getRealCost() != null ? chargeOrderInfo.getRealCost() : BigDecimal.ZERO;
  622. // 剩余余额 = 账户余额 - 实时消费
  623. BigDecimal remainingBalance = balance.subtract(realCost);
  624. log.info("熔断检查 - startChargeSeq: {}, 用户余额: {}, 实时消费: {}, 安全阈值: {}, 剩余余额: {}",
  625. chargeStatus.getStartChargeSeq(), balance, realCost, safetyThreshold, remainingBalance);
  626. // 熔断条件:剩余余额 < 安全阈值 且 已产生实际消费(实时消费 > 0)
  627. // 这样可以避免刚开始充电(消费为0)时因余额小于阈值而被误熔断
  628. if (remainingBalance.compareTo(safetyThreshold) < 0 && realCost.compareTo(BigDecimal.ZERO) > 0) {
  629. log.warn("余额不足,触发熔断停止充电 - startChargeSeq: {}, 用户余额: {}, 实时消费: {}",
  630. chargeStatus.getStartChargeSeq(), balance, realCost);
  631. // 调用第三方停止充电接口
  632. try {
  633. chargingBusinessService.stopCharging(
  634. chargeStatus.getStartChargeSeq(),
  635. chargeStatus.getConnectorId());
  636. log.info("已发送停止充电请求 - startChargeSeq: {}, connectorId: {}",
  637. chargeStatus.getStartChargeSeq(), chargeStatus.getConnectorId());
  638. // 更新订单停止类型为余额不足停止
  639. chargeOrderInfo.setStopType(3); // 3-余额不足停止
  640. chargeOrderInfo.setStopReason("余额不足,系统自动停止充电");
  641. chargeOrderInfoService.updateById(chargeOrderInfo);
  642. } catch (Exception e) {
  643. log.error("调用第三方停止充电接口失败 - startChargeSeq: {}",
  644. chargeStatus.getStartChargeSeq(), e);
  645. }
  646. }
  647. } finally {
  648. lock.unlock();
  649. }
  650. } else {
  651. log.warn("获取熔断检查锁超时 - startChargeSeq: {}", chargeStatus.getStartChargeSeq());
  652. }
  653. } catch (InterruptedException e) {
  654. Thread.currentThread().interrupt();
  655. log.error("获取熔断检查锁被中断 - startChargeSeq: {}", chargeStatus.getStartChargeSeq(), e);
  656. } catch (Exception e) {
  657. log.error("熔断检查失败 - startChargeSeq: {}", chargeStatus.getStartChargeSeq(), e);
  658. }
  659. }
  660. }