package com.xiaopeng.map.controller;
|
|
|
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.date.DatePattern;
|
import cn.hutool.core.date.LocalDateTimeUtil;
|
import cn.hutool.core.util.StrUtil;
|
import com.alibaba.fastjson.JSONArray;
|
import com.alibaba.fastjson.JSONObject;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.xiaopeng.api.dto.operation.ElectronicFenceAddDto;
|
import com.xiaopeng.api.dto.operation.ElectronicFenceAreaUpdateStatusDto;
|
import com.xiaopeng.api.dto.operation.ElectronicFenceOperationStrategyValidateDto;
|
import com.xiaopeng.api.dto.operation.ElectronicFenceUpdateDto;
|
import com.xiaopeng.api.model.operation.*;
|
import com.xiaopeng.api.query.operation.ElectronicPageListQuery;
|
import com.xiaopeng.api.query.operation.PointListQuery;
|
import com.xiaopeng.api.query.operation.XpStationQuery;
|
import com.xiaopeng.api.vo.operation.ElectronicFenceDetailVo;
|
import com.xiaopeng.common.core.domain.R;
|
import com.xiaopeng.common.core.exception.ServiceException;
|
import com.xiaopeng.common.core.utils.bean.BeanUtils;
|
import com.xiaopeng.common.core.web.page.PageInfo;
|
import com.xiaopeng.map.constant.I18nKeyConstant;
|
import com.xiaopeng.map.service.*;
|
import com.xiaopeng.map.service.serviceimpl.BootCommonService;
|
import com.xiaopeng.map.utils.OperationStrategyIndex;
|
import com.xiaopeng.api.query.operation.FenceAndStationVo;
|
import io.swagger.annotations.Api;
|
import io.swagger.annotations.ApiOperation;
|
import lombok.extern.slf4j.Slf4j;
|
import org.apache.shardingsphere.transaction.annotation.ShardingSphereTransactionType;
|
import org.apache.shardingsphere.transaction.core.TransactionType;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.validation.annotation.Validated;
|
import org.springframework.web.bind.annotation.*;
|
|
import javax.annotation.Resource;
|
import javax.validation.Valid;
|
import javax.validation.constraints.NotNull;
|
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
import java.time.LocalTime;
|
import java.util.Collection;
|
import java.util.Collections;
|
import java.util.HashSet;
|
import java.util.List;
|
import java.util.Objects;
|
import java.util.Set;
|
import java.util.stream.Collectors;
|
|
import static com.xiaopeng.map.service.serviceimpl.XpElectronicFenceServiceImpl.getPinyinInitials;
|
|
/**
|
* <p>
|
* 电子围栏 前端控制器
|
* </p>
|
*
|
* @author mitao
|
* @since 2026-02-03
|
*/
|
@RestController
|
@Slf4j
|
@Api(tags = "电子围栏管理")
|
@RequestMapping("/api/v1/electronicFence")
|
public class XpElectronicFenceManagementController {
|
|
|
@Resource
|
private XpElectronicFenceService xpElectronicFenceService;
|
|
@Resource
|
private XpElectronicFenceAreaService xpElectronicFenceAreaService;
|
|
@Resource
|
private XpRegionService regionService;
|
@Autowired
|
private XpRegionService xpRegionService;
|
|
@Autowired
|
private XpPointService xpPointService;
|
@Resource
|
private XpStationService xpStationService;
|
@Resource
|
private BootCommonService bootCommonService;
|
|
/**
|
* 查询省市管理
|
*/
|
@ApiOperation(value = "查询省市区",response = XpRegion.class)
|
@GetMapping(value = "/getRegion")
|
public R<List<List<XpRegion>>> geXpRegion() {
|
return R.ok(regionService.getXpRegion());
|
}
|
|
@ApiOperation(value = "获取围栏和场站数据",tags = {"管理后台-电子围栏"})
|
@GetMapping(value = "/findListFenceAndStation")
|
public R<List<FenceAndStationVo>> findListFenceAndStation(String cityCode) {
|
return R.ok(xpPointService.findListFenceAndStation(cityCode));
|
}
|
|
@ApiOperation(value = "获取不分页的电子围栏列表",tags = {"管理后台-电子围栏"})
|
@PostMapping("/list")
|
R<List<XpElectronicFence>> getList(@RequestBody ElectronicPageListQuery query){
|
return R.ok(xpElectronicFenceService.getList(query));
|
}
|
|
|
@ApiOperation(value = "电子围栏管理列表", response = XpElectronicFence.class)
|
@PostMapping("/pageList")
|
public R<PageInfo<XpElectronicFence>> pageList(@RequestBody ElectronicPageListQuery query) {
|
PageInfo<XpElectronicFence> pageList = xpElectronicFenceService.pageList(query);
|
return R.ok(pageList);
|
}
|
/**
|
* 电子围栏列表接口
|
*
|
* @return 电子围栏列表
|
*/
|
@ApiOperation(value = "电子围栏列表接口", tags = {"运维APP-电子围栏"})
|
@GetMapping("/maintenance-app/api/v1/electronicFence/list")
|
public R<List<XpElectronicFence>> vehicleMap() {
|
List<XpElectronicFence> list = xpElectronicFenceService.list();
|
return R.ok(list);
|
}
|
/**
|
* 新增电子围栏
|
*/
|
@PostMapping("/add")
|
@ApiOperation(value = "新增电子围栏")
|
public R add(@Valid @RequestBody ElectronicFenceAddDto dto) {
|
validateFenceTime(dto.getStatus(), dto.getEffectType(), dto.getEffectTime(), dto.getFailureType(), dto.getFailureTime());
|
return xpElectronicFenceService.addElectronic(dto);
|
}
|
|
|
|
/**
|
* 修改状态
|
*/
|
@PostMapping("/updateStatus")
|
@ResponseBody
|
@ApiOperation(value = "修改状态")
|
public R updateStatus(@RequestBody ElectronicFenceAreaUpdateStatusDto dto) {
|
XpElectronicFenceArea electronicFenceArea = xpElectronicFenceAreaService.getById(dto.getId());
|
if (electronicFenceArea == null) {
|
throw new ServiceException(
|
"围栏构成不存在,请刷新重试",
|
I18nKeyConstant.FENCE_STRUCTURE_NOT_EXIST
|
);
|
}
|
xpElectronicFenceAreaService.updateById(electronicFenceArea);
|
return R.ok();
|
}
|
|
/**
|
* 电子围栏详情
|
*/
|
@PostMapping("/queryById")
|
@ApiOperation(value = "电子围栏详情")
|
public R<ElectronicFenceDetailVo> queryById(@RequestParam("id")Long id) {
|
ElectronicFenceDetailVo res = new ElectronicFenceDetailVo();
|
XpElectronicFence electronicFence = xpElectronicFenceService.getById(id);
|
if (electronicFence == null) {
|
throw new ServiceException(
|
"电子围栏不存在,请刷新重试",
|
I18nKeyConstant.FENCE_NOT_EXIST
|
);
|
}
|
BeanUtils.copyProperties(electronicFence, res);
|
List<XpElectronicFenceArea> electronicFenceAreaList = xpElectronicFenceAreaService.lambdaQuery().eq(XpElectronicFenceArea::getElectronicId, id)
|
.list();
|
res.setElectronicFenceAreas(electronicFenceAreaList);
|
|
//点位的总数
|
if(!electronicFenceAreaList.isEmpty()){
|
PointListQuery query1 = new PointListQuery();
|
query1.setElectronicAreaIds(electronicFenceAreaList.stream().map(XpElectronicFenceArea::getId).collect(Collectors.toList()));
|
List<XpPoint> pointList = xpPointService.findList(query1);
|
|
ElectronicFenceDetailVo.Numbers pointsNumbers = new ElectronicFenceDetailVo.Numbers();
|
if(CollUtil.isNotEmpty(pointList)){
|
for (XpPoint xpPoint : pointList) {
|
if (xpPoint.getOperationStatus() == 1) {
|
pointsNumbers.setPendingLaunch(pointsNumbers.getPendingLaunch() + 1);
|
} else if (xpPoint.getOperationStatus() == 2) {
|
pointsNumbers.setStartingUp(pointsNumbers.getStartingUp() + 1);
|
}
|
else if (xpPoint.getOperationStatus() == 3) {
|
pointsNumbers.setExpired(pointsNumbers.getExpired() + 1);
|
}
|
else if (xpPoint.getOperationStatus() == 4) {
|
pointsNumbers.setDisabled(pointsNumbers.getDisabled() + 1);
|
}
|
}
|
pointsNumbers.setTotalNum(pointList.size());
|
}
|
res.setPointsNumbers(pointsNumbers);
|
}
|
|
//场站总数
|
XpStationQuery query2 = new XpStationQuery();
|
query2.setElectronicId(id);
|
List<XpStation> stationList = xpStationService.findList(query2);
|
|
ElectronicFenceDetailVo.Numbers stationNumbers = new ElectronicFenceDetailVo.Numbers();
|
if(CollUtil.isNotEmpty(stationList)){
|
LocalDateTime now = LocalDateTime.now();
|
LocalTime localTime = LocalTime.now();
|
//status 场站状态 0=草稿 1=待启用 2=启用中 3=已停用 4=失效
|
for (XpStation xpStation : stationList) {
|
switch (xpStation.getStatus()) {
|
case 0:
|
stationNumbers.setDraft(stationNumbers.getDraft() + 1);
|
break;
|
case 1:
|
stationNumbers.setPendingLaunch(stationNumbers.getPendingLaunch() + 1);
|
break;
|
case 2: //启动中 判断 待启动的状态 ,当前时间在 运营时间 内是启动中,否则是 待启动
|
|
boolean stationOperation = bootCommonService.getStationOperation(localTime, xpStation.getOperationType(), xpStation.getEffectivePeriod(), xpStation.getEffectiveTimePeriod(), xpStation.getFenceOperationStrategy());
|
if (stationOperation) {
|
stationNumbers.setStartingUp(stationNumbers.getStartingUp() + 1);
|
}else{
|
stationNumbers.setPendingLaunch(stationNumbers.getPendingLaunch() + 1);
|
}
|
break;
|
case 3:
|
stationNumbers.setDisabled(stationNumbers.getDisabled() + 1);
|
break;
|
case 4:
|
stationNumbers.setExpired(stationNumbers.getExpired() + 1);
|
break;
|
}
|
}
|
stationNumbers.setTotalNum(stationList.size());
|
}
|
res.setStationNumbers(stationNumbers);
|
return R.ok(res);
|
}
|
|
/**
|
* 编辑电子围栏
|
*/
|
@PostMapping("/edit")
|
@ApiOperation(value = "编辑电子围栏")
|
@Transactional
|
@ShardingSphereTransactionType(TransactionType.BASE)
|
public R edit(@Validated @RequestBody ElectronicFenceUpdateDto dto) {
|
validateFenceTime(dto.getStatus(), dto.getEffectType(), dto.getEffectTime(), dto.getFailureType(), dto.getFailureTime());
|
return xpElectronicFenceService.editElectronic(dto);
|
}
|
|
/**
|
* 校验编辑围栏时运营时间段缩短影响的点位数量
|
* <p>
|
* 统计:围栏下 operation_type=2(自定义运营时间段) 的点位中,
|
* 当围栏运营时间范围发生缩短时,不在新运营时间范围(Union)内的点位数量(需要停用)。
|
* </p>
|
*/
|
@PostMapping("/validateOperationStrategyShorten")
|
@ApiOperation(value = "校验围栏运营时间段缩短影响点位数量")
|
public R<Integer> validateOperationStrategyShorten(@Valid @RequestBody ElectronicFenceOperationStrategyValidateDto dto) {
|
XpElectronicFence fence = xpElectronicFenceService.getById(dto.getId());
|
if (fence == null) {
|
throw new ServiceException("围栏不存在", I18nKeyConstant.FENCE_NOT_EXIST);
|
}
|
|
OperationStrategyIndex oldIndex = OperationStrategyIndex.parse(fence.getOperationStrategy());
|
OperationStrategyIndex newIndex = OperationStrategyIndex.parse(dto.getOperationStrategy());
|
if (oldIndex.equalsNormalized(newIndex)) {
|
return R.ok(0);
|
}
|
if (!oldIndex.hasAnyShrinkComparedTo(newIndex)) {
|
return R.ok(0);
|
}
|
|
List<Long> fenceAreaIds = xpElectronicFenceAreaService.lambdaQuery()
|
.eq(XpElectronicFenceArea::getElectronicId, dto.getId())
|
.list()
|
.stream()
|
.map(XpElectronicFenceArea::getId)
|
.filter(Objects::nonNull)
|
.distinct()
|
.collect(Collectors.toList());
|
if (CollUtil.isEmpty(fenceAreaIds)) {
|
return R.ok(0);
|
}
|
|
List<XpPoint> customPoints = xpPointService.lambdaQuery()
|
.in(XpPoint::getElectronicAreaId, fenceAreaIds)
|
.eq(XpPoint::getPointType, 1)
|
.eq(XpPoint::getOperationType, 2)
|
.list();
|
if (CollUtil.isEmpty(customPoints)) {
|
return R.ok(0);
|
}
|
|
int disableCount = 0;
|
for (XpPoint point : customPoints) {
|
if (point == null || point.getEffectiveTimePeriod() == null || point.getEffectiveTimePeriod().isEmpty()) {
|
continue;
|
}
|
List<OperationStrategyIndex.TimeInterval> pointIntervals = OperationStrategyIndex.parsePointEffectiveTimePeriod(point.getEffectiveTimePeriod());
|
if (pointIntervals.isEmpty()) {
|
continue;
|
}
|
|
boolean shouldDisable = !newIndex.isPointScheduleWithin(pointIntervals);
|
if (shouldDisable) {
|
disableCount++;
|
}
|
}
|
|
List<XpStation> listByElectronicId = xpStationService.findListByElectronicId(dto.getId());
|
if(CollUtil.isNotEmpty(listByElectronicId)){
|
|
for (XpStation bean : listByElectronicId) {
|
if (StrUtil.isBlank(bean.getEffectiveTimePeriod())) {
|
continue;
|
}
|
List<OperationStrategyIndex.TimeInterval> pointIntervals = OperationStrategyIndex.parsePointEffectiveTimePeriod(bean.getEffectiveTimePeriod());
|
if (pointIntervals.isEmpty()) {
|
continue;
|
}
|
|
boolean shouldDisable = !newIndex.isPointScheduleWithin(pointIntervals);
|
if (shouldDisable) {
|
disableCount++;
|
}
|
}
|
}
|
return R.ok(disableCount);
|
}
|
|
/**
|
* 复制电子围栏
|
*/
|
@PostMapping("/copy")
|
@ApiOperation(value = "复制电子围栏")
|
@Transactional
|
@ShardingSphereTransactionType(TransactionType.BASE)
|
public R<Long> copy(@RequestParam("id") Integer id) {
|
XpElectronicFence electronicFence = xpElectronicFenceService.getById(id);
|
if (electronicFence.getStatus()==2){
|
throw new ServiceException(
|
"当前电子围栏状态为启用中,不可复制",
|
I18nKeyConstant.FENCE_STATUS_CANNOT_COPIED
|
);
|
}
|
electronicFence.setElectronicName(buildCopyFenceName(electronicFence.getElectronicName()));
|
electronicFence.setId(null);
|
electronicFence.setStatus(0);
|
List<XpElectronicFenceArea> electronicFenceAreaList = xpElectronicFenceAreaService.lambdaQuery().eq(XpElectronicFenceArea::getElectronicId, id)
|
.list();
|
//根据经纬度获取当前城市code
|
// 生成围栏id 规则:城市首字母-类型简写-4位自增流水号(如 SZ-OP-001),支持复制;数据停用或删除后,流水号不回收
|
XpRegion xpRegion = xpRegionService.getOne(new LambdaQueryWrapper<XpRegion>()
|
.eq(XpRegion::getCode, electronicFence.getCityCode()));
|
String name = xpRegion.getName();
|
// 根据城市中文名,截取前两位的首字母并且大写
|
// 截取前两个字符
|
String prefix = name.substring(0, 2);
|
// 获取拼音首字母并大写
|
String result = getPinyinInitials(prefix).toUpperCase();
|
xpElectronicFenceService.save(electronicFence);
|
electronicFence.setElectronicNumber(result + "-OP-" + String.format("%04d", electronicFence.getId()));
|
xpElectronicFenceService.updateById(electronicFence);
|
for (XpElectronicFenceArea area : electronicFenceAreaList) {
|
area.setId(null);
|
area.setElectronicId(electronicFence.getId());
|
area.setAreaStatus(2);
|
area.setAreaName("副本_"+area.getAreaName());
|
}
|
xpElectronicFenceAreaService.saveBatch(electronicFenceAreaList);
|
return R.ok(electronicFence.getId());
|
}
|
|
private String buildCopyFenceName(String sourceName) {
|
Set<String> existingNames = new HashSet<>(xpElectronicFenceService.lambdaQuery()
|
.likeRight(XpElectronicFence::getElectronicName, sourceName)
|
.list()
|
.stream()
|
.map(XpElectronicFence::getElectronicName)
|
.collect(Collectors.toSet()));
|
|
String candidateName = sourceName + "_副本";
|
while (existingNames.contains(candidateName)) {
|
candidateName = candidateName + "_副本";
|
}
|
return candidateName;
|
}
|
|
/**
|
* 根据ID列表批量查询电子围栏
|
*
|
* @param electronicIdSet
|
* @return
|
*/
|
@PostMapping("/listByIds")
|
R<List<XpElectronicFence>> listByIds(@RequestBody Collection<Long> electronicIdSet) {
|
if (CollUtil.isEmpty(electronicIdSet)) {
|
return R.ok(Collections.emptyList());
|
}
|
return R.ok(xpElectronicFenceService.listByIds(electronicIdSet));
|
}
|
|
@ApiOperation(value = "获取已启用的电子围栏列表",tags = {"管理后台-电子围栏"})
|
@GetMapping("/available/list")
|
R<List<XpElectronicFence>> getAvailableList(){
|
return R.ok(xpElectronicFenceService.getAvailableList());
|
}
|
|
|
|
|
/**
|
* 批量查询电子围栏
|
* @param fenceIds
|
* @return
|
*/
|
@PostMapping("/inner/xpElectronicFence/selectBatchIds")
|
public R<List<XpElectronicFence>> selectBatchIds(@RequestBody List<String> fenceIds){
|
List<XpElectronicFence> xpElectronicFences = xpElectronicFenceService.listByIds(fenceIds);
|
return R.ok(xpElectronicFences);
|
}
|
|
|
/**
|
* 根据ID查询电子围栏
|
* @param fenceId
|
* @return
|
*/
|
@GetMapping("/inner/xpElectronicFence/selectById")
|
public R<XpElectronicFence> selectById(@RequestParam String fenceId){
|
XpElectronicFence xpElectronicFence = xpElectronicFenceService.getById(fenceId);
|
return R.ok(xpElectronicFence);
|
}
|
|
/**
|
* 校验围栏生效时间和失效时间的合法性:
|
* <ul>
|
* <li>生效类型不是立即生效(effectType != 1)时,生效时间不能早于当前时间</li>
|
* <li>失效类型不是永不失效(failureType != 1)时,失效时间不能早于当前时间</li>
|
* </ul>
|
*
|
* @param status
|
* @param effectType 生效类型(1=立即生效 2=定时生效)
|
* @param effectTime 生效时间
|
* @param failureType 失效类型(1=永不失效 2=定时失效)
|
* @param failureTime 失效时间
|
*/
|
private void validateFenceTime(@NotNull Integer status, Integer effectType, LocalDateTime effectTime,
|
Integer failureType, LocalDateTime failureTime) {
|
LocalDateTime now = LocalDateTime.now();
|
// 发布/启用时,定时生效的生效时间不能早于当前时间
|
if (requiresScheduleValidation(status) && effectType != null && effectType != 1) {
|
if (effectTime == null || effectTime.isBefore(now)) {
|
throw new ServiceException("围栏生效时间不合法,定时生效时生效时间不能早于当前时间",
|
I18nKeyConstant.FENCE_EFFECT_TIME_INVALID);
|
}
|
}
|
// 发布/启用时,定时失效的失效时间不能早于当前时间
|
if (requiresScheduleValidation(status) && failureType != null && failureType != 1) {
|
if (failureTime == null || failureTime.isBefore(now)) {
|
throw new ServiceException("围栏失效时间不合法,定时失效时失效时间不能早于当前时间",
|
I18nKeyConstant.FENCE_FAILURE_TIME_INVALID);
|
}
|
}
|
}
|
|
private boolean requiresScheduleValidation(Integer status) {
|
return status != null && (status == 1 || status == 2);
|
}
|
}
|