“mitao”
2026-03-14 c880977abc130e6820212f66af41033b67b65b14
bugfix
23个文件已修改
365 ■■■■■ 已修改文件
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/impl/XpOperationsUserServiceImpl.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/operation/ElectronicFenceUpdateDto.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/maintenance/XpClockInClientFallbackFactory.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/maintenance/XpClockInClient.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/operation/XpBanner.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/operation/XpCruisePlanVehicle.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/operation/XpPoint.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/account/XpOperationsUserProfileVO.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/maintenance/TaskRuleDetailVO.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpClockInController.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/dto/TaskRuleDetailDto.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/mapper/XpClockInMapper.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/IXpClockInService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpClockInServiceImpl.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpTaskServiceImpl.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/resources/mapper/maintenance/XpClockInMapper.xml 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpElectronicFenceManagementController.java 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpPointManagementController.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/service/serviceimpl/XpElectronicFenceServiceImpl.java 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/resources/i18n/messages.properties 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/resources/i18n/messages_zh_CN.properties 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/resources/mapper/XpPointMapper.xml 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpAppVersionServiceImpl.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/impl/XpOperationsUserServiceImpl.java
@@ -150,17 +150,17 @@
        // 7. 查询是否上班中
        try {
            R<Boolean> workStatusResult = xpClockInClient.checkWorkStatus(userId);
            R<Integer> workStatusResult = xpClockInClient.checkWorkStatus(userId);
            if (workStatusResult != null && workStatusResult.getCode() == 200) {
                vo.setWorkFlag(workStatusResult.getData());
            } else {
                // 调用失败,默认 false
                vo.setWorkFlag(false);
                vo.setWorkFlag(1);
                log.warn("查询用户上班状态失败, userId={}, result={}", userId, workStatusResult);
            }
        } catch (Exception e) {
            // 异常情况,默认 false
            vo.setWorkFlag(false);
            vo.setWorkFlag(1);
            log.error("查询用户上班状态异常, userId={}", userId, e);
        }
@@ -491,6 +491,6 @@
        if (StringUtils.isBlank(phoneNumber)) {
            return Collections.emptyList();
        }
        return lambdaQuery().eq(XpOperationsUser::getPhoneNumber, phoneNumber).list();
        return lambdaQuery().like(XpOperationsUser::getPhoneNumber, phoneNumber).list();
    }
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/operation/ElectronicFenceUpdateDto.java
@@ -54,4 +54,13 @@
    @ApiModelProperty(value = "电子围栏区域")
    @NotEmpty(message = "电子围栏区域不能为空")
    private List<XpElectronicFenceArea> electronicFenceAreas;
    @ApiModelProperty(value = "范围扩大 需要新增的点位ID列表")
    private List<Long> addPointIdList;
    @ApiModelProperty(value = "范围缩小 需要改为失效状态的点位ID列表")
    private List<Long> delPointIdList;
    @ApiModelProperty(value = "禁行区覆盖点位 需要改为停用状态的点位ID列表")
    private List<Long> disablePointIdList;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/maintenance/XpClockInClientFallbackFactory.java
