WashStationServiceImpl.java 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. package com.kym.service.impl;
  2. import com.alibaba.fastjson2.JSON;
  3. import com.alibaba.fastjson2.JSONObject;
  4. import com.github.pagehelper.Page;
  5. import com.github.pagehelper.PageHelper;
  6. import com.kym.common.utils.CommUtil;
  7. import com.kym.entity.common.PageBean;
  8. import com.kym.entity.WashDevice;
  9. import com.kym.entity.WashStation;
  10. import com.kym.entity.queryParams.StationQueryParams;
  11. import com.kym.entity.vo.WashStationVo;
  12. import com.kym.mapper.WashStationMapper;
  13. import com.kym.service.RechargeConfigGroupService;
  14. import com.kym.service.WashDeviceService;
  15. import com.kym.service.WashStationService;
  16. import com.kym.service.cache.KymCache;
  17. import com.kym.service.mybatisplus.MyBaseServiceImpl;
  18. import jakarta.annotation.PostConstruct;
  19. import org.springframework.beans.BeanUtils;
  20. import org.springframework.stereotype.Service;
  21. import java.util.Collections;
  22. import java.util.Comparator;
  23. import java.util.HashMap;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.stream.Collectors;
  27. /**
  28. * <p>
  29. * 洗车站点表 服务实现类
  30. * </p>
  31. *
  32. * @author skyline
  33. * @since 2024-09-11
  34. */
  35. @Service
  36. public class WashStationServiceImpl extends MyBaseServiceImpl<WashStationMapper, WashStation> implements WashStationService {
  37. @PostConstruct
  38. public void init() {
  39. var list = list();
  40. KymCache.INSTANCE.putStationId2Name(list.stream().collect(Collectors.toMap(WashStation::getStationId, WashStation::getStationName)));
  41. KymCache.INSTANCE.putStationId2ParkingQrCodeUrl(list.stream().collect(Collectors.toMap(WashStation::getStationId, WashStation::getParkingQrCode)));
  42. KymCache.INSTANCE.putStationId2ParkingCouponMinAmount(list.stream().filter(s -> s.getParkingCouponMinAmount() != null).collect(Collectors.toMap(WashStation::getStationId, WashStation::getParkingCouponMinAmount)));
  43. }
  44. private final WashDeviceService washDeviceService;
  45. private final RechargeConfigGroupService rechargeConfigGroupService;
  46. public WashStationServiceImpl(WashDeviceService washDeviceService, RechargeConfigGroupService rechargeConfigGroupService) {
  47. this.washDeviceService = washDeviceService;
  48. this.rechargeConfigGroupService = rechargeConfigGroupService;
  49. }
  50. // ====================================================== 运营平台接口 Start ===========================================================
  51. @Override
  52. public void add(WashStation station) {
  53. Long count = lambdaQuery().eq(WashStation::getStationId, station.getStationId()).count();
  54. CommUtil.asserts(count == 0, "站点已存在");
  55. save(station);
  56. // 新站点不自动创建分组,默认使用平台默认分组。管理员可在充值配置中为该站点分配专属分组。
  57. // 如需自动创建:rechargeConfigGroupService.createGroup(new RechargeConfigGroup(), List.of(station.getStationId()));
  58. }
  59. @Override
  60. public void modify(WashStation station) {
  61. CommUtil.assertsNonNulls(List.of(station.getStationName(), station.getStationId(), station.getStationType(), station.getId()), "参数异常");
  62. List<WashStation> list = lambdaQuery().eq(WashStation::getStationName, station.getStationName()).list();
  63. CommUtil.asserts(list.size() <= 1, "站点信息异常");
  64. if (list.size() == 1) {
  65. CommUtil.asserts(list.get(0).getStationName().equalsIgnoreCase(station.getStationName()), "站点已存在");
  66. }
  67. updateById(station);
  68. }
  69. @Override
  70. public PageBean<WashStation> list(StationQueryParams query) {
  71. PageHelper.startPage(query.getPageNum(), query.getPageSize());
  72. List<WashStation> list = lambdaQuery()
  73. .like(CommUtil.isNotEmptyAndNull(query.getStationName()), WashStation::getStationName, query.getStationName())
  74. .like(CommUtil.isNotEmptyAndNull(query.getAddress()), WashStation::getAddress, query.getAddress())
  75. .eq(CommUtil.isNotEmptyAndNull(query.getStationType()), WashStation::getStationType, query.getStationType())
  76. .list();
  77. return new PageBean<>(list);
  78. }
  79. @Override
  80. public WashStation detail(Long id) {
  81. return getById(id);
  82. }
  83. @Override
  84. public void remove(Long id) {
  85. }
  86. // ====================================================== 运营平台接口 Start ===========================================================
  87. // ====================================================== 小程序接口 Start ===========================================================
  88. /**
  89. * 默认最大距离(公里)
  90. */
  91. private static final double DEFAULT_MAX_DISTANCE_KM = 50d;
  92. @Override
  93. public PageBean<WashStationVo> listStationForApp(StationQueryParams params) {
  94. // 传入有效经纬度时启用附近站点模式:内存计算距离、过滤、排序后手动分页
  95. if (hasUserLocation(params)) {
  96. return listStationByDistance(params);
  97. }
  98. PageHelper.startPage(params.getPageNum(), params.getPageSize());
  99. var stationList = lambdaQuery()
  100. .eq(CommUtil.isNotEmptyAndNull(params.getStationId()), WashStation::getStationId, params.getStationId())
  101. .like(CommUtil.isNotEmptyAndNull(params.getStationName()), WashStation::getStationName, params.getStationName())
  102. .list();
  103. if (stationList.isEmpty()) {
  104. return new PageBean<>(Collections.emptyList());
  105. }
  106. var voList = buildStationVoList(stationList, Collections.emptyMap());
  107. return new PageBean<>(voList);
  108. }
  109. /**
  110. * 附近站点:站点表数据量小且坐标存于 JSON 字符串无法走索引,
  111. * 故全量查出后在内存中按 Haversine 公式计算距离,过滤超远站点并按距离升序,再分页。
  112. */
  113. private PageBean<WashStationVo> listStationByDistance(StationQueryParams params) {
  114. double userLat = params.getLatitude();
  115. double userLng = params.getLongitude();
  116. double maxDistance = params.getMaxDistance() != null && params.getMaxDistance() > 0
  117. ? params.getMaxDistance() : DEFAULT_MAX_DISTANCE_KM;
  118. // 距离模式下需要先过滤排序再分页,因此不能用 PageHelper 分页查询
  119. Map<String, Double> distanceMap = new HashMap<>();
  120. var matchedList = lambdaQuery()
  121. .eq(CommUtil.isNotEmptyAndNull(params.getStationId()), WashStation::getStationId, params.getStationId())
  122. .like(CommUtil.isNotEmptyAndNull(params.getStationName()), WashStation::getStationName, params.getStationName())
  123. .list().stream()
  124. .filter(station -> {
  125. Double distance = calcDistanceKm(userLat, userLng, station.getLocation());
  126. if (distance == null || distance > maxDistance) {
  127. return false;
  128. }
  129. distanceMap.put(station.getStationId(), distance);
  130. return true;
  131. })
  132. .sorted(Comparator.comparing(distanceMap::get))
  133. .toList();
  134. int total = matchedList.size();
  135. int pageNum = Math.max(params.getPageNum(), 1);
  136. int pageSize = Math.max(params.getPageSize(), 1);
  137. int fromIndex = (pageNum - 1) * pageSize;
  138. List<WashStation> pageList = fromIndex >= total
  139. ? Collections.emptyList()
  140. : matchedList.subList(fromIndex, Math.min(fromIndex + pageSize, total));
  141. if (pageList.isEmpty()) {
  142. Page<WashStationVo> emptyPage = new Page<>(pageNum, pageSize);
  143. emptyPage.setTotal(total);
  144. return new PageBean<>(emptyPage);
  145. }
  146. var voList = buildStationVoList(pageList, distanceMap);
  147. Page<WashStationVo> page = new Page<>(pageNum, pageSize);
  148. page.setTotal(total);
  149. page.addAll(voList);
  150. return new PageBean<>(page);
  151. }
  152. private static boolean hasUserLocation(StationQueryParams params) {
  153. return params.getLatitude() != null && params.getLongitude() != null
  154. && (params.getLatitude() != 0 || params.getLongitude() != 0);
  155. }
  156. /**
  157. * 解析站点 location JSON 并计算与用户的球面距离(公里),坐标缺失或格式异常返回 null
  158. */
  159. private static Double calcDistanceKm(double userLat, double userLng, String location) {
  160. if (CommUtil.isEmptyOrNull(location)) {
  161. return null;
  162. }
  163. double stationLat;
  164. double stationLng;
  165. try {
  166. JSONObject json = JSON.parseObject(location);
  167. stationLat = json.getDoubleValue("stationLat");
  168. stationLng = json.getDoubleValue("stationLng");
  169. } catch (Exception e) {
  170. return null;
  171. }
  172. if (stationLat == 0 && stationLng == 0) {
  173. return null;
  174. }
  175. double radLat1 = Math.toRadians(userLat);
  176. double radLat2 = Math.toRadians(stationLat);
  177. double a = radLat1 - radLat2;
  178. double b = Math.toRadians(userLng) - Math.toRadians(stationLng);
  179. double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2)
  180. + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
  181. s = s * 6378.137;
  182. return Math.round(s * 100) / 100.0;
  183. }
  184. /**
  185. * 组装站点 VO(含空闲设备数),distanceMap 非空时回填距离
  186. */
  187. private List<WashStationVo> buildStationVoList(List<WashStation> stationList, Map<String, Double> distanceMap) {
  188. // 查询站点下的所有设备,按照站点统计可用设备数量
  189. var res = washDeviceService.lambdaQuery().in(WashDevice::getStationId, stationList.stream().map(WashStation::getStationId).toList())
  190. .eq(WashDevice::getState, WashDevice.STATE_空闲)
  191. .list().stream().collect(Collectors.groupingBy(WashDevice::getStationId, Collectors.counting()));
  192. return stationList.stream().map(station -> {
  193. var vo = new WashStationVo();
  194. BeanUtils.copyProperties(station, vo);
  195. vo.setIdleParkingNum(Math.toIntExact(res.getOrDefault(station.getStationId(), 0L)));
  196. vo.setDistance(distanceMap.get(station.getStationId()));
  197. return vo;
  198. }).toList();
  199. }
  200. // ====================================================== 小程序接口 End ===========================================================
  201. }