Initial commit
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
package com.noblelift.ota.module.vehicle.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckRole;
|
||||
import com.noblelift.ota.common.annotation.Log;
|
||||
import com.noblelift.ota.module.vehicle.dto.VehicleView;
|
||||
import com.noblelift.ota.module.vehicle.param.RegisterVehicleParam;
|
||||
import com.noblelift.ota.module.vehicle.service.VehicleService;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/vehicles")
|
||||
@RequiredArgsConstructor
|
||||
public class VehicleController {
|
||||
|
||||
private final VehicleService vehicleService;
|
||||
|
||||
@SaCheckRole(value = {"SUPER_ADMIN", "ADMIN"}, mode = cn.dev33.satoken.annotation.SaMode.OR)
|
||||
@Log("注册车辆")
|
||||
@PostMapping
|
||||
public VehicleView registerVehicle(@Valid @RequestBody RegisterVehicleParam param) {
|
||||
return toView(vehicleService.registerVehicle(param));
|
||||
}
|
||||
|
||||
@SaCheckRole(value = {"SUPER_ADMIN", "ADMIN"}, mode = cn.dev33.satoken.annotation.SaMode.OR)
|
||||
@Log("修改车辆")
|
||||
@PutMapping("/{vehicleId}")
|
||||
public VehicleView updateVehicle(@PathVariable String vehicleId, @Valid @RequestBody RegisterVehicleParam param) {
|
||||
return toView(vehicleService.updateVehicle(vehicleId, param));
|
||||
}
|
||||
|
||||
@SaCheckRole(value = {"SUPER_ADMIN", "ADMIN"}, mode = cn.dev33.satoken.annotation.SaMode.OR)
|
||||
@Log("删除车辆")
|
||||
@DeleteMapping("/{vehicleId}")
|
||||
public void deleteVehicle(@PathVariable String vehicleId) {
|
||||
vehicleService.deleteVehicle(vehicleId);
|
||||
}
|
||||
|
||||
@SaCheckRole(value = {"SUPER_ADMIN", "ADMIN"}, mode = cn.dev33.satoken.annotation.SaMode.OR)
|
||||
@Log("查询车辆列表")
|
||||
@GetMapping
|
||||
public List<VehicleView> listVehicles() {
|
||||
return vehicleService.listVehicles().stream()
|
||||
.map(this::toView)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private VehicleView toView(com.noblelift.ota.common.model.VehicleInfo vehicle) {
|
||||
return new VehicleView(
|
||||
vehicle.getVehicleId(),
|
||||
vehicle.getVin(),
|
||||
vehicle.getCurrentRelease(),
|
||||
vehicle.getCurrentBackendVersion(),
|
||||
vehicle.getCurrentFrontendVersion(),
|
||||
vehicle.getCurrentRosVersion(),
|
||||
vehicle.getLastSeenAt(),
|
||||
vehicle.getAgentStatus(),
|
||||
vehicle.getTargetRelease(),
|
||||
vehicle.getLastResult(),
|
||||
vehicle.getImages(),
|
||||
vehicle.getBackupFile(),
|
||||
vehicle.getOnline()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.noblelift.ota.module.vehicle.dto;
|
||||
|
||||
import com.noblelift.ota.domain.AgentStatus;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class VehicleView {
|
||||
|
||||
private String vehicleId;
|
||||
private String vin;
|
||||
private String currentRelease;
|
||||
private String currentBackendVersion;
|
||||
private String currentFrontendVersion;
|
||||
private String currentRosVersion;
|
||||
private Instant lastSeenAt;
|
||||
private AgentStatus agentStatus;
|
||||
private String targetRelease;
|
||||
private String lastResult;
|
||||
private Map<String, String> images;
|
||||
private String backupFile;
|
||||
private Boolean online;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.noblelift.ota.module.vehicle.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@Data
|
||||
@TableName("ota_vehicle")
|
||||
public class VehicleEntity {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
private String vehicleId;
|
||||
private String vin;
|
||||
private String currentRelease;
|
||||
private String currentBackendVersion;
|
||||
private String currentFrontendVersion;
|
||||
private String currentRosVersion;
|
||||
private Instant lastSeenAt;
|
||||
private String agentStatus;
|
||||
private String targetRelease;
|
||||
private String lastResult;
|
||||
private String imagesJson;
|
||||
private String backupFile;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.noblelift.ota.module.vehicle.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.noblelift.ota.module.vehicle.entity.VehicleEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface VehicleMapper extends BaseMapper<VehicleEntity> {
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.noblelift.ota.module.vehicle.param;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RegisterVehicleParam {
|
||||
|
||||
@NotBlank
|
||||
private String vehicleId;
|
||||
|
||||
@NotBlank
|
||||
private String vin;
|
||||
|
||||
@NotBlank
|
||||
private String currentRelease;
|
||||
|
||||
private String currentBackendVersion;
|
||||
private String currentFrontendVersion;
|
||||
private String currentRosVersion;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.noblelift.ota.module.vehicle.service;
|
||||
|
||||
import com.noblelift.ota.common.model.VehicleInfo;
|
||||
import com.noblelift.ota.domain.AgentStatus;
|
||||
import com.noblelift.ota.module.vehicle.param.RegisterVehicleParam;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface VehicleService {
|
||||
|
||||
VehicleInfo registerVehicle(RegisterVehicleParam param);
|
||||
|
||||
VehicleInfo updateVehicle(String vehicleId, RegisterVehicleParam param);
|
||||
|
||||
void deleteVehicle(String vehicleId);
|
||||
|
||||
List<VehicleInfo> listVehicles();
|
||||
|
||||
VehicleInfo saveHeartbeat(String vehicleId,
|
||||
String vin,
|
||||
String currentRelease,
|
||||
AgentStatus agentStatus,
|
||||
String targetRelease,
|
||||
String lastResult,
|
||||
Map<String, String> images,
|
||||
String backupFile);
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
package com.noblelift.ota.module.vehicle.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.noblelift.ota.common.model.VehicleInfo;
|
||||
import com.noblelift.ota.config.OtaProperties;
|
||||
import com.noblelift.ota.domain.AgentStatus;
|
||||
import com.noblelift.ota.module.assignment.entity.VehicleAssignmentEntity;
|
||||
import com.noblelift.ota.module.assignment.mapper.VehicleAssignmentMapper;
|
||||
import com.noblelift.ota.module.vehicle.entity.VehicleEntity;
|
||||
import com.noblelift.ota.module.vehicle.mapper.VehicleMapper;
|
||||
import com.noblelift.ota.module.vehicle.param.RegisterVehicleParam;
|
||||
import com.noblelift.ota.module.vehicle.service.VehicleService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class VehicleServiceImpl implements VehicleService {
|
||||
|
||||
private static final TypeReference<Map<String, String>> STRING_MAP_TYPE = new TypeReference<>() {
|
||||
};
|
||||
|
||||
private final VehicleMapper vehicleMapper;
|
||||
private final VehicleAssignmentMapper vehicleAssignmentMapper;
|
||||
private final OtaProperties otaProperties;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public VehicleInfo registerVehicle(RegisterVehicleParam param) {
|
||||
VehicleEntity current = vehicleMapper.selectOne(new LambdaQueryWrapper<VehicleEntity>()
|
||||
.eq(VehicleEntity::getVehicleId, param.getVehicleId())
|
||||
.last("limit 1"));
|
||||
|
||||
VehicleInfo vehicleInfo = VehicleInfo.builder()
|
||||
.vehicleId(param.getVehicleId())
|
||||
.vin(param.getVin())
|
||||
.currentRelease(param.getCurrentRelease())
|
||||
.currentBackendVersion(param.getCurrentBackendVersion())
|
||||
.currentFrontendVersion(param.getCurrentFrontendVersion())
|
||||
.currentRosVersion(param.getCurrentRosVersion())
|
||||
.lastSeenAt(current == null ? null : current.getLastSeenAt())
|
||||
.agentStatus(current == null || current.getAgentStatus() == null ? AgentStatus.IDLE : AgentStatus.valueOf(current.getAgentStatus()))
|
||||
.targetRelease(current == null ? null : current.getTargetRelease())
|
||||
.lastResult(current == null ? null : current.getLastResult())
|
||||
.images(current == null ? Map.of() : parseImages(current.getImagesJson()))
|
||||
.backupFile(current == null ? null : current.getBackupFile())
|
||||
.online(current != null && isOnline(current.getLastSeenAt()))
|
||||
.build();
|
||||
|
||||
saveVehicle(vehicleInfo);
|
||||
return vehicleInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VehicleInfo updateVehicle(String vehicleId, RegisterVehicleParam param) {
|
||||
VehicleEntity existing = getEntity(vehicleId);
|
||||
VehicleEntity duplicate = vehicleMapper.selectOne(new LambdaQueryWrapper<VehicleEntity>()
|
||||
.eq(VehicleEntity::getVehicleId, param.getVehicleId())
|
||||
.last("limit 1"));
|
||||
if (duplicate != null && !duplicate.getId().equals(existing.getId())) {
|
||||
throw new IllegalArgumentException("车辆 ID 已存在:" + param.getVehicleId());
|
||||
}
|
||||
|
||||
VehicleInfo vehicleInfo = VehicleInfo.builder()
|
||||
.vehicleId(param.getVehicleId())
|
||||
.vin(param.getVin())
|
||||
.currentRelease(param.getCurrentRelease())
|
||||
.currentBackendVersion(param.getCurrentBackendVersion())
|
||||
.currentFrontendVersion(param.getCurrentFrontendVersion())
|
||||
.currentRosVersion(param.getCurrentRosVersion())
|
||||
.lastSeenAt(existing.getLastSeenAt())
|
||||
.agentStatus(existing.getAgentStatus() == null ? AgentStatus.IDLE : AgentStatus.valueOf(existing.getAgentStatus()))
|
||||
.targetRelease(existing.getTargetRelease())
|
||||
.lastResult(existing.getLastResult())
|
||||
.images(parseImages(existing.getImagesJson()))
|
||||
.backupFile(existing.getBackupFile())
|
||||
.online(isOnline(existing.getLastSeenAt()))
|
||||
.build();
|
||||
|
||||
saveVehicle(vehicleInfo, existing.getId());
|
||||
return vehicleInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteVehicle(String vehicleId) {
|
||||
VehicleEntity existing = getEntity(vehicleId);
|
||||
VehicleAssignmentEntity assignment = vehicleAssignmentMapper.selectOne(new LambdaQueryWrapper<VehicleAssignmentEntity>()
|
||||
.eq(VehicleAssignmentEntity::getVehicleId, vehicleId)
|
||||
.last("limit 1"));
|
||||
if (assignment != null) {
|
||||
throw new IllegalArgumentException("该车辆存在任务记录,无法删除:" + vehicleId);
|
||||
}
|
||||
vehicleMapper.deleteById(existing.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<VehicleInfo> listVehicles() {
|
||||
return vehicleMapper.selectList(new LambdaQueryWrapper<VehicleEntity>()
|
||||
.orderByAsc(VehicleEntity::getVehicleId))
|
||||
.stream()
|
||||
.map(this::toModel)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VehicleInfo saveHeartbeat(String vehicleId,
|
||||
String vin,
|
||||
String currentRelease,
|
||||
AgentStatus agentStatus,
|
||||
String targetRelease,
|
||||
String lastResult,
|
||||
Map<String, String> images,
|
||||
String backupFile) {
|
||||
VehicleEntity current = vehicleMapper.selectOne(new LambdaQueryWrapper<VehicleEntity>()
|
||||
.eq(VehicleEntity::getVehicleId, vehicleId)
|
||||
.last("limit 1"));
|
||||
|
||||
VehicleInfo info = VehicleInfo.builder()
|
||||
.vehicleId(vehicleId)
|
||||
.vin(vin)
|
||||
.currentRelease(currentRelease)
|
||||
.currentBackendVersion(current == null ? null : current.getCurrentBackendVersion())
|
||||
.currentFrontendVersion(current == null ? null : current.getCurrentFrontendVersion())
|
||||
.currentRosVersion(current == null ? null : current.getCurrentRosVersion())
|
||||
.lastSeenAt(Instant.now())
|
||||
.agentStatus(agentStatus == null
|
||||
? (current == null || current.getAgentStatus() == null ? AgentStatus.IDLE : AgentStatus.valueOf(current.getAgentStatus()))
|
||||
: agentStatus)
|
||||
.targetRelease(targetRelease == null && current != null ? current.getTargetRelease() : targetRelease)
|
||||
.lastResult(lastResult == null && current != null ? current.getLastResult() : lastResult)
|
||||
.images(images == null ? (current == null ? Map.of() : parseImages(current.getImagesJson())) : new LinkedHashMap<>(images))
|
||||
.backupFile(backupFile == null && current != null ? current.getBackupFile() : backupFile)
|
||||
.online(true)
|
||||
.build();
|
||||
saveVehicle(info, current == null ? null : current.getId());
|
||||
return info;
|
||||
}
|
||||
|
||||
private void saveVehicle(VehicleInfo vehicleInfo, Long existingId) {
|
||||
VehicleEntity entity = toEntity(vehicleInfo);
|
||||
if (existingId == null) {
|
||||
vehicleMapper.insert(entity);
|
||||
} else {
|
||||
entity.setId(existingId);
|
||||
vehicleMapper.updateById(entity);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveVehicle(VehicleInfo vehicleInfo) {
|
||||
VehicleEntity existing = vehicleMapper.selectOne(new LambdaQueryWrapper<VehicleEntity>()
|
||||
.eq(VehicleEntity::getVehicleId, vehicleInfo.getVehicleId())
|
||||
.last("limit 1"));
|
||||
saveVehicle(vehicleInfo, existing == null ? null : existing.getId());
|
||||
}
|
||||
|
||||
private VehicleEntity getEntity(String vehicleId) {
|
||||
VehicleEntity entity = vehicleMapper.selectOne(new LambdaQueryWrapper<VehicleEntity>()
|
||||
.eq(VehicleEntity::getVehicleId, vehicleId)
|
||||
.last("limit 1"));
|
||||
if (entity == null) {
|
||||
throw new IllegalArgumentException("Vehicle not found: " + vehicleId);
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
private VehicleInfo toModel(VehicleEntity entity) {
|
||||
return VehicleInfo.builder()
|
||||
.vehicleId(entity.getVehicleId())
|
||||
.vin(entity.getVin())
|
||||
.currentRelease(entity.getCurrentRelease())
|
||||
.currentBackendVersion(entity.getCurrentBackendVersion())
|
||||
.currentFrontendVersion(entity.getCurrentFrontendVersion())
|
||||
.currentRosVersion(entity.getCurrentRosVersion())
|
||||
.lastSeenAt(entity.getLastSeenAt())
|
||||
.agentStatus(entity.getAgentStatus() == null ? AgentStatus.IDLE : AgentStatus.valueOf(entity.getAgentStatus()))
|
||||
.targetRelease(entity.getTargetRelease())
|
||||
.lastResult(entity.getLastResult())
|
||||
.images(parseImages(entity.getImagesJson()))
|
||||
.backupFile(entity.getBackupFile())
|
||||
.online(isOnline(entity.getLastSeenAt()))
|
||||
.build();
|
||||
}
|
||||
|
||||
private VehicleEntity toEntity(VehicleInfo vehicleInfo) {
|
||||
VehicleEntity entity = new VehicleEntity();
|
||||
entity.setVehicleId(vehicleInfo.getVehicleId());
|
||||
entity.setVin(vehicleInfo.getVin());
|
||||
entity.setCurrentRelease(vehicleInfo.getCurrentRelease());
|
||||
entity.setCurrentBackendVersion(vehicleInfo.getCurrentBackendVersion());
|
||||
entity.setCurrentFrontendVersion(vehicleInfo.getCurrentFrontendVersion());
|
||||
entity.setCurrentRosVersion(vehicleInfo.getCurrentRosVersion());
|
||||
entity.setLastSeenAt(vehicleInfo.getLastSeenAt());
|
||||
entity.setAgentStatus(vehicleInfo.getAgentStatus() == null ? null : vehicleInfo.getAgentStatus().name());
|
||||
entity.setTargetRelease(vehicleInfo.getTargetRelease());
|
||||
entity.setLastResult(vehicleInfo.getLastResult());
|
||||
entity.setImagesJson(writeImages(vehicleInfo.getImages()));
|
||||
entity.setBackupFile(vehicleInfo.getBackupFile());
|
||||
return entity;
|
||||
}
|
||||
|
||||
private Map<String, String> parseImages(String imagesJson) {
|
||||
if (imagesJson == null || imagesJson.isBlank()) {
|
||||
return Map.of();
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(imagesJson, STRING_MAP_TYPE);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalStateException("Failed to parse imagesJson", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String writeImages(Map<String, String> images) {
|
||||
if (images == null || images.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return objectMapper.writeValueAsString(images);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalStateException("Failed to serialize imagesJson", e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isOnline(Instant lastSeenAt) {
|
||||
return lastSeenAt != null
|
||||
&& lastSeenAt.isAfter(Instant.now().minusSeconds(otaProperties.getHeartbeatTimeoutSeconds()));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user