@@ -20,11 +20,11 @@
    public XpClockInClient create(Throwable cause) {
        return new XpClockInClient() {
            @Override
            public R<Boolean> checkWorkStatus(Long appUserId) {
            public R<Integer> checkWorkStatus(Long appUserId) {
                log.error("调用打卡服务检查上班状态失败, appUserId={}, 异常: {}",
                    appUserId, cause.getMessage(), cause);
                // 降级策略: 返回 false (未上班状态)
                return R.ok(false);
                // 降级策略: 返回  (未上班状态)
                return R.ok(1);
            }
        };
    }
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/maintenance/XpClockInClient.java
@@ -26,5 +26,5 @@
     * @return true=上班中, false=未上班或已下班
     */
    @GetMapping("/inner/clock-in/checkWorkStatus")
    R<Boolean> checkWorkStatus(@RequestParam("appUserId") Long appUserId);
    R<Integer> checkWorkStatus(@RequestParam("appUserId") Long appUserId);
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/operation/XpBanner.java
@@ -69,8 +69,13 @@
    @ApiModelProperty(value = "广告图片")
    @TableField("banner_pictures")
    private String bannerPictures;
    @ApiModelProperty(value = "广告标题")
    @TableField("banner_title")
    private String bannerTitle;
    @ApiModelProperty(value = "排序")
    @TableField("sort")
    private Integer sort;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/operation/XpCruisePlanVehicle.java
@@ -30,6 +30,10 @@
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "自增id")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value = "巡游计划ID")
    private Long cruisePlanId;
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/operation/XpPoint.java
@@ -34,10 +34,9 @@
    private Long id;
    @ApiModelProperty(value = "电子围栏区域id")
    @TableField(value = "electronic_area_id", updateStrategy = FieldStrategy.NEVER)
    private Long electronicAreaId;
    @TableField("fp_electronic_area_id")
    @TableField(value = "fp_electronic_area_id", updateStrategy = FieldStrategy.NEVER)
    private Long fpElectronicAreaId;
    @ApiModelProperty(value = "点位名称")
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/account/XpOperationsUserProfileVO.java
@@ -83,8 +83,8 @@
    @ApiModelProperty(value = "角色列表")
    private List<RoleInfoVO> roles;
    @ApiModelProperty("是否上班中")
    private Boolean workFlag;
    @ApiModelProperty("打卡状态 1-未打卡 2-上班中 3-休息")
    private Integer workFlag;
    @ApiModelProperty(value = "登录设备类型 1-Android 2-iOS")
    private Integer deviceType;
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/maintenance/TaskRuleDetailVO.java
@@ -22,7 +22,7 @@
    @ApiModelProperty(value = "车辆日常监测 频次")
    private Integer vehicleInspectionFrequency;
    @ApiModelProperty(value = "车辆日常检测 任务下放时间")
    @JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")
    @JsonFormat(pattern = "HH:mm", timezone = "GMT+8")
    private LocalTime vehicleInspectionTime;
    @ApiModelProperty(value = "车辆日常监测 紧急程度 0=P0 1=P1 2=P2 3=P3")
@@ -32,7 +32,7 @@
    private Boolean vehicleCollection;
    @ApiModelProperty(value = "车辆收车每日时间")
    @JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")
    @JsonFormat(pattern = "HH:mm", timezone = "GMT+8")
    private LocalTime vehicleCollectionTime;
    @ApiModelProperty(value = "车辆保养周期是否启用")
@@ -45,7 +45,7 @@
    private Integer maintenanceCycleTimeType;
    @ApiModelProperty(value = "车辆保养周期 任务下放时间")
    @JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")
    @JsonFormat(pattern = "HH:mm", timezone = "GMT+8")
    private LocalTime maintenanceCycleTaskTime;
    @ApiModelProperty(value = "车辆保养周期 紧急程度 0=P0 1=P1 2=P2 3=P3")
@@ -64,7 +64,7 @@
    private Integer vehicleCleanCycle;
    @ApiModelProperty(value = "车身清洁保养任务下放时间")
    @JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")
    @JsonFormat(pattern = "HH:mm", timezone = "GMT+8")
    private LocalTime vehicleCleanTime;
    @ApiModelProperty(value = "车身清洁保养周期X 1=天 2=月")
