package com.kym.service.impl; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import com.kym.common.utils.CommUtil; import com.kym.entity.common.PageBean; import com.kym.entity.WashDevice; import com.kym.entity.WashStation; import com.kym.entity.queryParams.StationQueryParams; import com.kym.entity.vo.WashStationVo; import com.kym.mapper.WashStationMapper; import com.kym.service.RechargeConfigGroupService; import com.kym.service.WashDeviceService; import com.kym.service.WashStationService; import com.kym.service.cache.KymCache; import com.kym.service.mybatisplus.MyBaseServiceImpl; import jakarta.annotation.PostConstruct; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** *

* 洗车站点表 服务实现类 *

* * @author skyline * @since 2024-09-11 */ @Service public class WashStationServiceImpl extends MyBaseServiceImpl implements WashStationService { @PostConstruct public void init() { var list = list(); KymCache.INSTANCE.putStationId2Name(list.stream().collect(Collectors.toMap(WashStation::getStationId, WashStation::getStationName))); KymCache.INSTANCE.putStationId2ParkingQrCodeUrl(list.stream().collect(Collectors.toMap(WashStation::getStationId, WashStation::getParkingQrCode))); KymCache.INSTANCE.putStationId2ParkingCouponMinAmount(list.stream().filter(s -> s.getParkingCouponMinAmount() != null).collect(Collectors.toMap(WashStation::getStationId, WashStation::getParkingCouponMinAmount))); } private final WashDeviceService washDeviceService; private final RechargeConfigGroupService rechargeConfigGroupService; public WashStationServiceImpl(WashDeviceService washDeviceService, RechargeConfigGroupService rechargeConfigGroupService) { this.washDeviceService = washDeviceService; this.rechargeConfigGroupService = rechargeConfigGroupService; } // ====================================================== 运营平台接口 Start =========================================================== @Override public void add(WashStation station) { Long count = lambdaQuery().eq(WashStation::getStationId, station.getStationId()).count(); CommUtil.asserts(count == 0, "站点已存在"); save(station); // 新站点不自动创建分组,默认使用平台默认分组。管理员可在充值配置中为该站点分配专属分组。 // 如需自动创建:rechargeConfigGroupService.createGroup(new RechargeConfigGroup(), List.of(station.getStationId())); } @Override public void modify(WashStation station) { CommUtil.assertsNonNulls(List.of(station.getStationName(), station.getStationId(), station.getStationType(), station.getId()), "参数异常"); List list = lambdaQuery().eq(WashStation::getStationName, station.getStationName()).list(); CommUtil.asserts(list.size() <= 1, "站点信息异常"); if (list.size() == 1) { CommUtil.asserts(list.get(0).getStationName().equalsIgnoreCase(station.getStationName()), "站点已存在"); } updateById(station); } @Override public PageBean list(StationQueryParams query) { PageHelper.startPage(query.getPageNum(), query.getPageSize()); List list = lambdaQuery() .like(CommUtil.isNotEmptyAndNull(query.getStationName()), WashStation::getStationName, query.getStationName()) .like(CommUtil.isNotEmptyAndNull(query.getAddress()), WashStation::getAddress, query.getAddress()) .eq(CommUtil.isNotEmptyAndNull(query.getStationType()), WashStation::getStationType, query.getStationType()) .list(); return new PageBean<>(list); } @Override public WashStation detail(Long id) { return getById(id); } @Override public void remove(Long id) { } // ====================================================== 运营平台接口 Start =========================================================== // ====================================================== 小程序接口 Start =========================================================== /** * 默认最大距离(公里) */ private static final double DEFAULT_MAX_DISTANCE_KM = 50d; @Override public PageBean listStationForApp(StationQueryParams params) { // 传入有效经纬度时启用附近站点模式:内存计算距离、过滤、排序后手动分页 if (hasUserLocation(params)) { return listStationByDistance(params); } PageHelper.startPage(params.getPageNum(), params.getPageSize()); var stationList = lambdaQuery() .eq(CommUtil.isNotEmptyAndNull(params.getStationId()), WashStation::getStationId, params.getStationId()) .like(CommUtil.isNotEmptyAndNull(params.getStationName()), WashStation::getStationName, params.getStationName()) .list(); if (stationList.isEmpty()) { return new PageBean<>(Collections.emptyList()); } var voList = buildStationVoList(stationList, Collections.emptyMap()); return new PageBean<>(voList); } /** * 附近站点:站点表数据量小且坐标存于 JSON 字符串无法走索引, * 故全量查出后在内存中按 Haversine 公式计算距离,过滤超远站点并按距离升序,再分页。 */ private PageBean listStationByDistance(StationQueryParams params) { double userLat = params.getLatitude(); double userLng = params.getLongitude(); double maxDistance = params.getMaxDistance() != null && params.getMaxDistance() > 0 ? params.getMaxDistance() : DEFAULT_MAX_DISTANCE_KM; // 距离模式下需要先过滤排序再分页,因此不能用 PageHelper 分页查询 Map distanceMap = new HashMap<>(); var matchedList = lambdaQuery() .eq(CommUtil.isNotEmptyAndNull(params.getStationId()), WashStation::getStationId, params.getStationId()) .like(CommUtil.isNotEmptyAndNull(params.getStationName()), WashStation::getStationName, params.getStationName()) .list().stream() .filter(station -> { Double distance = calcDistanceKm(userLat, userLng, station.getLocation()); if (distance == null || distance > maxDistance) { return false; } distanceMap.put(station.getStationId(), distance); return true; }) .sorted(Comparator.comparing(distanceMap::get)) .toList(); int total = matchedList.size(); int pageNum = Math.max(params.getPageNum(), 1); int pageSize = Math.max(params.getPageSize(), 1); int fromIndex = (pageNum - 1) * pageSize; List pageList = fromIndex >= total ? Collections.emptyList() : matchedList.subList(fromIndex, Math.min(fromIndex + pageSize, total)); if (pageList.isEmpty()) { Page emptyPage = new Page<>(pageNum, pageSize); emptyPage.setTotal(total); return new PageBean<>(emptyPage); } var voList = buildStationVoList(pageList, distanceMap); Page page = new Page<>(pageNum, pageSize); page.setTotal(total); page.addAll(voList); return new PageBean<>(page); } private static boolean hasUserLocation(StationQueryParams params) { return params.getLatitude() != null && params.getLongitude() != null && (params.getLatitude() != 0 || params.getLongitude() != 0); } /** * 解析站点 location JSON 并计算与用户的球面距离(公里),坐标缺失或格式异常返回 null */ private static Double calcDistanceKm(double userLat, double userLng, String location) { if (CommUtil.isEmptyOrNull(location)) { return null; } double stationLat; double stationLng; try { JSONObject json = JSON.parseObject(location); stationLat = json.getDoubleValue("stationLat"); stationLng = json.getDoubleValue("stationLng"); } catch (Exception e) { return null; } if (stationLat == 0 && stationLng == 0) { return null; } double radLat1 = Math.toRadians(userLat); double radLat2 = Math.toRadians(stationLat); double a = radLat1 - radLat2; double b = Math.toRadians(userLng) - Math.toRadians(stationLng); double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2))); s = s * 6378.137; return Math.round(s * 100) / 100.0; } /** * 组装站点 VO(含空闲设备数),distanceMap 非空时回填距离 */ private List buildStationVoList(List stationList, Map distanceMap) { // 查询站点下的所有设备,按照站点统计可用设备数量 var res = washDeviceService.lambdaQuery().in(WashDevice::getStationId, stationList.stream().map(WashStation::getStationId).toList()) .eq(WashDevice::getState, WashDevice.STATE_空闲) .list().stream().collect(Collectors.groupingBy(WashDevice::getStationId, Collectors.counting())); return stationList.stream().map(station -> { var vo = new WashStationVo(); BeanUtils.copyProperties(station, vo); vo.setIdleParkingNum(Math.toIntExact(res.getOrDefault(station.getStationId(), 0L))); vo.setDistance(distanceMap.get(station.getStationId())); return vo; }).toList(); } // ====================================================== 小程序接口 End =========================================================== }