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 cn.hutool.core.util.StrUtil; import com.alibaba.fastjson2.JSON; 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.query.QueryWrapper; 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.xiaopeng.api.dto.account.UcUserInfo; import com.xiaopeng.api.dto.map.OrderRouteAndFareDto; import com.xiaopeng.api.dto.operation.LonLatDto; import com.xiaopeng.api.dto.pushMessage.PushOrderRouteDTO; import com.xiaopeng.api.dto.vehicleConvoy.ServiceVehicleListDto; import com.xiaopeng.api.dto.vehicleConvoy.VehicleTemperatureDto; import com.xiaopeng.api.feignClient.account.UserResourcesClient; import com.xiaopeng.api.feignClient.automaticPilot.XpuVinClient; import com.xiaopeng.api.feignClient.maintenance.XpVehicleModelClient; import com.xiaopeng.api.feignClient.map.AdcFencePushClient; import com.xiaopeng.api.feignClient.map.XpMapClient; import com.xiaopeng.api.feignClient.operation.XpElectronicClient; import com.xiaopeng.api.feignClient.operation.XpOfficialPartnerClient; import com.xiaopeng.api.feignClient.operation.XpTargetedServiceClient; 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.feignClient.vehicleConvoy.UserVehicleClient; import com.xiaopeng.api.model.account.XpOperationsUser; import com.xiaopeng.api.model.maintenance.XpVehicleModel; import com.xiaopeng.api.model.operation.XpElectronicFence; import com.xiaopeng.api.model.operation.XpElectronicFenceArea; import com.xiaopeng.api.model.operation.XpOfficialPartner; import com.xiaopeng.api.model.order.XpOrder; import com.xiaopeng.api.model.order.XpTestOrder; import com.xiaopeng.api.model.vehicleConvoy.XpFleetOperations; import com.xiaopeng.api.model.vehicleConvoy.XpFleetVehicle; import com.xiaopeng.api.model.vehicleConvoy.XpVehicle; import com.xiaopeng.api.model.vehicleConvoy.XpVehicleAnnualRecord; import com.xiaopeng.api.vo.automaticPilot.VehiclePosition; import com.xiaopeng.api.vo.automaticPilot.protobuf.RtTripInfo; import com.xiaopeng.api.vo.operation.ElectronicFenceDetailVo; import com.xiaopeng.api.vo.operation.RouteVo; import com.xiaopeng.api.vo.operation.VehiclePositionVo; import com.xiaopeng.api.vo.order.OrderTrajectoryVo; import com.xiaopeng.api.vo.vehicleConvoy.*; import com.xiaopeng.common.core.constant.CacheConstants; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.core.enums.PushMessageTypeEnum; import com.xiaopeng.common.core.exception.ServiceException; import com.xiaopeng.common.core.exception.base.BaseException; 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.config.MinistryTransportProperties; import com.xiaopeng.vehicleConvoy.constant.I18nKeyConstant; 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.api.vo.vehicleConvoy.XpVehicleVO; import com.xiaopeng.vehicleConvoy.model.vo.UnlockVehicleDoorVo; import com.xiaopeng.vehicleConvoy.model.vo.VehiclePositionDto; import com.xiaopeng.vehicleConvoy.service.AsyncVehicleStateService; import com.xiaopeng.vehicleConvoy.service.IXpFleetVehicleService; import com.xiaopeng.vehicleConvoy.service.IXpVehicleAnnualRecordService; import com.xiaopeng.vehicleConvoy.service.IXpVehicleService; import com.xiaopeng.vehicleConvoy.util.MinistryOfTransport; import com.xiaopeng.vehicleConvoy.util.OfficialPartnerUtil; 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.beans.factory.annotation.Value; import org.springframework.context.i18n.LocaleContextHolder; 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.mongodb.core.query.Update; 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.io.Serializable; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.util.*; import java.util.regex.Pattern; import java.util.stream.Collectors; /** *

* 车辆管理 服务实现类 *

* * @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; @Resource private XpMapClient xpMapClient; @Autowired private AliOssService aliOssService; @Autowired private RedisTemplate redisTemplate; @Autowired private XpPushMessageClient xpPushMessageClient; @Resource private XpTestOrderClient xpTestOrderClient; @Autowired(required = false) private AdcFencePushClient adcFencePushClient; @Resource private UserVehicleClient userVehicleClient; @Autowired private MinistryTransportProperties ministryTransportProperties; @Resource private UserResourcesClient userResourcesClient; @Resource private AsyncVehicleStateService asyncVehicleStateService; @Resource private XpOfficialPartnerClient officialPartnerClient; @Resource private OfficialPartnerUtil officialPartnerUtil; @Resource private XpVehicleModelClient xpVehicleModelClient; @Resource private XpTargetedServiceClient targetedServiceClient; @Resource private MinistryOfTransport ministryOfTransport; @Value("${vehicle.control.distance-threshold:100}") private int distanceThreshold; @Value("${vehicle.control.report.enable:true}") private boolean reportEnable; @Value("${robotaxi.appId:xp_robotaxi_api}") private String appid; @Override public List listByIds(List ids) { if (CollectionUtils.isEmpty(ids)) { return new ArrayList<>(); } //查询车型和 车队信息 List list = super.listByIds(ids); decryptVehicles(list); return list; } /** * * @param orderId * @param orderType 订单类型(1=用户单,2=测试单) * @return */ @Override public List listAvailableVehicles(Long orderId, Integer orderType, String plateNumber, String fleetName) { // 1. 通过订单ID查询订单信息,获取区域ID Long areaId = null; Double longitude =null;//经度 Double latitude =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(); longitude = order.getStartLon(); latitude = order.getStartLat(); } else { XpTestOrder testOrder = xpTestOrderClient.queryOrderById(orderId).getData(); areaId = testOrder.getXpElectronicFenceAreaId(); longitude = testOrder.getStartLon(); latitude = testOrder.getStartLat(); } 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) VehicleMapQuery query = new VehicleMapQuery(); query.setState(0); query.setVehicleType(2); query.setOperatingStatus(1); query.setElectronicId(electronicId.toString()); query.setFleetName(fleetName); query.setPlateNumber(plateNumber); List list = baseMapper.findList(query); decryptVehicleVos(list); //获取车的位置 和订单的位置 并计算 距离 和 时间 List lists = new ArrayList<>(); if(!list.isEmpty()){ for (XpVehicleVO xpVehicle : list) { String vin = SensitiveDataCryptoUtil.decryptIfPossible(xpVehicle.getVehicleVin()); VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(vin)), VehiclePosition.class); if(vehiclePosition == null){ continue; } OrderRouteAndFareDto dto = new OrderRouteAndFareDto(); dto.setOrderId(orderId); LonLatDto origin = new LonLatDto(); origin.setLongitude(longitude); origin.setLatitude(latitude); dto.setOrigin(origin); LonLatDto destination = new LonLatDto(); destination.setLongitude(vehiclePosition.getPoint().getX()); destination.setLatitude(vehiclePosition.getPoint().getY()); dto.setDestination(destination); dto.setRouteType(1); // dto.setVehicleId(xpVehicle.getId()); R routeVoR = xpMapClient.getOrderDistanceAndDuration(dto); if (200 != routeVoR.getCode()) { throw new RuntimeException("获取订单线路失败"); } if(routeVoR.getData() != null){ xpVehicle.setArrivalDistance(routeVoR.getData().getDistance()); xpVehicle.setArrivalTimeSecond(routeVoR.getData().getDuration()); if(routeVoR.getData().getDuration() != null){ BigDecimal time = new BigDecimal(routeVoR.getData().getDuration()); if(time.compareTo(new BigDecimal("60"))<0){ //秒 xpVehicle.setArrivalTime(time.toPlainString()+"秒"); }else if(time.compareTo(new BigDecimal("60"))>0 && time.compareTo(new BigDecimal("3600"))<0){//分 BigDecimal min = time.divide(new BigDecimal("60"), 0, RoundingMode.HALF_UP); xpVehicle.setArrivalTime(min.toPlainString()+"分钟"); }else if(time.compareTo(new BigDecimal("3600"))>0){//小时 // 计算小时和分钟 long totalSeconds = time.longValue(); long hours = totalSeconds / 3600; long minutes = (totalSeconds % 3600) / 60; StringBuilder timeStr = new StringBuilder(); if (hours > 0) { timeStr.append(hours).append("小时"); } if (minutes > 0 || hours == 0) { timeStr.append(minutes).append("分钟"); } xpVehicle.setArrivalTime(timeStr.toString()); } } lists.add(xpVehicle); } } // 按预计到达时间(秒)升序排序 lists.sort(Comparator.comparing(XpVehicle::getArrivalTimeSecond, Comparator.nullsLast(Comparator.naturalOrder()))); lists.subList(0, Math.min(lists.size(), 5)); } return lists; } @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); decryptVehicleVos(list); if (CollectionUtils.isEmpty(list)) { return pageInfo; } for (XpVehicleVO xpVehicleVO : list) { // 查询车辆的续航里程 String vin = SensitiveDataCryptoUtil.decryptIfPossible(xpVehicleVO.getVehicleVin()); VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(vin)), 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(); } List list = lambdaQuery().like(XpVehicle::getPlateNumber, plateNumber).list(); decryptVehicles(list); return list; } @Override public List listByFenceId(Long fenceId) { if (fenceId == null) { return Collections.emptyList(); } List list = lambdaQuery().eq(XpVehicle::getElectronicId, fenceId).eq(XpVehicle::getOperatingStatus, 1).eq(XpVehicle::getState, 0).list(); decryptVehicles(list); return list; } @Override public List listAllByFenceId(Long fenceId) { if (fenceId == null) { return Collections.emptyList(); } List list = lambdaQuery().eq(XpVehicle::getElectronicId, fenceId).list(); decryptVehicles(list); return 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米 String vin = SensitiveDataCryptoUtil.decryptIfPossible(xpVehicle.getVehicleVin()); VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(vin)), 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 > distanceThreshold) { 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); } } if(null == xpVehicle.getOfficialPartnerId()){ // 车门解锁、后备箱解锁 VehiclesCommandVo vehiclesCommandVo = new VehiclesCommandVo(); vehiclesCommandVo.setVin(vin); if ("door".equals(dto.getDoorType())) { // 开启车门 判断是否为P档 Integer gearLevel = redisService.getCacheObject(CacheConstants.GEAR_LEVEL + xpVehicle.getXpuId()); log.info("开启车门当前车辆档位: {} {}", xpVehicle.getXpuId(), gearLevel); if (gearLevel == null || gearLevel != 4) { throw new ServiceException("车辆非P档,无法开启车门", I18nKeyConstant.VEHICLE_NOT_P_DOOR); } vehiclesCommandVo.setCommandCode(VehiclesCommandEnum.DOOR_UNLOCK.getCommand()); } else { // 开启后备箱 判断是否为P档 Integer gearLevel = redisService.getCacheObject(CacheConstants.GEAR_LEVEL + xpVehicle.getXpuId()); log.info("开启后备箱当前车辆档位: {} {}", xpVehicle.getXpuId(), gearLevel); if (gearLevel == null || gearLevel != 4) { throw new ServiceException("车辆非P档,无法操作后备箱", I18nKeyConstant.VEHICLE_NOT_P_GEAR); } //查询后备箱开关状态(打开状态=true,关闭状态=false) Boolean hasKey = vehicleControlUtil.queryTrunkDoorStateSync(xpOrder.getAppUserId(), vin, reportEnable); //打开状态 if (hasKey) { 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 msgId = vehicleControlUtil.vehiclesCommand(vehiclesCommandVo, xpOrder.getAppUserId()); log.info("车辆控制结果:{}", msgId); if (StringUtils.isEmpty(msgId)) { return 0; } else { xpOrderClient.updateOrder(xpOrder); return 1; } } catch (Exception e) { log.error("车辆控制异常:{}", e.getMessage()); return 0; } }else{ //流量接入-车辆控制推送 车门解锁/后备箱开关 UnlockVehicleDoorVo vo = new UnlockVehicleDoorVo(); vo.setVin(vin); if("door".equals(dto.getDoorType())){ vo.setAction(1); }else{ Integer status = officialPartnerUtil.queryVehicleDoorStatus(xpVehicle.getOfficialPartnerId(), vin).getData(); if(0 == status){ vo.setAction(1); }else{ vo.setAction(2); } } vo.setLongitude(dto.getLongitude()); vo.setLatitude(dto.getLatitude()); officialPartnerUtil.unlockVehicleDoor(xpVehicle.getOfficialPartnerId(), vo, dto.getDoorType()); return 1; } } @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() == null) { throw new ServiceException("温度不能为空"); } if (dto.getTargetTemperature() > 28) { throw new ServiceException("温度最高28℃", I18nKeyConstant.VEHICLE_TEMPERATURE_UP); } if (dto.getTargetTemperature() < 22) { throw new ServiceException("温度最低22℃", I18nKeyConstant.VEHICLE_TEMPERATURE_DOWN); } if(null == xpVehicle.getOfficialPartnerId()){ String vin = SensitiveDataCryptoUtil.decryptIfPossible(xpVehicle.getVehicleVin()); // 设定温度 VehiclesCommandVo vehiclesCommandVo = new VehiclesCommandVo(); vehiclesCommandVo.setVin(vin); vehiclesCommandVo.setCommandCode(VehiclesCommandEnum.LEFT_REAR_HVAC_TEMP_SET_FLOAT.getCommand()); VehiclesCommandParamVo vehiclesCommandParamVo = new VehiclesCommandParamVo(); vehiclesCommandParamVo.setDegree(dto.getTargetTemperature()); vehiclesCommandVo.setParam(vehiclesCommandParamVo); try { String msgId = vehicleControlUtil.vehiclesCommand(vehiclesCommandVo, xpOrder.getAppUserId()); log.info("车辆温度设置结果:{}", msgId); if (StringUtils.isEmpty(msgId)) { return 0; } else { return 1; } } catch (Exception e) { log.info("车辆温度设置异常:{}", e.getMessage()); return 0; } }else{ String vin = SensitiveDataCryptoUtil.decryptIfPossible(xpVehicle.getVehicleVin()); //流量接入-车辆控制推送 合作商车辆设置空调温度 officialPartnerUtil.setAirConditioningTemperature(xpVehicle.getOfficialPartnerId(), vin, dto.getTargetTemperature()); return 1; } } @Override public XpVehicle addVehicle(VehicleAddDTO dto, Integer officialPartnerId) { 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()); if(null == officialPartnerId){ //绑定XPUID String vin = SensitiveDataCryptoUtil.decryptIfPossible(xpVehicle.getVehicleVin()); R xpuIdByVin = xpuVinClient.getXpuIdByVin(vin); if (200 != xpuIdByVin.getCode()) { throw new RuntimeException(xpuIdByVin.getMsg()); } String xpuid = xpuIdByVin.getData(); xpVehicle.setXpuId(xpuid); } //绑定XPUID String vin = SensitiveDataCryptoUtil.decryptIfPossible(xpVehicle.getVehicleVin()); String encryptVin = SensitiveDataCryptoUtil.encrypt(vin); xpVehicle.setVehicleVin(encryptVin); xpVehicle.setOfficialPartnerId(officialPartnerId); this.save(xpVehicle); // ADC推送:新车绑定围栏时触发单车推送 if (dto.getElectronicId() != null && adcFencePushClient != null) { try { adcFencePushClient.pushFenceToVehicle(vin, dto.getElectronicId()); log.info("[ADC推送] 新增车辆{}绑定围栏{},已触发推送", vin, dto.getElectronicId()); } catch (Exception e) { log.error("[ADC推送] 新增车辆{}绑定围栏{}推送失败,不影响主流程", vin, dto.getElectronicId(), e); } } if (isInfoComplete(xpVehicle)) { //如果信息完善则状态改为待运营 xpVehicle.setStatus(2); this.updateById(xpVehicle); //保存年检记录 XpVehicleAnnualRecord xpVehicleAnnualRecord = BeanUtil.copyProperties(dto, XpVehicleAnnualRecord.class, "id"); xpVehicleAnnualRecord.setVehicleId(xpVehicle.getId()); xpVehicleAnnualRecordService.save(xpVehicleAnnualRecord); } // pushVehicleBaseInfoToMinistry(this.getById(xpVehicle.getId()), vin, officialPartnerId, "新增车辆"); return xpVehicle; } @Override public void editVehicle(VehicleEditDTO dto, Integer officialPartnerId) { XpVehicle existingVehicle = this.getById(dto.getId()); if(null != officialPartnerId && !existingVehicle.getOfficialPartnerId().equals(officialPartnerId)){ throw new ServiceException("车辆不属于当前合作伙伴"); } 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 vin = xpVehicle.getVehicleVin(); String encryptVin = SensitiveDataCryptoUtil.encrypt(vin); xpVehicle.setVehicleVin(encryptVin); this.updateById(xpVehicle); // ADC推送:围栏绑定变更时触发单车推送 if (electronicIdChanged && dto.getElectronicId() != null && adcFencePushClient != null) { try { adcFencePushClient.pushFenceToVehicle(vin, dto.getElectronicId()); log.info("[ADC推送] 编辑车辆{}变更围栏为{},已触发推送", vin, dto.getElectronicId()); } catch (Exception e) { log.error("[ADC推送] 编辑车辆{}变更围栏{}推送失败,不影响主流程", vin, dto.getElectronicId(), e); } } // pushVehicleBaseInfoToMinistry(this.getById(dto.getId()), vin, officialPartnerId, "编辑车辆"); } 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; } private void pushVehicleBaseInfoToMinistry(XpVehicle xpVehicle, String vin, Integer officialPartnerId, String scene) { if (officialPartnerId != null || xpVehicle == null) { return; } try { RegulationVehicleBaseInfoDTO regulationDto = buildRegulationVehicleBaseInfoDto(xpVehicle, vin); String response = ministryOfTransport.pushVehicleBaseInfo(regulationDto); log.info("{}后上报交通部CLJB完成, vehicleId={}, vin={}, response={}", scene, xpVehicle.getId(), vin, response); } catch (Exception e) { log.error("{}后上报交通部CLJB失败, vehicleId={}, vin={}", scene, xpVehicle.getId(), vin, e); } } private void pushVehicleInsuranceInfoToMinistry(XpVehicle xpVehicle, Integer officialPartnerId, String scene) { if (officialPartnerId != null || xpVehicle == null) { return; } try { RegulationVehicleInsuranceInfoDTO regulationDto = buildRegulationVehicleInsuranceInfoDto(xpVehicle); String response = ministryOfTransport.pushVehicleInsuranceInfo(regulationDto); log.info("{}后上报交通部CLBX完成, vehicleId={}, vin={}, response={}", scene, xpVehicle.getId(), SensitiveDataCryptoUtil.decryptIfPossible(xpVehicle.getVehicleVin()), response); } catch (Exception e) { log.error("{}后上报交通部CLBX失败, vehicleId={}, vin={}", scene, xpVehicle.getId(), SensitiveDataCryptoUtil.decryptIfPossible(xpVehicle.getVehicleVin()), e); } } private RegulationVehicleBaseInfoDTO buildRegulationVehicleBaseInfoDto(XpVehicle xpVehicle, String vin) { RegulationVehicleBaseInfoDTO regulationDto = new RegulationVehicleBaseInfoDTO(); XpVehicleModel vehicleModel = getVehicleModel(xpVehicle.getVehicleModel()); String modelName = vehicleModel == null ? trimToNull(xpVehicle.getVehicleModel()) : trimToNull(vehicleModel.getName()); String seat = vehicleModel == null ? null : trimToNull(vehicleModel.getSeat()); LocalDateTime now = LocalDateTime.now(); regulationDto.setAddress(ministryTransportProperties.getVehicleAddressCode()); regulationDto.setVehicleNo(trimToNull(xpVehicle.getPlateNumber())); regulationDto.setPlateColor(getPlateColor(xpVehicle.getPlateNumber())); regulationDto.setSeats(defaultIfBlank(seat, "5")); regulationDto.setBrand("小鹏"); regulationDto.setModel(defaultIfBlank(modelName, "未知车型")); regulationDto.setVehicleType("小型轿车"); regulationDto.setOwnerName(ministryTransportProperties.getOwnerName()); regulationDto.setOwnerType("1"); regulationDto.setVehicleColor(trimToNull(xpVehicle.getVehicleColor())); regulationDto.setEngineId(defaultIfBlank(trimToNull(xpVehicle.getVehicleCode()), vin)); regulationDto.setVin(vin); regulationDto.setCertifyDateA(formatDate(now)); regulationDto.setFuelType("纯电动"); regulationDto.setEngineDisplace("0"); regulationDto.setEnginePower("0"); regulationDto.setWheelBase("0"); regulationDto.setPhotoId(defaultIfBlank(trimToNull(xpVehicle.getVehicleCode()), vin)); regulationDto.setTransAgency(ministryTransportProperties.getTransAgency()); regulationDto.setTransArea(ministryTransportProperties.getTransArea()); regulationDto.setTransDateStart(formatDate(now)); regulationDto.setTransDateStop(formatDate(defaultIfNull(xpVehicle.getQualificationExpireTime(), now.plusYears(1)))); regulationDto.setCertifyDateB(formatDate(now)); regulationDto.setFixState("1"); regulationDto.setNextFixDate(formatDate(defaultIfNull(xpVehicle.getAnnualInspectionDate(), now.plusYears(1)))); regulationDto.setCheckState("1"); regulationDto.setCheckDate(formatDate(defaultIfNull(xpVehicle.getAnnualInspectionDate(), now))); regulationDto.setGpsImei(defaultIfBlank(trimToNull(xpVehicle.getXpuId()), vin)); regulationDto.setGpsInstallDate(formatDate(now)); regulationDto.setRegisterDate(formatDate(now)); regulationDto.setCommercialType("1"); regulationDto.setFareType(ministryTransportProperties.getFareType()); regulationDto.setUpdateTime(now.format(MinistryOfTransport.DATA_TIME_FORMATTER)); return regulationDto; } private RegulationVehicleInsuranceInfoDTO buildRegulationVehicleInsuranceInfoDto(XpVehicle xpVehicle) { RegulationVehicleInsuranceInfoDTO regulationDto = new RegulationVehicleInsuranceInfoDTO(); LocalDateTime now = LocalDateTime.now(); LocalDateTime insuranceExpireTime = defaultIfNull(xpVehicle.getInsuranceEndTime(), now.plusYears(1)); regulationDto.setVehicleNo(trimToNull(xpVehicle.getPlateNumber())); regulationDto.setPlateColor(getPlateColor(xpVehicle.getPlateNumber())); regulationDto.setInsurCom(defaultIfBlank(trimToNull(xpVehicle.getInsuranceCompany()), "未知保险公司")); regulationDto.setInsurNum("无"); regulationDto.setInsurType("交强险"); regulationDto.setInsurCount("0"); regulationDto.setInsurEff(formatDate(insuranceExpireTime.minusYears(1))); regulationDto.setInsurExp(formatDate(insuranceExpireTime)); regulationDto.setFlag("1"); regulationDto.setUpdateTime(now.format(MinistryOfTransport.DATA_TIME_FORMATTER)); return regulationDto; } private XpVehicleModel getVehicleModel(String vehicleModelId) { if (StringUtils.isBlank(vehicleModelId)) { return null; } R> result = xpVehicleModelClient.listByIds(Collections.singleton(vehicleModelId)); if (result == null || CollUtil.isEmpty(result.getData())) { return null; } return result.getData().get(0); } /** * 根据车牌号判断车牌颜色 * 规则:除去第一个汉字后,6位为蓝色,7位为渐变绿 * * @param plateNumber 车牌号 * @return 车牌颜色 */ private String getPlateColor(String plateNumber) { if (StringUtils.isBlank(plateNumber)) { return "蓝色"; } // 去除第一个汉字(省份简称) String plateWithoutProvince = plateNumber.substring(1); // 判断位数 if (plateWithoutProvince.length() == 7) { return "渐变绿"; } else { return "蓝色"; } } private LocalDateTime defaultIfNull(LocalDateTime value, LocalDateTime defaultValue) { return value == null ? defaultValue : value; } private String defaultIfBlank(String value, String defaultValue) { return StringUtils.isBlank(value) ? defaultValue : value; } private String formatDate(LocalDateTime time) { return time == null ? null : time.format(MinistryOfTransport.DATA_TIME_FORMATTER); } private void pushDriverPositionInfoToMinistry(XpVehicle xpVehicle, VehiclePositionDto dto, String scene) { if (xpVehicle == null || dto == null) { return; } try { RegulationDriverPositionInfoDTO regulationDto = buildRegulationDriverPositionInfoDto(xpVehicle, dto); String response = ministryOfTransport.pushDriverPositionInfo(regulationDto); log.info("{}后上报交通部JSYDW完成, vehicleId={}, vin={}, response={}", scene, xpVehicle.getId(), SensitiveDataCryptoUtil.decryptIfPossible(xpVehicle.getVehicleVin()), response); } catch (Exception e) { log.error("{}后上报交通部JSYDW失败, vehicleId={}, vin={}", scene, xpVehicle.getId(), SensitiveDataCryptoUtil.decryptIfPossible(xpVehicle.getVehicleVin()), e); } } private RegulationDriverPositionInfoDTO buildRegulationDriverPositionInfoDto(XpVehicle xpVehicle, VehiclePositionDto dto) { RegulationDriverPositionInfoDTO regulationDto = new RegulationDriverPositionInfoDTO(); regulationDto.setAddress(ministryTransportProperties.getDriverAddressCode()); regulationDto.setDriverRegionCode(ministryTransportProperties.getDriverRegionCode()); regulationDto.setVehicleNo(trimToNull(xpVehicle.getPlateNumber())); regulationDto.setPlateColor(getPlateColor(xpVehicle.getPlateNumber())); regulationDto.setPositionTime(LocalDateTime.now().format(MinistryOfTransport.DATA_TIME_FORMATTER)); regulationDto.setLongitude(formatCoordinate(dto.getLongitude())); regulationDto.setLatitude(formatCoordinate(dto.getLatitude())); regulationDto.setSpeed(formatNumber(dto.getSpeed(), 0)); regulationDto.setDirection(formatNumber(dto.getCourse(), 0)); regulationDto.setElevation(formatNumber(dto.getAlt(), 0)); regulationDto.setMileage(dto.getTotalOdometer() == null ? "0" : String.valueOf(dto.getTotalOdometer())); regulationDto.setEncrypt("1"); regulationDto.setBizStatus(dto.getOrderId() == null ? "0" : "1"); regulationDto.setOrderId(dto.getOrderId() == null ? null : String.valueOf(dto.getOrderId())); regulationDto.setLicenseId(ministryTransportProperties.getLicenseId()); regulationDto.setDriverName(ministryTransportProperties.getDriverName()); regulationDto.setDriverCertCard(ministryTransportProperties.getDriverCertCard()); return regulationDto; } private String formatCoordinate(Double value) { if (value == null) { return "0"; } return BigDecimal.valueOf(value) .multiply(BigDecimal.valueOf(1_000_000L)) .setScale(0, RoundingMode.HALF_UP) .toPlainString(); } private String formatNumber(Number value, int scale) { if (value == null) { return "0"; } return BigDecimal.valueOf(value.doubleValue()) .setScale(scale, RoundingMode.HALF_UP) .toPlainString(); } private List parseVehicleIds(String idStr) { if (StringUtils.isBlank(idStr)) { return Collections.emptyList(); } return Arrays.stream(idStr.split(",")) .map(String::trim) .filter(StringUtils::isNotBlank) .map(Long::valueOf) .collect(Collectors.toList()); } private VehiclePosition findVehiclePosition(String vin) { if (StringUtils.isBlank(vin)) { return null; } return mongoTemplate.findOne(new Query(Criteria.where("vin").is(vin)), VehiclePosition.class); } private void pushVehicleOperateLoginToMinistry(XpVehicle xpVehicle, String scene) { if (xpVehicle == null || xpVehicle.getOfficialPartnerId() != null) { return; } String vin = SensitiveDataCryptoUtil.decryptIfPossible(xpVehicle.getVehicleVin()); try { RegulationVehicleOperateLoginInfoDTO regulationDto = buildRegulationVehicleOperateLoginInfoDto(xpVehicle, vin); String response = ministryOfTransport.pushVehicleOperateLoginInfo(regulationDto); log.info("{}后上报交通部CLYYSX完成, vehicleId={}, vin={}, response={}", scene, xpVehicle.getId(), vin, response); } catch (Exception e) { log.error("{}后上报交通部CLYYSX失败, vehicleId={}, vin={}", scene, xpVehicle.getId(), vin, e); } } private void pushVehicleOperateLogoutToMinistry(XpVehicle xpVehicle, String scene) { if (xpVehicle == null || xpVehicle.getOfficialPartnerId() != null) { return; } String vin = SensitiveDataCryptoUtil.decryptIfPossible(xpVehicle.getVehicleVin()); try { RegulationVehicleOperateLogoutInfoDTO regulationDto = buildRegulationVehicleOperateLogoutInfoDto(xpVehicle, vin); String response = ministryOfTransport.pushVehicleOperateLogoutInfo(regulationDto); log.info("{}后上报交通部CLYYXX完成, vehicleId={}, vin={}, response={}", scene, xpVehicle.getId(), vin, response); } catch (Exception e) { log.error("{}后上报交通部CLYYXX失败, vehicleId={}, vin={}", scene, xpVehicle.getId(), vin, e); } } @Override public void reportVehicleOperateLogoutToMinistry(List vehicles, String scene) { if (CollectionUtils.isEmpty(vehicles)) { return; } for (XpVehicle vehicle : vehicles) { // pushVehicleOperateLogoutToMinistry(vehicle, scene); } } private RegulationVehicleOperateLoginInfoDTO buildRegulationVehicleOperateLoginInfoDto(XpVehicle xpVehicle, String vin) { RegulationVehicleOperateLoginInfoDTO regulationDto = new RegulationVehicleOperateLoginInfoDTO(); VehiclePosition vehiclePosition = findVehiclePosition(vin); GeoJsonPoint point = vehiclePosition == null ? null : vehiclePosition.getPoint(); LocalDateTime now = LocalDateTime.now(); regulationDto.setAddress(ministryTransportProperties.getOperateAddressCode()); regulationDto.setIdentifier(ministryTransportProperties.getOperateIdentifier()); regulationDto.setDriverName(ministryTransportProperties.getDriverName()); regulationDto.setDriverIDCard(ministryTransportProperties.getDriverIdCard()); regulationDto.setDriverCertCard(ministryTransportProperties.getDriverCertCard()); regulationDto.setDriverPhone(ministryTransportProperties.getDriverPhone()); regulationDto.setLicenseId(ministryTransportProperties.getLicenseId()); regulationDto.setVehicleNo(trimToNull(xpVehicle.getPlateNumber())); regulationDto.setPlateColor(getPlateColor(xpVehicle.getPlateNumber())); regulationDto.setLoginTime(formatDate(now)); regulationDto.setLongitude(formatCoordinate(point == null ? null : point.getX())); regulationDto.setLatitude(formatCoordinate(point == null ? null : point.getY())); regulationDto.setEncrypt("1"); return regulationDto; } private RegulationVehicleOperateLogoutInfoDTO buildRegulationVehicleOperateLogoutInfoDto(XpVehicle xpVehicle, String vin) { RegulationVehicleOperateLogoutInfoDTO regulationDto = new RegulationVehicleOperateLogoutInfoDTO(); VehiclePosition vehiclePosition = findVehiclePosition(vin); GeoJsonPoint point = vehiclePosition == null ? null : vehiclePosition.getPoint(); LocalDateTime now = LocalDateTime.now(); regulationDto.setAddress(ministryTransportProperties.getOperateAddressCode()); regulationDto.setIdentifier(ministryTransportProperties.getOperateIdentifier()); regulationDto.setDriverName(ministryTransportProperties.getDriverName()); regulationDto.setDriverIDCard(ministryTransportProperties.getDriverIdCard()); regulationDto.setDriverPhone(ministryTransportProperties.getDriverPhone()); regulationDto.setDriverCertCard(ministryTransportProperties.getDriverCertCard()); regulationDto.setLicenseId(ministryTransportProperties.getLicenseId()); regulationDto.setVehicleNo(trimToNull(xpVehicle.getPlateNumber())); regulationDto.setPlateColor(getPlateColor(xpVehicle.getPlateNumber())); regulationDto.setLoginTime(formatDate(defaultIfNull(xpVehicle.getUpdateTime(), now))); regulationDto.setLogoutTime(formatDate(now)); regulationDto.setLongitude(formatCoordinate(point == null ? null : point.getX())); regulationDto.setLatitude(formatCoordinate(point == null ? null : point.getY())); regulationDto.setEncrypt("1"); regulationDto.setDriveCount("0"); regulationDto.setDriveMile("0"); regulationDto.setDriveTime("0"); regulationDto.setPrice("0"); regulationDto.setFactPrice("0"); return regulationDto; } @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); // pushVehicleBaseInfoToMinistry(vehicle, // SensitiveDataCryptoUtil.decryptIfPossible(vehicle.getVehicleVin()), // vehicle.getOfficialPartnerId(), // "完善车辆资料"); // pushVehicleInsuranceInfoToMinistry(vehicle, // vehicle.getOfficialPartnerId(), // "完善车辆资料"); } /** * 校验已填写的时间字段是否在有效范围内(未过期) */ 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) { List vehicleIds = parseVehicleIds(idStr); if (CollectionUtils.isEmpty(vehicleIds)) { return; } // 查询车辆完整信息 List vehicles = this.listByIds(vehicleIds); 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, vehicleIds) .set(XpVehicle::getVehicleType, 2) .set(XpVehicle::getOperatingStatus, 1); boolean updated = this.update(wrapper); if (updated) { for (XpVehicle vehicle : vehicles) { // pushVehicleOperateLoginToMinistry(vehicle, "批量开始运营"); } } } @Override public void batchStock(String idStr) { List vehicleIds = parseVehicleIds(idStr); if (CollectionUtils.isEmpty(vehicleIds)) { return; } List vehicles = this.listByIds(vehicleIds).stream() .filter(vehicle -> Integer.valueOf(2).equals(vehicle.getVehicleType())) .collect(Collectors.toList()); // 批量将运营车辆转为在库状态(vehicleType: 2→1) LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); wrapper.in(XpVehicle::getId, vehicleIds) .set(XpVehicle::getVehicleType, 1); boolean updated = this.update(wrapper); if (updated) { // reportVehicleOperateLogoutToMinistry(vehicles, "批量收车下线"); } } @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); List collect = page.getRecords().stream().map(XpVehicle::getVehicleModel).collect(Collectors.toList()); List vehicleModels = xpVehicleModelClient.listByIds(collect).getData(); for (XpVehicle record : page.getRecords()) { if(null != record && StringUtils.isNotEmpty(record.getVehicleModel())){ XpVehicleModel xpVehicleModel = vehicleModels.stream().filter(item -> item.getId().toString().equals(record.getVehicleModel())).findFirst().orElse(null); record.setVehicleModelName(null != xpVehicleModel ? xpVehicleModel.getName() : ""); } } decryptVehicles(page.getRecords()); //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); decryptVehicles(list); //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); } String vin = SensitiveDataCryptoUtil.decryptIfPossible(xpVehicle.getVehicleVin()); if(null == xpVehicle.getOfficialPartnerId()){ try { VehicleTemperatureDto vehicleTemperatureDto = vehicleControlUtil.queryVehicleTemperature(uid, vin); log.info("查询车辆温度成功:{}", vehicleTemperatureDto); return vehicleTemperatureDto; } catch (Exception e) { log.error("查询车辆温度失败", e); return new VehicleTemperatureDto(); } }else{ //流量接入-车辆控制推送 合作商查询车辆空调温度 VehicleTemperatureDto data = officialPartnerUtil.getAirConditioningTemperature(xpVehicle.getOfficialPartnerId(), vin).getData(); return data; } } @Override public Boolean queryTrunkOpenStatusByOrderId(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); } String vin = SensitiveDataCryptoUtil.decryptIfPossible(xpVehicle.getVehicleVin()); try { return vehicleControlUtil.queryTrunkDoorStateSync(Long.parseLong(uid), vin, reportEnable); } catch (Exception e) { log.error("查询车门状态失败", e); return false; } } @Override public PageInfo getVehiclePageList(VehicleSelectPageQuery query) { Page page = new Page<>(query.getPageNum(), query.getPageSize()); page = baseMapper.getVehiclePageList(page, query.getPlateNumber(), query.getFleetName()); List collect = page.getRecords().stream().map(XpVehicleVO::getVehicleModel).collect(Collectors.toList()); if(!collect.isEmpty()){ List vehicleModels = xpVehicleModelClient.listByIds(collect).getData(); for (XpVehicleVO record : page.getRecords()) { if(StringUtils.isNotEmpty(record.getVehicleModel())){ Optional first = vehicleModels.stream().filter(s -> s.getId().toString().equals(record.getVehicleModel())).findFirst(); if(first.isPresent()){ record.setVehicleModelName(first.get().getName()); } } } } PageInfo pageInfo = new PageInfo<>(); pageInfo.setTotal(page.getTotal()); pageInfo.setRecords(page.getRecords()); return pageInfo; } @Override public PageInfo getTargetedVehiclePageList(VehicleSelectPageQuery query) { //获取所有定向服务中的车辆id List ids = targetedServiceClient.getAllTargetedServiceVehicle().getData(); Page page = new Page<>(query.getPageNum(), query.getPageSize()); page = baseMapper.getTargetedVehiclePageList(page, query.getPlateNumber(), query.getFleetName(), ids); List collect = page.getRecords().stream().map(XpVehicleVO::getVehicleModel).collect(Collectors.toList()); if(!collect.isEmpty()){ List vehicleModels = xpVehicleModelClient.listByIds(collect).getData(); for (XpVehicleVO record : page.getRecords()) { if(StringUtils.isNotEmpty(record.getVehicleModel())){ Optional first = vehicleModels.stream().filter(s -> s.getId().toString().equals(record.getVehicleModel())).findFirst(); if(first.isPresent()){ record.setVehicleModelName(first.get().getName()); } } } } PageInfo pageInfo = new PageInfo<>(); pageInfo.setTotal(page.getTotal()); pageInfo.setRecords(page.getRecords()); return pageInfo; } @Override public Boolean getVehicleTrunkOpenStatus(String uid, Long id) { XpVehicle xpVehicle = this.getById(id); try { String vin = SensitiveDataCryptoUtil.decryptIfPossible(xpVehicle.getVehicleVin()); return vehicleControlUtil.queryTrunkDoorStateSync(Long.parseLong(uid), vin, reportEnable); } catch (Exception e) { log.error("查询车辆后备箱失败", e); return false; } } /** * 获取服务车辆列表 * * @param dto * @return */ @Override public List getServiceVehicleList(ServiceVehicleListDto dto) { PageInfo pageInfo = new PageInfo<>(dto.getPageNo(), dto.getPageSize()); PageInfo page = this.page(pageInfo, new QueryWrapper().eq("vehicle_type", 2) .eq("status", 2).eq("operating_status", 1) .eq("is_deleted", 0).like(StringUtils.isNotBlank(dto.getPlateNumber()), "plate_number", dto.getPlateNumber()) .in(StringUtils.isNotBlank(dto.getVin()), "vehicle_vin", SensitiveDataCryptoUtil.queryValues(dto.getVin())) .last(" order by create_time desc")); decryptVehicles(page.getRecords()); return page.getRecords(); } /** * 运维APP解锁车门和开关后备箱 * * @param dto * @return */ @Override public Integer unlockDoor(UnlockAppDTO dto, XpOperationsUser xpOperationsUser) { XpVehicle xpVehicle = this.getById(dto.getVehicleId()); // 判断距离是否在范围之内 15米 String vin = SensitiveDataCryptoUtil.decryptIfPossible(xpVehicle.getVehicleVin()); VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(vin)), 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 > distanceThreshold) { 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); } } //先将运维人员和车辆绑定 String phoneNumber = xpOperationsUser.getPhoneNumber(); UcUserInfo userByMobile = userResourcesClient.getUserByMobile(phoneNumber); Long uid = userByMobile.getUid(); AddTaxiPassengerVo addVo = new AddTaxiPassengerVo(); addVo.setVin(vin); addVo.setUid(userByMobile.getUid().toString()); addVo.setOrderNo(phoneNumber); addVo.setAppId(appid); userVehicleClient.addTaxiPassenger(addVo); log.info("已绑定运维与新车辆关系,电话号码: {}, VIN: {}", phoneNumber, vin); try { // 车门解锁、后备箱解锁 VehiclesCommandVo vehiclesCommandVo = new VehiclesCommandVo(); vehiclesCommandVo.setVin(vin); if ("door".equals(dto.getDoorType())) { // 开启车门 判断是否为P档 Integer gearLevel = redisService.getCacheObject(CacheConstants.GEAR_LEVEL + xpVehicle.getXpuId()); log.info("开启车门当前车辆档位: {} {}", xpVehicle.getXpuId(), gearLevel); if (gearLevel == null || gearLevel != 4) { throw new ServiceException("车辆非P档,无法开启车门", I18nKeyConstant.VEHICLE_NOT_P_DOOR); } vehiclesCommandVo.setCommandCode(VehiclesCommandEnum.DOOR_UNLOCK.getCommand()); } else { // 开启后备箱 判断是否为P档 Integer gearLevel = redisService.getCacheObject(CacheConstants.GEAR_LEVEL + xpVehicle.getXpuId()); log.info("开启后备箱当前车辆档位: {} {}", xpVehicle.getXpuId(), gearLevel); if (gearLevel == null || gearLevel != 4) { throw new ServiceException("车辆非P档,无法操作后备箱", I18nKeyConstant.VEHICLE_NOT_P_GEAR); } //查询后备箱开关状态(打开状态=true,关闭状态=false) Boolean hasKey = vehicleControlUtil.queryTrunkDoorStateSync(uid, xpVehicle.getVehicleVin(), reportEnable); //打开状态 if (hasKey) { vehiclesCommandVo.setCommandCode(VehiclesCommandEnum.DOOR_TRUNK_CLOSE.getCommand()); } else { //关闭状态 vehiclesCommandVo.setCommandCode(VehiclesCommandEnum.DOOR_TRUNK_OPEN.getCommand()); } } // { // "code": 0, // "data": { // "msgId": "1234556" // }, // "msg": "", // "desc": "成功" //} String msgId = vehicleControlUtil.vehiclesCommand(vehiclesCommandVo, uid); log.info("车辆控制结果:{}", msgId); if (StringUtils.isEmpty(msgId)) { return 0; } else { return 1; } }catch (Exception e){ e.printStackTrace(); }finally { //解绑运维与车辆的绑定关系 CancelTaxiPassengerVo cancelTaxiPassengerVo = new CancelTaxiPassengerVo(); cancelTaxiPassengerVo.setVin(xpVehicle.getVehicleVin()); cancelTaxiPassengerVo.setUid(userByMobile.getUid().toString()); cancelTaxiPassengerVo.setOrderNo(phoneNumber); cancelTaxiPassengerVo.setAppId(appid); userVehicleClient.cancelTaxiPassenger(cancelTaxiPassengerVo); } return 0; } /** * 根据订单id获取空调开关状态 * @param orderId * @param uid * @return */ @Override public Boolean queryVehicleAirConditionerSwitch(Long orderId, String uid) { XpOrder xpOrder = xpOrderClient.queryOrderById(orderId).getData(); if(null == xpOrder){ return false; } XpVehicle vehicle = this.getById(xpOrder.getVehicleId()); String vin = vehicle.getVehicleVin(); if(null == vehicle.getOfficialPartnerId()){ // 1. 创建车辆控制指令 VehiclesCommandVo commandVo = new VehiclesCommandVo(); commandVo.setVin(vin); commandVo.setCommandCode(VehiclesCommandEnum.STATUS_REPORT_NOW.getCommand()); // 2. 下发指令获取消息ID String msgId = vehicleControlUtil.vehiclesCommand(commandVo, Long.valueOf(uid)); if (StrUtil.isEmpty(msgId)) { throw new BaseException("获取消息ID失败: " + msgId); } log.info("同步查询车辆空调状态成功,开始等到查询回调结果"); try { Thread.sleep(5000); } catch (InterruptedException e) { throw new RuntimeException(e); } String cacheObject = redisService.getCacheObject("HVAC:" + msgId); log.info("查询空调状态:{}", cacheObject); if(StringUtils.isEmpty(cacheObject)){ return false; } JSONObject jsonObject = JSON.parseObject(cacheObject); Integer hvac_on = jsonObject.getInteger("hvac_on"); return hvac_on == 1; }else{ //流量接入-车辆控制推送 查询合作商车辆空调状态 Integer data = officialPartnerUtil.queryVehicleAirConditionerSwitch(vehicle.getOfficialPartnerId(), vehicle.getVehicleVin()).getData(); return data == 1; } } /** * 开关空调 * @param dto * @param uid * @return */ @Override public Integer airConditionerUnlock(AirConditionerUnlockDTO dto, String uid) { XpOrder xpOrder = xpOrderClient.queryOrderById(dto.getOrderId()).getData(); if(null == xpOrder){ return 0; } Boolean open = queryVehicleAirConditionerSwitch(dto.getOrderId(), uid); XpVehicle vehicle = this.getById(xpOrder.getVehicleId()); VehiclesCommandVo vehiclesCommandVo = new VehiclesCommandVo(); vehiclesCommandVo.setVin(vehicle.getVehicleVin()); vehiclesCommandVo.setCommandCode(open ? VehiclesCommandEnum.HVAC_OFF.getCommand() : VehiclesCommandEnum.HVAC_ON.getCommand()); String msgId = vehicleControlUtil.vehiclesCommand(vehiclesCommandVo, Long.valueOf(uid)); log.info("空调控制结果:{}", msgId); open = queryVehicleAirConditionerSwitch(dto.getOrderId(), uid); return open ? 1 : 0; } /** * 获取车辆门锁状态 * @param orderId * @return */ @Override public VehicleDoorStatusVo getVehicleDoorStatus(Long orderId) { XpOrder xpOrder = xpOrderClient.queryOrderById(orderId).getData(); if(null == xpOrder){ return null; } XpVehicle vehicle = this.getById(xpOrder.getVehicleId()); String vin = vehicle.getVehicleVin(); // 1. 创建车辆控制指令 VehiclesCommandVo commandVo = new VehiclesCommandVo(); commandVo.setVin(vin); commandVo.setCommandCode(VehiclesCommandEnum.STATUS_REPORT_NOW.getCommand()); // 2. 下发指令获取消息ID String msgId = vehicleControlUtil.vehiclesCommand(commandVo, xpOrder.getAppUserId()); if (StrUtil.isEmpty(msgId)) { throw new BaseException("获取消息ID失败: " + msgId); } log.info("同步查询车辆空调状态成功,开始等到查询回调结果"); try { Thread.sleep(5000); } catch (InterruptedException e) { throw new RuntimeException(e); } VehicleDoorStatusVo vo = new VehicleDoorStatusVo(); String cacheObject = redisService.getCacheObject("DOOR:" + msgId); if(StringUtils.isNotEmpty(cacheObject)){ JSONObject jsonObject = JSON.parseObject(cacheObject); Integer driver_door_ajar_st = jsonObject.getInteger("driver_door_ajar_st"); Integer passenger_door_ajar_st = jsonObject.getInteger("passenger_door_ajar_st"); Integer rear_left_door_ajar_st = jsonObject.getInteger("rear_left_door_ajar_st"); Integer rear_right_door_ajar_st = jsonObject.getInteger("rear_right_door_ajar_st"); Integer trunk_ajar_st_new = jsonObject.getInteger("trunk_ajar_st_new"); Integer bonnet_ajar_st = jsonObject.getInteger("bonnet_ajar_st"); vo.setFrontLeftDoor(driver_door_ajar_st); vo.setFrontRightDoor(passenger_door_ajar_st); vo.setRearLeftDoor(rear_left_door_ajar_st); vo.setRearRightDoor(rear_right_door_ajar_st); vo.setTrunk(trunk_ajar_st_new); vo.setFrunk(bonnet_ajar_st); } String hvac = redisService.getCacheObject("HVAC:" + msgId); if(StringUtils.isNotEmpty(hvac)){ JSONObject hvacJsonObject = JSON.parseObject(hvac); Integer hvac_on = hvacJsonObject.getInteger("hvac_on"); Double hvac_inner_temp_float = hvacJsonObject.getDouble("hvac_inner_temp_float"); Double hvac_temp_float = hvacJsonObject.getDouble("hvac_temp_float"); Double rear_left_hvac_temp_float = hvacJsonObject.getDouble("rear_left_hvac_temp_float"); Double passenger_hvac_temp_float = hvacJsonObject.getDouble("passenger_hvac_temp_float"); vo.setHvacOn(hvac_on); vo.setHvacInnerTempFloat(hvac_inner_temp_float); vo.setHvacTempFloat(hvac_temp_float); vo.setRearLeftHvacTempFloat(rear_left_hvac_temp_float); vo.setPassengerHvacTempFloat(passenger_hvac_temp_float); } String seat = redisService.getCacheObject("SEAT:" + msgId); if(StringUtils.isNotEmpty(seat)){ JSONObject seatJsonObject = JSON.parseObject(seat); Integer driver_seat_state = seatJsonObject.getInteger("driver_seat_state"); Integer psngr_seat_occupancy_status = seatJsonObject.getInteger("psngr_seat_occupancy_status"); Integer sec_row_left_seat_occupancy_status = seatJsonObject.getInteger("sec_row_left_seat_occupancy_status"); Integer sec_row_mid_seat_occupancy_status = seatJsonObject.getInteger("sec_row_mid_seat_occupancy_status"); Integer sec_row_right_seat_occupancy_status = seatJsonObject.getInteger("sec_row_right_seat_occupancy_status"); Integer third_row_left_seat_occupancy_status = seatJsonObject.getInteger("third_row_left_seat_occupancy_status"); Integer third_row_mid_seat_occupancy_status = seatJsonObject.getInteger("third_row_mid_seat_occupancy_status"); Integer third_row_right_seat_occupancy_status = seatJsonObject.getInteger("third_row_right_seat_occupancy_status"); Integer drv_seat_belt_buckle_st = seatJsonObject.getInteger("drv_seat_belt_buckle_st"); Integer psngr_seat_belt_buckle_st = seatJsonObject.getInteger("psngr_seat_belt_buckle_st"); Integer sec_row_lt_seat_belt_buckle_st = seatJsonObject.getInteger("sec_row_lt_seat_belt_buckle_st"); Integer sec_row_mid_seat_belt_buckle_st = seatJsonObject.getInteger("sec_row_mid_seat_belt_buckle_st"); Integer sec_row_rt_seat_belt_buckle_st = seatJsonObject.getInteger("sec_row_rt_seat_belt_buckle_st"); Integer trd_row_lt_seat_belt_buckle_st = seatJsonObject.getInteger("trd_row_lt_seat_belt_buckle_st"); Integer trd_row_mid_seat_belt_buckle_st = seatJsonObject.getInteger("trd_row_mid_seat_belt_buckle_st"); Integer trd_row_rt_seat_belt_buckle_st = seatJsonObject.getInteger("trd_row_rt_seat_belt_buckle_st"); vo.setDriverSeatState(driver_seat_state); vo.setPsngrSeatOccupancyStatus(psngr_seat_occupancy_status); vo.setSecRowLeftSeatOccupancyStatus(sec_row_left_seat_occupancy_status); vo.setSecRowMidSeatOccupancyStatus(sec_row_mid_seat_occupancy_status); vo.setSecRowRightSeatOccupancyStatus(sec_row_right_seat_occupancy_status); vo.setThirdRowLeftSeatOccupancyStatus(third_row_left_seat_occupancy_status); vo.setThirdRowMidSeatOccupancyStatus(third_row_mid_seat_occupancy_status); vo.setThirdRowRightSeatOccupancyStatus(third_row_right_seat_occupancy_status); vo.setDrvSeatBeltBuckleSt(drv_seat_belt_buckle_st); vo.setPsngrSeatBeltBuckleSt(psngr_seat_belt_buckle_st); vo.setSecRowLtSeatBeltBuckleSt(sec_row_lt_seat_belt_buckle_st); vo.setSecRowMidSeatBeltBuckleSt(sec_row_mid_seat_belt_buckle_st); vo.setSecRowRtSeatBeltBuckleSt(sec_row_rt_seat_belt_buckle_st); vo.setTrdRowLtSeatBeltBuckleSt(trd_row_lt_seat_belt_buckle_st); vo.setTrdRowMidSeatBeltBuckleSt(trd_row_mid_seat_belt_buckle_st); vo.setTrdRowRtSeatBeltBuckleSt(trd_row_rt_seat_belt_buckle_st); } String charge = redisService.getCacheObject("CHARGE:" + msgId); if(StringUtils.isNotEmpty(charge)){ JSONObject chargeJsonObject = JSON.parseObject(charge); Integer slow_charge_port_status = chargeJsonObject.getInteger("slow_charge_port_status"); Integer fast_charge_port_status = chargeJsonObject.getInteger("fast_charge_port_status"); vo.setSlowChargePortStatus(slow_charge_port_status); vo.setFastChargePortStatus(fast_charge_port_status); } return vo; } /** * 上报车辆实时位置 * @param dto */ @Override public void reportVehicleLocation(VehiclePositionDto dto, Integer officialPartnerId) { if(StringUtils.isEmpty(dto.getVin())){ throw new ServiceException("无效数据"); } if(null == dto.getLatitude() || null == dto.getLongitude()){ throw new ServiceException("无效数据"); } XpVehicle vehicle = this.getOne(new QueryWrapper().in("vehicle_vin", SensitiveDataCryptoUtil.queryValues(dto.getVin())).eq("is_deleted", 0)); if(null == vehicle){ throw new ServiceException("无效数据"); } if(!Objects.equals(vehicle.getOfficialPartnerId(), officialPartnerId)){ throw new ServiceException("无效数据"); } VehiclePosition vehiclePosition = mongoTemplate.findOne(Query.query(Criteria.where("vin").is(dto.getVin())), VehiclePosition.class); if(null == vehiclePosition){ vehiclePosition = new VehiclePosition(); BeanUtil.copyProperties(dto, vehiclePosition); vehiclePosition.setPoint(new GeoJsonPoint(dto.getLongitude(), dto.getLatitude())); vehiclePosition.setAddTime(System.currentTimeMillis()); mongoTemplate.save(vehiclePosition); }else{ Update update = new Update() .set("point", new GeoJsonPoint(dto.getLongitude(), dto.getLatitude())) .set("course", dto.getCourse()) .set("alt", dto.getAlt()) .set("speed", dto.getSpeed()) .set("currentElectricity", dto.getCurrentElectricity()) .set("mileageRange", dto.getMileageRange()) .set("orderId", dto.getOrderId()) .set("totalOdometer", dto.getTotalOdometer()) .set("addTime", System.currentTimeMillis()); mongoTemplate.updateFirst(new Query(Criteria.where("vin").is(dto.getVin())), update, VehiclePosition.class); } // pushDriverPositionInfoToMinistry(vehicle, dto, "上报车辆实时位置"); Long orderId = dto.getOrderId(); Double longitude = dto.getLongitude(); Double latitude = dto.getLatitude(); //调用推送服务接口,给乘客推送剩余轨迹和里程时长 if (null != orderId) { PushOrderRouteDTO dto1 = new PushOrderRouteDTO(); dto1.setMessageType(PushMessageTypeEnum.ORDER_ROUTE.getDesc()); dto1.setOrderId(orderId); dto1.setRouteType(1); dto1.setLongitude(longitude); dto1.setLatitude(latitude); dto1.setDuration(dto.getDuration()); dto1.setDistance(dto.getDistance()); dto1.setSpeed(dto.getSpeed()); dto1.setDirection(dto.getCourse().intValue()); OrderTrajectoryVo orderTrajectory = new OrderTrajectoryVo(); orderTrajectory.setOrderId(orderId); orderTrajectory.setLocation(longitude + "," + latitude); orderTrajectory.setDirection(dto.getCourse().intValue()); orderTrajectory.setSpeed((double) dto.getSpeed()); orderTrajectory.setLocatetime(System.currentTimeMillis()); orderTrajectory.setCreateTime(new Date()); //只有乘客订单需要推送剩余导航数据 xpPushMessageClient.pushOrderRoute(dto1);//存储历史轨迹 // 给连环派单的用户推送 String json = redisService.getCacheObject(CacheConstants.nextOrder(orderId + "")); if (com.xiaopeng.common.core.utils.StringUtils.isNotEmpty(json)){ JSONObject jsonObject = JSON.parseObject(json); dto1.setOrderId(jsonObject.getLong("orderId")); dto1.setEndLongitude(jsonObject.getDouble("endLongitude")); dto1.setEndLatitude(jsonObject.getDouble("endLatitude")); xpPushMessageClient.pushOrderRoute(dto1); } xpOrderClient.saveVehicleLocation(orderTrajectory); } } @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); } } private void decryptVehicleVos(Collection vehicles) { if (vehicles != null) { vehicles.forEach(this::decryptVehicle); } } }