add:第一版测试版本,第二次联调。

This commit is contained in:
2025-12-26 15:27:54 +08:00
parent cd483c81d1
commit 6e554b6bf7
32 changed files with 903 additions and 73 deletions

View File

@@ -14,5 +14,5 @@ public class ScheduleAPISettingSpeedParam {
* 速度单位m/s
*/
@NotBlank(message = "速度不能为空")
private Integer customSpeed;
private double customSpeed;
}

View File

@@ -1,5 +1,8 @@
package org.nl.api.task.api;
import org.nl.api.task.core.QRCodeTaskRequestParam;
import org.nl.response.WebResponse;
/**
* @author dsh
* 2025/12/11
@@ -28,4 +31,32 @@ public interface TaskAPI {
* @return
*/
String taskOperationConfirm(String taskCode,String vehicleNumber);
/**
* 二维码生成任务
* @param param
* @return
*/
WebResponse createTask(QRCodeTaskRequestParam param);
/**
* 二维码生成任务
* @param taskCode
* @return
*/
WebResponse cancelTask(String taskCode);
/**
* 二维码查询任务信息
* @param room
* @return
*/
WebResponse queryTaskInfoByRoom(String room);
/**
* 二维码查询任务信息
* @param taskCode
* @return
*/
WebResponse taskOperationConfirm(String taskCode);
}

View File

@@ -0,0 +1,39 @@
package org.nl.api.task.core;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* @author dsh
* 2025/12/23
*/
@Data
public class QRCodeTaskRequestParam {
/**
* 目标点
*/
@NotBlank(message = "目标点不能为空")
private String destinations;
/**
* 任务类型
*/
@NotBlank(message = "任务类型不能为空")
private String type;
/**
* 任务号
*/
private String task_code;
/**
* 任务优先级
*/
private String priority;
/**
* 指定车号
*/
private String vehicle_number;
}

View File

@@ -0,0 +1,28 @@
package org.nl.map.controller;
import jakarta.annotation.Resource;
import org.nl.logging.annotation.Log;
import org.nl.map.service.MapService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author dsh
* 2025/12/18
*/
@RestController
@RequestMapping("/mapinfo")
public class MapInfoController {
@Resource
private MapService mapService;
@GetMapping("/queryCurrentMapInfo")
@Log("获取当前地图信息")
public ResponseEntity<Object> queryCurrentMapInfo() {
return new ResponseEntity<>(mapService.queryCurrentMapInfo(), HttpStatus.OK);
}
}

View File

@@ -0,0 +1,51 @@
package org.nl.map.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* @author dsh
* 2025/12/18
*/
@Data
@TableName("map_info")
public class MapInfo {
/**
* 地图编号
*/
@TableId
private String mapCode;
/**
* 地图名称
*/
private String mapName;
/**
* 地图图片地址
*/
private String mapImageAddress;
/**
* 点云图像素比例
*/
private Double resolution;
/**
* 左下角x坐标
*/
private Double x;
/**
* 左下角y坐标
*/
private Double y;
/**
* 左下角 角度
*/
private Double angle;
}

View File

@@ -0,0 +1,14 @@
package org.nl.map.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.nl.map.entity.MapInfo;
/**
* @author dsh
* 2025/12/18
*/
@Mapper
public interface MapInfoMapper extends BaseMapper<MapInfo> {
}

View File

@@ -0,0 +1,11 @@
package org.nl.map.service;
import org.nl.response.WebResponse;
/**
* @author dsh
* 2025/12/18
*/
public interface MapService {
WebResponse queryCurrentMapInfo();
}

View File

