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.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.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.map.AdcFencePushClient;
|
import com.xiaopeng.api.feignClient.map.XpMapClient;
|
import com.xiaopeng.api.feignClient.operation.XpElectronicClient;
|
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.operation.XpElectronicFence;
|
import com.xiaopeng.api.model.operation.XpElectronicFenceArea;
|
import com.xiaopeng.api.model.order.XpOrder;
|
import com.xiaopeng.api.model.order.XpTestOrder;
|
import com.xiaopeng.api.model.vehicleConvoy.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.operation.ElectronicFenceDetailVo;
|
import com.xiaopeng.api.vo.operation.RouteVo;
|
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.exception.ServiceException;
|
import com.xiaopeng.common.core.exception.base.BaseException;
|
import com.xiaopeng.common.core.utils.GeodesyUtil;
|
import com.xiaopeng.common.core.web.page.PageInfo;
|
import com.xiaopeng.common.redis.service.RedisService;
|
import com.xiaopeng.evi.oss.service.AliOssService;
|
import com.xiaopeng.vehicleConvoy.constant.I18nKeyConstant;
|
import com.xiaopeng.vehicleConvoy.mapper.XpFleetOperationsMapper;
|
import com.xiaopeng.vehicleConvoy.mapper.XpVehicleMapper;
|
import com.xiaopeng.vehicleConvoy.model.dto.*;
|
import com.xiaopeng.vehicleConvoy.model.importExcel.XpVehicleImportExcel;
|
import com.xiaopeng.vehicleConvoy.model.query.*;
|
import com.xiaopeng.vehicleConvoy.model.vo.XpVehicleVO;
|
import com.xiaopeng.vehicleConvoy.service.AsyncVehicleStateService;
|
import com.xiaopeng.vehicleConvoy.service.IXpFleetVehicleService;
|
import com.xiaopeng.vehicleConvoy.service.IXpVehicleAnnualRecordService;
|
import com.xiaopeng.vehicleConvoy.service.IXpVehicleService;
|
import com.xiaopeng.vehicleConvoy.util.VehicleControlUtil;
|
import lombok.extern.slf4j.Slf4j;
|
import org.apache.commons.lang3.StringUtils;
|
import org.apache.shardingsphere.transaction.annotation.ShardingSphereTransactionType;
|
import org.apache.shardingsphere.transaction.core.TransactionType;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.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.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.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;
|
|
/**
|
* <p>
|
* 车辆管理 服务实现类
|
* </p>
|
*
|
* @author mitao
|
* @since 2026-02-03
|
*/
|
@Slf4j
|
@Service
|
public class XpVehicleServiceImpl extends ServiceImpl<XpVehicleMapper, XpVehicle> 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;
|
|
@Resource
|
private UserResourcesClient userResourcesClient;
|
@Resource
|
private AsyncVehicleStateService asyncVehicleStateService;
|
|
@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<XpVehicle> listByIds(List<Long> ids) {
|
if (CollectionUtils.isEmpty(ids)) {
|
return new ArrayList<>();
|
}
|
//查询车型和 车队信息
|
return super.listByIds(ids);
|
}
|
|
/**
|
*
|
* @param orderId
|
* @param orderType 订单类型(1=用户单,2=测试单)
|
* @return
|
*/
|
@Override
|
public List<XpVehicleVO> 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<XpOrder> 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<XpElectronicFenceArea> 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<XpVehicleVO> list = baseMapper.findList(query);
|
//获取车的位置 和订单的位置 并计算 距离 和 时间
|
if(!list.isEmpty()){
|
for (XpVehicle xpVehicle : list) {
|
|
VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(xpVehicle.getVehicleVin())), 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<RouteVo> 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());
|
}
|
}
|
}
|
}
|
// 按预计到达时间(秒)升序排序
|
list.sort(Comparator.comparing(XpVehicle::getArrivalTimeSecond, Comparator.nullsLast(Comparator.naturalOrder())));
|
}
|
return list;
|
}
|
|
@Override
|
public PageInfo<XpVehicleVO> pageList(VehicleMapQuery query) {
|
PageInfo<XpVehicleVO> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
|
// 查询用户所在车队的车辆id集合
|
List<XpFleetOperations> xpFleetOperations = xpFleetOperationsMapper.selectList(Wrappers.lambdaQuery(XpFleetOperations.class)
|
.eq(XpFleetOperations::getOperationsUserId, query.getOperationsUserId()));
|
if (CollectionUtils.isEmpty(xpFleetOperations)) {
|
return pageInfo;
|
}
|
List<Long> fleetIds = xpFleetOperations.stream().map(XpFleetOperations::getFleetId).collect(Collectors.toList());
|
|
// 查询关联车辆
|
List<XpFleetVehicle> xpFleetVehicles = xpFleetVehicleService.list(Wrappers.lambdaQuery(XpFleetVehicle.class)
|
.in(XpFleetVehicle::getFleetId, fleetIds));
|
if (CollectionUtils.isEmpty(xpFleetVehicles)) {
|
return pageInfo;
|
}
|
|
List<Long> 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<XpVehicleVO> list = this.baseMapper.pageList(pageInfo, query);
|
if (CollectionUtils.isEmpty(list)) {
|
return pageInfo;
|
}
|
|
for (XpVehicleVO xpVehicleVO : list) {
|
// 查询车辆的续航里程
|
VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(xpVehicleVO.getVehicleVin())), VehiclePosition.class);
|
if (Objects.nonNull(vehiclePosition)) {
|
xpVehicleVO.setMileageRange(vehiclePosition.getMileageRange());
|
xpVehicleVO.setCurrentElectricity(vehiclePosition.getCurrentElectricity());
|
GeoJsonPoint point = vehiclePosition.getPoint();
|
if (Objects.nonNull(point)) {
|
xpVehicleVO.setLongitude(point.getX());
|
xpVehicleVO.setLatitude(point.getY());
|
}
|
} else {
|
xpVehicleVO.setMileageRange(0F);
|
xpVehicleVO.setCurrentElectricity(0);
|
xpVehicleVO.setLongitude(104.056302);
|
xpVehicleVO.setLatitude(30.586688);
|
}
|
if (org.springframework.util.StringUtils.hasLength(xpVehicleVO.getVehiclePictures())) {
|
JSONObject jsonObject = JSONObject.parseObject(xpVehicleVO.getVehiclePictures());
|
if (jsonObject != null) {
|
String downloadUrl = null;
|
try {
|
// 1
|
downloadUrl = aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
|
} catch (Exception e) {
|
e.printStackTrace();
|
}
|
jsonObject.put("url", downloadUrl);
|
xpVehicleVO.setVehiclePictures(jsonObject.toJSONString());
|
}
|
}
|
}
|
|
pageInfo.setRecords(list);
|
return pageInfo;
|
}
|
|
@Override
|
public List<XpVehicle> listByPlateNumber(String plateNumber) {
|
if (StringUtils.isBlank(plateNumber)) {
|
return Collections.emptyList();
|
}
|
|
return lambdaQuery().like(XpVehicle::getPlateNumber, plateNumber).list();
|
}
|
|
@Override
|
public List<XpVehicle> listByFenceId(Long fenceId) {
|
if (fenceId == null) {
|
return Collections.emptyList();
|
}
|
return lambdaQuery().eq(XpVehicle::getElectronicId, fenceId).eq(XpVehicle::getOperatingStatus, 1).eq(XpVehicle::getState, 0).list();
|
}
|
|
@Override
|
public List<XpVehicle> listAllByFenceId(Long fenceId) {
|
if (fenceId == null) {
|
return Collections.emptyList();
|
}
|
return lambdaQuery().eq(XpVehicle::getElectronicId, fenceId).list();
|
}
|
|
@Override
|
public Integer unlock(UnlockAppletDTO dto) {
|
// 查询订单
|
XpOrder xpOrder = xpOrderClient.queryOrderById(dto.getOrderId()).getData();
|
if (Objects.isNull(xpOrder)) {
|
throw new ServiceException("未查询到有效订单信息", I18nKeyConstant.ORDER_NOT_EXISTS);
|
}
|
XpVehicle xpVehicle = this.getById(xpOrder.getVehicleId());
|
if (Objects.isNull(xpVehicle)) {
|
throw new ServiceException("未查询到有效车辆信息", I18nKeyConstant.VEHICLE_NOT_EXISTS);
|
}
|
// 判断距离是否在范围之内 15米
|
VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(xpVehicle.getVehicleVin())), VehiclePosition.class);
|
if (Objects.nonNull(vehiclePosition)) {
|
GeoJsonPoint point = vehiclePosition.getPoint();
|
if (Objects.nonNull(point)) {
|
Map<String, Double> 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);
|
}
|
}
|
// 车门解锁、后备箱解锁
|
VehiclesCommandVo vehiclesCommandVo = new VehiclesCommandVo();
|
vehiclesCommandVo.setVin(xpVehicle.getVehicleVin());
|
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_GEAR);
|
}
|
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(), xpVehicle.getVehicleVin(), 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;
|
}
|
}
|
|
@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);
|
}
|
// 设定温度
|
VehiclesCommandVo vehiclesCommandVo = new VehiclesCommandVo();
|
vehiclesCommandVo.setVin(xpVehicle.getVehicleVin());
|
vehiclesCommandVo.setCommandCode(VehiclesCommandEnum.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;
|
}
|
}
|
|
@Override
|
public void addVehicle(VehicleAddDTO dto) {
|
String normalizedVehicleCode = trimToNull(dto.getVehicleCode());
|
String normalizedVin = normalizeVin(dto.getVehicleVin());
|
String normalizedPlateNumber = trimToNull(dto.getPlateNumber());
|
String normalizedVehicleColor = trimToNull(dto.getVehicleColor());
|
validateAndNormalizeIdentityFields(normalizedVehicleCode, normalizedVin, normalizedPlateNumber, normalizedVehicleColor, null);
|
validateElectronicFenceExists(dto.getElectronicId());
|
|
dto.setVehicleCode(normalizedVehicleCode);
|
dto.setVehicleVin(normalizedVin);
|
dto.setPlateNumber(normalizedPlateNumber);
|
dto.setVehicleColor(normalizedVehicleColor);
|
|
// DTO转实体
|
XpVehicle xpVehicle = new XpVehicle();
|
BeanUtil.copyProperties(dto, xpVehicle);
|
|
// 设置默认值:在库车辆
|
xpVehicle.setVehicleType(1);
|
// 新增时状态为待完善
|
xpVehicle.setStatus(1);
|
|
// 设置入库时间
|
xpVehicle.setWarehousingTime(LocalDateTime.now());
|
//绑定XPUID
|
String vin = xpVehicle.getVehicleVin();
|
R<String> xpuIdByVin = xpuVinClient.getXpuIdByVin(vin);
|
if(200 != xpuIdByVin.getCode()) {
|
throw new ServiceException(xpuIdByVin.getMsg());
|
}
|
if (200 != xpuIdByVin.getCode()) {
|
throw new RuntimeException(xpuIdByVin.getMsg());
|
}
|
String xpuid = xpuIdByVin.getData();
|
xpVehicle.setXpuId(xpuid);
|
this.save(xpVehicle);
|
|
// ADC推送:新车绑定围栏时触发单车推送
|
if (dto.getElectronicId() != null && adcFencePushClient != null) {
|
try {
|
adcFencePushClient.pushFenceToVehicle(xpVehicle.getVehicleVin(), dto.getElectronicId());
|
log.info("[ADC推送] 新增车辆{}绑定围栏{},已触发推送", xpVehicle.getVehicleVin(), dto.getElectronicId());
|
} catch (Exception e) {
|
log.error("[ADC推送] 新增车辆{}绑定围栏{}推送失败,不影响主流程", xpVehicle.getVehicleVin(), 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);
|
}
|
}
|
|
@Override
|
public void editVehicle(VehicleEditDTO dto) {
|
XpVehicle existingVehicle = this.getById(dto.getId());
|
String normalizedVehicleCode = trimToNull(dto.getVehicleCode());
|
String normalizedVin = normalizeVin(dto.getVehicleVin());
|
String normalizedPlateNumber = trimToNull(dto.getPlateNumber());
|
String normalizedVehicleColor = trimToNull(dto.getVehicleColor());
|
validateAndNormalizeIdentityFields(normalizedVehicleCode, normalizedVin, normalizedPlateNumber, normalizedVehicleColor, dto.getId());
|
boolean electronicIdChanged = existingVehicle == null
|
|| !Objects.equals(existingVehicle.getElectronicId(), dto.getElectronicId());
|
if (electronicIdChanged) {
|
validateElectronicFenceExists(dto.getElectronicId());
|
}
|
|
dto.setVehicleCode(normalizedVehicleCode);
|
dto.setVehicleVin(normalizedVin);
|
dto.setPlateNumber(normalizedPlateNumber);
|
dto.setVehicleColor(normalizedVehicleColor);
|
|
// DTO转实体
|
XpVehicle xpVehicle = new XpVehicle();
|
BeanUtil.copyProperties(dto, xpVehicle);
|
boolean vinChanged = existingVehicle == null
|
|| !Objects.equals(existingVehicle.getVehicleVin(), xpVehicle.getVehicleVin());
|
if (vinChanged || existingVehicle == null || StringUtils.isBlank(existingVehicle.getXpuId())) {
|
// 仅在 VIN 变更或原 XPUID 为空时才重新绑定 XPUID,避免不必要的远程调用
|
String vin = xpVehicle.getVehicleVin();
|
R<String> 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());
|
}
|
this.updateById(xpVehicle);
|
|
// ADC推送:围栏绑定变更时触发单车推送
|
if (electronicIdChanged && dto.getElectronicId() != null && adcFencePushClient != null) {
|
try {
|
adcFencePushClient.pushFenceToVehicle(xpVehicle.getVehicleVin(), dto.getElectronicId());
|
log.info("[ADC推送] 编辑车辆{}变更围栏为{},已触发推送", xpVehicle.getVehicleVin(), dto.getElectronicId());
|
} catch (Exception e) {
|
log.error("[ADC推送] 编辑车辆{}变更围栏{}推送失败,不影响主流程", xpVehicle.getVehicleVin(), dto.getElectronicId(), e);
|
}
|
}
|
}
|
|
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<XpVehicle> duplicates = lambdaQuery()
|
.ne(excludeId != null, XpVehicle::getId, excludeId)
|
.and(w -> w.eq(XpVehicle::getVehicleCode, vehicleCode)
|
.or()
|
.eq(XpVehicle::getVehicleVin, 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(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<List<XpElectronicFence>> result = xpElectronicClient.listByIds(Collections.singleton(electronicId));
|
boolean exists = result != null
|
&& result.getData() != null
|
&& result.getData().stream().anyMatch(fence -> fence != null && electronicId.equals(fence.getId()));
|
if (!exists) {
|
throw new ServiceException("运营区域不存在", I18nKeyConstant.VEHICLE_ELECTRONIC_NOT_EXISTS, new Object[]{electronicId});
|
}
|
}
|
|
private static String normalizeVin(String vehicleVin) {
|
String trimmed = trimToNull(vehicleVin);
|
return trimmed == null ? null : trimmed.toUpperCase(Locale.ROOT);
|
}
|
|
private static String trimToNull(String value) {
|
if (value == null) {
|
return null;
|
}
|
String trimmed = value.trim();
|
return trimmed.isEmpty() ? null : trimmed;
|
}
|
|
@Override
|
@Transactional
|
@ShardingSphereTransactionType(TransactionType.BASE)
|
public void improveInfo(VehicleImproveInfoDTO dto) {
|
// 校验已填写的时间字段是否在有效范围内
|
validateTimeFields(dto);
|
|
// DTO转实体
|
XpVehicle xpVehicle = new XpVehicle();
|
BeanUtil.copyProperties(dto, xpVehicle);
|
|
// 更新保险、年检、运营资质等信息
|
this.updateById(xpVehicle);
|
// 查询更新后的完整信息
|
XpVehicle vehicle = this.getById(dto.getId());
|
if (vehicle == null) {
|
return;
|
}
|
|
if (Integer.valueOf(2).equals(dto.getSaveType())) {
|
// 保存并开始运营:校验保险、年检、运营区域、运营资质是否完善
|
validateForOperation(vehicle);
|
// 校验通过,直接转为运营状态
|
vehicle.setStatus(2);
|
vehicle.setVehicleType(2);
|
vehicle.setOperatingStatus(1);
|
this.updateById(vehicle);
|
} else {
|
// 仅保存:如果信息完善则状态改为待运营
|
if (isInfoComplete(vehicle)) {
|
vehicle.setStatus(2);
|
this.updateById(vehicle);
|
}
|
}
|
//保存年检记录
|
XpVehicleAnnualRecord xpVehicleAnnualRecord = BeanUtil.copyProperties(dto, XpVehicleAnnualRecord.class, "id");
|
xpVehicleAnnualRecord.setVehicleId(dto.getId());
|
xpVehicleAnnualRecordService.save(xpVehicleAnnualRecord);
|
}
|
|
/**
|
* 校验已填写的时间字段是否在有效范围内(未过期)
|
*/
|
private void validateTimeFields(VehicleImproveInfoDTO dto) {
|
LocalDateTime now = LocalDateTime.now();
|
List<String> 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<String> missingItems = new ArrayList<>();
|
Locale locale = LocaleContextHolder.getLocale();
|
|
// 校验保险信息
|
if (StringUtils.isBlank(vehicle.getInsuranceCompany())
|
|| StringUtils.isBlank(vehicle.getInsurancePhone())
|
|| vehicle.getInsuranceEndTime() == null
|
|| StringUtils.isBlank(vehicle.getPolicyInformation())) {
|
if ("zh".equals(locale.getLanguage())) {
|
missingItems.add("保险信息");
|
} else {
|
missingItems.add("Insurance information");
|
}
|
}
|
|
// 校验年检信息
|
if (vehicle.getAnnualInspectionDate() == null
|
|| StringUtils.isBlank(vehicle.getInspectionAttachments())) {
|
if ("zh".equals(locale.getLanguage())) {
|
missingItems.add("年检信息");
|
} else {
|
missingItems.add("Annual inspection information");
|
}
|
}
|
|
// 校验运营区域
|
if (vehicle.getElectronicId() == null) {
|
if ("zh".equals(locale.getLanguage())) {
|
missingItems.add("运营区域");
|
} else {
|
missingItems.add("Operation area");
|
}
|
}
|
|
// 校验运营资质
|
if (StringUtils.isBlank(vehicle.getOperationQualification())
|
|| vehicle.getQualificationExpireTime() == null) {
|
if ("zh".equals(locale.getLanguage())) {
|
missingItems.add("运营资质");
|
} else {
|
missingItems.add("Operation qualification");
|
}
|
}
|
|
if (!missingItems.isEmpty()) {
|
String detail;
|
if ("zh".equals(locale.getLanguage())) {
|
detail = String.join("、", missingItems);
|
} else {
|
detail = String.join("; ", missingItems);
|
}
|
throw new ServiceException("以下信息未完善,无法开始运营:" + detail,
|
I18nKeyConstant.VEHICLE_OPERATION_INFO_INCOMPLETE, new Object[]{detail});
|
}
|
}
|
|
/**
|
* 判断车辆信息是否已完善
|
*/
|
private boolean isInfoComplete(XpVehicle vehicle) {
|
return vehicle.getAnnualInspectionDate() != null
|
&& StringUtils.isNotBlank(vehicle.getInsuranceCompany())
|
&& StringUtils.isNotBlank(vehicle.getInsurancePhone())
|
&& vehicle.getInsuranceEndTime() != null
|
&& StringUtils.isNotBlank(vehicle.getInspectionAttachments())
|
&& StringUtils.isNotBlank(vehicle.getPolicyInformation())
|
&& StringUtils.isNotBlank(vehicle.getOperationQualification())
|
&& vehicle.getQualificationExpireTime() != null;
|
}
|
|
/**
|
* 检查车辆是否有任意一项证件过期(精确到日期)
|
* - 精确度:yyyy-MM-dd(只比较日期部分)
|
* - 到期日期等于今天不算过期(使用 isBefore)
|
*
|
* @param vehicle 车辆信息
|
* @param today 今天日期
|
*/
|
private boolean isAnyDateExpired(XpVehicle vehicle, LocalDate today) {
|
// 年检到期时间检查:已过期
|
if (vehicle.getAnnualInspectionDate() != null
|
&& vehicle.getAnnualInspectionDate().toLocalDate().isBefore(today)) {
|
return true;
|
}
|
// 保险到期时间检查:已过期
|
if (vehicle.getInsuranceEndTime() != null
|
&& vehicle.getInsuranceEndTime().toLocalDate().isBefore(today)) {
|
return true;
|
}
|
// 运营资质到期时间检查:已过期
|
if (vehicle.getQualificationExpireTime() != null
|
&& vehicle.getQualificationExpireTime().toLocalDate().isBefore(today)) {
|
return true;
|
}
|
return false;
|
}
|
|
@Override
|
public void batchOperation(String idStr) {
|
if (StringUtils.isBlank(idStr)) {
|
return;
|
}
|
List<String> ids = Arrays.asList(idStr.split(","));
|
// 查询车辆完整信息
|
List<XpVehicle> vehicles = this.listByIds(ids);
|
List<Long> vehicleIdList = vehicles.stream()
|
.map(XpVehicle::getId)
|
.filter(Objects::nonNull)
|
.collect(Collectors.toList());
|
Set<Long> 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<String> errorMessages = new ArrayList<>();
|
|
for (XpVehicle vehicle : vehicles) {
|
List<String> 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<XpVehicle> wrapper = new LambdaUpdateWrapper<>();
|
wrapper.in(XpVehicle::getId, ids)
|
.set(XpVehicle::getVehicleType, 2)
|
.set(XpVehicle::getOperatingStatus, 1);
|
this.update(wrapper);
|
}
|
|
@Override
|
public void batchStock(String idStr) {
|
if (StringUtils.isBlank(idStr)) {
|
return;
|
}
|
List<String> ids = Arrays.asList(idStr.split(","));
|
// 批量将运营车辆转为在库状态(vehicleType: 2→1)
|
LambdaUpdateWrapper<XpVehicle> wrapper = new LambdaUpdateWrapper<>();
|
wrapper.in(XpVehicle::getId, ids)
|
.set(XpVehicle::getVehicleType, 1);
|
this.update(wrapper);
|
}
|
|
@Override
|
public void batchSetElectronic(List<Long> ids, Long electronicId) {
|
if (CollectionUtils.isEmpty(ids)) {
|
return;
|
}
|
// 批量更新运营区域并设置状态为待运营
|
LambdaUpdateWrapper<XpVehicle> wrapper = new LambdaUpdateWrapper<>();
|
wrapper.in(XpVehicle::getId, ids)
|
.set(XpVehicle::getElectronicId, electronicId)
|
.set(XpVehicle::getStatus, 2);
|
this.update(wrapper);
|
}
|
|
@Override
|
public PageInfo<XpVehicle> vehiclePageList(VehiclePageQuery query) {
|
PageInfo<XpVehicle> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
|
List<XpVehicle> list = this.baseMapper.vehiclePageList(pageInfo, query);
|
//查询运营区域名称
|
List<Long> electronicIds = list.stream()
|
.map(XpVehicle::getElectronicId)
|
.filter(Objects::nonNull)
|
.distinct()
|
.collect(Collectors.toList());
|
Map<Long, String> electronicNameMap = new HashMap<>();
|
if (CollUtil.isNotEmpty(electronicIds)) {
|
List<XpElectronicFence> data = xpElectronicClient.listByIds(electronicIds).getData();
|
electronicNameMap = CollUtil.isNotEmpty(data) ?
|
data.stream().collect(Collectors.toMap(XpElectronicFence::getId, XpElectronicFence::getElectronicName))
|
: Collections.emptyMap();
|
}
|
Map<Long, String> finalElectronicNameMap = electronicNameMap;
|
list.forEach(item -> {
|
if (item.getElectronicId() != null) {
|
item.setElectronicName(finalElectronicNameMap.get(item.getElectronicId()));
|
}
|
});
|
if (CollUtil.isNotEmpty(list)) {
|
LocalDate today = LocalDate.now();
|
List<Long> expiredVehicleIds = new ArrayList<>();
|
List<Long> 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<XpVehicle> updateWrapper = new LambdaUpdateWrapper<>();
|
updateWrapper.in(XpVehicle::getId, expiredVehicleIds)
|
.set(XpVehicle::getStatus, 3);
|
this.update(updateWrapper);
|
}
|
}
|
|
pageInfo.setRecords(list);
|
return pageInfo;
|
}
|
|
@Override
|
public List<XpVehicle> vehicleExportList(VehicleExportQuery query) {
|
return this.baseMapper.vehicleExportList(query);
|
}
|
|
@Override
|
public void importVehicles(List<XpVehicleImportExcel> importList) {
|
if (CollectionUtils.isEmpty(importList)) {
|
return;
|
}
|
List<XpVehicle> 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<ElectronicFenceDetailVo> 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<XpVehicle> operationPageList(OperationVehiclePageQuery query) {
|
//1.构建分页参数
|
PageInfo<XpVehicle> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
|
|
// 2.构建查询条件
|
LambdaQueryWrapper<XpVehicle> wrapper = new LambdaQueryWrapper<>();
|
wrapper.eq(XpVehicle::getVehicleType, 2)
|
.like(StringUtils.isNotBlank(query.getVehicleCode()), XpVehicle::getVehicleCode, query.getVehicleCode())
|
.like(StringUtils.isNotBlank(query.getVehicleVin()), XpVehicle::getVehicleVin, 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<XpVehicle> page = this.page(pageInfo, wrapper);
|
|
//4.批量查询围栏名称并填充
|
Set<Long> electronicIdSet = page.getRecords().stream().map(XpVehicle::getElectronicId).collect(Collectors.toSet());
|
List<XpElectronicFence> fenceList = CollUtil.isNotEmpty(electronicIdSet) ?
|
xpElectronicClient.listByIds(electronicIdSet).getData() :
|
Collections.emptyList();
|
if (CollUtil.isNotEmpty(fenceList)) {
|
Map<Long, String> 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<XpVehicle> operationVehicleExportList(OperationVehicleExportQuery query) {
|
// 1.构建查询条件
|
LambdaQueryWrapper<XpVehicle> wrapper = new LambdaQueryWrapper<>();
|
wrapper.eq(XpVehicle::getVehicleType, 2)
|
.like(StringUtils.isNotBlank(query.getVehicleCode()), XpVehicle::getVehicleCode, query.getVehicleCode())
|
.like(StringUtils.isNotBlank(query.getVehicleVin()), XpVehicle::getVehicleVin, 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<XpVehicle> list = this.list(wrapper);
|
|
//3.批量查询围栏名称并填充
|
Set<Long> electronicIdSet = list.stream().map(XpVehicle::getElectronicId).collect(Collectors.toSet());
|
List<XpElectronicFence> fenceList = CollUtil.isNotEmpty(electronicIdSet) ?
|
xpElectronicClient.listByIds(electronicIdSet).getData() :
|
Collections.emptyList();
|
if (CollUtil.isNotEmpty(fenceList)) {
|
Map<Long, String> fenceNameMap = fenceList.stream().collect(Collectors.toMap(XpElectronicFence::getId, XpElectronicFence::getElectronicName));
|
list.forEach(vehicle -> {
|
if (vehicle.getElectronicId() != null) {
|
vehicle.setElectronicName(fenceNameMap.get(vehicle.getElectronicId()));
|
}
|
});
|
}
|
return list;
|
}
|
|
@Override
|
public VehicleTemperatureDto queryTemperatureByOrderId(Long orderId, String uid) {
|
XpOrder xpOrder = xpOrderClient.queryOrderById(orderId).getData();
|
if (Objects.isNull(xpOrder)) {
|
throw new ServiceException("未查询到有效订单信息", I18nKeyConstant.ORDER_NOT_EXISTS);
|
}
|
XpVehicle xpVehicle = this.getById(xpOrder.getVehicleId());
|
if (Objects.isNull(xpVehicle) || Objects.isNull(xpVehicle.getVehicleVin())) {
|
throw new ServiceException("未查询到有效车辆信息", I18nKeyConstant.VEHICLE_NOT_EXISTS);
|
}
|
try {
|
VehicleTemperatureDto vehicleTemperatureDto = vehicleControlUtil.queryVehicleTemperature(uid, xpVehicle.getVehicleVin());
|
log.info("查询车辆温度成功:{}", vehicleTemperatureDto);
|
return vehicleTemperatureDto;
|
} catch (Exception e) {
|
log.error("查询车辆温度失败", e);
|
return new VehicleTemperatureDto();
|
}
|
}
|
|
|
@Override
|
public 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);
|
}
|
try {
|
return vehicleControlUtil.queryTrunkDoorStateSync(Long.parseLong(uid), xpVehicle.getVehicleVin(), reportEnable);
|
} catch (Exception e) {
|
log.error("查询车门状态失败", e);
|
return false;
|
}
|
}
|
|
@Override
|
public PageInfo<XpVehicleVO> getVehiclePageList(VehicleSelectPageQuery query) {
|
Page<XpVehicleVO> page = new Page<>(query.getPageNum(), query.getPageSize());
|
page = baseMapper.getVehiclePageList(page, query.getPlateNumber(), query.getFleetName());
|
PageInfo<XpVehicleVO> 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 {
|
return vehicleControlUtil.queryTrunkDoorStateSync(Long.parseLong(uid), xpVehicle.getVehicleVin(), reportEnable);
|
} catch (Exception e) {
|
log.error("查询车辆后备箱失败", e);
|
return false;
|
}
|
}
|
|
|
/**
|
* 获取服务车辆列表
|
*
|
* @param dto
|
* @return
|
*/
|
@Override
|
public List<XpVehicle> getServiceVehicleList(ServiceVehicleListDto dto) {
|
List<XpVehicle> list = this.list(new QueryWrapper<XpVehicle>().eq("vehicle_type", 2)
|
.eq("status", 2).eq("operating_status", 1)
|
.eq("is_deleted", 0).like(StringUtils.isNotBlank(dto.getPlateNumber()), "plate_number", dto.getPlateNumber())
|
.like(StringUtils.isNotBlank(dto.getVin()), "vehicle_vin", dto.getVin())
|
.last(" order by create_time desc limit " + dto.getPageNo() + "," + dto.getPageSize())
|
);
|
|
return list;
|
}
|
|
|
/**
|
* 运维APP解锁车门和开关后备箱
|
*
|
* @param dto
|
* @return
|
*/
|
@Override
|
public Integer unlockDoor(UnlockAppDTO dto, XpOperationsUser xpOperationsUser) {
|
XpVehicle xpVehicle = this.getById(dto.getVehicleId());
|
// 判断距离是否在范围之内 15米
|
VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(xpVehicle.getVehicleVin())), VehiclePosition.class);
|
if (Objects.nonNull(vehiclePosition)) {
|
GeoJsonPoint point = vehiclePosition.getPoint();
|
if (Objects.nonNull(point)) {
|
Map<String, Double> 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(xpVehicle.getVehicleVin());
|
addVo.setUid(userByMobile.getUid().toString());
|
addVo.setOrderNo(phoneNumber);
|
addVo.setAppId(appid);
|
userVehicleClient.addTaxiPassenger(addVo);
|
log.info("已绑定运维与新车辆关系,电话号码: {}, VIN: {}", phoneNumber, xpVehicle.getVehicleVin());
|
|
try {
|
// 车门解锁、后备箱解锁
|
VehiclesCommandVo vehiclesCommandVo = new VehiclesCommandVo();
|
vehiclesCommandVo.setVin(xpVehicle.getVehicleVin());
|
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_GEAR);
|
}
|
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();
|
// 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(2000);
|
} catch (InterruptedException e) {
|
throw new RuntimeException(e);
|
}
|
String cacheObject = redisService.getCacheObject("HVAC:" + msgId);
|
if(StringUtils.isEmpty(cacheObject)){
|
return false;
|
}
|
JSONObject jsonObject = JSON.parseObject(cacheObject);
|
Integer hvac_on = jsonObject.getInteger("hvac_on");
|
return hvac_on == 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;
|
}
|
XpVehicle vehicle = this.getById(xpOrder.getVehicleId());
|
VehiclesCommandVo vehiclesCommandVo = new VehiclesCommandVo();
|
vehiclesCommandVo.setVin(vehicle.getVehicleVin());
|
vehiclesCommandVo.setCommandCode(dto.getOpen() ? VehiclesCommandEnum.HVAC_ON.getCommand() : VehiclesCommandEnum.HVAC_OFF.getCommand());
|
String msgId = vehicleControlUtil.vehiclesCommand(vehiclesCommandVo, Long.valueOf(uid));
|
log.info("空调控制结果:{}", msgId);
|
if (StringUtils.isEmpty(msgId)) {
|
return 0;
|
} else {
|
return 1;
|
}
|
}
|
|
|
/**
|
* 获取车辆门锁状态
|
* @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(2000);
|
} catch (InterruptedException e) {
|
throw new RuntimeException(e);
|
}
|
String cacheObject = redisService.getCacheObject("DOOR:" + msgId);
|
if(StringUtils.isEmpty(cacheObject)){
|
return null;
|
}
|
// todo 待文档补充后再更新代码
|
VehicleDoorStatusVo vo = new VehicleDoorStatusVo();
|
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");
|
vo.setTrunk(trunk_ajar_st_new == 5 ? 1 : 0);
|
return vo;
|
}
|
}
|