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.common.log.annotation.AdminOperationLog; import com.xiaopeng.common.log.enums.AdminBusinessType; import com.xiaopeng.common.log.enums.AdminOperationType; 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; /** *

* 电子围栏 前端控制器 *

* * @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>> geXpRegion() { return R.ok(regionService.getXpRegion()); } @ApiOperation(value = "获取围栏和场站数据",tags = {"管理后台-电子围栏"}) @GetMapping(value = "/findListFenceAndStation") public R> findListFenceAndStation(String cityCode) { return R.ok(xpPointService.findListFenceAndStation(cityCode)); } @ApiOperation(value = "获取不分页的电子围栏列表",tags = {"管理后台-电子围栏"}) @PostMapping("/list") R> getList(@RequestBody ElectronicPageListQuery query){ return R.ok(xpElectronicFenceService.getList(query)); } @ApiOperation(value = "电子围栏管理列表", response = XpElectronicFence.class) @PostMapping("/pageList") public R> pageList(@RequestBody ElectronicPageListQuery query) { PageInfo pageList = xpElectronicFenceService.pageList(query); return R.ok(pageList); } /** * 电子围栏列表接口 * * @return 电子围栏列表 */ @ApiOperation(value = "电子围栏列表接口", tags = {"运维APP-电子围栏"}) @GetMapping("/maintenance-app/api/v1/electronicFence/list") public R> vehicleMap() { List list = xpElectronicFenceService.list(); return R.ok(list); } /** * 新增电子围栏 */ @PostMapping("/add") @ApiOperation(value = "新增电子围栏") @AdminOperationLog( businessType = AdminBusinessType.ELECTRONIC_FENCE_MANAGEMENT, operationType = AdminOperationType.ADD, content = "新增电子围栏", appendRequestData = true ) 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 = "修改状态") @AdminOperationLog( businessType = AdminBusinessType.ELECTRONIC_FENCE_MANAGEMENT, operationType = AdminOperationType.EDIT, content = "修改电子围栏状态", appendRequestData = true ) 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 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 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 pointList = xpPointService.findList(query1); ElectronicFenceDetailVo.Numbers pointsNumbers = new ElectronicFenceDetailVo.Numbers(); if(CollUtil.isNotEmpty(pointList)){ LocalTime localTime = LocalTime.now(); for (XpPoint xpPoint : pointList) { if(xpPoint.getVerificationStatus() !=5){ //verificationStatus 验证状态 1=待验证、2=验证中、3=已验证 4验证失败 5待生效 if (xpPoint.getVerificationStatus() == 1){ pointsNumbers.setToBeTested(pointsNumbers.getToBeTested() + 1); }else if (xpPoint.getVerificationStatus() == 2){ pointsNumbers.setTesting(pointsNumbers.getTesting() + 1); }else if (xpPoint.getVerificationStatus() == 4){ pointsNumbers.setTestNotPass(pointsNumbers.getTestNotPass() + 1); } }else{ //operationStatus 运营状态 1=待启用 2=启用中 3=已停用 4=已失效 if(xpPoint.getOperationStatus() == 1){ pointsNumbers.setPendingLaunch(pointsNumbers.getPendingLaunch() + 1); } else if (xpPoint.getOperationStatus() == 2) { boolean pointOperation = bootCommonService.getPointOperation(localTime, xpPoint); if (pointOperation) { pointsNumbers.setStartingUp(pointsNumbers.getStartingUp() + 1); }else{ pointsNumbers.setPendingLaunch(pointsNumbers.getPendingLaunch() + 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 stationList = xpStationService.findList(query2); ElectronicFenceDetailVo.Numbers stationNumbers = new ElectronicFenceDetailVo.Numbers(); if(CollUtil.isNotEmpty(stationList)){ 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) @AdminOperationLog( businessType = AdminBusinessType.ELECTRONIC_FENCE_MANAGEMENT, operationType = AdminOperationType.EDIT, content = "编辑电子围栏", appendRequestData = true ) public R edit(@Validated @RequestBody ElectronicFenceUpdateDto dto) { validateFenceTime(dto.getStatus(), dto.getEffectType(), dto.getEffectTime(), dto.getFailureType(), dto.getFailureTime()); return xpElectronicFenceService.editElectronic(dto); } /** * 校验编辑围栏时运营时间段缩短影响的点位数量 *

* 统计:围栏下 operation_type=2(自定义运营时间段) 的点位中, * 当围栏运营时间范围发生缩短时,不在新运营时间范围(Union)内的点位数量(需要停用)。 *

*/ @PostMapping("/validateOperationStrategyShorten") @ApiOperation(value = "校验围栏运营时间段缩短影响点位数量") public R 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 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 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 pointIntervals = OperationStrategyIndex.parsePointEffectiveTimePeriod(point.getEffectiveTimePeriod()); if (pointIntervals.isEmpty()) { continue; } boolean shouldDisable = !newIndex.isPointScheduleWithin(pointIntervals); if (shouldDisable) { disableCount++; } } List listByElectronicId = xpStationService.findListByElectronicId(dto.getId()); if(CollUtil.isNotEmpty(listByElectronicId)){ for (XpStation bean : listByElectronicId) { if (StrUtil.isBlank(bean.getEffectiveTimePeriod())) { continue; } List 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) @AdminOperationLog( businessType = AdminBusinessType.ELECTRONIC_FENCE_MANAGEMENT, operationType = AdminOperationType.ADD, contentExpression = "'复制电子围栏,id=' + #p0", appendRequestData = false ) public R 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 electronicFenceAreaList = xpElectronicFenceAreaService.lambdaQuery().eq(XpElectronicFenceArea::getElectronicId, id) .list(); //根据经纬度获取当前城市code // 生成围栏id 规则:城市首字母-类型简写-4位自增流水号(如 SZ-OP-001),支持复制;数据停用或删除后,流水号不回收 XpRegion xpRegion = xpRegionService.getOne(new LambdaQueryWrapper() .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 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> listByIds(@RequestBody Collection electronicIdSet) { if (CollUtil.isEmpty(electronicIdSet)) { return R.ok(Collections.emptyList()); } return R.ok(xpElectronicFenceService.listByIds(electronicIdSet)); } @ApiOperation(value = "获取已启用的电子围栏列表",tags = {"管理后台-电子围栏"}) @GetMapping("/available/list") R> getAvailableList(){ return R.ok(xpElectronicFenceService.getAvailableList()); } /** * 批量查询电子围栏 * @param fenceIds * @return */ @PostMapping("/inner/xpElectronicFence/selectBatchIds") public R> selectBatchIds(@RequestBody List fenceIds){ List xpElectronicFences = xpElectronicFenceService.listByIds(fenceIds); return R.ok(xpElectronicFences); } /** * 根据ID查询电子围栏 * @param fenceId * @return */ @GetMapping("/inner/xpElectronicFence/selectById") public R selectById(@RequestParam String fenceId){ XpElectronicFence xpElectronicFence = xpElectronicFenceService.getById(fenceId); return R.ok(xpElectronicFence); } /** * 校验围栏生效时间和失效时间的合法性: *
    *
  • 生效类型不是立即生效(effectType != 1)时,生效时间不能早于当前时间
  • *
  • 失效类型不是永不失效(failureType != 1)时,失效时间不能早于当前时间
  • *
* * @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); } }