xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/service/serviceimpl/XpElectronicFenceServiceImpl.java
@@ -46,10 +46,7 @@ import javax.annotation.Resource; import java.awt.geom.Point2D; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.*; import java.util.stream.Collectors; /** @@ -729,14 +726,34 @@ * @return */ private org.locationtech.jts.geom.Point calculateCentroid(Coordinate[] coordinates) { GeometryFactory factory = new GeometryFactory(); // 创建线性环 (LinearRing) LinearRing ring = factory.createLinearRing(coordinates); // 创建多边形 (Polygon) Polygon polygon = factory.createPolygon(ring); // 计算质心 (Centroid) org.locationtech.jts.geom.Point centroid = polygon.getCentroid(); // 1. 判空校验 if (coordinates == null || coordinates.length == 0) { throw new ServiceException("坐标数组不能为空"); } // 2. 过滤掉 null 坐标点 Coordinate[] validCoords = Arrays.stream(coordinates) .filter(Objects::nonNull) .toArray(Coordinate[]::new); // 3. LinearRing 至少需要 4 个点(首尾相同),且必须闭合 if (validCoords.length < 4) { throw new ServiceException("有效坐标点不足,至少需要4个点"); } // 4. 确保首尾坐标相同(闭合) Coordinate first = validCoords[0]; Coordinate last = validCoords[validCoords.length - 1]; if (!first.equals2D(last)) { // 自动补上闭合点 validCoords = Arrays.copyOf(validCoords, validCoords.length + 1); validCoords[validCoords.length - 1] = new Coordinate(first.x, first.y); } GeometryFactory factory = new GeometryFactory(); LinearRing ring = factory.createLinearRing(validCoords); Polygon polygon = factory.createPolygon(ring); org.locationtech.jts.geom.Point centroid = polygon.getCentroid(); System.out.println("中心点经度: " + centroid.getX()); System.out.println("中心点纬度: " + centroid.getY()); return centroid; xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/service/serviceimpl/XpPointServiceImpl.java
@@ -385,9 +385,9 @@ if (CollUtil.isNotEmpty(lonAndLat)) { query.setPointLon(lonAndLat.get(0)); query.setPointLat(lonAndLat.get(1)); } query.setPointLonAndLat(null); } } List<XpPoint> list = this.baseMapper.pageList(pageInfo,query); pageInfo.setRecords(list); return pageInfo; xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/constant/I18nKeyConstant.java
@@ -50,4 +50,17 @@ String VEHICLE_COLOR_INVALID = "vehicle.color.invalid"; String VEHICLE_ELECTRONIC_NOT_EXISTS = "vehicle.electronic.not.exists"; // ========== 车辆导入校验 ========== String VEHICLE_IMPORT_VALIDATE_FAIL = "vehicle.import.validate.fail"; String VEHICLE_IMPORT_CODE_INVALID = "vehicle.import.code.invalid"; String VEHICLE_IMPORT_VIN_INVALID = "vehicle.import.vin.invalid"; String VEHICLE_IMPORT_PLATE_INVALID = "vehicle.import.plate.invalid"; String VEHICLE_IMPORT_COLOR_INVALID = "vehicle.import.color.invalid"; String VEHICLE_IMPORT_CODE_DUPLICATE = "vehicle.import.code.duplicate"; String VEHICLE_IMPORT_VIN_DUPLICATE = "vehicle.import.vin.duplicate"; String VEHICLE_IMPORT_PLATE_DUPLICATE = "vehicle.import.plate.duplicate"; String VEHICLE_IMPORT_CODE_EXISTS = "vehicle.import.code.exists"; String VEHICLE_IMPORT_VIN_EXISTS = "vehicle.import.vin.exists"; String VEHICLE_IMPORT_PLATE_EXISTS = "vehicle.import.plate.exists"; } xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/controller/XpVehicleController.java
@@ -5,6 +5,7 @@ import cn.afterturn.easypoi.excel.entity.ExportParams; import cn.afterturn.easypoi.excel.entity.ImportParams; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.lang.Validator; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; @@ -69,8 +70,13 @@ import java.net.URLEncoder; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.Set; import java.util.regex.Pattern; /** * <p> @@ -85,6 +91,9 @@ @Api(tags = "车辆管理") @RequiredArgsConstructor(onConstructor_ = @Lazy) public class XpVehicleController { private static final Pattern VEHICLE_CODE_PATTERN = Pattern.compile("^[A-Za-z0-9_]{1,5}$"); private static final Pattern VEHICLE_VIN_PATTERN = Pattern.compile("^[A-Z0-9]{17}$"); private final IXpVehicleService xpVehicleService; private final IXpFleetVehicleService vehicleService; @@ -294,6 +303,7 @@ throw new ServiceException("导入数据为空!", I18nKeyConstant.VEHICLE_IMPORT_DATA_EMPTY); } validateImportRequiredFields(importList, params.getHeadRows()); validateImportBusinessFields(importList, params.getHeadRows()); uploadVehiclePicture(importList); xpVehicleService.importVehicles(importList); return R.ok(); @@ -326,6 +336,189 @@ } } private void validateImportBusinessFields(List<XpVehicleImportExcel> importList, int headRows) { List<String> errors = new ArrayList<>(); Locale locale = LocaleContextHolder.getLocale(); for (int index = 0; index < importList.size(); index++) { XpVehicleImportExcel row = importList.get(index); int excelRowNumber = index + headRows + 1; String vehicleCode = trimToNull(row.getVehicleCode()); String vehicleVin = normalizeVin(row.getVehicleVin()); String plateNumber = trimToNull(row.getPlateNumber()); String vehicleColor = trimToNull(row.getVehicleColor()); row.setVehicleCode(vehicleCode); row.setVehicleVin(vehicleVin); row.setPlateNumber(plateNumber); row.setVehicleColor(vehicleColor); if (org.apache.commons.lang3.StringUtils.isNotBlank(vehicleCode) && !VEHICLE_CODE_PATTERN.matcher(vehicleCode).matches()) { errors.add(messageSource.getMessage(I18nKeyConstant.VEHICLE_IMPORT_CODE_INVALID, new Object[]{excelRowNumber, vehicleCode}, I18nKeyConstant.VEHICLE_IMPORT_CODE_INVALID, locale)); } if (org.apache.commons.lang3.StringUtils.isNotBlank(vehicleVin) && !VEHICLE_VIN_PATTERN.matcher(vehicleVin).matches()) { errors.add(messageSource.getMessage(I18nKeyConstant.VEHICLE_IMPORT_VIN_INVALID, new Object[]{excelRowNumber, vehicleVin}, I18nKeyConstant.VEHICLE_IMPORT_VIN_INVALID, locale)); } if (org.apache.commons.lang3.StringUtils.isNotBlank(plateNumber) && !Validator.isPlateNumber(plateNumber)) { errors.add(messageSource.getMessage(I18nKeyConstant.VEHICLE_IMPORT_PLATE_INVALID, new Object[]{excelRowNumber, plateNumber}, I18nKeyConstant.VEHICLE_IMPORT_PLATE_INVALID, locale)); } if (org.apache.commons.lang3.StringUtils.isNotBlank(vehicleColor) && vehicleColor.length() > 2) { errors.add(messageSource.getMessage(I18nKeyConstant.VEHICLE_IMPORT_COLOR_INVALID, new Object[]{excelRowNumber, vehicleColor}, I18nKeyConstant.VEHICLE_IMPORT_COLOR_INVALID, locale)); } } Set<String> duplicatedCodes = findDuplicated(importList.stream() .map(XpVehicleImportExcel::getVehicleCode) .filter(org.apache.commons.lang3.StringUtils::isNotBlank) .collect(java.util.stream.Collectors.toList())); Set<String> duplicatedVins = findDuplicated(importList.stream() .map(XpVehicleImportExcel::getVehicleVin) .filter(org.apache.commons.lang3.StringUtils::isNotBlank) .collect(java.util.stream.Collectors.toList())); Set<String> duplicatedPlates = findDuplicated(importList.stream() .map(XpVehicleImportExcel::getPlateNumber) .filter(org.apache.commons.lang3.StringUtils::isNotBlank) .collect(java.util.stream.Collectors.toList())); for (int index = 0; index < importList.size(); index++) { XpVehicleImportExcel row = importList.get(index); int excelRowNumber = index + headRows + 1; if (duplicatedCodes.contains(row.getVehicleCode())) { errors.add(messageSource.getMessage(I18nKeyConstant.VEHICLE_IMPORT_CODE_DUPLICATE, new Object[]{excelRowNumber, row.getVehicleCode()}, I18nKeyConstant.VEHICLE_IMPORT_CODE_DUPLICATE, locale)); } if (duplicatedVins.contains(row.getVehicleVin())) { errors.add(messageSource.getMessage(I18nKeyConstant.VEHICLE_IMPORT_VIN_DUPLICATE, new Object[]{excelRowNumber, row.getVehicleVin()}, I18nKeyConstant.VEHICLE_IMPORT_VIN_DUPLICATE, locale)); } if (duplicatedPlates.contains(row.getPlateNumber())) { errors.add(messageSource.getMessage(I18nKeyConstant.VEHICLE_IMPORT_PLATE_DUPLICATE, new Object[]{excelRowNumber, row.getPlateNumber()}, I18nKeyConstant.VEHICLE_IMPORT_PLATE_DUPLICATE, locale)); } } Set<String> codeSet = importList.stream() .map(XpVehicleImportExcel::getVehicleCode) .filter(org.apache.commons.lang3.StringUtils::isNotBlank) .collect(java.util.stream.Collectors.toSet()); Set<String> vinSet = importList.stream() .map(XpVehicleImportExcel::getVehicleVin) .filter(org.apache.commons.lang3.StringUtils::isNotBlank) .collect(java.util.stream.Collectors.toSet()); Set<String> plateSet = importList.stream() .map(XpVehicleImportExcel::getPlateNumber) .filter(org.apache.commons.lang3.StringUtils::isNotBlank) .collect(java.util.stream.Collectors.toSet()); Set<String> existsCodes = Collections.emptySet(); if (!codeSet.isEmpty()) { existsCodes = xpVehicleService.lambdaQuery() .select(XpVehicle::getVehicleCode) .in(XpVehicle::getVehicleCode, codeSet) .list() .stream() .map(XpVehicle::getVehicleCode) .filter(org.apache.commons.lang3.StringUtils::isNotBlank) .collect(java.util.stream.Collectors.toSet()); } Set<String> existsVins = Collections.emptySet(); if (!vinSet.isEmpty()) { existsVins = xpVehicleService.lambdaQuery() .select(XpVehicle::getVehicleVin) .in(XpVehicle::getVehicleVin, vinSet) .list() .stream() .map(XpVehicle::getVehicleVin) .filter(org.apache.commons.lang3.StringUtils::isNotBlank) .collect(java.util.stream.Collectors.toSet()); } Set<String> existsPlates = Collections.emptySet(); if (!plateSet.isEmpty()) { existsPlates = xpVehicleService.lambdaQuery() .select(XpVehicle::getPlateNumber) .in(XpVehicle::getPlateNumber, plateSet) .list() .stream() .map(XpVehicle::getPlateNumber) .filter(org.apache.commons.lang3.StringUtils::isNotBlank) .collect(java.util.stream.Collectors.toSet()); } for (int index = 0; index < importList.size(); index++) { XpVehicleImportExcel row = importList.get(index); int excelRowNumber = index + headRows + 1; if (existsCodes.contains(row.getVehicleCode())) { errors.add(messageSource.getMessage(I18nKeyConstant.VEHICLE_IMPORT_CODE_EXISTS, new Object[]{excelRowNumber, row.getVehicleCode()}, I18nKeyConstant.VEHICLE_IMPORT_CODE_EXISTS, locale)); } if (existsVins.contains(row.getVehicleVin())) { errors.add(messageSource.getMessage(I18nKeyConstant.VEHICLE_IMPORT_VIN_EXISTS, new Object[]{excelRowNumber, row.getVehicleVin()}, I18nKeyConstant.VEHICLE_IMPORT_VIN_EXISTS, locale)); } if (existsPlates.contains(row.getPlateNumber())) { errors.add(messageSource.getMessage(I18nKeyConstant.VEHICLE_IMPORT_PLATE_EXISTS, new Object[]{excelRowNumber, row.getPlateNumber()}, I18nKeyConstant.VEHICLE_IMPORT_PLATE_EXISTS, locale)); } } if (!errors.isEmpty()) { throw new ServiceException("导入校验失败!", I18nKeyConstant.VEHICLE_IMPORT_VALIDATE_FAIL, new Object[]{String.join("\n", errors)}); } } private static String normalizeVin(String vin) { String trimmed = trimToNull(vin); return trimmed == null ? null : trimmed.toUpperCase(Locale.ROOT); } private static String trimToNull(String value) { if (value == null) { return null; } String trimmed = value.trim(); return trimmed.isEmpty() ? null : trimmed; } private static Set<String> findDuplicated(List<String> values) { if (values == null || values.isEmpty()) { return Collections.emptySet(); } Set<String> seen = new HashSet<>(); Set<String> duplicated = new HashSet<>(); for (String value : values) { if (!seen.add(value)) { duplicated.add(value); } } return duplicated; } private void uploadVehiclePicture(List<XpVehicleImportExcel> importList) { for (XpVehicleImportExcel xpVehicleImportExcel : importList) { xpVehicleImportExcel.setVehiclePictures(getOssUrl(xpVehicleImportExcel.getVehiclePictures())); xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/impl/XpVehicleServiceImpl.java
@@ -24,6 +24,7 @@ import com.xiaopeng.common.core.constant.CacheConstants; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.core.exception.ServiceException; import com.xiaopeng.common.core.utils.DateUtils; import com.xiaopeng.common.core.utils.GeodesyUtil; import com.xiaopeng.common.core.web.page.PageInfo; import com.xiaopeng.common.redis.service.RedisService; @@ -73,7 +74,7 @@ @Service public class XpVehicleServiceImpl extends ServiceImpl<XpVehicleMapper, XpVehicle> implements IXpVehicleService { private static final Pattern VEHICLE_CODE_PATTERN = Pattern.compile("^[A-Za-z0-9_]{5}$"); private static final Pattern VEHICLE_CODE_PATTERN = Pattern.compile("^[A-Za-z0-9_]{1,5}$"); private static final Pattern VEHICLE_VIN_PATTERN = Pattern.compile("^[A-Z0-9]{17}$"); @Autowired xp-robotaxi-vehicle-convoy-boot/src/main/resources/i18n/messages.properties
@@ -21,3 +21,22 @@ vehicle.operation.info.incomplete=以下信息未完善,无法开始运营:{0} vehicle.batch.operation.condition.not.met=以下车辆不满足运营条件:{0} vehicle.time.expired=以下时间已过期,请填写有效日期:{0} vehicle.code.invalid=车辆编码格式不正确,仅支持1-5位字母、数字、下划线:{0} vehicle.code.exists=车辆编码已存在:{0} vehicle.vin.invalid=车辆VIN码格式不正确,仅支持17位大写字母与数字:{0} vehicle.vin.exists=车辆VIN码已存在:{0} vehicle.plate.invalid=车牌号格式不正确:{0} vehicle.plate.exists=车牌号已存在:{0} vehicle.color.invalid=车辆颜色长度不能超过2:{0} vehicle.electronic.not.exists=选择的运营区域不存在:{0} vehicle.import.validate.fail=导入校验失败:\n{0} vehicle.import.code.invalid=第{0}行车辆编码格式不正确(1-5位字母/数字/下划线):{1} vehicle.import.vin.invalid=第{0}行车辆VIN码格式不正确(17位大写字母与数字):{1} vehicle.import.plate.invalid=第{0}行车牌号格式不正确:{1} vehicle.import.color.invalid=第{0}行车辆颜色长度不能超过2:{1} vehicle.import.code.duplicate=第{0}行车辆编码在导入文件中重复:{1} vehicle.import.vin.duplicate=第{0}行车辆VIN码在导入文件中重复:{1} vehicle.import.plate.duplicate=第{0}行车牌号在导入文件中重复:{1} vehicle.import.code.exists=第{0}行车辆编码已存在:{1} vehicle.import.vin.exists=第{0}行车辆VIN码已存在:{1} vehicle.import.plate.exists=第{0}行车牌号已存在:{1} xp-robotaxi-vehicle-convoy-boot/src/main/resources/i18n/messages_en_US.properties
@@ -25,7 +25,7 @@ vehicle.not.p.gear=The trunk cannot be opened when the vehicle is not in P gear vehicle.temperature.up=The highest temperature is 28°C vehicle.temperature.down=The lowest temperature is 22°C vehicle.code.invalid=Invalid vehicle code (5 chars, letters/numbers/underscore only): {0} vehicle.code.invalid=Invalid vehicle code (1-5 chars, letters/numbers/underscore only): {0} vehicle.code.exists=Vehicle code already exists: {0} vehicle.vin.invalid=Invalid VIN (17 chars, uppercase letters and digits only): {0} vehicle.vin.exists=VIN already exists: {0} @@ -33,4 +33,15 @@ vehicle.plate.exists=Plate number already exists: {0} vehicle.color.invalid=Vehicle color length must be <= 2: {0} vehicle.electronic.not.exists=Selected operating area does not exist: {0} vehicle.import.validate.fail=Import validation failed:\n{0} vehicle.import.code.invalid=Row {0}: invalid vehicle code (1-5 chars, letters/numbers/underscore only): {1} vehicle.import.vin.invalid=Row {0}: invalid VIN (17 chars, uppercase letters and digits only): {1} vehicle.import.plate.invalid=Row {0}: invalid plate number: {1} vehicle.import.color.invalid=Row {0}: vehicle color length must be <= 2: {1} vehicle.import.code.duplicate=Row {0}: duplicate vehicle code in file: {1} vehicle.import.vin.duplicate=Row {0}: duplicate VIN in file: {1} vehicle.import.plate.duplicate=Row {0}: duplicate plate number in file: {1} vehicle.import.code.exists=Row {0}: vehicle code already exists: {1} vehicle.import.vin.exists=Row {0}: VIN already exists: {1} vehicle.import.plate.exists=Row {0}: plate number already exists: {1} vehicle.distance.too.long=Too far away, unable to open xp-robotaxi-vehicle-convoy-boot/src/main/resources/i18n/messages_zh_CN.properties
@@ -26,7 +26,7 @@ vehicle.not.p.gear=车辆非P档,无法开启后备箱 vehicle.temperature.up=温度最高28℃ vehicle.temperature.down=温度最低22℃ vehicle.code.invalid=车辆编码格式不正确,仅支持5位字母、数字、下划线:{0} vehicle.code.invalid=车辆编码格式不正确,仅支持1-5位字母、数字、下划线:{0} vehicle.code.exists=车辆编码已存在:{0} vehicle.vin.invalid=车辆VIN码格式不正确,仅支持17位大写字母与数字:{0} vehicle.vin.exists=车辆VIN码已存在:{0} @@ -34,4 +34,15 @@ vehicle.plate.exists=车牌号已存在:{0} vehicle.color.invalid=车辆颜色长度不能超过2:{0} vehicle.electronic.not.exists=选择的运营区域不存在:{0} vehicle.import.validate.fail=导入校验失败:\n{0} vehicle.import.code.invalid=第{0}行车辆编码格式不正确(1-5位字母/数字/下划线):{1} vehicle.import.vin.invalid=第{0}行车辆VIN码格式不正确(17位大写字母与数字):{1} vehicle.import.plate.invalid=第{0}行车牌号格式不正确:{1} vehicle.import.color.invalid=第{0}行车辆颜色长度不能超过2:{1} vehicle.import.code.duplicate=第{0}行车辆编码在导入文件中重复:{1} vehicle.import.vin.duplicate=第{0}行车辆VIN码在导入文件中重复:{1} vehicle.import.plate.duplicate=第{0}行车牌号在导入文件中重复:{1} vehicle.import.code.exists=第{0}行车辆编码已存在:{1} vehicle.import.vin.exists=第{0}行车辆VIN码已存在:{1} vehicle.import.plate.exists=第{0}行车牌号已存在:{1} vehicle.distance.too.long=距离过远,无法打开 xp-robotaxi-vehicle-convoy-boot/src/main/resources/mapper/vehicleConvoy/XpVehicleMapper.xml
@@ -73,7 +73,7 @@ </foreach> </if> AND is_deleted = 0 AND vehicleType = 2 AND vehicle_type = 2 </where> ORDER BY create_time DESC </select>