开发完成等待测试
This commit is contained in:
parent
7b436316f1
commit
5309b98bcf
@ -0,0 +1,153 @@
|
||||
package com.qaup.collision.controller;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.qaup.common.constant.HttpStatus;
|
||||
import com.qaup.common.core.domain.AjaxResult;
|
||||
import com.qaup.collision.datacollector.dao.DataCollectorDao;
|
||||
import com.qaup.collision.datacollector.model.dto.BatteryStatusDTO;
|
||||
import com.qaup.collision.datacollector.model.dto.MissionContextDTO;
|
||||
import com.qaup.collision.datacollector.model.dto.UniversalVehicleStatusDTO;
|
||||
import com.qaup.collision.datacollector.service.VehicleManagerCacheService;
|
||||
import com.qaup.collision.datacollector.service.VehicleManagerQueryService;
|
||||
import com.qaup.collision.datacollector.service.VehicleStatusAggregationService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/vehicle-manager")
|
||||
@RequiredArgsConstructor
|
||||
public class VehicleManagerApiController {
|
||||
|
||||
private final VehicleManagerQueryService queryService;
|
||||
private final VehicleManagerCacheService cacheService;
|
||||
private final VehicleStatusAggregationService vehicleStatusAggregationService;
|
||||
private final DataCollectorDao dataCollectorDao;
|
||||
|
||||
@GetMapping("/vehicles/summary")
|
||||
public AjaxResult getVehicleSummary(@RequestParam(defaultValue = "30000") long staleMillis) {
|
||||
VehicleManagerQueryService.VehicleSummary summary = queryService.getVehicleSummary(staleMillis);
|
||||
return AjaxResult.success(summary).put("timestamp", System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@GetMapping("/vehicles/{vehicleId}")
|
||||
public AjaxResult getVehicleSnapshot(@PathVariable String vehicleId) {
|
||||
Map<String, Object> snapshot = queryService.getVehicleSnapshot(vehicleId);
|
||||
return AjaxResult.success(snapshot).put("timestamp", System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@GetMapping("/vehicles/{vehicleId}/battery")
|
||||
public AjaxResult getVehicleBattery(@PathVariable String vehicleId) {
|
||||
UniversalVehicleStatusDTO status = dataCollectorDao.getVehicleManagerStatus(vehicleId);
|
||||
|
||||
BatteryStatusDTO batteryStatus = status != null ? status.getBatteryStatus() : null;
|
||||
if (batteryStatus == null) {
|
||||
batteryStatus = BatteryStatusDTO.builder()
|
||||
.mainBattery(BatteryStatusDTO.MainBatteryDTO.builder()
|
||||
.chargeLevel(null)
|
||||
.voltage(0.0)
|
||||
.current(0.0)
|
||||
.temperature(0.0)
|
||||
.chargingStatus("DISCHARGING")
|
||||
.build())
|
||||
.build();
|
||||
} else if (batteryStatus.getMainBattery() == null) {
|
||||
batteryStatus.setMainBattery(BatteryStatusDTO.MainBatteryDTO.builder()
|
||||
.chargeLevel(null)
|
||||
.voltage(0.0)
|
||||
.current(0.0)
|
||||
.temperature(0.0)
|
||||
.chargingStatus("DISCHARGING")
|
||||
.build());
|
||||
}
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("vehicleId", vehicleId);
|
||||
data.put("batteryStatus", batteryStatus);
|
||||
data.put("source", "vehicle_manager_http");
|
||||
data.put("upstreamTimestamp", status != null ? status.getOperationalStatus() != null ? status.getOperationalStatus().getLastHeartbeat() : null : null);
|
||||
|
||||
return AjaxResult.success(data).put("timestamp", System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@GetMapping("/vehicles/{vehicleId}/path")
|
||||
public AjaxResult getVehiclePath(@PathVariable String vehicleId) {
|
||||
VehicleManagerCacheService.CacheEntry entry = cacheService.getVehiclePath(vehicleId);
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("vehicleId", vehicleId);
|
||||
|
||||
if (entry != null && entry.getData() != null && entry.getData().has("path")) {
|
||||
JsonNode rawPath = entry.getData().get("path");
|
||||
data.put("rawPath", rawPath);
|
||||
data.put("waypoints", convertRawPathToWaypoints(rawPath));
|
||||
data.put("source", "ws_cache");
|
||||
return AjaxResult.success(data).put("timestamp", System.currentTimeMillis());
|
||||
}
|
||||
|
||||
UniversalVehicleStatusDTO status = vehicleStatusAggregationService.getAggregatedStatus(vehicleId);
|
||||
if (status == null || status.getMissionContext() == null) {
|
||||
return AjaxResult.error(HttpStatus.NOT_FOUND, "车辆路径不可用").put("timestamp", System.currentTimeMillis());
|
||||
}
|
||||
|
||||
MissionContextDTO missionContext = status.getMissionContext();
|
||||
data.put("rawPath", null);
|
||||
data.put("waypoints", missionContext.getWaypoints() != null ? missionContext.getWaypoints() : List.of());
|
||||
data.put("source", "http_fallback");
|
||||
|
||||
return AjaxResult.success(data).put("timestamp", System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@GetMapping("/charging-stations")
|
||||
public AjaxResult getChargingStations() {
|
||||
return AjaxResult.success(List.of()).put("timestamp", System.currentTimeMillis());
|
||||
}
|
||||
|
||||
private List<MissionContextDTO.WaypointDTO> convertRawPathToWaypoints(JsonNode rawPath) {
|
||||
if (rawPath == null || !rawPath.isArray()) {
|
||||
return List.of();
|
||||
}
|
||||
List<MissionContextDTO.WaypointDTO> waypoints = new ArrayList<>();
|
||||
int index = 1;
|
||||
for (JsonNode node : rawPath) {
|
||||
Double x = getDouble(node, "x");
|
||||
Double y = getDouble(node, "y");
|
||||
waypoints.add(MissionContextDTO.WaypointDTO.builder()
|
||||
.waypointId(String.valueOf(index++))
|
||||
.longitude(x)
|
||||
.latitude(y)
|
||||
.status("PENDING")
|
||||
.build());
|
||||
}
|
||||
return waypoints;
|
||||
}
|
||||
|
||||
private Double getDouble(JsonNode node, String field) {
|
||||
if (node == null || node.isMissingNode()) {
|
||||
return null;
|
||||
}
|
||||
JsonNode value = node.get(field);
|
||||
if (value == null || value.isNull()) {
|
||||
return null;
|
||||
}
|
||||
if (value.isNumber()) {
|
||||
return value.asDouble();
|
||||
}
|
||||
if (value.isTextual()) {
|
||||
try {
|
||||
return Double.parseDouble(value.asText());
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,125 @@
|
||||
package com.qaup.collision.controller;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.qaup.common.constant.HttpStatus;
|
||||
import com.qaup.common.core.domain.AjaxResult;
|
||||
import com.qaup.collision.task.InMemoryTaskStore;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/vehicle-manager/tasks")
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
public class VehicleTaskController {
|
||||
|
||||
private final InMemoryTaskStore taskStore;
|
||||
|
||||
@Data
|
||||
public static class TaskUpsertRequest {
|
||||
@NotBlank
|
||||
private String name;
|
||||
|
||||
@NotBlank
|
||||
private String type;
|
||||
|
||||
private Integer priority;
|
||||
|
||||
private JsonNode payload;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public AjaxResult list(@RequestParam(required = false, defaultValue = "1") int pageNum,
|
||||
@RequestParam(required = false, defaultValue = "10") int pageSize) {
|
||||
List<InMemoryTaskStore.Task> all = taskStore.listAll();
|
||||
int total = all.size();
|
||||
|
||||
int fromIndex = Math.max(0, (pageNum - 1) * pageSize);
|
||||
int toIndex = Math.min(total, fromIndex + pageSize);
|
||||
List<InMemoryTaskStore.Task> page = fromIndex >= total ? List.of() : all.subList(fromIndex, toIndex);
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("total", total);
|
||||
data.put("pageNum", pageNum);
|
||||
data.put("pageSize", pageSize);
|
||||
data.put("rows", page);
|
||||
|
||||
return AjaxResult.success(data).put("timestamp", System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public AjaxResult get(@PathVariable long id) {
|
||||
InMemoryTaskStore.Task task = taskStore.get(id);
|
||||
if (task == null) {
|
||||
return AjaxResult.error(HttpStatus.NOT_FOUND, "任务不存在").put("timestamp", System.currentTimeMillis());
|
||||
}
|
||||
return AjaxResult.success(task).put("timestamp", System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public AjaxResult create(@Valid @RequestBody TaskUpsertRequest request) {
|
||||
InMemoryTaskStore.Task task = taskStore.create(
|
||||
request.getName(),
|
||||
request.getType(),
|
||||
request.getPriority(),
|
||||
request.getPayload()
|
||||
);
|
||||
return AjaxResult.success(task).put("timestamp", System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public AjaxResult update(@PathVariable long id, @Valid @RequestBody TaskUpsertRequest request) {
|
||||
InMemoryTaskStore.Task task = taskStore.update(
|
||||
id,
|
||||
request.getName(),
|
||||
request.getType(),
|
||||
request.getPriority(),
|
||||
request.getPayload()
|
||||
);
|
||||
if (task == null) {
|
||||
return AjaxResult.error(HttpStatus.NOT_FOUND, "任务不存在").put("timestamp", System.currentTimeMillis());
|
||||
}
|
||||
return AjaxResult.success(task).put("timestamp", System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@DeleteMapping("/{ids}")
|
||||
public AjaxResult delete(@PathVariable String ids) {
|
||||
List<Long> idList = parseIds(ids);
|
||||
int removed = taskStore.deleteMany(idList);
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("removed", removed);
|
||||
data.put("requested", idList.size());
|
||||
|
||||
return AjaxResult.success(data).put("timestamp", System.currentTimeMillis());
|
||||
}
|
||||
|
||||
private List<Long> parseIds(String ids) {
|
||||
if (ids == null || ids.isBlank()) {
|
||||
return List.of();
|
||||
}
|
||||
return List.of(ids.split(","))
|
||||
.stream()
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.map(s -> {
|
||||
try {
|
||||
return Long.parseLong(s);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(v -> v != null)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,190 @@
|
||||
package com.qaup.collision.datacollector.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class VehicleManagerQueryService {
|
||||
|
||||
private final VehicleManagerCacheService cacheService;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public static class VehicleSummary {
|
||||
private int totalCount;
|
||||
private int onlineCount;
|
||||
private int offlineCount;
|
||||
private int emergencyCount;
|
||||
private int faultCount;
|
||||
private int unknownCount;
|
||||
private long staleMillis;
|
||||
private long timestamp;
|
||||
}
|
||||
|
||||
public VehicleSummary getVehicleSummary(long staleMillis) {
|
||||
long now = System.currentTimeMillis();
|
||||
Set<String> vehicleIds = cacheService.getKnownVehicleIds();
|
||||
|
||||
int online = 0;
|
||||
int offline = 0;
|
||||
int emergency = 0;
|
||||
int fault = 0;
|
||||
int unknown = 0;
|
||||
|
||||
for (String vehicleId : vehicleIds) {
|
||||
VehicleManagerCacheService.CacheEntry loginEntry = cacheService.getVehicleLoginStatus(vehicleId);
|
||||
boolean loginStale = isStale(loginEntry, now, staleMillis);
|
||||
|
||||
if (loginEntry == null) {
|
||||
long lastSeenAt = getLastSeenAt(vehicleId);
|
||||
if (now - lastSeenAt > staleMillis) {
|
||||
offline++;
|
||||
} else {
|
||||
unknown++;
|
||||
}
|
||||
} else {
|
||||
if (loginStale) {
|
||||
offline++;
|
||||
} else {
|
||||
String loginStatus = getText(loginEntry.getData(), "loginStatus");
|
||||
if ("login".equalsIgnoreCase(loginStatus)) {
|
||||
online++;
|
||||
} else {
|
||||
offline++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VehicleManagerCacheService.CacheEntry suspendEntry = cacheService.getVehicleSuspend(vehicleId);
|
||||
if (!isStale(suspendEntry, now, staleMillis)) {
|
||||
Integer suspendStatus = getInt(suspendEntry != null ? suspendEntry.getData() : null, "suspendStatus");
|
||||
if (suspendStatus != null && suspendStatus != 0) {
|
||||
emergency++;
|
||||
}
|
||||
}
|
||||
|
||||
VehicleManagerCacheService.CacheEntry fmsEntry = cacheService.getVehicleFmsMessage(vehicleId);
|
||||
if (!isStale(fmsEntry, now, staleMillis)) {
|
||||
if (isFault(fmsEntry != null ? fmsEntry.getData() : null)) {
|
||||
fault++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return VehicleSummary.builder()
|
||||
.totalCount(vehicleIds.size())
|
||||
.onlineCount(online)
|
||||
.offlineCount(offline)
|
||||
.emergencyCount(emergency)
|
||||
.faultCount(fault)
|
||||
.unknownCount(unknown)
|
||||
.staleMillis(staleMillis)
|
||||
.timestamp(now)
|
||||
.build();
|
||||
}
|
||||
|
||||
public Map<String, Object> getVehicleSnapshot(String vehicleId) {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("vehicleId", vehicleId);
|
||||
|
||||
VehicleManagerCacheService.CacheEntry details = cacheService.getVehicleDetails(vehicleId);
|
||||
VehicleManagerCacheService.CacheEntry login = cacheService.getVehicleLoginStatus(vehicleId);
|
||||
VehicleManagerCacheService.CacheEntry chassis = cacheService.getVehicleChassis(vehicleId);
|
||||
VehicleManagerCacheService.CacheEntry order = cacheService.getVehicleOrder(vehicleId);
|
||||
VehicleManagerCacheService.CacheEntry suspend = cacheService.getVehicleSuspend(vehicleId);
|
||||
VehicleManagerCacheService.CacheEntry tailer = cacheService.getVehicleTailer(vehicleId);
|
||||
VehicleManagerCacheService.CacheEntry position = cacheService.getVehiclePosition(vehicleId);
|
||||
VehicleManagerCacheService.CacheEntry path = cacheService.getVehiclePath(vehicleId);
|
||||
VehicleManagerCacheService.CacheEntry fms = cacheService.getVehicleFmsMessage(vehicleId);
|
||||
|
||||
data.put("details", details != null ? details.getData() : null);
|
||||
data.put("loginStatus", login != null ? login.getData() : null);
|
||||
data.put("chassis", chassis != null ? chassis.getData() : null);
|
||||
data.put("order", order != null ? order.getData() : null);
|
||||
data.put("suspend", suspend != null ? suspend.getData() : null);
|
||||
data.put("tailer", tailer != null ? tailer.getData() : null);
|
||||
data.put("position", position != null ? position.getData() : null);
|
||||
data.put("path", path != null ? path.getData() : null);
|
||||
data.put("fmsMessage", fms != null ? fms.getData() : null);
|
||||
data.put("lastSeenAt", getLastSeenAt(vehicleId));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private long getLastSeenAt(String vehicleId) {
|
||||
long last = 0L;
|
||||
last = Math.max(last, getTimestamp(cacheService.getVehicleDetails(vehicleId)));
|
||||
last = Math.max(last, getTimestamp(cacheService.getVehicleLoginStatus(vehicleId)));
|
||||
last = Math.max(last, getTimestamp(cacheService.getVehicleChassis(vehicleId)));
|
||||
last = Math.max(last, getTimestamp(cacheService.getVehicleOrder(vehicleId)));
|
||||
last = Math.max(last, getTimestamp(cacheService.getVehicleSuspend(vehicleId)));
|
||||
last = Math.max(last, getTimestamp(cacheService.getVehicleTailer(vehicleId)));
|
||||
last = Math.max(last, getTimestamp(cacheService.getVehiclePosition(vehicleId)));
|
||||
last = Math.max(last, getTimestamp(cacheService.getVehiclePath(vehicleId)));
|
||||
last = Math.max(last, getTimestamp(cacheService.getVehicleFmsMessage(vehicleId)));
|
||||
return last;
|
||||
}
|
||||
|
||||
private long getTimestamp(VehicleManagerCacheService.CacheEntry entry) {
|
||||
return entry == null ? 0L : entry.getTimestamp();
|
||||
}
|
||||
|
||||
private boolean isStale(VehicleManagerCacheService.CacheEntry entry, long now, long staleMillis) {
|
||||
if (entry == null) {
|
||||
return true;
|
||||
}
|
||||
return now - entry.getTimestamp() > staleMillis;
|
||||
}
|
||||
|
||||
private boolean isFault(JsonNode node) {
|
||||
if (node == null || node.isNull()) {
|
||||
return false;
|
||||
}
|
||||
Integer isActive = getInt(node, "isActive");
|
||||
if (isActive != null && isActive == 1) {
|
||||
return true;
|
||||
}
|
||||
Integer level = getInt(node, "level");
|
||||
return level != null && level >= 4;
|
||||
}
|
||||
|
||||
private Integer getInt(JsonNode node, String field) {
|
||||
if (node == null || node.isMissingNode()) {
|
||||
return null;
|
||||
}
|
||||
JsonNode value = node.get(field);
|
||||
if (value == null || value.isNull()) {
|
||||
return null;
|
||||
}
|
||||
if (value.isInt() || value.isLong()) {
|
||||
return value.asInt();
|
||||
}
|
||||
if (value.isTextual()) {
|
||||
try {
|
||||
return Integer.parseInt(value.asText());
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getText(JsonNode node, String field) {
|
||||
if (node == null || node.isMissingNode()) {
|
||||
return null;
|
||||
}
|
||||
JsonNode value = node.get(field);
|
||||
if (value == null || value.isNull()) {
|
||||
return null;
|
||||
}
|
||||
return value.asText();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
package com.qaup.collision.task;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
@Component
|
||||
public class InMemoryTaskStore {
|
||||
|
||||
private final AtomicLong idGenerator = new AtomicLong(1);
|
||||
private final Map<Long, Task> tasks = new ConcurrentHashMap<>();
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public static class Task {
|
||||
private Long id;
|
||||
private String name;
|
||||
private String type;
|
||||
private Integer priority;
|
||||
private JsonNode payload;
|
||||
private long createdAt;
|
||||
private long updatedAt;
|
||||
}
|
||||
|
||||
public Task create(String name, String type, Integer priority, JsonNode payload) {
|
||||
long now = System.currentTimeMillis();
|
||||
long id = idGenerator.getAndIncrement();
|
||||
Task task = Task.builder()
|
||||
.id(id)
|
||||
.name(name)
|
||||
.type(type)
|
||||
.priority(priority)
|
||||
.payload(payload)
|
||||
.createdAt(now)
|
||||
.updatedAt(now)
|
||||
.build();
|
||||
tasks.put(id, task);
|
||||
return task;
|
||||
}
|
||||
|
||||
public Task update(long id, String name, String type, Integer priority, JsonNode payload) {
|
||||
Task existing = tasks.get(id);
|
||||
if (existing == null) {
|
||||
return null;
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
if (name != null) {
|
||||
existing.setName(name);
|
||||
}
|
||||
if (type != null) {
|
||||
existing.setType(type);
|
||||
}
|
||||
existing.setPriority(priority);
|
||||
existing.setPayload(payload);
|
||||
existing.setUpdatedAt(now);
|
||||
return existing;
|
||||
}
|
||||
|
||||
public Task get(long id) {
|
||||
return tasks.get(id);
|
||||
}
|
||||
|
||||
public List<Task> listAll() {
|
||||
List<Task> list = new ArrayList<>(tasks.values());
|
||||
list.sort(Comparator.comparing(Task::getId).reversed());
|
||||
return list;
|
||||
}
|
||||
|
||||
public int deleteMany(List<Long> ids) {
|
||||
int removed = 0;
|
||||
for (Long id : ids) {
|
||||
if (id == null) {
|
||||
continue;
|
||||
}
|
||||
if (tasks.remove(id) != null) {
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
}
|
||||
@ -119,6 +119,7 @@ public class SecurityConfig
|
||||
// 前端 API 接口,允许匿名访问(测试阶段用)
|
||||
.requestMatchers("/system/**").permitAll()
|
||||
.requestMatchers("/api/v1/vehicles/**").permitAll()
|
||||
.requestMatchers("/api/vehicle-manager/**").permitAll()
|
||||
// 除上面外的所有请求全部需要鉴权认证
|
||||
.anyRequest().authenticated();
|
||||
})
|
||||
|
||||
222
前端开发_无人车VehicleManager接口.md
Normal file
222
前端开发_无人车VehicleManager接口.md
Normal file
@ -0,0 +1,222 @@
|
||||
# 前端开发文档:无人车 VehicleManager HTTP 接口
|
||||
|
||||
本文档描述本次新增的前端 HTTP 接口(基于“接口文档1/2”无人车数据整合)。
|
||||
|
||||
## 1. 基本信息
|
||||
|
||||
- **服务地址**:`http://localhost:8080`
|
||||
- **接口前缀**:`/api/vehicle-manager`
|
||||
- **鉴权**:当前已在后端放开 `permitAll`(无需 token 即可访问该前缀)。后续如恢复鉴权,前端需要按系统登录流程携带 `Authorization`。
|
||||
- **返回格式**:RuoYi 风格 `AjaxResult`
|
||||
- `code`: 200 成功
|
||||
- `msg`: 提示信息
|
||||
- `data`: 业务数据
|
||||
- `timestamp`: 后端附加的时间戳(毫秒)
|
||||
|
||||
## 2. 接口列表
|
||||
|
||||
### 2.1 车辆统计汇总
|
||||
|
||||
**GET** `/api/vehicle-manager/vehicles/summary`
|
||||
|
||||
#### Query
|
||||
- `staleMillis`(可选,默认 `30000`):缓存过期阈值(毫秒)。超过该阈值会被视为离线/不可用。
|
||||
|
||||
#### Response.data
|
||||
```json
|
||||
{
|
||||
"totalCount": 10,
|
||||
"onlineCount": 8,
|
||||
"offlineCount": 2,
|
||||
"emergencyCount": 1,
|
||||
"faultCount": 1,
|
||||
"unknownCount": 0,
|
||||
"staleMillis": 30000,
|
||||
"timestamp": 1730000000000
|
||||
}
|
||||
```
|
||||
|
||||
#### 说明
|
||||
- online 判定:`VehicleLoginStatus.loginStatus == "login"` 且未过期
|
||||
- emergency 判定:`VehicleSuspendReport.suspendStatus != 0` 且未过期
|
||||
- fault(故障)临时规则:
|
||||
- `GetFmsMessage.isActive == 1`(字段存在时)或
|
||||
- `GetFmsMessage.level >= 4`
|
||||
|
||||
---
|
||||
|
||||
### 2.2 车辆详情快照(缓存聚合)
|
||||
|
||||
**GET** `/api/vehicle-manager/vehicles/{vehicleId}`
|
||||
|
||||
#### Path
|
||||
- `vehicleId`:车号(例如 `AET01`)
|
||||
|
||||
#### Response.data
|
||||
```json
|
||||
{
|
||||
"vehicleId": "AET01",
|
||||
"details": { "messageName": "VehicleDetails", "vehicleId": "AET01", "vehicleType": "QTRUCK" },
|
||||
"loginStatus": { "messageName": "VehicleLoginStatus", "vehicleId": "AET01", "loginStatus": "login" },
|
||||
"position": { "messageName": "VehiclePositionInfo", "vehicleId": "AET01", "x": 6.12, "y": 101.70 },
|
||||
"chassis": { "messageName": "VehicleChassisInfo", "vehicleId": "AET01", "sys_info": { "state_info": { "d_speed_kmph": 0, "d_battery_available": "0.0" } } },
|
||||
"suspend": { "messageName": "VehicleSuspendReport", "vehicleId": "AET01", "suspendStatus": 0 },
|
||||
"tailer": { "messageName": "VehicleTailerNum", "vehicleId": "AET01", "tailerNum": "0" },
|
||||
"order": { "messageName": "VehicleOrderInfo", "vehicleId": "AET01", "businessKey": "1767..." },
|
||||
"path": { "messageName": "NaviShortPathReport", "vehicleId": "AET01", "path": [ {"x": 1, "y": 2, "theta": 0} ] },
|
||||
"fmsMessage": { "messageName": "GetFmsMessage", "vehicleID": "AET01", "level": 4, "description": "..." },
|
||||
"lastSeenAt": 1730000000000
|
||||
}
|
||||
```
|
||||
|
||||
#### 说明
|
||||
- 该接口只读后端内存缓存,不会额外打上游 HTTP。
|
||||
- 字段可能为 `null`(例如 WS 尚未推送/缓存未命中)。
|
||||
|
||||
---
|
||||
|
||||
### 2.3 电池信息(通过接口文档2上游 HTTP 获取后转发)
|
||||
|
||||
**GET** `/api/vehicle-manager/vehicles/{vehicleId}/battery`
|
||||
|
||||
#### 行为
|
||||
- 后端会调用上游(接口文档2):`POST /api/vehicle_manager/v1/vehicles/{vehicleId}/status`
|
||||
- 并把其中的 `batteryStatus` 转发给前端
|
||||
|
||||
#### Response.data
|
||||
```json
|
||||
{
|
||||
"vehicleId": "AET01",
|
||||
"batteryStatus": {
|
||||
"mainBattery": {
|
||||
"chargeLevel": 0.0,
|
||||
"voltage": 0.0,
|
||||
"current": 0.0,
|
||||
"temperature": 0.0,
|
||||
"chargingStatus": "DISCHARGING"
|
||||
}
|
||||
},
|
||||
"source": "vehicle_manager_http",
|
||||
"upstreamTimestamp": 1767838334301.7153
|
||||
}
|
||||
```
|
||||
|
||||
#### 说明
|
||||
- 上游不可达或无数据时,后端会返回默认结构(voltage/current/temperature=0,chargingStatus=DISCHARGING)。
|
||||
|
||||
---
|
||||
|
||||
### 2.4 车辆路径
|
||||
|
||||
**GET** `/api/vehicle-manager/vehicles/{vehicleId}/path`
|
||||
|
||||
#### Response.data
|
||||
```json
|
||||
{
|
||||
"vehicleId": "AET01",
|
||||
"rawPath": [
|
||||
{ "x": 6.12613, "y": 101.70456, "theta": -1.52245 }
|
||||
],
|
||||
"waypoints": [
|
||||
{ "waypointId": "1", "longitude": 6.12613, "latitude": 101.70456, "status": "PENDING" }
|
||||
],
|
||||
"source": "ws_cache"
|
||||
}
|
||||
```
|
||||
|
||||
#### 说明
|
||||
- `source=ws_cache`:来自文档1 `NaviShortPathReport.path`
|
||||
- `source=http_fallback`:WS 缓存没有路径时,后端会走聚合服务兜底(从 `missionContext.waypoints` 返回)
|
||||
|
||||
---
|
||||
|
||||
### 2.5 充电桩(占位接口)
|
||||
|
||||
**GET** `/api/vehicle-manager/charging-stations`
|
||||
|
||||
#### Response.data
|
||||
```json
|
||||
[]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 任务 CRUD(内存,不落库)
|
||||
|
||||
> 注意:这是“前端任务管理/任务库”的 CRUD,数据仅存在于后端内存中,服务重启会清空。
|
||||
|
||||
### 3.1 列表
|
||||
|
||||
**GET** `/api/vehicle-manager/tasks?pageNum=1&pageSize=10`
|
||||
|
||||
#### Response.data
|
||||
```json
|
||||
{
|
||||
"total": 1,
|
||||
"pageNum": 1,
|
||||
"pageSize": 10,
|
||||
"rows": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "测试任务1",
|
||||
"type": "CARGO_TRANSPORT",
|
||||
"priority": 3,
|
||||
"payload": { "note": "from frontend" },
|
||||
"createdAt": 1730000000000,
|
||||
"updatedAt": 1730000000000
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 创建
|
||||
|
||||
**POST** `/api/vehicle-manager/tasks`
|
||||
|
||||
Body:
|
||||
```json
|
||||
{
|
||||
"name": "测试任务1",
|
||||
"type": "CARGO_TRANSPORT",
|
||||
"priority": 3,
|
||||
"payload": {
|
||||
"note": "from frontend"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 详情
|
||||
|
||||
**GET** `/api/vehicle-manager/tasks/{id}`
|
||||
|
||||
### 3.4 更新
|
||||
|
||||
**PUT** `/api/vehicle-manager/tasks/{id}`
|
||||
|
||||
Body(同创建结构):
|
||||
```json
|
||||
{
|
||||
"name": "测试任务1-已更新",
|
||||
"type": "CARGO_TRANSPORT",
|
||||
"priority": 5,
|
||||
"payload": { "note": "updated" }
|
||||
}
|
||||
```
|
||||
|
||||
### 3.5 删除(支持批量)
|
||||
|
||||
**DELETE** `/api/vehicle-manager/tasks/{ids}`
|
||||
|
||||
示例:
|
||||
- 删除单个:`/api/vehicle-manager/tasks/1`
|
||||
- 删除多个:`/api/vehicle-manager/tasks/1,2,3`
|
||||
|
||||
---
|
||||
|
||||
## 4. 前端接入建议
|
||||
|
||||
1) 页面初始化先调:`/vehicles/summary` 拿到统计。
|
||||
2) 点击某辆车再调:`/vehicles/{vehicleId}` 拿快照。
|
||||
3) 电池面板单独调:`/vehicles/{vehicleId}/battery`(该接口会请求上游,避免高频轮询)。
|
||||
4) 路径面板调:`/vehicles/{vehicleId}/path`。
|
||||
5) 任务管理页面直接使用 `/tasks` CRUD。
|
||||
Loading…
Reference in New Issue
Block a user