puzhibing
2026-04-24 929332664363a6e496fa0367b08aa2c3689ad8fe
Merge remote-tracking branch 'origin/1.0' into 1.0
2个文件已添加
28个文件已修改
792 ■■■■■ 已修改文件
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/controller/XpAppUserCouponController.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/model/dto/SendCouponDto.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/model/vo/XpAppUserCouponVo.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/schedule/AppUserCouponTask.java 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/IXpAppUserCouponService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/impl/XpAppUserCouponServiceImpl.java 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/account/UseUserCouponDto.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/account/XpAppUserCouponClientFallbackFactory.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/account/XpAppUserCouponClient.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/account/XpAppUserCoupon.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/operation/XpElectronicFence.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/operation/XpPoint.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/query/operation/PointPageListListQuery.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/enums/MessageTemplateEnum.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpElectronicFenceManagementController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/job/jobs/ElectronicEffectJob.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/mapper/XpElectronicFenceMapper.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/mapper/XpPointMapper.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/schedule/ElectronicFenceFailureCompensationTask.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/service/XpElectronicFenceService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/service/XpPointService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/service/serviceimpl/XpElectronicFenceServiceImpl.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/service/serviceimpl/XpPointServiceImpl.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/service/serviceimpl/XpStationServiceImpl.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/vo/FenceAndStationVo.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/resources/mapper/XpElectronicFenceMapper.xml 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/resources/mapper/XpPointMapper.xml 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/resources/mapper/XpStationMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/resources/mapper/XpTestElectronicFenceMapper.xml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/BootCommonService.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/controller/XpAppUserCouponController.java
@@ -273,6 +273,17 @@
        }
        return R.ok(null);
    }
    /**
     * 获取即将过期的优惠卷
     */
    @GetMapping("/api/v1/mini-program/coupon/findListExpirationTime")
    @ApiOperation(value = "获取即将过期的优惠卷", tags = {"小程序-获取即将过期的优惠卷"})
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<List<XpAppUserCoupon>> findListExpirationTime(HttpServletRequest request) {
        String uid = request.getHeader("XP-Uid");
        List<XpAppUserCoupon> listExpirationTime = xpAppUserCouponService.findListExpirationTime(uid);
        return R.ok(listExpirationTime);
    }
    /**
     * 获取优惠券列表total
@@ -294,6 +305,12 @@
    }
    @PostMapping("/api/v1/appUserCoupon/saveInfo")
    @ApiOperation(value = "活动发放优惠券", tags = {"管理后台-活动发放优惠券"})
    public R<String> sendSave(@RequestBody UseUserCouponDto dto) {
        xpAppUserCouponService.saveInfo(dto);
        return R.ok();
    }
    /**
     * 发放优惠券
     */
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/model/dto/SendCouponDto.java
@@ -15,4 +15,7 @@
    @ApiModelProperty("优惠券id")
    private Long couponId;
    @ApiModelProperty(value = "活动id")
    private Long activityId;
}
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/model/vo/XpAppUserCouponVo.java
@@ -1,6 +1,9 @@
package com.xiaopeng.account.model.vo;
import com.xiaopeng.api.model.account.XpAppUserCoupon;
import com.xiaopeng.api.model.operation.XpCoupon;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@@ -13,4 +16,12 @@
    private Integer total;
    private List<XpAppUserCouponListVo> list;
    @ApiModelProperty(value = "用户优惠券")
    private XpAppUserCoupon userCoupon;
    @ApiModelProperty(value = "优惠券")
    private XpCoupon xpCoupon;
}
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/schedule/AppUserCouponTask.java
New file
@@ -0,0 +1,89 @@
package com.xiaopeng.account.schedule;
import cn.hutool.core.collection.CollUtil;
import com.xiaopeng.account.model.vo.XpAppUserCouponVo;
import com.xiaopeng.account.service.IXpAppUserCouponService;
import com.xiaopeng.api.feignClient.pushMessage.SubscribeMessageClient;
import com.xiaopeng.api.model.account.XpAppUserCoupon;
import com.xiaopeng.api.vo.pushMessage.SendSubscribeMessageVo;
import com.xiaopeng.common.core.enums.MessageTemplateEnum;
import com.xiaopeng.common.redis.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class AppUserCouponTask {
    @Resource
    private  IXpAppUserCouponService xpAppUserCouponService;
    @Resource
    private SubscribeMessageClient subscribeMessageClient;
    @Resource
    private RedissonClient redissonClient;
    /**
     * 优惠卷过期 前 24 小时 发送微信通知
     * 每5分钟执行一次
     */
    @Scheduled(fixedRate = 60*1000)
    public void appUserCoupon_expired() throws InterruptedException {
        List<XpAppUserCoupon> list = xpAppUserCouponService.findListExpirationTime(null);
        if(CollUtil.isNotEmpty( list)){
            Map<Long, List<XpAppUserCoupon>> groupedByUserId = list.stream()
                    .collect(java.util.stream.Collectors.groupingBy(XpAppUserCoupon::getAppUserId));
            for (Map.Entry<Long, List<XpAppUserCoupon>> entry : groupedByUserId.entrySet()) {
                Long appUserId = entry.getKey();
                RLock lock = redissonClient.getLock("app_user_coupon_expired_" + appUserId );
                if(lock.tryLock(0, 10, TimeUnit.SECONDS)){
                    try {
                        List<Long> ids = new ArrayList<>();
                        BigDecimal money = BigDecimal.ZERO;
                        String couponName = "";
                        for (XpAppUserCoupon xpAppUserCoupon : entry.getValue()) {
                            ids.add(xpAppUserCoupon.getId());
                            if(money.compareTo(xpAppUserCoupon.getMoney())<0){
                                money = xpAppUserCoupon.getMoney();
                                couponName = xpAppUserCoupon.getCouponName();
                            }
                        }
                        //发送微信通知
                        SendSubscribeMessageVo msgVo = new SendSubscribeMessageVo();
                        msgVo.setUserId(appUserId);
                        msgVo.setTemplateId(MessageTemplateEnum.C.getTemplateId());
                        String json = MessageTemplateEnum.C.getRemarks();
                        json = json.replace("优惠券名称", couponName);
                        json = json.replace("XXX", money.toPlainString() );
                        msgVo.setJson(json);
                        subscribeMessageClient.sendSubscribeMessage(msgVo);
                        xpAppUserCouponService.updateExpirationTime(ids);
                    } catch (Exception e) {
                        log.error("优惠券过期发送微信通知失败", e);
                    } finally {
                        lock.unlock();
                    }
                }
            }
        }
    }
}
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/IXpAppUserCouponService.java
@@ -9,11 +9,13 @@
import com.xiaopeng.account.model.vo.XpAppUserCouponVo;
import com.xiaopeng.account.vo.UserCouponListVo;
import com.xiaopeng.api.dto.account.NavigationCouponPickerRequest;
import com.xiaopeng.api.dto.account.UseUserCouponDto;
import com.xiaopeng.api.model.account.XpAppUserCoupon;
import com.xiaopeng.api.query.account.XpAppUserCouponPageListQuery;
import com.xiaopeng.api.vo.account.NavigationCouponPickerVo;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.web.page.PageInfo;
import org.springframework.scheduling.annotation.Async;
import java.math.BigDecimal;
import java.util.List;
@@ -29,6 +31,10 @@
public interface IXpAppUserCouponService extends IService<XpAppUserCoupon> {
    PageInfo<XpAppUserCouponListVo> pageList(XpAppUserCouponPageListQuery query);
    List<XpAppUserCoupon> findListExpirationTime(String appUserId);
    boolean updateExpirationTime(List<Long> appUserIds);
    /**
     * 获取用户优惠券列表
@@ -65,6 +71,9 @@
     */
    NavigationCouponPickerVo navigationCouponPicker(Long userId, NavigationCouponPickerRequest request);
    @Async("couponSendExecutor")
    boolean saveInfo(UseUserCouponDto dto);
    /**
     * 发送优惠券
     *
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/impl/XpAppUserCouponServiceImpl.java
@@ -4,6 +4,7 @@
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaopeng.account.mapper.XpAppUserCouponMapper;
@@ -19,7 +20,9 @@
import com.xiaopeng.account.vo.UserCouponListVo;
import com.xiaopeng.api.dto.account.NavigationCouponPickerRequest;
import com.xiaopeng.api.dto.account.UcUserInfo;
import com.xiaopeng.api.dto.account.UseUserCouponDto;
import com.xiaopeng.api.enums.NavigationCouponBubbleType;
import com.xiaopeng.api.feignClient.pushMessage.SubscribeMessageClient;
import com.xiaopeng.api.query.account.XpAppUserCouponPageListQuery;
import com.xiaopeng.api.vo.account.NavigationCouponItemVo;
import com.xiaopeng.api.vo.account.NavigationCouponPickerVo;
@@ -28,13 +31,16 @@
import com.xiaopeng.api.feignClient.operation.XpWhiteListClient;
import com.xiaopeng.api.model.account.XpAppUserCoupon;
import com.xiaopeng.api.model.operation.XpCoupon;
import com.xiaopeng.api.vo.pushMessage.SendSubscribeMessageVo;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.enums.MessageTemplateEnum;
import com.xiaopeng.common.core.utils.DateUtils;
import com.xiaopeng.common.core.web.page.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
@@ -65,6 +71,8 @@
    @Autowired
    private UserResourcesClient userResourcesClient;
    @Resource
    private SubscribeMessageClient subscribeMessageClient;
    @Override
@@ -73,6 +81,42 @@
        List<XpAppUserCouponListVo> list = this.baseMapper.pageList(pageInfo,query);
        pageInfo.setRecords(list);
        return pageInfo;
    }
    /**
     * 查询未使用、还有24小时就要过期的数据
     * @return
     */
    @Override
    public List<XpAppUserCoupon> findListExpirationTime(String appUserId){
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime expirationThreshold = now.plusHours(24);
        LambdaQueryWrapper<XpAppUserCoupon> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(XpAppUserCoupon::getDeleted, 0);
        wrapper.eq(XpAppUserCoupon::getStatus, 1);
        if(appUserId != null){
            wrapper.eq(XpAppUserCoupon::getAppUserId, appUserId);
        }
        // - 券失效前 24 小时
        wrapper.ge(XpAppUserCoupon::getExpirationTime, now);
        wrapper.le(XpAppUserCoupon::getExpirationTime, expirationThreshold);
        // 过滤 sendMsgTimeWx 不是当天发送的消息,避免重复发送
        wrapper.isNull(XpAppUserCoupon::getSendMsgTimeWx)
                .or()
                .lt(XpAppUserCoupon::getSendMsgTimeWx, now.toLocalDate().atStartOfDay());
        return super.list(wrapper);
    }
    @Override
    public boolean updateExpirationTime(List<Long> appUserIds){
        LambdaUpdateWrapper<XpAppUserCoupon> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.in(XpAppUserCoupon::getId, appUserIds);
        updateWrapper.set(XpAppUserCoupon::getSendMsgTimeWx, LocalDateTime.now());
        return this.update(updateWrapper);
    }
@@ -156,6 +200,36 @@
        return xpAppUserCoupons;
    }
    @Async("couponSendExecutor")
    @Override
    public boolean saveInfo(UseUserCouponDto dto) {
        R<XpCoupon> coupon = xpCouponClient.getCouponById(dto.getCouponId());
        if (coupon != null) {
            XpCoupon xpCoupon = coupon.getData();
            for (Long userId : dto.getUserIds()) {
                UcUserInfo userByMobile = userResourcesClient.getBaseUserByUid(String.valueOf(userId));
                XpAppUserCoupon xpAppUserCoupon = new XpAppUserCoupon();
                xpAppUserCoupon.setFullMoney(xpCoupon.getConditionalAmount());
                xpAppUserCoupon.setMoney(xpCoupon.getAmount());
                xpAppUserCoupon.setCouponId(dto.getCouponId());
                xpAppUserCoupon.setActivityId(dto.getActivityId());
                xpAppUserCoupon.setAppUserId(userId);
                xpAppUserCoupon.setExpirationTime(LocalDateTime.now().plusDays(7));
                xpAppUserCoupon.setStatus(1);
                xpAppUserCoupon.setCouponType(xpCoupon.getCouponType());
                xpAppUserCoupon.setPhone(userByMobile.getMobile());
                xpAppUserCoupon.setCouponName(xpCoupon.getCouponName());
                xpAppUserCoupon.setOpenCityId("0");
                xpAppUserCoupon.setTimeJson("[{\"time\": [\"00:00-23:59\"], \"type\": 3, \"typeStr\": 0}]");
                baseMapper.insert(xpAppUserCoupon);
                sendMsg(xpAppUserCoupon.getAppUserId(), xpCoupon, xpAppUserCoupon.getExpirationTime());
            }
        }
        return true;
    }
    /**
     * 发送优惠券
     *
@@ -186,6 +260,7 @@
                    xpAppUserCoupon.setFullMoney(xpCoupon.getConditionalAmount());
                    xpAppUserCoupon.setMoney(xpCoupon.getAmount());
                    xpAppUserCoupon.setCouponId(dto.getCouponId());
                    xpAppUserCoupon.setActivityId(dto.getActivityId());
                    xpAppUserCoupon.setAppUserId(uid);
                    xpAppUserCoupon.setExpirationTime(LocalDateTime.now().plusDays(7));
                    xpAppUserCoupon.setStatus(1);
@@ -196,6 +271,8 @@
                    xpAppUserCoupon.setOpenCityId("0");
                    xpAppUserCoupon.setTimeJson("[{\"time\": [\"00:00-23:59\"], \"type\": 3, \"typeStr\": 0}]");
                    baseMapper.insert(xpAppUserCoupon);
                    sendMsg(uid, xpCoupon, xpAppUserCoupon.getExpirationTime());
                }
            }
        }
@@ -203,6 +280,19 @@
        return null;
    }
    private void sendMsg(Long uid, XpCoupon xpCoupon, LocalDateTime expirationTime ) {
        //发送微信通知
        SendSubscribeMessageVo vo = new SendSubscribeMessageVo();
        vo.setUserId(uid);
        vo.setTemplateId(MessageTemplateEnum.D.getTemplateId());
        String json = MessageTemplateEnum.D.getRemarks();
        json = json.replace("优惠券名称", xpCoupon.getCouponName());
        json = json.replace("券有效期", expirationTime.toString() );
        json = json.replace("使用规则", xpCoupon.getCouponDescription());
        vo.setJson(json);
        subscribeMessageClient.sendSubscribeMessage(vo);
    }
    @Override
    public Integer getUserCouponNum(String uid) {
        if (uid == null) {
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/account/UseUserCouponDto.java
@@ -6,6 +6,8 @@
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@ApiModel(value = "使用用户优惠券参数")
@AllArgsConstructor
@@ -13,6 +15,11 @@
public class UseUserCouponDto {
    @ApiModelProperty(value = "用户ID")
    private Long userId;
    @ApiModelProperty(value = "用户IDs")
    private List<Long> userIds;
    @ApiModelProperty(value = "订单ID")
    private Long orderId;
    @ApiModelProperty(value = "用户优惠券id")
@@ -23,4 +30,11 @@
    @ApiModelProperty(value = "开通城市id")
    private Long openCityId;
    @ApiModelProperty("优惠券id")
    private Long couponId;
    @ApiModelProperty(value = "活动id")
    private Long activityId;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/account/XpAppUserCouponClientFallbackFactory.java
@@ -52,6 +52,12 @@
                return false;
            }
            @Override
            public R<String> sendSave(UseUserCouponDto dto) {
                log.error("优惠券服务繁忙,原因: {}", cause.getMessage());
                return null;
            }
        };
    }
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/account/XpAppUserCouponClient.java
@@ -6,6 +6,7 @@
import com.xiaopeng.api.vo.account.UserCouponVo;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.dto.ServiceNameConstantsEnum;
import io.swagger.annotations.ApiOperation;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@@ -41,4 +42,15 @@
    @GetMapping("/logan/inner/appUserCoupon/holding")
    boolean isHolding(Long id);
    /**
     * userIds 、couponId、activityId
     * @param dto
     * @return
     */
    @PostMapping("/api/v1/appUserCoupon/saveInfo")
    @ApiOperation(value = "活动发放优惠券", tags = {"管理后台-活动发放优惠券"})
     R<String> sendSave(@RequestBody UseUserCouponDto dto) ;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/account/XpAppUserCoupon.java