@@ -77,7 +77,7 @@
    private Integer routineMaintenanceCycle;
    @ApiModelProperty(value = "日常整备保养任务下放时间")
    @JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")
    @JsonFormat(pattern = "HH:mm", timezone = "GMT+8")
    private LocalTime routineMaintenanceTime;
    @ApiModelProperty(value = "日常整备保养周期X 1=天 2=月")
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpClockInController.java
@@ -58,9 +58,9 @@
     */
    @GetMapping("/inner/clock-in/checkWorkStatus")
    @ApiOperation(value = "检查用户是否上班中", tags = {"内部接口"})
    public R<Boolean> checkWorkStatus(@RequestParam("appUserId") Long appUserId) {
    public R<Integer> checkWorkStatus(@RequestParam("appUserId") Long appUserId) {
        log.info("检查用户上班状态, appUserId={}", appUserId);
        Boolean workFlag = xpClockInService.checkWorkStatus(appUserId);
        Integer workFlag = xpClockInService.checkWorkStatus(appUserId);
        return R.ok(workFlag);
    }
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/dto/TaskRuleDetailDto.java
@@ -23,7 +23,7 @@
    @ApiModelProperty(value = "车辆日常监测 频次")
    private Integer vehicleInspectionFrequency;
    @ApiModelProperty(value = "车辆日常检测 任务下放时间")
    @JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")
    @JsonFormat(pattern = "HH:mm", timezone = "GMT+8")
    private LocalTime vehicleInspectionTime;
    @ApiModelProperty(value = "车辆日常监测 紧急程度 0=P0 1=P1 2=P2 3=P3")
@@ -33,7 +33,7 @@
    private Boolean vehicleCollection;
    @ApiModelProperty(value = "车辆收车每日时间")
    @JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")
    @JsonFormat(pattern = "HH:mm", timezone = "GMT+8")
    private LocalTime vehicleCollectionTime;
    @ApiModelProperty(value = "车辆保养周期是否启用")
@@ -46,7 +46,7 @@
    private Integer maintenanceCycleTimeType;
    @ApiModelProperty(value = "车辆保养周期 任务下放时间")
    @JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")
    @JsonFormat(pattern = "HH:mm", timezone = "GMT+8")
    private LocalTime maintenanceCycleTaskTime;
    @ApiModelProperty(value = "车辆保养周期 紧急程度 0=P0 1=P1 2=P2 3=P3")
@@ -65,7 +65,7 @@
    private Integer vehicleCleanCycle;
    @ApiModelProperty(value = "车身清洁保养任务下放时间")
    @JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")
    @JsonFormat(pattern = "HH:mm", timezone = "GMT+8")
    private LocalTime vehicleCleanTime;
    @ApiModelProperty(value = "车身清洁保养周期X 1=天 2=月")
@@ -78,7 +78,7 @@
    private Integer routineMaintenanceCycle;
    @ApiModelProperty(value = "日常整备保养任务下放时间")
    @JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")
    @JsonFormat(pattern = "HH:mm", timezone = "GMT+8")
    private LocalTime routineMaintenanceTime;
    @ApiModelProperty(value = "日常整备保养周期X 1=天 2=月")
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/mapper/XpClockInMapper.java
@@ -23,20 +23,6 @@
public interface XpClockInMapper extends BaseMapper<XpClockIn> {
    /**
     * 查询用户当天的打卡记录
     * @param appUserId 用户ID
     * @param startTime 开始时间 (当天00:00:00)
     * @param endTime 结束时间 (当天23:59:59)
     * @return 打卡记录列表
     */
    List<XpClockIn> selectTodayClockInRecords(
        @Param("appUserId") Long appUserId,
        @Param("startTime") LocalDateTime startTime,
        @Param("endTime") LocalDateTime endTime
    );
    /**
     * 获取打卡统计
     * @param pageInfo
     * @return
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/IXpClockInService.java
@@ -49,7 +49,7 @@
     * @param appUserId 用户ID
     * @return true=上班中(只打了上班卡), false=未上班或已下班
     */
    Boolean checkWorkStatus(Long appUserId);
    Integer checkWorkStatus(Long appUserId);
    /**
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpClockInServiceImpl.java
@@ -1,6 +1,7 @@
package com.xiaopeng.maintenance.service.impl;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
@@ -186,10 +187,10 @@
    }
    @Override
    public Boolean checkWorkStatus(Long appUserId) {
    public Integer checkWorkStatus(Long appUserId) {
        if (appUserId == null) {
            log.warn("检查上班状态失败: appUserId 为空");
            return false;
            return 1;
        }
        try {
@@ -198,29 +199,23 @@
            LocalDateTime endTime = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);   // 23:59:59
            // 2. 查询当天打卡记录
            List<XpClockIn> records = baseMapper.selectTodayClockInRecords(
                    appUserId, startTime, endTime
            );
            XpClockIn xpClockIn = baseMapper.selectOne(new LambdaQueryWrapper<XpClockIn>()
                    .eq(XpClockIn::getAppUserId, appUserId)
                    .between(XpClockIn::getClockInTime, startTime, endTime)
                    .last("limit 1"));
            // 3. 判断上班状态
            if (records == null || records.isEmpty()) {
                // 无打卡记录 = 未上班
                log.debug("用户 {} 今日无打卡记录, workFlag=false", appUserId);
                return false;
            } else if (records.size() == 1) {
                // 1条记录 = 已上班未下班
                log.debug("用户 {} 今日打卡1次, workFlag=true", appUserId);
                return true;
            // 有上班打卡状态,无下班打卡状态
            if (Objects.isNull(xpClockIn)) {
                return 1;
            } else if (Objects.nonNull(xpClockIn.getClockInResult()) && Objects.isNull(xpClockIn.getClockOutResult())) {
                return 2;
            } else {
                // 2条及以上记录 = 已下班
                log.debug("用户 {} 今日打卡{}次, workFlag=false", appUserId, records.size());
                return false;
                return 3;
            }
        } catch (Exception e) {
            log.error("检查用户上班状态异常, appUserId={}", appUserId, e);
            // 异常情况返回 false
            return false;
            return 1;
        }
    }
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpTaskServiceImpl.java
@@ -453,6 +453,9 @@
                    I18nKeyConstant.VEHICLE_NOT_EXIST
            );
        }
        //查询任务处理记录
        XpTaskHandleDetailRoutine xpTaskHandleDetailRoutine = xpTaskHandleDetailRoutineMapper.selectOne(new LambdaQueryWrapper<XpTaskHandleDetailRoutine>()
                .eq(XpTaskHandleDetailRoutine::getTaskId, id).last("limit 1"));
        res.setId(id);
        res.setTaskUrgencyLevel(task.getTaskUrgencyLevel());
        res.setEndTime(task.getEndTime());
@@ -474,6 +477,12 @@
        res.setTaskImage(task.getTaskImage());
        res.setHandleUserId(task.getHandleUserId());
        res.setHandleTime(task.getHandleTime());
        if (Objects.nonNull(xpTaskHandleDetailRoutine)) {
            res.setTaskDescription(xpTaskHandleDetailRoutine.getTaskDescription());
            task.setHandleUserId(xpTaskHandleDetailRoutine.getOperationsUserId().toString());
            res.setHandleTime(xpTaskHandleDetailRoutine.getCreateTime());
            res.setTaskImage(xpTaskHandleDetailRoutine.getTaskImage());
        }
        if (Objects.nonNull(task.getHandleUserId()) && task.getHandleUserId().split(",").length == 1) {
            XpOperationsUser data = xpOperationUserClient.queryById(Long.valueOf(task.getHandleUserId())).getData();
            if (Objects.nonNull(data)) {
xp-robotaxi-maintenance-boot/src/main/resources/mapper/maintenance/XpClockInMapper.xml
@@ -18,29 +18,6 @@
        <result column="update_time" property="updateTime"/>
    </resultMap>
    <!-- 查询用户当天的打卡记录 -->
    <select id="selectTodayClockInRecords" resultMap="BaseResultMap">
        SELECT id,
               app_user_id,
               clock_in_time,
               clock_out_time,
               shift_id,
               clock_in_result,
               clock_out_result,
               clock_in_lon,
               clock_in_lat,
               clock_out_lon,
               clock_out_lat,
               create_time,
               update_time
        FROM xp_clock_in
        WHERE app_user_id = #{appUserId}
          AND clock_in_time BETWEEN #{startTime} AND #{endTime}
          AND clock_in_result IN (1, 2)
        ORDER BY clock_in_time ASC
    </select>
    <select id="getClockInStatistics" resultType="com.xiaopeng.maintenance.dto.ClockInStatisticsDto">
        select
        aa.app_user_id as appUserId,
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpElectronicFenceManagementController.java
@@ -5,6 +5,7 @@
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.feignClient.operation.XpElectronicClient;
import com.xiaopeng.api.model.operation.XpElectronicFence;
@@ -21,7 +22,9 @@
import com.xiaopeng.map.constant.I18nKeyConstant;
import com.xiaopeng.map.service.XpElectronicFenceAreaService;
import com.xiaopeng.map.service.XpElectronicFenceService;
import com.xiaopeng.map.service.XpPointService;
import com.xiaopeng.map.service.XpRegionService;
import com.xiaopeng.map.utils.OperationStrategyIndex;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
@@ -34,10 +37,12 @@
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import static com.xiaopeng.map.service.serviceimpl.XpElectronicFenceServiceImpl.getPinyinInitials;
@@ -66,6 +71,9 @@
    private XpRegionService regionService;
    @Autowired
    private XpRegionService xpRegionService;
    @Autowired
    private XpPointService xpPointService;
    /**
     * 查询省市管理
     */
@@ -146,11 +154,76 @@
     */
    @PostMapping("/edit")
    @ApiOperation(value = "编辑电子围栏")
    @Transactional
    @ShardingSphereTransactionType(TransactionType.BASE)
    public R edit(@Validated @RequestBody ElectronicFenceUpdateDto dto) {
        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++;
            }
        }
        return R.ok(disableCount);
    }
    /**
     * 复制电子围栏
     */
    @PostMapping("/copy")
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpPointManagementController.java
@@ -186,7 +186,7 @@
            throw new ServiceException("系统异常", I18nKeyConstant.SYSTEM_EXCEPTION);
        }
        if (Boolean.TRUE.equals(existsResult.getData())) {
            throw new ServiceException("操作失败,该点位有关联的巡游点位", I18nKeyConstant.POINT_DELETE_HAS_CRUISE_PLAN_POINT);
            throw new ServiceException("操作失败,该点位有关联的巡游计划点位", I18nKeyConstant.POINT_DELETE_HAS_CRUISE_PLAN_POINT);
        }
        xpPointService.removeBatchByIds(pointIds);
        return R.ok();