@@ -0,0 +1,152 @@
package org.nl.map.service.impl;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpStatus;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.nl.map.entity.MapInfo;
import org.nl.map.mapper.MapInfoMapper;
import org.nl.map.service.MapService;
import org.nl.map.station.entity.Station;
import org.nl.map.station.mapper.StationMapper;
import org.nl.response.WebResponse;
import org.nl.util.FileProperties;
import org.nl.util.URLConstant;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* @author dsh
* 2025/12/18
*/
@Slf4j
@Service
public class MapServiceImpl implements MapService {
@Resource
private StationMapper stationMapper;
@Resource
private MapInfoMapper mapInfoMapper;
@Resource
private FileProperties properties;
@PostConstruct
public void init() {
getCurrentMapInfo();
downloadImage();
}
/**
* 下载图片并保存
*/
public void downloadImage() {
String filename = "currentMapImage.png";
Path filePath = Paths.get(properties.getPath().getPath(), filename);
try {
// 确保目录存在
createDirectoryIfNotExists();
// 发送请求
HttpResponse response = HttpRequest.get("http://"+ URLConstant.SCHEDULE_IP_PORT+"/maps/current/image/file")
.header("Accept", "image/png")
.header("User-Agent", "Hutool-HTTP")
.timeout(30000)
.execute();
if (response.getStatus() != HttpStatus.HTTP_OK) {
throw new RuntimeException("HTTP请求失败状态码: " + response.getStatus());
}
// 获取 Content-Type 验证
String contentType = response.header("Content-Type");
log.info("Content-Type: {}", contentType);
// 保存文件
byte[] bytes = response.bodyBytes();
Files.write(filePath, bytes);
log.info("图片下载成功: {}, 大小: {} bytes",
filePath.toAbsolutePath(), bytes.length);
mapInfoMapper.delete(new LambdaQueryWrapper<>());
MapInfo mapInfo = new MapInfo();
mapInfo.setMapCode(filename);
mapInfo.setMapName(filename);
mapInfo.setMapImageAddress("/file/" + filename);
mapInfoMapper.insert(mapInfo);
} catch (Exception e) {
log.error("下载图片失败", e);
}
}
/**
* 创建下载目录
*/
private void createDirectoryIfNotExists() throws IOException {
Path dirPath = Paths.get(properties.getPath().getPath());
if (!Files.exists(dirPath)) {
Files.createDirectories(dirPath);
log.info("创建下载目录: {}", dirPath.toAbsolutePath());
}
}
public void getCurrentMapInfo(){
try {
// 发送请求
HttpResponse response = HttpRequest
.get("http://"+ URLConstant.SCHEDULE_IP_PORT+"/maps/current")
.execute();
if (response.getStatus() != HttpStatus.HTTP_OK) {
throw new RuntimeException("HTTP请求失败状态码: " + response.getStatus());
}
// 清除站点数据
stationMapper.delete(new LambdaQueryWrapper<>());
JSONArray pois = JSONArray.parseArray(JSONObject.parseObject(response.body()).getString("pois"));
for (int i = 0; i < pois.size(); i++) {
JSONObject poi = pois.getJSONObject(i);
Station station = new Station();
station.setStation_id(poi.getString("id"));
station.setStation_code(poi.getString("name"));
station.setStation_name(poi.getString("name"));
station.setStation_type("Normal");
JSONObject pose = poi.getJSONObject("pose");
station.setX(pose.getDouble("x"));
station.setY(pose.getDouble("y"));
station.setAngle(pose.getDouble("yaw"));
stationMapper.insert(station);
}
}catch (Exception e){
log.info("获取当前地图信息失败:{}",e.getMessage());
}
}
@Override
public WebResponse queryCurrentMapInfo() {
MapInfo mapInfo = mapInfoMapper.selectOne(new LambdaQueryWrapper<>(MapInfo.class)
.eq(MapInfo::getMapCode, "currentMapImage.png")
);
return WebResponse.requestParamOk(mapInfo);
}
}

View File

@@ -0,0 +1,47 @@
package org.nl.map.station.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* @author dsh
* 2025/12/18
*/
@Data
@TableName("station")
public class Station {
@TableId
private String station_id;
/**
* 站点编码 对应地图上站点
*/
private String station_code;
/**
* 站点别名
*/
private String station_name;
/**
* 站点类型 (Normal 普通站点)
*/
private String station_type;
/**
* x坐标
*/
private Double x;
/**
* y坐标
*/
private Double y;
/**
* 角度
*/
private Double angle;
}

View File

@@ -0,0 +1,14 @@
package org.nl.map.station.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.nl.map.station.entity.Station;
/**
* @author dsh
* 2025/12/18
*/
@Mapper
public interface StationMapper extends BaseMapper<Station> {
}

View File

