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 extends XpVehicle> vehicles) {
if (vehicles != null) {
vehicles.forEach(this::decryptVehicle);
}
}
private void decryptVehicleVos(Collection vehicles) {
if (vehicles != null) {
vehicles.forEach(this::decryptVehicle);
}
}
}