TGeoFenceServiceImpl.java 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. package com.ylx.massage.service.impl;
  2. import cn.hutool.core.collection.CollUtil;
  3. import cn.hutool.core.util.ObjectUtil;
  4. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  5. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  6. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  7. import com.ylx.common.exception.ServiceException;
  8. import com.ylx.common.utils.DateUtils;
  9. import com.ylx.common.utils.DistanceUtil;
  10. import com.ylx.massage.domain.TGeoFence;
  11. import com.ylx.massage.mapper.TGeoFenceMapper;
  12. import com.ylx.massage.service.TGeoFenceService;
  13. import com.ylx.order.service.IGeoRiskInfo;
  14. import org.apache.commons.lang3.StringUtils;
  15. import org.springframework.stereotype.Service;
  16. import java.math.BigDecimal;
  17. import java.util.Collections;
  18. import java.util.List;
  19. import java.util.stream.Collectors;
  20. /**
  21. * 地理围栏服务实现类
  22. */
  23. @Service
  24. public class TGeoFenceServiceImpl extends ServiceImpl<TGeoFenceMapper, TGeoFence> implements TGeoFenceService {
  25. private static final BigDecimal MIN_LONGITUDE = new BigDecimal("-180");
  26. private static final BigDecimal MAX_LONGITUDE = new BigDecimal("180");
  27. private static final BigDecimal MIN_LATITUDE = new BigDecimal("-90");
  28. private static final BigDecimal MAX_LATITUDE = new BigDecimal("90");
  29. private static final int RISK_LOW = 1;
  30. private static final int RISK_MEDIUM = 2;
  31. private static final int RISK_HIGH = 3;
  32. private static final int NOT_DELETE = 0;
  33. @Override
  34. public Page<TGeoFence> pageGeoFence(Page<TGeoFence> page, TGeoFence geoFence) {
  35. LambdaQueryWrapper<TGeoFence> queryWrapper = buildQueryWrapper(geoFence);
  36. queryWrapper.orderByAsc(TGeoFence::getId);
  37. return this.page(page, queryWrapper);
  38. }
  39. @Override
  40. public List<TGeoFence> listMapGeoFence(TGeoFence geoFence) {
  41. LambdaQueryWrapper<TGeoFence> queryWrapper = buildQueryWrapper(geoFence);
  42. queryWrapper.select(TGeoFence::getId, TGeoFence::getCityCode, TGeoFence::getCityName,
  43. TGeoFence::getFenceName, TGeoFence::getAddress, TGeoFence::getLongitude,
  44. TGeoFence::getLatitude, TGeoFence::getRadiusKm, TGeoFence::getRiskLevel);
  45. queryWrapper.isNotNull(TGeoFence::getLongitude)
  46. .isNotNull(TGeoFence::getLatitude)
  47. .orderByAsc(TGeoFence::getId);
  48. return this.list(queryWrapper);
  49. }
  50. @Override
  51. public Boolean addGeoFence(TGeoFence geoFence) {
  52. validateGeoFenceParam(geoFence);
  53. geoFence.setId(null);
  54. geoFence.setIsDelete(NOT_DELETE);
  55. geoFence.setCreateTime(DateUtils.getNowDate());
  56. geoFence.setUpdateTime(DateUtils.getNowDate());
  57. boolean saved = this.save(geoFence);
  58. if (!saved) {
  59. throw new ServiceException("新增围栏失败");
  60. }
  61. return true;
  62. }
  63. @Override
  64. public Boolean updateGeoFence(TGeoFence geoFence) {
  65. validateUpdateParam(geoFence);
  66. TGeoFence exists = this.getById(geoFence.getId());
  67. if (exists == null) {
  68. throw new ServiceException("围栏不存在");
  69. }
  70. TGeoFence update = new TGeoFence();
  71. update.setId(geoFence.getId());
  72. update.setCityCode(geoFence.getCityCode());
  73. update.setCityName(geoFence.getCityName());
  74. update.setFenceName(geoFence.getFenceName());
  75. update.setFenceIntro(geoFence.getFenceIntro());
  76. update.setAddress(geoFence.getAddress());
  77. update.setLongitude(geoFence.getLongitude());
  78. update.setLatitude(geoFence.getLatitude());
  79. update.setRadiusKm(geoFence.getRadiusKm());
  80. update.setRiskLevel(geoFence.getRiskLevel());
  81. update.setUpdateTime(DateUtils.getNowDate());
  82. boolean updated = this.updateById(update);
  83. if (!updated) {
  84. throw new ServiceException("编辑围栏失败");
  85. }
  86. return true;
  87. }
  88. @Override
  89. public Boolean deleteGeoFence(TGeoFence geoFence) {
  90. Long id = getValidId(geoFence);
  91. TGeoFence exists = this.getById(id);
  92. if (exists == null) {
  93. throw new ServiceException("围栏不存在");
  94. }
  95. boolean removed = this.removeById(id);
  96. if (!removed) {
  97. throw new ServiceException("删除围栏失败");
  98. }
  99. return true;
  100. }
  101. @Override
  102. public List<TGeoFence> selectValidFences(String cityCode) {
  103. LambdaQueryWrapper<TGeoFence> queryWrapper = new LambdaQueryWrapper<>();
  104. queryWrapper.eq(StringUtils.isNotBlank(cityCode), TGeoFence::getCityCode, cityCode)
  105. .eq(TGeoFence::getIsDelete, NOT_DELETE);
  106. return this.list(queryWrapper);
  107. }
  108. /**
  109. * 过滤可用围栏:剔除坐标/半径为空、半径<=0的无效围栏,减少内层循环次数
  110. */
  111. @Override
  112. public List<TGeoFence> filterUsableFence(List<TGeoFence> allFence) {
  113. if (CollUtil.isEmpty(allFence)) {
  114. return Collections.emptyList();
  115. }
  116. return allFence.stream()
  117. .filter(fence -> !ObjectUtil.hasNull(fence.getLatitude(), fence.getLongitude(), fence.getRadiusKm()))
  118. .filter(fence -> fence.getRadiusKm().compareTo(BigDecimal.ZERO) > 0)
  119. .collect(Collectors.toList());
  120. }
  121. /**
  122. * 计算单条订单围栏距离、是否高风险区域、围栏备注
  123. */
  124. @Override
  125. public void calcOrderGeoFenceInfo(IGeoRiskInfo info, List<TGeoFence> usableFence) {
  126. // 用户坐标为空直接标记非风险
  127. BigDecimal userLat = info.getUserLatitude();
  128. BigDecimal userLon = info.getUserLongitude();
  129. if (ObjectUtil.hasNull(userLat, userLon) || CollUtil.isEmpty(usableFence)) {
  130. info.setIsHighRiskArea(false);
  131. info.setDistanceKm(null);
  132. info.setFenceIntro(null);
  133. return;
  134. }
  135. BigDecimal minDistanceKm = null;
  136. TGeoFence nearestFence = null;
  137. // 遍历可用围栏,计算最小距离围栏
  138. for (TGeoFence fence : usableFence) {
  139. BigDecimal distKm = DistanceUtil.formatDistanceInKilometers(userLat, userLon, fence.getLatitude(), fence.getLongitude());
  140. if (ObjectUtil.isNull(distKm)) {
  141. continue;
  142. }
  143. // 更新最近围栏
  144. if (ObjectUtil.isNull(minDistanceKm) || distKm.compareTo(minDistanceKm) < 0) {
  145. minDistanceKm = distKm;
  146. nearestFence = fence;
  147. }
  148. }
  149. // 赋值围栏相关信息
  150. if (ObjectUtil.isNull(minDistanceKm) || ObjectUtil.isNull(nearestFence)) {
  151. info.setIsHighRiskArea(false);
  152. info.setDistanceKm(null);
  153. info.setFenceIntro(null);
  154. return;
  155. }
  156. info.setDistanceKm(minDistanceKm);
  157. // 判断是否在围栏半径内
  158. boolean isHighRisk = minDistanceKm.compareTo(nearestFence.getRadiusKm()) <= 0;
  159. info.setIsHighRiskArea(isHighRisk);
  160. info.setFenceIntro(nearestFence.getFenceIntro());
  161. }
  162. private void validateUpdateParam(TGeoFence geoFence) {
  163. if (geoFence == null) {
  164. throw new ServiceException("参数不能为空");
  165. }
  166. if (geoFence.getId() == null || geoFence.getId() <= 0) {
  167. throw new ServiceException("围栏ID不能为空");
  168. }
  169. validateGeoFenceParam(geoFence);
  170. }
  171. private Long getValidId(TGeoFence geoFence) {
  172. if (geoFence == null) {
  173. throw new ServiceException("参数不能为空");
  174. }
  175. if (geoFence.getId() == null || geoFence.getId() <= 0) {
  176. throw new ServiceException("围栏ID不能为空");
  177. }
  178. return geoFence.getId();
  179. }
  180. private void validateGeoFenceParam(TGeoFence geoFence) {
  181. if (geoFence == null) {
  182. throw new ServiceException("参数不能为空");
  183. }
  184. if (StringUtils.isBlank(geoFence.getCityCode())) {
  185. throw new ServiceException("城市编码不能为空");
  186. }
  187. if (StringUtils.isBlank(geoFence.getCityName())) {
  188. throw new ServiceException("城市名称不能为空");
  189. }
  190. if (StringUtils.isBlank(geoFence.getFenceName())) {
  191. throw new ServiceException("围栏名称不能为空");
  192. }
  193. if (StringUtils.isBlank(geoFence.getFenceIntro())) {
  194. throw new ServiceException("围栏介绍不能为空");
  195. }
  196. if (geoFence.getRadiusKm() == null || geoFence.getRadiusKm().compareTo(BigDecimal.ZERO) <= 0) {
  197. throw new ServiceException("围栏半径必须大于0");
  198. }
  199. if (!isValidRiskLevel(geoFence.getRiskLevel())) {
  200. throw new ServiceException("风险等级值不正确");
  201. }
  202. validateCoordinate(geoFence.getLongitude(), geoFence.getLatitude());
  203. }
  204. private boolean isValidRiskLevel(Integer riskLevel) {
  205. return riskLevel != null && (riskLevel == RISK_LOW || riskLevel == RISK_MEDIUM || riskLevel == RISK_HIGH);
  206. }
  207. /**
  208. * 构建查询条件。
  209. *
  210. * @param geoFence
  211. * @return LambdaQueryWrapper<TGeoFence>
  212. */
  213. private LambdaQueryWrapper<TGeoFence> buildQueryWrapper(TGeoFence geoFence) {
  214. LambdaQueryWrapper<TGeoFence> queryWrapper = new LambdaQueryWrapper<>();
  215. if (geoFence != null) {
  216. queryWrapper.eq(StringUtils.isNotBlank(geoFence.getCityCode()), TGeoFence::getCityCode, geoFence.getCityCode())
  217. .like(StringUtils.isNotBlank(geoFence.getFenceName()), TGeoFence::getFenceName, geoFence.getFenceName())
  218. .like(StringUtils.isNotBlank(geoFence.getAddress()), TGeoFence::getAddress, geoFence.getAddress());
  219. }
  220. return queryWrapper;
  221. }
  222. /**
  223. * 验证地理坐标栏中心点坐标是否有效。
  224. *
  225. * @param longitude 经度
  226. * @param latitude 纬度
  227. */
  228. private void validateCoordinate(BigDecimal longitude, BigDecimal latitude) {
  229. if (longitude == null) {
  230. throw new ServiceException("围栏中心点经度不能为空");
  231. }
  232. if (latitude == null) {
  233. throw new ServiceException("围栏中心点纬度不能为空");
  234. }
  235. if (longitude.compareTo(MIN_LONGITUDE) < 0 || longitude.compareTo(MAX_LONGITUDE) > 0) {
  236. throw new ServiceException("经度必须在-180到180之间");
  237. }
  238. if (latitude.compareTo(MIN_LATITUDE) < 0 || latitude.compareTo(MAX_LATITUDE) > 0) {
  239. throw new ServiceException("纬度必须在-90到90之间");
  240. }
  241. }
  242. }