@@ -38,7 +38,7 @@ public class MapMonitorServiceImpl implements MapMonitorService {
JSONObject jsonObject = jsonArray.getJSONObject(i);
StationInfoDto stationInfoDto =new StationInfoDto();
stationInfoDto.setId(jsonObject.getString("id"));
stationInfoDto.setName(String.valueOf(i+1));
stationInfoDto.setName(jsonObject.getString("name"));
stationInfoDto.setType("1");
JSONObject poseJson = jsonObject.getJSONObject("pose");
stationInfoDto.setX(poseJson.getDouble("x"));

View File

@@ -7,6 +7,7 @@ import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.nl.api.schedule.map.api.ScheduleMapAPI;
import org.nl.exception.BadRequestException;
import org.nl.util.URLConstant;
import org.springframework.stereotype.Service;
/**
@@ -22,7 +23,7 @@ public class ScheduleMapAPIProvider implements ScheduleMapAPI {
log.info("获取当前地图中的所有POI");
HttpResponse result = null;
try {
result = HttpRequest.get("http://127.0.0.1:8011/api/core/artifact/v1/pois")
result = HttpRequest.get(URLConstant.SCHEDULE_IP_PORT+"/maps/pois")
.execute();
log.info("获取当前地图中的所有POI响应结果:{}",result.body());
}catch (Exception e){

View File

@@ -9,6 +9,7 @@ import org.nl.api.schedule.setting.api.ScheduleSettingAPI;
import org.nl.api.schedule.setting.core.ScheduleAPISettingChargeParam;
import org.nl.api.schedule.setting.core.ScheduleAPISettingSpeedParam;
import org.nl.exception.BadRequestException;
import org.nl.util.URLConstant;
import org.springframework.stereotype.Service;
/**
@@ -27,7 +28,7 @@ public class ScheduleSettingAPIProvider implements ScheduleSettingAPI {
log.info("设置调度配送速度参数:{}", scheduleAPISettingSpeedParam);
HttpResponse result = null;
try {
result = HttpRequest.put("http://127.0.0.1:8011/system/motion/speed")
result = HttpRequest.put(URLConstant.SCHEDULE_IP_PORT+"/system/motion/speed")
.body(String.valueOf(JSONObject.toJSON(scheduleAPISettingSpeedParam)))
.execute();
log.info("设置调度配送速度响应结果:{}",result.body());
@@ -45,7 +46,7 @@ public class ScheduleSettingAPIProvider implements ScheduleSettingAPI {
log.info("设置调度充电参数:{}", scheduleAPISettingChargeParam);
HttpResponse result = null;
try {
result = HttpRequest.put("http://127.0.0.1:8011/system/charging/auto-threshold")
result = HttpRequest.put(URLConstant.SCHEDULE_IP_PORT+"/system/charging/auto-threshold")
.body(String.valueOf(JSONObject.toJSON(scheduleAPISettingChargeParam)))
.execute();
log.info("设置调度充电响应结果:{}",result.body());

View File

@@ -11,6 +11,7 @@ import org.nl.api.schedule.task.core.ScheduleAPICreateOneClickTaskParam;
import org.nl.api.schedule.task.core.ScheduleAPICreateTaskParam;
import org.nl.exception.BadRequestException;
import org.nl.schedule.core.util.ScheduleUtil;
import org.nl.util.URLConstant;
import org.springframework.stereotype.Service;
/**
@@ -55,7 +56,7 @@ public class ScheduleTaskAPIProvider implements ScheduleTaskAPI {
HttpResponse result = null;
try {
result = HttpRequest
.post("http://127.0.0.1:8011/tasks/"+scheduleAPICreateTaskParam.getTask_code())
.post(URLConstant.SCHEDULE_IP_PORT+"/tasks/"+scheduleAPICreateTaskParam.getTask_code())
.body(String.valueOf(request_body))
.execute();
@@ -75,7 +76,7 @@ public class ScheduleTaskAPIProvider implements ScheduleTaskAPI {
HttpResponse result = null;
try {
result = HttpRequest
.post("http://127.0.0.1:8011/tasks/"+taskId+"/withdrawl")
.post(URLConstant.SCHEDULE_IP_PORT+"/tasks/"+taskId+"/withdrawl")
.body(String.valueOf(new JSONObject()))
.execute();
@@ -95,7 +96,7 @@ public class ScheduleTaskAPIProvider implements ScheduleTaskAPI {
HttpResponse result = null;
try {
result = HttpRequest
.get("http://127.0.0.1:8011/tasks/"+taskId)
.get(URLConstant.SCHEDULE_IP_PORT+"/tasks/"+taskId)
.execute();
log.info("查询调度任务状态响应结果:{}",result.body());
@@ -114,7 +115,7 @@ public class ScheduleTaskAPIProvider implements ScheduleTaskAPI {
HttpResponse result = null;
try {
result = HttpRequest
.post("http://127.0.0.1:8011/tasks/"+taskId+"/pause")
.post(URLConstant.SCHEDULE_IP_PORT+"/tasks/"+taskId+"/pause")
.body(String.valueOf(new JSONObject()))
.execute();
@@ -134,7 +135,7 @@ public class ScheduleTaskAPIProvider implements ScheduleTaskAPI {
HttpResponse result = null;
try {
result = HttpRequest
.post("http://127.0.0.1:8011/tasks/"+taskId+"/resume")
.post(URLConstant.SCHEDULE_IP_PORT+"/tasks/"+taskId+"/resume")
.body(String.valueOf(new JSONObject()))
.execute();
@@ -162,14 +163,14 @@ public class ScheduleTaskAPIProvider implements ScheduleTaskAPI {
JSONObject request_body = new JSONObject();
request_body.put("vehicleNumber", vehicleNumber);
request_body.put("taskId",task_code);
// request_body.put("taskId",task_code);
request_body.put("taskType",type);
log.info("下发调度一键任务参数:{}", request_body);
HttpResponse result = null;
try {
result = HttpRequest
.post("http://127.0.0.1:8011/tasks/quick-create")
.post(URLConstant.SCHEDULE_IP_PORT+"/tasks/one-click/"+task_code)
.body(String.valueOf(request_body))
.execute();

View File

@@ -12,6 +12,7 @@ import org.nl.response.WebResponse;
import org.nl.schedule.core.websocket.WebSocketVehicleInfoServer;
import org.nl.schedule.modular.vehicle.dto.VehicleInfoDto;
import org.nl.schedule.modular.vehicle.service.VehicleService;
import org.nl.util.URLConstant;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@@ -22,6 +23,8 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import static org.nl.util.URLConstant.SCHEDULE_IP_PORT;
/**
* @author dsh
* 2025/11/25
@@ -75,10 +78,10 @@ public class VehicleServiceImpl implements VehicleService {
@Override
public List<VehicleInfoDto> getAllVehicles() {
String url = "http://127.0.0.1:8011/schedule/vehicles";
String url = URLConstant.SCHEDULE_IP_PORT+"/vehicles";
List<VehicleInfoDto> vehicles = new ArrayList<>();
try {
try (HttpResponse response = HttpRequest.get("http://127.0.0.1:8011/schedule/vehicles")
try (HttpResponse response = HttpRequest.get(URLConstant.SCHEDULE_IP_PORT+"/vehicles")
.setConnectionTimeout(10000)
.setReadTimeout(10000)
.execute()) {

View File

@@ -73,8 +73,8 @@ public class SettingServiceImpl extends ServiceImpl<SettingMapper, Setting> impl
throw new BadRequestException("修改配送速度,设置值不能为空");
}
ScheduleAPISettingSpeedParam scheduleAPISettingSpeedParam = new ScheduleAPISettingSpeedParam();
// 前端值是0.1-1.0,传给调度需要转换成1-100。
int customSpeed = (int) (Double.parseDouble(setting_value) * 100);
// 前端值是0.1-1.0
double customSpeed = Double.parseDouble(setting_value);
scheduleAPISettingSpeedParam.setCustomSpeed(customSpeed);
HttpResponse speedResult = scheduleSettingAPI.settingSpeed(scheduleAPISettingSpeedParam);
if (speedResult == null || !speedResult.isOk()){

View File

@@ -0,0 +1,52 @@
package org.nl.sys.modular.qrcode.controller;
import jakarta.annotation.Resource;
import org.nl.api.task.core.QRCodeTaskRequestParam;
import org.nl.logging.annotation.Log;
import org.nl.sys.modular.qrcode.param.GenerateQRCodeParam;
import org.nl.sys.modular.qrcode.service.QRCodeService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
/**
* @author dsh
* 2025/12/22
*/
@RestController
@RequestMapping("/qrcodeManager")
public class QRCodeController {
@Resource
private QRCodeService qrCodeService;
@PostMapping("/generateQRCode")
@Log("生成二维码")
public ResponseEntity<Object> generateQRCode(@RequestBody GenerateQRCodeParam generateQRCodeParam){
return new ResponseEntity<>(qrCodeService.generateQRCode(generateQRCodeParam), HttpStatus.OK);
}
@GetMapping("/queryTaskInfoByRoom")
@Log("获取当前队列信息")
public ResponseEntity<Object> queryTaskInfoByRoom(@RequestParam String room){
return new ResponseEntity<>(qrCodeService.queryTaskInfoByRoom(room),HttpStatus.OK);
}
@PostMapping("/createTask")
@Log("二维码创建任务")
public ResponseEntity<Object> createTask(@RequestBody QRCodeTaskRequestParam param){
return new ResponseEntity<>(qrCodeService.createTask(param),HttpStatus.OK);
}
@PostMapping("/cancelTask")
@Log("二维码取消任务")
public ResponseEntity<Object> cancelTask(@RequestParam String taskCode){
return new ResponseEntity<>(qrCodeService.cancelTask(taskCode),HttpStatus.OK);
}
@PostMapping("/taskOperationConfirm")
@Log("二维码任务操作确认")
public ResponseEntity<Object> taskOperationConfirm(@RequestParam String taskCode){
return new ResponseEntity<>(qrCodeService.taskOperationConfirm(taskCode),HttpStatus.OK);
}
}

View File

@@ -0,0 +1,36 @@
package org.nl.sys.modular.qrcode.param;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* @author dsh
* 2025/12/22
*/
@Data
public class GenerateQRCodeParam {
/**
* 房间号
*/
@NotBlank(message = "房间号不能为空")
private String room;
/**
* 二维码内容(文本/URL/WiFi配置等
*/
@NotBlank(message = "二维码内容不能为空")
private String data;
/**
* 二维码宽度(像素)
*/
@NotBlank(message = "二维码宽度不能为空")
private int width;
/**
* 二维码高度(像素)
*/
@NotBlank(message = "二维码高度不能为空")
private int height;
}

View File

@@ -0,0 +1,46 @@
package org.nl.sys.modular.qrcode.service;
import org.nl.api.task.core.QRCodeTaskRequestParam;
import org.nl.response.WebResponse;
import org.nl.sys.modular.qrcode.param.GenerateQRCodeParam;
/**
* @author dsh
* 2025/12/22
*/
public interface QRCodeService {
/**
* 生成二维码
* @param generateQRCodeParam
* @return
*/
WebResponse generateQRCode(GenerateQRCodeParam generateQRCodeParam);
/**
* 二维码创建任务
* @param qrCodeTaskRequestParam
* @return
*/
WebResponse createTask(QRCodeTaskRequestParam qrCodeTaskRequestParam);
/**
* 二维码创建任务
* @return
*/
WebResponse cancelTask(String taskCode);
/**
* 二维码房间是否有任务
* @param room
* @return
*/
WebResponse queryTaskInfoByRoom(String room);
/**
* 二维码操作确认
* @param taskCode
* @return
*/
WebResponse taskOperationConfirm(String taskCode);
}

View File

@@ -0,0 +1,58 @@
package org.nl.sys.modular.qrcode.service.impl;
import cn.hutool.core.util.StrUtil;
import jakarta.annotation.Resource;
import org.nl.api.task.api.TaskAPI;
import org.nl.api.task.core.QRCodeTaskRequestParam;
import org.nl.exception.BadRequestException;
import org.nl.response.WebResponse;
import org.nl.sys.modular.qrcode.param.GenerateQRCodeParam;
import org.nl.sys.modular.qrcode.service.QRCodeService;
import org.nl.util.FileProperties;
import org.nl.util.QRCodeUtil;
import org.springframework.stereotype.Service;
/**
* @author dsh
* 2025/12/22
*/
@Service
public class QRCodeServiceImpl implements QRCodeService {
@Resource
private FileProperties fileProperties;
@Resource
private TaskAPI taskAPI;
@Override
public WebResponse generateQRCode(GenerateQRCodeParam param) {
String result = QRCodeUtil.generateQRCode(param.getData(), param.getWidth(),
param.getHeight(),fileProperties.getPath().getQrcode(),param.getRoom()+".png");
if (StrUtil.isBlank(result)){
throw new BadRequestException("生成二维码失败");
}
return WebResponse.requestOk();
}
@Override
public WebResponse createTask(QRCodeTaskRequestParam qrCodeTaskRequestParam) {
taskAPI.createTask(qrCodeTaskRequestParam);
return null;
}
@Override
public WebResponse cancelTask(String taskCode) {
return taskAPI.cancelTask(taskCode);
}
@Override
public WebResponse queryTaskInfoByRoom(String room) {
return taskAPI.queryTaskInfoByRoom(room);
}
@Override
public WebResponse taskOperationConfirm(String taskCode) {
return taskAPI.taskOperationConfirm(taskCode);
}
}

View File

@@ -1,5 +1,6 @@
package org.nl.task.provider;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
@@ -8,10 +9,15 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.nl.api.task.api.TaskAPI;
import org.nl.api.task.core.QRCodeTaskRequestParam;
import org.nl.enums.ScheduleTaskReportStatusEnum;
import org.nl.exception.BadRequestException;
import org.nl.response.WebResponse;
import org.nl.task.entity.Task;
import org.nl.task.enums.TaskSourceEnum;
import org.nl.task.enums.TaskStatusEnum;
import org.nl.task.param.CancelTaskRequestParam;
import org.nl.task.param.CreateTaskRequestParam;
import org.nl.task.service.TaskService;
import org.springframework.stereotype.Service;
@@ -54,7 +60,7 @@ public class TaskAPIProvider implements TaskAPI {
@Override
public String taskOperationConfirm(String taskCode,String vehicleNumber) {
Task task = this.queryCurrentTaskByVehicleNumber(vehicleNumber);
if (ObjectUtil.isEmpty(task)){
if (task == null || BeanUtil.isEmpty(task)){
log.info("任务操作确认失败,任务不存在");
return null;
}
@@ -64,4 +70,38 @@ public class TaskAPIProvider implements TaskAPI {
}
return task.getVehicleReportStatus();
}
@Override
public WebResponse createTask(QRCodeTaskRequestParam qrCodeTaskRequestParam) {
CreateTaskRequestParam param = BeanUtil.toBean(qrCodeTaskRequestParam, CreateTaskRequestParam.class);
return taskService.createTask(param, TaskSourceEnum.QRCODE.getName());
}
@Override
public WebResponse cancelTask(String taskCode) {
CancelTaskRequestParam param = new CancelTaskRequestParam();
param.setTask_code(taskCode);
return taskService.cancelTask(param);
}
@Override
public WebResponse queryTaskInfoByRoom(String room) {
// 获取当前存在的任务
List<Task> taskList = taskService.queryCurrentTaskInfo();
// 根据房间号查找当前任务中是否存在
Task currentTask = taskList.stream().filter(task -> room.equals(task.getDestinations()))
.findFirst()
.orElse(null);
JSONObject jsonObject = new JSONObject();
if (ObjectUtil.isNotEmpty(currentTask)){
jsonObject.put("currentTaskInfo", currentTask);
}
jsonObject.put("currentTaskCount", taskList.size());
return WebResponse.requestParamOk(jsonObject);
}
@Override
public WebResponse taskOperationConfirm(String taskCode) {
return taskService.taskOperationConfirm(taskCode);
}
}

View File

@@ -41,13 +41,13 @@ public interface TaskService extends IService<Task>{
WebResponse resumeTask(PauseAndResumeTaskParam pauseAndResumeTaskParam);
/**
* 查询当前未完成的任务
* 根据车号查询当前未完成的任务
* @return WebResponse
*/
WebResponse queryNotFinishTaskInfoByVehicleNumber(String vehicle_number);
/**
* 查询当前未完成的任务
* 根据车号查询当前未完成的任务
* @return List<Task>
*/
List<Task> queryCurrentTaskInfoByVehicleNumber(String vehicle_number);
@@ -64,4 +64,10 @@ public interface TaskService extends IService<Task>{
* @return WebResponse
*/
WebResponse oneClickOperation(OneClickOperationRequestParam oneClickOperationRequestParam,String taskSource);
/**
* 根据房间号查询当前任务信息
* @return List<Task>
*/
List<Task> queryCurrentTaskInfo();
}

View File

@@ -40,6 +40,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
/**
@@ -56,8 +57,16 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper,Task> implements Tas
@Resource
private ScheduleTaskAPI scheduleTaskAPI;
/**
* <车号,List<任务信息>>
*/
private ConcurrentHashMap<String,List<Task>> taskInfoList = new ConcurrentHashMap<>();
/**
* 当前任务集合
*/
private List<Task> currentTaskList = new CopyOnWriteArrayList<>();
/**
* 定时查询车辆任务状态(每秒执行)
*/
@@ -67,51 +76,56 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper,Task> implements Tas
List<Task> taskList = taskMapper.selectList(new LambdaQueryWrapper<>(Task.class)
.lt(Task::getStatus,TaskStatusEnum.FINISHED.getCode())
);
currentTaskList.clear();
currentTaskList = taskList;
taskInfoList.clear();
//根据车辆编号分类 车辆任务队列。
taskInfoList = taskList.stream()
.filter(task -> StrUtil.isNotBlank(task.getVehicle_number()) || StrUtil.isNotBlank(task.getProcessingVehicle()))
.collect(Collectors.groupingByConcurrent(task ->
StrUtil.isNotBlank(task.getProcessingVehicle()) ?
task.getProcessingVehicle() :
task.getVehicle_number(),
ConcurrentHashMap::new,
Collectors.toList()
));
for (Task task : taskList) {
HttpResponse result = scheduleTaskAPI.queryTaskStatusByTaskId(task.getTask_code());
if (result == null || !result.isOk()){
log.info("获取调度任务状态失败");
continue;
}
JSONObject scheduleTaskStatusJSON = JSONObject.parseObject(result.body());
String scheduleTaskState = scheduleTaskStatusJSON.getString("state");
String newTaskState = "";
if (ScheduleTaskStatusEnum.FINISHED.name().equals(scheduleTaskState)){
newTaskState = TaskStatusEnum.FINISHED.getCode();
} else if (ScheduleTaskStatusEnum.isFinalState(scheduleTaskState)) {
newTaskState = TaskStatusEnum.CANCELED.getCode();
task.setRemark("无路由或取消:" + scheduleTaskState);
} else if (ScheduleTaskStatusEnum.BEING_PROCESSED.name().equals(scheduleTaskState)) {
newTaskState = TaskStatusEnum.EXECUTING.getCode();
// 充电和加水任务的目标点是调度分配的,在查询任务状态时更新目标点。
if (StrUtil.isBlank(task.getDestinations()) && ObjectUtil.isNotEmpty(scheduleTaskStatusJSON.getJSONArray("destinations"))){
JSONObject destination = scheduleTaskStatusJSON.getJSONArray("destinations").getJSONObject(0);
task.setDestinations(destination.getString("locationName"));
if (!taskList.isEmpty()) {
taskInfoList = taskList.stream()
.filter(task -> StrUtil.isNotBlank(task.getVehicle_number()) || StrUtil.isNotBlank(task.getProcessingVehicle()))
.collect(Collectors.groupingByConcurrent(task ->
StrUtil.isNotBlank(task.getProcessingVehicle()) ?
task.getProcessingVehicle() :
task.getVehicle_number(),
ConcurrentHashMap::new,
Collectors.toList()
));
for (Task task : taskList) {
HttpResponse result = scheduleTaskAPI.queryTaskStatusByTaskId(task.getTask_code());
if (result == null || !result.isOk()){
log.info("获取调度任务状态失败");
continue;
}
JSONObject scheduleTaskStatusJSON = JSONObject.parseObject(result.body());
String scheduleTaskState = scheduleTaskStatusJSON.getString("state");
String newTaskState = "";
if (ScheduleTaskStatusEnum.FINISHED.name().equals(scheduleTaskState)){
newTaskState = TaskStatusEnum.FINISHED.getCode();
} else if (ScheduleTaskStatusEnum.isFinalState(scheduleTaskState)) {
newTaskState = TaskStatusEnum.CANCELED.getCode();
task.setRemark("无路由或取消:" + scheduleTaskState);
} else if (ScheduleTaskStatusEnum.BEING_PROCESSED.name().equals(scheduleTaskState)) {
newTaskState = TaskStatusEnum.EXECUTING.getCode();
// 充电和加水任务的目标点是调度分配的,在查询任务状态时更新目标点。
if (StrUtil.isBlank(task.getDestinations()) && ObjectUtil.isNotEmpty(scheduleTaskStatusJSON.getJSONArray("destinations"))){
JSONObject destination = scheduleTaskStatusJSON.getJSONArray("destinations").getJSONObject(0);
task.setDestinations(destination.getString("locationName"));
}
task.setProcessingVehicle(scheduleTaskStatusJSON.getString("processingVehicle"));
}
// 任务状态改变 进行更新
if (StrUtil.isNotBlank(newTaskState) && !newTaskState.equals(task.getStatus())){
log.info("更新任务状态,任务号:{},更新前状态:{},需要更新成的状态:{}",task.getTask_code(),task.getStatus(),newTaskState);
task.setStatus(newTaskState);
task.setUpdate_time(DateUtil.now());
// 更新任务状态
taskMapper.updateById(task);
log.info("更新任务状态成功");
}
task.setProcessingVehicle(scheduleTaskStatusJSON.getString("processingVehicle"));
}
// 任务状态改变 进行更新
if (StrUtil.isNotBlank(newTaskState) && !newTaskState.equals(task.getStatus())){
log.info("更新任务状态,任务号:{},更新前状态:{},需要更新成的状态:{}",task.getTask_code(),task.getStatus(),newTaskState);
task.setStatus(newTaskState);
task.setUpdate_time(DateUtil.now());
// 更新任务状态
taskMapper.updateById(task);
log.info("更新任务状态成功");
}
}
} catch (Exception e) {
log.error("更新任务状态失败: {}", e.getMessage());
log.error("更新任务状态失败: {}", e);
}
}
@@ -287,4 +301,9 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper,Task> implements Tas
}
}
@Override
public List<Task> queryCurrentTaskInfo() {
return currentTaskList;
}
}

View File

@@ -59,5 +59,17 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- zxing -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
</dependency>
<!-- zxing -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,9 +1,11 @@
package org.nl.config;
import org.nl.util.FileProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
@@ -14,6 +16,24 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@EnableWebMvc
public class CorsConfig implements WebMvcConfigurer {
/** 文件配置 */
private final FileProperties properties;
public CorsConfig(FileProperties properties) {
this.properties = properties;
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
FileProperties.ElPath path = properties.getPath();
String qrcodeUtl = "file:" + path.getQrcode().replace("\\","/");
String pathUtl = "file:" + path.getPath().replace("\\","/");
registry.addResourceHandler("/qrcode/**").addResourceLocations(qrcodeUtl).setCachePeriod(0);
registry.addResourceHandler("/file/**").addResourceLocations(pathUtl).setCachePeriod(0);
registry.addResourceHandler("/**").addResourceLocations("classpath:/META-INF/resources/").setCachePeriod(0);
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")

View File

@@ -0,0 +1,45 @@
package org.nl.util;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @author dsh
* 2025/12/18
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "file")
public class FileProperties {
/** 文件大小限制 */
private Long maxSize;
/** 头像大小限制 */
private Long avatarMaxSize;
private ElPath mac;
private ElPath linux;
private ElPath windows;
public ElPath getPath(){
String os = System.getProperty("os.name");
if(os.toLowerCase().startsWith("win")) {
return windows;
} else if(os.toLowerCase().startsWith("mac")){
return mac;
}
return linux;
}
@Data
public static class ElPath{
private String path;
private String qrcode;
}
}

View File

@@ -0,0 +1,73 @@
package org.nl.util;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.common.BitMatrix;
import lombok.extern.slf4j.Slf4j;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
/**
* @author dsh
* 2025/12/22
*/
@Slf4j
public class QRCodeUtil {
/**
* 生成二维码图片并保存到指定路径
*
* @param data 二维码内容(文本/URL/WiFi配置等
* @param width 二维码宽度(像素)
* @param height 二维码高度(像素)
* @param filePath 保存路径(需包含.png后缀
* @return 生成的二维码文件路径失败时返回null
*/
public static String generateQRCode(String data, int width, int height, String filePath,String fileName) {
try {
//1.设置二维码生成参数
Map<EncodeHintType, Object> hints = new HashMap<>();
// 支持中文等特殊字符
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
// 30%容错率
hints.put(EncodeHintType.ERROR_CORRECTION, com.google.zxing.qrcode.decoder.ErrorCorrectionLevel.H);
// 二维码边框空白宽度
hints.put(EncodeHintType.MARGIN, 1);
//2.生成二维码矩阵数据
MultiFormatWriter writer = new MultiFormatWriter();
BitMatrix bitMatrix = writer.encode(data, BarcodeFormat.QR_CODE, width, height, hints);
//3.转换为图像对象
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
image.setRGB(x, y, bitMatrix.get(x, y) ? Color.BLACK.getRGB() : Color.WHITE.getRGB());
}
}
// 确保目录存在
File outputDir = new File(filePath);
if (!outputDir.exists()) {
outputDir.mkdirs();
log.info("目录已创建: {}", outputDir.getAbsolutePath());
}
//4.保存为PNG文件
File qrCodeFile = new File(filePath, fileName);
ImageIO.write(image, "png", qrCodeFile);
log.info("二维码已生成并保存到: {}", filePath);
return filePath;
} catch (Exception e) {
log.info("生成二维码错误:{}", e.getMessage());
return null;
}
}
}

