“mitao”
2026-03-06 412fa709df33fcad4647f394fcebba8f68c2d471
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/service/serviceimpl/XpPointServiceImpl.java
@@ -3,10 +3,13 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaopeng.api.dto.operation.PointAddDto;
import com.xiaopeng.api.dto.operation.PointMapQuery;
import com.xiaopeng.api.dto.order.HistoryEndPointsDto;
import com.xiaopeng.api.dto.order.HistoryStartPointsDto;
import com.xiaopeng.api.model.operation.XpElectronicFenceArea;
import com.xiaopeng.api.model.operation.XpPoint;
import com.xiaopeng.api.model.operation.XpRegion;
import com.xiaopeng.api.model.operation.XpSystemSetting;
import com.xiaopeng.api.query.operation.PointListQuery;
import com.xiaopeng.api.query.operation.PointPageListListQuery;
@@ -15,12 +18,17 @@
import com.xiaopeng.api.vo.order.XpPointsListVo;
import com.xiaopeng.api.vo.order.XpPointsSearchVo;
import com.xiaopeng.common.core.constant.CacheConstants;
import com.xiaopeng.common.core.exception.ServiceException;
import com.xiaopeng.common.core.utils.StringUtils;
import com.xiaopeng.common.core.web.page.PageInfo;
import com.xiaopeng.common.redis.service.RedisService;
import com.xiaopeng.map.constant.I18nKeyConstant;
import com.xiaopeng.map.dto.NearestDto;
import com.xiaopeng.map.mapper.XpPointMapper;
import com.xiaopeng.map.mapper.XpRegionMapper;
import com.xiaopeng.map.mapper.XpSystemSettingMapper;
import com.xiaopeng.map.query.PointByNameQuery;
import com.xiaopeng.map.service.XpElectronicFenceAreaService;
import com.xiaopeng.map.service.XpPointService;
import com.xiaopeng.map.utils.TimeRangeChecker;
import com.xiaopeng.map.utils.holiday.HolidayUtil;
@@ -28,6 +36,12 @@
import com.xiaopeng.map.vo.NearestNextVo;
import com.xiaopeng.map.vo.NearestVo;
import com.xiaopeng.map.vo.OrderDistanceVo;
import lombok.extern.slf4j.Slf4j;
import net.sourceforge.pinyin4j.PinyinHelper;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@@ -35,6 +49,7 @@
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
@@ -47,7 +62,13 @@
 * @since 2026-02-03
 */