@@ -97,4 +97,11 @@
    @ApiModelProperty(value = "使用时间规则")
    private String timeJson;
    @ApiModelProperty(value = "发送微信通知时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime sendMsgTimeWx;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/operation/XpElectronicFence.java
@@ -57,6 +57,7 @@
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime failureTime;
    //[{"name": "策略组1", "time": ["04:00-10:00", "14:00-23:00"], "type": 0, "typeStr": []}]
    @ApiModelProperty(value = "运营策略")
    private String operationStrategy;
    @ApiModelProperty(value = "城市code")
@@ -70,4 +71,10 @@
    @ApiModelProperty(value = "禁行区域数量")
    @TableField(exist = false)
    private Long prohibitedAreaCount;
    @ApiModelProperty(value = "点位数量")
    @TableField(exist = false)
    private Long pointCount;
    @ApiModelProperty(value = "场站数量")
    @TableField(exist = false)
    private Long stationCount;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/operation/XpPoint.java
@@ -6,6 +6,7 @@
import java.time.LocalDateTime;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.xiaopeng.common.core.web.domain.BaseModel;
@@ -69,7 +70,7 @@
    @ApiModelProperty(value = "生效周期 1=“每日生效”、2=“仅工作日生效”、3=“仅非工作日生效”、4=“仅节假日生效”")
    private Integer effectivePeriod;
    @ApiModelProperty(value = "生效时间段")
    @ApiModelProperty(value = "自定义运营生效时间段")
    private String effectiveTimePeriod;
    @ApiModelProperty(value = "点位地址")
@@ -123,6 +124,7 @@
    @TableField("district_code")
    private String districtCode;
    @ApiModelProperty(value = "所属场站id,为空表示运营区直属点位")
    @TableField("station_id")
    private Long stationId;
@@ -134,5 +136,46 @@
    @ApiModelProperty(value = "点位上下车 1仅上车点/ 2 仅下车/ 3上下车均可")
    private Integer pointOnOff;
    public String getPointOnOffName() {
        if(pointOnOff != null){
            if(pointOnOff == 1){
                return "仅上车点";
            }else if(pointOnOff == 2){
                return "仅下车点";
            }else if(pointOnOff == 3){
                return "上下车均可";
            }
        }
        return "";
    }
    @ApiModelProperty(value = "电子围栏名称")
    @TableField(exist = false)
    private String electronicName;
    @ApiModelProperty(value = "所属场站名称")
    @TableField(exist = false)
    private String stationName;
    /**
     * hover在状态标签时,展示信息
     * - 已停用:展示停用原因;比如“因所属场站 [白云机场] 已关闭,该点位同步失效”
     * - 测试不通过:展示不通过原因
     * - 待生效:展示生效时间
     */
    @ApiModelProperty(value = "状态描述")
    @TableField(exist = false)
    private String hoverStatusDes;
    @ApiModelProperty(value = "场站的生效时间")
    @TableField(exist = false)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime electronicEffectTime;
    //[{"name": "策略组1", "time": ["04:00-10:00", "14:00-23:00"], "type": 0, "typeStr": []}]
    @ApiModelProperty(value = "场站运营策略")
    @TableField(exist = false)
    private String electronicOperationStrategy;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/query/operation/PointPageListListQuery.java
@@ -27,9 +27,21 @@
    @ApiModelProperty(value = "创建结束时间 yyyy-MM-dd HH:mm:ss")
    private String endTime;
    @ApiModelProperty(value = "城市编码")
    private String cityCode;
    @ApiModelProperty(value = "点位经度")
    private String pointLon;
    @ApiModelProperty(value = "点位纬度")
    private String pointLat;
    @ApiModelProperty(value = "类型 1围栏 2场站 ")
    private Integer idType;
    @ApiModelProperty(value = "fenceAndStationIds")
    private String fenceAndStationIds;
}
xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/enums/MessageTemplateEnum.java
@@ -12,7 +12,7 @@
            "{\"thing3\":{\"value\":\"活动名称\"},\"time1\":{\"value\":\"开始时间\"},\"time4\":{\"value\":\"结束时间\"},\"thing6\":{\"value\":\"温馨提示\"}}"),
    C(1,"微信","ANtToR63JPCV_ujV69gmO15xCa8O3elnLWB52HLdcHM","券效期通用模板",
            "{\"thing1\":{\"value\":\"优惠券名称\"},\"thing2\":{\"value\":\"备注\"}}"),
            "{\"thing1\":{\"value\":\"优惠券名称\"},\"thing2\":{\"value\":\"今日出行最高立减XXX元,点击领取\"}}"),
    D(1,"微信","P7z_ksdyzpiiOBO07EJ1AZ6WeHlWs4XveQYjmt9JRE8","券到账通用模板",
            "{\"thing1\":{\"value\":\"优惠券名称\"},\"time2\":{\"value\":\"券有效期\"},\"thing3\":{\"value\":\"使用规则\"}}"),
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpElectronicFenceManagementController.java
@@ -24,6 +24,7 @@
import com.xiaopeng.map.constant.I18nKeyConstant;
import com.xiaopeng.map.service.*;
import com.xiaopeng.map.utils.OperationStrategyIndex;
import com.xiaopeng.map.vo.FenceAndStationVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
@@ -89,6 +90,11 @@
        return R.ok(regionService.getXpRegion());
    }
    @ApiOperation(value = "获取围栏和场站数据",tags = {"管理后台-电子围栏"})
    @GetMapping(value = "/findListFenceAndStation")
    public R<List<FenceAndStationVo>> findListFenceAndStation() {
        return R.ok(xpPointService.findListFenceAndStation());
    }
    @ApiOperation(value = "获取不分页的电子围栏列表",tags = {"管理后台-电子围栏"})
    @PostMapping("/list")
