package com.xiaopeng.vehicleConvoy.service.impl; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Validator; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.google.common.collect.ImmutableMap; import com.xiaopeng.api.dto.pushMessage.SendMessageDTO; import com.xiaopeng.api.dto.vehicleConvoy.VehicleTemperatureDto; import com.xiaopeng.api.feignClient.automaticPilot.XpuVinClient; import com.xiaopeng.api.feignClient.operation.XpElectronicClient; import com.xiaopeng.api.feignClient.operation.XpSystemSettingClient; import com.xiaopeng.api.feignClient.order.XpOrderClient; import com.xiaopeng.api.feignClient.order.XpTestOrderClient; import com.xiaopeng.api.feignClient.pushMessage.XpPushMessageClient; import com.xiaopeng.api.model.operation.XpElectronicFence; import com.xiaopeng.api.model.operation.XpElectronicFenceArea; import com.xiaopeng.api.model.order.XpOrder; import com.xiaopeng.api.model.order.XpTestOrder; import com.xiaopeng.api.model.vehicleConvoy.*; import com.xiaopeng.api.vo.automaticPilot.VehiclePosition; import com.xiaopeng.api.vo.operation.ElectronicFenceDetailVo; import com.xiaopeng.api.vo.vehicleConvoy.VehicleStateVo;import com.xiaopeng.api.vo.vehicleConvoy.VehiclesCommandEnum; import com.xiaopeng.api.vo.vehicleConvoy.VehiclesCommandParamVo; import com.xiaopeng.api.vo.vehicleConvoy.VehiclesCommandVo; import com.xiaopeng.common.core.constant.CacheConstants; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.core.enums.OrderStatusEnum; import com.xiaopeng.common.core.enums.PushMessageTypeEnum; 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.utils.SensitiveDataCryptoUtil; import com.xiaopeng.common.core.web.page.PageInfo; import com.xiaopeng.common.redis.service.RedisService; import com.xiaopeng.evi.oss.service.AliOssService; import com.xiaopeng.vehicleConvoy.constant.I18nKeyConstant; import com.xiaopeng.vehicleConvoy.mapper.XpFleetMapper; import com.xiaopeng.vehicleConvoy.mapper.XpFleetOperationsMapper; import com.xiaopeng.vehicleConvoy.mapper.XpVehicleMapper; import com.xiaopeng.vehicleConvoy.model.dto.*; import com.xiaopeng.vehicleConvoy.model.importExcel.XpVehicleImportExcel; import com.xiaopeng.vehicleConvoy.model.query.*; import com.xiaopeng.vehicleConvoy.model.vo.XpVehicleVO; import com.xiaopeng.vehicleConvoy.service.IXpFleetService; import com.xiaopeng.vehicleConvoy.service.IXpVehicleAnnualRecordService; import com.xiaopeng.vehicleConvoy.service.IXpFleetVehicleService; import com.xiaopeng.vehicleConvoy.service.IXpVehicleService; import com.xiaopeng.vehicleConvoy.util.VehicleControlUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.shardingsphere.transaction.annotation.ShardingSphereTransactionType; import org.apache.shardingsphere.transaction.core.TransactionType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.data.geo.Point; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.geo.GeoJsonPoint; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import javax.annotation.Resource; import java.io.Serializable; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.util.*; import java.util.stream.Collectors; import java.util.regex.Pattern; /** *

* 车辆管理 服务实现类 *

* * @author mitao * @since 2026-02-03 */ @Slf4j @Service public class XpVehicleServiceImpl extends ServiceImpl implements IXpVehicleService { 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 private VehicleControlUtil vehicleControlUtil; @Autowired private XpOrderClient xpOrderClient; @Autowired private XpElectronicClient xpElectronicClient; @Autowired private IXpVehicleAnnualRecordService xpVehicleAnnualRecordService; @Autowired private IXpFleetVehicleService xpFleetVehicleService; @Autowired private XpFleetOperationsMapper xpFleetOperationsMapper; @Resource private XpuVinClient xpuVinClient; @Autowired private RedisService redisService; @Autowired private MongoTemplate mongoTemplate; @Autowired private AliOssService aliOssService; @Autowired private RedisTemplate redisTemplate; @Autowired private XpPushMessageClient xpPushMessageClient; @Resource private XpTestOrderClient xpTestOrderClient; @Override public List listByIds(List ids) { if (CollectionUtils.isEmpty(ids)) { return new ArrayList<>(); } return super.listByIds(ids); } @Override public List listAvailableVehicles(Long orderId, Integer orderType) { // 1. 通过订单ID查询订单信息,获取区域ID Long areaId = null; if(null == orderType || 1 == orderType){ R orderResult = xpOrderClient.queryOrderById(orderId); if (orderResult == null || orderResult.getData() == null) { log.warn("查询订单失败,orderId: {}", orderId); return Collections.emptyList(); } XpOrder order = orderResult.getData(); areaId = order.getXpElectronicFenceAreaId(); }else{ XpTestOrder testOrder = xpTestOrderClient.queryOrderById(orderId).getData(); areaId = testOrder.getXpElectronicFenceAreaId(); } if (areaId == null) { log.warn("订单区域ID为空,orderId: {}", orderId); return Collections.emptyList(); } // 2. 通过区域ID查询围栏ID R areaResult = xpElectronicClient.queryElectronicFenceAreaById(areaId); if (areaResult == null || areaResult.getData() == null) { log.warn("查询围栏区域失败,areaId: {}", areaId); return Collections.emptyList(); } Long electronicId = areaResult.getData().getElectronicId(); if (electronicId == null) { log.warn("围栏ID为空,areaId: {}", areaId); return Collections.emptyList(); } // 3. 根据围栏ID查询车辆列表 (operatingStatus=1 且 state=0) LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(XpVehicle::getElectronicId, electronicId) .eq(XpVehicle::getOperatingStatus, 1) .eq(XpVehicle::getVehicleType, 2) .eq(XpVehicle::getState, 0); // 排序:按电量降序、入库时间降序 wrapper.orderByDesc(XpVehicle::getCurrentElectricity) .orderByDesc(XpVehicle::getWarehousingTime); return this.list(wrapper); } @Override public PageInfo pageList(VehicleMapQuery query) { PageInfo pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize()); // 查询用户所在车队的车辆id集合 List xpFleetOperations = xpFleetOperationsMapper.selectList(Wrappers.lambdaQuery(XpFleetOperations.class) .eq(XpFleetOperations::getOperationsUserId, query.getOperationsUserId())); if (CollectionUtils.isEmpty(xpFleetOperations)) { return pageInfo; } List fleetIds = xpFleetOperations.stream().map(XpFleetOperations::getFleetId).collect(Collectors.toList()); // 查询关联车辆 List xpFleetVehicles = xpFleetVehicleService.list(Wrappers.lambdaQuery(XpFleetVehicle.class) .in(XpFleetVehicle::getFleetId, fleetIds)); if (CollectionUtils.isEmpty(xpFleetVehicles)) { return pageInfo; } List vehicleIds = xpFleetVehicles.stream().map(XpFleetVehicle::getVehicleId).distinct().collect(Collectors.toList()); query.setVehicleIds(vehicleIds); if (StringUtils.isNotBlank(query.getStatus())) { query.setStatusList(Arrays.asList(query.getStatus().split(","))); } if (StringUtils.isNotBlank(query.getElectronicId())) { query.setElectronicIdList(Arrays.asList(query.getElectronicId().split(","))); } List list = this.baseMapper.pageList(pageInfo, query); if (CollectionUtils.isEmpty(list)) { return pageInfo; } for (XpVehicleVO xpVehicleVO : list) { // 查询车辆的续航里程 VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(xpVehicleVO.getVehicleVin())), VehiclePosition.class); if (Objects.nonNull(vehiclePosition)) { xpVehicleVO.setMileageRange(vehiclePosition.getMileageRange()); xpVehicleVO.setCurrentElectricity(vehiclePosition.getCurrentElectricity()); GeoJsonPoint point = vehiclePosition.getPoint(); if(Objects.nonNull(point)){ xpVehicleVO.setLongitude(point.getX()); xpVehicleVO.setLatitude(point.getY()); } } else { xpVehicleVO.setMileageRange(0F); xpVehicleVO.setCurrentElectricity(0); xpVehicleVO.setLongitude(104.056302); xpVehicleVO.setLatitude(30.586688); } if (org.springframework.util.StringUtils.hasLength(xpVehicleVO.getVehiclePictures())) { JSONObject jsonObject = JSONObject.parseObject(xpVehicleVO.getVehiclePictures()); if(jsonObject!=null){ String downloadUrl = null; try { // 1 downloadUrl= aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true); } catch (Exception e) { e.printStackTrace(); } jsonObject.put("url", downloadUrl); xpVehicleVO.setVehiclePictures(jsonObject.toJSONString()); } } } pageInfo.setRecords(list); return pageInfo; } @Override public List listByPlateNumber(String plateNumber) { if (StringUtils.isBlank(plateNumber)) { return Collections.emptyList(); } return lambdaQuery().like(XpVehicle::getPlateNumber, plateNumber).list(); } @Override public List listByFenceId(Long fenceId) { if (fenceId == null) { return Collections.emptyList(); } return lambdaQuery().eq(XpVehicle::getElectronicId, fenceId).eq(XpVehicle::getOperatingStatus, 1).eq(XpVehicle::getState, 0).list(); } @Override public Integer unlock(UnlockAppletDTO dto) { // 查询订单 XpOrder xpOrder = xpOrderClient.queryOrderById(dto.getOrderId()).getData(); if (Objects.isNull(xpOrder)) { throw new ServiceException("未查询到有效订单信息", I18nKeyConstant.ORDER_NOT_EXISTS); } XpVehicle xpVehicle = this.getById(xpOrder.getVehicleId()); if (Objects.isNull(xpVehicle)) { throw new ServiceException("未查询到有效车辆信息", I18nKeyConstant.VEHICLE_NOT_EXISTS); } // 判断距离是否在范围之内 15米 VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(xpVehicle.getVehicleVin())), VehiclePosition.class); if (Objects.nonNull(vehiclePosition)) { GeoJsonPoint point = vehiclePosition.getPoint(); if(Objects.nonNull(point)){ Map distance = GeodesyUtil.getDistance(dto.getLongitude()+ "," + dto.getLatitude(), point.getX()+","+point.getY()); if (null != distance) { Double distance1 = distance.get("WGS84"); if (distance1 > 50) { log.info("距离过远,无法打开 {} {} {}", dto.getLongitude()+ "," + dto.getLatitude(), point.getX()+","+point.getY(), distance1); throw new ServiceException("距离过远,无法打开", I18nKeyConstant.VEHICLE_DISTANCE_TOO_LONG); } }else { log.info("计算距离失败,无法打开车门"); throw new ServiceException("距离过远,无法打开", I18nKeyConstant.VEHICLE_DISTANCE_TOO_LONG); } }else { log.info("车辆无实时定位,无法打开车门"); throw new ServiceException("距离过远,无法打开", I18nKeyConstant.VEHICLE_DISTANCE_TOO_LONG); } } // 车门解锁、后备箱解锁 VehiclesCommandVo vehiclesCommandVo = new VehiclesCommandVo(); vehiclesCommandVo.setVin(xpVehicle.getVehicleVin()); if ("door".equals(dto.getDoorType())) { // 开启车门 判断是否为P档 Integer gearLevel = redisService.getCacheObject(CacheConstants.GEAR_LEVEL + xpVehicle.getXpuId()); if (gearLevel == null || gearLevel != 4) { throw new ServiceException("车辆非P档,无法开启车门", I18nKeyConstant.VEHICLE_NOT_P_GEAR); } vehiclesCommandVo.setCommandCode(VehiclesCommandEnum.DOOR_UNLOCK.getCommand()); } else { // 开启后备箱 判断是否为P档 Integer gearLevel = redisService.getCacheObject(CacheConstants.GEAR_LEVEL + xpVehicle.getXpuId()); if (gearLevel == null || gearLevel != 4) { throw new ServiceException("车辆非P档,无法操作后备箱", I18nKeyConstant.VEHICLE_NOT_P_GEAR); } //查询后备箱开关状态 VehicleTemperatureDto vehicleTemperatureDto = vehicleControlUtil.queryVehicleTemperature(String.valueOf(xpOrder.getAppUserId()), xpVehicle.getVehicleVin()); VehicleStateVo trunkAjarCloseSt = vehicleTemperatureDto.getStateMap().get("trunkAjarCloseSt"); //打开状态 if(null != trunkAjarCloseSt && trunkAjarCloseSt.getValue().equals("1")){ xpOrder.setOpenTrunk(2); vehiclesCommandVo.setCommandCode(VehiclesCommandEnum.DOOR_TRUNK_CLOSE.getCommand()); }else{ //关闭状态 xpOrder.setOpenTrunk(1); vehiclesCommandVo.setCommandCode(VehiclesCommandEnum.DOOR_TRUNK_OPEN.getCommand()); } } try { // { // "code": 0, // "data": { // "msgId": "1234556" // }, // "msg": "", // "desc": "成功" //} String result = vehicleControlUtil.vehiclesCommand(vehiclesCommandVo, xpOrder.getAppUserId()); log.info("车辆控制结果:{}", result); if (!result.contains("msgId")) { return 0; }else { xpOrderClient.updateOrder(xpOrder); return 1; } } catch (Exception e) { log.error("车辆控制异常:{}", e.getMessage()); return 0; } } @Override public Integer setTemperature(SetTemperatureDTO dto) { // 查询订单 XpOrder xpOrder = xpOrderClient.queryOrderById(dto.getOrderId()).getData(); if (Objects.isNull(xpOrder)) { throw new ServiceException("未查询到有效订单信息", I18nKeyConstant.ORDER_NOT_EXISTS); } XpVehicle xpVehicle = this.getById(xpOrder.getVehicleId()); if (Objects.isNull(xpVehicle)) { throw new ServiceException("未查询到有效车辆信息", I18nKeyConstant.VEHICLE_NOT_EXISTS); } if (dto.getTargetTemperature() > 28) { throw new ServiceException("温度最高28℃", I18nKeyConstant.VEHICLE_TEMPERATURE_UP); } if (dto.getTargetTemperature() < 22) { throw new ServiceException("温度最低22℃", I18nKeyConstant.VEHICLE_TEMPERATURE_DOWN); } // 设定温度 VehiclesCommandVo vehiclesCommandVo = new VehiclesCommandVo(); vehiclesCommandVo.setVin(xpVehicle.getVehicleVin()); vehiclesCommandVo.setCommandCode(VehiclesCommandEnum.HVAC_TEMP_SET.getCommand()); VehiclesCommandParamVo vehiclesCommandParamVo = new VehiclesCommandParamVo(); vehiclesCommandParamVo.setDegree(dto.getTargetTemperature()); vehiclesCommandVo.setExtParam(vehiclesCommandParamVo); try { String result = vehicleControlUtil.vehiclesCommand(vehiclesCommandVo, xpOrder.getAppUserId()); log.info("车辆温度设置结果:{}", result); if (!result.contains("msgId")) { return 0; }else { return 1; } } catch (Exception e) { log.info("车辆温度设置异常:{}", e.getMessage()); return 0; } } @Override public void addVehicle(VehicleAddDTO dto) { String normalizedVehicleCode = trimToNull(dto.getVehicleCode()); String normalizedVin = normalizeVin(dto.getVehicleVin()); String normalizedPlateNumber = trimToNull(dto.getPlateNumber()); String normalizedVehicleColor = trimToNull(dto.getVehicleColor()); validateAndNormalizeIdentityFields(normalizedVehicleCode, normalizedVin, normalizedPlateNumber, normalizedVehicleColor, null); validateElectronicFenceExists(dto.getElectronicId()); dto.setVehicleCode(normalizedVehicleCode); dto.setVehicleVin(normalizedVin); dto.setPlateNumber(normalizedPlateNumber); dto.setVehicleColor(normalizedVehicleColor); // DTO转实体 XpVehicle xpVehicle = new XpVehicle(); BeanUtil.copyProperties(dto, xpVehicle); // 设置默认值:在库车辆 xpVehicle.setVehicleType(1); // 新增时状态为待完善 xpVehicle.setStatus(1); // 设置入库时间 xpVehicle.setWarehousingTime(LocalDateTime.now()); //绑定XPUID String vin = xpVehicle.getVehicleVin(); R xpuIdByVin = xpuVinClient.getXpuIdByVin(vin); if(200 != xpuIdByVin.getCode()){ throw new ServiceException(xpuIdByVin.getMsg()); } String xpuid = xpuIdByVin.getData(); xpVehicle.setXpuId(xpuid); String encryptVin = SensitiveDataCryptoUtil.encrypt(vin); xpVehicle.setVehicleVin(encryptVin); this.save(xpVehicle); if (isInfoComplete(xpVehicle)) { //如果信息完善则状态改为待运营 xpVehicle.setStatus(2); this.updateById(xpVehicle); //保存年检记录 XpVehicleAnnualRecord xpVehicleAnnualRecord = BeanUtil.copyProperties(dto, XpVehicleAnnualRecord.class, "id"); xpVehicleAnnualRecord.setVehicleId(xpVehicle.getId()); xpVehicleAnnualRecordService.save(xpVehicleAnnualRecord); } } @Override public void editVehicle(VehicleEditDTO dto) { XpVehicle existingVehicle = this.getById(dto.getId()); String normalizedVehicleCode = trimToNull(dto.getVehicleCode()); String normalizedVin = normalizeVin(dto.getVehicleVin()); String normalizedPlateNumber = trimToNull(dto.getPlateNumber()); String normalizedVehicleColor = trimToNull(dto.getVehicleColor()); validateAndNormalizeIdentityFields(normalizedVehicleCode, normalizedVin, normalizedPlateNumber, normalizedVehicleColor, dto.getId()); boolean electronicIdChanged = existingVehicle == null || !Objects.equals(existingVehicle.getElectronicId(), dto.getElectronicId()); if (electronicIdChanged) { validateElectronicFenceExists(dto.getElectronicId()); } dto.setVehicleCode(normalizedVehicleCode); dto.setVehicleVin(normalizedVin); dto.setPlateNumber(normalizedPlateNumber); dto.setVehicleColor(normalizedVehicleColor); // DTO转实体 XpVehicle xpVehicle = new XpVehicle(); BeanUtil.copyProperties(dto, xpVehicle); boolean vinChanged = existingVehicle == null || !Objects.equals(existingVehicle.getVehicleVin(), xpVehicle.getVehicleVin()); if (vinChanged || existingVehicle == null || StringUtils.isBlank(existingVehicle.getXpuId())) { // 仅在 VIN 变更或原 XPUID 为空时才重新绑定 XPUID,避免不必要的远程调用 String vin = xpVehicle.getVehicleVin(); R xpuIdByVin = xpuVinClient.getXpuIdByVin(vin); if(200 != xpuIdByVin.getCode()){ throw new ServiceException(xpuIdByVin.getMsg()); } String xpuid = xpuIdByVin.getData(); xpVehicle.setXpuId(xpuid); } else { xpVehicle.setXpuId(existingVehicle.getXpuId()); } String encryptVin = SensitiveDataCryptoUtil.encrypt(xpVehicle.getVehicleVin()); xpVehicle.setVehicleVin(encryptVin); this.updateById(xpVehicle); } private void validateAndNormalizeIdentityFields(String vehicleCode, String vehicleVin, String plateNumber, String vehicleColor, Long excludeId) { if (StringUtils.isBlank(vehicleCode) || !VEHICLE_CODE_PATTERN.matcher(vehicleCode).matches()) { throw new ServiceException("车辆编码格式不正确", I18nKeyConstant.VEHICLE_CODE_INVALID, new Object[]{vehicleCode}); } if (StringUtils.isBlank(vehicleVin) || !VEHICLE_VIN_PATTERN.matcher(vehicleVin).matches()) { throw new ServiceException("车辆VIN码格式不正确", I18nKeyConstant.VEHICLE_VIN_INVALID, new Object[]{vehicleVin}); } if (StringUtils.isBlank(plateNumber) || !Validator.isPlateNumber(plateNumber)) { throw new ServiceException("车牌号格式不正确", I18nKeyConstant.VEHICLE_PLATE_INVALID, new Object[]{plateNumber}); } if (StringUtils.isNotBlank(vehicleColor) && vehicleColor.length() > 3) { throw new ServiceException("车辆颜色长度不能超过3", I18nKeyConstant.VEHICLE_COLOR_INVALID, new Object[]{vehicleColor}); } List duplicates = lambdaQuery() .ne(excludeId != null, XpVehicle::getId, excludeId) .and(w -> w.eq(XpVehicle::getVehicleCode, vehicleCode) .or() .in(XpVehicle::getVehicleVin, SensitiveDataCryptoUtil.queryValues(vehicleVin)) .or() .eq(XpVehicle::getPlateNumber, plateNumber)) .last("limit 3") .list(); if (CollectionUtils.isEmpty(duplicates)) { return; } boolean vehicleCodeExists = duplicates.stream().anyMatch(v -> vehicleCode.equals(v.getVehicleCode())); if (vehicleCodeExists) { throw new ServiceException("车辆编码已存在", I18nKeyConstant.VEHICLE_CODE_EXISTS, new Object[]{vehicleCode}); } boolean vehicleVinExists = duplicates.stream().anyMatch(v -> vehicleVin.equals(SensitiveDataCryptoUtil.decryptIfPossible(v.getVehicleVin()))); if (vehicleVinExists) { throw new ServiceException("车辆VIN码已存在", I18nKeyConstant.VEHICLE_VIN_EXISTS, new Object[]{vehicleVin}); } boolean plateNumberExists = duplicates.stream().anyMatch(v -> plateNumber.equals(v.getPlateNumber())); if (plateNumberExists) { throw new ServiceException("车牌号已存在", I18nKeyConstant.VEHICLE_PLATE_EXISTS, new Object[]{plateNumber}); } } private void validateElectronicFenceExists(Long electronicId) { if (electronicId == null) { return; } R> result = xpElectronicClient.listByIds(Collections.singleton(electronicId)); boolean exists = result != null && result.getData() != null && result.getData().stream().anyMatch(fence -> fence != null && electronicId.equals(fence.getId())); if (!exists) { throw new ServiceException("运营区域不存在", I18nKeyConstant.VEHICLE_ELECTRONIC_NOT_EXISTS, new Object[]{electronicId}); } } private static String normalizeVin(String vehicleVin) { String trimmed = trimToNull(vehicleVin); 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; } @Override @Transactional @ShardingSphereTransactionType(TransactionType.BASE) public void improveInfo(VehicleImproveInfoDTO dto) { // 校验已填写的时间字段是否在有效范围内 validateTimeFields(dto); // DTO转实体 XpVehicle xpVehicle = new XpVehicle(); BeanUtil.copyProperties(dto, xpVehicle); // 更新保险、年检、运营资质等信息 this.updateById(xpVehicle); // 查询更新后的完整信息 XpVehicle vehicle = this.getById(dto.getId()); if (vehicle == null) { return; } if (Integer.valueOf(2).equals(dto.getSaveType())) { // 保存并开始运营:校验保险、年检、运营区域、运营资质是否完善 validateForOperation(vehicle); // 校验通过,直接转为运营状态 vehicle.setStatus(2); vehicle.setVehicleType(2); vehicle.setOperatingStatus(1); this.updateById(vehicle); } else { // 仅保存:如果信息完善则状态改为待运营 if (isInfoComplete(vehicle)) { vehicle.setStatus(2); this.updateById(vehicle); } } //保存年检记录 XpVehicleAnnualRecord xpVehicleAnnualRecord = BeanUtil.copyProperties(dto, XpVehicleAnnualRecord.class, "id"); xpVehicleAnnualRecord.setVehicleId(dto.getId()); xpVehicleAnnualRecordService.save(xpVehicleAnnualRecord); } /** * 校验已填写的时间字段是否在有效范围内(未过期) */ private void validateTimeFields(VehicleImproveInfoDTO dto) { LocalDateTime now = LocalDateTime.now(); List expiredItems = new ArrayList<>(); Locale locale = LocaleContextHolder.getLocale(); if (dto.getAnnualInspectionDate() != null && dto.getAnnualInspectionDate().isBefore(now)) { if ("zh".equals(locale.getLanguage())) { expiredItems.add("年检到期时间"); } else { expiredItems.add("Annual inspection expiration date"); } } if (dto.getInsuranceEndTime() != null && dto.getInsuranceEndTime().isBefore(now)) { if ("zh".equals(locale.getLanguage())) { expiredItems.add("保险到期时间"); } else { expiredItems.add("Insurance expiration date"); } } if (dto.getQualificationExpireTime() != null && dto.getQualificationExpireTime().isBefore(now)) { if ("zh".equals(locale.getLanguage())) { expiredItems.add("运营资质到期时间"); } else { expiredItems.add("Operation qualification expiration date"); } } if (!expiredItems.isEmpty()) { String detail; if ("zh".equals(locale.getLanguage())) { detail = String.join("、", expiredItems); } else { detail = String.join("; ", expiredItems); } throw new ServiceException("以下时间已过期,请填写有效日期:" + detail, I18nKeyConstant.VEHICLE_TIME_EXPIRED, new Object[]{detail}); } } /** * 校验车辆运营前置条件 */ private void validateForOperation(XpVehicle vehicle) { List missingItems = new ArrayList<>(); Locale locale = LocaleContextHolder.getLocale(); // 校验保险信息 if (StringUtils.isBlank(vehicle.getInsuranceCompany()) || StringUtils.isBlank(vehicle.getInsurancePhone()) || vehicle.getInsuranceEndTime() == null || StringUtils.isBlank(vehicle.getPolicyInformation())) { if ("zh".equals(locale.getLanguage())) { missingItems.add("保险信息"); } else { missingItems.add("Insurance information"); } } // 校验年检信息 if (vehicle.getAnnualInspectionDate() == null || StringUtils.isBlank(vehicle.getInspectionAttachments())) { if ("zh".equals(locale.getLanguage())) { missingItems.add("年检信息"); } else { missingItems.add("Annual inspection information"); } } // 校验运营区域 if (vehicle.getElectronicId() == null) { if ("zh".equals(locale.getLanguage())) { missingItems.add("运营区域"); } else { missingItems.add("Operation area"); } } // 校验运营资质 if (StringUtils.isBlank(vehicle.getOperationQualification()) || vehicle.getQualificationExpireTime() == null) { if ("zh".equals(locale.getLanguage())) { missingItems.add("运营资质"); } else { missingItems.add("Operation qualification"); } } if (!missingItems.isEmpty()) { String detail; if ("zh".equals(locale.getLanguage())) { detail = String.join("、", missingItems); } else { detail = String.join("; ", missingItems); } throw new ServiceException("以下信息未完善,无法开始运营:" + detail, I18nKeyConstant.VEHICLE_OPERATION_INFO_INCOMPLETE, new Object[]{detail}); } } /** * 判断车辆信息是否已完善 */ private boolean isInfoComplete(XpVehicle vehicle) { return vehicle.getAnnualInspectionDate() != null && StringUtils.isNotBlank(vehicle.getInsuranceCompany()) && StringUtils.isNotBlank(vehicle.getInsurancePhone()) && vehicle.getInsuranceEndTime() != null && StringUtils.isNotBlank(vehicle.getInspectionAttachments()) && StringUtils.isNotBlank(vehicle.getPolicyInformation()) && StringUtils.isNotBlank(vehicle.getOperationQualification()) && vehicle.getQualificationExpireTime() != null; } /** * 检查车辆是否有任意一项证件过期(精确到日期) * - 精确度:yyyy-MM-dd(只比较日期部分) * - 到期日期等于今天不算过期(使用 isBefore) * * @param vehicle 车辆信息 * @param today 今天日期 */ private boolean isAnyDateExpired(XpVehicle vehicle, LocalDate today) { // 年检到期时间检查:已过期 if (vehicle.getAnnualInspectionDate() != null && vehicle.getAnnualInspectionDate().toLocalDate().isBefore(today)) { return true; } // 保险到期时间检查:已过期 if (vehicle.getInsuranceEndTime() != null && vehicle.getInsuranceEndTime().toLocalDate().isBefore(today)) { return true; } // 运营资质到期时间检查:已过期 if (vehicle.getQualificationExpireTime() != null && vehicle.getQualificationExpireTime().toLocalDate().isBefore(today)) { return true; } return false; } @Override public void batchOperation(String idStr) { if (StringUtils.isBlank(idStr)) { return; } List ids = Arrays.asList(idStr.split(",")); // 查询车辆完整信息 List vehicles = this.listByIds(ids); List vehicleIdList = vehicles.stream() .map(XpVehicle::getId) .filter(Objects::nonNull) .collect(Collectors.toList()); Set boundVehicleIdSet = CollUtil.isEmpty(vehicleIdList) ? Collections.emptySet() : xpFleetVehicleService.lambdaQuery() .in(XpFleetVehicle::getVehicleId, vehicleIdList) .list() .stream() .map(XpFleetVehicle::getVehicleId) .filter(Objects::nonNull) .collect(Collectors.toSet()); LocalDate now = LocalDate.now(); List errorMessages = new ArrayList<>(); for (XpVehicle vehicle : vehicles) { List reasons = new ArrayList<>(); Locale locale = LocaleContextHolder.getLocale(); // 校验状态为待运营 if (!Integer.valueOf(2).equals(vehicle.getStatus())) { if ("zh".equals(locale.getLanguage())) { reasons.add("状态非待运营"); } else { reasons.add("Status is not 'Pending Operation'"); } } // 校验车辆是否绑定车队 if (vehicle.getId() == null || !boundVehicleIdSet.contains(vehicle.getId())) { if ("zh".equals(locale.getLanguage())) { reasons.add("操作失败,车辆未绑定车队"); } else { reasons.add("Operation failed, vehicle is not bound to a fleet"); } } // 校验年检到期时间 if (vehicle.getAnnualInspectionDate() == null || vehicle.getAnnualInspectionDate().toLocalDate().isBefore(now)) { if ("zh".equals(locale.getLanguage())) { reasons.add("年检已过期"); } else { reasons.add("Annual inspection has expired"); } } // 校验保险到期时间 if (vehicle.getInsuranceEndTime() == null || vehicle.getInsuranceEndTime().toLocalDate().isBefore(now)) { if ("zh".equals(locale.getLanguage())) { reasons.add("保险已过期"); } else { reasons.add("Insurance has expired"); } } // 校验运营资质到期时间 if (vehicle.getQualificationExpireTime() == null || vehicle.getQualificationExpireTime().toLocalDate().isBefore(now)) { if ("zh".equals(locale.getLanguage())) { reasons.add("运营资质已过期"); } else { reasons.add("Operation qualification has expired"); } } if (!reasons.isEmpty()) { if ("zh".equals(locale.getLanguage())) { errorMessages.add("车辆[" + vehicle.getPlateNumber() + "]:" + String.join("、", reasons)); } else { errorMessages.add("Vehicles [" + vehicle.getPlateNumber() + "]: " + String.join("; ", reasons)); } } } if (!errorMessages.isEmpty()) { String detail = String.join(";", errorMessages); throw new ServiceException("以下车辆不满足运营条件:" + detail, I18nKeyConstant.VEHICLE_BATCH_OPERATION_CONDITION_NOT_MET, new Object[]{detail}); } // 校验通过,批量转为运营状态(vehicleType: 1→2) LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); wrapper.in(XpVehicle::getId, ids) .set(XpVehicle::getVehicleType, 2) .set(XpVehicle::getOperatingStatus, 1); this.update(wrapper); } @Override public void batchStock(String idStr) { if (StringUtils.isBlank(idStr)) { return; } List ids = Arrays.asList(idStr.split(",")); // 批量将运营车辆转为在库状态(vehicleType: 2→1) LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); wrapper.in(XpVehicle::getId, ids) .set(XpVehicle::getVehicleType, 1); this.update(wrapper); } @Override public void batchSetElectronic(List ids, Long electronicId) { if (CollectionUtils.isEmpty(ids)) { return; } // 批量更新运营区域并设置状态为待运营 LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); wrapper.in(XpVehicle::getId, ids) .set(XpVehicle::getElectronicId, electronicId) .set(XpVehicle::getStatus, 2); this.update(wrapper); } @Override public PageInfo vehiclePageList(VehiclePageQuery query) { query.setVehicleVinQueryValues(SensitiveDataCryptoUtil.queryValues(query.getVehicleVin())); PageInfo pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize()); List list = this.baseMapper.vehiclePageList(pageInfo, query); decryptVehicles(list); //查询运营区域名称 List electronicIds = list.stream() .map(XpVehicle::getElectronicId) .filter(Objects::nonNull) .distinct() .collect(Collectors.toList()); Map electronicNameMap = new HashMap<>(); if (CollUtil.isNotEmpty(electronicIds)) { List data = xpElectronicClient.listByIds(electronicIds).getData(); electronicNameMap = CollUtil.isNotEmpty(data) ? data.stream().collect(Collectors.toMap(XpElectronicFence::getId, XpElectronicFence::getElectronicName)) : Collections.emptyMap(); } Map finalElectronicNameMap = electronicNameMap; list.forEach(item -> { if (item.getElectronicId() != null) { item.setElectronicName(finalElectronicNameMap.get(item.getElectronicId())); } }); if (CollUtil.isNotEmpty(list)) { LocalDate today = LocalDate.now(); List expiredVehicleIds = new ArrayList<>(); List VehicleIds = new ArrayList<>(); for (XpVehicle vehicle : list) { // 检查是否有任意一项过期(精确到 yyyy-MM-dd) boolean isExpired = isAnyDateExpired(vehicle, today); if (isExpired && !Integer.valueOf(3).equals(vehicle.getStatus())) { // 记录需要更新状态的车辆ID expiredVehicleIds.add(vehicle.getId()); // 同时更新返回结果中的状态 vehicle.setStatus(3); } } // 批量更新过期车辆状态到数据库 if (CollUtil.isNotEmpty(expiredVehicleIds)) { LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.in(XpVehicle::getId, expiredVehicleIds) .set(XpVehicle::getStatus, 3); this.update(updateWrapper); } } pageInfo.setRecords(list); return pageInfo; } @Override public List vehicleExportList(VehicleExportQuery query) { query.setVehicleVinQueryValues(SensitiveDataCryptoUtil.queryValues(query.getVehicleVin())); List list = this.baseMapper.vehicleExportList(query); decryptVehicles(list); return list; } @Override public void importVehicles(List importList) { if (CollectionUtils.isEmpty(importList)) { return; } List vehicleList = importList.stream().map(item -> { XpVehicle vehicle = new XpVehicle(); BeanUtil.copyProperties(item, vehicle); // 设置默认值 vehicle.setVehicleType(1); vehicle.setStatus(1); vehicle.setWarehousingTime(LocalDateTime.now()); return vehicle; }).collect(Collectors.toList()); this.saveBatch(vehicleList); } @Override public XpVehicle getVehicleDetail(Long id) { XpVehicle vehicle = this.getById(id); if (vehicle == null) { return null; } // 查询围栏名称 if (vehicle.getElectronicId() != null) { try { R result = xpElectronicClient.queryById(vehicle.getElectronicId()); if (R.isSuccess(result) && result.getData() != null) { vehicle.setElectronicName(result.getData().getElectronicName()); } } catch (Exception e) { log.warn("查询围栏名称失败, electronicId={}", vehicle.getElectronicId(), e); } } return vehicle; } @Override public PageInfo operationPageList(OperationVehiclePageQuery query) { //1.构建分页参数 PageInfo pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize()); // 2.构建查询条件 LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(XpVehicle::getVehicleType, 2) .like(StringUtils.isNotBlank(query.getVehicleCode()), XpVehicle::getVehicleCode, query.getVehicleCode()) .in(StringUtils.isNotBlank(query.getVehicleVin()), XpVehicle::getVehicleVin, SensitiveDataCryptoUtil.queryValues(query.getVehicleVin())) .like(StringUtils.isNotBlank(query.getPlateNumber()), XpVehicle::getPlateNumber, query.getPlateNumber()) .eq(Objects.nonNull(query.getOperatingStatus()), XpVehicle::getOperatingStatus, query.getOperatingStatus()) .eq(Objects.nonNull(query.getElectronicId()), XpVehicle::getElectronicId, query.getElectronicId()); if (Objects.nonNull(query.getStartTime())) { wrapper.ge(XpVehicle::getAnnualInspectionDate, LocalDateTime.of(query.getStartTime(), LocalTime.MIN)); } if (Objects.nonNull(query.getEndTime())) { wrapper.le(XpVehicle::getAnnualInspectionDate, LocalDateTime.of(query.getEndTime(), LocalTime.MAX)); } wrapper.orderByDesc(XpVehicle::getCreateTime); //3.执行分页查询 PageInfo page = this.page(pageInfo, wrapper); //4.批量查询围栏名称并填充 Set electronicIdSet = page.getRecords().stream().map(XpVehicle::getElectronicId).collect(Collectors.toSet()); List fenceList = CollUtil.isNotEmpty(electronicIdSet) ? xpElectronicClient.listByIds(electronicIdSet).getData() : Collections.emptyList(); if (CollUtil.isNotEmpty(fenceList)) { Map fenceNameMap = fenceList.stream().collect(Collectors.toMap(XpElectronicFence::getId, XpElectronicFence::getElectronicName)); page.getRecords().forEach(vehicle -> { if (vehicle.getElectronicId() != null) { vehicle.setElectronicName(fenceNameMap.get(vehicle.getElectronicId())); } }); } return pageInfo; } @Override public List operationVehicleExportList(OperationVehicleExportQuery query) { // 1.构建查询条件 LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(XpVehicle::getVehicleType, 2) .like(StringUtils.isNotBlank(query.getVehicleCode()), XpVehicle::getVehicleCode, query.getVehicleCode()) .in(StringUtils.isNotBlank(query.getVehicleVin()), XpVehicle::getVehicleVin, SensitiveDataCryptoUtil.queryValues(query.getVehicleVin())) .like(StringUtils.isNotBlank(query.getPlateNumber()), XpVehicle::getPlateNumber, query.getPlateNumber()) .eq(Objects.nonNull(query.getOperatingStatus()), XpVehicle::getOperatingStatus, query.getOperatingStatus()) .eq(Objects.nonNull(query.getElectronicId()), XpVehicle::getElectronicId, query.getOperatingStatus()) .in(CollUtil.isNotEmpty(query.getIds()), XpVehicle::getId, query.getIds()); if (Objects.nonNull(query.getStartTime())) { wrapper.ge(XpVehicle::getAnnualInspectionDate, LocalDateTime.of(query.getStartTime(), LocalTime.MIN)); } if (Objects.nonNull(query.getEndTime())) { wrapper.le(XpVehicle::getAnnualInspectionDate, LocalDateTime.of(query.getEndTime(), LocalTime.MAX)); } wrapper.orderByDesc(XpVehicle::getCreateTime); //2.执行查询 List list = this.list(wrapper); //3.批量查询围栏名称并填充 Set electronicIdSet = list.stream().map(XpVehicle::getElectronicId).collect(Collectors.toSet()); List fenceList = CollUtil.isNotEmpty(electronicIdSet) ? xpElectronicClient.listByIds(electronicIdSet).getData() : Collections.emptyList(); if (CollUtil.isNotEmpty(fenceList)) { Map fenceNameMap = fenceList.stream().collect(Collectors.toMap(XpElectronicFence::getId, XpElectronicFence::getElectronicName)); list.forEach(vehicle -> { if (vehicle.getElectronicId() != null) { vehicle.setElectronicName(fenceNameMap.get(vehicle.getElectronicId())); } }); } return list; } @Override public VehicleTemperatureDto queryTemperatureByOrderId(Long orderId, String uid) { XpOrder xpOrder = xpOrderClient.queryOrderById(orderId).getData(); if (Objects.isNull(xpOrder)) { throw new ServiceException("未查询到有效订单信息", I18nKeyConstant.ORDER_NOT_EXISTS); } XpVehicle xpVehicle = this.getById(xpOrder.getVehicleId()); if (Objects.isNull(xpVehicle) || Objects.isNull(xpVehicle.getVehicleVin())) { throw new ServiceException("未查询到有效车辆信息", I18nKeyConstant.VEHICLE_NOT_EXISTS); } try { VehicleTemperatureDto vehicleTemperatureDto = vehicleControlUtil.queryVehicleTemperature(uid, xpVehicle.getVehicleVin()); log.info("查询车辆温度成功:{}", vehicleTemperatureDto); return vehicleTemperatureDto; } catch (Exception e) { log.error("查询车辆温度失败", e); return new VehicleTemperatureDto(); } } @Override public PageInfo getVehiclePageList(VehicleSelectPageQuery query) { Page page = new Page<>(query.getPageNum(), query.getPageSize()); page = baseMapper.getVehiclePageList(page, query.getPlateNumber(), query.getFleetName()); PageInfo pageInfo = new PageInfo<>(); pageInfo.setTotal(page.getTotal()); pageInfo.setRecords(page.getRecords()); return pageInfo; } @Override public Boolean getVehicleTrunkOpenStatus(Long uid, Long id) { XpVehicle xpVehicle = this.getById(id); try { VehicleTemperatureDto vehicleTemperatureDto = vehicleControlUtil.queryVehicleTemperature(String.valueOf(uid), xpVehicle.getVehicleVin()); log.info("查询车辆温度成功:{}", vehicleTemperatureDto); VehicleStateVo trunkAjarCloseSt = vehicleTemperatureDto.getStateMap().get("trunkAjarCloseSt"); if(null == trunkAjarCloseSt){ return false; } return trunkAjarCloseSt.getValue().equals("1"); } catch (Exception e) { log.error("查询车辆温度失败", e); return false; } } @Override public XpVehicle getById(Serializable id) { XpVehicle vehicle = super.getById(id); decryptVehicle(vehicle); return vehicle; } @Override public boolean save(XpVehicle entity) { encryptVehicle(entity); try { return super.save(entity); } finally { decryptVehicle(entity); } } @Override public boolean saveBatch(Collection entityList) { if (entityList != null) { entityList.forEach(this::encryptVehicle); } try { return super.saveBatch(entityList); } finally { decryptVehicles(entityList); } } @Override public boolean updateById(XpVehicle entity) { encryptVehicle(entity); try { return super.updateById(entity); } finally { decryptVehicle(entity); } } @Override public List list() { List list = super.list(); decryptVehicles(list); return list; } @Override public List list(Wrapper queryWrapper) { List list = super.list(queryWrapper); decryptVehicles(list); return list; } @Override public XpVehicle getOne(Wrapper queryWrapper) { XpVehicle vehicle = super.getOne(queryWrapper); decryptVehicle(vehicle); return vehicle; } private void encryptVehicle(XpVehicle vehicle) { if (vehicle != null) { vehicle.setVehicleVin(SensitiveDataCryptoUtil.encrypt(vehicle.getVehicleVin())); } } private void decryptVehicle(XpVehicle vehicle) { if (vehicle != null) { vehicle.setVehicleVin(SensitiveDataCryptoUtil.decryptIfPossible(vehicle.getVehicleVin())); } } private void decryptVehicles(Collection vehicles) { if (vehicles != null) { vehicles.forEach(this::decryptVehicle); } } }