@@ -201,25 +201,12 @@
    @ShardingSphereTransactionType(TransactionType.BASE)
    public R edit(@Validated @RequestBody PointUpdateDto pointAddDto) {
        XpPoint xpPoint = xpPointService.getById(pointAddDto.getId());
        if (!xpPoint.getElectronicAreaId().equals(pointAddDto.getElectronicAreaId())) {
            BeanUtils.copyProperties(pointAddDto, xpPoint);
            if (xpGeoFencingPolygonService.isInFence(pointAddDto.getPointLon(), pointAddDto.getPointLat(), pointAddDto.getFenceId(), "prohibited_area")) {
                return R.ok("in_prohibited_area");
            }
            xpPointService.updateById(xpPoint);
        } else {
            if (xpGeoFencingPolygonService.isInFence(pointAddDto.getPointLon(), pointAddDto.getPointLat(), pointAddDto.getFenceId(), "prohibited_area")) {
                return R.ok("in_prohibited_area");
            }
            //先删除原有的数据,因为会存在点位的从属区域变化的情况,所以先删除原有的数据后添加新的数据重新分片到新的库中
            BeanUtils.copyProperties(pointAddDto, xpPoint);
            xpPointService.removeById(pointAddDto.getId());
            xpPoint.setId(null);
            xpPointService.save(xpPoint);
        BeanUtils.copyProperties(pointAddDto, xpPoint);
        if (xpGeoFencingPolygonService.isInFence(pointAddDto.getPointLon(), pointAddDto.getPointLat(), pointAddDto.getFenceId(), "prohibited_area")) {
            return R.ok("in_prohibited_area");
        }
        xpPointService.updateById(xpPoint);
        return R.ok();
    }
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/service/serviceimpl/XpElectronicFenceServiceImpl.java
@@ -1,6 +1,7 @@
package com.xiaopeng.map.service.serviceimpl;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -27,6 +28,8 @@
import com.xiaopeng.map.mapper.XpRegionMapper;
import com.xiaopeng.map.service.XpElectronicFenceAreaService;
import com.xiaopeng.map.service.XpElectronicFenceService;
import com.xiaopeng.map.service.XpPointService;
import com.xiaopeng.map.utils.OperationStrategyIndex;
import com.xiaopeng.map.utils.GDMapGeocodingUtil;
import com.xiaopeng.map.utils.PolygonOverlapService;
import lombok.extern.slf4j.Slf4j;
@@ -72,6 +75,10 @@
    private MongoTemplate mongoTemplate;
    @Autowired
    private XpPointMapper xpPointMapper;
    @Autowired
    private XpPointService xpPointService;
    @Autowired
    private XpRegionMapper xpRegionMapper;
    @Autowired
@@ -427,6 +434,8 @@
        XpElectronicFence electronicFence = this.getById(dto.getId());
        Integer status = electronicFence.getStatus();
        String oldOperationStrategy = electronicFence.getOperationStrategy();
        String newOperationStrategy = dto.getOperationStrategy();
        // 更新主表数据
@@ -666,9 +675,109 @@
                xpPointMapper.updateById(xpPoint);
            }
        }
        OperationStrategyIndex oldIndex = OperationStrategyIndex.parse(oldOperationStrategy);
        OperationStrategyIndex newIndex = OperationStrategyIndex.parse(newOperationStrategy);
        boolean needAutoDisableByUnion = !oldIndex.equalsNormalized(newIndex) && oldIndex.hasAnyShrinkComparedTo(newIndex);
        List<Long> addPointIds = normalizePointIds(dto.getAddPointIdList());
        List<Long> delPointIds = normalizePointIds(dto.getDelPointIdList());
        List<Long> disablePointIds = normalizePointIds(dto.getDisablePointIdList());
        boolean needPointHandling = CollUtil.isNotEmpty(addPointIds)
                || CollUtil.isNotEmpty(delPointIds)
                || CollUtil.isNotEmpty(disablePointIds);
        boolean needFenceAreaIds = needPointHandling || needAutoDisableByUnion;
        XpElectronicFenceArea operationArea = needFenceAreaIds
                ? xpElectronicFenceAreaService.lambdaQuery()
                .eq(XpElectronicFenceArea::getElectronicId, dto.getId())
                .eq(XpElectronicFenceArea::getAreaType, 1)
                .orderByDesc(XpElectronicFenceArea::getId)
                .last("limit 1")
                .one()
                : null;
        Long operationAreaId = operationArea == null ? null : operationArea.getId();
        if (needPointHandling && operationAreaId == null) {
            throw new ServiceException("运营区域不存在", I18nKeyConstant.POINT_ELECTRONIC_AREA_NOT_EXIST);
        }
        if (CollUtil.isNotEmpty(addPointIds)) {
            if (operationAreaId == null) {
                throw new ServiceException("运营区域不存在", I18nKeyConstant.POINT_ELECTRONIC_AREA_NOT_EXIST);
            }
            xpPointService.lambdaUpdate()
                    .in(XpPoint::getId, addPointIds)
                    .set(XpPoint::getElectronicAreaId, operationAreaId)
                    .set(XpPoint::getVerificationStatus, 1)
                    .update();
        }
        if (CollUtil.isNotEmpty(disablePointIds)) {
            // operation_status: 3=已停用
            xpPointService.lambdaUpdate()
                    .in(XpPoint::getId, disablePointIds)
                    .eq(XpPoint::getElectronicAreaId, operationAreaId)
                    .set(XpPoint::getOperationStatus, 3)
                    .update();
        }
        if (CollUtil.isNotEmpty(delPointIds)) {
            // operation_status: 4=已失效
            xpPointService.lambdaUpdate()
                    .in(XpPoint::getId, delPointIds)
                    .eq(XpPoint::getElectronicAreaId, operationAreaId)
                    .set(XpPoint::getOperationStatus, 4)
                    .update();
        }
        // 围栏运营时间缩短校验:若缩短后点位时间段不在新范围内,则更新为已停用(operation_status=3)
        if (needAutoDisableByUnion && operationAreaId != null) {
            LambdaQueryWrapper<XpPoint> pointQuery = new LambdaQueryWrapper<>();
            pointQuery.eq(XpPoint::getElectronicAreaId, operationAreaId)
                    .eq(XpPoint::getPointType, 1)
                    .eq(XpPoint::getOperationType, 2);
            List<XpPoint> points = xpPointMapper.selectList(pointQuery);
            if (points != null && !points.isEmpty()) {
                List<Long> disablePointIdList = new ArrayList<>();
                for (XpPoint pointItem : points) {
                    if (pointItem == null) {
                        continue;
                    }
                    if (pointItem.getOperationStatus() != null && pointItem.getOperationStatus() == 4) {
                        continue;
                    }
                    String effectiveTimePeriod = pointItem.getEffectiveTimePeriod();
                    if (effectiveTimePeriod == null || effectiveTimePeriod.isEmpty()) {
                        continue;
                    }
                    List<OperationStrategyIndex.TimeInterval> pointIntervals = OperationStrategyIndex.parsePointEffectiveTimePeriod(effectiveTimePeriod);
                    if (pointIntervals.isEmpty()) {
                        continue;
                    }
                    boolean shouldDisable = !newIndex.isPointScheduleWithin(pointIntervals);
                    if (shouldDisable && pointItem.getId() != null) {
                        disablePointIdList.add(pointItem.getId());
                    }
                }
                if (!disablePointIdList.isEmpty()) {
                    XpPoint updatePoint = new XpPoint();
                    updatePoint.setOperationStatus(3);
                    UpdateWrapper<XpPoint> updateWrapper = new UpdateWrapper<>();
                    updateWrapper.in("id", disablePointIdList);
                    updateWrapper.and(w -> w.isNull("operation_status").or().ne("operation_status", 4));
                    xpPointMapper.update(updatePoint, updateWrapper);
                    log.info("围栏运营时间缩短停用点位完成 fenceId={}, disableCount={}", dto.getId(), disablePointIdList.size());
                }
            }
        }
        return R.ok();
    }
    private static List<Long> normalizePointIds(List<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        return ids.stream().filter(Objects::nonNull).distinct().collect(Collectors.toList());
    }
    /**
     * 判断点是否在多边形内
xp-robotaxi-map-boot/src/main/resources/i18n/messages.properties
@@ -39,4 +39,4 @@
point.city.not.exist=城市信息不存在
point.number.lock.fail=点位编号生成繁忙,请稍后重试
point.number.parse.fail=点位编号生成失败(历史编号格式异常)
point.delete.has.cruise.plan.point=操作失败,该点位有关联的巡游点位
point.delete.has.cruise.plan.point=操作失败,该点位有关联的巡游计划点位
xp-robotaxi-map-boot/src/main/resources/i18n/messages_zh_CN.properties
@@ -32,4 +32,4 @@
point.city.not.exist=城市信息不存在
point.number.lock.fail=点位编号生成繁忙,请稍后重试
point.number.parse.fail=点位编号生成失败(历史编号格式异常)
point.delete.has.cruise.plan.point=操作失败,该点位有关联的巡游点位
point.delete.has.cruise.plan.point=操作失败,该点位有关联的巡游计划点位
xp-robotaxi-map-boot/src/main/resources/mapper/XpPointMapper.xml
@@ -91,7 +91,7 @@
                        <when test="st == 5">OR (t1.verification_status = 5 and t1.operation_status = 1)</when>
                        <when test="st == 6">OR t1.operation_status = 2</when>
                        <when test="st == 7">OR t1.operation_status = 3</when>
                        <when test="st == 8">OR t1.operation_status = 4</when>
                        <when test="st == 8">OR (t1.verification_status = 5 and t1.operation_status = 4)</when>
                        <otherwise>OR 1 = 0</otherwise>
                    </choose>
                </foreach>
@@ -157,7 +157,7 @@
                        <when test="st == 5">OR (xp.verification_status = 5 and xp.operation_status = 1)</when>
                        <when test="st == 6">OR xp.operation_status = 2</when>
                        <when test="st == 7">OR xp.operation_status = 3</when>
                        <when test="st == 8">OR xp.operation_status = 4</when>
                        <when test="st == 8">OR (xp.verification_status = 5 and xp.operation_status = 4)</when>
                        <otherwise>OR 1 = 0</otherwise>
                    </choose>
                </foreach>
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpAppVersionServiceImpl.java
@@ -62,7 +62,7 @@
        if (Objects.isNull(latestVersion)) {
            log.warn("未找到App版本信息");
            throw new ServiceException("App版本信息不存在");
            return null;
        }
        // 转换为 VO