View File

@@ -0,0 +1,13 @@
package org.nl.util;
/**
* @author dsh
* 2025/12/16
*/
public class URLConstant {
/**
* 调度IP及端口
*/
public static String SCHEDULE_IP_PORT = "127.0.0.1:55200";
}

View File

@@ -1,6 +1,6 @@
server:
# 端口
port: 8081
port: 8011
tomcat:
max-swallow-size: 100MB
spring:
@@ -64,13 +64,13 @@ spring:
file:
mac:
path: ~/file/
avatar: ~/avatar/
qrcode: ~/avatar/
linux:
path: /home/eladmin/file/
avatar: /home/eladmin/avatar/
qrcode: /home/eladmin/qrcode/
windows:
path: C:\eladmin\file\
avatar: C:\eladmin\avatar\
qrcode: C:\eladmin\qrcode\
# 文件大小 /M
maxSize: 100
avatarMaxSize: 5

View File

@@ -5,12 +5,15 @@ server:
max-swallow-size: 100MB
spring:
datasource:
dynamic:
datasource:
master:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/nl_frobot?serverTimezone=GMT%2B8&characterEncoding=utf-8&userSSL=false
username: root
password: nlrobot
druid:
db-type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.100.201:3307/nl_apt15e?serverTimezone=GMT%2B8&characterEncoding=utf-8&userSSL=false
username: root
password: 123456
# 初始连接数
initial-size: 5
# 最小连接数
@@ -57,13 +60,13 @@ spring:
file:
mac:
path: ~/file/
avatar: ~/avatar/
qrcode: ~/qrcode/
linux:
path: /home/fr1511b/nlapt15e/file/
avatar: /home/fr1511b/nlapt15e/avatar/
path: /home/pi/nl_robot/map/
qrcode: /home/pi/nl_robot/qrcode/
windows:
path: C:\eladmin\file\
avatar: C:\eladmin\avatar\
path: C:\eladmin\file\currentMap\
qrcode: C:\eladmin\qrcode\currentMap\
# 文件大小 /M
maxSize: 100
avatarMaxSize: 5

View File

@@ -7,7 +7,7 @@ spring:
basename: language/task/task,language/error/error,language/buss/buss
encoding: UTF-8
profiles:
active: dev
active: prod
jackson:
time-zone: GMT+8

14
pom.xml
View File

@@ -139,6 +139,20 @@
<version>3.0.0</version>
</dependency>
<!-- zxing -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.5.3</version>
</dependency>
<!-- zxing -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.5.3</version>
</dependency>
<!-- easy-trans-mybatis-plus-extend -->
<dependency>
<groupId>com.fhs-opensource</groupId>