@Service
@Slf4j
public class XpPointServiceImpl extends ServiceImpl<XpPointMapper, XpPoint> implements XpPointService {
    private static final String POINT_BIZ_TYPE = "PUD";
    private static final int SERIAL_WIDTH = 6;
    private static final int LOCK_WAIT_SECONDS = 5;
    private static final int LOCK_LEASE_SECONDS = 30;
    @Resource
    private XpSystemSettingMapper xpSystemSettingMapper;
@@ -58,6 +79,59 @@
    @Resource
    private HolidayUtil holidayUtil;
    @Resource
    private XpElectronicFenceAreaService xpElectronicFenceAreaService;
    @Resource
    private XpRegionMapper xpRegionMapper;
    @Autowired
    private RedissonClient redissonClient;
    @Override
    public void addPointWithGeneratedNumber(PointAddDto pointAddDto) {
        if (pointAddDto == null) {
            throw new ServiceException("参数不能为空", I18nKeyConstant.SYSTEM_EXCEPTION);
        }
        if (pointAddDto.getElectronicAreaId() == null) {
            throw new ServiceException("运营区域ID不能为空", I18nKeyConstant.POINT_ELECTRONIC_AREA_ID_EMPTY);
        }
        String cityPrefix = buildCityPrefix(pointAddDto.getElectronicAreaId());
        String lockKey = buildPointNumberLockKey(cityPrefix, POINT_BIZ_TYPE);
        RLock lock = redissonClient.getLock(lockKey);
        boolean locked = false;
        String pointNumber;
        try {
            locked = lock.tryLock(LOCK_WAIT_SECONDS, LOCK_LEASE_SECONDS, TimeUnit.SECONDS);
            if (!locked) {
                throw new ServiceException("点位编号生成繁忙,请稍后重试", I18nKeyConstant.POINT_NUMBER_LOCK_FAIL);
            }
            String maxPointNumber = this.baseMapper.selectMaxPointNumber(cityPrefix, POINT_BIZ_TYPE);
            int nextSerial = 1;
            if (StringUtils.hasText(maxPointNumber)) {
                nextSerial = parseSerialOrThrow(maxPointNumber) + 1;
            }
            pointNumber = cityPrefix + "-" + POINT_BIZ_TYPE + "-" + String.format("%0" + SERIAL_WIDTH + "d", nextSerial);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new ServiceException("点位编号生成繁忙,请稍后重试", I18nKeyConstant.POINT_NUMBER_LOCK_FAIL);
        } finally {
            try {
                if (locked && lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            } catch (Exception e) {
                log.warn("释放点位编号生成锁失败 lockKey={}", lockKey, e);
            }
        }
        XpPoint xpPoint = new XpPoint();
        BeanUtils.copyProperties(pointAddDto, xpPoint);
        xpPoint.setPointNumber(pointNumber);
        this.save(xpPoint);
    }
    @Override
    public NearestVo nearest(NearestDto dto) {
@@ -112,6 +186,57 @@
    }
    private String buildCityPrefix(Long electronicAreaId) {
        XpElectronicFenceArea area = xpElectronicFenceAreaService.getById(electronicAreaId);
        if (area == null) {
            throw new ServiceException("运营区域不存在", I18nKeyConstant.POINT_ELECTRONIC_AREA_NOT_EXIST);
        }
        if (!StringUtils.hasText(area.getCityCode())) {
            throw new ServiceException("城市信息不存在", I18nKeyConstant.POINT_CITY_NOT_EXIST);
        }
        XpRegion region = xpRegionMapper.selectOne(new LambdaQueryWrapper<XpRegion>()
                .eq(XpRegion::getCode, area.getCityCode()));
        if (region == null || !StringUtils.hasText(region.getName())) {
            throw new ServiceException("城市信息不存在", I18nKeyConstant.POINT_CITY_NOT_EXIST);
        }
        String name = region.getName();
        String prefix = name.length() <= 2 ? name : name.substring(0, 2);
        return getPinyinInitials(prefix).toUpperCase();
    }
    private static String buildPointNumberLockKey(String cityPrefix, String bizType) {
        return "xp:point:number:" + cityPrefix + ":" + bizType;
    }
    private static String getPinyinInitials(String chinese) {
        StringBuilder pinyinInitials = new StringBuilder();
        for (char c : chinese.toCharArray()) {
            String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(c);
            if (pinyinArray != null && pinyinArray.length > 0) {
                pinyinInitials.append(pinyinArray[0].charAt(0));
            }
        }
        return pinyinInitials.toString();
    }
    private static int parseSerialOrThrow(String pointNumber) {
        String[] parts = pointNumber.split("-");
        if (parts.length < 3) {
            throw new ServiceException("点位编号生成失败(历史编号格式异常)", I18nKeyConstant.POINT_NUMBER_PARSE_FAIL);
        }
        String serialPart = parts[parts.length - 1];
        if (serialPart.length() != SERIAL_WIDTH) {
            throw new ServiceException("点位编号生成失败(历史编号格式异常)", I18nKeyConstant.POINT_NUMBER_PARSE_FAIL);
        }
        try {
            return Integer.parseInt(serialPart);
        } catch (NumberFormatException e) {
            throw new ServiceException("点位编号生成失败(历史编号格式异常)", I18nKeyConstant.POINT_NUMBER_PARSE_FAIL);
        }
    }
    public boolean getInTime(LocalTime now,  Integer effectivePeriod, String effectiveTimePeriod){