| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227 |
- 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;
- /**
- * <p>
- * 洗车站点表 服务实现类
- * </p>
- *
- * @author skyline
- * @since 2024-09-11
- */
- @Service
- public class WashStationServiceImpl extends MyBaseServiceImpl<WashStationMapper, WashStation> 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<WashStation> 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<WashStation> list(StationQueryParams query) {
- PageHelper.startPage(query.getPageNum(), query.getPageSize());
- List<WashStation> 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<WashStationVo> 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<WashStationVo> 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<String, Double> 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<WashStation> pageList = fromIndex >= total
- ? Collections.emptyList()
- : matchedList.subList(fromIndex, Math.min(fromIndex + pageSize, total));
- if (pageList.isEmpty()) {
- Page<WashStationVo> emptyPage = new Page<>(pageNum, pageSize);
- emptyPage.setTotal(total);
- return new PageBean<>(emptyPage);
- }
- var voList = buildStationVoList(pageList, distanceMap);
- Page<WashStationVo> 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<WashStationVo> buildStationVoList(List<WashStation> stationList, Map<String, Double> 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 ===========================================================
- }
|