package com.xiaopeng.vehicleConvoy.controller;
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Validator;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.mongodb.client.MongoCollection;import com.xiaopeng.api.dto.operation.VehiclePositionByVinDto;
import com.xiaopeng.api.dto.vehicleConvoy.UpdateVehicleStateDto;
import com.xiaopeng.api.dto.vehicleConvoy.VehicleTemperatureDto;
import com.xiaopeng.api.feignClient.maintenance.*;
import com.xiaopeng.api.model.account.XpOperationsUser;
import com.xiaopeng.api.model.maintenance.XpTask;
import com.xiaopeng.api.model.maintenance.XpTaskRule;
import com.xiaopeng.api.model.vehicleConvoy.XpFleet;
import com.xiaopeng.api.model.maintenance.XpVehicleRepair;
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.vehicleConvoy.VehicleStateVo;
import com.xiaopeng.api.vo.vehicleConvoy.VehiclesCommandCallbackVo;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.exception.ServiceException;
import com.xiaopeng.common.core.utils.SensitiveDataCryptoUtil;
import com.xiaopeng.common.core.utils.WebUtils;
import com.xiaopeng.common.core.web.page.PageInfo;
import com.xiaopeng.common.security.service.TokenService;
import com.xiaopeng.evi.oss.service.AliOssService;
import com.xiaopeng.vehicleConvoy.constant.I18nKeyConstant;
import com.xiaopeng.vehicleConvoy.model.dto.*;
import com.xiaopeng.vehicleConvoy.model.exportExcel.XpVehicleExportExcel;
import com.xiaopeng.vehicleConvoy.model.importExcel.XpVehicleImportExcel;
import com.xiaopeng.vehicleConvoy.model.query.*;
import com.xiaopeng.vehicleConvoy.model.vo.XpVehicleVO;
import com.xiaopeng.vehicleConvoy.service.IXpFleetService;
import com.xiaopeng.vehicleConvoy.service.IXpFleetVehicleService;
import com.xiaopeng.vehicleConvoy.service.IXpVehicleAnnualRecordService;
import com.xiaopeng.vehicleConvoy.service.IXpVehicleService;
import com.xiaopeng.vehicleConvoy.util.AliOssDownloadUtil;
import io.swagger.annotations.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.openxml4j.exceptions.NotOfficeXmlFileException;
import org.apache.poi.ss.usermodel.Workbook;
import org.bson.Document;import org.springframework.beans.BeanUtils;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.*;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import com.xiaopeng.common.log.annotation.AdminOperationLog;
import com.xiaopeng.common.log.enums.AdminBusinessType;
import com.xiaopeng.common.log.enums.AdminOperationType;
/**
*
* 车辆管理 前端控制器
*
*
* @author mitao
* @since 2026-02-03
*/
@Slf4j
@RestController
@Api(tags = "车辆管理")
@RequiredArgsConstructor(onConstructor_ = @Lazy)
public class XpVehicleController {
private static final Pattern VEHICLE_CODE_PATTERN = Pattern.compile("^[A-Za-z0-9_]{1,5}$");
private static final Pattern VEHICLE_VIN_PATTERN = Pattern.compile("^[A-Z0-9]{17}$");
private final IXpVehicleService xpVehicleService;
private final IXpFleetVehicleService vehicleService;
private final IXpFleetService xpFleetService;
private final IXpVehicleAnnualRecordService xpVehicleAnnualRecordService;
private final XpVehiclePreparationClient xpVehiclePreparationClient;
private final XpChargingRecordClient xpChargingRecordClient;
private final XpTaskClient xpTaskClient;
private final XpTaskRuleClient xpTaskRuleClient;
private final MongoTemplate mongoTemplate;
private final MessageSource messageSource;
private final TokenService tokenService;
private final XpVehicleRepairClient xpVehicleRepairClient;
private final AliOssService aliOssService;
private final AliOssDownloadUtil aliOssDownloadUtil;
/**
* 根据ID列表批量查询车辆信息(内部接口)
*
* @param ids 车辆ID列表
* @return 车辆列表
*/
@ApiOperation(value = "批量查询车辆信息", tags = {"内部接口"})
@PostMapping("/inner/vehicle/listByIds")
public R> listByIds(@RequestBody List ids) {
if (CollectionUtils.isEmpty(ids)) {
return R.ok(new ArrayList<>());
}
return R.ok(xpVehicleService.listByIds(ids));
}
/**
* 批量获取车辆列表
*
* @return 车辆列表
*/
@ApiOperation(value = "批量查询车辆信息", tags = {"内部接口"})
@PostMapping("/inner/vehicle/listVehicle")
public R> listVehicle() {
return R.ok(xpVehicleService.list());
}
/**
* 根据ID查询车辆信息(内部接口)
*
* @param id 车辆ID
* @return 车辆列表
*/
@GetMapping("/inner/vehicle/queryById")
public R queryById(@RequestParam Long id) throws UnsupportedEncodingException {
XpVehicle xpVehicle = xpVehicleService.getById(id);
if (Objects.nonNull(xpVehicle)) {
String vehiclePictures = xpVehicle.getVehiclePictures();
if(vehiclePictures!=null){
JSONObject jsonObject = JSON.parseObject(vehiclePictures);
// 1
String downloadUrl = aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
jsonObject.put("url", downloadUrl);
xpVehicle.setVehiclePictures(jsonObject.toJSONString());
}
// 获取车队
XpFleetVehicle xpFleetVehicle = vehicleService.getOne(new LambdaQueryWrapper().eq(XpFleetVehicle::getVehicleId, xpVehicle.getId()));
if (Objects.nonNull(xpFleetVehicle)) {
xpVehicle.setFleetId(xpFleetVehicle.getFleetId());
}
}
return R.ok(xpVehicle);
}
/**
* 修改车辆信息(内部接口)
*
* @param xpVehicle 车辆信息
* @return 车辆
*/
@PostMapping("/inner/vehicle/updateVehicleById")
public R updateVehicleById(@RequestBody XpVehicle xpVehicle){
xpVehicleService.updateById(xpVehicle);
return R.ok();
}
/**
* 新增车辆
*
* @param dto 车辆信息
* @return 操作结果
*/
@ApiOperation(value = "新增车辆", tags = {"管理后台-车辆管理-在库车辆管理"})
@PostMapping("/api/v1/vehicle/add")
@AdminOperationLog(
businessType = AdminBusinessType.STOCK_VEHICLE_MANAGEMENT,
operationType = AdminOperationType.ADD,
content = "新增车辆",
appendRequestData = true
)
public R> addVehicle(@Valid @RequestBody VehicleAddDTO dto) {
xpVehicleService.addVehicle(dto);
return R.ok();
}
/**
* 编辑车辆
*
* @param dto 车辆信息
* @return 操作结果
*/
@ApiOperation(value = "编辑车辆", tags = {"管理后台-车辆管理-在库车辆管理"})
@PostMapping("/api/v1/vehicle/edit")
@AdminOperationLog(
businessType = AdminBusinessType.STOCK_VEHICLE_MANAGEMENT,
operationType = AdminOperationType.EDIT,
content = "编辑车辆",
appendRequestData = true
)
public R> editVehicle(@Valid @RequestBody VehicleEditDTO dto) {
xpVehicleService.editVehicle(dto);
return R.ok();
}
/**
* 完善/更新车辆信息(保险、年检、运营资质等)
*
* @param dto 车辆信息
* @return 操作结果
*/
@ApiOperation(value = "完善/更新车辆信息", tags = {"管理后台-车辆管理-在库车辆管理"})
@PostMapping("/api/v1/vehicle/improveInfo")
@AdminOperationLog(
businessType = AdminBusinessType.STOCK_VEHICLE_MANAGEMENT,
operationType = AdminOperationType.EDIT,
content = "完善/更新车辆信息",
appendRequestData = true
)
public R> improveInfo(@RequestBody VehicleImproveInfoDTO dto) {
xpVehicleService.improveInfo(dto);
return R.ok();
}
/**
* 批量运营(在库→运营)
*
* @param idStr 车辆ID字符串列表
* @return 操作结果
*/
@ApiOperation(value = "批量运营", tags = {"管理后台-车辆管理-在库车辆管理"})
@PostMapping("/api/v1/vehicle/batchOperation")
@ApiImplicitParam(name = "idStr", value = "车辆ID字符串列表,逗号分隔", required = true, dataTypeClass = String.class)
@AdminOperationLog(
businessType = AdminBusinessType.STOCK_VEHICLE_MANAGEMENT,
operationType = AdminOperationType.EDIT,
contentExpression = "'批量运营车辆,ids=' + #p0",
appendRequestData = false
)
public R> batchOperation(@RequestParam String idStr) {
xpVehicleService.batchOperation(idStr);
return R.ok();
}
/**
* 批量设置运营区域
*
* @param dto 批量设置运营区域参数
* @return 操作结果
*/
@ApiOperation(value = "批量设置运营区域", tags = {"管理后台-车辆管理-在库车辆管理"})
@PostMapping("/api/v1/vehicle/batchSetElectronic")
@AdminOperationLog(
businessType = AdminBusinessType.STOCK_VEHICLE_MANAGEMENT,
operationType = AdminOperationType.EDIT,
content = "批量设置运营区域",
appendRequestData = true
)
public R> batchSetElectronic(@Valid @RequestBody VehicleBatchSetElectronicDTO dto) {
xpVehicleService.batchSetElectronic(dto.getIds(), dto.getElectronicId());
return R.ok();
}
/**
* 在库车辆管理分页列表
*
* @param query 查询参数
* @return 分页结果
*/
@ApiOperation(value = "在库车辆管理分页列表", tags = {"管理后台-车辆管理-在库车辆管理"})
@PostMapping("/api/v1/vehicle/pageList")
public R> vehiclePageList(@RequestBody VehiclePageQuery query) {
return R.ok(xpVehicleService.vehiclePageList(query));
}
/**
* 导出在库车辆列表
*
*/
@ApiOperation(value = "下载导入模板", tags = {"管理后台-车辆管理-在库车辆管理"})
@PostMapping("/api/v1/vehicle/importTemplate")
@AdminOperationLog(
businessType = AdminBusinessType.STOCK_VEHICLE_MANAGEMENT,
operationType = AdminOperationType.EXPORT,
content = "下载车辆导入模板",
appendRequestData = false
)
public void importTemplate() {
// 1. 查询导出数据
Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), XpVehicleImportExcel.class, new ArrayList<>());
// 4. 输出到响应流
HttpServletResponse response = WebUtils.response();
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
ServletOutputStream outputStream = null;
try {
String fileName = URLEncoder.encode("在库车辆导入模板.xlsx", "utf-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName);
outputStream = response.getOutputStream();
workbook.write(outputStream);
} catch (IOException e) {
log.error("下载在库车辆导入模板失败", e);
} finally {
try {
if (outputStream != null) {
outputStream.close();
}
} catch (IOException e) {
log.error("关闭输出流失败", e);
}
}
}
/**
* 导入车辆
*
* @param file Excel文件
* @return 操作结果
*/
@ApiOperation(value = "导入", tags = {"管理后台-车辆管理-在库车辆管理"})
@PostMapping("/api/v1/vehicle/importExcel")
@AdminOperationLog(
businessType = AdminBusinessType.STOCK_VEHICLE_MANAGEMENT,
operationType = AdminOperationType.IMPORT,
content = "导入车辆",
appendRequestData = false
)
public R> importExcel(@RequestParam("file") MultipartFile file) {
ImportParams params = new ImportParams();
params.setHeadRows(1);
InputStream inputStream = null;
List importList;
try {
inputStream = file.getInputStream();
importList = ExcelImportUtil.importExcel(inputStream, XpVehicleImportExcel.class, params);
} catch (Exception e) {
log.error("车辆导入失败:{}", e.getMessage(), e);
if (e instanceof NotOfficeXmlFileException || e.getCause() instanceof NotOfficeXmlFileException) {
throw new ServiceException("上传文件不是有效的Excel(.xlsx)文件!", I18nKeyConstant.VEHICLE_IMPORT_FAILED);
}
throw new ServiceException("车辆导入失败!", I18nKeyConstant.VEHICLE_IMPORT_FAILED);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
log.error("关闭输入流失败", e);
}
}
}
if (CollectionUtils.isEmpty(importList)) {
throw new ServiceException("导入数据为空!", I18nKeyConstant.VEHICLE_IMPORT_DATA_EMPTY);
}
validateImportRequiredFields(importList, params.getHeadRows());
validateImportBusinessFields(importList, params.getHeadRows());
uploadVehiclePicture(importList);
xpVehicleService.importVehicles(importList);
return R.ok();
}
private void validateImportRequiredFields(List importList, int headRows) {
for (int index = 0; index < importList.size(); index++) {
XpVehicleImportExcel row = importList.get(index);
List missingFields = new ArrayList<>();
addIfBlank(missingFields, row.getVehicleCode(), "vehicle.import.field.vehicleCode");
addIfBlank(missingFields, row.getVehicleVin(), "vehicle.import.field.vehicleVin");
addIfBlank(missingFields, row.getPlateNumber(), "vehicle.import.field.plateNumber");
addIfBlank(missingFields, row.getVehicleModel(), "vehicle.import.field.vehicleModel");
addIfBlank(missingFields, row.getVehicleColor(), "vehicle.import.field.vehicleColor");
addIfBlank(missingFields, row.getVehiclePictures(), "vehicle.import.field.vehiclePictures.tip");
addIfBlank(missingFields, row.getProcurementSource(), "vehicle.import.field.procurementSource");
if (!missingFields.isEmpty()) {
int excelRowNumber = index + headRows + 1;
String missing = String.join(", ", missingFields);
throw new ServiceException("导入数据必填字段为空!", I18nKeyConstant.VEHICLE_IMPORT_FIELDS_REQUIRED,
new Object[]{excelRowNumber, missing});
}
}
}
private void addIfBlank(List missingFields, String value, String fieldNameKey) {
if (StringUtils.isBlank(value)) {
String fieldName = messageSource.getMessage(fieldNameKey, null, fieldNameKey, LocaleContextHolder.getLocale());
missingFields.add(fieldName);
}
}
private void validateImportBusinessFields(List importList, int headRows) {
List errors = new ArrayList<>();
Locale locale = LocaleContextHolder.getLocale();
for (int index = 0; index < importList.size(); index++) {
XpVehicleImportExcel row = importList.get(index);
int excelRowNumber = index + headRows + 1;
String vehicleCode = trimToNull(row.getVehicleCode());
String vehicleVin = normalizeVin(row.getVehicleVin());
String plateNumber = trimToNull(row.getPlateNumber());
String vehicleColor = trimToNull(row.getVehicleColor());
row.setVehicleCode(vehicleCode);
row.setVehicleVin(vehicleVin);
row.setPlateNumber(plateNumber);
row.setVehicleColor(vehicleColor);
if (org.apache.commons.lang3.StringUtils.isNotBlank(vehicleCode) && !VEHICLE_CODE_PATTERN.matcher(vehicleCode).matches()) {
errors.add(messageSource.getMessage(I18nKeyConstant.VEHICLE_IMPORT_CODE_INVALID,
new Object[]{excelRowNumber, vehicleCode},
I18nKeyConstant.VEHICLE_IMPORT_CODE_INVALID,
locale));
}
if (org.apache.commons.lang3.StringUtils.isNotBlank(vehicleVin) && !VEHICLE_VIN_PATTERN.matcher(vehicleVin).matches()) {
errors.add(messageSource.getMessage(I18nKeyConstant.VEHICLE_IMPORT_VIN_INVALID,
new Object[]{excelRowNumber, vehicleVin},
I18nKeyConstant.VEHICLE_IMPORT_VIN_INVALID,
locale));
}
if (org.apache.commons.lang3.StringUtils.isNotBlank(plateNumber) && !Validator.isPlateNumber(plateNumber)) {
errors.add(messageSource.getMessage(I18nKeyConstant.VEHICLE_IMPORT_PLATE_INVALID,
new Object[]{excelRowNumber, plateNumber},
I18nKeyConstant.VEHICLE_IMPORT_PLATE_INVALID,
locale));
}
if (org.apache.commons.lang3.StringUtils.isNotBlank(vehicleColor) && vehicleColor.length() > 3) {
errors.add(messageSource.getMessage(I18nKeyConstant.VEHICLE_IMPORT_COLOR_INVALID,
new Object[]{excelRowNumber, vehicleColor},
I18nKeyConstant.VEHICLE_IMPORT_COLOR_INVALID,
locale));
}
}
Set duplicatedCodes = findDuplicated(importList.stream()
.map(XpVehicleImportExcel::getVehicleCode)
.filter(org.apache.commons.lang3.StringUtils::isNotBlank)
.collect(java.util.stream.Collectors.toList()));
Set duplicatedVins = findDuplicated(importList.stream()
.map(XpVehicleImportExcel::getVehicleVin)
.filter(org.apache.commons.lang3.StringUtils::isNotBlank)
.collect(java.util.stream.Collectors.toList()));
Set duplicatedPlates = findDuplicated(importList.stream()
.map(XpVehicleImportExcel::getPlateNumber)
.filter(org.apache.commons.lang3.StringUtils::isNotBlank)
.collect(java.util.stream.Collectors.toList()));
for (int index = 0; index < importList.size(); index++) {
XpVehicleImportExcel row = importList.get(index);
int excelRowNumber = index + headRows + 1;
if (duplicatedCodes.contains(row.getVehicleCode())) {
errors.add(messageSource.getMessage(I18nKeyConstant.VEHICLE_IMPORT_CODE_DUPLICATE,
new Object[]{excelRowNumber, row.getVehicleCode()},
I18nKeyConstant.VEHICLE_IMPORT_CODE_DUPLICATE,
locale));
}
if (duplicatedVins.contains(row.getVehicleVin())) {
errors.add(messageSource.getMessage(I18nKeyConstant.VEHICLE_IMPORT_VIN_DUPLICATE,
new Object[]{excelRowNumber, row.getVehicleVin()},
I18nKeyConstant.VEHICLE_IMPORT_VIN_DUPLICATE,
locale));
}
if (duplicatedPlates.contains(row.getPlateNumber())) {
errors.add(messageSource.getMessage(I18nKeyConstant.VEHICLE_IMPORT_PLATE_DUPLICATE,
new Object[]{excelRowNumber, row.getPlateNumber()},
I18nKeyConstant.VEHICLE_IMPORT_PLATE_DUPLICATE,
locale));
}
}
Set codeSet = importList.stream()
.map(XpVehicleImportExcel::getVehicleCode)
.filter(org.apache.commons.lang3.StringUtils::isNotBlank)
.collect(java.util.stream.Collectors.toSet());
Set vinSet = importList.stream()
.map(XpVehicleImportExcel::getVehicleVin)
.filter(org.apache.commons.lang3.StringUtils::isNotBlank)
.collect(java.util.stream.Collectors.toSet());
Set plateSet = importList.stream()
.map(XpVehicleImportExcel::getPlateNumber)
.filter(org.apache.commons.lang3.StringUtils::isNotBlank)
.collect(java.util.stream.Collectors.toSet());
Set existsCodes = Collections.emptySet();
if (!codeSet.isEmpty()) {
existsCodes = xpVehicleService.lambdaQuery()
.select(XpVehicle::getVehicleCode)
.in(XpVehicle::getVehicleCode, codeSet)
.list()
.stream()
.map(XpVehicle::getVehicleCode)
.filter(org.apache.commons.lang3.StringUtils::isNotBlank)
.collect(java.util.stream.Collectors.toSet());
}
Set existsVins = Collections.emptySet();
if (!vinSet.isEmpty()) {
existsVins = xpVehicleService.lambdaQuery()
.select(XpVehicle::getVehicleVin)
.in(XpVehicle::getVehicleVin, buildVinQueryValues(vinSet))
.list()
.stream()
.map(XpVehicle::getVehicleVin)
.map(SensitiveDataCryptoUtil::decryptIfPossible)
.filter(org.apache.commons.lang3.StringUtils::isNotBlank)
.collect(java.util.stream.Collectors.toSet());
}
Set existsPlates = Collections.emptySet();
if (!plateSet.isEmpty()) {
existsPlates = xpVehicleService.lambdaQuery()
.select(XpVehicle::getPlateNumber)
.in(XpVehicle::getPlateNumber, plateSet)
.list()
.stream()
.map(XpVehicle::getPlateNumber)
.filter(org.apache.commons.lang3.StringUtils::isNotBlank)
.collect(java.util.stream.Collectors.toSet());
}
for (int index = 0; index < importList.size(); index++) {
XpVehicleImportExcel row = importList.get(index);
int excelRowNumber = index + headRows + 1;
if (existsCodes.contains(row.getVehicleCode())) {
errors.add(messageSource.getMessage(I18nKeyConstant.VEHICLE_IMPORT_CODE_EXISTS,
new Object[]{excelRowNumber, row.getVehicleCode()},
I18nKeyConstant.VEHICLE_IMPORT_CODE_EXISTS,
locale));
}
if (existsVins.contains(row.getVehicleVin())) {
errors.add(messageSource.getMessage(I18nKeyConstant.VEHICLE_IMPORT_VIN_EXISTS,
new Object[]{excelRowNumber, row.getVehicleVin()},
I18nKeyConstant.VEHICLE_IMPORT_VIN_EXISTS,
locale));
}
if (existsPlates.contains(row.getPlateNumber())) {
errors.add(messageSource.getMessage(I18nKeyConstant.VEHICLE_IMPORT_PLATE_EXISTS,
new Object[]{excelRowNumber, row.getPlateNumber()},
I18nKeyConstant.VEHICLE_IMPORT_PLATE_EXISTS,
locale));
}
}
if (!errors.isEmpty()) {
throw new ServiceException("导入校验失败!", I18nKeyConstant.VEHICLE_IMPORT_VALIDATE_FAIL,
new Object[]{String.join("\n", errors)});
}
}
private static String normalizeVin(String vin) {
String trimmed = trimToNull(vin);
return trimmed == null ? null : trimmed.toUpperCase(Locale.ROOT);
}
private static String trimToNull(String value) {
if (value == null) {
return null;
}
String trimmed = value.trim();
return trimmed.isEmpty() ? null : trimmed;
}
private static Set findDuplicated(List values) {
if (values == null || values.isEmpty()) {
return Collections.emptySet();
}
Set seen = new HashSet<>();
Set duplicated = new HashSet<>();
for (String value : values) {
if (!seen.add(value)) {
duplicated.add(value);
}
}
return duplicated;
}
private static List buildVinQueryValues(Iterable vins) {
if (vins == null) {
return Collections.emptyList();
}
List values = new ArrayList<>();
for (String vin : vins) {
values.addAll(SensitiveDataCryptoUtil.queryValues(vin));
}
return values.stream().distinct().collect(Collectors.toList());
}
private void uploadVehiclePicture(List importList) {
for (XpVehicleImportExcel xpVehicleImportExcel : importList) {
xpVehicleImportExcel.setVehiclePictures(getOssUrl(xpVehicleImportExcel.getVehiclePictures()));
}
}
private String getOssUrl(String filePath) {
try {
// 获取到暂存的文件
File tmpFile = new File(filePath);
FileInputStream fileInputStream = new FileInputStream(tmpFile);
// TODO 上传oss
JSONObject res = new JSONObject();
res.put("url", "https://we-park-life.oss-cn-beijing.aliyuncs.com/img/K4vqtLmVc6.png");
res.put("id", "1");
res.put("name", "测试.png");
return res.toJSONString();
} catch (Exception e) {
log.error("在库车辆导入图片上传oss异常", e);
}
return null;
}
/**
* 导出在库车辆列表
*
* @param query 查询参数
*/
@ApiOperation(value = "导出", tags = {"管理后台-车辆管理-在库车辆管理"})
@PostMapping("/api/v1/vehicle/exportExcel")
@AdminOperationLog(
businessType = AdminBusinessType.STOCK_VEHICLE_MANAGEMENT,
operationType = AdminOperationType.EXPORT,
content = "导出在库车辆",
appendRequestData = true
)
public void exportExcel(@RequestBody VehicleExportQuery query) {
// 1. 查询导出数据
List dataList = xpVehicleService.vehicleExportList(query);
// 2. 转换为导出对象
List exportList = BeanUtil.copyToList(dataList, XpVehicleExportExcel.class);
for (XpVehicleExportExcel xpVehicleExportExcel : exportList) {
if (StringUtils.isNotBlank(xpVehicleExportExcel.getVehiclePictures())) {
JSONObject jsonObject = JSONObject.parseObject(xpVehicleExportExcel.getVehiclePictures());
xpVehicleExportExcel.setVehiclePictures(jsonObject.getString("url"));
}
}
// 3. 生成Excel
Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), XpVehicleExportExcel.class, exportList);
// 4. 输出到响应流
HttpServletResponse response = WebUtils.response();
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
ServletOutputStream outputStream = null;
try {
String fileName = URLEncoder.encode("在库车辆列表.xls", "utf-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName);
outputStream = response.getOutputStream();
workbook.write(outputStream);
} catch (IOException e) {
log.error("导出在库车辆列表失败", e);
} finally {
try {
if (outputStream != null) {
outputStream.close();
}
} catch (IOException e) {
log.error("关闭输出流失败", e);
}
}
}
/**
* 批量在库(运营→在库)
*
* @param idStr 车辆ID列表
* @return 操作结果
*/
@ApiOperation(value = "批量在库", tags = {"管理后台-车辆管理-运营车辆管理"})
@ApiImplicitParam(name = "idStr", value = "车辆ID字符串列表,逗号分隔", required = true, dataTypeClass = String.class)
@PostMapping("/api/v1/vehicle/batchStock")
@AdminOperationLog(
businessType = AdminBusinessType.OPERATION_VEHICLE_MANAGEMENT,
operationType = AdminOperationType.EDIT,
contentExpression = "'批量在库车辆,ids=' + #p0",
appendRequestData = false
)
public R> batchStock(@RequestParam String idStr) {
xpVehicleService.batchStock(idStr);
return R.ok();
}
/**
* 车辆详情(含围栏名称)
*
* @param id 车辆ID
* @return 车辆详情
*/
@ApiOperation(value = "车辆详情", tags = {"管理后台-车辆管理-在库车辆管理"})
@GetMapping("/api/v1/vehicle/queryById")
public R getVehicleDetail(@ApiParam(value = "车辆id", required = true) @RequestParam Long id) throws UnsupportedEncodingException {
XpVehicle vehicleDetail = xpVehicleService.getVehicleDetail(id);
if(vehicleDetail.getVehiclePictures()!=null){
JSONObject jsonObject = JSONObject.parseObject(vehicleDetail.getVehiclePictures());
String downloadUrl = aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
jsonObject.put("url",downloadUrl);
String jsonString = jsonObject.toJSONString();
vehicleDetail.setVehiclePictures(jsonString);
}
return R.ok(vehicleDetail);
}
/**
* 删除在库车辆
* @param id
* @return
*/
@ApiOperation(value = "删除在库车辆",tags = {"管理后台-车辆管理-在库车辆管理"})
@DeleteMapping("/api/v1/vehicle/delete/{id}")
@AdminOperationLog(
businessType = AdminBusinessType.STOCK_VEHICLE_MANAGEMENT,
operationType = AdminOperationType.DELETE,
contentExpression = "'删除在库车辆,id=' + #p0",
appendRequestData = false
)
public R> delete(@ApiParam(name = "id", value = "车辆ID") @PathVariable Long id) {
XpFleetVehicle binding = vehicleService.lambdaQuery()
.eq(XpFleetVehicle::getVehicleId, id)
.last("limit 1")
.one();
if (binding != null && binding.getFleetId() != null) {
String fleetName = String.valueOf(binding.getFleetId());
try {
XpFleet fleet = xpFleetService.getById(binding.getFleetId());
if (fleet != null && StringUtils.isNotBlank(fleet.getFleetName())) {
fleetName = fleet.getFleetName();
}
} catch (Exception e) {
// ignore, fallback to fleetId
}
throw new ServiceException("当前车辆已关联车队:" + fleetName, I18nKeyConstant.VEHICLE_DELETE_HAS_BOUND_FLEET,
new Object[]{fleetName});
}
xpVehicleService.removeById(id);
return R.ok();
}
/**
* 运营车辆管理分页列表
*
* @param query 查询参数
* @return 分页结果
*/
@ApiOperation(value = "运营车辆管理分页列表", tags = {"管理后台-车辆管理-运营车辆管理"})
@PostMapping("/api/v1/vehicle/operationPageList")
public R> operationPageList(@RequestBody OperationVehiclePageQuery query) {
return R.ok(xpVehicleService.operationPageList(query));
}
/**
* 导出在库车辆列表
*
* @param query 查询参数
*/
@ApiOperation(value = "运营车辆管理列表导出", tags = {"管理后台-车辆管理-运营车辆管理"})
@PostMapping("/api/v1/vehicle/operationExportExcel")
@AdminOperationLog(
businessType = AdminBusinessType.OPERATION_VEHICLE_MANAGEMENT,
operationType = AdminOperationType.EXPORT,
content = "导出运营车辆",
appendRequestData = true
)
public void exportExcel(@RequestBody OperationVehicleExportQuery query) {
// 1. 查询导出数据
List dataList = xpVehicleService.operationVehicleExportList(query);
// 2. 转换为导出对象
List exportList = BeanUtil.copyToList(dataList, XpVehicleExportExcel.class);
for (XpVehicleExportExcel xpVehicleExportExcel : exportList) {
if (StringUtils.isNotBlank(xpVehicleExportExcel.getVehiclePictures())) {
String pictureJSONObject = aliOssDownloadUtil.getPictureJSONObject(xpVehicleExportExcel.getVehiclePictures());
if (StringUtils.isNotBlank(pictureJSONObject)) {
JSONObject jsonObject = JSONObject.parseObject(pictureJSONObject);
xpVehicleExportExcel.setVehiclePictures(jsonObject.containsKey("url") ? jsonObject.getString("url") : "");
}
}
if (Integer.valueOf(2).equals(xpVehicleExportExcel.getStatus()) && Integer.valueOf(1).equals(xpVehicleExportExcel.getOperatingStatus())) {
xpVehicleExportExcel.setStatus(4);
}
}
// 3. 生成Excel
Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), XpVehicleExportExcel.class, exportList);
// 4. 输出到响应流
HttpServletResponse response = WebUtils.response();
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
ServletOutputStream outputStream = null;
try {
String fileName = URLEncoder.encode("运营车辆列表.xls", "utf-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName);
outputStream = response.getOutputStream();
workbook.write(outputStream);
} catch (IOException e) {
log.error("导出在库车辆列表失败", e);
} finally {
try {
if (outputStream != null) {
outputStream.close();
}
} catch (IOException e) {
log.error("关闭输出流失败", e);
}
}
}
@ApiOperation(value = "通过车辆id查询年检分页列表", tags = {"管理后台-车辆管理-运营车辆管理"})
@PostMapping("/api/v1/vehicle/queryTaskRoutineByVehicleId")
public R> queryTaskRoutineByVehicleId(
@Valid @RequestBody XpVehicleAnnualRecordPageQuery query) throws UnsupportedEncodingException {
return R.ok(xpVehicleAnnualRecordService.queryTaskRoutineByVehicleId(query));
}
public static void main(String[] args) {
String inspectionAttachments = "[{\"id\": \"d57c4ac4-acb0-451b-8e94-70f226331e0c\", \"url\": \"https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/552a3a31-e4dd-4a28-8748-fd8b87dd3055.png\", \"name\": \"测试.jpg\"}]";
if (StringUtils.isNotBlank(inspectionAttachments)) {
JSONArray jsonArray = JSONArray.parseArray(inspectionAttachments);
for (int i = 0; i < jsonArray.size(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
if (Objects.isNull(jsonObject)) {
continue;
}
jsonObject.put("url", "downloadUrl");
}
String jsonString = JSONArray.toJSONString(jsonArray);
System.out.println(jsonString);
}
}
/**
* 根据围栏id查询空闲车辆
*
*/
@GetMapping("/inner/vehicle/listByFenceId")
public R> listByFenceId(@RequestParam("fenceId") Long fenceId){
return R.ok(xpVehicleService.listByFenceId(fenceId));
}
/**
* 查询车辆位置信息 做筛选
* @param dto
* @return
*/
@PostMapping("/inner/vehicle/queryVehiclePositionByVin")
public R> queryVehiclePositionByVin(@RequestBody VehiclePositionByVinDto dto){
List list = xpVehicleService.list(new LambdaQueryWrapper().eq(XpVehicle::getState, 0)
.eq(XpVehicle::getOperatingStatus, 1).eq(XpVehicle::getVehicleType,2).eq(XpVehicle::getElectronicId, dto.getFenceId()).in(XpVehicle::getVehicleVin,
buildVinQueryValues(dto.getVinList())));
if(list.isEmpty()){
return R.ok(new ArrayList<>());
}else {
List vehiclePositionVos = new ArrayList<>(list.size());
for (XpVehicle xpVehicle : list) {
vehiclePositionVos.add(xpVehicle.getVehicleVin());
}
return R.ok(vehiclePositionVos);
}
}
/**
* 查询车辆信息
* @param vehicleVin
* @return
*/
@GetMapping("/inner/vehicle/findByVehicleVin")
public R findByVehicleVin(@RequestParam("vehicleVin") String vehicleVin){
XpVehicle xpVehicle = xpVehicleService.getOne(new LambdaQueryWrapper().in(XpVehicle::getVehicleVin, SensitiveDataCryptoUtil.queryValues(vehicleVin)));
// 获取车队
if (Objects.nonNull(xpVehicle)) {
XpFleetVehicle xpFleetVehicle = vehicleService.getOne(new LambdaQueryWrapper().eq(XpFleetVehicle::getVehicleId, xpVehicle.getId()));
if (Objects.nonNull(xpFleetVehicle)) {
xpVehicle.setFleetId(xpFleetVehicle.getFleetId());
}
}
return R.ok(xpVehicle);
}
/**
* 根据XPUID查询车辆信息
* @param xpuid
* @return
*/
@GetMapping("/inner/vehicle/findByVehicleXpuId")
public R findByVehicleXpuId(@RequestParam("xpuid") String xpuid){
XpVehicle xpVehicle = xpVehicleService.getOne(new LambdaQueryWrapper().eq(XpVehicle::getXpuId, xpuid));
return R.ok(xpVehicle);
}
/**
* 修改车辆状态
* @param updateVehicleStateDto
* @return
*/
@PostMapping("/inner/vehicle/updateVehicleStatus")
public R> updateVehicleStatus(@RequestBody UpdateVehicleStateDto updateVehicleStateDto){
xpVehicleService.update(new LambdaUpdateWrapper().in(XpVehicle::getVehicleVin, SensitiveDataCryptoUtil.queryValues(updateVehicleStateDto.getVin())).set(XpVehicle::getState, updateVehicleStateDto.getState()));
return R.ok();
}
/**
*
* 查询车辆所属车队ID
* @param vehicleId
* @return
*/
@GetMapping("/inner/vehicle/findFleetIdByVehicleId")
public R findFleetIdByVehicleId(@RequestParam Long vehicleId){
XpFleetVehicle xpFleetVehicle = vehicleService.getOne(new LambdaQueryWrapper().eq(XpFleetVehicle::getVehicleId, vehicleId));
return R.ok(xpFleetVehicle.getFleetId());
}
/**
* 查询可改派的车辆列表
*
* @return 车辆列表
*/
@ApiOperation(value = "查询可改派的车辆列表", tags = {"管理后台-订单管理"})
@GetMapping("/api/v1/vehicle/availableList")
@ApiImplicitParams({
@ApiImplicitParam(name = "orderId", value = "订单ID", required = true, dataTypeClass = Long.class),
@ApiImplicitParam(name = "orderType", value = "订单类型(1=用户单,2=测试单)", required = true, dataTypeClass = Integer.class)
})
public R> listAvailableVehicles(@RequestParam Long orderId, Integer orderType) {
List list = xpVehicleService.listAvailableVehicles(orderId, orderType);
return R.ok(list);
}
/**
* 车辆控制消息回调通知
* 开关门、设定温度
* @param callbackVo
*/
@PostMapping("/base/vehicles/command/callback")
public void vehiclesCommandCallback(@RequestBody VehiclesCommandCallbackVo callbackVo){
log.info("车辆控制消息回调:{}", JSON.toJSONString(callbackVo));
if(null != callbackVo.getMsg_ref()){
//继续执行业务逻辑上的代码
}
}
/**
* 车辆详情-车辆调度-接单、测试
*/
@ApiOperation(value = "车辆详情", tags = {"运维APP-车辆管理"})
@PostMapping("/api/v1/vehicle/vehicleDetail")
public R vehicleDetail(@RequestParam Long id) {
// 查询车辆
XpVehicle xpVehicle = xpVehicleService.getById(id);
XpVehicleVO xpVehicleVO = new XpVehicleVO();
BeanUtils.copyProperties(xpVehicle, xpVehicleVO);
// 查询运维规则 保养周期
XpTaskRule xpTaskRule = xpTaskRuleClient.queryDetail().getData();
if(Objects.isNull(xpTaskRule)){
return R.ok(xpVehicleVO);
}
Boolean maintenanceCycle = xpTaskRule.getMaintenanceCycle();
Integer maintenanceCycleTime = xpTaskRule.getMaintenanceCycleTime();
Integer maintenanceCycleTimeType = xpTaskRule.getMaintenanceCycleTimeType();
if(maintenanceCycle){
// 查询车辆上次保养记录
XpTask xpTask = xpTaskClient.queryInfoByVehicleId(id).getData();
if(Objects.nonNull(xpTask)){
// 存在保养记录
LocalDateTime handleTime = xpTask.getCreateTime();
switch (maintenanceCycleTimeType){
case 1:
handleTime.plusDays(maintenanceCycleTime);
break;
default:
handleTime.plusMonths(maintenanceCycleTime);
break;
}
xpVehicleVO.setNextMaintenanceTime(handleTime.toLocalDate());
}else {
// 没有保养记录
// 入库时间
LocalDateTime warehousingTime = xpVehicle.getWarehousingTime();
if(Objects.nonNull(warehousingTime)){
switch (maintenanceCycleTimeType){
case 1:
warehousingTime.plusDays(maintenanceCycleTime);
break;
default:
warehousingTime.plusMonths(maintenanceCycleTime);
break;
}
xpVehicleVO.setNextMaintenanceTime(warehousingTime.toLocalDate());
}
}
}
// 查询车辆的续航里程
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());
}else {
xpVehicleVO.setMileageRange(0F);
xpVehicleVO.setCurrentElectricity(0);
}
if (StringUtils.isNotBlank(xpVehicleVO.getVehiclePictures())) {
JSONObject jsonObject = JSONObject.parseObject(xpVehicleVO.getVehiclePictures());
if(jsonObject!=null){
String downloadUrl = null;
try {
downloadUrl= aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
} catch (Exception e) {
e.printStackTrace();
}
jsonObject.put("url", downloadUrl);
xpVehicleVO.setVehiclePictures(jsonObject.toJSONString());
}
}
// 判断车辆维修状态
Integer maintenanceStatus = xpVehicleRepairClient.getMaintenanceStatus(xpVehicleVO.getId()).getData();
xpVehicleVO.setMaintenanceStatus(maintenanceStatus);
return R.ok(xpVehicleVO);
}
/**
* 车辆管理-地图
* @return 车辆列表
*/
@ApiOperation(value = "车辆管理-地图", tags = {"运维APP-车辆管理"})
@PostMapping("/api/v1/vehicle/vehicleMap")
public R> vehicleMap(@RequestBody VehicleMapQuery query) {
Long operationsUserId = tokenService.getLoginOperationUser().getId();
query.setOperationsUserId(operationsUserId);
return R.ok(xpVehicleService.pageList(query));
}
/**
* 根据车牌号查询车辆信息
* @param plateNumber
* @return
*/
@PostMapping("/inner/vehicle/listByPlateNumber")
R> listByPlateNumber(@RequestParam("plateNumber") String plateNumber){
return R.ok(xpVehicleService.listByPlateNumber(plateNumber));
}
/**
* 车辆详情-车门解锁/后备箱解锁
*/
@ApiOperation(value = "车辆运维-车门解锁/后备箱解锁", tags = {"运维APP-车辆管理"})
@PostMapping("/api/v1/vehicle/unlockByType")
public R unlockByType(@RequestBody UnlockByTypeDTO dto) {
// TODO 车门解锁、后备箱解锁
return R.ok();
}
/**
* 业务流程-车门解锁/后备箱解锁
*/
@ApiOperation(value = "打开车门解锁/后备箱开关车门", tags = {"小程序-业务流程"})
@PostMapping("/api/v1/mini-program/order/vehicle/unlock")
public R unlock(@RequestBody UnlockAppletDTO dto) {
return R.ok(xpVehicleService.unlock(dto));
}
/**
* 业务流程-打开设置温度
*/
@ApiOperation(value = "打开设置温度", tags = {"小程序-业务流程"})
@PostMapping("/api/v1/mini-program/order/ac/set-temperature")
public R setTemperature(@RequestBody SetTemperatureDTO dto) {
return R.ok(xpVehicleService.setTemperature(dto));
}
/**
* 业务流程-通过订单id查询当前车辆温度
*/
@ApiOperation(value = "通过订单id查询当前车辆温度", tags = {"小程序-业务流程"})
@GetMapping("/api/v1/mini-program/order/queryTemperatureByOrderId")
public R queryTemperatureByOrderId(@RequestParam Long orderId, HttpServletRequest request) {
String uid = request.getHeader("XP-Uid");
VehicleTemperatureDto vehicleTemperatureDto = xpVehicleService.queryTemperatureByOrderId(orderId,uid);
VehicleStateVo hvacInnerTemp = vehicleTemperatureDto.getStateMap().get("hvacInnerTemp");
return R.ok(hvacInnerTemp.getValue());
}
@ApiOperation(value = "通过订单id查询当前车辆车门状态", tags = {"小程序-业务流程"})
@GetMapping("/api/v1/mini-program/order/queryVehicleTrunkOpenStatus")
public R queryVehicleTrunkOpenStatus(@RequestParam Long orderId, HttpServletRequest request) {
String uid = request.getHeader("XP-Uid");
VehicleTemperatureDto vehicleTemperatureDto = xpVehicleService.queryTemperatureByOrderId(orderId,uid);
return R.ok(vehicleTemperatureDto);
}
/**
* 获取车辆不分页列表
*
* @param query
* @return
*/
@ApiOperation(value = "获取车辆分页列表", tags = {"管理后台-车队管理"})
@PostMapping("/api/v1/vehicle/page-list")
public R> getVehiclePageList(@RequestBody VehicleSelectPageQuery query) {
return R.ok(xpVehicleService.getVehiclePageList(query));
}
/**
* 临时用于修改车辆接单状态
* @param updateVehicleStateDto
* @return
*/
@PostMapping("/base/vi/vehicle/updateVehicleStatus")
public R> testUpdateVehicleStatus(@RequestBody UpdateVehicleStateDto updateVehicleStateDto){
xpVehicleService.update(new LambdaUpdateWrapper().in(XpVehicle::getVehicleVin, SensitiveDataCryptoUtil.queryValues(updateVehicleStateDto.getVin())).set(XpVehicle::getState, updateVehicleStateDto.getState()));
return R.ok();
}
/**
* 删除mongodb中的文档
* @param collectionName
* @return
*/
@PostMapping("/base/vi/removeMongodb")
public R> removeMongodb(String collectionName){
MongoCollection collection = mongoTemplate.getCollection(collectionName);
collection.drop();
return R.ok();
}
/**
* 获取车辆后备箱开关状态
* @param uid
* @param id
* @return
*/
@PostMapping("/inner/vi/vehicle/getVehicleTrunkOpenStatus")
public R getVehicleTrunkOpenStatus(@RequestParam("uid") Long uid, @RequestParam("id") Long id){
Boolean vehicleTrunkOpenStatus = xpVehicleService.getVehicleTrunkOpenStatus(uid, id);
return R.ok(vehicleTrunkOpenStatus);
}
}