@@ -119,7 +125,7 @@
     */
    @PostMapping("/add")
    @ApiOperation(value = "新增电子围栏")
    public R add(@Validated @RequestBody ElectronicFenceAddDto dto) {
    public R add(@Valid @RequestBody ElectronicFenceAddDto dto) {
        validateFenceTime(dto.getStatus(), dto.getEffectType(), dto.getEffectTime(), dto.getFailureType(), dto.getFailureTime());
        return xpElectronicFenceService.addElectronic(dto);
    }
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/job/jobs/ElectronicEffectJob.java
@@ -22,23 +22,13 @@
    public static final String failure = "electronicFailure_";
    @Override
    public void execute(JobExecutionContext context)
            throws JobExecutionException {
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDataMap maps = context.getMergedJobDataMap();
        Long electronicId = maps.getLong("id");
        Long type = maps.getLong("type");
        try {
            if (type==1L){
                // 电子围栏生效
                XpElectronicFence electronicFence = xpElectronicFenceService.getById(electronicId);
                electronicFence.setStatus(2);
                xpElectronicFenceService.updateById(electronicFence);
                List<XpElectronicFenceArea> electronicFenceAreaList = xpElectronicFenceAreaService.lambdaQuery().eq(XpElectronicFenceArea::getElectronicId, electronicId).list();
                // 设置为生效
                for(XpElectronicFenceArea electronicFenceArea : electronicFenceAreaList){
                    electronicFenceArea.setAreaStatus(1);
                }
                xpElectronicFenceAreaService.updateBatchById(electronicFenceAreaList);
                xpElectronicFenceService.enableFence(electronicId);
                // 推送 FBS 到绑定车辆
                try {
@@ -49,17 +39,7 @@
                }
            }else{
                // 电子围栏失效
                XpElectronicFence electronicFence = xpElectronicFenceService.getById(electronicId);
                electronicFence.setStatus(3);
                xpElectronicFenceService.updateById(electronicFence);
                List<XpElectronicFenceArea> electronicFenceAreaList = xpElectronicFenceAreaService.lambdaQuery().eq(XpElectronicFenceArea::getElectronicId, electronicId).list();
                // 设置为禁用
                for(XpElectronicFenceArea electronicFenceArea : electronicFenceAreaList){
                    electronicFenceArea.setAreaStatus(2);
                }
                xpElectronicFenceAreaService.updateBatchById(electronicFenceAreaList);
                xpStationService.setStatusFailure(electronicId);//场站失效
                xpElectronicFenceService.expireFence(electronicId);
                // 清除绑定车辆的 FBS 数据
                try {
                    adcFencePushService.deactivateFenceForVehicles(electronicId);
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/mapper/XpElectronicFenceMapper.java
@@ -18,6 +18,9 @@
 * @since 2026-02-03
 */
public interface XpElectronicFenceMapper extends BaseMapper<XpElectronicFence> {
    Long pageListCount(@Param("query") ElectronicPageListQuery query);
    List<XpElectronicFence> pageList(@Param("pageInfo") PageInfo<XpElectronicFence> pageInfo, @Param("query") ElectronicPageListQuery query);
}
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/mapper/XpPointMapper.java
@@ -9,6 +9,7 @@
import com.xiaopeng.api.vo.order.XpPointsListVo;
import com.xiaopeng.common.core.web.page.PageInfo;
import com.xiaopeng.map.query.PointByNameQuery;
import com.xiaopeng.map.vo.FenceAndStationVo;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Param;
@@ -25,6 +26,8 @@
 */
public interface XpPointMapper extends BaseMapper<XpPoint> {
    List<FenceAndStationVo> findListFenceAndStation();
    List<XpPoint> pointMap(@Param("query") PointMapQuery query);
    List<XpPoint> listAll(@Param("query") PointListQuery query);
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/schedule/ElectronicFenceFailureCompensationTask.java
@@ -22,6 +22,7 @@
public class ElectronicFenceFailureCompensationTask {
    private static final String LOCK_KEY = "MAP:ELECTRONIC_FENCE:FAILURE_COMPENSATE";
    private static final String LOCK_KEY_ENABLE = "MAP:ELECTRONIC_FENCE:ENABLE_COMPENSATE";
    private static final int LOCK_WAIT_SECONDS = 0;
    private static final int LOCK_LEASE_SECONDS = 15;
@@ -31,6 +32,10 @@
    @Resource
    private RedissonClient redissonClient;
    /**
     * 定时任务 围栏失效补偿定时任务
     */
    @Scheduled(fixedDelay = 10 * 1000L)
    public void compensateExpiredFence() {
        RLock lock = redissonClient.getLock(LOCK_KEY);
@@ -78,4 +83,52 @@
            }
        }
    }
    /**
     * 围栏启动偿定时任务
     */
    @Scheduled(fixedDelay = 10 * 1000L)
    public void compensateEnableFence() {
        RLock lock = redissonClient.getLock(LOCK_KEY_ENABLE);
        boolean locked = false;
        try {
            locked = lock.tryLock(LOCK_WAIT_SECONDS, LOCK_LEASE_SECONDS, TimeUnit.SECONDS);
            if (!locked) {
                return;
            }
            List<XpElectronicFence> expiredFenceList = xpElectronicFenceService.lambdaQuery()
                    .eq(XpElectronicFence::getDeleted, 0)
                    .eq(XpElectronicFence::getStatus, 1)//围栏状态 0=草稿 1=待启用 2=启用中 3=已停用
                    .ge(XpElectronicFence::getFailureTime, LocalDateTime.now())//当前时间小于生效时间
                    .list();
            if (expiredFenceList == null || expiredFenceList.isEmpty()) {
                return;
            }
            List<Long> electronicIds = expiredFenceList.stream()
                    .map(XpElectronicFence::getId)
                    .collect(Collectors.toList());
            log.info("电子围栏失效补偿开始, count={}, ids={}", electronicIds.size(), electronicIds);
            for (Long electronicId : electronicIds) {
                try {
                    xpElectronicFenceService.enableFence(electronicId);
                } catch (Exception e) {
                    log.error("电子围栏失效补偿单条处理异常, electronicId={}", electronicId, e);
                }
            }
            log.info("电子围栏失效补偿完成, count={}, ids={}", electronicIds.size(), electronicIds);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.warn("电子围栏失效补偿任务获取锁被中断", e);
        } catch (Exception e) {
            log.error("电子围栏失效补偿任务执行异常", e);
        } finally {
            try {
                if (locked && lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            } catch (Exception e) {
                log.warn("释放电子围栏失效补偿锁失败 lockKey={}", LOCK_KEY, e);
            }
        }
    }
}
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/service/XpElectronicFenceService.java
@@ -8,7 +8,10 @@
import com.xiaopeng.api.query.operation.ElectronicPageListQuery;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.web.page.PageInfo;
import org.apache.shardingsphere.transaction.annotation.ShardingSphereTransactionType;
import org.apache.shardingsphere.transaction.core.TransactionType;
import org.springframework.data.mongodb.core.geo.GeoJsonPolygon;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@@ -46,6 +49,10 @@
     */
    List<XpElectronicFence> getList(ElectronicPageListQuery query);
    @Transactional
    @ShardingSphereTransactionType(TransactionType.BASE)
    void enableFence(Long electronicId);
    /**
     * 将围栏置为已停用,并同步禁用围栏区域
     * @param electronicId 围栏ID
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/service/XpPointService.java
@@ -16,6 +16,7 @@
import com.xiaopeng.map.dto.NearestDto;
import com.xiaopeng.map.dto.StationDetailQueryDto;
import com.xiaopeng.map.query.PointByNameQuery;
import com.xiaopeng.map.vo.FenceAndStationVo;
import com.xiaopeng.map.vo.NearestVo;
import com.xiaopeng.map.vo.OrderDistanceVo;
import com.xiaopeng.map.vo.StationDetailVo;
@@ -73,6 +74,8 @@
    boolean getInTime(LocalTime now,  Integer effectivePeriod, String effectiveTimePeriod);
    List<XpPoint> pointMap(PointMapQuery query);
    List<FenceAndStationVo> findListFenceAndStation();
    List<XpPoint> findList(PointListQuery query);
    List<XpPoint> listAll(PointListQuery query);
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/service/serviceimpl/XpElectronicFenceServiceImpl.java
@@ -31,6 +31,7 @@
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.OperationStrategyIndex;
import com.xiaopeng.map.utils.GDMapGeocodingUtil;
import com.xiaopeng.map.utils.PolygonOverlapService;
@@ -44,6 +45,7 @@
import org.locationtech.jts.geom.Polygon;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.geo.GeoJsonPolygon;
@@ -92,6 +94,9 @@
    private XpElectronicFenceAreaService xpElectronicFenceAreaService;
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;
    @Autowired
    @Lazy
    protected XpStationService xpStationService;
    @Override
    public PageInfo<XpElectronicFence> pageList(ElectronicPageListQuery query) {
@@ -100,10 +105,13 @@
        }
        normalizeProhibitedAreaCountFilter(query);
        PageInfo<XpElectronicFence> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
        List<XpElectronicFence> list = this.baseMapper.pageList(pageInfo, query);
        if (list.isEmpty()) {
        pageInfo.setSearchCount(false);
        Long total = baseMapper.pageListCount(query);
        pageInfo.setTotal(total);
        if (total == 0L) {
            return new PageInfo<>();
        }
        List<XpElectronicFence> list = this.baseMapper.pageList(pageInfo, query);
        List<Long> electronicIds = list.stream().map(XpElectronicFence::getId).collect(Collectors.toList());
        Map<Long, List<XpElectronicFenceArea>> electronicFenceAreaMap = xpElectronicFenceAreaMapper.selectList(new LambdaQueryWrapper<XpElectronicFenceArea>()
                        .in(XpElectronicFenceArea::getElectronicId, electronicIds)).stream()
@@ -258,21 +266,23 @@
        }
        //根据经纬度获取当前城市code
        XpElectronicFenceArea xpElectronicFenceArea = dto.getElectronicFenceAreas().get(0);
        String[] split = xpElectronicFenceArea.getFencePoints().split(",");
        Coordinate[] coordinates = new Coordinate[split.length + 1];
        for (int i = 0; i < split.length; i++) {
            String[] split1 = split[i].split("_");
            coordinates[i] = new Coordinate(Double.parseDouble(split1[0]), Double.parseDouble(split1[1]));
        }
        org.locationtech.jts.geom.Point point = calculateCentroid(coordinates);
        Map<String, String> geocode = gdMapGeocodingUtil.geocode(point.getX() + "", point.getY() + "");
        if (null == geocode) {
            return R.fail("经纬度转换城市code失败");
        }
        String cityCode = geocode.get("cityCode");
        dto.setCityCode(cityCode);
//        XpElectronicFenceArea xpElectronicFenceArea = dto.getElectronicFenceAreas().get(0);
//        String[] split = xpElectronicFenceArea.getFencePoints().split(",");
//        Coordinate[] coordinates = new Coordinate[split.length + 1];
//        for (int i = 0; i < split.length; i++) {
//            String[] split1 = split[i].split("_");
//            coordinates[i] = new Coordinate(Double.parseDouble(split1[0]), Double.parseDouble(split1[1]));
//        }
//        org.locationtech.jts.geom.Point point = calculateCentroid(coordinates);
//        Map<String, String> geocode = gdMapGeocodingUtil.geocode(point.getX() + "", point.getY() + "");
//        if (null == geocode) {
//            return R.fail("经纬度转换城市code失败");
//        }
//        String cityCode = geocode.get("cityCode");
//        dto.setCityCode(cityCode);
        xpElectronicFence.setCityCode(dto.getCityCode());
        // 生成围栏id 规则:城市首字母-类型简写-4位自增流水号(如 SZ-OP-001),支持复制;数据停用或删除后,流水号不回收
        XpRegion xpRegion = xpRegionMapper.selectOne(new LambdaQueryWrapper<XpRegion>()
                .eq(XpRegion::getCode, dto.getCityCode()));
@@ -405,6 +415,7 @@
            QuartzManager.removeJob((ElectronicEffectJob.effect + xpElectronicFence.getId()).toUpperCase(), TimeJobType.ELECTRONIC_EFFECT);
            QuartzManager.removeJob((ElectronicEffectJob.failure + xpElectronicFence.getId()).toUpperCase(), TimeJobType.ELECTRONIC_FAILURE);
        }
        /*
        //根据经纬度获取当前城市code
        XpElectronicFenceArea electronicFenceArea = dto.getElectronicFenceAreas().get(0);
        String[] split = electronicFenceArea.getFencePoints().split(",");
@@ -420,6 +431,8 @@
        }
        String cityCode = geocode.get("cityCode");
        dto.setCityCode(cityCode);
         */
        xpElectronicFence.setCityCode(dto.getCityCode());
        this.updateById(xpElectronicFence);
@@ -493,7 +506,7 @@
                        XpGeoFencingPolygon xpGeoFencingPolygon = new XpGeoFencingPolygon();
                        xpGeoFencingPolygon.setFenceId(String.valueOf(xpElectronicFence.getId()));
                        xpGeoFencingPolygon.setName(xpElectronicFence.getElectronicName());
                        xpGeoFencingPolygon.setCityCode(cityCode);
                        xpGeoFencingPolygon.setCityCode(dto.getCityCode());
                        xpGeoFencingPolygon.setAreaId(String.valueOf(newElectronicFenceArea.getId()));
                        xpGeoFencingPolygon.setOperationStrategy(xpElectronicFence.getOperationStrategy());
                        // 将围栏点转换为GeoJSON多边形
@@ -504,7 +517,7 @@
                        // 如果存在,则更新已有文档
                        old.setFenceId(String.valueOf(xpElectronicFence.getId()));
                        old.setName(xpElectronicFence.getElectronicName());
                        old.setCityCode(cityCode);
                        old.setCityCode(dto.getCityCode());
                        old.setAreaId(String.valueOf(newElectronicFenceArea.getId()));
                        old.setOperationStrategy(xpElectronicFence.getOperationStrategy());
                        // 将围栏点转换为GeoJSON多边形
@@ -1000,7 +1013,26 @@
        queryWrapper.like(query.getCityCode() != null, XpElectronicFence::getCityCode, query.getCityCode());
        return list(queryWrapper);
    }
    @Transactional
    @ShardingSphereTransactionType(TransactionType.BASE)
    @Override
    public void enableFence(Long electronicId) {
        // 电子围栏生效
        XpElectronicFence electronicFence = super.getById(electronicId);
        if(electronicFence != null){
            electronicFence.setStatus(2);
            super.updateById(electronicFence);
            List<XpElectronicFenceArea> electronicFenceAreaList = xpElectronicFenceAreaService.lambdaQuery().eq(XpElectronicFenceArea::getElectronicId, electronicId).list();
            if(!electronicFenceAreaList.isEmpty()){
                // 设置为生效
                for(XpElectronicFenceArea electronicFenceArea : electronicFenceAreaList){
                    electronicFenceArea.setAreaStatus(1);
                }
                xpElectronicFenceAreaService.updateBatchById(electronicFenceAreaList);
            }
        }
    }
    @Override
    @Transactional
    @ShardingSphereTransactionType(TransactionType.BASE)
@@ -1019,16 +1051,30 @@
        electronicFence.setStatus(3);
        this.updateById(electronicFence);
        List<String> toDeleteAreaIdStrings = new ArrayList<>();
        List<XpElectronicFenceArea> electronicFenceAreaList = xpElectronicFenceAreaService.lambdaQuery()
                .eq(XpElectronicFenceArea::getElectronicId, electronicId)
                .list();
        if (CollUtil.isEmpty(electronicFenceAreaList)) {
            return;
        if (!CollUtil.isEmpty(electronicFenceAreaList)) {
            for (XpElectronicFenceArea electronicFenceArea : electronicFenceAreaList) {
                electronicFenceArea.setAreaStatus(2);
                toDeleteAreaIdStrings.add(String.valueOf(electronicFenceArea.getId()));
            }
            xpElectronicFenceAreaService.updateBatchById(electronicFenceAreaList);
        }
        for (XpElectronicFenceArea electronicFenceArea : electronicFenceAreaList) {
            electronicFenceArea.setAreaStatus(2);
        xpStationService.setStatusFailure(electronicId);//场站失效
        // 删除旧的区域数据,然后保存新的区域数据
        mongoTemplate.remove(new Query(Criteria.where("fenceId").is(String.valueOf(electronicId))), XpGeoFencingPolygon.class);
        if(CollUtil.isNotEmpty(toDeleteAreaIdStrings)){
            Query delMongoQuery = new Query();
            delMongoQuery.addCriteria(Criteria.where("fenceId").is(String.valueOf(electronicId)));
            delMongoQuery.addCriteria(Criteria.where("areaId").in(toDeleteAreaIdStrings));
            mongoTemplate.remove(delMongoQuery, XpGeoFencingProhibitedPolygon.class);
        }
        xpElectronicFenceAreaService.updateBatchById(electronicFenceAreaList);
    }
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/service/serviceimpl/XpPointServiceImpl.java
@@ -2,6 +2,7 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -44,11 +45,7 @@
import com.xiaopeng.map.utils.TimeRangeChecker;
import com.xiaopeng.map.utils.holiday.HolidayUtil;
import com.xiaopeng.map.utils.map.map.FastDistanceCalculator;
import com.xiaopeng.map.vo.NearestNextVo;
import com.xiaopeng.map.vo.NearestVo;
import com.xiaopeng.map.vo.OrderDistanceVo;
import com.xiaopeng.map.vo.StationDetailVo;
import com.xiaopeng.map.vo.StationPointItemVo;
import com.xiaopeng.map.vo.*;
import lombok.extern.slf4j.Slf4j;
import net.sourceforge.pinyin4j.PinyinHelper;
import org.apache.shardingsphere.transaction.annotation.ShardingSphereTransactionType;
@@ -369,6 +366,12 @@
        List<XpPoint> list = this.baseMapper.pointMap(query);
        return list;
    }
    @Override
    public List<FenceAndStationVo> findListFenceAndStation(){
        return baseMapper.findListFenceAndStation();
    }
    @Override
    public List<XpPoint> findList(PointListQuery query) {
        return this.baseMapper.listAll(query);
@@ -567,6 +570,49 @@
            }
        }
        List<XpPoint> list = this.baseMapper.pageList(pageInfo,query);
        if(CollUtil.isNotEmpty(list)){
            /**
             * hover在状态标签时,展示信息
             * - 已停用:展示停用原因;比如“因所属场站 [白云机场] 已关闭,该点位同步失效”
             * - 测试不通过:展示不通过原因
             * - 待生效:展示生效时间
             */
            for (XpPoint point : list) {
                String hoverStatusDes = "";
                if(point.getVerificationStatus()==4){//验证状态 1=待验证、2=验证中、3=已验证 4验证失败 5待生效
                    hoverStatusDes = (point.getRemarks() == null ?"":point.getRemarks());
                }
                if(point.getOperationStatus() == 3){//运营状态 1=待启用 2=启用中 3=已停用 4=已失效
                    hoverStatusDes = (point.getRemarks()==null?"":point.getRemarks());
                }
                if(point.getVerificationStatus()==5 && point.getOperationStatus() == 1){
                    if(point.getOperationType() ==  1){//运营类型 1=与运营区同步 2=自定义时间段
                         ;
                        //[{"name": "策略组1", "time": ["06:00-12:00", "13:00-22:59"], "type": 0, "typeStr": []},
                        // {"name": "策略组2", "time": ["05:00-12:00", "12:00-16:00"], "type": 2, "typeStr": ""},
                        // {"name": "策略组3", "time": ["09:00-21:59"], "type": 1, "typeStr": ""}]
                        JSONArray electronicOperationStrategys = JSONArray.parseArray(point.getElectronicOperationStrategy());
                        if(!electronicOperationStrategys.isEmpty()){
                            StringJoiner joiner = new StringJoiner(";");
                            for (Object electronicOperationStrategy : electronicOperationStrategys) {
                                JSONObject obj = (JSONObject) electronicOperationStrategy;
                                List<String> times = obj.getList("time",String.class);
                                joiner.add((String.join(";", times)));
                            }
                            hoverStatusDes = joiner.toString()  ;
                        }
                    }else{
                        if(StrUtil.isNotBlank(point.getEffectiveTimePeriod()) ){
                            List<String> effectiveTimePeriods = JSONArray.parseArray(point.getEffectiveTimePeriod(), String.class);
                            hoverStatusDes = String.join(";", effectiveTimePeriods);
//                        hoverStatusDes = "待生效:"+point.getElectronicEffectTime().toString()  ;
                        }
                    }
                }
                point.setHoverStatusDes(hoverStatusDes);
            }
        }
        pageInfo.setRecords(list);
        return pageInfo;
    }
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/service/serviceimpl/XpStationServiceImpl.java
@@ -85,6 +85,15 @@
        if (list.isEmpty()) {
            return new PageInfo<>();
        }
        for (XpStation xpStation : list) {
            xpStation.setPointCount(0);
            xpStation.setOperatingPointCount(0);
            List<XpPoint> xpPoints = xpPointService.listByStationId((xpStation.getId()));
            if(CollUtil.isNotEmpty(xpPoints)){
                xpStation.setPointCount(xpPoints.size());
                xpStation.setOperatingPointCount((int) xpPoints.stream().filter(p -> p.getOperationStatus() != null && p.getOperationStatus() == 2).count());
            }
        }
        pageInfo.setRecords(list);
        return pageInfo;
    }
@@ -292,6 +301,13 @@
        }
    }
    /**
     * 设置场站状态 0=草稿 1=待启用 2=启用中 3=已停用 4=失效
     * 并操作 mongo
     * @param bean
     * @param status
     * @return
     */
    @Override
    public boolean setStatus(XpStation bean, Integer status){
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/vo/FenceAndStationVo.java
New file
@@ -0,0 +1,20 @@
package com.xiaopeng.map.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class FenceAndStationVo {
//    1 as type, a.electronic_name as name,
//    GROUP_CONCAT(b.id) ids
    @ApiModelProperty(value = "类型 1围栏 2场站 ")
    private Integer type;
    @ApiModelProperty(value = "名称")
    private String name;
    @ApiModelProperty(value = "ids")
    private String ids;
}
xp-robotaxi-map-boot/src/main/resources/mapper/XpElectronicFenceMapper.xml
@@ -23,11 +23,43 @@
    <sql id="Base_Column_List">
        id, electronic_name, electronic_number, status, effect_type, effect_time, failure_type, failure_time, operation_strategy, is_deleted, create_time, update_time, create_by, update_by
    </sql>
    <select id="pageListCount" resultType="Long">
        select count(f.id)  as count from (
            select ef.id
        <include refid="pageListWhere"/>
        ) f
    </select>
    <select id="pageList" resultType="com.xiaopeng.api.model.operation.XpElectronicFence">
        select ef.id, ef.electronic_name as electronicName, ef.electronic_number as electronicNumber, ef.status, ef.effect_type as effectType,
        ef.city_code,
               ef.effect_time as effectTime, ef.failure_type as failureType, ef.failure_time as failureTime, ef.operation_strategy as operationStrategy
        , ef.create_time as createTime, ef.update_time as updateTime, ef.create_by as createBy, ef.update_by as updateBy
        select ef.*,
        (SELECT COUNT(xs.id) FROM xp_station xs WHERE xs.is_deleted=0 and xs.electronic_id = ef.id) stationCount,
        SUM((SELECT COUNT(xp.id) FROM xp_point xp WHERE xp.is_deleted=0 and xp.electronic_area_id = efa.id ))as pointCount
        <include refid="pageListWhere"/>
        <choose>
            <when test="query.createSort == 1 or query.createSort == 2 or query.updateSort == 1 or query.updateSort == 2">
                <trim prefix="order by" suffixOverrides=",">
                    <if test="query.createSort == 1">
                        ef.create_time asc,
                    </if>
                    <if test="query.createSort == 2">
                        ef.create_time desc,
                    </if>
                    <if test="query.updateSort == 1">
                        ef.update_time asc,
                    </if>
                    <if test="query.updateSort == 2">
                        ef.update_time desc,
                    </if>
                </trim>
            </when>
            <otherwise>
                order by ef.update_time desc
            </otherwise>
        </choose>
    </select>
    <sql id="pageListWhere" >
        from xp_electronic_fence ef
        left join xp_electronic_fence_area efa on efa.electronic_id = ef.id and efa.is_deleted = 0
        where ef.is_deleted = 0
@@ -75,29 +107,6 @@
                having sum(case when efa.area_type = 2 then 1 else 0 end) &lt;= #{query.prohibitedAreaCountMax}
            </when>
        </choose>
        <choose>
            <when test="query.createSort == 1 or query.createSort == 2 or query.updateSort == 1 or query.updateSort == 2">
                <trim prefix="order by" suffixOverrides=",">
                    <if test="query.createSort == 1">
                        ef.create_time asc,
                    </if>
                    <if test="query.createSort == 2">
                        ef.create_time desc,
                    </if>
                    <if test="query.updateSort == 1">
                        ef.update_time asc,
                    </if>
                    <if test="query.updateSort == 2">
                        ef.update_time desc,
                    </if>
                </trim>
            </when>
            <otherwise>
                order by ef.update_time desc
            </otherwise>
        </choose>
    </select>
    </sql>
</mapper>
xp-robotaxi-map-boot/src/main/resources/mapper/XpPointMapper.xml
@@ -26,11 +26,13 @@
        <result column="update_by" property="updateBy" />
        <result column="station_id" property="stationId" />
        <result column="station_sort_no" property="stationSortNo" />
        <result column="point_on_off" property="pointOnOff" />
    </resultMap>
    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id,electronic_area_id, point_name, point_poi, point_number, operation_status, point_type, verification_status, parking_time_limit, operation_type, effective_period, effective_time_period, remarks, point_pictures, point_lon, point_lat, is_deleted, create_time, update_time, create_by, update_by, station_id, station_sort_no
        id,electronic_area_id, point_name, point_poi, point_number, operation_status, point_type, verification_status, parking_time_limit, operation_type, effective_period,
        effective_time_period, remarks, point_pictures, point_lon, point_lat, is_deleted, create_time, update_time, create_by, update_by, station_id, station_sort_no,point_on_off
    </sql>
    <select id="pointMap" resultType="com.xiaopeng.api.model.operation.XpPoint">
        select t1.*,
@@ -68,6 +70,18 @@
        </if>
        order by distance  asc
    </select>
    <select id="findListFenceAndStation" resultType="com.xiaopeng.map.vo.FenceAndStationVo">
        SELECT  1 as type, a.electronic_name as name, GROUP_CONCAT(b.id) ids
        FROM xp_electronic_fence a
        INNER JOIN xp_electronic_fence_area b on b.electronic_id = a.id and b.is_deleted = 0
        WHERE a.is_deleted = 0
        GROUP BY a.id
        UNION ALL
        SELECT 2 as type ,s.station_name as name,s.id as ids FROM xp_station s WHERE s.is_deleted = 0
    </select>
    <select id="listAll" resultType="com.xiaopeng.api.model.operation.XpPoint">
        select t1.*
        from xp_point t1
@@ -115,7 +129,8 @@
    <select id="queryPointByName" resultType="com.xiaopeng.api.vo.order.XpPointsListVo">
        select p.id,p.electronic_area_id as electronicAreaId,p.point_name as name,p.point_address as address,p.point_lon as longitude,p.point_lat as latitude,p.effective_period,p.effective_time_period
        select p.id,p.electronic_area_id as electronicAreaId,p.point_name as name,p.point_address as address,
               p.point_lon as longitude,p.point_lat as latitude,p.effective_period,p.effective_time_period,p.point_on_off,
        from xp_electronic_fence_area xefa
        left join xp_point p on p.electronic_area_id = xefa.id
        where p.is_deleted = 0 and p.operation_status =2 and xefa.is_deleted = 0
@@ -131,12 +146,32 @@
    </select>
    <select id="pageList" resultType="com.xiaopeng.api.model.operation.XpPoint">
        select xp.id,xp.point_number as pointNumber,xp.point_name as pointName,xp.point_poi as pointPoi,
               xp.point_address as pointAddress,xp.city_code,
               xp.point_address as pointAddress,xp.city_code,xp.point_on_off,xp.operation_type,xp.effective_time_period,
               xp.point_lon as pointLon,xp.point_lat as pointLat,xp.create_time as createTime,xp.create_by as createBy
        ,xp.parking_time_limit as parkingTimeLimit,xp.verification_status as verificationStatus,xp.operation_status as operationStatus
        ,xp.station_id as stationId,xp.station_sort_no as stationSortNo
        ,xp.station_id as stationId,xp.station_sort_no as stationSortNo,xp.remarks,
        xef.electronic_name as electronicName,xs.station_name  as stationName,xef.effect_time,
        xef.operation_strategy electronicOperationStrategy
        from xp_point xp
        left join xp_electronic_fence_area xefa on xefa.id = xp.electronic_area_id
        left join xp_electronic_fence xef on xef.id = xefa.electronic_id
        left join xp_station xs on xs.id = xp.station_id
        where xp.is_deleted = 0
        <if test="query.idType != null">
            <choose>
                <when test="query.idType == 1">
                    and xefa.id in (${query.fenceAndStationIds})
                </when>
                <when test="query.idType == 2">
                    and xs.id =#{query.fenceAndStationIds}
                </when>
            </choose>
        </if>
        <if test="query.cityCode != null and query.cityCode != ''">
            and xp.city_code =  #{query.cityCode}
        </if>
        <if test="query.pointName != null and query.pointName != ''">
            and xp.point_name like concat('%',#{query.pointName},'%')
        </if>
xp-robotaxi-map-boot/src/main/resources/mapper/XpStationMapper.xml
@@ -3,16 +3,11 @@
<mapper namespace="com.xiaopeng.map.mapper.XpStationMapper">
    <select id="pageList" resultType="com.xiaopeng.api.model.operation.XpStation">
        select a.*,
            (select count(1) from xp_point p where p.station_id = a.id) point_count,
            (select count(1) from xp_point p where p.station_id = a.id and p.operation_status = 2) operatingPointCount
        select a.*
        from xp_station a
        inner join xp_electronic_fence b on a.electronic_id = b.id and b.is_deleted = 0
        where a.is_deleted = 0
          <if test="query.ids != null">
            and a.id in
            <foreach item="id" collection="query.ids" open="(" separator="," close=")">
xp-robotaxi-map-boot/src/main/resources/mapper/XpTestElectronicFenceMapper.xml
@@ -29,7 +29,7 @@
               ef.effect_time as effectTime, ef.failure_type as failureType, ef.failure_time as failureTime, ef.operation_strategy as operationStrategy
        , ef.create_time as createTime, ef.update_time as updateTime, ef.create_by as createBy, ef.update_by as updateBy
        from xp_test_electronic_fence ef
        left join xp_test_electronic_fence_area efa on efa.electronic_id = ef.id and efa.is_deleted = 0
        left join xp_test_electronic_fence_area efa on efa.test_electronic_id = ef.id and efa.is_deleted = 0
        where ef.is_deleted = 0
        <if test="query.electronicNumberOrName != null and query.electronicNumberOrName != ''">
            and( ef.electronic_name like concat('%',#{query.electronicNumberOrName},'%')
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/BootCommonService.java
@@ -19,7 +19,9 @@
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@@ -118,7 +120,8 @@
        SendSubscribeMessageVo vo = new SendSubscribeMessageVo();
        vo.setUserId(order.getAppUserId());
        String json = null;
        if(order.getStatus() == OrderStatusEnum.PENDING_ORDER.getCode()){
//        status = 状态 0=已取消,1=待接单,2=待出发,3=接驾中,4=待客上车,5=乘客已上车,6=服务中,7=待客下车,8=待付款,9=待评价,10=已完成
        if(order.getStatus() == OrderStatusEnum.PICK_UP.getCode()){//车辆接单/改派通知
            String plateNumber = null;
                // 查询车辆
            XpVehicle xpVehicle = xpVehicleClient.queryById(order.getVehicleId()).getData();
@@ -129,26 +132,63 @@
                vo.setTemplateId(MessageTemplateEnum.E.getTemplateId());
                json = MessageTemplateEnum.E.getRemarks();
                json = json.replace("车牌号", plateNumber);
                json = json.replace("报警时间", LocalDateTime.now().toString());
                json = json.replace("预计到达时间", LocalDateTime.now().toString());
            }
        }else{
        }else if(order.getStatus() == OrderStatusEnum.PICK_UP_GUESTS.getCode()) { //车辆到达上车点通知
            String plateNumber = null;
            // 查询车辆
            XpVehicle xpVehicle = xpVehicleClient.queryById(order.getVehicleId()).getData();
            if(xpVehicle != null){
                plateNumber = xpVehicle.getPlateNumber();
            }
            if(StrUtil.isNotBlank(plateNumber)){
                vo.setTemplateId(MessageTemplateEnum.E.getTemplateId());
                json = MessageTemplateEnum.E.getRemarks();
                json = json.replace("车牌号", plateNumber);
                json = json.replace("预计驶离时间", LocalDateTime.now().toString());
            }
            vo.setTemplateId(MessageTemplateEnum.A.getTemplateId());
            json = MessageTemplateEnum.A.getRemarks();
            json = json.replace("订单编号", order.getOrderNumber());
            json = json.replace("订单状态", status.getDesc() );
            json = json.replace("订单状态", OrderStatusEnum.getDescByCode(order.getStatus()));
            json = json.replace("备注", "");
            json = json.replace("时间", LocalDateTime.now().toString());
            vo.setPage("");
        }else{
//        status = 状态 0=已取消,1=待接单,2=待出发,3=接驾中,4=待客上车,5=乘客已上车,6=服务中,7=待客下车,8=待付款,9=待评价,10=已完成
            List<Integer> statusList = Arrays.asList(OrderStatusEnum.CANCELED.getCode(),OrderStatusEnum.PASSENGER_BOARDED.getCode(),
                    OrderStatusEnum.SEE_OFF_GUESTS.getCode(),OrderStatusEnum.APPROVAL_REJECTION.getCode(),OrderStatusEnum.PENDING_PAYMENT.getCode(),
                    OrderStatusEnum.COMPLETED.getCode()) ;
            if(statusList.contains(order.getStatus())){
                String bz = getBz(order);
                vo.setTemplateId(MessageTemplateEnum.A.getTemplateId());
                json = MessageTemplateEnum.A.getRemarks();
                json = json.replace("订单编号", order.getOrderNumber());
                json = json.replace("订单状态", status.getDesc() );
                json = json.replace("订单状态", OrderStatusEnum.getDescByCode(order.getStatus()));
                json = json.replace("备注", bz);
                json = json.replace("时间", LocalDateTime.now().toString());
                vo.setPage("");
            }
        }
        if(StrUtil.isNotBlank(json)){
            vo.setJson(json);
            subscribeMessageClient.sendSubscribeMessage(vo);
        }
    }
    private static String getBz(XpOrder order) {
        String bz = "";
        if(order.getStatus() == OrderStatusEnum.CANCELED.getCode()){
            bz = "订单已"+OrderStatusEnum.CANCELED.getDesc();
        }else if(order.getStatus() == OrderStatusEnum.PASSENGER_BOARDED.getCode()){
            bz = OrderStatusEnum.PASSENGER_BOARDED.getDesc();
        }else if(order.getStatus() == OrderStatusEnum.SEE_OFF_GUESTS.getCode()){
            bz = OrderStatusEnum.SEE_OFF_GUESTS.getDesc();
        }else if(order.getStatus() == OrderStatusEnum.APPROVAL_REJECTION.getCode()){
            bz = "订单"+OrderStatusEnum.APPROVAL_REJECTION.getDesc();
        }else if(order.getStatus() == OrderStatusEnum.PENDING_PAYMENT.getCode()){
            bz = "订单"+OrderStatusEnum.PENDING_PAYMENT.getDesc();
        }else if(order.getStatus() == OrderStatusEnum.COMPLETED.getCode()){
            bz = "订单"+OrderStatusEnum.COMPLETED.getDesc();
        }
        return bz;
    }
}