package com.xiaopeng.map.service.serviceimpl;
|
|
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.util.StrUtil;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.xiaopeng.api.model.map.XpGeoFencingPolygon;
|
import com.xiaopeng.api.model.operation.*;
|
import com.xiaopeng.api.query.operation.XpStationQuery;
|
import com.xiaopeng.api.vo.account.LoginUser;
|
import com.xiaopeng.api.vo.operation.XpStationVo;
|
import com.xiaopeng.common.core.domain.R;
|
import com.xiaopeng.common.core.exception.ServiceException;
|
import com.xiaopeng.common.core.web.page.PageInfo;
|
import com.xiaopeng.common.security.service.TokenService;
|
import com.xiaopeng.map.constant.I18nKeyConstant;
|
import com.xiaopeng.map.mapper.XpRegionMapper;
|
import com.xiaopeng.map.mapper.XpStationMapper;
|
import com.xiaopeng.map.service.XpElectronicFenceAreaService;
|
import com.xiaopeng.map.service.XpElectronicFenceService;
|
import com.xiaopeng.map.service.XpPointService;
|
import com.xiaopeng.map.service.XpStationService;
|
import com.xiaopeng.map.utils.GDMapGeocodingUtil;
|
import com.xiaopeng.map.utils.PolygonOverlapService;
|
import net.sourceforge.pinyin4j.PinyinHelper;
|
import org.apache.shardingsphere.transaction.annotation.ShardingSphereTransactionType;
|
import org.apache.shardingsphere.transaction.core.TransactionType;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.context.annotation.Lazy;
|
import org.springframework.data.mongodb.core.MongoTemplate;
|
import org.springframework.data.mongodb.core.geo.GeoJsonPolygon;
|
import org.springframework.data.mongodb.core.query.Criteria;
|
import org.springframework.data.mongodb.core.query.Query;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import javax.annotation.Resource;
|
import java.time.LocalDateTime;
|
import java.util.ArrayList;
|
import java.util.List;
|
|
/**
|
* <p>
|
* 场站 服务实现类
|
* </p>
|
*
|
* @author mitao
|
* @since 2026-04-12
|
*/
|
@Service
|
public class XpStationServiceImpl extends ServiceImpl<XpStationMapper, XpStation> implements XpStationService {
|
|
|
@Resource
|
private TokenService tokenService;
|
|
@Autowired
|
private MongoTemplate mongoTemplate;
|
@Resource
|
private PolygonOverlapService polygonOverlapService;
|
@Resource
|
private XpElectronicFenceService xpElectronicFenceService;
|
@Resource
|
private XpElectronicFenceAreaService xpElectronicFenceAreaService;
|
@Autowired
|
private GDMapGeocodingUtil gdMapGeocodingUtil;
|
@Autowired
|
private XpRegionMapper xpRegionMapper;
|
@Lazy
|
@Resource
|
private XpPointService xpPointService;
|
|
@Override
|
public List<XpStation> findList(XpStationQuery query) {
|
return this.baseMapper.pageList( query);
|
}
|
|
@Override
|
public PageInfo<XpStation> pageList(XpStationQuery query) {
|
PageInfo<XpStation> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
|
if (null == query.getCreateSort() && null == query.getUpdateSort()){
|
query.setCreateSort(2);
|
}
|
List<XpStation> list = this.baseMapper.pageList(pageInfo, query);
|
if (list.isEmpty()) {
|
return new PageInfo<>();
|
}
|
pageInfo.setRecords(list);
|
return pageInfo;
|
}
|
|
|
public List<XpStation> findList() {
|
// 获取现有的运营区域(areaType=1)
|
LambdaQueryWrapper<XpStation> queryWrapper = new LambdaQueryWrapper<>();
|
queryWrapper.eq(XpStation::getDeleted, 0);
|
//status = 场站状态 0=草稿 1=待测试 2=测试中 3=测试不通过 4=可运营 5=待生效 6=启用中 7=已停用 8=失效
|
queryWrapper.in(XpStation::getStatus, 1,2,3,4,5,6,7);
|
return super.list(queryWrapper);
|
}
|
|
|
@Override
|
public void validate(XpStationVo dto) {
|
if (dto.getElectronicAreaId() == null) {
|
throw new ServiceException("运营区域ID不能为空");
|
}
|
// if (dto.getElectronicId() == null) {
|
// throw new ServiceException("围栏ID不能为空");
|
// }
|
if(dto.getXpPointList().isEmpty()){
|
throw new ServiceException("场站点位不能为空");
|
}
|
for (XpPoint xpPoint : dto.getXpPointList()) {
|
if(xpPoint.getId() == null ){
|
throw new ServiceException("点位id不能为空");
|
}
|
XpPoint point = xpPointService.getById(xpPoint.getId());
|
if(point == null){
|
throw new ServiceException(xpPoint.getId()+"点位不存在");
|
}
|
xpPoint.setPointLon(point.getPointLon());
|
xpPoint.setPointLat(point.getPointLat());
|
}
|
// if (dto.getEffectTime().isBefore(LocalDateTime.now())) {
|
// throw new ServiceException("计划生效时间不能小于当前时间");
|
// }
|
XpElectronicFenceArea electronicFenceArea = xpElectronicFenceAreaService.getById(dto.getElectronicAreaId());
|
if(electronicFenceArea == null){
|
throw new ServiceException("所属运营区id错误");
|
}
|
dto.setElectronicId(electronicFenceArea.getElectronicId());
|
XpElectronicFence electronicFence = xpElectronicFenceService.getById(electronicFenceArea.getElectronicId());
|
dto.setCityCode(electronicFence.getCityCode());
|
|
double[] centerPoints = polygonOverlapService.calculateCenterPoint(dto.getFencePoints());//场站中心点位
|
dto.setCenterLon(centerPoints[0]);
|
dto.setCenterLat(centerPoints[1]);
|
//判断场站是否在 围栏里
|
boolean polygonInPolygon = polygonOverlapService.isPolygonInPolygon( dto.getFencePoints(),electronicFenceArea.getFencePoints());
|
if(!polygonInPolygon){
|
throw new ServiceException("场站不在"+electronicFence.getElectronicName()+"围栏内");
|
|
}
|
//判断点位是否在 场站里
|
|
if(!dto.getXpPointList().isEmpty()) {
|
for (XpPoint xpPoint : dto.getXpPointList()) {
|
if(!polygonOverlapService.isPointInPolygon(xpPoint.getPointLon(),xpPoint.getPointLat(),dto.getFencePoints())){
|
throw new ServiceException(xpPoint.getPointName()+" 点位不在 "+dto.getStationName()+"场站内");
|
}
|
}
|
}
|
//获取现有的场站区域
|
List<XpStation> listAll = findList();
|
// 检查新区域与现有运营区域是否有重叠
|
for (XpStation existingArea : listAll) {
|
if(dto.getId() != null){
|
if(!dto.getId().equals(existingArea.getId())){
|
if (polygonOverlapService.checkPolygonOverlap(dto.getFencePoints(),existingArea.getFencePoints())) {
|
throw new ServiceException("场站区域{}与现有场站区域{}发生重叠",I18nKeyConstant.EDIT_STATION_ERROR,
|
new Object[]{dto.getStationName(), existingArea.getStationName()}
|
);
|
}
|
}
|
}
|
}
|
|
}
|
|
|
|
public static String getPinyinInitials(String chinese) {
|
StringBuilder pinyinInitials = new StringBuilder();
|
for (char c : chinese.toCharArray()) {
|
// 获取拼音数组(多音字会返回多个拼音)
|
String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(c);
|
if (pinyinArray != null && pinyinArray.length > 0) {
|
// 取第一个拼音的首字母
|
pinyinInitials.append(pinyinArray[0].charAt(0));
|
}
|
}
|
return pinyinInitials.toString();
|
}
|
|
@Transactional
|
@ShardingSphereTransactionType(TransactionType.BASE)
|
@Override
|
public R add(XpStationVo dto) {
|
|
try {
|
// 生成围栏id 规则:城市首字母-类型简写-4位自增流水号(如 SZ-OP-001),支持复制;数据停用或删除后,流水号不回收
|
XpRegion xpRegion = xpRegionMapper.selectOne(new LambdaQueryWrapper<XpRegion>()
|
.eq(XpRegion::getCode, dto.getCityCode()));
|
String name = xpRegion.getName();
|
dto.setCityName(name);
|
|
this.save(dto);
|
|
//场站编号唯一 规则:城市首字母-HUB-4位流水号
|
// 根据城市中文名,截取前两位的首字母并且大写
|
// 截取前两个字符
|
String prefix = name.substring(0, 2);
|
// 获取拼音首字母并大写
|
String result = getPinyinInitials(prefix).toUpperCase();
|
dto.setStationNumber(result + "-HUB-" + String.format("%04d", dto.getId()));
|
|
this.updateById(dto);
|
|
savePoint(dto);
|
|
saveMongo_operatingArea(dto);
|
|
|
return R.ok();
|
} catch (Exception e) {
|
log.error("新增场站失败", e);
|
throw new ServiceException("新增场站失败");
|
}
|
}
|
|
|
@Transactional
|
@ShardingSphereTransactionType(TransactionType.BASE)
|
@Override
|
public R edit(XpStationVo dto) {
|
|
try {
|
// dto.setStatus(0); //status = 场站状态 0=草稿 1=待测试 2=测试中 3=测试不通过 4=可运营 5=待生效 6=启用中 7=已停用 8=失效
|
this.updateById(dto);
|
xpPointService.clearStationInfoByStationId(dto.getId());
|
savePoint(dto);
|
// 删除旧的区域数据,然后保存新的区域数据
|
removeMongo_operatingArea(dto);
|
saveMongo_operatingArea(dto);
|
return R.ok();
|
} catch (Exception e) {
|
log.error("新增场站失败", e);
|
throw new ServiceException("新增场站失败");
|
}
|
}
|
@Transactional
|
public void savePoint(XpStationVo dto) {
|
if(CollUtil.isNotEmpty(dto.getXpPointList())){
|
//保存点位
|
List<XpPoint> xpPointListAdd = new ArrayList<>();
|
List<XpPoint> xpPointListUpdate = new ArrayList<>();
|
int stationSortNo = 1;
|
for (XpPoint xpPoint : dto.getXpPointList()) {
|
xpPoint.setStationSortNo(stationSortNo++);
|
xpPoint.setStationId(dto.getId());
|
if(dto.getStatus() == 2){//场站状态 0=草稿 1=待启用 2=启用中 3=已停用 4=失效
|
//运营状态 1=待启用 2=启用中 3=已停用 4=已失效
|
xpPoint.setOperationStatus(2);
|
}else if(dto.getStatus() == 3){
|
xpPoint.setOperationStatus(3);
|
}
|
|
if(xpPoint.getId() == null){
|
xpPointService.getPointNumber(dto.getElectronicAreaId());
|
xpPoint.setFpElectronicAreaId(dto.getElectronicAreaId());
|
xpPoint.setElectronicAreaId(dto.getElectronicAreaId());
|
xpPointListAdd.add(xpPoint);
|
}else{
|
xpPointListUpdate.add(xpPoint);
|
}
|
}
|
if(CollUtil.isNotEmpty(xpPointListAdd)){
|
xpPointService.saveBatch(xpPointListAdd);
|
}
|
if(CollUtil.isNotEmpty(xpPointListUpdate)){
|
xpPointService.updateBatchById(xpPointListUpdate);
|
}
|
}
|
}
|
|
/**
|
* 场站 失效-围栏失效 场站失效
|
* @param electronicId
|
*/
|
@Override
|
public void setStatusFailure(Long electronicId){
|
LambdaQueryWrapper<XpStation> query = new LambdaQueryWrapper<>();
|
query.eq(XpStation::getElectronicId, electronicId);
|
query.eq(XpStation::getDeleted, 0);
|
|
List<XpStation> list = this.list(query);
|
if(!list.isEmpty()){
|
for (XpStation xpStation : list) {
|
this.setStatus(xpStation,4);
|
}
|
}
|
}
|
|
@Override
|
public boolean setStatus(XpStation bean, Integer status){
|
|
LoginUser loginUser = tokenService.getLoginUser();
|
bean.setUpdateBy(loginUser.getUsername());
|
bean.setUpdateTime(LocalDateTime.now());
|
bean.setStatus(status);//场站状态 0=草稿 1=待启用 2=启用中 3=已停用 4=失效
|
boolean b = updateById(bean);
|
if(b){
|
if(status == 2){
|
saveMongo_operatingArea(bean);
|
}else if(status == 3 || status == 4){
|
removeMongo_operatingArea(bean);
|
}
|
}
|
return b;
|
}
|
|
private void removeMongo_operatingArea(XpStation bean) {
|
// 同步删除Mongo电子围栏多边形
|
Query delMongoQuery = new Query();
|
delMongoQuery.addCriteria(Criteria.where("stationId").is(String.valueOf(bean.getId())));
|
mongoTemplate.remove(delMongoQuery, XpGeoFencingPolygon.class);
|
}
|
|
private void saveMongo_operatingArea (XpStation bean) {
|
|
//场站状态 0=草稿 1=待启用 2=启用中 3=已停用 4=失效
|
if(bean.getStatus() == 2){
|
XpGeoFencingPolygon xpGeoFencingPolygon = new XpGeoFencingPolygon();
|
xpGeoFencingPolygon.setFenceId(String.valueOf(bean.getElectronicId()));
|
xpGeoFencingPolygon.setName(bean.getStationName());
|
xpGeoFencingPolygon.setAreaId(String.valueOf(bean.getElectronicAreaId()));
|
xpGeoFencingPolygon.setCityCode(bean.getCityCode());
|
xpGeoFencingPolygon.setStationId(String.valueOf(bean.getId()));
|
xpGeoFencingPolygon.setOperationStrategy(null);//运营策略
|
// 将围栏点转换为GeoJSON多边形
|
GeoJsonPolygon geoJsonPolygon = xpElectronicFenceService.convertToGeoJsonPolygon(bean.getFencePoints());
|
xpGeoFencingPolygon.setPoints(geoJsonPolygon);
|
mongoTemplate.insert(xpGeoFencingPolygon);
|
}
|
}
|
|
/**
|
* 启用操作
|
* @param bean
|
* @return
|
*/
|
@Override
|
public boolean updateStatusEnable(XpStation bean){
|
|
xpPointService.statusEnableByStationId(bean.getId());
|
return setStatus(bean, 2);
|
}
|
|
/**
|
* 停用
|
* - 操作项展示为 [停用]。点击后弹出二次确认框,“当前场站包含12个点位,确认一起停用?
|
* - 是:确认后场站以及子点位状态流转至“已停用”。
|
* - 否:子点位维持当前状态,但自动退回为“独立点位”。
|
*/
|
@Override
|
public boolean updateStatusStop(XpStation bean, Boolean pointStop){
|
|
xpPointService.statusStopByStationId(bean.getId(),pointStop);
|
return setStatus(bean, 3);
|
}
|
|
/**
|
* - 删除拦截逻辑:
|
* - 测试阶段(待测试、测试中等):点击直接删除。
|
* - 历史运营阶段(已停用、失效):点击弹出二次确认框“当前场站包含12个点位,确认一起删除?”
|
* - 是:点位统一一起删除
|
* - 否:不自动删除点位,点位自动退回为[待测试]的“独立点位”,并自动继承父级运营区的运营时段
|
* @param bean 实体
|
* @return
|
*/
|
@Transactional
|
@Override
|
public boolean removeById(XpStation bean, Boolean pointDelete){
|
boolean b = true;
|
//场站状态 0=草稿 1=待启用 2=启用中 3=已停用 4=失效
|
if(bean.getStatus() != 2){
|
b = super.removeById(bean.getId());
|
xpPointService.pointDeleteByStationId(bean.getId(),pointDelete);
|
if(b){
|
// 同步删除Mongo电子围栏多边形
|
Query delMongoQuery = new Query();
|
delMongoQuery.addCriteria(Criteria.where("stationId").is(String.valueOf(bean.getId())));
|
mongoTemplate.remove(delMongoQuery, XpGeoFencingPolygon.class);
|
}
|
}
|
return b;
|
|
}
|
|
}
|