ThirdPartyChargingServiceImpl.java 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890
  1. package com.zsElectric.boot.business.service.impl;
  2. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  3. import com.baomidou.mybatisplus.core.metadata.IPage;
  4. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  5. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  6. import com.fasterxml.jackson.core.JsonProcessingException;
  7. import com.fasterxml.jackson.core.type.TypeReference;
  8. import com.fasterxml.jackson.databind.ObjectMapper;
  9. import com.zsElectric.boot.charging.entity.*;
  10. import com.zsElectric.boot.business.mapper.FirmInfoMapper;
  11. import com.zsElectric.boot.business.mapper.PolicyFeeMapper;
  12. import com.zsElectric.boot.business.mapper.ThirdPartyEquipmentInfoMapper;
  13. import com.zsElectric.boot.business.mapper.ThirdPartyInfoMapper;
  14. import com.zsElectric.boot.business.mapper.ThirdPartyStationInfoMapper;
  15. import com.zsElectric.boot.charging.mapper.ThirdPartyConnectorInfoMapper;
  16. import com.zsElectric.boot.charging.mapper.ThirdPartyEquipmentPricePolicyMapper;
  17. import com.zsElectric.boot.charging.mapper.ThirdPartyPolicyInfoMapper;
  18. import com.zsElectric.boot.business.model.entity.FirmInfo;
  19. import com.zsElectric.boot.business.model.entity.PolicyFee;
  20. import com.zsElectric.boot.business.model.entity.ThirdPartyInfo;
  21. import com.zsElectric.boot.business.model.query.ThirdPartyEquipmentInfoQuery;
  22. import com.zsElectric.boot.business.model.query.ThirdPartyStationInfoQuery;
  23. import com.zsElectric.boot.business.model.vo.PartyStationInfoVO;
  24. import com.zsElectric.boot.business.model.vo.ThirdPartyEquipmentInfoVO;
  25. import com.zsElectric.boot.business.model.vo.ThirdPartyStationInfoVO;
  26. import com.zsElectric.boot.business.model.vo.StationDetailVO;
  27. import com.zsElectric.boot.business.model.dto.StationDetailDTO;
  28. import com.zsElectric.boot.business.service.ThirdPartyChargingService;
  29. import com.zsElectric.boot.charging.vo.ChargingPricePolicyVO;
  30. import com.zsElectric.boot.charging.vo.QueryStationsInfoVO;
  31. import lombok.RequiredArgsConstructor;
  32. import lombok.extern.slf4j.Slf4j;
  33. import org.springframework.stereotype.Service;
  34. import org.springframework.transaction.annotation.Transactional;
  35. import org.springframework.util.CollectionUtils;
  36. import java.math.BigDecimal;
  37. import java.time.LocalDateTime;
  38. import java.util.Comparator;
  39. import java.util.List;
  40. import java.util.Map;
  41. import java.util.Objects;
  42. import java.util.stream.Collectors;
  43. /**
  44. * 第三方充电站/充电桩/价格策略统一服务实现
  45. *
  46. * @author system
  47. * @since 2025-12-15
  48. */
  49. @Slf4j
  50. @Service
  51. @RequiredArgsConstructor
  52. public class ThirdPartyChargingServiceImpl implements ThirdPartyChargingService {
  53. private final ThirdPartyStationInfoMapper stationInfoMapper;
  54. private final ThirdPartyEquipmentInfoMapper equipmentInfoMapper;
  55. private final ThirdPartyConnectorInfoMapper connectorInfoMapper;
  56. private final ThirdPartyEquipmentPricePolicyMapper pricePolicyMapper;
  57. private final ThirdPartyPolicyInfoMapper policyInfoMapper;
  58. private final FirmInfoMapper firmInfoMapper;
  59. private final ThirdPartyInfoMapper thirdPartyInfoMapper;
  60. private final PolicyFeeMapper policyFeeMapper;
  61. private final ObjectMapper objectMapper;
  62. // ==================== 充电站信息查询 ====================
  63. @Override
  64. public IPage<ThirdPartyStationInfoVO> getStationInfoPage(ThirdPartyStationInfoQuery queryParams) {
  65. // 构建分页
  66. Page<ThirdPartyStationInfoVO> page = new Page<>(queryParams.getPageNum(), queryParams.getPageSize());
  67. // 调用Mapper联表查询
  68. return stationInfoMapper.selectStationInfoPage(page, queryParams);
  69. }
  70. // ==================== 充电桩信息查询 ====================
  71. @Override
  72. public IPage<ThirdPartyEquipmentInfoVO> getEquipmentInfoPage(ThirdPartyEquipmentInfoQuery queryParams) {
  73. // 构建分页
  74. Page<ThirdPartyEquipmentInfoVO> page = new Page<>(queryParams.getPageNum(), queryParams.getPageSize());
  75. // 调用Mapper联表查询
  76. return equipmentInfoMapper.selectEquipmentInfoPage(page, queryParams);
  77. }
  78. @Override
  79. public List<PartyStationInfoVO> getPartyStationInfo() {
  80. // 查询所有设备信息
  81. List<ThirdPartyEquipmentInfo> equipmentInfos = equipmentInfoMapper.selectList(null);
  82. // 获取所有不重复的充电站ID
  83. List<String> stationIds = equipmentInfos.stream()
  84. .map(ThirdPartyEquipmentInfo::getStationId)
  85. .distinct()
  86. .collect(Collectors.toList());
  87. if (stationIds.isEmpty()) {
  88. return List.of();
  89. }
  90. // 查询充电站信息
  91. List<ThirdPartyStationInfo> stationInfos = stationInfoMapper.selectList(
  92. new LambdaQueryWrapper<ThirdPartyStationInfo>()
  93. .in(ThirdPartyStationInfo::getStationId, stationIds)
  94. );
  95. // 转换为VO
  96. return stationInfos.stream()
  97. .map(station -> {
  98. PartyStationInfoVO vo = new PartyStationInfoVO();
  99. vo.setId(station.getId());
  100. vo.setStationName(station.getStationName());
  101. return vo;
  102. })
  103. .collect(Collectors.toList());
  104. }
  105. // ==================== 充电站数据保存 ====================
  106. @Override
  107. @Transactional(rollbackFor = Exception.class)
  108. public void saveStationsInfo(QueryStationsInfoVO queryStationsInfoVO) {
  109. if (queryStationsInfoVO == null || CollectionUtils.isEmpty(queryStationsInfoVO.getStationInfos())) {
  110. log.warn("充电站信息为空,跳过存储");
  111. return;
  112. }
  113. List<StationInfo> stationInfos = queryStationsInfoVO.getStationInfos();
  114. log.info("开始保存充电站信息,总数: {}", stationInfos.size());
  115. for (StationInfo stationInfo : stationInfos) {
  116. try {
  117. // 保存充电站信息
  118. saveStationInfo(stationInfo);
  119. // 保存设备信息
  120. if (!CollectionUtils.isEmpty(stationInfo.getEquipmentInfos())) {
  121. for (EquipmentInfo equipmentInfo : stationInfo.getEquipmentInfos()) {
  122. saveEquipmentInfo(equipmentInfo, stationInfo.getStationID());
  123. // 保存接口信息
  124. if (!CollectionUtils.isEmpty(equipmentInfo.getConnectorInfos())) {
  125. for (ConnectorInfo connectorInfo : equipmentInfo.getConnectorInfos()) {
  126. saveConnectorInfo(connectorInfo, equipmentInfo.getEquipmentID(), stationInfo.getStationID());
  127. }
  128. }
  129. }
  130. }
  131. } catch (Exception e) {
  132. log.error("保存充电站信息失败, stationId: {}", stationInfo.getStationID(), e);
  133. throw new RuntimeException("保存充电站信息失败: " + stationInfo.getStationID(), e);
  134. }
  135. }
  136. log.info("充电站信息保存完成");
  137. }
  138. /**
  139. * 保存充电站信息
  140. */
  141. private void saveStationInfo(StationInfo stationInfo) {
  142. // 查询是否已存在
  143. ThirdPartyStationInfo existingStation = stationInfoMapper.selectOne(
  144. new LambdaQueryWrapper<ThirdPartyStationInfo>()
  145. .eq(ThirdPartyStationInfo::getStationId, stationInfo.getStationID())
  146. );
  147. ThirdPartyStationInfo entity = buildStationEntity(stationInfo, existingStation);
  148. if (existingStation == null) {
  149. // 不存在,执行新增
  150. stationInfoMapper.insert(entity);
  151. log.debug("充电站信息新增成功 - stationId: {}", stationInfo.getStationID());
  152. } else if (!isStationSame(existingStation, entity)) {
  153. // 存在且数据变化,执行更新
  154. stationInfoMapper.updateById(entity);
  155. log.debug("充电站信息更新成功 - stationId: {}", stationInfo.getStationID());
  156. } else {
  157. log.debug("充电站信息未变化,跳过保存 - stationId: {}", stationInfo.getStationID());
  158. }
  159. }
  160. /**
  161. * 构建充电站实体
  162. */
  163. private ThirdPartyStationInfo buildStationEntity(StationInfo stationInfo, ThirdPartyStationInfo existingStation) {
  164. ThirdPartyStationInfo entity = new ThirdPartyStationInfo();
  165. if (existingStation != null) {
  166. entity.setId(existingStation.getId());
  167. // 保留原有的自有字段(不会被第三方接口覆盖)
  168. entity.setPolicyConfigured(existingStation.getPolicyConfigured());
  169. entity.setStationTips(existingStation.getStationTips());
  170. entity.setOwnBusinessHours(existingStation.getOwnBusinessHours());
  171. entity.setCustomerServiceHotline(existingStation.getCustomerServiceHotline());
  172. entity.setBannerPictures(existingStation.getBannerPictures());
  173. }
  174. // 设置字段值
  175. entity.setStationId(stationInfo.getStationID());
  176. entity.setOperatorId(stationInfo.getOperatorID());
  177. entity.setEquipmentOwnerId(stationInfo.getEquipmentOwnerID());
  178. entity.setStationName(stationInfo.getStationName());
  179. entity.setCountryCode(stationInfo.getCountryCode());
  180. entity.setAreaCode(stationInfo.getAreaCode());
  181. entity.setAddress(stationInfo.getAddress());
  182. entity.setStationTel(stationInfo.getStationTel());
  183. entity.setServiceTel(stationInfo.getServiceTel());
  184. entity.setStationType(stationInfo.getStationType());
  185. entity.setStationStatus(stationInfo.getStationStatus());
  186. entity.setParkNums(stationInfo.getParkNums());
  187. // 处理经纬度
  188. if (stationInfo.getStationLng() != null) {
  189. entity.setStationLng(BigDecimal.valueOf(stationInfo.getStationLng()));
  190. }
  191. if (stationInfo.getStationLat() != null) {
  192. entity.setStationLat(BigDecimal.valueOf(stationInfo.getStationLat()));
  193. }
  194. entity.setSiteGuide(stationInfo.getSiteGuide());
  195. entity.setConstruction(stationInfo.getConstruction());
  196. // 处理图片列表(转JSON)
  197. if (!CollectionUtils.isEmpty(stationInfo.getPictures())) {
  198. try {
  199. entity.setPictures(objectMapper.writeValueAsString(stationInfo.getPictures()));
  200. } catch (JsonProcessingException e) {
  201. log.warn("图片列表转JSON失败", e);
  202. }
  203. }
  204. entity.setBusineHours(stationInfo.getBusineHours());
  205. // 处理费用(字符串转BigDecimal)
  206. if (stationInfo.getElectricityFee() != null) {
  207. try {
  208. entity.setElectricityFee(new BigDecimal(stationInfo.getElectricityFee()));
  209. } catch (NumberFormatException e) {
  210. log.warn("电费转换失败: {}", stationInfo.getElectricityFee(), e);
  211. }
  212. }
  213. if (stationInfo.getServiceFee() != null) {
  214. try {
  215. entity.setServiceFee(new BigDecimal(stationInfo.getServiceFee()));
  216. } catch (NumberFormatException e) {
  217. log.warn("服务费转换失败: {}", stationInfo.getServiceFee(), e);
  218. }
  219. }
  220. entity.setParkFee(stationInfo.getParkFee());
  221. entity.setPayment(stationInfo.getPayment());
  222. entity.setSupportOrder(stationInfo.getSupportOrder());
  223. entity.setRemark(stationInfo.getRemark());
  224. return entity;
  225. }
  226. /**
  227. * 判断充电站信息是否相同
  228. */
  229. private boolean isStationSame(ThirdPartyStationInfo existing, ThirdPartyStationInfo newEntity) {
  230. return Objects.equals(existing.getOperatorId(), newEntity.getOperatorId()) &&
  231. Objects.equals(existing.getEquipmentOwnerId(), newEntity.getEquipmentOwnerId()) &&
  232. Objects.equals(existing.getStationName(), newEntity.getStationName()) &&
  233. Objects.equals(existing.getCountryCode(), newEntity.getCountryCode()) &&
  234. Objects.equals(existing.getAreaCode(), newEntity.getAreaCode()) &&
  235. Objects.equals(existing.getAddress(), newEntity.getAddress()) &&
  236. Objects.equals(existing.getStationTel(), newEntity.getStationTel()) &&
  237. Objects.equals(existing.getServiceTel(), newEntity.getServiceTel()) &&
  238. Objects.equals(existing.getStationType(), newEntity.getStationType()) &&
  239. Objects.equals(existing.getStationStatus(), newEntity.getStationStatus()) &&
  240. Objects.equals(existing.getParkNums(), newEntity.getParkNums()) &&
  241. isBigDecimalEqual(existing.getStationLng(), newEntity.getStationLng()) &&
  242. isBigDecimalEqual(existing.getStationLat(), newEntity.getStationLat()) &&
  243. Objects.equals(existing.getSiteGuide(), newEntity.getSiteGuide()) &&
  244. Objects.equals(existing.getConstruction(), newEntity.getConstruction()) &&
  245. Objects.equals(existing.getPictures(), newEntity.getPictures()) &&
  246. Objects.equals(existing.getBusineHours(), newEntity.getBusineHours()) &&
  247. isBigDecimalEqual(existing.getElectricityFee(), newEntity.getElectricityFee()) &&
  248. isBigDecimalEqual(existing.getServiceFee(), newEntity.getServiceFee()) &&
  249. Objects.equals(existing.getParkFee(), newEntity.getParkFee()) &&
  250. Objects.equals(existing.getPayment(), newEntity.getPayment()) &&
  251. Objects.equals(existing.getSupportOrder(), newEntity.getSupportOrder()) &&
  252. Objects.equals(existing.getRemark(), newEntity.getRemark());
  253. }
  254. /**
  255. * 保存充电设备信息
  256. */
  257. private void saveEquipmentInfo(EquipmentInfo equipmentInfo, String stationId) {
  258. // 查询是否已存在
  259. ThirdPartyEquipmentInfo existingEquipment = equipmentInfoMapper.selectOne(
  260. new LambdaQueryWrapper<ThirdPartyEquipmentInfo>()
  261. .eq(ThirdPartyEquipmentInfo::getEquipmentId, equipmentInfo.getEquipmentID())
  262. );
  263. ThirdPartyEquipmentInfo entity = buildEquipmentEntity(equipmentInfo, stationId, existingEquipment);
  264. if (existingEquipment == null) {
  265. // 不存在,执行新增
  266. equipmentInfoMapper.insert(entity);
  267. log.debug("充电设备信息新增成功 - equipmentId: {}", equipmentInfo.getEquipmentID());
  268. } else if (!isEquipmentSame(existingEquipment, entity)) {
  269. // 存在且数据变化,执行更新
  270. equipmentInfoMapper.updateById(entity);
  271. log.debug("充电设备信息更新成功 - equipmentId: {}", equipmentInfo.getEquipmentID());
  272. } else {
  273. log.debug("充电设备信息未变化,跳过保存 - equipmentId: {}", equipmentInfo.getEquipmentID());
  274. }
  275. }
  276. /**
  277. * 构建充电设备实体
  278. */
  279. private ThirdPartyEquipmentInfo buildEquipmentEntity(EquipmentInfo equipmentInfo, String stationId, ThirdPartyEquipmentInfo existingEquipment) {
  280. ThirdPartyEquipmentInfo entity = new ThirdPartyEquipmentInfo();
  281. if (existingEquipment != null) {
  282. entity.setId(existingEquipment.getId());
  283. }
  284. // 设置字段值
  285. entity.setEquipmentId(equipmentInfo.getEquipmentID());
  286. entity.setStationId(stationId);
  287. entity.setManufacturerId(equipmentInfo.getManufacturerID());
  288. entity.setManufacturerName(equipmentInfo.getManufacturerName());
  289. entity.setEquipmentModel(equipmentInfo.getEquipmentModel());
  290. entity.setProductionDate(equipmentInfo.getProductionDate());
  291. entity.setEquipmentType(equipmentInfo.getEquipmentType());
  292. // 处理经纬度
  293. if (equipmentInfo.getEquipmentLng() != null) {
  294. entity.setEquipmentLng(BigDecimal.valueOf(equipmentInfo.getEquipmentLng()));
  295. }
  296. if (equipmentInfo.getEquipmentLat() != null) {
  297. entity.setEquipmentLat(BigDecimal.valueOf(equipmentInfo.getEquipmentLat()));
  298. }
  299. // 处理功率
  300. if (equipmentInfo.getPower() != null) {
  301. entity.setPower(BigDecimal.valueOf(equipmentInfo.getPower()));
  302. }
  303. entity.setEquipmentName(equipmentInfo.getEquipmentName());
  304. return entity;
  305. }
  306. /**
  307. * 判断充电设备信息是否相同
  308. */
  309. private boolean isEquipmentSame(ThirdPartyEquipmentInfo existing, ThirdPartyEquipmentInfo newEntity) {
  310. return Objects.equals(existing.getStationId(), newEntity.getStationId()) &&
  311. Objects.equals(existing.getManufacturerId(), newEntity.getManufacturerId()) &&
  312. Objects.equals(existing.getManufacturerName(), newEntity.getManufacturerName()) &&
  313. Objects.equals(existing.getEquipmentModel(), newEntity.getEquipmentModel()) &&
  314. Objects.equals(existing.getProductionDate(), newEntity.getProductionDate()) &&
  315. Objects.equals(existing.getEquipmentType(), newEntity.getEquipmentType()) &&
  316. isBigDecimalEqual(existing.getEquipmentLng(), newEntity.getEquipmentLng()) &&
  317. isBigDecimalEqual(existing.getEquipmentLat(), newEntity.getEquipmentLat()) &&
  318. isBigDecimalEqual(existing.getPower(), newEntity.getPower()) &&
  319. Objects.equals(existing.getEquipmentName(), newEntity.getEquipmentName());
  320. }
  321. /**
  322. * 保存充电接口信息
  323. */
  324. private void saveConnectorInfo(ConnectorInfo connectorInfo, String equipmentId, String stationId) {
  325. // 查询是否已存在
  326. ThirdPartyConnectorInfo existingConnector = connectorInfoMapper.selectOne(
  327. new LambdaQueryWrapper<ThirdPartyConnectorInfo>()
  328. .eq(ThirdPartyConnectorInfo::getConnectorId, connectorInfo.getConnectorID())
  329. );
  330. ThirdPartyConnectorInfo entity = buildConnectorEntity(connectorInfo, equipmentId, stationId, existingConnector);
  331. if (existingConnector == null) {
  332. // 不存在,执行新增
  333. connectorInfoMapper.insert(entity);
  334. log.debug("充电接口信息新增成功 - connectorId: {}", connectorInfo.getConnectorID());
  335. } else if (!isConnectorSame(existingConnector, entity)) {
  336. // 存在且数据变化,执行更新
  337. connectorInfoMapper.updateById(entity);
  338. log.debug("充电接口信息更新成功 - connectorId: {}", connectorInfo.getConnectorID());
  339. } else {
  340. log.debug("充电接口信息未变化,跳过保存 - connectorId: {}", connectorInfo.getConnectorID());
  341. }
  342. }
  343. /**
  344. * 构建充电接口实体
  345. */
  346. private ThirdPartyConnectorInfo buildConnectorEntity(ConnectorInfo connectorInfo, String equipmentId, String stationId, ThirdPartyConnectorInfo existingConnector) {
  347. ThirdPartyConnectorInfo entity = new ThirdPartyConnectorInfo();
  348. if (existingConnector != null) {
  349. entity.setId(existingConnector.getId());
  350. // 保留原有的status字段,该字段由设备状态推送接口单独维护
  351. entity.setStatus(existingConnector.getStatus());
  352. }
  353. // 设置字段值
  354. entity.setConnectorId(connectorInfo.getConnectorID());
  355. entity.setEquipmentId(equipmentId);
  356. entity.setStationId(stationId);
  357. entity.setConnectorName(connectorInfo.getConnectorName());
  358. entity.setConnectorType(connectorInfo.getConnectorType());
  359. entity.setVoltageUpperLimits(connectorInfo.getVoltageUpperLimits());
  360. entity.setVoltageLowerLimits(connectorInfo.getVoltageLowerLimits());
  361. entity.setCurrent(connectorInfo.getCurrent());
  362. // 处理功率
  363. if (connectorInfo.getPower() != null) {
  364. entity.setPower(BigDecimal.valueOf(connectorInfo.getPower()));
  365. }
  366. entity.setParkNo(connectorInfo.getParkNo());
  367. entity.setNationalStandard(connectorInfo.getNationalStandard());
  368. return entity;
  369. }
  370. /**
  371. * 判断充电接口信息是否相同
  372. */
  373. private boolean isConnectorSame(ThirdPartyConnectorInfo existing, ThirdPartyConnectorInfo newEntity) {
  374. return Objects.equals(existing.getEquipmentId(), newEntity.getEquipmentId()) &&
  375. Objects.equals(existing.getStationId(), newEntity.getStationId()) &&
  376. Objects.equals(existing.getConnectorName(), newEntity.getConnectorName()) &&
  377. Objects.equals(existing.getConnectorType(), newEntity.getConnectorType()) &&
  378. Objects.equals(existing.getVoltageUpperLimits(), newEntity.getVoltageUpperLimits()) &&
  379. Objects.equals(existing.getVoltageLowerLimits(), newEntity.getVoltageLowerLimits()) &&
  380. Objects.equals(existing.getCurrent(), newEntity.getCurrent()) &&
  381. isBigDecimalEqual(existing.getPower(), newEntity.getPower()) &&
  382. Objects.equals(existing.getParkNo(), newEntity.getParkNo()) &&
  383. Objects.equals(existing.getNationalStandard(), newEntity.getNationalStandard());
  384. }
  385. // ==================== 价格策略数据保存 ====================
  386. @Override
  387. @Transactional(rollbackFor = Exception.class)
  388. public void savePricePolicyInfo(ChargingPricePolicyVO pricePolicyVO) {
  389. if (pricePolicyVO == null) {
  390. log.warn("价格策略信息为空,跳过存储");
  391. return;
  392. }
  393. try {
  394. // 查询最新的价格策略记录
  395. ThirdPartyEquipmentPricePolicy latestPolicy = getLatestPricePolicy(
  396. pricePolicyVO.getEquipBizSeq(),
  397. pricePolicyVO.getConnectorID()
  398. );
  399. // 如果数据完全相同,仅更新同步时间
  400. if (latestPolicy != null && isPolicySame(latestPolicy, pricePolicyVO)) {
  401. log.info("价格策略数据未发生变化,仅更新同步时间 - equipBizSeq: {}, connectorId: {}",
  402. pricePolicyVO.getEquipBizSeq(), pricePolicyVO.getConnectorID());
  403. // 更新所有明细的同步时间
  404. updateAllDetailSyncTime(latestPolicy.getId());
  405. return;
  406. }
  407. // 数据发生变化
  408. Long policyId;
  409. if (latestPolicy != null) {
  410. // 已存在记录,执行更新
  411. policyId = updatePricePolicy(latestPolicy, pricePolicyVO);
  412. log.info("价格策略信息保存成功(更新记录) - equipBizSeq: {}, connectorId: {}, policyId: {}",
  413. pricePolicyVO.getEquipBizSeq(), pricePolicyVO.getConnectorID(), policyId);
  414. } else {
  415. // 不存在记录,执行新增
  416. policyId = insertNewPricePolicy(pricePolicyVO);
  417. log.info("价格策略信息保存成功(新增记录) - equipBizSeq: {}, connectorId: {}, policyId: {}",
  418. pricePolicyVO.getEquipBizSeq(), pricePolicyVO.getConnectorID(), policyId);
  419. }
  420. // 保存价格策略明细(对比后更新或新增,不做删除操作)
  421. if (!CollectionUtils.isEmpty(pricePolicyVO.getPolicyInfos())) {
  422. savePolicyInfoDetails(pricePolicyVO.getPolicyInfos(), policyId);
  423. }
  424. } catch (Exception e) {
  425. log.error("保存价格策略信息失败 - equipBizSeq: {}, connectorId: {}",
  426. pricePolicyVO.getEquipBizSeq(), pricePolicyVO.getConnectorID(), e);
  427. throw new RuntimeException("保存价格策略信息失败", e);
  428. }
  429. }
  430. @Override
  431. public IPage<ThirdPartyStationInfoVO> getStationInfoPageByEquipment(ThirdPartyStationInfoQuery queryParams) {
  432. // 构建分页
  433. Page<ThirdPartyStationInfoVO> page = new Page<>(queryParams.getPageNum(), queryParams.getPageSize());
  434. // 调用Mapper联表查询(固定设备所属方MA6DP6BE7)
  435. IPage<ThirdPartyStationInfoVO> resultPage = stationInfoMapper.selectStationInfoPageByEquipment(page, queryParams);
  436. // 填充单位名称
  437. for (ThirdPartyStationInfoVO vo : resultPage.getRecords()) {
  438. if (vo.getSalesType() != null) {
  439. if (vo.getSalesType() == 1 && vo.getFirmId() != null) {
  440. // 企业类型,查询企业名称
  441. FirmInfo firmInfo = firmInfoMapper.selectById(vo.getFirmId());
  442. if (firmInfo != null) {
  443. vo.setUnitName(firmInfo.getName());
  444. }
  445. } else if (vo.getSalesType() == 2 && vo.getThirdPartyId() != null) {
  446. // 渠道方类型,查询渠道方名称
  447. ThirdPartyInfo thirdPartyInfo = thirdPartyInfoMapper.selectById(vo.getThirdPartyId());
  448. if (thirdPartyInfo != null) {
  449. vo.setUnitName(thirdPartyInfo.getEcName());
  450. }
  451. }
  452. }
  453. }
  454. return resultPage;
  455. }
  456. /**
  457. * 获取最新的价格策略记录
  458. */
  459. private ThirdPartyEquipmentPricePolicy getLatestPricePolicy(String equipBizSeq, String connectorId) {
  460. List<ThirdPartyEquipmentPricePolicy> policies = pricePolicyMapper.selectList(
  461. Wrappers.<ThirdPartyEquipmentPricePolicy>lambdaQuery()
  462. .eq(ThirdPartyEquipmentPricePolicy::getEquipBizSeq, equipBizSeq)
  463. .eq(ThirdPartyEquipmentPricePolicy::getConnectorId, connectorId)
  464. .orderByDesc(ThirdPartyEquipmentPricePolicy::getCreateTime)
  465. .last("LIMIT 1")
  466. );
  467. return CollectionUtils.isEmpty(policies) ? null : policies.get(0);
  468. }
  469. /**
  470. * 判断价格策略是否相同(比较主表和明细表)
  471. */
  472. private boolean isPolicySame(ThirdPartyEquipmentPricePolicy latestPolicy, ChargingPricePolicyVO newPolicy) {
  473. // 比较主表字段
  474. if (!Objects.equals(latestPolicy.getSuccStat(), newPolicy.getSuccStat()) ||
  475. !Objects.equals(latestPolicy.getFailReason(), newPolicy.getFailReason()) ||
  476. !Objects.equals(latestPolicy.getSumPeriod(), newPolicy.getSumPeriod())) {
  477. return false;
  478. }
  479. // 查询明细表数据
  480. List<ThirdPartyPolicyInfo> existingDetails = policyInfoMapper.selectList(
  481. Wrappers.<ThirdPartyPolicyInfo>lambdaQuery()
  482. .eq(ThirdPartyPolicyInfo::getPricePolicyId, latestPolicy.getId())
  483. .orderBy(true, true, ThirdPartyPolicyInfo::getStartTime)
  484. );
  485. // 比较明细表
  486. return isPolicyDetailsSame(existingDetails, newPolicy.getPolicyInfos());
  487. }
  488. /**
  489. * 比较价格策略明细是否相同
  490. */
  491. private boolean isPolicyDetailsSame(List<ThirdPartyPolicyInfo> existingDetails,
  492. List<ChargingPricePolicyVO.PolicyInfo> newDetails) {
  493. if (CollectionUtils.isEmpty(existingDetails) && CollectionUtils.isEmpty(newDetails)) {
  494. return true;
  495. }
  496. if (CollectionUtils.isEmpty(existingDetails) || CollectionUtils.isEmpty(newDetails)) {
  497. return false;
  498. }
  499. if (existingDetails.size() != newDetails.size()) {
  500. return false;
  501. }
  502. // 按 StartTime 排序后比较
  503. List<ChargingPricePolicyVO.PolicyInfo> sortedNewDetails = newDetails.stream()
  504. .sorted(Comparator.comparing(ChargingPricePolicyVO.PolicyInfo::getStartTime))
  505. .collect(Collectors.toList());
  506. for (int i = 0; i < existingDetails.size(); i++) {
  507. ThirdPartyPolicyInfo existing = existingDetails.get(i);
  508. ChargingPricePolicyVO.PolicyInfo newDetail = sortedNewDetails.get(i);
  509. if (!Objects.equals(existing.getStartTime(), newDetail.getStartTime()) ||
  510. !isBigDecimalEqual(existing.getElecPrice(), newDetail.getElecPrice()) ||
  511. !isBigDecimalEqual(existing.getServicePrice(), newDetail.getServicePrice()) ||
  512. !Objects.equals(existing.getPeriodFlag(), newDetail.getPeriodFlag())) {
  513. return false;
  514. }
  515. }
  516. return true;
  517. }
  518. /**
  519. * 比较 BigDecimal 是否相等(处理 null 情况)
  520. */
  521. private boolean isBigDecimalEqual(BigDecimal a, BigDecimal b) {
  522. if (a == null && b == null) {
  523. return true;
  524. }
  525. if (a == null || b == null) {
  526. return false;
  527. }
  528. return a.compareTo(b) == 0;
  529. }
  530. /**
  531. * 插入新的价格策略记录
  532. */
  533. private Long insertNewPricePolicy(ChargingPricePolicyVO pricePolicyVO) {
  534. ThirdPartyEquipmentPricePolicy entity = new ThirdPartyEquipmentPricePolicy();
  535. // 设置字段值
  536. entity.setEquipBizSeq(pricePolicyVO.getEquipBizSeq());
  537. entity.setConnectorId(pricePolicyVO.getConnectorID());
  538. entity.setSuccStat(pricePolicyVO.getSuccStat());
  539. entity.setFailReason(pricePolicyVO.getFailReason());
  540. entity.setSumPeriod(pricePolicyVO.getSumPeriod());
  541. entity.setCreateTime(LocalDateTime.now());
  542. // 插入新记录
  543. pricePolicyMapper.insert(entity);
  544. return entity.getId();
  545. }
  546. /**
  547. * 更新价格策略记录
  548. */
  549. private Long updatePricePolicy(ThirdPartyEquipmentPricePolicy existingPolicy, ChargingPricePolicyVO pricePolicyVO) {
  550. existingPolicy.setSuccStat(pricePolicyVO.getSuccStat());
  551. existingPolicy.setFailReason(pricePolicyVO.getFailReason());
  552. existingPolicy.setSumPeriod(pricePolicyVO.getSumPeriod());
  553. pricePolicyMapper.updateById(existingPolicy);
  554. return existingPolicy.getId();
  555. }
  556. /**
  557. * 保存价格策略明细(对比后更新或新增)
  558. */
  559. private void savePolicyInfoDetails(List<ChargingPricePolicyVO.PolicyInfo> policyInfos, Long policyId) {
  560. // 查询已存在的明细记录,以 startTime 为 key
  561. Map<String, ThirdPartyPolicyInfo> existingMap = policyInfoMapper.selectList(
  562. Wrappers.<ThirdPartyPolicyInfo>lambdaQuery()
  563. .eq(ThirdPartyPolicyInfo::getPricePolicyId, policyId))
  564. .stream()
  565. .collect(Collectors.toMap(ThirdPartyPolicyInfo::getStartTime, info -> info, (v1, v2) -> v1));
  566. for (ChargingPricePolicyVO.PolicyInfo policyInfo : policyInfos) {
  567. ThirdPartyPolicyInfo existing = existingMap.get(policyInfo.getStartTime());
  568. if (existing == null) {
  569. // 不存在,执行新增
  570. savePolicyInfoDetail(policyInfo, policyId);
  571. log.debug("价格策略明细新增成功 - policyId: {}, startTime: {}", policyId, policyInfo.getStartTime());
  572. } else if (!isPolicyInfoSame(existing, policyInfo)) {
  573. // 检查上次更新时间,如果低于1小时,跳过本次更新
  574. if (existing.getUpdateTime() != null) {
  575. LocalDateTime oneHourAgo = LocalDateTime.now().minusHours(1);
  576. if (existing.getUpdateTime().isAfter(oneHourAgo)) {
  577. log.info("价格策略明细上次更新时间不足1小时,跳过更新 - policyId: {}, startTime: {}, lastUpdateTime: {}",
  578. policyId, policyInfo.getStartTime(), existing.getUpdateTime());
  579. continue; // 跳过本次更新
  580. }
  581. }
  582. // 存在且数据变化,且距离上次更新超过1小时,执行更新
  583. updatePolicyInfoDetail(existing, policyInfo);
  584. log.debug("价格策略明细更新成功 - policyId: {}, startTime: {}", policyId, policyInfo.getStartTime());
  585. // 价格变化后,同步更新 c_policy_fee 表中的 comp_sales_fee
  586. updateRelatedPolicyFeeCompSalesFee(policyId, policyInfo.getPeriodFlag());
  587. } else {
  588. // 数据未变化,但仍更新同步时间
  589. updateSyncTime(existing);
  590. log.debug("价格策略明细未变化,更新同步时间 - policyId: {}, startTime: {}", policyId, policyInfo.getStartTime());
  591. }
  592. }
  593. }
  594. /**
  595. * 更新同步时间(数据未变化时仅更新update_time)
  596. */
  597. private void updateSyncTime(ThirdPartyPolicyInfo existing) {
  598. existing.setUpdateTime(LocalDateTime.now());
  599. policyInfoMapper.updateById(existing);
  600. }
  601. /**
  602. * 更新所有明细的同步时间
  603. */
  604. private void updateAllDetailSyncTime(Long policyId) {
  605. policyInfoMapper.update(null, Wrappers.<ThirdPartyPolicyInfo>lambdaUpdate()
  606. .eq(ThirdPartyPolicyInfo::getPricePolicyId, policyId)
  607. .set(ThirdPartyPolicyInfo::getUpdateTime, LocalDateTime.now()));
  608. log.debug("批量更新价格策略明细同步时间完成 - policyId: {}", policyId);
  609. }
  610. /**
  611. * 判断价格策略明细是否相同
  612. */
  613. private boolean isPolicyInfoSame(ThirdPartyPolicyInfo existing, ChargingPricePolicyVO.PolicyInfo newInfo) {
  614. return isBigDecimalEqual(existing.getElecPrice(), newInfo.getElecPrice()) &&
  615. isBigDecimalEqual(existing.getServicePrice(), newInfo.getServicePrice()) &&
  616. Objects.equals(existing.getPeriodFlag(), newInfo.getPeriodFlag());
  617. }
  618. /**
  619. * 更新价格策略明细
  620. */
  621. private void updatePolicyInfoDetail(ThirdPartyPolicyInfo existing, ChargingPricePolicyVO.PolicyInfo policyInfo) {
  622. existing.setElecPrice(policyInfo.getElecPrice());
  623. existing.setServicePrice(policyInfo.getServicePrice());
  624. existing.setPeriodFlag(policyInfo.getPeriodFlag());
  625. existing.setUpdateTime(LocalDateTime.now());
  626. policyInfoMapper.updateById(existing);
  627. }
  628. /**
  629. * 价格策略变化后,同步更新 c_policy_fee 表中的 comp_sales_fee
  630. * 根据 pricePolicyId 查询对应的 stationInfoId,然后更新相关的 policy_fee 记录
  631. *
  632. * @param policyId 价格策略ID
  633. * @param periodFlag 时段标志(1-尖,2-峰,3-平,4-谷)
  634. */
  635. private void updateRelatedPolicyFeeCompSalesFee(Long policyId, Integer periodFlag) {
  636. try {
  637. // 1. 通过 policyId 查询 connectorId
  638. ThirdPartyEquipmentPricePolicy pricePolicy = pricePolicyMapper.selectById(policyId);
  639. if (pricePolicy == null) {
  640. log.warn("未找到价格策略记录,policyId: {}", policyId);
  641. return;
  642. }
  643. // 2. 通过 connectorId 查询 stationId
  644. ThirdPartyConnectorInfo connector = connectorInfoMapper.selectOne(
  645. Wrappers.<ThirdPartyConnectorInfo>lambdaQuery()
  646. .eq(ThirdPartyConnectorInfo::getConnectorId, pricePolicy.getConnectorId())
  647. .last("LIMIT 1")
  648. );
  649. if (connector == null) {
  650. log.warn("未找到充电接口记录,connectorId: {}", pricePolicy.getConnectorId());
  651. return;
  652. }
  653. // 3. 通过 stationId 查询 stationInfoId
  654. ThirdPartyStationInfo station = stationInfoMapper.selectOne(
  655. Wrappers.<ThirdPartyStationInfo>lambdaQuery()
  656. .eq(ThirdPartyStationInfo::getStationId, connector.getStationId())
  657. .last("LIMIT 1")
  658. );
  659. if (station == null) {
  660. log.warn("未找到站点记录,stationId: {}", connector.getStationId());
  661. return;
  662. }
  663. // 4. 查询该站点、该时段标志的所有 policy_fee 记录
  664. List<PolicyFee> policyFees = policyFeeMapper.selectList(
  665. Wrappers.<PolicyFee>lambdaQuery()
  666. .eq(PolicyFee::getStationInfoId, station.getId())
  667. .eq(PolicyFee::getPeriodFlag, periodFlag)
  668. .eq(PolicyFee::getIsDeleted, 0)
  669. );
  670. if (policyFees.isEmpty()) {
  671. log.debug("该站点该时段标志没有 policy_fee 记录,stationInfoId: {}, periodFlag: {}", station.getId(), periodFlag);
  672. return;
  673. }
  674. // 5. 重新计算并更新每条记录的 comp_sales_fee
  675. for (PolicyFee policyFee : policyFees) {
  676. BigDecimal newCompSalesFee = calculateCompSalesFeeForPolicyFee(
  677. station.getId(),
  678. periodFlag,
  679. policyFee.getOpFee()
  680. );
  681. policyFee.setCompSalesFee(newCompSalesFee);
  682. policyFeeMapper.updateById(policyFee);
  683. log.info("更新 policy_fee 的 comp_sales_fee - id: {}, stationInfoId: {}, periodFlag: {}, salesType: {}, 新值: {}",
  684. policyFee.getId(), station.getId(), periodFlag, policyFee.getSalesType(), newCompSalesFee);
  685. }
  686. } catch (Exception e) {
  687. log.error("更新 policy_fee 的 comp_sales_fee 失败 - policyId: {}, periodFlag: {}", policyId, periodFlag, e);
  688. // 不抛出异常,避免影响主流程
  689. }
  690. }
  691. /**
  692. * 计算 comp_sales_fee
  693. * 公式:compSalesFee = elec_price + service_price + op_fee
  694. * 注意:这里不加字典值,与 PolicyFeeServiceImpl 中的计算逻辑保持一致
  695. */
  696. private BigDecimal calculateCompSalesFeeForPolicyFee(Long stationInfoId, Integer periodFlag, BigDecimal opFee) {
  697. BigDecimal elecPrice = BigDecimal.ZERO;
  698. BigDecimal servicePrice = BigDecimal.ZERO;
  699. // 查询电价和服务费
  700. ThirdPartyPolicyInfo policyInfo = policyInfoMapper.selectElecAndServicePriceByStationAndPeriodFlag(stationInfoId, periodFlag);
  701. if (policyInfo != null) {
  702. elecPrice = policyInfo.getElecPrice() != null ? policyInfo.getElecPrice() : BigDecimal.ZERO;
  703. servicePrice = policyInfo.getServicePrice() != null ? policyInfo.getServicePrice() : BigDecimal.ZERO;
  704. }
  705. BigDecimal opFeeValue = opFee != null ? opFee : BigDecimal.ZERO;
  706. return elecPrice.add(servicePrice).add(opFeeValue);
  707. }
  708. /**
  709. * 保存价格策略明细
  710. */
  711. private void savePolicyInfoDetail(ChargingPricePolicyVO.PolicyInfo policyInfo, Long policyId) {
  712. ThirdPartyPolicyInfo entity = new ThirdPartyPolicyInfo();
  713. entity.setPricePolicyId(policyId);
  714. entity.setStartTime(policyInfo.getStartTime());
  715. entity.setElecPrice(policyInfo.getElecPrice());
  716. entity.setServicePrice(policyInfo.getServicePrice());
  717. entity.setPeriodFlag(policyInfo.getPeriodFlag());
  718. entity.setCreateTime(LocalDateTime.now());
  719. policyInfoMapper.insert(entity);
  720. }
  721. @Override
  722. public boolean updateStationTips(Long stationId, String stationTips) {
  723. return stationInfoMapper.update(null, Wrappers.<ThirdPartyStationInfo>lambdaUpdate()
  724. .eq(ThirdPartyStationInfo::getId, stationId)
  725. .set(ThirdPartyStationInfo::getStationTips, stationTips)) > 0;
  726. }
  727. @Override
  728. public StationDetailVO getStationDetail(Long stationId) {
  729. ThirdPartyStationInfo station = stationInfoMapper.selectById(stationId);
  730. if (station == null) {
  731. return null;
  732. }
  733. StationDetailVO vo = new StationDetailVO();
  734. vo.setId(station.getId());
  735. vo.setStationName(station.getStationName());
  736. vo.setStationTips(station.getStationTips());
  737. vo.setOwnBusinessHours(station.getOwnBusinessHours());
  738. vo.setCustomerServiceHotline(station.getCustomerServiceHotline());
  739. // 解析banner图片JSON
  740. if (station.getBannerPictures() != null) {
  741. try {
  742. vo.setBannerPictures(objectMapper.readValue(station.getBannerPictures(), new TypeReference<List<String>>() {}));
  743. } catch (JsonProcessingException e) {
  744. log.warn("解析banner图片失败", e);
  745. }
  746. }
  747. return vo;
  748. }
  749. @Override
  750. @Transactional(rollbackFor = Exception.class)
  751. public boolean updateStationDetail(StationDetailDTO dto) {
  752. ThirdPartyStationInfo station = stationInfoMapper.selectById(dto.getId());
  753. if (station == null) {
  754. return false;
  755. }
  756. // 转banner图片为JSON
  757. String bannerJson = null;
  758. if (dto.getBannerPictures() != null) {
  759. try {
  760. bannerJson = objectMapper.writeValueAsString(dto.getBannerPictures());
  761. } catch (JsonProcessingException e) {
  762. log.warn("banner图片转JSON失败", e);
  763. }
  764. }
  765. return stationInfoMapper.update(null, Wrappers.<ThirdPartyStationInfo>lambdaUpdate()
  766. .eq(ThirdPartyStationInfo::getId, dto.getId())
  767. .set(ThirdPartyStationInfo::getStationTips, dto.getStationTips())
  768. .set(ThirdPartyStationInfo::getOwnBusinessHours, dto.getOwnBusinessHours())
  769. .set(ThirdPartyStationInfo::getCustomerServiceHotline, dto.getCustomerServiceHotline())
  770. .set(bannerJson != null, ThirdPartyStationInfo::getBannerPictures, bannerJson)) > 0;
  771. }
  772. }