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 ===========================================================
}