diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6af54a3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### log ### +*/*.log diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..db3b4c4 --- /dev/null +++ b/pom.xml @@ -0,0 +1,230 @@ + + + 4.0.0 + org.nl + QRobot + 0.0.1-SNAPSHOT + QRobot + QRobot + + 1.8 + UTF-8 + UTF-8 + 2.6.13 + + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.springframework.boot + spring-boot-starter-logging + + + ch.qos.logback + logback-classic + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + ch.qos.logback + logback-classic + + + + + + org.springframework.boot + spring-boot-starter-websocket + + + + org.springframework.boot + spring-boot-starter-aop + + + + + com.google.protobuf + protobuf-java + 3.21.12 + + + + com.baomidou + mybatis-plus-boot-starter + 3.5.2 + + + + + cn.dev33 + sa-token-spring-boot-starter + 1.34.0 + + + + org.aspectj + aspectjweaver + 1.9.2 + + + + cn.hutool + hutool-all + 5.7.14 + + + + com.alibaba + fastjson + 1.2.37 + + + + com.mysql + mysql-connector-j + runtime + + + + + ch.qos.logback + logback-classic + 1.2.3 + + + + + com.alibaba + druid-spring-boot-starter + 1.1.22 + + + + + com.alibaba + easyexcel + 3.1.1 + + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + + + kr.motd.maven + os-maven-plugin + 1.7.0 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + UTF-8 + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + org.nl.qrobot.QRobotApplication + + + + + repackage + + repackage + + + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + 0.6.1 + + + ${project.basedir}/src/main/proto + + ${project.build.directory}/generated-sources/protobuf/java + + com.google.protobuf:protoc:3.21.12:exe:windows-x86_64 + + true + + + + + compile + test-compile + + + + + + + + ${basedir}/src/main/java + + **/*.* + + + + + ${basedir}/src/main/resources + + **/*.* + + + + + + diff --git a/src/main/java/org/nl/qrobot/QRobotApplication.java b/src/main/java/org/nl/qrobot/QRobotApplication.java new file mode 100644 index 0000000..e2c9e4d --- /dev/null +++ b/src/main/java/org/nl/qrobot/QRobotApplication.java @@ -0,0 +1,43 @@ +package org.nl.qrobot; + +import org.mybatis.spring.annotation.MapperScan; +import org.nl.qrobot.config.SpringContextHolder; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@EnableAsync +@EnableScheduling +@SpringBootApplication(exclude = { + org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class +}) +@EnableTransactionManagement +@RestController +@MapperScan("org.nl.qrobot.**.mapper") +public class QRobotApplication { + + public static void main(String[] args) { + SpringApplication.run(QRobotApplication.class, args); + } + + @Bean + public SpringContextHolder springContextHolder() { + return new SpringContextHolder(); + } + + /** + * 访问首页提示 + * + * @return / + */ + @GetMapping("/") + public String index() { + return "Backend service started successfully"; + } + +} diff --git a/src/main/java/org/nl/qrobot/apt/anomalyInfo/controller/AnomalyInfoController.java b/src/main/java/org/nl/qrobot/apt/anomalyInfo/controller/AnomalyInfoController.java new file mode 100644 index 0000000..203f124 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/anomalyInfo/controller/AnomalyInfoController.java @@ -0,0 +1,124 @@ +package org.nl.qrobot.apt.anomalyInfo.controller; + +import cn.dev33.satoken.annotation.SaIgnore; +import com.alibaba.excel.EasyExcel; +import org.nl.qrobot.apt.anomalyInfo.dao.ErrorHandling; +import org.nl.qrobot.apt.anomalyInfo.dao.ErrorInfo; +import org.nl.qrobot.apt.anomalyInfo.service.AnomalyInfoService; +import org.nl.qrobot.apt.anomalyInfo.service.ErrorHandlingService; +import org.nl.qrobot.apt.anomalyInfo.service.ErrorInfoService; +import org.nl.qrobot.apt.vehicle.ProcessZip; +import org.nl.qrobot.common.BadRequestException; +import org.nl.qrobot.common.excel.ErrorHandlingListener; +import org.nl.qrobot.common.excel.ErrorInfoListener; +import org.nl.qrobot.config.file.FileProperties; +import org.nl.qrobot.config.language.LangProcess; +import org.nl.qrobot.util.FileConstant; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import java.io.File; +import java.io.IOException; + +/** + * @author dsh + * 2025/8/26 + */ +@RestController +@RequestMapping("/anomalyInfo") +public class AnomalyInfoController { + + @Resource + private AnomalyInfoService anomalyInfoService; + + @Resource + private ErrorInfoService errorInfoService; + + @Resource + private ErrorHandlingService errorHandlingService; + + @Resource + private FileProperties properties; + + @Resource + private ProcessZip processZip; + + @SaIgnore + @PostMapping("/queryErrorDataByCode") + public ResponseEntity queryErrorDataByCode(@RequestParam("code") String code) { + return new ResponseEntity<>(anomalyInfoService.queryErrorDataByCode(code), HttpStatus.OK); + } + + @PostMapping("/importErrorInfoExcel") + public ResponseEntity importErrorInfoExcel(@RequestParam("file") MultipartFile file) throws IOException { + if (file.isEmpty()) { + throw new BadRequestException("请选择文件上传"); + } + + // 创建监听器实例,传入所需的Service + ErrorInfoListener listener = new ErrorInfoListener(errorInfoService); + + // 读取Excel文件 + // withFile(file.getInputStream())... 也可以 + + EasyExcel.read(file.getInputStream(), ErrorInfo.class, listener) + .sheet() // 默认读取第一个sheet + .doRead(); + return new ResponseEntity<>(LangProcess.msg("successful"),HttpStatus.OK); + } + + @PostMapping("/importErrorHandlingExcel") + public ResponseEntity importErrorHandlingExcel(@RequestParam("file") MultipartFile file) throws IOException { + if (file.isEmpty()) { + throw new BadRequestException("请选择文件上传"); + } + + // 创建监听器实例,传入所需的Service + ErrorHandlingListener listener = new ErrorHandlingListener(errorHandlingService); + + // 读取Excel文件 + // withFile(file.getInputStream())... 也可以 + + EasyExcel.read(file.getInputStream(), ErrorHandling.class, listener) + .sheet() // 默认读取第一个sheet + .doRead(); + return new ResponseEntity<>(LangProcess.msg("successful"),HttpStatus.OK); + } + + @PostMapping("/importErrorImage") + public ResponseEntity importErrorImage(@RequestParam("file") MultipartFile file) throws IOException { + if (file.isEmpty()) { + throw new BadRequestException("文件不能为空"); + } + + String originalFilename = file.getOriginalFilename(); + if (originalFilename == null || + (!originalFilename.toLowerCase().endsWith(".zip"))) { + throw new BadRequestException("目前只支持ZIP格式"); + } + + try { + // 创建上传目录 + File uploadDir = new File(FileConstant.ERROR_IMAGE_PATH); + if (!uploadDir.exists()) { + uploadDir.mkdirs(); + } + + // 处理压缩文件 + processZip.processCompressedFile(file); + + + return new ResponseEntity<>("上传成功!",HttpStatus.OK); + + } catch (Exception e) { + throw new BadRequestException("处理文件失败:{}"+e.getMessage()); + } + } + +} diff --git a/src/main/java/org/nl/qrobot/apt/anomalyInfo/dao/ErrorHandling.java b/src/main/java/org/nl/qrobot/apt/anomalyInfo/dao/ErrorHandling.java new file mode 100644 index 0000000..6106db8 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/anomalyInfo/dao/ErrorHandling.java @@ -0,0 +1,64 @@ +package org.nl.qrobot.apt.anomalyInfo.dao; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * @author dsh + * 2025/8/20 + */ +@Data +@TableName("error_handling") +public class ErrorHandling { + + /** + * 异常处理标识 + */ + @TableId + @ExcelProperty(index = 0,value = "标识") + private String error_handling_id; + + /** + * 异常信息编码 + */ + @ExcelProperty(index = 1,value = "异常编码") + private String error_code; + + /** + * 异常处理类型(1 图文说明 2视频说明) + */ + @ExcelProperty(index = 2,value = "异常处理类型(1图文说明2视频说明)") + private Integer error_handling_type; + + /** + * 异常处理说明 + */ + @ExcelProperty(index = 3,value = "异常处理说明") + private String error_handling_title; + + /** + * 中文异常处理说明 + */ + @ExcelProperty(index = 4,value = "异常处理说明(中文)") + private String zh_error_handling_title; + + /** + * 英文异常处理说明 + */ + @ExcelProperty(index = 5,value = "异常处理说明(英文)") + private String en_error_handling_title; + + /** + * 异常处理排序 + */ + @ExcelProperty(index = 6,value = "异常处理排序") + private Integer error_handling_seq; + + /** + * 异常处理地址(图片 或 视频地址) + */ + @ExcelProperty(index = 7,value = "异常处理地址(图片或视频地址)") + private String error_handling_addre; +} diff --git a/src/main/java/org/nl/qrobot/apt/anomalyInfo/dao/ErrorInfo.java b/src/main/java/org/nl/qrobot/apt/anomalyInfo/dao/ErrorInfo.java new file mode 100644 index 0000000..879f699 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/anomalyInfo/dao/ErrorInfo.java @@ -0,0 +1,76 @@ +package org.nl.qrobot.apt.anomalyInfo.dao; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * @author dsh + * 2025/8/20 + */ +@Data +@TableName("error_info") +public class ErrorInfo { + + /** + * 异常信息标识 + */ + @TableId + @ExcelProperty(index = 0,value = "唯一标识") + private String error_id; + + /** + * 异常信息名称 + */ + @ExcelProperty(index = 1,value = "异常名称") + private String error_name; + + /** + * 中文异常信息名称 + */ + @ExcelProperty(index = 2,value = "异常名称(中文)") + private String zh_error_name; + + /** + * 英文异常信息名称 + */ + @ExcelProperty(index = 3,value = "异常名称(英文)") + private String en_error_name; + + /** + * 异常信息编码 + */ + @ExcelProperty(index = 4,value = "异常编号") + private String error_code; + + /** + * 异常信息类别(1普通故障 2严重故障) + */ + @ExcelProperty(index = 5,value = "异常类别(1普通故障,2严重故障)") + private Integer error_category; + + /** + * 异常说明 + */ + @ExcelProperty(index = 6,value = "异常说明") + private String error_description; + + /** + * 中文异常说明 + */ + @ExcelProperty(index = 7,value = "异常说明(中文)") + private String zh_error_description; + + /** + * 英文异常说明 + */ + @ExcelProperty(index = 8,value = "异常说明(英文)") + private String en_error_description; + + /** + * 异常信息类型(1电气 2导航) + */ + @ExcelProperty(index = 9,value = "异常类型(1电气,2导航)") + private Integer error_type; +} diff --git a/src/main/java/org/nl/qrobot/apt/anomalyInfo/dto/ErrorDataDto.java b/src/main/java/org/nl/qrobot/apt/anomalyInfo/dto/ErrorDataDto.java new file mode 100644 index 0000000..23f1cd6 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/anomalyInfo/dto/ErrorDataDto.java @@ -0,0 +1,26 @@ +package org.nl.qrobot.apt.anomalyInfo.dto; + +import lombok.Data; +import org.nl.qrobot.apt.anomalyInfo.dao.ErrorHandling; +import org.nl.qrobot.apt.anomalyInfo.dao.ErrorInfo; + +import java.util.List; + +/** + * @author dsh + * 2025/8/26 + */ +@Data +public class ErrorDataDto extends ErrorInfo { + + /** + * 图文异常处理 + */ + private List graphicDescription; + + /** + * 视频异常处理 + */ + private List videoDescription; + +} diff --git a/src/main/java/org/nl/qrobot/apt/anomalyInfo/service/AnomalyInfoService.java b/src/main/java/org/nl/qrobot/apt/anomalyInfo/service/AnomalyInfoService.java new file mode 100644 index 0000000..3c7316e --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/anomalyInfo/service/AnomalyInfoService.java @@ -0,0 +1,17 @@ +package org.nl.qrobot.apt.anomalyInfo.service; + +import org.nl.qrobot.apt.anomalyInfo.dto.ErrorDataDto; + +/** + * @author dsh + * 2025/8/26 + */ +public interface AnomalyInfoService { + + /** + * 根据异常编码查询处理方法 + * @param code + * @return + */ + ErrorDataDto queryErrorDataByCode(String code); +} diff --git a/src/main/java/org/nl/qrobot/apt/anomalyInfo/service/ErrorHandlingService.java b/src/main/java/org/nl/qrobot/apt/anomalyInfo/service/ErrorHandlingService.java new file mode 100644 index 0000000..f76fb88 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/anomalyInfo/service/ErrorHandlingService.java @@ -0,0 +1,12 @@ +package org.nl.qrobot.apt.anomalyInfo.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import org.nl.qrobot.apt.anomalyInfo.dao.ErrorHandling; + +/** + * @author dsh + * 2025/8/27 + */ +public interface ErrorHandlingService extends IService { + +} diff --git a/src/main/java/org/nl/qrobot/apt/anomalyInfo/service/ErrorInfoService.java b/src/main/java/org/nl/qrobot/apt/anomalyInfo/service/ErrorInfoService.java new file mode 100644 index 0000000..1e74378 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/anomalyInfo/service/ErrorInfoService.java @@ -0,0 +1,11 @@ +package org.nl.qrobot.apt.anomalyInfo.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import org.nl.qrobot.apt.anomalyInfo.dao.ErrorInfo; + +/** + * @author dsh + * 2025/8/26 + */ +public interface ErrorInfoService extends IService { +} diff --git a/src/main/java/org/nl/qrobot/apt/anomalyInfo/service/impl/AnomalyInfoServiceImpl.java b/src/main/java/org/nl/qrobot/apt/anomalyInfo/service/impl/AnomalyInfoServiceImpl.java new file mode 100644 index 0000000..a8f62d1 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/anomalyInfo/service/impl/AnomalyInfoServiceImpl.java @@ -0,0 +1,47 @@ +package org.nl.qrobot.apt.anomalyInfo.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.nl.qrobot.apt.anomalyInfo.dao.ErrorHandling; +import org.nl.qrobot.apt.anomalyInfo.dao.ErrorInfo; +import org.nl.qrobot.apt.anomalyInfo.dto.ErrorDataDto; +import org.nl.qrobot.apt.anomalyInfo.service.AnomalyInfoService; +import org.nl.qrobot.apt.anomalyInfo.service.mapper.ErrorHandlingMapper; +import org.nl.qrobot.apt.anomalyInfo.service.mapper.ErrorInfoMapper; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author dsh + * 2025/8/26 + */ +@Service +public class AnomalyInfoServiceImpl implements AnomalyInfoService { + + @Resource + private ErrorHandlingMapper errorHandlingMapper; + + @Resource + private ErrorInfoMapper errorInfoMapper; + + @Override + public ErrorDataDto queryErrorDataByCode(String code) { + + ErrorDataDto errorDataDto = new ErrorDataDto(); + // 异常信息 + ErrorInfo errorInfo = errorInfoMapper.selectOne(new LambdaQueryWrapper<>(ErrorInfo.class).eq(ErrorInfo::getError_code,code)); + BeanUtil.copyProperties(errorInfo,errorDataDto); + // 异常处理 + List errorHandlingList = errorHandlingMapper.selectList(new LambdaQueryWrapper<>(ErrorHandling.class).eq(ErrorHandling::getError_code,code)); + // 图文说明 + List graphicDescription = errorHandlingList.stream().filter(errorHandling -> errorHandling.getError_handling_type() == 1).collect(Collectors.toList()); + // 视频说明 + List videoDescription = errorHandlingList.stream().filter(errorHandling -> errorHandling.getError_handling_type() == 2).collect(Collectors.toList()); + errorDataDto.setGraphicDescription(graphicDescription); + errorDataDto.setVideoDescription(videoDescription); + return errorDataDto; + } +} diff --git a/src/main/java/org/nl/qrobot/apt/anomalyInfo/service/impl/ErrorHandlingServiceImpl.java b/src/main/java/org/nl/qrobot/apt/anomalyInfo/service/impl/ErrorHandlingServiceImpl.java new file mode 100644 index 0000000..50e9a5e --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/anomalyInfo/service/impl/ErrorHandlingServiceImpl.java @@ -0,0 +1,15 @@ +package org.nl.qrobot.apt.anomalyInfo.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.nl.qrobot.apt.anomalyInfo.dao.ErrorHandling; +import org.nl.qrobot.apt.anomalyInfo.service.ErrorHandlingService; +import org.nl.qrobot.apt.anomalyInfo.service.mapper.ErrorHandlingMapper; +import org.springframework.stereotype.Service; + +/** + * @author dsh + * 2025/8/27 + */ +@Service +public class ErrorHandlingServiceImpl extends ServiceImpl implements ErrorHandlingService { +} diff --git a/src/main/java/org/nl/qrobot/apt/anomalyInfo/service/impl/ErrorInfoServiceImpl.java b/src/main/java/org/nl/qrobot/apt/anomalyInfo/service/impl/ErrorInfoServiceImpl.java new file mode 100644 index 0000000..62e4dcc --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/anomalyInfo/service/impl/ErrorInfoServiceImpl.java @@ -0,0 +1,16 @@ +package org.nl.qrobot.apt.anomalyInfo.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.nl.qrobot.apt.anomalyInfo.dao.ErrorInfo; +import org.nl.qrobot.apt.anomalyInfo.service.ErrorInfoService; +import org.nl.qrobot.apt.anomalyInfo.service.mapper.ErrorInfoMapper; +import org.springframework.stereotype.Service; + +/** + * @author dsh + * 2025/8/26 + */ +@Service +public class ErrorInfoServiceImpl extends ServiceImpl implements ErrorInfoService { + +} diff --git a/src/main/java/org/nl/qrobot/apt/anomalyInfo/service/mapper/ErrorHandlingMapper.java b/src/main/java/org/nl/qrobot/apt/anomalyInfo/service/mapper/ErrorHandlingMapper.java new file mode 100644 index 0000000..0e50407 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/anomalyInfo/service/mapper/ErrorHandlingMapper.java @@ -0,0 +1,14 @@ +package org.nl.qrobot.apt.anomalyInfo.service.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.nl.qrobot.apt.anomalyInfo.dao.ErrorHandling; + +/** + * @author dsh + * 2025/8/26 + */ +@Mapper +public interface ErrorHandlingMapper extends BaseMapper { + +} diff --git a/src/main/java/org/nl/qrobot/apt/anomalyInfo/service/mapper/ErrorInfoMapper.java b/src/main/java/org/nl/qrobot/apt/anomalyInfo/service/mapper/ErrorInfoMapper.java new file mode 100644 index 0000000..1e2da57 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/anomalyInfo/service/mapper/ErrorInfoMapper.java @@ -0,0 +1,14 @@ +package org.nl.qrobot.apt.anomalyInfo.service.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.nl.qrobot.apt.anomalyInfo.dao.ErrorInfo; + +/** + * @author dsh + * 2025/8/26 + */ +@Mapper +public interface ErrorInfoMapper extends BaseMapper { + +} diff --git a/src/main/java/org/nl/qrobot/apt/dto/WebResponse.java b/src/main/java/org/nl/qrobot/apt/dto/WebResponse.java new file mode 100644 index 0000000..9ff83a6 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/dto/WebResponse.java @@ -0,0 +1,51 @@ +package org.nl.qrobot.apt.dto; + +import lombok.Builder; +import lombok.Data; +import org.nl.qrobot.config.language.LangProcess; + +/** + *

+ * 返回结果 + *

+ * + * @author Liuxy + * @since 2025-06-05 + */ +@Data +@Builder +public class WebResponse { + + /** + * 信息 + */ + private String message; + + /** + * 返回数据 + */ + private T data; + + /** + * 不带数据反馈 + * @return ErpResponse + */ + public static WebResponse requestOk() { + return WebResponse.builder() + .message(LangProcess.msg("successful")) + .build(); + } + + /** + * 带数据反馈 + * @return ErpResponse + */ + public static WebResponse requestParamOk(T data) { + return WebResponse.builder() + .message(LangProcess.msg("successful")) + .data(data) + .build(); + } + +} + diff --git a/src/main/java/org/nl/qrobot/apt/ext/controller/RcsToAptController.java b/src/main/java/org/nl/qrobot/apt/ext/controller/RcsToAptController.java new file mode 100644 index 0000000..a382728 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/ext/controller/RcsToAptController.java @@ -0,0 +1,37 @@ +package org.nl.qrobot.apt.ext.controller; + +import lombok.extern.slf4j.Slf4j; +import org.nl.qrobot.apt.ext.dto.RcsToAptTaskDto; +import org.nl.qrobot.apt.ext.service.RcsToAptService; +import org.nl.qrobot.common.logging.annotation.Log; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + *

+ * RCS调用APT控制层 + *

+ * + * @author Liuxy + * @since 2025-07-10 + */ +@Slf4j +@RestController +@RequestMapping("api/rcs") +public class RcsToAptController { + + @Autowired + private RcsToAptService rcsToAptService; + + @PostMapping("/reportTaskInfo") + @Log("RCS上报任务信息") + public ResponseEntity reportTaskInfo(@Validated @RequestBody RcsToAptTaskDto dto) { + return new ResponseEntity<>(rcsToAptService.reportTaskInfo(dto),HttpStatus.OK); + } +} diff --git a/src/main/java/org/nl/qrobot/apt/ext/dto/RcsToAptTaskChainPoDto.java b/src/main/java/org/nl/qrobot/apt/ext/dto/RcsToAptTaskChainPoDto.java new file mode 100644 index 0000000..9caf226 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/ext/dto/RcsToAptTaskChainPoDto.java @@ -0,0 +1,42 @@ +package org.nl.qrobot.apt.ext.dto; + +import lombok.Data; + +/** + * @author dsh + * 2025/7/30 + * 任务链信息 + */ +@Data +public class RcsToAptTaskChainPoDto { + + /** + * 车辆 ID + */ + private Long amrId; + + /** + * 任务链开始时间 + */ + private Long startTime; + + /** + * 任务链id + */ + private Long id; + + /** + * 区域 ID + */ + private Long areaId; + + /** + * 任务链创建时间 + */ + private Long createTime; + + /** + * 任务链状态 0未执行 1正在执行 2已完成 3取消 4异常 5跳过 6暂停 + */ + private Integer status; +} diff --git a/src/main/java/org/nl/qrobot/apt/ext/dto/RcsToAptTaskDto.java b/src/main/java/org/nl/qrobot/apt/ext/dto/RcsToAptTaskDto.java new file mode 100644 index 0000000..99cb4a4 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/ext/dto/RcsToAptTaskDto.java @@ -0,0 +1,93 @@ +package org.nl.qrobot.apt.ext.dto; + +import lombok.Data; + + +/** + *

+ * RCS上报任务信息Dto + *

+ * + * @author Liuxy + * @since 2025-07-10 + */ +@Data +public class RcsToAptTaskDto { + +// /** +// * 任务链 ID +// */ +// private String taskChainId; +// +// /** +// * 区域 ID +// */ +// private String areaId; +// +// /** +// * 任务创建时间 +// */ +// private String createTime; +// +// /** +// * 任务链开始时间 +// */ +// private String chainStartTime; +// +// /** +// * 任务链结束时间 +// */ +// private String chainFinishTime; +// +// /** +// * 车辆 ID +// */ +// private String amrId; +// +// /** +// * 任务状态 +// * 0-未执行,1-子任务正在执行,2-子任务已完成,3-任务链取消 +// * 4-子任务异常,5-任务链跳过,6-任务链异常,7-任务链完成 +// */ +// private String status; +// +// /** +// * 子任务 ID +// */ +// private String taskId; +// +// /** +// * 小车实际动作类型 +// */ +// private String action; +// +// /** +// * 子任务类型 +// */ +// private String taskType; +// +// /** +// * 目标点编号 +// */ +// private String endPointCode; +// +// /** +// * 地图 id +// */ +// private String mapId; +// +// /** +// * 小车状态 id +// */ +// private String stateId; +// +// /** +// * 小车状态名称 +// */ +// private String state; + + private RcsToAptTaskPo taskPo; + + private RcsToAptTaskChainPoDto taskChainPo; + +} diff --git a/src/main/java/org/nl/qrobot/apt/ext/dto/RcsToAptTaskPo.java b/src/main/java/org/nl/qrobot/apt/ext/dto/RcsToAptTaskPo.java new file mode 100644 index 0000000..785ac37 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/ext/dto/RcsToAptTaskPo.java @@ -0,0 +1,54 @@ +package org.nl.qrobot.apt.ext.dto; + +import lombok.Data; + +/** + * @author dsh + * 2025/7/30 + * 单任务信息 + */ +@Data +public class RcsToAptTaskPo { + + /** + * 动作类型 O0 移动任务 O12 直接上料任务 O13 直接下料任务 + */ + private String taskType; + + /** + * 小车实际动作类型 + */ + private Integer action; + + /** + * 开始时间 + */ + private Long startTime; + + /** + * 任务编号 + */ + private Integer id; + + /** + * 完成时间 + */ + private Long finishTime; + + /** + * 目标点编号 + */ + private String endPointCode; + + /** + * 地图 id + */ + private Long mapId; + + /** + * 任务状态 + * 0-未执行,1-子任务正在执行,2-子任务已完成,3-任务链取消 + * 4-子任务异常,5-任务链跳过,6-任务链异常,7-任务链完成 + */ + private Integer status; +} diff --git a/src/main/java/org/nl/qrobot/apt/ext/enums/RcsTaskChainStatus.java b/src/main/java/org/nl/qrobot/apt/ext/enums/RcsTaskChainStatus.java new file mode 100644 index 0000000..80e666c --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/ext/enums/RcsTaskChainStatus.java @@ -0,0 +1,51 @@ +package org.nl.qrobot.apt.ext.enums; + +import lombok.Getter; + +/** + * @author dsh + * 2025/7/30 + */ +@Getter +public enum RcsTaskChainStatus { + /** + * 未执行 + */ + CREATE("0", "未执行", "未执行"), + /** + * 正在执行 + */ + EXECUTING("1", "正在执行", "正在执行"), + /** + * 已完成 + */ + FINISHED("2", "已完成", "已完成"), + /** + * 取消 + */ + CANCELED("3", "取消", "取消"), + /** + * 异常 + */ + UNUSUAL("4", "异常", "异常"), + /** + * 任务链跳过 + */ + SKIP("5", "跳过", "跳过"), + /** + * 暂停 + */ + PAUSED("6", "暂停", "暂停") + ; + + + RcsTaskChainStatus(String code, String name, String desc) { + this.code = code; + this.name = name; + this.desc = desc; + } + + private final String code; + private final String name; + private final String desc; +} diff --git a/src/main/java/org/nl/qrobot/apt/ext/enums/RcsTaskStatus.java b/src/main/java/org/nl/qrobot/apt/ext/enums/RcsTaskStatus.java new file mode 100644 index 0000000..39b7d4b --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/ext/enums/RcsTaskStatus.java @@ -0,0 +1,80 @@ +package org.nl.qrobot.apt.ext.enums; + +/** + *

+ * RCS任务枚举 + *

+ * + * @author Liuxy + * @since 2025-07-10 + */ +public enum RcsTaskStatus { + /** + * 未执行 + */ + CREATE("0", "未执行", "未执行"), + /** + * 子任务执行中 + */ + SON_EXECUTING("1", "子任务执行中", "子任务执行中"), + /** + * 子任务完成 + */ + SON_FINISHED("2", "子任务完成", "子任务完成"), + /** + * 任务链取消 + */ + TASK_CANCELED("3", "任务链取消", "任务链取消"), + /** + * 子任务异常 + */ + SON_UNUSUAL("4", "子任务异常", "子任务异常"), + /** + * 任务链跳过 + */ + TASK_SKIP("5", "任务链跳过", "任务链跳过"), + /** + * 任务链异常 + */ + TASK_UNUSUAL("6", "任务链异常", "任务链异常"), + /** + * 任务链完成 + */ + TASK_FINISHED("7", "任务链完成", "任务链完成") + ; + + + RcsTaskStatus(String code, String name, String desc) { + this.code = code; + this.name = name; + this.desc = desc; + } + + private String code; + private String name; + private String desc; + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } +} diff --git a/src/main/java/org/nl/qrobot/apt/ext/service/RcsToAptService.java b/src/main/java/org/nl/qrobot/apt/ext/service/RcsToAptService.java new file mode 100644 index 0000000..7de8b4f --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/ext/service/RcsToAptService.java @@ -0,0 +1,24 @@ +package org.nl.qrobot.apt.ext.service; + +import com.alibaba.fastjson.JSONObject; +import org.nl.qrobot.apt.ext.dto.RcsToAptTaskDto; + +/** + *

+ * RCS调用APT服务 + *

+ * + * @author Liuxy + * @since 2025-07-10 + */ +public interface RcsToAptService { + + /** + * RCS上报任务信息 + * @param dto RcsToAptTaskDto + * @return { + * receive: 1(收到) + * } + */ + JSONObject reportTaskInfo(RcsToAptTaskDto dto); +} diff --git a/src/main/java/org/nl/qrobot/apt/ext/service/impl/RcsToAptServiceImpl.java b/src/main/java/org/nl/qrobot/apt/ext/service/impl/RcsToAptServiceImpl.java new file mode 100644 index 0000000..ea06dd2 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/ext/service/impl/RcsToAptServiceImpl.java @@ -0,0 +1,94 @@ +package org.nl.qrobot.apt.ext.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.nl.qrobot.apt.ext.dto.RcsToAptTaskChainPoDto; +import org.nl.qrobot.apt.ext.dto.RcsToAptTaskDto; +import org.nl.qrobot.apt.ext.dto.RcsToAptTaskPo; +import org.nl.qrobot.apt.ext.enums.RcsTaskChainStatus; +import org.nl.qrobot.apt.ext.service.RcsToAptService; +import org.nl.qrobot.apt.station.dao.Station; +import org.nl.qrobot.apt.station.service.StationService; +import org.nl.qrobot.apt.task.enums.TaskStatus; +import org.nl.qrobot.apt.task.service.ITaskService; +import org.nl.qrobot.apt.task.service.dao.Task; +import org.nl.qrobot.config.language.LangProcess; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + *

+ * RCS调用APT实现类 + *

+ * + * @author Liuxy + * @since 2025-07-10 + */ +@Service +@Slf4j +public class RcsToAptServiceImpl implements RcsToAptService { + + /** + * 任务服务 + */ + @Autowired + private ITaskService iTaskService; + + /** + * 站点服务 + */ + @Autowired + private StationService stationService; + + @Override + public JSONObject reportTaskInfo(RcsToAptTaskDto dto) { + log.info("RCS上报任务信息输入参数{}", dto.toString()); + JSONObject result = new JSONObject(); + RcsToAptTaskPo rcsToAptTaskPo = dto.getTaskPo(); + RcsToAptTaskChainPoDto rcsToAptTaskChainPoDto = dto.getTaskChainPo(); + + Task task = iTaskService.getOne( + new QueryWrapper().lambda() + .eq(Task::getTask_id, rcsToAptTaskChainPoDto.getId().toString()) + ); + if (ObjectUtil.isEmpty(task)) { + result.put("receive", "1"); + result.put("message", LangProcess.msg("task_Id_isNull",rcsToAptTaskChainPoDto.getId().toString())); + log.info("RCS上报任务信息输出参数{}", result.toString()); + return result; + } + + // 更新任务状态 + String taskChainPoStatus = rcsToAptTaskChainPoDto.getStatus().toString(); +// if (taskPoStatus.equals(RcsTaskStatus.SON_EXECUTING.getCode()) || taskPoStatus.equals(RcsTaskStatus.SON_FINISHED.getCode())) { +// task.setTask_status(TaskStatus.EXECUTING.getCode()); +// } else if (taskChainPoStatus.equals(RcsTaskStatus.TASK_FINISHED.getCode())) { +// task.setTask_status(TaskStatus.FINISHED.getCode()); +// } + if (RcsTaskChainStatus.FINISHED.getCode().equals(taskChainPoStatus)) { + task.setTask_status(TaskStatus.FINISHED.getCode()); + } else if (RcsTaskChainStatus.EXECUTING.getCode().equals(taskChainPoStatus)) { + task.setTask_status(TaskStatus.EXECUTING.getCode()); + } else if (RcsTaskChainStatus.CANCELED.getCode().equals(taskChainPoStatus)) { + task.setTask_status(TaskStatus.CANCELED.getCode()); + } + // 更新当前执行点位 + if (ObjectUtil.isNotEmpty(rcsToAptTaskPo)){ + String endPointCode = rcsToAptTaskPo.getEndPointCode(); + if (ObjectUtil.isNotEmpty(endPointCode)) { + Station staDao = stationService.getOne(new LambdaQueryWrapper<>(Station.class) + .eq(Station::getStation_code, endPointCode)); + task.setTask_point(staDao.getStation_name()); + } + } + + iTaskService.updateById(task); + + result.put("receive", "1"); + log.info("RCS上报任务信息输出参数{}", result.toString()); + return result; + } +} diff --git a/src/main/java/org/nl/qrobot/apt/map/dao/MapInfo.java b/src/main/java/org/nl/qrobot/apt/map/dao/MapInfo.java new file mode 100644 index 0000000..2a1417b --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/map/dao/MapInfo.java @@ -0,0 +1,60 @@ +package org.nl.qrobot.apt.map.dao; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * @author dsh + * 2025/7/17 + */ +@Data +@TableName("map_info") +public class MapInfo { + + /** + * 地图编号 + */ + @TableId + private String mapCode; + + /** + * 地图名称 + */ + private String mapName; + + /** + * 地图图片地址 + */ + private String mapImageAddress; + + /** + * 图片像素宽 + */ + private Double width; + + /** + * 图片像素高 + */ + private Double height; + + /** + * 点云图像素比例 如:一个像素0.05 就是5cm + */ + private Double resolution; + + /** + * 左下角x坐标 + */ + private Double x; + + /** + * 左下角y坐标 + */ + private Double y; + + /** + * 左下角 角度 + */ + private Double angle; +} diff --git a/src/main/java/org/nl/qrobot/apt/map/dto/PointCloudDataDto.java b/src/main/java/org/nl/qrobot/apt/map/dto/PointCloudDataDto.java new file mode 100644 index 0000000..5a5b992 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/map/dto/PointCloudDataDto.java @@ -0,0 +1,26 @@ +package org.nl.qrobot.apt.map.dto; + +import lombok.Data; + +/** + * @author dsh + * 2025/7/25 + */ +@Data +public class PointCloudDataDto { + + /** + * 点云x坐标 + */ + private double x; + + /** + * 点云y坐标 + */ + private double y; + + /** + * 点云强度 + */ + private double intensity; +} diff --git a/src/main/java/org/nl/qrobot/apt/map/dto/ProcessMapYamlDto.java b/src/main/java/org/nl/qrobot/apt/map/dto/ProcessMapYamlDto.java new file mode 100644 index 0000000..df1e4d2 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/map/dto/ProcessMapYamlDto.java @@ -0,0 +1,31 @@ +package org.nl.qrobot.apt.map.dto; + +import lombok.Data; + +/** + * @author dsh + * 2025/7/17 + */ +@Data +public class ProcessMapYamlDto { + + /** + * 点云图名称 + */ + private String image; + + /** + * 点云图像素比例 如:一个像素0.05 就是5cm + */ + private Double resolution; + + /** + * 地图左下角像素的坐标 [x,y,angle] + */ + private Double[] origin; + + private Double negate; + private Double occupied_thresh; + private Double free_thresh; + private Double main_normal; +} diff --git a/src/main/java/org/nl/qrobot/apt/map/rest/MapInfoController.java b/src/main/java/org/nl/qrobot/apt/map/rest/MapInfoController.java new file mode 100644 index 0000000..874bd75 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/map/rest/MapInfoController.java @@ -0,0 +1,30 @@ +package org.nl.qrobot.apt.map.rest; + +import org.nl.qrobot.apt.map.service.MapInfoService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +/** + * @author dsh + * 2025/7/17 + */ +@RestController +@RequestMapping("/mapInfo") +public class MapInfoController { + + @Resource + private MapInfoService mapInfoService; + + @GetMapping("/getMapInfoByCode") + public ResponseEntity getMapInfoByCode() { + return new ResponseEntity<>(mapInfoService.getMapInfoByCode(), HttpStatus.OK); + } + + @PostMapping("/synchronizedMap") + public ResponseEntity synchronizedMap() { + return new ResponseEntity<>(mapInfoService.synchronizedMap(), HttpStatus.OK); + } +} diff --git a/src/main/java/org/nl/qrobot/apt/map/service/MapInfoService.java b/src/main/java/org/nl/qrobot/apt/map/service/MapInfoService.java new file mode 100644 index 0000000..c3ec48b --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/map/service/MapInfoService.java @@ -0,0 +1,23 @@ +package org.nl.qrobot.apt.map.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import org.nl.qrobot.apt.map.dao.MapInfo; + +/** + * @author dsh + * 2025/7/17 + */ +public interface MapInfoService extends IService { + + /** + * 返回当前正在使用的地图数据 + */ + MapInfo getMapInfoByCode(); + + /** + * 获取当前车辆地图同步到本地 + * @return + */ + boolean synchronizedMap(); + +} diff --git a/src/main/java/org/nl/qrobot/apt/map/service/impl/MapInfoServiceImpl.java b/src/main/java/org/nl/qrobot/apt/map/service/impl/MapInfoServiceImpl.java new file mode 100644 index 0000000..4adb86c --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/map/service/impl/MapInfoServiceImpl.java @@ -0,0 +1,72 @@ +package org.nl.qrobot.apt.map.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpResponse; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.nl.qrobot.apt.map.dao.MapInfo; +import org.nl.qrobot.apt.map.service.MapInfoService; +import org.nl.qrobot.apt.map.service.mapper.MapInfoMapper; +import org.nl.qrobot.apt.teaching.service.TeachingService; +import org.nl.qrobot.common.BadRequestException; +import org.nl.qrobot.config.language.LangProcess; +import org.nl.qrobot.util.HTTPUtil; +import org.nl.qrobot.util.URLConstant; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +/** + * @author dsh + * 2025/7/17 + */ +@Slf4j +@Service +public class MapInfoServiceImpl extends ServiceImpl implements MapInfoService { + + @Resource + private MapInfoMapper mapInfoMapper; + + @Resource + private TeachingService teachingService; + + @Override + public MapInfo getMapInfoByCode() { + return this.getOne(new LambdaQueryWrapper<>(MapInfo.class)); + } + + @Override + public boolean synchronizedMap() { + HttpResponse response = null; + try { + response = HTTPUtil.post(URLConstant.VEHICLE_IP_PORT,"/tool/rob/getVehicle", new JSONObject()); + if (response!=null && response.isOk()) { + // 获取响应体内容 + JSONObject body = JSON.parseObject(response.body()); + // 如:"../map/apt_map_20250814141523" + String mapId = body.getJSONObject("data").getString("mapId"); + if (StrUtil.isEmpty(mapId)) { + throw new BadRequestException(LangProcess.msg("param_is_null")); + } + String mapName = mapId.substring(mapId.lastIndexOf('/') + 1); + MapInfo mapInfo = mapInfoMapper.selectOne(new LambdaQueryWrapper<>(MapInfo.class)); + // 如果数据库地图数据为空 或者 地图数据和车辆后台数据不一致 执行同步数据操作 + if (ObjectUtil.isEmpty(mapInfo) || !mapName.equals(mapInfo.getMapName())) { + teachingService.getRunMapZip(mapName); + return true; + }else { + throw new BadRequestException(LangProcess.msg("latest")); + } + }else { + throw new BadRequestException(LangProcess.msg("error_request_vehicle")); + } + } catch (Exception e) { + log.info("同步地图失败:{}",e.getMessage()); + throw new BadRequestException(LangProcess.msg("failed")+":"+e.getMessage()); + } + } +} diff --git a/src/main/java/org/nl/qrobot/apt/map/service/mapper/MapInfoMapper.java b/src/main/java/org/nl/qrobot/apt/map/service/mapper/MapInfoMapper.java new file mode 100644 index 0000000..ceb02ae --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/map/service/mapper/MapInfoMapper.java @@ -0,0 +1,13 @@ +package org.nl.qrobot.apt.map.service.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.nl.qrobot.apt.map.dao.MapInfo; + +/** + * @author dsh + * 2025/7/17 + */ +@Mapper +public interface MapInfoMapper extends BaseMapper { +} diff --git a/src/main/java/org/nl/qrobot/apt/route/dao/RouteInfo.java b/src/main/java/org/nl/qrobot/apt/route/dao/RouteInfo.java new file mode 100644 index 0000000..36886e1 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/route/dao/RouteInfo.java @@ -0,0 +1,61 @@ +package org.nl.qrobot.apt.route.dao; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * @author dsh + * 2025/7/17 + */ +@Data +@TableName("route_info") +public class RouteInfo { + + /** + * 路径标识 + */ + @TableId + private Integer route_id; + + /** + * 起点ID + */ + private Integer start_id; + + /** + * 终点ID + */ + private Integer end_id; + + /** + * 起点x坐标 + */ + private Double start_x; + + /** + * 起点y坐标 + */ + private Double start_y; + + /** + * 终点x坐标 + */ + private Double end_x; + + /** + * 终点y坐标 + */ + private Double end_y; + + /** + * 导航模式 前进模式, 后退模式 ,双向模式 + */ + private String navigation_mode; + + /** + * 路线类型 直行,转弯 + */ + private String route_type; + +} diff --git a/src/main/java/org/nl/qrobot/apt/route/service/RouteInfoService.java b/src/main/java/org/nl/qrobot/apt/route/service/RouteInfoService.java new file mode 100644 index 0000000..3f3ffba --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/route/service/RouteInfoService.java @@ -0,0 +1,15 @@ +package org.nl.qrobot.apt.route.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import org.nl.qrobot.apt.route.dao.RouteInfo; + +import java.util.List; + +/** + * @author dsh + * 2025/7/18 + */ +public interface RouteInfoService extends IService { + + List getAllRouteInfo(); +} diff --git a/src/main/java/org/nl/qrobot/apt/route/service/impl/RouteInfoServiceImpl.java b/src/main/java/org/nl/qrobot/apt/route/service/impl/RouteInfoServiceImpl.java new file mode 100644 index 0000000..54bc5c1 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/route/service/impl/RouteInfoServiceImpl.java @@ -0,0 +1,26 @@ +package org.nl.qrobot.apt.route.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.nl.qrobot.apt.route.dao.RouteInfo; +import org.nl.qrobot.apt.route.service.RouteInfoService; +import org.nl.qrobot.apt.route.service.mapper.RouteInfoMapper; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; + +/** + * @author dsh + * 2025/7/18 + */ +@Service +public class RouteInfoServiceImpl extends ServiceImpl implements RouteInfoService { + + @Resource + private RouteInfoMapper routeInfoMapper; + + @Override + public List getAllRouteInfo() { + return routeInfoMapper.selectList(null); + } +} diff --git a/src/main/java/org/nl/qrobot/apt/route/service/mapper/RouteInfoMapper.java b/src/main/java/org/nl/qrobot/apt/route/service/mapper/RouteInfoMapper.java new file mode 100644 index 0000000..db68235 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/route/service/mapper/RouteInfoMapper.java @@ -0,0 +1,13 @@ +package org.nl.qrobot.apt.route.service.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.nl.qrobot.apt.route.dao.RouteInfo; + +/** + * @author dsh + * 2025/7/18 + */ +@Mapper +public interface RouteInfoMapper extends BaseMapper { +} diff --git a/src/main/java/org/nl/qrobot/apt/route/service/rest/RouteInfoController.java b/src/main/java/org/nl/qrobot/apt/route/service/rest/RouteInfoController.java new file mode 100644 index 0000000..852224d --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/route/service/rest/RouteInfoController.java @@ -0,0 +1,27 @@ +package org.nl.qrobot.apt.route.service.rest; + +import org.nl.qrobot.apt.route.service.RouteInfoService; +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; + +import javax.annotation.Resource; + +/** + * @author dsh + * 2025/7/18 + */ +@RestController +@RequestMapping("/routeInfo") +public class RouteInfoController { + + @Resource + private RouteInfoService routeInfoService; + + @GetMapping("/getRouteInfo") + public ResponseEntity getRouteInfo(){ + return new ResponseEntity<>(routeInfoService.getAllRouteInfo(), HttpStatus.OK); + } +} diff --git a/src/main/java/org/nl/qrobot/apt/station/StationTypeEnum.java b/src/main/java/org/nl/qrobot/apt/station/StationTypeEnum.java new file mode 100644 index 0000000..aa6903e --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/station/StationTypeEnum.java @@ -0,0 +1,45 @@ +package org.nl.qrobot.apt.station; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @author dsh + * 2025/7/7 + */ +@Getter +@AllArgsConstructor +public enum StationTypeEnum { + + //站点类型 Station工位点 Charge充电点 Breaks休息点 Docking对接点 System系统自动生成点 + STATION(1, "Station", "工位点"), + CHARGE(2, "Charge", "充电点"), + BREAKS(3, "Breaks", "休息点"), + DOCKING(4, "Docking", "对接点"), + SYSTEM(5, "System", "系统自动生成点"), + + + //动作类型 Ascend升叉 Descend降叉 Move移动 Customize自定义 Return返回 + ASCEND(1, "Ascend", "升叉"), + DESCEND(2, "Descend", "降叉"), + MOVE(3, "Move", "移动"), + CUSTOMIZE(4, "Customize", "自定义"), + RETURN(5, "Return", "返回"); + + + /** + *索引 + */ + private final Integer index; + + /** + * 编码 + */ + private final String code; + + /** + *名称 + */ + private final String name; + +} diff --git a/src/main/java/org/nl/qrobot/apt/station/dao/Station.java b/src/main/java/org/nl/qrobot/apt/station/dao/Station.java new file mode 100644 index 0000000..3de4582 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/station/dao/Station.java @@ -0,0 +1,56 @@ +package org.nl.qrobot.apt.station.dao; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; + +/** + * @author dsh + * 2025/7/4 + */ +@Data +@TableName("station") +public class Station implements Serializable { + private static final long serialVersionUID = 1L; + + @TableId + private Integer station_id; + + /** + * 站点编码 对应地图上站点 + */ + private String station_code; + + /** + * 站点别名 + */ + private String station_name; + + /** + * 站点类型 (Station工位点 Charge充电点 Breaks休息点 Docking对接点 System系统自动生成点) + */ + private String station_type; + + /** + * 动作类型 (Ascend升叉 Descend降叉 Move移动 Customize自定义 Return返回) + */ + private String action_type; + + /** + * x坐标 + */ + private Double x; + + /** + * y坐标 + */ + private Double y; + + /** + * 角度 + */ + private Double angle; + +} diff --git a/src/main/java/org/nl/qrobot/apt/station/service/StationService.java b/src/main/java/org/nl/qrobot/apt/station/service/StationService.java new file mode 100644 index 0000000..90953de --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/station/service/StationService.java @@ -0,0 +1,33 @@ +package org.nl.qrobot.apt.station.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import org.nl.qrobot.apt.dto.WebResponse; +import org.nl.qrobot.apt.station.dao.Station; + +import java.util.List; + +/** + * @author dsh + * 2025/7/7 + */ +public interface StationService extends IService { + + List queryAllStation(); + + List queryMapAllStation(); + + Station queryStationById(Integer id); + + /** + * 获取返回点 + * @return + */ + Station getReturnStation(); + + /** + * 更新休息点 + * @param station_id + * @return + */ + WebResponse updateReturnStation(Integer station_id); +} diff --git a/src/main/java/org/nl/qrobot/apt/station/service/impl/StationServiceImpl.java b/src/main/java/org/nl/qrobot/apt/station/service/impl/StationServiceImpl.java new file mode 100644 index 0000000..a0731c2 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/station/service/impl/StationServiceImpl.java @@ -0,0 +1,79 @@ +package org.nl.qrobot.apt.station.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.nl.qrobot.apt.dto.WebResponse; +import org.nl.qrobot.apt.station.StationTypeEnum; +import org.nl.qrobot.apt.station.dao.Station; +import org.nl.qrobot.apt.station.service.StationService; +import org.nl.qrobot.apt.station.service.mapper.StationMapper; +import org.nl.qrobot.common.BadRequestException; +import org.nl.qrobot.config.language.LangProcess; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.List; + +/** + * @author dsh + * 2025/7/7 + */ +@Service +public class StationServiceImpl extends ServiceImpl implements StationService { + + @Resource + private StationMapper stationMapper; + + @Override + public List queryAllStation() { + return stationMapper.selectList(new LambdaQueryWrapper<>(Station.class) + .ne(Station::getStation_type, StationTypeEnum.SYSTEM.getCode())); + } + + @Override + public List queryMapAllStation() { + return stationMapper.selectList(null); + } + + @Override + public Station queryStationById(Integer id) { + return stationMapper.selectOne(new LambdaQueryWrapper().eq(Station::getStation_id, id)); + } + + @Override + public Station getReturnStation() { + return stationMapper.selectOne(new LambdaQueryWrapper().eq(Station::getStation_type, StationTypeEnum.BREAKS.getCode())); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public WebResponse updateReturnStation(Integer station_id) { + if (ObjectUtil.isEmpty(station_id)) { + throw new BadRequestException(LangProcess.msg("param_is_null")); + } + Station station = this.getReturnStation(); + boolean verify = false; + //当前存在返回点就先更新状态为工作点 + if (ObjectUtil.isNotEmpty(station)){ + station.setStation_type(StationTypeEnum.STATION.getCode()); + station.setAction_type(StationTypeEnum.CUSTOMIZE.getCode()); + verify = stationMapper.updateById(station)>0; + } + // 判断用户是否选择返回点 + if (station_id != -1){ + // 更新休息点 + verify = stationMapper.update(new Station(),new LambdaUpdateWrapper<>(Station.class) + .set(Station::getStation_type,StationTypeEnum.BREAKS.getCode()) + .set(Station::getAction_type,StationTypeEnum.RETURN.getCode()) + .eq(Station::getStation_id,station_id) + ) > 0; + } + if (!verify){ + throw new BadRequestException(LangProcess.msg("failed")); + } + return WebResponse.requestOk(); + } +} diff --git a/src/main/java/org/nl/qrobot/apt/station/service/mapper/StationMapper.java b/src/main/java/org/nl/qrobot/apt/station/service/mapper/StationMapper.java new file mode 100644 index 0000000..76702b2 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/station/service/mapper/StationMapper.java @@ -0,0 +1,14 @@ +package org.nl.qrobot.apt.station.service.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.nl.qrobot.apt.station.dao.Station; + +/** + * @author dsh + * 2025/7/7 + */ +@Mapper +public interface StationMapper extends BaseMapper { + +} diff --git a/src/main/java/org/nl/qrobot/apt/station/service/rest/StationController.java b/src/main/java/org/nl/qrobot/apt/station/service/rest/StationController.java new file mode 100644 index 0000000..a6c12a1 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/station/service/rest/StationController.java @@ -0,0 +1,47 @@ +package org.nl.qrobot.apt.station.service.rest; + +import lombok.extern.slf4j.Slf4j; +import org.nl.qrobot.apt.station.service.StationService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +/** + * @author dsh + * 2025/7/8 + */ +@RestController +@Slf4j +@RequestMapping("/station") +public class StationController { + + @Resource + private StationService stationService; + + @GetMapping("/queryAllStation") + private ResponseEntity queryAllStation() { + return new ResponseEntity<>(stationService.queryAllStation(), HttpStatus.OK); + } + + @GetMapping("/queryMapAllStation") + private ResponseEntity queryMapAllStation() { + return new ResponseEntity<>(stationService.queryMapAllStation(), HttpStatus.OK); + } + + @GetMapping("/queryStationById") + private ResponseEntity queryStationById(@RequestParam("station_id") Integer station_id) { + return new ResponseEntity<>(stationService.queryStationById(station_id), HttpStatus.OK); + } + + @GetMapping("/getReturnStation") + private ResponseEntity getReturnStation() { + return new ResponseEntity<>(stationService.getReturnStation(), HttpStatus.OK); + } + + @PostMapping("/updateReturnStation") + private ResponseEntity updateReturnStation(@RequestParam("station_id") Integer station_id){ + return new ResponseEntity<>(stationService.updateReturnStation(station_id), HttpStatus.OK); + } +} diff --git a/src/main/java/org/nl/qrobot/apt/task/enums/TaskStatus.java b/src/main/java/org/nl/qrobot/apt/task/enums/TaskStatus.java new file mode 100644 index 0000000..10384e8 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/task/enums/TaskStatus.java @@ -0,0 +1,63 @@ +package org.nl.qrobot.apt.task.enums; + +/** + *

+ * 任务枚举 + *

+ * + * @author Liuxy + * @since 2025-07-09 + */ +public enum TaskStatus { + /** + * 申请 + */ + CREATE("0", "生成", "生成"), + /** + * 执行中 + */ + EXECUTING("1", "执行中", "执行中"), + /** + * 完成 + */ + FINISHED("2", "完成", "完成"), + /** + * 已取消 + */ + CANCELED("3", "已取消", "已取消"); + + + TaskStatus(String code, String name, String desc) { + this.code = code; + this.name = name; + this.desc = desc; + } + + private String code; + private String name; + private String desc; + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } +} diff --git a/src/main/java/org/nl/qrobot/apt/task/service/ITaskService.java b/src/main/java/org/nl/qrobot/apt/task/service/ITaskService.java new file mode 100644 index 0000000..e3c9501 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/task/service/ITaskService.java @@ -0,0 +1,26 @@ +package org.nl.qrobot.apt.task.service; + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.extension.service.IService; +import org.nl.qrobot.apt.station.dao.Station; +import org.nl.qrobot.apt.task.service.dao.Task; + +import java.util.List; + +/** + *

+ * 服务类 + *

+ * + * @author Liuxy + * @since 2025-07-09 + */ +public interface ITaskService extends IService { + + /** + * 组织下发任务数据 + * @param staList 站点集合 + * @return JSONObject + */ + JSONObject sendTask(List staList); +} diff --git a/src/main/java/org/nl/qrobot/apt/task/service/dao/Task.java b/src/main/java/org/nl/qrobot/apt/task/service/dao/Task.java new file mode 100644 index 0000000..df5fc67 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/task/service/dao/Task.java @@ -0,0 +1,63 @@ +package org.nl.qrobot.apt.task.service.dao; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + *

+ * + *

+ * + * @author Liuxy + * @since 2025-07-09 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("task") +public class Task implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 标识 + */ + @TableId(value = "id") + private String id; + + /** + * 任务id + */ + private String task_id; + + /** + * 任务顺序 + */ + private String task_seq; + + /** + * 任务状态 + */ + private String task_status; + + /** + * 当前站点 + */ + private String task_point; + + /** + * 创建时间 + */ + private String create_time; + + /** + * 完成时间 + */ + private LocalDateTime confirm_time; + + +} diff --git a/src/main/java/org/nl/qrobot/apt/task/service/dao/mapper/TaskMapper.java b/src/main/java/org/nl/qrobot/apt/task/service/dao/mapper/TaskMapper.java new file mode 100644 index 0000000..57a02ae --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/task/service/dao/mapper/TaskMapper.java @@ -0,0 +1,16 @@ +package org.nl.qrobot.apt.task.service.dao.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.nl.qrobot.apt.task.service.dao.Task; + +/** + *

+ * Mapper 接口 + *

+ * + * @author Liuxy + * @since 2025-07-09 + */ +public interface TaskMapper extends BaseMapper { + +} diff --git a/src/main/java/org/nl/qrobot/apt/task/service/dao/mapper/TaskMapper.xml b/src/main/java/org/nl/qrobot/apt/task/service/dao/mapper/TaskMapper.xml new file mode 100644 index 0000000..a24fa26 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/task/service/dao/mapper/TaskMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/java/org/nl/qrobot/apt/task/service/impl/TaskServiceImpl.java b/src/main/java/org/nl/qrobot/apt/task/service/impl/TaskServiceImpl.java new file mode 100644 index 0000000..be22af5 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/task/service/impl/TaskServiceImpl.java @@ -0,0 +1,71 @@ +package org.nl.qrobot.apt.task.service.impl; + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.nl.qrobot.apt.station.dao.Station; +import org.nl.qrobot.apt.task.service.ITaskService; +import org.nl.qrobot.apt.task.service.dao.Task; +import org.nl.qrobot.apt.task.service.dao.mapper.TaskMapper; +import org.nl.qrobot.apt.vehicle.dao.VehicleInfo; +import org.nl.qrobot.apt.vehicle.service.VehicleInfoService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +/** + *

+ * 服务实现类 + *

+ * + * @author Liuxy + * @since 2025-07-09 + */ +@Service +public class TaskServiceImpl extends ServiceImpl implements ITaskService { + + /** + * 车辆服务 + */ + @Autowired + private VehicleInfoService vehicleInfoService; + + @Override + public JSONObject sendTask (List staList ) { + JSONObject result = new JSONObject(); + // 获取车辆信息 + VehicleInfo vehicleInfo = vehicleInfoService.getVehicleInfo(); + JSONObject taskChain = new JSONObject(); + taskChain.put("areaId", vehicleInfo.getAreaId()); + taskChain.put("amrId", vehicleInfo.getId()); + // 1需要RCS回传,0不需要回传 + taskChain.put("isReturn", 1); + result.put("taskChain",taskChain); + + // 组织站点信息 + List tasks = new ArrayList<>(); + for (Station dao : staList) { + JSONObject json = new JSONObject(); + json.put("endPointCode", dao.getStation_code()); + json.put("mapId", vehicleInfo.getMapId()); + switch (dao.getAction_type()) { + case "Ascend" : + json.put("taskType", "O12"); + break; + case "Descend" : + json.put("taskType", "O13"); + break; + case "Move" : + json.put("taskType", "O0"); + break; + case "Return" : + json.put("taskType", "O0"); + break; + } + tasks.add(json); + } + result.put("tasks", tasks); + return result; + } +} diff --git a/src/main/java/org/nl/qrobot/apt/taskOperate/controller/TaskManageController.java b/src/main/java/org/nl/qrobot/apt/taskOperate/controller/TaskManageController.java new file mode 100644 index 0000000..3f6c638 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/taskOperate/controller/TaskManageController.java @@ -0,0 +1,86 @@ +package org.nl.qrobot.apt.taskOperate.controller; + +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.nl.qrobot.apt.dto.WebResponse; +import org.nl.qrobot.apt.station.service.StationService; +import org.nl.qrobot.apt.taskOperate.service.TaskManageService; +import org.nl.qrobot.common.logging.annotation.Log; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +/** + *

+ * 任务操作模块控制层 + *

+ * + * @author Liuxy + * @since 2025-07-09 + */ +@Slf4j +@RestController +@RequestMapping("api/operate") +public class TaskManageController { + + /** + * 站点服务 + */ + @Autowired + private StationService stationService; + + /** + * 站点服务 + */ + @Autowired + private TaskManageService taskManageService; + + @PostMapping("/queryStation") + @Log("站点查询") + public ResponseEntity queryStation() { + return new ResponseEntity<>(WebResponse.requestParamOk(stationService.queryAllStation()),HttpStatus.OK); + } + + @PostMapping("/sendTask") + @Log("下发任务") + public ResponseEntity sendTask(@RequestBody JSONObject whereJson) { + return new ResponseEntity<>(taskManageService.sendTask(whereJson),HttpStatus.OK); + } + + @PostMapping("/saveTask") + @Log("保存任务") + public ResponseEntity saveTask(@RequestBody JSONObject whereJson) { + return new ResponseEntity<>(taskManageService.saveTask(whereJson),HttpStatus.OK); + } + + @PostMapping("/cancelTask") + @Log("取消任务") + public ResponseEntity cancelTask() { + return new ResponseEntity<>(taskManageService.cancelTask(),HttpStatus.OK); + } + + @PostMapping("/queryTaskChain") + @Log("任务链查询") + public ResponseEntity queryTaskChain() { + return new ResponseEntity<>(taskManageService.queryTaskChain(),HttpStatus.OK); + } + + @PostMapping("/deleteTaskChain") + @Log("删除任务链") + public ResponseEntity deleteTaskChain(@RequestBody JSONObject whereJson) { + return new ResponseEntity<>(taskManageService.deleteTaskChain(whereJson),HttpStatus.OK); + } + + @PostMapping("/updateStation") + @Log("修改站点名称") + public ResponseEntity updateStation(@RequestBody JSONObject whereJson) { + return new ResponseEntity<>(taskManageService.updateStation(whereJson),HttpStatus.OK); + } + + @PostMapping("/queryTaskChainDtl") + @Log("查询任务链明细") + public ResponseEntity TaskChainDtl(@RequestBody JSONObject whereJson) { + return new ResponseEntity<>(taskManageService.TaskChainDtl(whereJson),HttpStatus.OK); + } +} diff --git a/src/main/java/org/nl/qrobot/apt/taskOperate/service/TaskManageService.java b/src/main/java/org/nl/qrobot/apt/taskOperate/service/TaskManageService.java new file mode 100644 index 0000000..e2f1d1a --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/taskOperate/service/TaskManageService.java @@ -0,0 +1,82 @@ +package org.nl.qrobot.apt.taskOperate.service; + +import com.alibaba.fastjson.JSONObject; +import org.nl.qrobot.apt.dto.WebResponse; + + +/** + *

+ * 任务操作接口 + *

+ * + * @author Liuxy + * @since 2025-07-09 + */ +public interface TaskManageService { + + /** + * 下发任务 + * @param whereJson { + * data:[ + * station_code:站点编码 + * station_name:站点别名 + * action_type:动作类型 + * ] + * } + * @return WebResponse + */ + WebResponse sendTask(JSONObject whereJson); + + /** + * 保存任务 + * @param whereJson { + * data:[ + * station_code:站点编码 + * station_name:站点别名 + * action_type:动作类型 + * ] + * } + * @return WebResponse + */ + WebResponse saveTask(JSONObject whereJson); + + /** + * 取消任务 + * @return WebResponse + */ + WebResponse cancelTask(); + + /** + * 任务链查询 + * @return WebResponse + */ + WebResponse queryTaskChain(); + + /** + * 删除任务链 + * @param whereJson { + * chain_id: 任务链标识 + * } + * @return WebResponse + */ + WebResponse deleteTaskChain(JSONObject whereJson); + + /** + * 修改站点名称 + * @param whereJson{ + * station_code:站点编码 + * station_name:站点别名 + * } + * @return WebResponse + */ + WebResponse updateStation(JSONObject whereJson); + + /** + * 查询任务链明细 + * @param whereJson { + * chain_id: 任务链标识 + * } + * @return WebResponse + */ + WebResponse TaskChainDtl(JSONObject whereJson); +} diff --git a/src/main/java/org/nl/qrobot/apt/taskOperate/service/impl/TaskManageServiceImpl.java b/src/main/java/org/nl/qrobot/apt/taskOperate/service/impl/TaskManageServiceImpl.java new file mode 100644 index 0000000..e1306e7 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/taskOperate/service/impl/TaskManageServiceImpl.java @@ -0,0 +1,248 @@ +package org.nl.qrobot.apt.taskOperate.service.impl; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.http.HttpResponse; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import lombok.extern.slf4j.Slf4j; +import org.nl.qrobot.apt.dto.WebResponse; +import org.nl.qrobot.apt.station.StationTypeEnum; +import org.nl.qrobot.apt.station.dao.Station; +import org.nl.qrobot.apt.station.service.StationService; +import org.nl.qrobot.apt.task.enums.TaskStatus; +import org.nl.qrobot.apt.task.service.ITaskService; +import org.nl.qrobot.apt.task.service.dao.Task; +import org.nl.qrobot.apt.taskOperate.service.TaskManageService; +import org.nl.qrobot.apt.taskchain.service.ITaskchainService; +import org.nl.qrobot.apt.taskchain.service.dao.Taskchain; +import org.nl.qrobot.apt.vehicle.service.impl.VehicleInfoServiceImpl; +import org.nl.qrobot.common.BadRequestException; +import org.nl.qrobot.config.language.LangProcess; +import org.nl.qrobot.util.HTTPUtil; +import org.nl.qrobot.util.IdUtil; +import org.nl.qrobot.util.URLConstant; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.stream.Collectors; + +/** + *

+ * 任务操作模块实现类 + *

+ * + * @author Liuxy + * @since 2025-07-09 + */ +@Service +@Slf4j +public class TaskManageServiceImpl implements TaskManageService { + + /** + * 任务服务 + */ + @Autowired + private ITaskService iTaskService; + + /** + * 任务链服务 + */ + @Autowired + private ITaskchainService iTaskchainService; + + /** + * 站点服务 + */ + @Autowired + private StationService stationService; + + @Override + public WebResponse sendTask(JSONObject whereJson) { + // 校验当前是否有任务 + List list = iTaskService.list( + new QueryWrapper().lambda() + .lt(Task::getTask_status, TaskStatus.FINISHED.getCode()) + ); + if (ObjectUtil.isNotEmpty(list)) { + throw new BadRequestException(LangProcess.msg("task_Is_exist")); + } + + List staList = whereJson.getJSONArray("data").toJavaList(Station.class); + + // 判断前端参数是否有返回点 + boolean move = staList.stream() + .anyMatch(row -> row.getAction_type().equals("Return")); + if (!move) { + + // 取返回点 + Station breakPoint = stationService.getOne( + new QueryWrapper().lambda() + .eq(Station::getStation_type, "Breaks") + ); + if (ObjectUtil.isEmpty(breakPoint)) { + throw new BadRequestException(LangProcess.msg("not_return_point")); + } + staList.add(breakPoint); + } + + // 当前叉腿载货状态(Ascend或者Descend) +// String currentType = VehicleInfoServiceImpl.vehicleInfo.getVehiclePayloads() == 1? StationTypeEnum.ASCEND.getCode():StationTypeEnum.DESCEND.getCode(); + String currentType = Objects.equals(VehicleInfoServiceImpl.vehicleInfo.getVehiclePayloads(), "1")? StationTypeEnum.ASCEND.getCode():StationTypeEnum.DESCEND.getCode(); + + for (int i = 0; i < staList.size(); i++) { + String proposedAction = staList.get(i).getAction_type(); + + // 检查动作是否与当前动作相同 + if (currentType.equals(proposedAction)) { + throw new BadRequestException(LangProcess.msg("task_cannot_be_enforced")); + } + + // 只有升叉和降叉会改变当前状态 + if (StationTypeEnum.ASCEND.getCode().equals(proposedAction) || StationTypeEnum.DESCEND.getCode().equals(proposedAction)) { + currentType = proposedAction; + } + } + + // 任务id + String task_id = ""; + // 组织任务下发 + try { + JSONObject param = iTaskService.sendTask(staList); + log.info("下发RCS任务输入参数{}", param.toString()); + HttpResponse response = HTTPUtil.post(URLConstant.RCS_IP_PORT, URLConstant.SEND_TASK_URL, param); + + JSONObject result = JSONObject.parseObject(response.body()); + log.info("下发RCS任务输出参数{}", result.toString()); + if (result.getBoolean("state").equals(false)) { + throw new BadRequestException(LangProcess.msg("error_request_rcs") + result.getString("errMsg")); + } + task_id = result.getString("data"); + } catch (Exception e) { + throw new BadRequestException(LangProcess.msg("error_request_rcs") + e.getMessage()); + } + + // 创建任务 + Task task = new Task(); + task.setId(IdUtil.getStringId()); + task.setTask_id(task_id); + task.setTask_seq( + staList.stream() + .map(Station::getStation_name) + .collect(Collectors.joining(",")) + ); + task.setTask_status(TaskStatus.CREATE.getCode()); + task.setCreate_time(DateUtil.now()); + iTaskService.save(task); + return WebResponse.requestOk(); + } + + @Override + @Transactional + public WebResponse saveTask(JSONObject whereJson) { + List staList = whereJson.getJSONArray("data").toJavaList(Station.class); + + List taskChainList = new ArrayList<>(); + String chain_id = IdUtil.getStringId(); + for (int i = 0; i < staList.size(); i++) { + Station station = staList.get(i); + Taskchain taskchain = new Taskchain(); + taskchain.setId(IdUtil.getStringId()); + taskchain.setChain_id(chain_id); + taskchain.setChain_name(whereJson.getString("chain_name")); + taskchain.setOrder_num(i + 1 + ""); + taskchain.setStation_code(station.getStation_code()); + taskchain.setStation_name(station.getStation_name()); + taskchain.setAction_type(station.getAction_type()); + taskChainList.add(taskchain); + } + iTaskchainService.saveBatch(taskChainList); + return WebResponse.requestOk(); + } + + @Override + public WebResponse cancelTask() { + // 找到当前正在执行的任务 + Task task = iTaskService.getOne( + new QueryWrapper().lambda() + .lt(Task::getTask_status, TaskStatus.FINISHED.getCode()) + ); +// if (ObjectUtil.isEmpty(task)) { +// throw new BadRequestException(LangProcess.msg("task_Is_not_exist")); +// } + try { + JSONObject param = new JSONObject(); + param.put("amrId", VehicleInfoServiceImpl.vehicleInfo.getId()); + //取消调度任务 + HttpResponse response = HTTPUtil.post(URLConstant.RCS_IP_PORT, URLConstant.VEHICLE_ID_CANCEL_TASK_URL, param); + JSONObject result = JSONObject.parseObject(response.body()); + if (result.getBoolean("state").equals(false)) { + throw new BadRequestException(LangProcess.msg("error_request_rcs") + result.getString("errMsg")); + } + // 取消当前任务 + if (ObjectUtil.isNotEmpty(task)) { + task.setTask_status(TaskStatus.CANCELED.getCode()); + iTaskService.updateById(task); + } + } catch (Exception e) { + throw new BadRequestException(LangProcess.msg("error_request_rcs") + e.getMessage()); + } + return WebResponse.requestOk(); + } + + @Override + public WebResponse queryTaskChain() { + List taskList = iTaskchainService.queryAllTaskchain(); + // 根据任务链id分组 + Map> mapTask = taskList.stream() + .collect(Collectors.groupingBy(Taskchain::getChain_id)); + + List resultList = new ArrayList<>(); + for (String chain_id : mapTask.keySet()) { + // 根据循序号排序 + List taskChainList = mapTask.get(chain_id).stream() + .sorted(Comparator.comparing(Taskchain::getOrder_num)) + .collect(Collectors.toList()); + + JSONObject jsonTaskChain = new JSONObject(); + jsonTaskChain.put("chain_id", chain_id); + jsonTaskChain.put("chain_name", taskChainList.get(0).getChain_name()); + jsonTaskChain.put("chain_point", + taskChainList.stream() + .map(Taskchain::getStation_name) + .collect(Collectors.joining("-")) + ); + resultList.add(jsonTaskChain); + } + return WebResponse.requestParamOk(resultList); + } + + @Override + @Transactional + public WebResponse deleteTaskChain(JSONObject whereJson) { + iTaskchainService.remove( + new QueryWrapper().lambda() + .eq(Taskchain::getChain_id, whereJson.getString("chain_id")) + ); + return WebResponse.requestOk(); + } + + @Override + @Transactional + public WebResponse updateStation(JSONObject whereJson) { + stationService.update( + new UpdateWrapper().lambda() + .eq(Station::getStation_code, whereJson.getString("station_code")) + .set(Station::getStation_name, whereJson.getString("station_name")) + ); + return WebResponse.requestOk(); + } + + @Override + public WebResponse TaskChainDtl(JSONObject whereJson) { + return WebResponse.requestParamOk(iTaskchainService.queryTaskchainById(whereJson.getString("chain_id"))); + } +} diff --git a/src/main/java/org/nl/qrobot/apt/taskchain/service/ITaskchainService.java b/src/main/java/org/nl/qrobot/apt/taskchain/service/ITaskchainService.java new file mode 100644 index 0000000..a3366aa --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/taskchain/service/ITaskchainService.java @@ -0,0 +1,21 @@ +package org.nl.qrobot.apt.taskchain.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import org.nl.qrobot.apt.taskchain.service.dao.Taskchain; + +import java.util.List; + +/** + *

+ * 服务类 + *

+ * + * @author Liuxy + * @since 2025-07-09 + */ +public interface ITaskchainService extends IService { + + List queryAllTaskchain(); + + List queryTaskchainById(String chain_id); +} diff --git a/src/main/java/org/nl/qrobot/apt/taskchain/service/dao/Taskchain.java b/src/main/java/org/nl/qrobot/apt/taskchain/service/dao/Taskchain.java new file mode 100644 index 0000000..768a877 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/taskchain/service/dao/Taskchain.java @@ -0,0 +1,62 @@ +package org.nl.qrobot.apt.taskchain.service.dao; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; + +/** + *

+ * + *

+ * + * @author author + * @since 2025-07-09 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("taskchain") +public class Taskchain implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 标识 + */ + @TableId(value = "id") + private String id; + + /** + * 任务链标识 + */ + private String chain_id; + + /** + * 任务链名称 + */ + private String chain_name; + + /** + * 顺序号 + */ + private String order_num; + + /** + * 站点编码 + */ + private String station_code; + + /** + * 站点名称 + */ + private String station_name; + + /** + * 动作类型 + */ + private String action_type; + + +} diff --git a/src/main/java/org/nl/qrobot/apt/taskchain/service/dao/mapper/TaskchainMapper.java b/src/main/java/org/nl/qrobot/apt/taskchain/service/dao/mapper/TaskchainMapper.java new file mode 100644 index 0000000..c8bfdca --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/taskchain/service/dao/mapper/TaskchainMapper.java @@ -0,0 +1,28 @@ +package org.nl.qrobot.apt.taskchain.service.dao.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.nl.qrobot.apt.taskchain.service.dao.Taskchain; + +import java.util.List; + +/** + *

+ * Mapper 接口 + *

+ * + * @author Liuxy + * @since 2025-07-09 + */ +@Mapper +public interface TaskchainMapper extends BaseMapper { + + /** + * 查询所有任务链 + * @return + */ + List queryAllTaskchain(); + + List queryTaskchainById(@Param("chain_id") String chain_id); +} diff --git a/src/main/java/org/nl/qrobot/apt/taskchain/service/dao/mapper/TaskchainMapper.xml b/src/main/java/org/nl/qrobot/apt/taskchain/service/dao/mapper/TaskchainMapper.xml new file mode 100644 index 0000000..62da605 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/taskchain/service/dao/mapper/TaskchainMapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/src/main/java/org/nl/qrobot/apt/taskchain/service/impl/TaskchainServiceImpl.java b/src/main/java/org/nl/qrobot/apt/taskchain/service/impl/TaskchainServiceImpl.java new file mode 100644 index 0000000..4315e73 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/taskchain/service/impl/TaskchainServiceImpl.java @@ -0,0 +1,35 @@ +package org.nl.qrobot.apt.taskchain.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.nl.qrobot.apt.taskchain.service.ITaskchainService; +import org.nl.qrobot.apt.taskchain.service.dao.Taskchain; +import org.nl.qrobot.apt.taskchain.service.dao.mapper.TaskchainMapper; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; + +/** + *

+ * 服务实现类 + *

+ * + * @author Liuxy + * @since 2025-07-09 + */ +@Service +public class TaskchainServiceImpl extends ServiceImpl implements ITaskchainService { + + @Resource + private TaskchainMapper taskchainMapper; + + @Override + public List queryAllTaskchain() { + return taskchainMapper.queryAllTaskchain(); + } + + @Override + public List queryTaskchainById(String chain_id) { + return taskchainMapper.queryTaskchainById(chain_id); + } +} diff --git a/src/main/java/org/nl/qrobot/apt/teaching/dto/TeachingRequest.java b/src/main/java/org/nl/qrobot/apt/teaching/dto/TeachingRequest.java new file mode 100644 index 0000000..66f6fe2 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/teaching/dto/TeachingRequest.java @@ -0,0 +1,36 @@ +package org.nl.qrobot.apt.teaching.dto; + +import lombok.Data; + +/** + * @author dsh + * 2025/7/14 + */ +@Data +public class TeachingRequest { + + /** + * 地图名称 + */ + private String mapName; + + /** + * 站点名称 + */ + private String stationName; + + /** + * x坐标 + */ + private Double x; + + /** + * y坐标 + */ + private Double y; + + /** + * 角度 + */ + private Double z; +} diff --git a/src/main/java/org/nl/qrobot/apt/teaching/rest/TeachingController.java b/src/main/java/org/nl/qrobot/apt/teaching/rest/TeachingController.java new file mode 100644 index 0000000..a223098 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/teaching/rest/TeachingController.java @@ -0,0 +1,107 @@ +package org.nl.qrobot.apt.teaching.rest; + +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.nl.qrobot.apt.teaching.service.TeachingService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +/** + * @author dsh + * 2025/7/3 + */ +@Slf4j +@RestController +@RequestMapping("/teaching") +public class TeachingController { + + @Resource + private TeachingService teachingService; + + + @PostMapping("/startMapping") +// @Log("开始建图") + private ResponseEntity startMapping(@RequestParam("mapName") String mapName) { + return new ResponseEntity<>(teachingService.startMapping(mapName), HttpStatus.OK); + } + + @PostMapping("/stopMapping") + private ResponseEntity stopMapping() { + return new ResponseEntity<>(teachingService.stopMapping(), HttpStatus.OK); + } + + @PostMapping("/setStation") + private ResponseEntity setStation(@RequestParam("stationName") String stationName,@RequestParam("stationCode") String stationCode) { + return new ResponseEntity<>(teachingService.setStation(stationName,stationCode), HttpStatus.OK); + } + + @PostMapping("/deployRunMap") + private ResponseEntity deployRunMap(@RequestParam("mapName") String mapName) { + return new ResponseEntity<>(teachingService.deployRunMap(mapName), HttpStatus.OK); + } + + @PostMapping("/changeCurrentRunMap") + private ResponseEntity changeCurrentRunMap(@RequestParam("mapName") String mapName) { + return new ResponseEntity<>(teachingService.changeCurrentRunMap(mapName), HttpStatus.OK); + } + + @PostMapping("/getLocalMaps") +// @Log("获取地图列表") + private JSONObject getLocalMaps() { +// return new ResponseEntity<>(teachingService.getLocalMaps(), HttpStatus.OK); + return teachingService.getLocalMaps(); + } + + @PostMapping("/getRunMapZip") + private ResponseEntity getRunMapZip(@RequestParam("mapName") String mapName) { + return new ResponseEntity<>(teachingService.getRunMapZip(mapName), HttpStatus.OK); + } + +// @PostMapping("/synchronizeMap") +// private ResponseEntity synchronizeMap(@RequestParam("mapName") String mapName) { +// return new ResponseEntity<>(teachingService.synchronizeMap(mapName), HttpStatus.OK); +// } + + @PostMapping("/restart") + private ResponseEntity restart() { + return new ResponseEntity<>(teachingService.restart(), HttpStatus.OK); + } + + @PostMapping("/relocate") + private ResponseEntity relocate(@RequestParam("x") Double x, @RequestParam("y") Double y, @RequestParam("angle") Double angle) { + return new ResponseEntity<>(teachingService.relocate(x, y, angle), HttpStatus.OK); + } + + @PostMapping("/oneClickDeployment") + private ResponseEntity oneClickDeployment(@RequestParam("mapName") String mapName) { + return new ResponseEntity<>(teachingService.oneClickDeployment(mapName), HttpStatus.OK); + } + + @PostMapping("/abandonMapping") + private ResponseEntity abandonMapping() { + return new ResponseEntity<>(teachingService.abandonMapping(),HttpStatus.OK); + } + + @PostMapping("/startManual") + private ResponseEntity startManual() { + return new ResponseEntity<>(teachingService.startManual(), HttpStatus.OK); + } + + @PostMapping("/stopManual") + private ResponseEntity stopManual() { + return new ResponseEntity<>(teachingService.stopManual(), HttpStatus.OK); + } + + @PostMapping("/getMappingStatus") + private ResponseEntity getMappingStatus() { + return new ResponseEntity<>(teachingService.getMappingStatus(), HttpStatus.OK); + } + + @PostMapping("/sendAutoBack") + private ResponseEntity sendAutoBack(@RequestParam("isBack") Integer isBack) { + return new ResponseEntity<>(teachingService.sendAutoBack(isBack), HttpStatus.OK); + } +} diff --git a/src/main/java/org/nl/qrobot/apt/teaching/service/TeachingService.java b/src/main/java/org/nl/qrobot/apt/teaching/service/TeachingService.java new file mode 100644 index 0000000..6948078 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/teaching/service/TeachingService.java @@ -0,0 +1,104 @@ +package org.nl.qrobot.apt.teaching.service; + +import com.alibaba.fastjson.JSONObject; + +import java.util.Map; + +/** + * @author dsh + * 2025/7/3 + */ +public interface TeachingService { + + /** + * 开始建图 + */ + Map startMapping(String mapName); + + /** + * 切软手动,可远程控制 + */ + Map startManual(); + + /** + * 停止软手动,关闭远程控制 + */ + Map stopManual(); + + /** + * 结束建图 + */ + Map stopMapping(); + + /** + * 建图过程中设置站点 + */ + Map setStation(String stationName,String stationCode); + + /** + * 获取后台地图列表 + */ + JSONObject getLocalMaps(); + + /** + * 部署地图 + * @param mapName + * @return + */ + Map deployRunMap(String mapName); + + /** + * 应用地图 + * @param mapName + * @return + */ + Map changeCurrentRunMap(String mapName); + + /** + * 获取主机地图包 + * @param mapName + * @return + */ + byte[] getRunMapZip(String mapName); + + /** + * 同步地图到调度 + */ + Map synchronizeMap(String mapName, byte[] zipFile); + + /** + * 重定位 + * @param x + * @param y + * @param angle + * @return + */ + Map relocate(Double x,Double y,Double angle); + + /** + * 重启车辆后台程序 + */ + Map restart(); + + /** + * 一键部署地图 + * @return + */ + Map oneClickDeployment(String mapName); + + /** + * 放弃当前示教建图 + * @return + */ + Map abandonMapping(); + + /** + * 获取后台地图列表 + */ + Map getMappingStatus(); + + /** + * 打点后 自动回到上一个点 + */ + Map sendAutoBack(Integer isBack); +} diff --git a/src/main/java/org/nl/qrobot/apt/teaching/service/impl/TeachingServiceImpl.java b/src/main/java/org/nl/qrobot/apt/teaching/service/impl/TeachingServiceImpl.java new file mode 100644 index 0000000..9e702d1 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/teaching/service/impl/TeachingServiceImpl.java @@ -0,0 +1,486 @@ +package org.nl.qrobot.apt.teaching.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.nl.qrobot.apt.station.dao.Station; +import org.nl.qrobot.apt.station.service.StationService; +import org.nl.qrobot.apt.teaching.service.TeachingService; +import org.nl.qrobot.apt.vehicle.ProcessZip; +import org.nl.qrobot.apt.vehicle.service.impl.VehicleInfoServiceImpl; +import org.nl.qrobot.common.BadRequestException; +import org.nl.qrobot.config.language.LangProcess; +import org.nl.qrobot.util.HTTPUtil; +import org.nl.qrobot.util.URLConstant; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.Map; + +/** + * @author dsh + * 2025/7/3 + */ +@Slf4j +@Service +public class TeachingServiceImpl implements TeachingService { + + @Resource + private ProcessZip processZip; + + @Resource + private StationService stationService; + + /** + * 建图状态和进度条 + */ + public static Map teachingMappingStatus = new HashMap<>(); + + /** + * 打点 站点编号:站点名称 + */ + public static Map teachingStationMap = new HashMap<>(); + + @Override + public Map startMapping(String mapName) { + if (StrUtil.isBlank(mapName)){ + throw new BadRequestException(LangProcess.msg("param_is_null")); + } + // 开始建图时清除一下 打点记录 + teachingStationMap.clear(); + JSONObject params = new JSONObject(); + params.put("name", mapName); + HttpResponse response = null; + try { + response = HTTPUtil.post(URLConstant.VEHICLE_IP_PORT,"/tool/rob/startMapping", params); + } catch (Exception e) { + log.info("访问车体开始建图接口报错:{}",e.getMessage()); + throw new BadRequestException(LangProcess.msg("error_starting_mapping")); + } + // 检查响应状态码 + if (response.isOk()) { + // 获取响应体内容 + JSONObject body = JSON.parseObject(response.body()); + log.info("开始建图:{}",body); + if ("200".equals(body.getString("code"))){ +// body =(JSONObject) this.startManual(); + body.put("message",LangProcess.msg("successful")); + return body; + } + + } + log.info("开始建图失败"); + throw new BadRequestException(LangProcess.msg("error_starting_mapping")); + } + + @Override + public Map startManual() { + HttpResponse response = null; + try { + response = HTTPUtil.post(URLConstant.VEHICLE_IP_PORT,"/tool/rob/startManual", new JSONObject()); + } catch (Exception e) { + log.info("访问车体切手动接口报错:{}",e.getMessage()); + throw new BadRequestException(LangProcess.msg("error_cut_manually")); + } + // 检查响应状态码 + if (response.isOk() && response.body() != null) { + // 获取响应体内容 + JSONObject body = JSON.parseObject(response.body()); + log.info("切手动:{}",body); + if ("200".equals(body.getString("code"))){ + body.put("message",LangProcess.msg("successful")); + return body; + } + } + log.info("切手动失败"); + throw new BadRequestException(LangProcess.msg("error_cut_manually")); + } + + @Override + public Map stopManual() { + HttpResponse response = null; + try { + response = HTTPUtil.post(URLConstant.VEHICLE_IP_PORT,"/tool/rob/stopManual", new JSONObject()); + } catch (Exception e) { + log.info("访问车体切自动接口报错:{}",e.getMessage()); + throw new BadRequestException(LangProcess.msg("error_cut_automatically")); + } + // 检查响应状态码 + if (response.isOk() && response.body() != null) { + // 获取响应体内容 + JSONObject body = JSON.parseObject(response.body()); + log.info("切自动:{}",body); + if ("200".equals(body.getString("code"))){ + body.put("message",LangProcess.msg("successful")); + } + return body; + } + log.info("切自动失败"); + throw new BadRequestException(LangProcess.msg("error_cut_automatically")); + } + + @Override + public Map stopMapping() { + HttpResponse response = null; + try { + response = HTTPUtil.post(URLConstant.VEHICLE_IP_PORT,"/tool/rob/stopMapping", new JSONObject()); + } catch (Exception e) { + log.info("访问车体结束建图接口报错:{}",e.getMessage()); + throw new BadRequestException(LangProcess.msg("error_end_mapping")); + } + // 检查响应状态码 + if (response.isOk() && response.body() != null) { + // 获取响应体内容 + JSONObject body = JSON.parseObject(response.body()); + log.info("结束建图:{}",body); + if ("200".equals(body.getString("code"))){ +// body =(JSONObject) this.stopManual(); + body.put("message",LangProcess.msg("successful")); + return body; + } + } + log.info("结束建图失败"); + throw new BadRequestException(LangProcess.msg("error_end_mapping")); + } + + @Override + public Map setStation(String stationName,String stationCode) { + if (StrUtil.isBlank(stationCode) || StrUtil.isBlank(stationName)){ + throw new BadRequestException(LangProcess.msg("param_is_null")); + } + // 记录打点时站点数据 + teachingStationMap.put(stationCode,stationName); + JSONObject params = new JSONObject(); + params.put("spotCode", stationCode); + HttpResponse response = null; + try { + response = HTTPUtil.post(URLConstant.VEHICLE_IP_PORT,"/tool/rob/setStates", params); + } catch (Exception e) { + log.info("访问车体设置站点接口报错:{}",e.getMessage()); + throw new BadRequestException(LangProcess.msg("error_set_station")); + } + // 检查响应状态码 + if (response.isOk() && response.body() != null) { + // 获取响应体内容 + JSONObject body = JSON.parseObject(response.body()); + log.info("设置站点:{}",body); + if ("200".equals(body.getString("code"))){ + body.put("message",LangProcess.msg("successful")); + return body; + } + } + log.info("设置站点失败"); + throw new BadRequestException(LangProcess.msg("error_set_station")); + } + + @Override + public JSONObject getLocalMaps() { + HttpResponse response = null; + try { + response = HTTPUtil.get(URLConstant.VEHICLE_IP_PORT,"/tool/editor/getLocalMaps", new JSONObject(),""); + } catch (Exception e) { + log.info("访问车体地图列表接口报错:{}",e.getMessage()); + throw new BadRequestException(LangProcess.msg("error_get_Maps")); + } + // 检查响应状态码 + if (response.isOk() && response.body() != null) { + // 获取响应体内容 + JSONObject body = JSON.parseObject(response.body()); + log.info("获取地图列表:{}",body); + if ("200".equals(body.getString("code"))){ + body.put("message",LangProcess.msg("successful")); + return body; + } + } + log.info("获取地图列表失败"); + throw new BadRequestException(LangProcess.msg("error_get_Maps")); + } + + @Override + public Map deployRunMap(String mapName) { + if (StrUtil.isBlank(mapName)){ + throw new BadRequestException(LangProcess.msg("param_is_null")); + } + JSONObject params = new JSONObject(); + params.put("id", mapName); + HttpResponse response = null; + try { + response = HTTPUtil.post(URLConstant.VEHICLE_IP_PORT,"/tool/rob/deployRunMap", params); + } catch (Exception e) { + log.info("访问车体部署地图接口报错:{}",e.getMessage()); + throw new BadRequestException(LangProcess.msg("error_deploy_maps")); + } + // 检查响应状态码 + if (response.isOk() && response.body() != null) { + // 获取响应体内容 + JSONObject body = JSON.parseObject(response.body()); + log.info("部署地图:{}",body); + if ("200".equals(body.getString("code"))){ + body.put("message",LangProcess.msg("successful")); + return body; + } + } + log.info("部署地图失败"); + throw new BadRequestException(LangProcess.msg("error_deploy_maps")); + } + + @Override + public Map changeCurrentRunMap(String mapName) { + if (StrUtil.isBlank(mapName)){ + throw new BadRequestException(LangProcess.msg("param_is_null")); + } + JSONObject params = new JSONObject(); + params.put("name", mapName); + HttpResponse response = null; + try { + response = HTTPUtil.post(URLConstant.VEHICLE_IP_PORT,"/tool/rob/changeCurrentRunMap", params); + } catch (Exception e) { + log.info("访问车体应用地图接口报错:{}",e.getMessage()); + throw new BadRequestException(LangProcess.msg("error_app_maps")); + } + // 检查响应状态码 + if (response.isOk() && response.body() != null) { + // 获取响应体内容 + JSONObject body = JSON.parseObject(response.body()); + log.info("应用地图:{}",body); + if ("200".equals(body.getString("code"))){ + body.put("message",LangProcess.msg("successful")); + return body; + } + } + log.info("应用地图失败"); + throw new BadRequestException(LangProcess.msg("error_app_maps")); + } + + @Override + public byte[] getRunMapZip(String mapName) { + if (StrUtil.isBlank(mapName)){ + throw new BadRequestException(LangProcess.msg("param_is_null")); + } + JSONObject params = new JSONObject(); + params.put("name", mapName); + HttpResponse response = null; + try { + response = HTTPUtil.post(URLConstant.VEHICLE_IP_PORT,"/tool/rob/getRunMapZip", params); + } catch (Exception e) { + log.info("访问车体地图包接口报错:{}",e.getMessage()); + throw new BadRequestException(LangProcess.msg("error_get_map_pack")); + } + // 检查响应状态码 + if (response.isOk() && response.body() != null) { + // 获取zip包 + byte[] zipBytes = response.bodyBytes(); +// File fileName = new File(mapName); +// File tempZipFile = FileUtil.writeBytes(zipBytes, FileUtil.createTempFile(fileName)); + log.info("解析地图包数据"); + processZip.processZipResponse(zipBytes); + return zipBytes; + } + log.info("获取地图包失败"); + throw new BadRequestException(LangProcess.msg("error_get_map_pack")); + } + + @Override + public Map synchronizeMap(String mapName,byte[] zipFile) { + + HttpResponse response = null; + try { + response = HttpRequest.post(URLConstant.RCS_IP_PORT+"/map/uploadFile") + .setConnectionTimeout(3000) + .setReadTimeout(30000) + .header("token", "admin123") + .header("name", "lx-script") + .header("Content-Type", "multipart/form-data;charset=UTF-8") + .form("areaId", 1) + .form("file",zipFile,mapName+".zip") + .execute(); + } catch (Exception e) { + log.info("同步地图到调度接口报错:{}",e.getMessage()); + throw new BadRequestException(LangProcess.msg("error_synchronized_map")); + } + // 检查响应状态码 + if (response.isOk() && response.body() != null) { + // 获取响应体内容 + JSONObject body = JSON.parseObject(response.body()); + log.info("同步地图:{}",body); + if (body.getBoolean("state")){ + body.put("message",LangProcess.msg("successful")); + return body; + } + } + log.info("同步地图失败"); + throw new BadRequestException(LangProcess.msg("error_synchronized_map")); + } + + @Override + public Map relocate(Double x, Double y, Double angle) { + if (ObjectUtil.isEmpty(x) || ObjectUtil.isEmpty(y) || ObjectUtil.isEmpty(angle)){ + throw new BadRequestException(LangProcess.msg("param_is_null")); + } + log.info(angle.toString()); + angle = (angle * 180) / Math.PI; + log.info(angle.toString()); + JSONObject params = new JSONObject(); + params.put("x", x); + params.put("y", y); + params.put("angle", angle / 180.0 * Math.PI); + params.put("noisyX", 0.5); + params.put("noisyY", 0.5); + params.put("noisyAngle", Math.PI); + log.info(params.getString("angle")); + HttpResponse response = null; + try { + response = HTTPUtil.post(URLConstant.VEHICLE_IP_PORT,"/tool/rob/relocate", params); + } catch (Exception e) { + log.info("访问车体重定位接口报错:{}",e.getMessage()); + throw new BadRequestException(LangProcess.msg("error_relocate")); + } + // 检查响应状态码 + if (response.isOk() && response.body() != null) { + // 获取响应体内容 + JSONObject body = JSON.parseObject(response.body()); + log.info("重定位:{}",body); + if ("200".equals(body.getString("code"))){ + body.put("message",LangProcess.msg("successful")); + return body; + } + } + log.info("重定位指令下发失败"); + throw new BadRequestException(LangProcess.msg("error_relocate")); + } + + @Override + public Map restart() { + HttpResponse response = null; + try { + response = HTTPUtil.post(URLConstant.VEHICLE_IP_PORT,"/tool/rob/restart", new JSONObject()); + } catch (Exception e) { + log.info("访问车体程序重启接口报错:{}",e.getMessage()); + throw new BadRequestException(LangProcess.msg("error_restart")); + } + // 检查响应状态码 + if (response.isOk() && response.body() != null) { + // 获取响应体内容 + JSONObject body = JSON.parseObject(response.body()); + log.info("车体程序重启:{}",body); + if ("200".equals(body.getString("code"))){ + body.put("message",LangProcess.msg("successful")); + return body; + } + } + log.info("车体程序重启失败"); + throw new BadRequestException(LangProcess.msg("error_restart")); + } + + @Override + public Map oneClickDeployment(String mapName) { + if (StrUtil.isBlank(mapName)){ + throw new BadRequestException(LangProcess.msg("param_is_null")); + } + JSONObject response = new JSONObject(); + // 获取地图列表 + this.getLocalMaps(); + // 解析地图数据 + byte[] zipFile = this.getRunMapZip(mapName); + // 部署地图 + this.deployRunMap(mapName); + // 应用地图 + this.changeCurrentRunMap(mapName); + // 解析地图包并同步到调度 + this.synchronizeMap(mapName,zipFile); + // 重启本体程序 + this.restart(); + + // 重定位,需要等待一会 + while (!"1".equals(VehicleInfoServiceImpl.vehicleInfo.getReady())){ + log.info("建图 重启本体程序中,还未接收到本体重启信号"); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + log.info("接收到本体重启信号,发送重定位指令"); + Station station = stationService.getOne(new LambdaQueryWrapper<>(Station.class) + .eq(Station::getStation_code,"A") + ); + this.relocate(station.getX(),station.getY(),station.getAngle()); + response.put("code", 200); + response.put("message", LangProcess.msg("successful")); + return response; + } + + @Override + public Map abandonMapping() { + JSONObject params = new JSONObject(); + String attach = "{"+ + "'sub_command':0,"+ + "'param':''"+ + "}"; + params.put("cmd", 906); + params.put("attach", JSON.parseObject(attach)); + HttpResponse response = null; + try { + response = HTTPUtil.post(URLConstant.VEHICLE_IP_PORT,"/tool/rob/sendCMD", params); + } catch (Exception e) { + log.info("访问车体放弃示教建图接口报错:{}",e.getMessage()); + throw new BadRequestException(LangProcess.msg("error_abandon_mapping")); + } + // 检查响应状态码 + if (response.isOk() && response.body() != null) { + // 获取响应体内容 + JSONObject body = JSON.parseObject(response.body()); + log.info("放弃示教建图:{}",body); + if ("200".equals(body.getString("code"))){ + body.put("message",LangProcess.msg("successful")); + return body; + } + } + log.info("放弃示教建图失败"); + throw new BadRequestException(LangProcess.msg("error_abandon_mapping")); + } + + @Override + public Map getMappingStatus() { + return teachingMappingStatus; + } + + @Override + public Map sendAutoBack(Integer isBack) { + if (ObjectUtil.isEmpty(isBack)){ + throw new BadRequestException(LangProcess.msg("param_is_null")); + } + JSONObject params = new JSONObject(); + String attach = "{"+ + "'sub_command':"+isBack+","+ + "'param':''"+ + "}"; + params.put("cmd", 2212); + params.put("attach", JSON.parseObject(attach)); + HttpResponse response = null; + try { + response = HTTPUtil.post(URLConstant.VEHICLE_IP_PORT,"/tool/rob/sendCMD", params); + } catch (Exception e) { + log.info("访问车体自动返回上一个点接口报错:{}",e.getMessage()); + throw new BadRequestException(LangProcess.msg("error_auto_back")); + } + // 检查响应状态码 + if (response.isOk() && response.body() != null) { + // 获取响应体内容 + JSONObject body = JSON.parseObject(response.body()); + log.info("自动返回上一个点:{}",body); + if ("200".equals(body.getString("code"))){ + body.put("message",LangProcess.msg("successful")); + return body; + } + } + log.info("自动返回上一个点失败"); + throw new BadRequestException(LangProcess.msg("error_auto_back")); + } +} diff --git a/src/main/java/org/nl/qrobot/apt/vehicle/ProcessZip.java b/src/main/java/org/nl/qrobot/apt/vehicle/ProcessZip.java new file mode 100644 index 0000000..9fefd7c --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/vehicle/ProcessZip.java @@ -0,0 +1,556 @@ +package org.nl.qrobot.apt.vehicle; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.nl.qrobot.apt.map.dao.MapInfo; +import org.nl.qrobot.apt.map.dto.ProcessMapYamlDto; +import org.nl.qrobot.apt.map.service.mapper.MapInfoMapper; +import org.nl.qrobot.apt.route.dao.RouteInfo; +import org.nl.qrobot.apt.route.service.RouteInfoService; +import org.nl.qrobot.apt.station.StationTypeEnum; +import org.nl.qrobot.apt.station.dao.Station; +import org.nl.qrobot.apt.station.service.StationService; +import org.nl.qrobot.apt.taskchain.service.ITaskchainService; +import org.nl.qrobot.apt.teaching.service.impl.TeachingServiceImpl; +import org.nl.qrobot.common.BadRequestException; +import org.nl.qrobot.config.file.FileProperties; +import org.nl.qrobot.config.language.LangProcess; +import org.nl.qrobot.util.FileConstant; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import org.yaml.snakeyaml.Yaml; + +import javax.annotation.Resource; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferByte; +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.*; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * 解析迈尔微视地图包数据 + * @author dsh + * 2025/7/7 + */ +@Slf4j +@Service +public class ProcessZip { + + @Resource + private StationService stationService; + + @Resource + private MapInfoMapper mapInfoMapper; + + @Resource + private RouteInfoService routeInfoService; + + @Resource + private ITaskchainService taskchainService; + + @Resource + private FileProperties properties; + + + private static final String PGM_MAGIC_NUMBER = "P5"; + + private List routeInfoList = new ArrayList<>(); + + @Transactional(rollbackFor = Exception.class) + public void processZipResponse(byte[] zipBytes) { + try (ByteArrayInputStream bais = new ByteArrayInputStream(zipBytes); + ZipInputStream zis = new ZipInputStream(bais, StandardCharsets.UTF_8)) { + + MapInfo mapInfo = new MapInfo(); + + //存储之前先清除站点表、路径表、地图信息表的数据 + stationService.remove(new LambdaQueryWrapper<>()); + routeInfoService.remove(new LambdaQueryWrapper<>()); + mapInfoMapper.delete(new LambdaQueryWrapper<>()); + taskchainService.remove(new LambdaQueryWrapper<>()); + + ZipEntry entry; + // 遍历ZIP文件中的每个条目 + while ((entry = zis.getNextEntry()) != null) { + String entryName = entry.getName(); + + // 处理.lxmap文件,获取点位信息和路径信息 + if (entryName.toLowerCase().endsWith(".lxmap")) { + log.info("解析文件: {}", entryName); + + // 读取文件内容(使用BufferedReader按行处理) + BufferedReader reader = new BufferedReader( + new InputStreamReader(zis, StandardCharsets.UTF_8)); + + String line; + int lineNumber = 0; + // 清除路径信息 + routeInfoList.clear(); + while ((line = reader.readLine()) != null) { + lineNumber++; + // 5. 处理每一行数据 + processLine(line, lineNumber); + } + if (lineNumber == 0) { + throw new BadRequestException(LangProcess.msg("error_map_pack_isNull")); + } + // 批量保存路径信息 + if (ObjectUtil.isNotEmpty(routeInfoList)){ + routeInfoList = mergeBidirectionalRoutes(routeInfoList); + routeInfoService.saveBatch(routeInfoList); + } + + } + // 点云图 .pgm 文件转换 .png + if (entryName.toLowerCase().endsWith(".pgm")) { + log.info("处理PGM文件: {}",entryName); + + // 读取整个PGM文件到字节数组 + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + byte[] buffer = new byte[1024]; + int len; + while ((len = zis.read(buffer)) > 0) { + baos.write(buffer, 0, len); + } + byte[] pgmData = baos.toByteArray(); + + // 转换PGM为PNG + BufferedImage pngImage = convertPgmToPng(pgmData); + // 记录地图图片像素高、宽 + mapInfo.setWidth((double) pngImage.getWidth()); + mapInfo.setHeight((double) pngImage.getHeight()); + if (ObjectUtil.isEmpty(pngImage)) { + log.info("PGM转换失败: {}", entryName); + throw new BadRequestException(LangProcess.msg("error_map_pgm")); + } + + // 将灰度图转换为透明背景图 + pngImage = convertToTransparentBackground(pngImage); + + String mapName = entryName.substring(0, entryName.lastIndexOf('.')); + // 保存PNG文件 + String pngFileName = mapName + ".png"; + // 记录地图编码、名称 + mapInfo.setMapCode(mapName); + mapInfo.setMapName(mapName); + mapInfo.setMapImageAddress("/file/"+pngFileName); + savePngImage(pngImage, pngFileName); + } + } + // 解析.yaml点云图信息(像素比例,点云图左下角像素坐标信息) + if (entryName.toLowerCase().endsWith(".yaml")) { + log.info("处理.yaml文件地图信息: {}",entryName); + + try{ + BufferedReader reader = new BufferedReader(new InputStreamReader(zis, StandardCharsets.UTF_8)); + // 检查并处理YAML头部 + StringBuilder contentBuilder = new StringBuilder(); + String line = reader.readLine(); + // 读取内容标识 + boolean flag = false; + // 跳过以%开头的指令行 + if (line != null && line.startsWith("%")) { + log.info("跳过YAML指令: {}" , line); + // 读取下一行 + line = reader.readLine(); + } + + // 读取剩余内容 + while (line != null) { + flag = true; + contentBuilder.append(line).append("\n"); + line = reader.readLine(); + } + if (!flag){ + throw new BadRequestException(LangProcess.msg("error_yaml_isNull")); + } + // 解析YAML内容 + String yamlContent = contentBuilder.toString(); + ProcessMapYamlDto processMapYamlDto = new Yaml().loadAs(yamlContent, ProcessMapYamlDto.class); + log.info(".yaml数据: {}", processMapYamlDto); + if (ObjectUtil.isEmpty(processMapYamlDto) || processMapYamlDto.getOrigin().length<3){ + throw new BadRequestException(LangProcess.msg("error_formatting_error")); + } + // 记录像素比例和图片左下角像素坐标 + mapInfo.setResolution(processMapYamlDto.getResolution()); + mapInfo.setX(processMapYamlDto.getOrigin()[0]); + mapInfo.setY(processMapYamlDto.getOrigin()[1]); + mapInfo.setAngle(processMapYamlDto.getOrigin()[2]); + } catch (IOException e) { + throw new BadRequestException(LangProcess.msg("error_parsing")+ e.getMessage()); + } + } + } + // 保存地图信息 + if (ObjectUtil.isNotEmpty(mapInfo)) { + log.info("地图信息:{}",mapInfo); + mapInfoMapper.insert(mapInfo); + } + } + catch (Exception e) { + throw new BadRequestException(LangProcess.msg("error_process_map_pack")+e.getMessage()); + } + } + + // 将灰度图像转换为透明背景图像 + private BufferedImage convertToTransparentBackground(BufferedImage grayImage) { + try { + + // 创建支持透明度的ARGB图像 + BufferedImage transparentImage = new BufferedImage( + grayImage.getWidth(), + grayImage.getHeight(), + BufferedImage.TYPE_INT_ARGB + ); + + // 获取灰度图像的像素数据 + byte[] grayPixels = ((DataBufferByte) grayImage.getRaster().getDataBuffer()).getData(); + int width = grayImage.getWidth(); + int height = grayImage.getHeight(); + + // 创建ARGB像素数组 + int[] argbPixels = new int[width * height]; + +// // 转换每个像素 +// for (int y = 0; y < height; y++) { +// for (int x = 0; x < width; x++) { +// int index = y * width + x; +// // 灰度值(0-255),0=黑,255=白 +// int grayValue = grayPixels[index] & 0xFF; +// +// // 创建ARGB像素(Alpha, Red, Green, Blue) +// // 白色背景(>=250)设为完全透明,其他保持黑色但完全不透明 +// if (grayValue >= 250) { +// // 完全透明 (Alpha=0) +// argbPixels[index] = 0x00FFFFFF; +// } else { +// // 完全不透明 (Alpha=255) + 灰度值转为RGB +// int rgb = grayValue << 16 | grayValue << 8 | grayValue; +// argbPixels[index] = 0xFF000000 | rgb; +// } +// } +// } + // 转换每个像素 + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int index = y * width + x; + // 灰度值(0-255),0=黑,255=白 + int grayValue = grayPixels[index] & 0xFF; + + // 创建ARGB像素(Alpha, Red, Green, Blue) + if (grayValue >= 250) { + // 白色背景(>=250)设为完全透明 + argbPixels[index] = 0x00FFFFFF; // 透明 + } else { + // 黑色点云转为白色点云(完全不透明) + argbPixels[index] = 0xFFFFFFFF; // 不透明的白色 + } + } + } + + // 设置ARGB像素数据 + transparentImage.setRGB(0, 0, width, height, argbPixels, 0, width); + return transparentImage; + }catch (Exception e) { + log.info(".png点云图将灰度图像转换为透明背景图像失败:{}",e.getMessage()); + throw new BadRequestException(LangProcess.msg("error_png_convert_transparency")+e.getMessage()); + } + } + + public void processLine(String line, int lineNumber) { + // 跳过空行 + if (line.trim().isEmpty()) { + return; + } + + // 检查是否以"Cairn"开头 + if (line.startsWith("Cairn:")) { + // 截取开头后面的数据(6个字符长度) + String dataPart = line.substring(6).trim(); + + if (!dataPart.isEmpty()) { + // 按空格分割字段(多个连续空格视为一个分隔符) + String[] processData = dataPart.split("\\s+"); + + //判断数据是否是点位数据 && 数据长度正常 + // Goal点位标识 + if ("Goal".equals(processData[0]) && processData.length > 9) { + System.out.printf("行号 %d - 有效点位数据: %s%n", lineNumber, Arrays.toString(processData)); + Station station = new Station(); + station.setStation_id(Integer.valueOf(processData[1])); + String stationName = ""; + if ("A".equals(processData[2])){ + stationName = LangProcess.msg("starting_point"); + }else if ("C".equals(processData[2])){ + stationName = LangProcess.msg("end_point"); + }else { + // 判断是否存在打点记录里 + if (TeachingServiceImpl.teachingStationMap.containsKey(processData[2])){ + stationName = TeachingServiceImpl.teachingStationMap.get(processData[2]); + }else { + stationName = processData[2]; + } + } + station.setStation_code(processData[2]); + station.setStation_name(stationName); + station.setX(Double.valueOf(processData[5])); + station.setY(Double.valueOf(processData[6])); + station.setAngle(Double.valueOf(processData[7])); + // 1类型手动设置站点: 工位点、自定义 + if ("1".equals(processData[8])) { + station.setAction_type(StationTypeEnum.CUSTOMIZE.getCode()); + station.setStation_type(StationTypeEnum.STATION.getCode()); + // 0类型系统自动生成: 系统自动生成点、只移动 + }else if ("0".equals(processData[8])) { + station.setAction_type(StationTypeEnum.MOVE.getCode()); + station.setStation_type(StationTypeEnum.SYSTEM.getCode()); + } + stationService.save(station); + System.out.printf("行号 %d - 有效数据: %s%n", lineNumber, station); + //Route路径标识 + }else if ("Route".equals(processData[0]) && processData.length >22 ){ + System.out.printf("行号 %d - 有效路径数据: %s%n", lineNumber, Arrays.toString(processData)); + RouteInfo routeInfo = new RouteInfo(); + routeInfo.setRoute_id(Integer.valueOf(processData[1])); + routeInfo.setStart_id(Integer.valueOf(processData[2])); + routeInfo.setEnd_id(Integer.valueOf(processData[3])); + routeInfo.setStart_x(Double.valueOf(processData[4])); + routeInfo.setStart_y(Double.valueOf(processData[5])); + routeInfo.setEnd_x(Double.valueOf(processData[6])); + routeInfo.setEnd_y(Double.valueOf(processData[7])); + routeInfo.setNavigation_mode(processData[16].replaceAll("^\"|\"$", "")); + routeInfo.setRoute_type(processData[17].replaceAll("^\"|\"$", "")); + routeInfoList.add(routeInfo); +// routeInfoService.save(routeInfo); + + System.out.printf("行号 %d - 有效数据: %s%n", lineNumber, routeInfo); + } + } else { + System.out.printf("行号 %d - 警告: 'Cairn: Goal'后无内容%n", lineNumber); + } + } else { + // 非目标行,可根据需要处理或忽略 + System.out.printf("行号 %d - 忽略: %s%n", lineNumber, line); + } + } + + private BufferedImage convertPgmToPng(byte[] pgmData) { + try { + // 使用字节缓冲区方便解析 + ByteBuffer buffer = ByteBuffer.wrap(pgmData); + + // 解析魔术数字 (P5) + StringBuilder magic = new StringBuilder(); + char c; + while ((c = (char) buffer.get()) != '\n') { + magic.append(c); + } + if (!magic.toString().equals(PGM_MAGIC_NUMBER)) { + throw new BadRequestException(LangProcess.msg("error_p5")); + } + + // 解析宽度和高度 + StringBuilder dimensionBuilder = new StringBuilder(); + while ((c = (char) buffer.get()) != '\n') { + dimensionBuilder.append(c); + } + String[] dimensions = dimensionBuilder.toString().trim().split("\\s+"); + if (dimensions.length < 2) { + throw new BadRequestException(LangProcess.msg("error_invalid_size")); + } + int width = Integer.parseInt(dimensions[0]); + int height = Integer.parseInt(dimensions[1]); + + // 解析最大灰度值 (通常255) + StringBuilder maxValBuilder = new StringBuilder(); + while ((c = (char) buffer.get()) != '\n') { + maxValBuilder.append(c); + } + int maxVal = Integer.parseInt(maxValBuilder.toString().trim()); + + // 创建BufferedImage (灰度图像) + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); + byte[] imageData = ((DataBufferByte) image.getRaster().getDataBuffer()).getData(); + + // 复制图像数据 (跳过头部) + buffer.get(imageData); + + return image; + } catch (Exception e) { + log.info("PGM解析错误: {}", e.getMessage()); + throw new BadRequestException(LangProcess.msg("error_pgm_parse")+e.getMessage()); + } + } + + private void savePngImage(BufferedImage image, String fileName) { + try { + // 确保-输出目录存在 + File outputDir = new File(properties.getPath().getPath()); + if (!outputDir.exists()) { + outputDir.mkdirs(); + } + + // 写入PNG文件 + File outputFile = new File(outputDir, fileName); + ImageIO.write(image, "png", outputFile); + log.info("PNG文件已保存: {}", outputFile.getAbsolutePath()); + } catch (IOException e) { + log.info("保存PNG失败: {}",e.getMessage()); + } + } + + /** + * 处理上传的异常文件 + * @param file + * @throws IOException + */ + public void processCompressedFile(MultipartFile file) throws IOException { + List skippedFiles = new ArrayList<>(); + + String filename = file.getOriginalFilename(); + String extension = filename.substring(filename.lastIndexOf(".")); + + try (InputStream inputStream = file.getInputStream()) { + if (extension.toLowerCase().equals(".zip")) { + extractZipFile(inputStream, skippedFiles); + } else { + throw new BadRequestException("不支持的压缩格式: " + extension); + } + } + } + + /** + * 解压上传异常图片压缩文件 + * @param inputStream + * @param skippedFiles + * @throws IOException + */ + private void extractZipFile(InputStream inputStream, List skippedFiles) + throws IOException { + int processedCount = 0; + int skippedCount = 0; + + try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) { + ZipEntry entry; + while ((entry = zipInputStream.getNextEntry()) != null) { + if (!entry.isDirectory() && isImageFile(entry.getName())) { + String filename = new File(entry.getName()).getName(); + + if (isFileExists(filename)) { + skippedFiles.add(filename); + skippedCount++; + continue; + } + + if (saveImageFile(zipInputStream, filename)) { + processedCount++; + } + } + zipInputStream.closeEntry(); + } + } + log.info("处理文件数:{},跳过文件数:{},跳过文件名称集合:{}",processedCount,skippedCount,skippedFiles); + } + + /** + * 判断是否是图片文件或者视频文件 + * @param filename + * @return + */ + private boolean isImageFile(String filename) { + String lowerCaseFilename = filename.toLowerCase(); + return lowerCaseFilename.endsWith(".jpg") || + lowerCaseFilename.endsWith(".jpeg") || + lowerCaseFilename.endsWith(".png") || + lowerCaseFilename.endsWith(".gif") || + lowerCaseFilename.endsWith(".bmp") || + lowerCaseFilename.endsWith(".webp")|| + lowerCaseFilename.endsWith(".mp4"); + } + + /** + * 查看文件名是否有重名 + * @param filename + * @return + */ + private boolean isFileExists(String filename) { + File targetFile = new File(FileConstant.ERROR_IMAGE_PATH+ "/" + filename); + return targetFile.exists(); + } + + /** + * 保存图片文件到目录 + * @param inputStream + * @param filename + * @return + */ + private boolean saveImageFile(InputStream inputStream, String filename) { + try { + File outputFile = new File(FileConstant.ERROR_IMAGE_PATH + "/" + filename); + + // 确保目录存在 + File parentDir = outputFile.getParentFile(); + if (!parentDir.exists()) { + parentDir.mkdirs(); + } + + Files.copy(inputStream, outputFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + return true; + } catch (IOException e) { + log.error("保存文件失败: " + filename, e); + return false; + } + } + + public static List mergeBidirectionalRoutes(List originalData) { + List mergedData = new ArrayList<>(); + Map routeMap = new HashMap<>(); + Set processedIds = new HashSet<>(); + + // 第一次遍历:构建快速查找映射 + for (RouteInfo route : originalData) { + String key = route.getStart_id() + "-" + route.getEnd_id(); + routeMap.put(key, route); + } + + // 第二次遍历:查找并合并双向路径 + for (RouteInfo route : originalData) { + if (processedIds.contains(route.getRoute_id())) { + continue; + } + +// String forwardKey = route.getStart_id() + "-" + route.getEnd_id(); + String reverseKey = route.getEnd_id() + "-" + route.getStart_id(); + + // 检查是否存在精确反向路径 + if (routeMap.containsKey(reverseKey)) { + RouteInfo reverseRoute = routeMap.get(reverseKey); + + if (!processedIds.contains(reverseRoute.getRoute_id())) { + // 选择routeId较小的作为主记录,导航模式改为2(双向) + RouteInfo bidirectionalRoute = route.getRoute_id() < reverseRoute.getRoute_id() ? route : reverseRoute; + bidirectionalRoute.setNavigation_mode("2"); + mergedData.add(bidirectionalRoute); + + processedIds.add(route.getRoute_id()); + processedIds.add(reverseRoute.getRoute_id()); + } + } else { + // 没有反向路径,直接添加 + mergedData.add(route); + processedIds.add(route.getRoute_id()); + } + } + + return mergedData; + } +} diff --git a/src/main/java/org/nl/qrobot/apt/vehicle/dao/VehicleException.java b/src/main/java/org/nl/qrobot/apt/vehicle/dao/VehicleException.java new file mode 100644 index 0000000..f85a3e4 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/vehicle/dao/VehicleException.java @@ -0,0 +1,26 @@ +package org.nl.qrobot.apt.vehicle.dao; + +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * @author dsh + * 2025/7/2 + */ +@Data +public class VehicleException implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 错误信息 + */ + private List exception; + + /** + * 错误码 + */ + private List exceptionCodes; + +} diff --git a/src/main/java/org/nl/qrobot/apt/vehicle/dao/VehicleInfo.java b/src/main/java/org/nl/qrobot/apt/vehicle/dao/VehicleInfo.java new file mode 100644 index 0000000..29c08cc --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/vehicle/dao/VehicleInfo.java @@ -0,0 +1,147 @@ +package org.nl.qrobot.apt.vehicle.dao; + +import lombok.Data; +import org.nl.qrobot.apt.anomalyInfo.dao.ErrorInfo; + +import java.io.Serializable; +import java.util.List; + +/** + * @author dsh + * 2025/7/2 + */ +@Data +public class VehicleInfo implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 车辆 ID + */ + private String id; + + /** + * 车辆名称 + */ + private String name; + + /** + * 车辆 IP + */ + private String ip; + + /** + * 车辆所在地图 ID + */ + private Long mapId; + + /** + * 车辆所在地图 名称 + */ + private String mapName; + + /** + * 车辆状态 ID + */ + private Long stateId; + + /** + * 车辆状态名称 + */ + private String state; + + /** + * 区域 ID + */ + private Long areaId; + + /** + * 电量 + */ + private Integer batteryPower; + + /** + * 异常信息 + */ + private VehicleException exceptionInfo; + + /** + * 车辆x坐标 + */ + private double x; + + /** + * 车辆y坐标 + */ + private double y; + + /** + * 车辆角度 + */ + private double theta; + + /** + * 是否是手动模式 + */ + private Boolean isManual; + + /** + * 车辆当前执行的任务链 + */ + private String task_seq; + + /** + * 车辆任务链中当前点位 + */ + private String task_point; + + /** + * 是否重启完成 1重启完成 + */ + private String ready; + + /** + * 叉间避障 + */ + private String forkTipObstacles; + + /** + * 当前异常列表 + */ + private List errorData; + + /** + * 异常等级 0无异常(绿灯) 1普通异常(黄灯) 2严重异常(红灯) + */ + private Integer anomalyLevel; + + /** + * 打点后是否可以自动沿着路线开出来(0计算中 1失败 2成功) + */ + private String auto_back_enable; + + /** + * 起点终点是否重叠,结束建图 + */ + private String auto_loop_enable; + + /** + * 自动回退状态(0未处理 1成功 2失败) + */ + private String auto_back_finish; + + /** + * RCS调度连接状态 + */ + private boolean rcsConnected; + + /** + * 本体连接状态 + */ + private boolean vehicleConnected; + + /** + * 车辆载货状态 0未载货 1载货 + */ + private String vehiclePayloads; + +} diff --git a/src/main/java/org/nl/qrobot/apt/vehicle/rest/VehicleInfoController.java b/src/main/java/org/nl/qrobot/apt/vehicle/rest/VehicleInfoController.java new file mode 100644 index 0000000..bbdfe1f --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/vehicle/rest/VehicleInfoController.java @@ -0,0 +1,43 @@ +package org.nl.qrobot.apt.vehicle.rest; + +import lombok.extern.slf4j.Slf4j; +import org.nl.qrobot.apt.vehicle.dao.VehicleInfo; +import org.nl.qrobot.apt.vehicle.service.VehicleInfoService; +import org.nl.qrobot.common.logging.annotation.Log; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +/** + * @author dsh + * 2025/7/3 + */ +@Slf4j +@RestController +@RequestMapping("/vehicle") +public class VehicleInfoController { + + @Resource + private VehicleInfoService vehicleInfoService; + + @GetMapping("/getVehicleInfo") + @Log("获取车辆信息") + public VehicleInfo getVehicleInfo() { + return vehicleInfoService.getVehicleInfo(); + } + + @GetMapping("/rebootVehicle") + @Log("重启车辆系统") + public ResponseEntity rebootVehicle() { + return new ResponseEntity<>(vehicleInfoService.rebootVehicle(), HttpStatus.OK); + } + + @PostMapping("/setForkLegsObstacles") + @Log("设置叉腿避障") + public ResponseEntity setForkLegsObstacles(@RequestParam("backIoStatus") String backIoStatus) { + return new ResponseEntity<>(vehicleInfoService.setForkLegsObstacles(backIoStatus), HttpStatus.OK); + } + +} diff --git a/src/main/java/org/nl/qrobot/apt/vehicle/service/VehicleInfoService.java b/src/main/java/org/nl/qrobot/apt/vehicle/service/VehicleInfoService.java new file mode 100644 index 0000000..7e1a1ad --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/vehicle/service/VehicleInfoService.java @@ -0,0 +1,35 @@ +package org.nl.qrobot.apt.vehicle.service; + +import org.nl.qrobot.apt.vehicle.dao.VehicleInfo; + +import java.util.Map; + +/** + * @author dsh + * 2025/7/2 + */ +public interface VehicleInfoService { + + /** + * 获取车辆基础信息 + * @return + */ + VehicleInfo getVehicleInfo(); + + void setVehicleInfo(VehicleInfo vehicleInfo); + + /** + * 车辆系统重启 + */ + Map rebootVehicle(); + + /** + * 系统初始化 + */ + void systemInitialization(); + + /** + * 设置叉尖避障 + */ + Map setForkLegsObstacles(String backIoStatus); +} diff --git a/src/main/java/org/nl/qrobot/apt/vehicle/service/impl/VehicleInfoServiceImpl.java b/src/main/java/org/nl/qrobot/apt/vehicle/service/impl/VehicleInfoServiceImpl.java new file mode 100644 index 0000000..147235a --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/vehicle/service/impl/VehicleInfoServiceImpl.java @@ -0,0 +1,310 @@ +package org.nl.qrobot.apt.vehicle.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.http.HttpResponse; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.nl.qrobot.apt.anomalyInfo.dao.ErrorInfo; +import org.nl.qrobot.apt.anomalyInfo.service.ErrorInfoService; +import org.nl.qrobot.apt.map.dto.PointCloudDataDto; +import org.nl.qrobot.apt.map.service.MapInfoService; +import org.nl.qrobot.apt.route.service.RouteInfoService; +import org.nl.qrobot.apt.station.service.StationService; +import org.nl.qrobot.apt.task.enums.TaskStatus; +import org.nl.qrobot.apt.task.service.dao.Task; +import org.nl.qrobot.apt.task.service.dao.mapper.TaskMapper; +import org.nl.qrobot.apt.taskchain.service.ITaskchainService; +import org.nl.qrobot.apt.vehicle.dao.VehicleException; +import org.nl.qrobot.apt.vehicle.dao.VehicleInfo; +import org.nl.qrobot.apt.vehicle.service.VehicleInfoService; +import org.nl.qrobot.apt.websocket.WebSocketPointCloudDataServer; +import org.nl.qrobot.apt.websocket.WebSocketVehicleServer; +import org.nl.qrobot.common.BadRequestException; +import org.nl.qrobot.config.language.LangProcess; +import org.nl.qrobot.config.language.RcsLang; +import org.nl.qrobot.system.enums.ParamCodeConstant; +import org.nl.qrobot.system.service.ParamService; +import org.nl.qrobot.util.HTTPUtil; +import org.nl.qrobot.util.URLConstant; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.util.*; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * @author dsh + * 2025/7/2 + */ +@Slf4j +@Service +public class VehicleInfoServiceImpl implements VehicleInfoService { + + @Resource + private TaskScheduler scheduler; + + @Resource + private TaskMapper taskMapper; + + @Resource + private StationService stationService; + + @Resource + private MapInfoService mapInfoService; + + @Resource + private RouteInfoService routeInfoService; + + @Resource + private ITaskchainService taskchainService; + + @Resource + private ErrorInfoService errorInfoService; + + @Resource + private ParamService paramService; + + public static VehicleInfo vehicleInfo = new VehicleInfo(); + + public static Set currentPointCloudData = new HashSet<>(); + + public static Set globalPointCloudData = new HashSet<>(); + + @Override + public VehicleInfo getVehicleInfo() { + return vehicleInfo; + } + + @Override + public void setVehicleInfo(VehicleInfo vehicleInfo) { + + } + + @Override + public JSONObject rebootVehicle() { + HttpResponse response = null; + JSONObject result = new JSONObject(); + result.put("status", LangProcess.msg("failed")); + try { + response = HTTPUtil.post(URLConstant.VEHICLE_IP_PORT,"/tool/rob/reboot", new JSONObject()); + if (response.isOk()){ + result.put("status", LangProcess.msg("successful")); + } + return result; + } catch (Exception e) { + log.info("重启系统失败:{}",e.getMessage()); + throw new BadRequestException(LangProcess.msg("error_restart_system")); + } + } + + @Override + public void systemInitialization() { + HttpResponse response = null; + try { + response = HTTPUtil.post(URLConstant.VEHICLE_IP_PORT,"/tool/rob/recover", new JSONObject()); + } catch (Exception e) { + log.info("初始化底层系统失败:{}",e.getMessage()); + throw new BadRequestException(LangProcess.msg("error_initializing_system")); + } + + // 暂时缺少 调度系统初始化接口 + + // 清除本地数据 + taskMapper.delete(new LambdaQueryWrapper<>()); + stationService.remove(new LambdaQueryWrapper<>()); + routeInfoService.remove(new LambdaQueryWrapper<>()); + mapInfoService.remove(new LambdaQueryWrapper<>()); + taskchainService.remove(new LambdaQueryWrapper<>()); + } + + @Override + public Map setForkLegsObstacles(String backIoStatus) { +// if (StrUtil.isBlank(stationCode) || StrUtil.isBlank(stationName)){ +// throw new BadRequestException("spotCode is empty"); +// } + JSONObject params = new JSONObject(); + params.put("enable_back_tof", backIoStatus); + HttpResponse response = null; + try { + response = HTTPUtil.post(URLConstant.VEHICLE_IP_PORT,"/tool/rob/setStates", params); + } catch (Exception e) { + log.info("访问车体关闭叉腿避障接口报错:{}",e.getMessage()); + throw new BadRequestException(LangProcess.msg("error_set_forkLegs")); + } + // 检查响应状态码 + if (response.isOk() && response.body() != null) { + // 获取响应体内容 + JSONObject body = JSON.parseObject(response.body()); + log.info("操作叉腿避障:{}",body); + if ("200".equals(body.getString("code"))){ + body.put("message",LangProcess.msg("successful")); + return body; + } + } + log.info("操作叉腿避障失败"); + throw new BadRequestException(LangProcess.msg("error_set_forkLegs")); + } + + @Async("asynchronousTasks") + public void queryVehicleInfo() { + HttpResponse response = null; + vehicleInfo.setRcsConnected(false); + try { + response = HTTPUtil.get(URLConstant.RCS_IP_PORT,"/amr/onlineAmr",null,""); + // 检查响应状态码 + if (response!=null && response.isOk()) { + // 获取响应体内容 + String body = response.body(); + JSONArray jsonArray = JSONObject.parseObject(body).getJSONArray("data"); + if (jsonArray.isEmpty()) { + log.info("车辆数据为空"); + return; + } + //调度连接状态 + vehicleInfo.setRcsConnected(true); + for (int i = 0; i < jsonArray.size(); i++) { + JSONObject data = jsonArray.getJSONObject(i); + vehicleInfo.setId(data.getString("id")); + vehicleInfo.setName(data.getString("name")); + vehicleInfo.setIp(data.getString("ip")); + vehicleInfo.setMapId(data.getLong("mapId")); + vehicleInfo.setMapName(data.getString("mapName")); + vehicleInfo.setStateId(data.getLong("stateId")); + vehicleInfo.setState(data.getString("state")); + vehicleInfo.setAreaId(data.getLong("areaId")); + vehicleInfo.setErrorData(new ArrayList<>()); + //上报的异常信息 + JSONObject amrException = data.getJSONObject("amrException"); + VehicleException vehicleException = JSONObject.toJavaObject(amrException, VehicleException.class); + vehicleInfo.setExceptionInfo(vehicleException); + List errorInfoList = new ArrayList<>(); + // 消息列表 + if (ObjectUtil.isNotEmpty(vehicleException.getExceptionCodes())){ + errorInfoList = errorInfoService.list(new LambdaQueryWrapper().in(ErrorInfo::getError_code,vehicleException.getExceptionCodes())); + } + // 异常等级 0无异常 1普通异常 2严重异常 + OptionalInt maxLevel = errorInfoList.stream().mapToInt(ErrorInfo::getError_category).max(); + int anomalyLevel = 0; + if (maxLevel.isPresent()) { + anomalyLevel = maxLevel.getAsInt(); + } + vehicleInfo.setAnomalyLevel(anomalyLevel); + vehicleInfo.setErrorData(errorInfoList); + System.out.println("vehicleInfo: " + vehicleInfo); + } + } else { + log.info("查询调度车辆信息失败:{}",response); + } + } catch (Exception e) { + log.info("访问调度报错{}", e.getMessage()); + } + if (response != null) { + response.close(); + } + } + + public VehicleInfo queryVehicleInfoI18n(String lang,VehicleInfo vehicleInfo) { + HttpResponse response = null; + vehicleInfo.setRcsConnected(false); + try { + response = HTTPUtil.get(URLConstant.RCS_IP_PORT,"/amr/onlineAmr",null,RcsLang.getRcsLanguage(lang)); + // 检查响应状态码 + if (response!=null && response.isOk()) { + // 获取响应体内容 + String body = response.body(); + JSONArray jsonArray = JSONObject.parseObject(body).getJSONArray("data"); + if (jsonArray.isEmpty()) { + log.info("车辆数据为空"); + return vehicleInfo; + } + vehicleInfo.setRcsConnected(true); + for (int i = 0; i < jsonArray.size(); i++) { + JSONObject data = jsonArray.getJSONObject(i); + vehicleInfo.setId(data.getString("id")); + vehicleInfo.setName(data.getString("name")); + vehicleInfo.setIp(data.getString("ip")); + vehicleInfo.setMapId(data.getLong("mapId")); + vehicleInfo.setMapName(data.getString("mapName")); + vehicleInfo.setStateId(data.getLong("stateId")); + vehicleInfo.setState(data.getString("state")); + vehicleInfo.setAreaId(data.getLong("areaId")); + vehicleInfo.setErrorData(new ArrayList<>()); + //上报的异常信息 + JSONObject amrException = data.getJSONObject("amrException"); + VehicleException vehicleException = JSONObject.toJavaObject(amrException, VehicleException.class); + vehicleInfo.setExceptionInfo(vehicleException); + List errorInfoList = new ArrayList<>(); + // 消息列表 + if (ObjectUtil.isNotEmpty(vehicleException.getExceptionCodes())){ + errorInfoList = errorInfoService.list(new LambdaQueryWrapper().in(ErrorInfo::getError_code,vehicleException.getExceptionCodes())); + } + vehicleInfo.setErrorData(errorInfoList); + System.out.println("vehicleInfo: " + vehicleInfo); + } + } else { + log.info("查询调度车辆信息失败:{}",response); + } + } catch (Exception e) { + log.info("访问调度报错{}", e.getMessage()); + } + if (response != null) { + response.close(); + } + return vehicleInfo; + } + + @Async("asynchronousTasks") + public void sendVehicleInfo() { + CopyOnWriteArraySet webSocketSet = + WebSocketVehicleServer.getWebSocketSet(); + if (webSocketSet.size() > 0) { + Task task = taskMapper.selectOne(new LambdaQueryWrapper<>(Task.class).eq(Task::getTask_status, TaskStatus.CREATE.getCode()).or().eq(Task::getTask_status, TaskStatus.EXECUTING.getCode())); + if (ObjectUtil.isNotEmpty(task)){ + vehicleInfo.setTask_seq(task.getTask_seq()); + vehicleInfo.setTask_point(task.getTask_point()); + }else { + vehicleInfo.setTask_seq(""); + vehicleInfo.setTask_point(""); + } + webSocketSet.forEach(c -> { + Map vehicleInfoMap = new HashMap<>(); + VehicleInfo webVehicleInfo = vehicleInfo; + if (!"zh".equals(c.getLang())){ + webVehicleInfo = this.queryVehicleInfoI18n(c.getLang(),webVehicleInfo); + } + vehicleInfoMap.put("data", webVehicleInfo); + c.sendDataToClient(vehicleInfoMap); + }); + } + } + + @Async("asynchronousTasks") + public void sendPointCloudData() { + CopyOnWriteArraySet webSocketSet = + WebSocketPointCloudDataServer.getWebSocketSet(); + if (webSocketSet.size() > 0) { + webSocketSet.forEach(c -> { + Map vehicleInfoMap = new HashMap<>(); + vehicleInfoMap.put("globalData", globalPointCloudData); + vehicleInfoMap.put("currentData", currentPointCloudData); + c.sendDataToClient(vehicleInfoMap); + }); + } + } + + @PostConstruct + public void init() { + // 初始化车辆IP和调度IP + URLConstant.RCS_IP_PORT = paramService.queryParamObjectByCode(ParamCodeConstant.RCS_URL).getValue(); + URLConstant.VEHICLE_IP_PORT = paramService.queryParamObjectByCode(ParamCodeConstant.VEHICLE_URL).getValue(); + scheduler.scheduleAtFixedRate(this::queryVehicleInfo,1000); + scheduler.scheduleAtFixedRate(this::sendVehicleInfo, 1000); + scheduler.scheduleAtFixedRate(this::sendPointCloudData, 1000); + } +} diff --git a/src/main/java/org/nl/qrobot/apt/websocket/WebSocketPointCloudDataServer.java b/src/main/java/org/nl/qrobot/apt/websocket/WebSocketPointCloudDataServer.java new file mode 100644 index 0000000..60cb661 --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/websocket/WebSocketPointCloudDataServer.java @@ -0,0 +1,126 @@ +package org.nl.qrobot.apt.websocket; + +import cn.hutool.core.map.MapUtil; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.websocket.*; +import javax.websocket.server.PathParam; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * @author dsh + * 2025/7/25 + */ +@Slf4j +@ServerEndpoint("/webSocket/PointCloudData/{sid}") +@Component +public class WebSocketPointCloudDataServer { + + /** + * concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。 + */ + private static CopyOnWriteArraySet webSocketSet = new CopyOnWriteArraySet<>(); + + /** + * 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。 + */ + private static int onlineCount = 0; + + + /** + * 与某个客户端的连接会话,需要通过它来给客户端发送数据 + */ + private Session session; + /** + * 接收userId + */ + private String sid = ""; + + /** + * 连接建立成功调用的方法 + */ + @OnOpen + public void onOpen(Session session, @PathParam("sid") String sid) { + this.session = session; + //如果存在就先删除一个,防止重复推送消息 + webSocketSet.removeIf(webSocket -> webSocket.sid.equals(sid)); + webSocketSet.add(this); + //在线数加1 + addOnlineCount(); + log.info("PointCloudDataWS:sid{}连接成功,当前在线人数为{}", sid, getOnlineCount()); + this.sid = sid; + } + + /** + * 连接关闭调用的方法 + */ + @OnClose + public void onClose() { + webSocketSet.remove(this); + //在线数减1 + subOnlineCount(); + log.info("PointCloudDataWS:sid{}关闭连接!当前在线人数为{}", sid, getOnlineCount()); + } + + /** + * 收到客户端消息后调用的方法 + * + * @param message 客户端发送过来的消息 + */ + @OnMessage + public void onMessage(String message, Session session) { + //System.out.println(webSocketSet.size() + "_接收到消息_" + session.getId()); + } + + @OnError + public void onError(Session session, Throwable error) { + //log.error("发生错误"); + webSocketSet.remove(session); + error.printStackTrace(); + } + + public Session getSession() { + return session; + } + + // 发送消息,在定时任务中会调用此方法 + public void sendMessage(String message) throws IOException { + this.session.getBasicRemote().sendText(message); + } + + + public void sendDataToClient(Map data) { + try { + if (this.session != null&& MapUtil.isNotEmpty(data)) { + this.session.getBasicRemote().sendText(JSON.toJSONString(data)); + } + } catch (IOException e) { + log.error("发送消息给客户端失败", e); + } + } + public static synchronized int getOnlineCount() { + return onlineCount; + } + + public static CopyOnWriteArraySet getWebSocketSet() { + return webSocketSet; + } + + + public void setSession(Session session) { + this.session = session; + } + + public static synchronized void addOnlineCount() { + WebSocketPointCloudDataServer.onlineCount++; + } + + public static synchronized void subOnlineCount() { + WebSocketPointCloudDataServer.onlineCount--; + } +} diff --git a/src/main/java/org/nl/qrobot/apt/websocket/WebSocketVehicleServer.java b/src/main/java/org/nl/qrobot/apt/websocket/WebSocketVehicleServer.java new file mode 100644 index 0000000..865d90f --- /dev/null +++ b/src/main/java/org/nl/qrobot/apt/websocket/WebSocketVehicleServer.java @@ -0,0 +1,150 @@ +package org.nl.qrobot.apt.websocket; + +import cn.hutool.core.map.MapUtil; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.websocket.*; +import javax.websocket.server.PathParam; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * @author dsh + * 2025/7/3 + */ +@Slf4j +@ServerEndpoint("/webSocket/VehicleInfo/{sid}") +@Component +public class WebSocketVehicleServer { + + /** + * concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。 + */ + private static CopyOnWriteArraySet webSocketSet = new CopyOnWriteArraySet<>(); + + /** + * 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。 + */ + private static int onlineCount = 0; + + + /** + * 与某个客户端的连接会话,需要通过它来给客户端发送数据 + */ + private Session session; + /** + * 接收userId + */ + private String sid = ""; + + /** + * 默认语言 + */ + private String lang = "zh"; + + /** + * 连接建立成功调用的方法 + */ + @OnOpen + public void onOpen(Session session, @PathParam("sid") String sid) { + this.session = session; + + // 获取查询参数中的语言设置 + String queryString = session.getRequestURI().getQuery(); + if (queryString != null) { + String[] params = queryString.split("&"); + for (String param : params) { + if (param.startsWith("lang=")) { + // 获取lang参数值 + this.lang = param.substring(5); + break; + } + } + } + + //如果存在就先删除一个,防止重复推送消息 + webSocketSet.removeIf(webSocket -> webSocket.sid.equals(sid)); + webSocketSet.add(this); + //在线数加1 + addOnlineCount(); + log.info("VehicleWS:sid{}连接成功,当前在线人数为{}", sid, getOnlineCount()); + this.sid = sid; + } + + /** + * 连接关闭调用的方法 + */ + @OnClose + public void onClose() { + webSocketSet.remove(this); + //在线数减1 + subOnlineCount(); + log.info("VehicleWS:sid{}关闭连接!当前在线人数为{}", sid, getOnlineCount()); + } + + /** + * 收到客户端消息后调用的方法 + * + * @param message 客户端发送过来的消息 + */ + @OnMessage + public void onMessage(String message, Session session) { + //System.out.println(webSocketSet.size() + "_接收到消息_" + session.getId()); + } + + @OnError + public void onError(Session session, Throwable error) { + //log.error("发生错误"); + webSocketSet.remove(session); + error.printStackTrace(); + } + + public Session getSession() { + return session; + } + + // 获取语言的方法 + public String getLang() { + return lang; + } + + // 推送方法 + public void sendMessage(String message) throws IOException { + this.session.getBasicRemote().sendText(message); + } + + + public void sendDataToClient(Map data) { + try { + if (this.session != null&& MapUtil.isNotEmpty(data)) { + this.session.getBasicRemote().sendText(JSON.toJSONString(data)); + } + } catch (IOException e) { + log.error("发送消息给客户端失败", e); + } + } + public static synchronized int getOnlineCount() { + return onlineCount; + } + + public static CopyOnWriteArraySet getWebSocketSet() { + return webSocketSet; + } + + + public void setSession(Session session) { + this.session = session; + } + + public static synchronized void addOnlineCount() { + WebSocketVehicleServer.onlineCount++; + } + + public static synchronized void subOnlineCount() { + WebSocketVehicleServer.onlineCount--; + } +} diff --git a/src/main/java/org/nl/qrobot/common/BadRequestException.java b/src/main/java/org/nl/qrobot/common/BadRequestException.java new file mode 100644 index 0000000..a78f4be --- /dev/null +++ b/src/main/java/org/nl/qrobot/common/BadRequestException.java @@ -0,0 +1,25 @@ +package org.nl.qrobot.common; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; + +/** + * @author dsh + * 2025/7/3 + */ +@Getter +public class BadRequestException extends RuntimeException{ + + private Integer status = BAD_REQUEST.value(); + + public BadRequestException(String msg){ + super(msg); + } + + public BadRequestException(HttpStatus status, String msg){ + super(msg); + this.status = status.value(); + } +} diff --git a/src/main/java/org/nl/qrobot/common/excel/ErrorHandlingListener.java b/src/main/java/org/nl/qrobot/common/excel/ErrorHandlingListener.java new file mode 100644 index 0000000..c9fdc2f --- /dev/null +++ b/src/main/java/org/nl/qrobot/common/excel/ErrorHandlingListener.java @@ -0,0 +1,91 @@ +package org.nl.qrobot.common.excel; + +import com.alibaba.excel.context.AnalysisContext; +import com.alibaba.excel.event.AnalysisEventListener; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.IService; +import lombok.extern.slf4j.Slf4j; +import org.nl.qrobot.apt.anomalyInfo.dao.ErrorHandling; +import org.nl.qrobot.common.BadRequestException; +import org.nl.qrobot.config.language.LangProcess; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author dsh + * 2025/8/27 + */ +@Slf4j +public class ErrorHandlingListener extends AnalysisEventListener { + /** + * 每隔 BATCH_COUNT 条存储数据库,然后清理list,方便内存回收 + */ + private static final int BATCH_COUNT = 1000; // 批处理阈值:cite[2]:cite[5] + + private List cachedDataList = new ArrayList<>(BATCH_COUNT); + + /** + * 假设我们需要Service进行批量插入 + * 也可以使用Mapper,但Service层封装批量操作更常见 + */ + private IService service; + + /** + * 通过构造器传入需要的Service或Mapper + */ + public ErrorHandlingListener(IService service) { + this.service = service; + } + + /** + * 每读一行数据,都会调用此方法 + * @param data 一行数据,类型是泛型T + * @param context + */ + @Override + public void invoke(ErrorHandling data, AnalysisContext context) { + cachedDataList.add(data); + // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM + if (cachedDataList.size() >= BATCH_COUNT) { + saveData(); + // 存储完成清理 list + cachedDataList.clear(); + } + } + + /** + * 所有数据解析完成后,会调用此方法 + * @param context + */ + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + // 确保最后一批数据也持久化到数据库 + if (!cachedDataList.isEmpty()) { + saveData(); + cachedDataList.clear(); + } + log.info("所有Excel数据解析并导入完成!"); + } + + /** + * 批量保存数据到数据库 + * 使用MyBatis-Plus的saveBatch方法 + */ + @Transactional(rollbackFor = Exception.class) + public void saveData() { + if (!cachedDataList.isEmpty()) { + // 清除所有异常信息 重新导入 + service.remove(new QueryWrapper()); + // 第二个参数是批次大小 + boolean saveResult = service.saveBatch(cachedDataList, BATCH_COUNT); + if (saveResult) { + log.info("成功批量插入 {} 条数据。", cachedDataList.size()); + } else { + log.error("批量插入数据失败!"); + throw new BadRequestException(LangProcess.msg("failed")); + } + } + } +} diff --git a/src/main/java/org/nl/qrobot/common/excel/ErrorInfoListener.java b/src/main/java/org/nl/qrobot/common/excel/ErrorInfoListener.java new file mode 100644 index 0000000..f3fd371 --- /dev/null +++ b/src/main/java/org/nl/qrobot/common/excel/ErrorInfoListener.java @@ -0,0 +1,92 @@ +package org.nl.qrobot.common.excel; + +import com.alibaba.excel.context.AnalysisContext; +import com.alibaba.excel.event.AnalysisEventListener; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.IService; +import lombok.extern.slf4j.Slf4j; +import org.nl.qrobot.apt.anomalyInfo.dao.ErrorInfo; +import org.nl.qrobot.common.BadRequestException; +import org.nl.qrobot.config.language.LangProcess; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author dsh + * 2025/8/26 + */ +@Slf4j +public class ErrorInfoListener extends AnalysisEventListener { + + /** + * 每隔 BATCH_COUNT 条存储数据库,然后清理list,方便内存回收 + */ + private static final int BATCH_COUNT = 1000; // 批处理阈值:cite[2]:cite[5] + + private List cachedDataList = new ArrayList<>(BATCH_COUNT); + + /** + * 假设我们需要Service进行批量插入 + * 也可以使用Mapper,但Service层封装批量操作更常见 + */ + private IService service; + + /** + * 通过构造器传入需要的Service或Mapper + */ + public ErrorInfoListener(IService service) { + this.service = service; + } + + /** + * 每读一行数据,都会调用此方法 + * @param data 一行数据,类型是泛型T + * @param context + */ + @Override + public void invoke(ErrorInfo data, AnalysisContext context) { + cachedDataList.add(data); + // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM + if (cachedDataList.size() >= BATCH_COUNT) { + saveData(); + // 存储完成清理 list + cachedDataList.clear(); + } + } + + /** + * 所有数据解析完成后,会调用此方法 + * @param context + */ + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + // 确保最后一批数据也持久化到数据库 + if (!cachedDataList.isEmpty()) { + saveData(); + cachedDataList.clear(); + } + log.info("所有Excel数据解析并导入完成!"); + } + + /** + * 批量保存数据到数据库 + * 使用MyBatis-Plus的saveBatch方法 + */ + @Transactional(rollbackFor = Exception.class) + public void saveData() { + if (!cachedDataList.isEmpty()) { + // 清除所有异常信息 重新导入 + service.remove(new QueryWrapper()); + // 第二个参数是批次大小 + boolean saveResult = service.saveBatch(cachedDataList, BATCH_COUNT); + if (saveResult) { + log.info("成功批量插入 {} 条数据。", cachedDataList.size()); + } else { + log.error("批量插入数据失败!"); + throw new BadRequestException(LangProcess.msg("failed")); + } + } + } +} diff --git a/src/main/java/org/nl/qrobot/common/exception/ApiError.java b/src/main/java/org/nl/qrobot/common/exception/ApiError.java new file mode 100644 index 0000000..881ef9c --- /dev/null +++ b/src/main/java/org/nl/qrobot/common/exception/ApiError.java @@ -0,0 +1,49 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.nl.qrobot.common.exception; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +class ApiError { + + private Integer code = 400; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime Date; + private String message; + + private ApiError() { + Date = LocalDateTime.now(); + } + + public static ApiError error(String message){ + ApiError apiError = new ApiError(); + apiError.setMessage(message); + return apiError; + } + + public static ApiError error(Integer status, String message){ + ApiError apiError = new ApiError(); + apiError.setCode(status); + apiError.setMessage(message); + return apiError; + } +} + + diff --git a/src/main/java/org/nl/qrobot/common/exception/GlobalExceptionHandler.java b/src/main/java/org/nl/qrobot/common/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..82940f2 --- /dev/null +++ b/src/main/java/org/nl/qrobot/common/exception/GlobalExceptionHandler.java @@ -0,0 +1,78 @@ +package org.nl.qrobot.common.exception; + +import lombok.extern.slf4j.Slf4j; +import org.nl.qrobot.common.BadRequestException; +import org.nl.qrobot.util.ThrowableUtil; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.Objects; + + +/** + * @author liejiu + */ +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + /** + * 处理所有不可知的异常 + */ + @ExceptionHandler(Throwable.class) + public ResponseEntity handleException(Throwable e){ + // 打印堆栈信息 + log.error(ThrowableUtil.getStackTrace(e)); + return buildResponseEntity(ApiError.error(e.getMessage())); + } + + /** + * token 无效的异常拦截 + * @param e + * @return + */ +// @ExceptionHandler(value = NotLoginException.class) +// public ResponseEntity notLoginException(Exception e) { +//// log.error(ThrowableUtil.getStackTrace(e)); +// return buildResponseEntity(ApiError.error(401,"token 失效")); +// } + + + /** + * 处理自定义异常 + */ + @ExceptionHandler(value = BadRequestException.class) + public ResponseEntity badRequestException(BadRequestException e) { + // 打印堆栈信息 + log.error(ThrowableUtil.getStackTrace(e)); + log.info(e.getMessage()); + return buildResponseEntity(ApiError.error(e.getStatus(),e.getMessage())); + } + + + /** + * 处理所有接口数据验证异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e){ + // 打印堆栈信息 + log.error(ThrowableUtil.getStackTrace(e)); + String[] str = Objects.requireNonNull(e.getBindingResult().getAllErrors().get(0).getCodes())[1].split("\\."); + String message = e.getBindingResult().getAllErrors().get(0).getDefaultMessage(); + String msg = "不能为空"; + if(msg.equals(message)){ + message = str[1] + ":" + message; + } + return buildResponseEntity(ApiError.error(message)); + } + + /** + * 统一返回 + */ + private ResponseEntity buildResponseEntity(ApiError apiError) { + return new ResponseEntity<>(apiError, HttpStatus.valueOf(apiError.getCode())); + } +} diff --git a/src/main/java/org/nl/qrobot/common/logging/annotation/Log.java b/src/main/java/org/nl/qrobot/common/logging/annotation/Log.java new file mode 100644 index 0000000..198c9c4 --- /dev/null +++ b/src/main/java/org/nl/qrobot/common/logging/annotation/Log.java @@ -0,0 +1,15 @@ +package org.nl.qrobot.common.logging.annotation; + +import java.lang.annotation.*; + +/** + * @author dsh + * 2025/7/3 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Log { + String value() default ""; + +} diff --git a/src/main/java/org/nl/qrobot/common/logging/aspect/LogAspect.java b/src/main/java/org/nl/qrobot/common/logging/aspect/LogAspect.java new file mode 100644 index 0000000..2eb29ec --- /dev/null +++ b/src/main/java/org/nl/qrobot/common/logging/aspect/LogAspect.java @@ -0,0 +1,78 @@ +package org.nl.qrobot.common.logging.aspect; + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.nl.qrobot.common.logging.annotation.Log; +import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; + + +/** + * @author dsh + * 2025/7/3 + */ +@Slf4j +@Aspect +@Component +public class LogAspect { + + /** + * 配置切入点 + */ + @Pointcut("@annotation(org.nl.qrobot.common.logging.annotation.Log)") + public void logPointCut(){} + + /** + * 环绕通知 + * @param point + * @return + * @throws Throwable + */ + @Around("logPointCut()") + public Object logAround(ProceedingJoinPoint point) throws Throwable { + Object result = null; + long beginTime = System.currentTimeMillis(); + // 执行方法 + result = point.proceed(); + // 执行时长(毫秒) + long time = System.currentTimeMillis() - beginTime; + // 保存日志 + saveLog(point, time); + return result; + } + + private void saveLog(ProceedingJoinPoint joinPoint, long time) { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + Log logAnnotation = method.getAnnotation(Log.class); + // 注解上的描述 + String description = ""; + if (logAnnotation != null) { + description = logAnnotation.value(); + } + // 请求的方法名 + String className = joinPoint.getTarget().getClass().getName(); + String methodName = signature.getName(); + // 请求的方法参数值 + Object[] args = joinPoint.getArgs(); + // 请求的方法参数名称 + LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer(); + String[] paramNames = u.getParameterNames(method); + String params = ""; + if (args != null && paramNames != null) { + + for (int i = 0; i < args.length; i++) { + params += " " + paramNames[i] + ": " + args[i]; + } + } + log.info("【日志注解】开始执行 -- 描述:{} -- 类方法:{}.{}() -- 参数:{} -- 执行时长:{}毫秒", description, className, methodName, params, time); + } + + +} diff --git a/src/main/java/org/nl/qrobot/config/CallBack.java b/src/main/java/org/nl/qrobot/config/CallBack.java new file mode 100644 index 0000000..6fa43b7 --- /dev/null +++ b/src/main/java/org/nl/qrobot/config/CallBack.java @@ -0,0 +1,20 @@ +package org.nl.qrobot.config; + +/** + * @author dsh + * 2025/8/25 + */ +public interface CallBack { + /** + * 回调执行方法 + */ + void executor(); + + /** + * 本回调任务名称 + * @return / + */ + default String getCallBackName() { + return Thread.currentThread().getId() + ":" + this.getClass().getName(); + } +} diff --git a/src/main/java/org/nl/qrobot/config/CorsConfig.java b/src/main/java/org/nl/qrobot/config/CorsConfig.java new file mode 100644 index 0000000..95ee6a9 --- /dev/null +++ b/src/main/java/org/nl/qrobot/config/CorsConfig.java @@ -0,0 +1,46 @@ +package org.nl.qrobot.config; + +import org.nl.qrobot.config.file.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; + +/** + * @author dsh + * 2025/7/14 + */ +@Configuration +@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 avatarUtl = "file:" + path.getAvatar().replace("\\","/"); + String pathUtl = "file:" + path.getPath().replace("\\","/"); + registry.addResourceHandler("/avatar/**").addResourceLocations(avatarUtl).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("/**") + .allowedOriginPatterns("*") + .allowedHeaders(CorsConfiguration.ALL) + .allowedMethods(CorsConfiguration.ALL) + .allowCredentials(true) + .maxAge(3600); + } +} diff --git a/src/main/java/org/nl/qrobot/config/MapOf.java b/src/main/java/org/nl/qrobot/config/MapOf.java new file mode 100644 index 0000000..64a2c4b --- /dev/null +++ b/src/main/java/org/nl/qrobot/config/MapOf.java @@ -0,0 +1,20 @@ +package org.nl.qrobot.config; + + +import java.io.Serializable; +import java.util.HashMap; + +/* + * @author ZZQ + * @Date 2022/11/29 2:55 下午 + */ +public class MapOf implements Serializable { + + public static HashMap of(K... key){ + HashMap map = new HashMap<>(); + for (int i = 0; i < (key.length & ~1); i=i+2) { + map.put(key[i],key[i+1]); + } + return map; + } +} diff --git a/src/main/java/org/nl/qrobot/config/SpringContextHolder.java b/src/main/java/org/nl/qrobot/config/SpringContextHolder.java new file mode 100644 index 0000000..c8df540 --- /dev/null +++ b/src/main/java/org/nl/qrobot/config/SpringContextHolder.java @@ -0,0 +1,141 @@ +package org.nl.qrobot.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.core.env.Environment; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author dsh + * 2025/8/25 + */ +@Slf4j +public class SpringContextHolder implements ApplicationContextAware, DisposableBean { + private static ApplicationContext applicationContext = null; + //数据库连接的bean名字 + public static String dataSourceBeanName="dataSource"; + private static final List CALL_BACKS = new ArrayList<>(); + private static boolean addCallback = true; + + /** + * 针对 某些初始化方法,在SpringContextHolder 未初始化时 提交回调方法。 + * 在SpringContextHolder 初始化后,进行回调使用 + * + * @param callBack 回调函数 + */ + public synchronized static void addCallBacks(CallBack callBack) { + if (addCallback) { + SpringContextHolder.CALL_BACKS.add(callBack); + } else { + log.warn("CallBack:{} 已无法添加!立即执行", callBack.getCallBackName()); + callBack.executor(); + } + } + public static ApplicationContext getApplicationContext() { + try { + + } catch (Exception e) { + e.printStackTrace(); + } + return applicationContext; + + } + + /** + * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. + */ + @SuppressWarnings("unchecked") + public static T getBean(String name) { + assertContextInjected(); + return (T) applicationContext.getBean(name); + } + + /** + * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. + */ + public static T getBean(Class requiredType) { + assertContextInjected(); + return applicationContext.getBean(requiredType); + } + + /** + * 获取SpringBoot 配置信息 + * + * @param property 属性key + * @param defaultValue 默认值 + * @param requiredType 返回类型 + * @return / + */ + public static T getProperties(String property, T defaultValue, Class requiredType) { + T result = defaultValue; + try { + result = getBean(Environment.class).getProperty(property, requiredType); + } catch (Exception ignored) {} + return result; + } + + /** + * 获取SpringBoot 配置信息 + * + * @param property 属性key + * @return / + */ + public static String getProperties(String property) { + return getProperties(property, null, String.class); + } + + /** + * 获取SpringBoot 配置信息 + * + * @param property 属性key + * @param requiredType 返回类型 + * @return / + */ + public static T getProperties(String property, Class requiredType) { + return getProperties(property, null, requiredType); + } + + /** + * 检查ApplicationContext不为空. + */ + private static void assertContextInjected() { + if (applicationContext == null) { + throw new IllegalStateException("applicaitonContext属性未注入, 请在applicationContext" + + ".xml中定义SpringContextHolder或在SpringBoot启动类中注册SpringContextHolder."); + } + } + + /** + * 清除SpringContextHolder中的ApplicationContext为Null. + */ + private static void clearHolder() { + log.debug("清除SpringContextHolder中的ApplicationContext:" + + applicationContext); + applicationContext = null; + } + + @Override + public void destroy() { + SpringContextHolder.clearHolder(); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + if (SpringContextHolder.applicationContext != null) { + log.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" + SpringContextHolder.applicationContext); + } + SpringContextHolder.applicationContext = applicationContext; + if (addCallback) { + for (CallBack callBack : SpringContextHolder.CALL_BACKS) { + callBack.executor(); + } + CALL_BACKS.clear(); + } + SpringContextHolder.addCallback = false; + } +} diff --git a/src/main/java/org/nl/qrobot/config/WebMvcConfig.java b/src/main/java/org/nl/qrobot/config/WebMvcConfig.java new file mode 100644 index 0000000..60a46c8 --- /dev/null +++ b/src/main/java/org/nl/qrobot/config/WebMvcConfig.java @@ -0,0 +1,21 @@ +package org.nl.qrobot.config; + +import org.nl.qrobot.config.language.InitLocaleResolver; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * @author dsh + * 2025/8/25 + */ +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + + @Bean + public LocaleResolver localeResolver(){ + return new InitLocaleResolver(); + } + +} diff --git a/src/main/java/org/nl/qrobot/config/file/FileProperties.java b/src/main/java/org/nl/qrobot/config/file/FileProperties.java new file mode 100644 index 0000000..7b10358 --- /dev/null +++ b/src/main/java/org/nl/qrobot/config/file/FileProperties.java @@ -0,0 +1,45 @@ +package org.nl.qrobot.config.file; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * @author dsh + * 2025/7/24 + */ +@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 avatar; + } +} diff --git a/src/main/java/org/nl/qrobot/config/language/InitLocaleResolver.java b/src/main/java/org/nl/qrobot/config/language/InitLocaleResolver.java new file mode 100644 index 0000000..ce0b486 --- /dev/null +++ b/src/main/java/org/nl/qrobot/config/language/InitLocaleResolver.java @@ -0,0 +1,44 @@ +package org.nl.qrobot.config.language; + +import cn.hutool.core.util.StrUtil; +import org.nl.qrobot.config.MapOf; +import org.springframework.web.servlet.LocaleResolver; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Locale; +import java.util.Map; + +/** + * @author dsh + * 2025/8/25 + */ +public class InitLocaleResolver implements LocaleResolver { + + public static Map Language_Country = MapOf.of("vi","vi-VN","id","in-ID","in","in-ID","en-US,en;q=0.9","en-US","en","en-US","zh","zh-CN","ko","ko-KR"); + public static String language = ""; + + @Override + public Locale resolveLocale(HttpServletRequest request) { + String header = request.getHeader("Accept-Language"); + if (StrUtil.isNotEmpty(header)){ + String lang = Language_Country.get(header); + language = lang; + if (StrUtil.isNotEmpty(lang)){ + String[] l = lang.split("-"); + //印尼的ISO标准国家代码为id-ID + return new Locale(l[0], l[1]); + } + } + return Locale.getDefault(); + } + + @Override + public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) { + + } + + public String getLanguage(){ + return language; + } +} diff --git a/src/main/java/org/nl/qrobot/config/language/LangProcess.java b/src/main/java/org/nl/qrobot/config/language/LangProcess.java new file mode 100644 index 0000000..7785ca7 --- /dev/null +++ b/src/main/java/org/nl/qrobot/config/language/LangProcess.java @@ -0,0 +1,23 @@ +package org.nl.qrobot.config.language; + +import cn.hutool.core.util.StrUtil; +import org.nl.qrobot.config.SpringContextHolder; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; + +/** + * @author dsh + * 2025/8/25 + */ +public class LangProcess { + + public static String msg(String code,String...args){ + MessageSource bean = SpringContextHolder.getBean(MessageSource.class); + if (StrUtil.isEmpty(code)){ + return " "; + } + String message = bean.getMessage(code, args, LocaleContextHolder.getLocale()); + return message; + } + +} diff --git a/src/main/java/org/nl/qrobot/config/language/RcsLang.java b/src/main/java/org/nl/qrobot/config/language/RcsLang.java new file mode 100644 index 0000000..06ec924 --- /dev/null +++ b/src/main/java/org/nl/qrobot/config/language/RcsLang.java @@ -0,0 +1,21 @@ +package org.nl.qrobot.config.language; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.nl.qrobot.config.MapOf; + +import java.util.Map; + +/** + * @author dsh + * 2025/8/27 + */ +@Getter +@AllArgsConstructor +public class RcsLang { + public static Map Language_Country = MapOf.of("en","EN","zh","ZH"); + + public static String getRcsLanguage(String language) { + return Language_Country.get(language); + } +} diff --git a/src/main/java/org/nl/qrobot/config/satoken/SaTokenConfig.java b/src/main/java/org/nl/qrobot/config/satoken/SaTokenConfig.java new file mode 100644 index 0000000..a114048 --- /dev/null +++ b/src/main/java/org/nl/qrobot/config/satoken/SaTokenConfig.java @@ -0,0 +1,32 @@ +package org.nl.qrobot.config.satoken; + +import cn.dev33.satoken.interceptor.SaInterceptor; +import cn.dev33.satoken.router.SaRouter; +import cn.dev33.satoken.stp.StpUtil; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import javax.annotation.Resource; + +/** + * @author dsh + * 2025/10/22 + */ +@Configuration +public class SaTokenConfig implements WebMvcConfigurer { + + @Resource + private SecurityProperties securityProperties; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new SaInterceptor(handle -> { + SaRouter + .match("/**") + .check(r -> StpUtil.checkLogin()); + })) + .addPathPatterns("/**") + .excludePathPatterns(securityProperties.getExcludes()); + } +} diff --git a/src/main/java/org/nl/qrobot/config/satoken/SecurityProperties.java b/src/main/java/org/nl/qrobot/config/satoken/SecurityProperties.java new file mode 100644 index 0000000..f98087f --- /dev/null +++ b/src/main/java/org/nl/qrobot/config/satoken/SecurityProperties.java @@ -0,0 +1,21 @@ +package org.nl.qrobot.config.satoken; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * @author dsh + * 2025/10/22 + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "security") +public class SecurityProperties { + + /** + * 排除路径 + */ + private String[] excludes; + +} diff --git a/src/main/java/org/nl/qrobot/config/thread/AsyncTaskExecutePool.java b/src/main/java/org/nl/qrobot/config/thread/AsyncTaskExecutePool.java new file mode 100644 index 0000000..7866f6b --- /dev/null +++ b/src/main/java/org/nl/qrobot/config/thread/AsyncTaskExecutePool.java @@ -0,0 +1,85 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.nl.qrobot.config.thread; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * 异步任务线程池装配类 + * + * @author https://juejin.im/entry/5abb8f6951882555677e9da2 + * @date 2019年10月31日15:06:18 + */ +@Slf4j +@Configuration +@EnableAsync +public class AsyncTaskExecutePool implements AsyncConfigurer{ + + @Override + public Executor getAsyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + //核心线程池大小 + executor.setCorePoolSize(20); + //最大线程数 + executor.setMaxPoolSize(50); + //队列容量 + executor.setQueueCapacity(50); + //活跃时间 + executor.setKeepAliveSeconds(60); + //线程名字前缀 + executor.setThreadNamePrefix("Async-"); + // setRejectedExecutionHandler:当pool已经达到max size的时候,如何处理新任务 + // CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + executor.initialize(); + return executor; + } + + /** + * 线程池配置 + */ + @Bean(name = "asynchronousTasks") + public ThreadPoolTaskExecutor threadPoolTaskExecutor() { + ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor(); + // 核心线程池大小 + threadPoolTaskExecutor.setCorePoolSize(20); + // 最大线程数 + threadPoolTaskExecutor.setMaxPoolSize(50); + // 队列容量 + threadPoolTaskExecutor.setQueueCapacity(50); + // 活跃时间 + threadPoolTaskExecutor.setKeepAliveSeconds(60); + // 主线程等待子线程执行时间 + threadPoolTaskExecutor.setAwaitTerminationSeconds(50); + // threadPoolTaskExecutor.setAwaitTerminationSeconds(30); + // 线程名字前缀 + threadPoolTaskExecutor.setThreadNamePrefix("apt-thread-"); + // RejectedExecutionHandler:当pool已经达到max-size的时候,如何处理新任务 + // CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行 + threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + // 初始化 + threadPoolTaskExecutor.initialize(); + return threadPoolTaskExecutor; + } +} diff --git a/src/main/java/org/nl/qrobot/config/thread/CorsFilter.java b/src/main/java/org/nl/qrobot/config/thread/CorsFilter.java new file mode 100644 index 0000000..92661e6 --- /dev/null +++ b/src/main/java/org/nl/qrobot/config/thread/CorsFilter.java @@ -0,0 +1,55 @@ +package org.nl.qrobot.config.thread; + +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * 跨域过滤器 + * @author kong + */ +@Component +@Order(-200) +public class CorsFilter implements Filter { + + static final String OPTIONS = "OPTIONS"; + + @Override + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) + throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) req; + HttpServletResponse response = (HttpServletResponse) res; + // 允许指定域访问跨域资源 + response.setHeader("Access-Control-Allow-Origin", "*"); + // 允许所有请求方式 + response.setHeader("Access-Control-Allow-Methods", "*"); + // 有效时间 + response.setHeader("Access-Control-Max-Age", "3600"); + // 允许的header参数 + response.setHeader("Access-Control-Allow-Headers", "*"); + response.setHeader("Access-Control-Allow-Credentials", "true"); + + // 如果是预检请求,直接返回 + if (OPTIONS.equals(request.getMethod())) { + System.out.println("=======================浏览器发来了OPTIONS预检请求=========="); + response.getWriter().print(""); + return; + } + + // System.out.println("*********************************过滤器被使用**************************"); + chain.doFilter(req, res); + } + + @Override + public void init(FilterConfig filterConfig) { + } + + @Override + public void destroy() { + } + +} diff --git a/src/main/java/org/nl/qrobot/config/thread/ProtobufWebSocketHandler.java b/src/main/java/org/nl/qrobot/config/thread/ProtobufWebSocketHandler.java new file mode 100644 index 0000000..cd29afe --- /dev/null +++ b/src/main/java/org/nl/qrobot/config/thread/ProtobufWebSocketHandler.java @@ -0,0 +1,182 @@ +package org.nl.qrobot.config.thread; + +import com.alibaba.fastjson.JSONObject; +import comm_protocol.Robottype; +import comm_protocol.Uidata; +import lombok.extern.slf4j.Slf4j; +import org.nl.qrobot.apt.map.dto.PointCloudDataDto; +import org.nl.qrobot.apt.teaching.service.impl.TeachingServiceImpl; +import org.nl.qrobot.apt.vehicle.service.impl.VehicleInfoServiceImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.BinaryMessage; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.client.WebSocketConnectionManager; +import org.springframework.web.socket.handler.BinaryWebSocketHandler; + +import java.text.DecimalFormat; +import java.util.HashSet; +import java.util.Set; + +import static java.lang.Math.cos; +import static java.lang.Math.sin; + +/** + * @author dsh + * 2025/7/9 + */ +@Slf4j +@Component +@EnableScheduling +public class ProtobufWebSocketHandler extends BinaryWebSocketHandler { + + public static Boolean isConnected = false; + @Autowired + private WebSocketConnectionManager connectionManager; + private WebSocketSession currentSession; + + + @Override + protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) { + try { + // 解析Protobuf数据 + byte[] payload = message.getPayload().array(); + Uidata.UiDatagram datagram = Uidata.UiDatagram.parseFrom(payload); + + switch (datagram.getItemCase()) { + case ROBOT_BASE: + Robottype.RobotBase robotBase = datagram.getRobotBase(); + JSONObject data_pool = JSONObject.parseObject(robotBase.getDataPool()); + String mapping_return = data_pool.getString("mapping_return"); + String mapping_percent = data_pool.getString("mapping_percent"); + + //打点后是否可以自动沿着路线开出来(0计算中 1失败 2成功) + VehicleInfoServiceImpl.vehicleInfo.setAuto_back_enable(data_pool.getString("auto_back_enable")); + //起点终点是否重叠,结束建图 + VehicleInfoServiceImpl.vehicleInfo.setAuto_loop_enable(data_pool.getString("auto_loop_enable")); + // 自动回退状态标识 0 不处理 1 自动回退成功 2 自动回退失败 + VehicleInfoServiceImpl.vehicleInfo.setAuto_back_finish(data_pool.getString("auto_back_finish")); + // 车辆载货状态 0未载货 1载货 + VehicleInfoServiceImpl.vehicleInfo.setVehiclePayloads(data_pool.getString("carry_status")); + String ready = data_pool.getString("ready"); + TeachingServiceImpl.teachingMappingStatus.put("mapping_return", mapping_return); + TeachingServiceImpl.teachingMappingStatus.put("mapping_percent", mapping_percent); +// log.info("建图状态mapping_return:{},建图进度条mapping_percent:{},teachingMappingStatus:{}", mapping_return, mapping_percent,TeachingServiceImpl.teachingMappingStatus); + // 叉尖避障 + String back_io_status = data_pool.getString("back_io_status"); + VehicleInfoServiceImpl.vehicleInfo.setForkTipObstacles(back_io_status); + // 手自动模式 1手动 0自动 + boolean isManual = "0".equals(data_pool.getString("have_hard_manual")); + VehicleInfoServiceImpl.vehicleInfo.setIsManual(isManual); + VehicleInfoServiceImpl.vehicleInfo.setReady(ready); + // 电量 + VehicleInfoServiceImpl.vehicleInfo.setBatteryPower((int)robotBase.getBatteryInfo().getSoc()); + //x,y,theta + + DecimalFormat xFormat = new DecimalFormat("0.000"); + String x_res = xFormat.format(robotBase.getLocate().getCurrentState().getX()); + double x = Double.parseDouble(x_res); + VehicleInfoServiceImpl.vehicleInfo.setX(x); + DecimalFormat yFormat = new DecimalFormat("0.000"); + String y_res = xFormat.format(robotBase.getLocate().getCurrentState().getY()); + double y = Double.parseDouble(y_res); + VehicleInfoServiceImpl.vehicleInfo.setY(y); + VehicleInfoServiceImpl.vehicleInfo.setTheta(robotBase.getLocate().getCurrentState().getTheta()); +// System.out.println("vehicleInfo2: " + VehicleInfoServiceImpl.vehicleInfo); + break; + + case HEARTBEAT: + Uidata.UiHeartbeat heartbeat = datagram.getHeartbeat(); + System.out.println("Received heartbeat: " + heartbeat); + break; + + case LASER_SCAN: + Robottype.LaserData laser = datagram.getLaserScan(); + // 9顶部激光 + if (laser.getLocation() == 9){ + Set globalData = new HashSet<>(); + Set currentData = new HashSet<>(); + for (Robottype.Point point: laser.getScanList()){ + double cosYaw = cos(VehicleInfoServiceImpl.vehicleInfo.getTheta()); + double sinYaw = sin(VehicleInfoServiceImpl.vehicleInfo.getTheta()); + double x_global = VehicleInfoServiceImpl.vehicleInfo.getX() + cosYaw * point.getX() - sinYaw * point.getY(); + DecimalFormat x_globalDF = new DecimalFormat("#.#"); + String x_result = x_globalDF.format(x_global); + double x_globalNum = Double.parseDouble(x_result); + double y_global = VehicleInfoServiceImpl.vehicleInfo.getY() + sinYaw * point.getX() + cosYaw * point.getY(); + DecimalFormat y_globalDF = new DecimalFormat("#.#"); + String y_result = y_globalDF.format(y_global); + double y_globalNum = Double.parseDouble(y_result); + PointCloudDataDto globalPointCloudDataDto = new PointCloudDataDto(); + globalPointCloudDataDto.setX(x_globalNum); + globalPointCloudDataDto.setY(y_globalNum); + globalData.add(globalPointCloudDataDto); + + double x_current = point.getX(); + DecimalFormat x_currentDF = new DecimalFormat("#.#"); + String x_CurrentResult = x_currentDF.format(x_current); + double x_currentNum = Double.parseDouble(x_CurrentResult); + double y_current = point.getY(); + DecimalFormat y_currentDF = new DecimalFormat("#.#"); + String y_CurrentResult = y_currentDF.format(y_current); + double y_currentNum = Double.parseDouble(y_CurrentResult); + PointCloudDataDto currentPointCloudDataDto = new PointCloudDataDto(); + currentPointCloudDataDto.setX(x_currentNum); + currentPointCloudDataDto.setY(y_currentNum); + currentData.add(currentPointCloudDataDto); + } + VehicleInfoServiceImpl.globalPointCloudData = globalData; + VehicleInfoServiceImpl.currentPointCloudData = currentData; + } +// System.out.println("Received laser_scan: " + list); + break; + } + } catch (Exception e) { + log.error("Error processing protobuf message", e); + } + } + + @Override + public void afterConnectionEstablished(WebSocketSession session) { + log.info("Connected to WebSocket server: {}", session.getUri()); + isConnected = true; + VehicleInfoServiceImpl.vehicleInfo.setVehicleConnected(true); + currentSession = session; + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { + log.info("Connection closed: {}", status); + isConnected = false; + VehicleInfoServiceImpl.vehicleInfo.setVehicleConnected(false); + currentSession = null; + } + + @Override + public void handleTransportError(WebSocketSession session, Throwable exception) { + log.info("传输错误: {}", exception.getMessage()); + isConnected = false; + VehicleInfoServiceImpl.vehicleInfo.setVehicleConnected(false); + currentSession = null; + } + + @Scheduled(fixedDelay = 3000) + public void reconnectTask() { + if (!isConnected) { + try { + log.info("websocket 正在进行重连.."); + // 先停止当前连接(如果正在运行) + if (connectionManager.isRunning()) { + connectionManager.stop(); + } + // 启动新连接 + connectionManager.start(); + }catch (Exception e) { + log.error("websocket重连失败:{}",e.getMessage()); + } + } + } +} diff --git a/src/main/java/org/nl/qrobot/config/thread/WebSocketConfig.java b/src/main/java/org/nl/qrobot/config/thread/WebSocketConfig.java new file mode 100644 index 0000000..c6d2759 --- /dev/null +++ b/src/main/java/org/nl/qrobot/config/thread/WebSocketConfig.java @@ -0,0 +1,19 @@ +package org.nl.qrobot.config.thread; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + +/** + * @author dsh + * 2025/7/3 + */ +@Configuration +public class WebSocketConfig { + + @Bean + public ServerEndpointExporter serverEndpointExporter() { + return new ServerEndpointExporter(); + } + +} diff --git a/src/main/java/org/nl/qrobot/config/thread/WebsocketClientConfig.java b/src/main/java/org/nl/qrobot/config/thread/WebsocketClientConfig.java new file mode 100644 index 0000000..7109705 --- /dev/null +++ b/src/main/java/org/nl/qrobot/config/thread/WebsocketClientConfig.java @@ -0,0 +1,47 @@ +package org.nl.qrobot.config.thread; + +import lombok.extern.slf4j.Slf4j; +import org.nl.qrobot.util.URLConstant; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.client.WebSocketClient; +import org.springframework.web.socket.client.WebSocketConnectionManager; +import org.springframework.web.socket.client.standard.StandardWebSocketClient; + +import javax.websocket.ContainerProvider; +import javax.websocket.WebSocketContainer; + +/** + * @author dsh + * 2025/7/9 + */ +@Slf4j +@Configuration +public class WebsocketClientConfig { + + @Bean + public WebSocketClient webSocketClient() { + // 创建WebSocket容器并配置缓冲区大小 + WebSocketContainer container = ContainerProvider.getWebSocketContainer(); + // 256KB + container.setDefaultMaxBinaryMessageBufferSize(100 * 1024); + container.setDefaultMaxTextMessageBufferSize(100 * 1024); + + return new StandardWebSocketClient(container); + } + + @Bean + public WebSocketConnectionManager webSocketConnectionManager() { + String serverUrl = "ws://"+ URLConstant.VEHICLE_IP_PORT+"/ws/AGVInfo"; + + WebSocketConnectionManager manager = new WebSocketConnectionManager( + this.webSocketClient(), + new ProtobufWebSocketHandler(), + serverUrl + ); + manager.setAutoStartup(false); + return manager; + } + + +} diff --git a/src/main/java/org/nl/qrobot/manage/logging/controller/LogController.java b/src/main/java/org/nl/qrobot/manage/logging/controller/LogController.java new file mode 100644 index 0000000..f9340df --- /dev/null +++ b/src/main/java/org/nl/qrobot/manage/logging/controller/LogController.java @@ -0,0 +1,71 @@ +package org.nl.qrobot.manage.logging.controller; + +import org.nl.qrobot.common.BadRequestException; +import org.nl.qrobot.common.logging.annotation.Log; +import org.nl.qrobot.manage.logging.service.LogService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.InputStreamResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +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.RestController; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * @author dsh + * 2025/11/3 + */ +@RestController +@RequestMapping("/api/logs") +public class LogController { + + @Autowired + private LogService logService; + + /** + * 获取日志文件列表 + */ + @Log("获取日志文件列表") + @GetMapping("/list") + public ResponseEntity getLogFiles() { + try { + return new ResponseEntity<>(logService.getLogFiles(), HttpStatus.OK); + } catch (Exception e) { + throw new BadRequestException("获取日志文件失败:"+e.getMessage()); + } + } + + /** + * 下载日志文件 + */ + @Log("下载日志文件") + @GetMapping("/download/{fileName}") + public ResponseEntity downloadLogFile(@PathVariable String fileName) { + try { + File file = logService.getLogFile(fileName); + + Path filePath = file.toPath(); + Resource resource = new InputStreamResource(Files.newInputStream(filePath)); + + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=\"" + file.getName() + "\"") + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .contentLength(file.length()) + .body(resource); + + } catch (RuntimeException e) { + return ResponseEntity.notFound().build(); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } +} diff --git a/src/main/java/org/nl/qrobot/manage/logging/dto/LogFileDTO.java b/src/main/java/org/nl/qrobot/manage/logging/dto/LogFileDTO.java new file mode 100644 index 0000000..4b5b009 --- /dev/null +++ b/src/main/java/org/nl/qrobot/manage/logging/dto/LogFileDTO.java @@ -0,0 +1,34 @@ +package org.nl.qrobot.manage.logging.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * @author dsh + * 2025/11/3 + */ +@Data +public class LogFileDTO { + private String fileName; + private String filePath; + private Long fileSize; + private LocalDateTime lastModified; + private String readableSize; + + public LogFileDTO(String fileName, String filePath, Long fileSize, LocalDateTime lastModified) { + this.fileName = fileName; + this.filePath = filePath; + this.fileSize = fileSize; + this.lastModified = lastModified; + this.readableSize = formatFileSize(fileSize); + } + + private String formatFileSize(long size) { + if (size <= 0) return "0 B"; + final String[] units = new String[]{"B", "KB", "MB", "GB"}; + int digitGroups = (int) (Math.log10(size) / Math.log10(1024)); + return String.format("%.1f %s", size / Math.pow(1024, digitGroups), units[digitGroups]); + } + +} diff --git a/src/main/java/org/nl/qrobot/manage/logging/service/LogService.java b/src/main/java/org/nl/qrobot/manage/logging/service/LogService.java new file mode 100644 index 0000000..fb6ab14 --- /dev/null +++ b/src/main/java/org/nl/qrobot/manage/logging/service/LogService.java @@ -0,0 +1,81 @@ +package org.nl.qrobot.manage.logging.service; + +import org.nl.qrobot.manage.logging.dto.LogFileDTO; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.List; + +/** + * @author dsh + * 2025/11/3 + */ +@Service +public class LogService { + + @Value("${customize.log-dir}") + private String logDir; + + /** + * 获取日志文件列表 + */ + public List getLogFiles() { + List logFiles = new ArrayList<>(); + File directory = new File(logDir); + + if (!directory.exists() || !directory.isDirectory()) { + return logFiles; + } + + File[] files = directory.listFiles((dir, name) -> + name.endsWith(".log") || name.endsWith(".txt")); + + if (files != null) { + for (File file : files) { + if (file.isFile()) { + LogFileDTO logFile = new LogFileDTO( + file.getName(), + file.getAbsolutePath(), + file.length(), + LocalDateTime.ofInstant( + Instant.ofEpochMilli(file.lastModified()), + ZoneId.systemDefault() + ) + ); + logFiles.add(logFile); + } + } + } + + // 按修改时间倒序排列 + logFiles.sort((a, b) -> b.getLastModified().compareTo(a.getLastModified())); + return logFiles; + } + + /** + * 获取日志文件 + */ + public File getLogFile(String fileName) { + // 防止路径遍历攻击 + if (fileName.contains("..") || fileName.contains("/") || fileName.contains("\\")) { + throw new IllegalArgumentException("Invalid file name"); + } + + File file = new File(logDir, fileName); + if (!file.exists() || !file.isFile()) { + throw new RuntimeException("File not found: " + fileName); + } + + // 确保文件在日志目录内 + if (!file.getAbsolutePath().startsWith(new File(logDir).getAbsolutePath())) { + throw new SecurityException("Access denied"); + } + + return file; + } +} diff --git a/src/main/java/org/nl/qrobot/manage/secutiry/controller/AuthorizationController.java b/src/main/java/org/nl/qrobot/manage/secutiry/controller/AuthorizationController.java new file mode 100644 index 0000000..d7aa004 --- /dev/null +++ b/src/main/java/org/nl/qrobot/manage/secutiry/controller/AuthorizationController.java @@ -0,0 +1,37 @@ +package org.nl.qrobot.manage.secutiry.controller; + +import cn.hutool.core.util.ObjectUtil; +import org.nl.qrobot.common.BadRequestException; +import org.nl.qrobot.config.language.LangProcess; +import org.nl.qrobot.manage.secutiry.dto.AuthUserDto; +import org.nl.qrobot.manage.secutiry.service.AuthorizationService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + * @author dsh + * 2025/10/22 + */ +@RestController +@RequestMapping("/auth") +public class AuthorizationController { + + @Resource + private AuthorizationService authorizationService; + + @PostMapping(value = "/login") + public ResponseEntity login(@RequestBody AuthUserDto user) { + + if (ObjectUtil.isEmpty(user)) { + throw new BadRequestException(LangProcess.msg("param_is_null")); + } + return new ResponseEntity<>(authorizationService.login(user), HttpStatus.OK); + } + +} diff --git a/src/main/java/org/nl/qrobot/manage/secutiry/dto/AuthUserDto.java b/src/main/java/org/nl/qrobot/manage/secutiry/dto/AuthUserDto.java new file mode 100644 index 0000000..6a9c368 --- /dev/null +++ b/src/main/java/org/nl/qrobot/manage/secutiry/dto/AuthUserDto.java @@ -0,0 +1,17 @@ +package org.nl.qrobot.manage.secutiry.dto; + +import lombok.Data; + +/** + * @author dsh + * 2025/10/23 + */ +@Data +public class AuthUserDto { + + private String userName; + + private String password; + + +} diff --git a/src/main/java/org/nl/qrobot/manage/secutiry/service/AuthorizationService.java b/src/main/java/org/nl/qrobot/manage/secutiry/service/AuthorizationService.java new file mode 100644 index 0000000..c71dce8 --- /dev/null +++ b/src/main/java/org/nl/qrobot/manage/secutiry/service/AuthorizationService.java @@ -0,0 +1,48 @@ +package org.nl.qrobot.manage.secutiry.service; + +import cn.dev33.satoken.secure.SaSecureUtil; +import cn.dev33.satoken.stp.SaLoginModel; +import cn.dev33.satoken.stp.StpUtil; +import cn.dev33.satoken.util.SaResult; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.SneakyThrows; +import org.nl.qrobot.common.BadRequestException; +import org.nl.qrobot.config.language.LangProcess; +import org.nl.qrobot.manage.secutiry.dto.AuthUserDto; +import org.nl.qrobot.manage.user.dao.User; +import org.nl.qrobot.manage.user.service.UserService; +import org.nl.qrobot.util.RsaUtils; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * @author dsh + * 2025/10/23 + */ +@Service +public class AuthorizationService{ + + @Resource + private UserService userService; + + @SneakyThrows + public Map login(AuthUserDto user){ + // 密码解密 - 前端的加密规则: encrypt(根据实际更改) + String password = RsaUtils.decryptByPrivateKey(RsaUtils.privateKey, user.getPassword()); + user.setPassword(password); + User userData = userService.getOne(new LambdaQueryWrapper().eq(User::getUserName, user.getUserName())); + if (ObjectUtil.isEmpty(userData) || !userData.getPassword().equals(SaSecureUtil.md5BySalt(user.getPassword(),"salt"))) { + throw new BadRequestException(LangProcess.msg("password_error")); + } + StpUtil.login(userData.getUser_id(),new SaLoginModel() + .setDevice("PC")); + Map data = new LinkedHashMap<>(); + data.put("user",userData); + data.put("token",StpUtil.getTokenValue()); + return SaResult.data(data); + } +} diff --git a/src/main/java/org/nl/qrobot/manage/user/dao/User.java b/src/main/java/org/nl/qrobot/manage/user/dao/User.java new file mode 100644 index 0000000..bc0b8c6 --- /dev/null +++ b/src/main/java/org/nl/qrobot/manage/user/dao/User.java @@ -0,0 +1,22 @@ +package org.nl.qrobot.manage.user.dao; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * @author dsh + * 2025/10/23 + */ +@Data +@TableName("user") +public class User { + + @TableId(value = "user_id") + private String user_id; + + private String userName; + + private String password; + +} diff --git a/src/main/java/org/nl/qrobot/manage/user/service/UserService.java b/src/main/java/org/nl/qrobot/manage/user/service/UserService.java new file mode 100644 index 0000000..f0c9aad --- /dev/null +++ b/src/main/java/org/nl/qrobot/manage/user/service/UserService.java @@ -0,0 +1,12 @@ +package org.nl.qrobot.manage.user.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import org.nl.qrobot.manage.user.dao.User; + +/** + * @author dsh + * 2025/10/23 + */ +public interface UserService extends IService { + +} diff --git a/src/main/java/org/nl/qrobot/manage/user/service/impl/UserServiceImpl.java b/src/main/java/org/nl/qrobot/manage/user/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..b37370d --- /dev/null +++ b/src/main/java/org/nl/qrobot/manage/user/service/impl/UserServiceImpl.java @@ -0,0 +1,16 @@ +package org.nl.qrobot.manage.user.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.nl.qrobot.manage.user.dao.User; +import org.nl.qrobot.manage.user.service.UserService; +import org.nl.qrobot.manage.user.service.mapper.UserMapper; +import org.springframework.stereotype.Service; + +/** + * @author dsh + * 2025/10/23 + */ +@Service +public class UserServiceImpl extends ServiceImpl implements UserService { + +} diff --git a/src/main/java/org/nl/qrobot/manage/user/service/mapper/UserMapper.java b/src/main/java/org/nl/qrobot/manage/user/service/mapper/UserMapper.java new file mode 100644 index 0000000..b052f40 --- /dev/null +++ b/src/main/java/org/nl/qrobot/manage/user/service/mapper/UserMapper.java @@ -0,0 +1,14 @@ +package org.nl.qrobot.manage.user.service.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.nl.qrobot.manage.user.dao.User; + +/** + * @author dsh + * 2025/10/23 + */ +@Mapper +public interface UserMapper extends BaseMapper { + +} diff --git a/src/main/java/org/nl/qrobot/system/controller/ParamController.java b/src/main/java/org/nl/qrobot/system/controller/ParamController.java new file mode 100644 index 0000000..74b3a2c --- /dev/null +++ b/src/main/java/org/nl/qrobot/system/controller/ParamController.java @@ -0,0 +1,29 @@ +package org.nl.qrobot.system.controller; + +import org.nl.qrobot.system.dao.Param; +import org.nl.qrobot.system.service.ParamService; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +/** + * @author dsh + * 2025/8/7 + */ +@RestController +@RequestMapping("/param") +public class ParamController { + + @Resource + private ParamService paramService; + + @GetMapping("/queryParamObjectByCode") + public Param queryParamObjectByCode(@RequestParam("code") String code){ + return paramService.queryParamObjectByCode(code); + } + + @PostMapping("/updateByCode") + public Boolean updateByCode(@RequestBody Param param){ + return paramService.updateByCode(param); + } +} diff --git a/src/main/java/org/nl/qrobot/system/dao/Param.java b/src/main/java/org/nl/qrobot/system/dao/Param.java new file mode 100644 index 0000000..25acd49 --- /dev/null +++ b/src/main/java/org/nl/qrobot/system/dao/Param.java @@ -0,0 +1,93 @@ +package org.nl.qrobot.system.dao; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; + +/** + * @author dsh + * 2025/8/4 + */ +@Data +@TableName("sys_param") +public class Param implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 标识 + */ + @TableId + private String id; + + /** + * 编码 + */ + private String code; + + /** + * 名称 + */ + private String name; + /** + * 中文名称 + */ + private String zh_name; + /** + * us名称 + */ + private String en_name; + + /** + * 值 + */ + private String value; + + /** + * 备注 + */ + private String remark; + + /** + * 是否启用 + */ + private Boolean is_active; + + /** + * 是否删除 + */ + private Boolean is_delete; + + /** + * 创建者ID + */ + private String create_id; + + /** + * 创建者 + */ + private String create_name; + + /** + * 创建时间 + */ + private String create_time; + + /** + * 修改者ID + */ + private String update_id; + + /** + * 修改者 + */ + private String update_name; + + /** + * 修改时间 + */ + private String update_time; + +} diff --git a/src/main/java/org/nl/qrobot/system/dao/mapper/ParamMapper.java b/src/main/java/org/nl/qrobot/system/dao/mapper/ParamMapper.java new file mode 100644 index 0000000..9189536 --- /dev/null +++ b/src/main/java/org/nl/qrobot/system/dao/mapper/ParamMapper.java @@ -0,0 +1,13 @@ +package org.nl.qrobot.system.dao.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.nl.qrobot.system.dao.Param; + +/** + * @author dsh + * 2025/8/7 + */ +@Mapper +public interface ParamMapper extends BaseMapper { +} diff --git a/src/main/java/org/nl/qrobot/system/enums/ParamCodeConstant.java b/src/main/java/org/nl/qrobot/system/enums/ParamCodeConstant.java new file mode 100644 index 0000000..6ee5da6 --- /dev/null +++ b/src/main/java/org/nl/qrobot/system/enums/ParamCodeConstant.java @@ -0,0 +1,12 @@ +package org.nl.qrobot.system.enums; + +/** + * @author dsh + * 2025/8/7 + */ +public class ParamCodeConstant { + + public static final String RCS_URL = "rcs_url"; + + public static final String VEHICLE_URL = "vehicle_url"; +} diff --git a/src/main/java/org/nl/qrobot/system/service/ParamService.java b/src/main/java/org/nl/qrobot/system/service/ParamService.java new file mode 100644 index 0000000..960447f --- /dev/null +++ b/src/main/java/org/nl/qrobot/system/service/ParamService.java @@ -0,0 +1,15 @@ +package org.nl.qrobot.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import org.nl.qrobot.system.dao.Param; + +/** + * @author dsh + * 2025/8/7 + */ +public interface ParamService extends IService { + + Param queryParamObjectByCode(String code); + + Boolean updateByCode(Param param); +} diff --git a/src/main/java/org/nl/qrobot/system/service/impl/ParamServiceImpl.java b/src/main/java/org/nl/qrobot/system/service/impl/ParamServiceImpl.java new file mode 100644 index 0000000..7ddc443 --- /dev/null +++ b/src/main/java/org/nl/qrobot/system/service/impl/ParamServiceImpl.java @@ -0,0 +1,41 @@ +package org.nl.qrobot.system.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.nl.qrobot.common.BadRequestException; +import org.nl.qrobot.system.dao.Param; +import org.nl.qrobot.system.dao.mapper.ParamMapper; +import org.nl.qrobot.system.service.ParamService; +import org.springframework.stereotype.Service; + +/** + * @author dsh + * 2025/8/7 + */ +@Service +public class ParamServiceImpl extends ServiceImpl implements ParamService { + + @Override + public Param queryParamObjectByCode(String code) { + if (StrUtil.isBlank(code)) { + throw new BadRequestException("code is Null"); + } + return this.getOne(new LambdaQueryWrapper<>(Param.class).eq(Param::getCode, code)); + } + + @Override + public Boolean updateByCode(Param param) { + if(ObjectUtil.isNull(param)){ + throw new BadRequestException("参数不能为空"); + } + boolean results = this.update(new LambdaUpdateWrapper<>(Param.class) + .set(StrUtil.isNotBlank(param.getName()),Param::getName,param.getName()) + .eq(Param::getCode,param.getCode())); + return results; + } + + +} diff --git a/src/main/java/org/nl/qrobot/util/FileConstant.java b/src/main/java/org/nl/qrobot/util/FileConstant.java new file mode 100644 index 0000000..0edc1a7 --- /dev/null +++ b/src/main/java/org/nl/qrobot/util/FileConstant.java @@ -0,0 +1,27 @@ +package org.nl.qrobot.util; + +import org.nl.qrobot.config.file.FileProperties; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * @author dsh + * 2025/11/3 + **/ +@Component +public class FileConstant { + + + public static String ERROR_IMAGE_PATH = ""; + + @Resource + private FileProperties properties; + + @Value("${customize.errorImage-dir}") + public void setErrorImagePath(String errorImagePath) { + FileConstant.ERROR_IMAGE_PATH = properties.getPath().getPath()+errorImagePath; + } + +} diff --git a/src/main/java/org/nl/qrobot/util/HTTPUtil.java b/src/main/java/org/nl/qrobot/util/HTTPUtil.java new file mode 100644 index 0000000..ee42c41 --- /dev/null +++ b/src/main/java/org/nl/qrobot/util/HTTPUtil.java @@ -0,0 +1,43 @@ +package org.nl.qrobot.util; + +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.i18n.LocaleContextHolder; + +/** + * @author dsh + * 2025/7/2 + */ +@Slf4j +public class HTTPUtil { + + public static HttpResponse post(String url, String method, JSONObject params)throws Exception{ + String sendUrl = url + method; + String lang = LocaleContextHolder.getLocale().getLanguage(); + return HttpRequest.post(sendUrl) + .setConnectionTimeout(10000) + .setReadTimeout(10000) + .header("token", "admin123") + .header("name", "lx-script") + .header("Content-Type", "application/json") + .header("Lang", "en".equals(lang)?"EN":"ZH") + .body(String.valueOf(params)) + .execute(); + } + + public static HttpResponse get(String url, String method, JSONObject params,String lang)throws Exception{ + String sendUrl = url + method; + return HttpRequest.get(sendUrl) + .setConnectionTimeout(10000) + .setReadTimeout(10000) + .header("token", "admin123") + .header("name", "lx-script") + .header("Content-Type", "application/json") + .header("Lang", lang) + .form(params) + .execute(); + + } +} diff --git a/src/main/java/org/nl/qrobot/util/IdUtil.java b/src/main/java/org/nl/qrobot/util/IdUtil.java new file mode 100644 index 0000000..c0d56dd --- /dev/null +++ b/src/main/java/org/nl/qrobot/util/IdUtil.java @@ -0,0 +1,11 @@ +package org.nl.qrobot.util; + +public class IdUtil { + public static Long getLongId() { + return cn.hutool.core.util.IdUtil.getSnowflake(1, 1).nextId(); + } + + public static String getStringId() { + return String.valueOf(IdUtil.getLongId()); + } +} diff --git a/src/main/java/org/nl/qrobot/util/MD5Util.java b/src/main/java/org/nl/qrobot/util/MD5Util.java new file mode 100644 index 0000000..7add385 --- /dev/null +++ b/src/main/java/org/nl/qrobot/util/MD5Util.java @@ -0,0 +1,20 @@ +package org.nl.qrobot.util; + +import cn.dev33.satoken.secure.SaSecureUtil; + +/** + * @author dsh + * 2025/10/23 + */ +public class MD5Util { + + public static void main(final String[] args) { + String str = "noblelift"; + + String salt = "salt"; + + String value = SaSecureUtil.md5BySalt(str,salt); + + System.out.println("加密后的值:"+value); + } +} diff --git a/src/main/java/org/nl/qrobot/util/RsaUtils.java b/src/main/java/org/nl/qrobot/util/RsaUtils.java new file mode 100644 index 0000000..9b4cfcd --- /dev/null +++ b/src/main/java/org/nl/qrobot/util/RsaUtils.java @@ -0,0 +1,90 @@ +package org.nl.qrobot.util; + +import org.apache.commons.codec.binary.Base64; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.crypto.Cipher; +import java.security.*; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.PKCS8EncodedKeySpec; + +/** + * @author liejiu + */ +@Component +public class RsaUtils { + + public static String privateKey; + + @Value("${rsa.private_key}") + public void setPrivateKey(String privateKey) { + RsaUtils.privateKey = privateKey; + } + + public static void main(String[] args) throws Exception { + System.out.println("\n"); + RsaKeyPair keyPair = generateKeyPair(); + System.out.println("公钥:" + keyPair.getPublicKey()); + System.out.println("私钥:" + keyPair.getPrivateKey()); + } + + /** + * 构建RSA密钥对 + * + * @return / + * @throws NoSuchAlgorithmException / + */ + public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(1024); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic(); + RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate(); + String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded()); + String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded()); + return new RsaKeyPair(publicKeyString, privateKeyString); + } + + /** + * RSA密钥对对象 + */ + public static class RsaKeyPair { + + private final String publicKey; + private final String privateKey; + + public RsaKeyPair(String publicKey, String privateKey) { + this.publicKey = publicKey; + this.privateKey = privateKey; + } + + public String getPublicKey() { + return publicKey; + } + + public String getPrivateKey() { + return privateKey; + } + + } + + /** + * 私钥解密 + * + * @param privateKeyText 私钥 + * @param text 待解密的文本 + * @return / + * @throws Exception / + */ + public static String decryptByPrivateKey(String privateKeyText, String text) throws Exception { + PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + byte[] result = cipher.doFinal(Base64.decodeBase64(text)); + return new String(result); + } +} diff --git a/src/main/java/org/nl/qrobot/util/ThrowableUtil.java b/src/main/java/org/nl/qrobot/util/ThrowableUtil.java new file mode 100644 index 0000000..b2b8825 --- /dev/null +++ b/src/main/java/org/nl/qrobot/util/ThrowableUtil.java @@ -0,0 +1,22 @@ +package org.nl.qrobot.util; + +import java.io.PrintWriter; +import java.io.StringWriter; + + +/** + * @author liejiu + */ +public class ThrowableUtil { + + /** + * 获取堆栈信息 + */ + public static String getStackTrace(Throwable throwable){ + StringWriter sw = new StringWriter(); + try (PrintWriter pw = new PrintWriter(sw)) { + throwable.printStackTrace(pw); + return sw.toString(); + } + } +} diff --git a/src/main/java/org/nl/qrobot/util/URLConstant.java b/src/main/java/org/nl/qrobot/util/URLConstant.java new file mode 100644 index 0000000..f9b9e8e --- /dev/null +++ b/src/main/java/org/nl/qrobot/util/URLConstant.java @@ -0,0 +1,38 @@ +package org.nl.qrobot.util; + +/** + *

+ * 地址常量 + *

+ * + * @author Liuxy + * @since 2025-07-09 + */ +public class URLConstant { + + /** + * RCS调度IP及端口 + */ + public static String RCS_IP_PORT = "192.168.100.201:8081"; + + /** + * 车体后台IP及端口 + */ + public static String VEHICLE_IP_PORT = "192.168.100.201:9998"; + + /** + * 下发任务URL + */ + public final static String SEND_TASK_URL = "/task/add"; + + /** + * 根据任务ID取消任务URL + 任务ID + */ + public final static String CANCEL_TASK_URL = "/task/cancel/"; + + /** + * 根据车号ID取消任务URL + */ + public final static String VEHICLE_ID_CANCEL_TASK_URL = "/task/batchCancel"; + +} diff --git a/src/main/proto/robottype.proto b/src/main/proto/robottype.proto new file mode 100644 index 0000000..1afed7b --- /dev/null +++ b/src/main/proto/robottype.proto @@ -0,0 +1,215 @@ +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +package comm_protocol; + +message RobotShape { + double front = 1; + double back = 2; + double left = 3; + double right = 4; + double radius = 5; +} + +message Point { + double x = 1; + double y = 2; + double intensity = 3; +} +message PointXYZ { + sint32 x = 1; + sint32 y = 2; + sint32 z = 3; +} +message OrientedPoint { + double x = 1; + double y = 2; + double theta = 3; +} + +message RobotSpeed { + double vx = 1; + double vy = 2; + double w = 3; + int64 timeStamp = 4; +} + +message RobotState { + OrientedPoint pose = 1; + RobotSpeed speed = 2; + int64 timeStamp = 3; +} + +message RobotPath { + int64 timeStamp = 1; + repeated RobotState state = 2; +} + +message LaserData { + sint32 location = 1; + int64 timeStamp = 2; + repeated Point scan = 3; +} + +message ImageData { + sint32 location = 1; + sint64 timeStamp = 2; + sint32 width = 3; + sint32 height = 4; + sint32 chanel = 5; + sint32 package_index=6; + sint32 package_length=7; + sint32 imgdata_size=8; + sint32 curr_size=9; + bytes data = 10; +} +message PcloudData { + sint32 location = 1; + sint64 timeStamp = 2; + repeated PointXYZ point = 3; +} + +message ControlLimit { + double max_vel = 1; + double max_w = 2; + int32 turn_dir_limit = 3; + int32 shelf_dir_limit = 4; +} +message stReachThreshold { + double reach_angle = 1; + double reach_dist = 2; +} + +message PathProperty { + int32 move_mode = 1; + bool avoid_obstacle = 2; + double path_width = 3; + double safe_dist = 4; + double min_turn_radius = 5; + int32 route_mode = 6; + string path_extends = 7; +} + + +message RobotTask { + + message WayPoint { + // 路径信息,路径a-b,路径信息放在b点中 + message PathInfo { + enum Sensor { + NONE = 0; + DEPTH_CAMERA = 1; //深度相机 + ULTRASONIC = 2; //超声 + } + double distance2point = 1; // 到点距离阈值(单位m) + double angle2point = 2; // 到点角度阈值(单位rad) + double safe_distance = 3; // 安全距离 + bool avoid_obstacle = 4; // 是否避障 + double max_velocity = 5; // 最大速度(单位m/s) + double path_width = 6; // 路径宽度(单位m) + double min_turn_radius = 7; // 最小转弯半径 + int32 sensor = 8; // 开启laser之外的其他传感器,可以是Sensor的多个组合 + int32 move_mode = 9; + int32 route_mode = 10; + string path_extends = 11; + } + string point_name = 1; // 点名 + RobotState coordinate = 2; // 位姿 + PathInfo path_info = 3; // 路径信息 + // 所有被其他车锁着点中距离该点最近的点,这个信息告诉本体可以让本体进行灵活多车避障 + RobotState min_dist_point = 4; + bool is_lock = 6; + stReachThreshold reach_theta = 7; + string point_extends = 8; + } + + int64 command = 1; + int64 sub_command = 2; + string params = 3; + repeated WayPoint way_point = 4; + RobotState init_pose = 5; + RobotState target_pose = 6; + bool is_final = 7; + string robot_id = 8; + string robot_task_id = 9; + int64 timeStamp = 10; + bool need_response = 11; // 是否需要回复 + // 货架信息,第一位数值代表货架类型,从1开始;第二位代表车与货架长边的关系,0-平行,1-垂直 + // 位数之间采用空格隔开;第一位是0的话代表不带货架; + string shelf_info = 12; + RobotPath nav_robot_path = 13; +} + +message RobotTaskResult { + string task_id = 1; + string robot_id = 2; + int64 command = 3; + int32 result = 4; // 0-success +} + +message RobotPoseWithCov { + double x = 1; + double y = 2; + double theta = 3; + double noisy_x = 4; + double noisy_y = 5; + double noisy_theta = 6; +} + +message Relocate { + RobotPoseWithCov reloc_pose = 1; + string robot_id = 2; +} + +message SwitchMap { + string map_id = 1; + string robot_id = 2; +} + +message RobotBase { + message BatteryInfo { + double soc = 1; // 电池剩余电量百分比 + int32 status = 2; // 电池异常 + uint64 update_time = 3; + } + message DeviceInfo { + bool stop_button = 1; // 急停按钮是否拍下 + bool collision = 2; // 是否发生碰撞 + bool shut_down = 3; // 软关机 + bool inactive = 4; // 底层硬件触发手动模式 + uint64 update_time = 5; + } + message MoveInfo { + string heading_spot = 1; // 下一个目标点 + // double task_complete_percent = 2; // 导航任务完成百分比 + // RobotSpeed velocity = 3; // 小车速度 + uint64 update_time = 4; + } + message Locate { + RobotPoseWithCov current_state = 1; // 小车当前位姿 + int32 locate = 2; // 定位结果 + uint64 update_time = 3; + } + BatteryInfo battery_info = 1; // 电池信息 + DeviceInfo device_info = 2; + MoveInfo move_info = 3; + Locate locate = 4; + string robot_id = 5; + // 高32位代表设备类型,低32位数值代表异常值 + repeated uint64 exception_code = 6; + int32 payloads = 7; // 物料信息,不同应用车型有不同含义 + int32 lift_state = 8; // 顶升状态 + bool robot_inactive = 9; // 是否手动模式,软件触发 + int32 digital_status = 10; // 数字输出端口状态,不同应用车型有不同含义 + string data_pool = 11; + map runnig_fsm = 12; +} + +message RCSInfo { + int32 action = 1; + string action_params = 2; + string fsm_state = 3; + string rcs_task_id = 4; + int32 rcs_task_state = 5; +} diff --git a/src/main/proto/uidata.proto b/src/main/proto/uidata.proto new file mode 100644 index 0000000..42d289f --- /dev/null +++ b/src/main/proto/uidata.proto @@ -0,0 +1,47 @@ +syntax = "proto3"; + +import "robottype.proto"; + +option optimize_for = LITE_RUNTIME; + +package comm_protocol; + +message UiOnline { + string uuid = 1; + int32 comm_type = 2; + int32 codec_pattern = 3; + string host = 4; + int32 port = 5; +} + +message UiHeartbeat { + string uuid = 1; +} + +message KVState { + string key = 1; + string value = 2; +} + +message DataPool { + map kv_state = 1; +} + +message UiDatagram { + oneof Item{ + UiOnline online_info = 1; // ui -> robot + UiHeartbeat heartbeat = 2; // ui -> robot + LaserData laser_scan = 3; + ImageData image = 4; + RobotSpeed control_speed = 5; + RobotState current_state = 6; + RobotPath current_path = 7; + RobotBase robot_base = 8; + RobotTask robot_task = 9; // ui -> robot + Relocate relocate = 10; // ui -> robot + RobotTaskResult robot_task_result = 11; + RCSInfo rcs_info = 12; + DataPool data_pool = 13; // ui -> robot + PcloudData pcloud = 14; + } +} diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt new file mode 100644 index 0000000..cf45865 --- /dev/null +++ b/src/main/resources/banner.txt @@ -0,0 +1,8 @@ + _ _ ___________ _ _____ _ ___________ _____ +| \ | | _ | ___ \ | | ___| | | ___| ___|_ _| +| \| | | | | |_/ / | | |__ | | | |__ | |_ | | +| . ` | | | | ___ \ | | __|| | | __|| _| | | +| |\ \ \_/ / |_/ / |____| |___| |____| |___| | | | +\_| \_/\___/\____/\_____/\____/\_____/\____/\_| \_/ + + :: Spring Boot :: (v2.6.13.RELEASE) \ No newline at end of file diff --git a/src/main/resources/config/application-dev.yml b/src/main/resources/config/application-dev.yml new file mode 100644 index 0000000..f486f02 --- /dev/null +++ b/src/main/resources/config/application-dev.yml @@ -0,0 +1,86 @@ +server: + # 端口 + port: 8011 + tomcat: + max-swallow-size: 100MB +spring: + datasource: + druid: + db-type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/nl_apt15e?serverTimezone=GMT%2B8&characterEncoding=utf-8&userSSL=false + username: root + password: 123456 + # 初始连接数 + initial-size: 5 + # 最小连接数 + min-idle: 15 + # 最大连接数 + max-active: 30 + # 超时时间(以秒数为单位) + remove-abandoned-timeout: 180 + # 获取连接超时时间 + max-wait: 3000 + # 连接有效性检测时间 + time-between-eviction-runs-millis: 60000 + # 连接在池中最小生存的时间 + min-evictable-idle-time-millis: 300000 + # 连接在池中最大生存的时间 + max-evictable-idle-time-millis: 900000 + # 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除 + test-while-idle: true + # 指明是否在从池中取出连接前进行检验,如果检验失败, 则从池中去除连接并尝试取出另一个 + test-on-borrow: true + # 是否在归还到池中前进行检验 + test-on-return: false + # 检测连接是否有效 + validation-query: select 1 + # 配置监控统计 + webStatFilter: + enabled: true + stat-view-servlet: + enabled: true + url-pattern: /druid/* + reset-enable: false + filter: + stat: + enabled: true + # 记录慢SQL + log-slow-sql: true + slow-sql-millis: 1000 + merge-sql: true + wall: + config: + multi-statement-allow: true + +# 文件存储路径 +file: + mac: + path: ~/file/ + avatar: ~/avatar/ + linux: + path: /home/eladmin/file/ + avatar: /home/eladmin/avatar/ + windows: + path: C:\eladmin\file\ + avatar: C:\eladmin\avatar\ + # 文件大小 /M + maxSize: 100 + avatarMaxSize: 5 + +############## Sa-Token 配置 (文档: https://sa-token.cc) ############## +sa-token: + # token名称 (同时也是cookie名称) + token-name: Authorization + # token有效期,单位s 默认30天, -1代表永不过期 + timeout: 2592000 + # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒 + activity-timeout: -1 + # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) + is-concurrent: true + # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) + is-share: false + # token风格 + token-style: random-128 + # 是否输出操作日志 + is-log: true \ No newline at end of file diff --git a/src/main/resources/config/application-prod.yml b/src/main/resources/config/application-prod.yml new file mode 100644 index 0000000..c83a39e --- /dev/null +++ b/src/main/resources/config/application-prod.yml @@ -0,0 +1,86 @@ +server: + # 端口 + port: 8011 + tomcat: + max-swallow-size: 100MB +spring: + datasource: + 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 + # 最小连接数 + min-idle: 15 + # 最大连接数 + max-active: 30 + # 超时时间(以秒数为单位) + remove-abandoned-timeout: 180 + # 获取连接超时时间 + max-wait: 3000 + # 连接有效性检测时间 + time-between-eviction-runs-millis: 60000 + # 连接在池中最小生存的时间 + min-evictable-idle-time-millis: 300000 + # 连接在池中最大生存的时间 + max-evictable-idle-time-millis: 900000 + # 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除 + test-while-idle: true + # 指明是否在从池中取出连接前进行检验,如果检验失败, 则从池中去除连接并尝试取出另一个 + test-on-borrow: true + # 是否在归还到池中前进行检验 + test-on-return: false + # 检测连接是否有效 + validation-query: select 1 + # 配置监控统计 + webStatFilter: + enabled: true + stat-view-servlet: + enabled: true + url-pattern: /druid/* + reset-enable: false + filter: + stat: + enabled: true + # 记录慢SQL + log-slow-sql: true + slow-sql-millis: 1000 + merge-sql: true + wall: + config: + multi-statement-allow: true + +# 文件存储路径 +file: + mac: + path: ~/file/ + avatar: ~/avatar/ + linux: + path: /home/fr1511b/nlapt15e/file/ + avatar: /home/fr1511b/nlapt15e/avatar/ + windows: + path: C:\eladmin\file\ + avatar: C:\eladmin\avatar\ + # 文件大小 /M + maxSize: 100 + avatarMaxSize: 5 + +############## Sa-Token 配置 (文档: https://sa-token.cc) ############## +sa-token: + # token名称 (同时也是cookie名称) + token-name: Authorization + # token有效期,单位s 默认30天, -1代表永不过期 + timeout: 2592000 + # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒 + activity-timeout: -1 + # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) + is-concurrent: true + # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) + is-share: false + # token风格 + token-style: random-128 + # 是否输出操作日志 + is-log: true \ No newline at end of file diff --git a/src/main/resources/config/application.yml b/src/main/resources/config/application.yml new file mode 100644 index 0000000..d7c3ce3 --- /dev/null +++ b/src/main/resources/config/application.yml @@ -0,0 +1,59 @@ +spring: + servlet: + multipart: + max-file-size: 100MB + max-request-size: 100MB + main: + allow-bean-definition-overriding: true + messages: + basename: language/task/task,language/error/error,language/buss/buss + encoding: UTF-8 + profiles: + active: dev + jackson: + time-zone: GMT+8 + +mybatis-plus: + configuration: + map-underscore-to-camel-case: false + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + mapper-locations: classpath*:org/nl/qrobot/**/mapper/*.xml + global-config: + banner: false + +logging: + file: + path: logs + config: classpath:logback-spring.xml + +customize: + log-dir: ${logging.file.path}/root + errorImage-dir: ErrorImage + +#密码加密传输,前端公钥加密,后端私钥解密 +rsa: + private_key: MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMjhH5OmC7osPfdelXwkX1uTW1vuj8miZIU546Y6dy7TI2RF04rcW2eneBqrvF/8Ni1b+A+bqAJfMi01TDBmdyp+7vyZzUPRXv02HpI/ZM9dkQhv2m4VnTNnWOM0mY/7fJtDfLXhfDNmBMz7h57oDUFV0ESQtp5i4K7hlIVeezijAgMBAAECgYBVuPQfruksPnsHGB7UhjUHQD/pYEmN8zXQQJ7sLeD0Y3ej78RRaq268xVm1Eij8V4xRyD5kCRHNtaTwj3MHb3V68QC3amBR392yay4+S5ZOEPOjZ64hMSZWPezHbemLMjtUn1NR1k1aGfaiGPEk3npiXJHf9ZXU2GGglLJ50eP+QJBAPD3TTRtOVuTIPQS+m0PEeW6ALZDp3EONoqOMePuGLqj38QjwV+jGdxzA+Hhsf8oQo+QG2S3YPph09TFZc3dvj0CQQDVaY9TGeDyxf2DDL/oBFecIkixkV/AWRJb9CrDCZ8H9K1xWuEmhIW1pFJ+gmjbZccZQe/n1M+tRKq9DPaXsiBfAkANgE27GkOUdfHquwV9BtMh5AIWNEQ1eW5k5QK2mqiYDIaFHtu+2Ayi5W7aQSMQANl54cEnK38riD+uNEE3/6yhAkAN3ZfkTFAjNd3sv81QI8gVatzSPKG9+4uH0etdVKiyeaEzNjZerEmLrat2cL6jUo+HApO1ukvr9AQr2EXFQVt9AkEAoQcTx50AK6osp15kAoOQH5NV7TF2qoDbkNfp4xJReLlNpn9oPh2CNQOuVfna5gDQmkaEaJwwVuBvWN5RPrp2HA== + +# sa-token白名单配置 +security: + # 排除路径 + excludes: + # 认证 + - /auth/login + # apt屏幕操作 +# - /anomalyInfo/** + - /api/rcs/** + - /mapInfo/** + - /api/operate/** + - /teaching/** + - /vehicle/** + - /vehicles/** + - /webSocket/** + - /file/** + - /routeInfo/** + - /station/** + # 静态资源 + - /*.html + - /**/*.html + - /**/*.css + - /**/*.js \ No newline at end of file diff --git a/src/main/resources/language/buss/buss.properties b/src/main/resources/language/buss/buss.properties new file mode 100644 index 0000000..8f76588 --- /dev/null +++ b/src/main/resources/language/buss/buss.properties @@ -0,0 +1,7 @@ +starting_point = 起点 +end_point = 终点 +successful = 操作成功! +failed = 操作失败! +latest = 已经是最新的! +param_is_null = 参数为空! +password_error = 账号或密码错误 \ No newline at end of file diff --git a/src/main/resources/language/buss/buss_en_US.properties b/src/main/resources/language/buss/buss_en_US.properties new file mode 100644 index 0000000..d817150 --- /dev/null +++ b/src/main/resources/language/buss/buss_en_US.properties @@ -0,0 +1,7 @@ +starting_point = Starting Point +end_point = End Point +successful = successful! +failed = Failed! +latest = Already up to date! +param_is_null = The parameter is empty! +password_error = Wrong account or password \ No newline at end of file diff --git a/src/main/resources/language/buss/buss_zh_CN.properties b/src/main/resources/language/buss/buss_zh_CN.properties new file mode 100644 index 0000000..8f76588 --- /dev/null +++ b/src/main/resources/language/buss/buss_zh_CN.properties @@ -0,0 +1,7 @@ +starting_point = 起点 +end_point = 终点 +successful = 操作成功! +failed = 操作失败! +latest = 已经是最新的! +param_is_null = 参数为空! +password_error = 账号或密码错误 \ No newline at end of file diff --git a/src/main/resources/language/error/error.properties b/src/main/resources/language/error/error.properties new file mode 100644 index 0000000..ac7746d --- /dev/null +++ b/src/main/resources/language/error/error.properties @@ -0,0 +1,29 @@ +error_request_vehicle = 访问车体接口异常: +error_request_rcs = 访问RCS接口异常: +error_starting_mapping = 开始建图失败 +error_cut_manually = 切手动失败 +error_cut_automatically = 切自动失败 +error_end_mapping = 结束建图失败 +error_set_station = 设置站点失败 +error_get_Maps = 获取地图列表失败 +error_deploy_maps = 部署地图失败 +error_app_maps = 应用地图失败 +error_get_map_pack = 获取地图包失败 +error_map_pack_isNull = "地图包数据为空,扫描失败。请重新建图" +error_map_pgm = 地图PGM转换失败 +error_yaml_isNull = .YAML文件内容为空 +error_formatting_error = 格式错误 +error_parsing = YAML解析失败: +error_process_map_pack = 处理地图包数据失败: +error_png_convert_transparency = .png点云图将灰度图像转换为透明背景图像失败: +error_p5 = 不支持的PGM格式,仅支持P5二进制格式 +error_invalid_size = 无效的尺寸格式 +error_pgm_parse = PGM解析错误: +error_synchronized_map = 同步地图失败 +error_relocate = 重定位指令下发失败 +error_restart = 车体程序重启失败 +error_abandon_mapping = 放弃建图失败 +error_auto_back = 自动返回上一个点失败 +error_restart_system = 重启系统失败 +error_initializing_system = 初始化底层系统失败 +error_set_forkLegs= 操作叉腿避障失败 \ No newline at end of file diff --git a/src/main/resources/language/error/error_en_US.properties b/src/main/resources/language/error/error_en_US.properties new file mode 100644 index 0000000..96d4e2e --- /dev/null +++ b/src/main/resources/language/error/error_en_US.properties @@ -0,0 +1,28 @@ +error_request_vehicle = Abnormal access to the vehicle body interface +error_request_rcs = Accessing the RCS interface is abnormal +error_starting_mapping = Failed to start mapping +error_cut_manually = Cut manual failure +error_cut_automatically = Cut automatically fails +error_end_mapping = End mapping failed +error_set_station = Setting up the site failed +error_get_Maps = Failed to get the map list +error_deploy_maps = Deploying the map failed +error_app_maps = Applying the map failed +error_get_map_pack = Failed to get the map package +error_map_pack_isNull = The map package data is empty and the scan fails. Please rebuild the map +error_map_pgm = Map PGM conversion failed +error_yaml_isNull = .yaml file content is empty +error_parsing = YAML parsing failed: +error_process_map_pack = Failed to process map packet data: +error_png_convert_transparency = Converting a grayscale image to a transparent background image .png a point cloud plot fails: +error_p5 = PGM format is not supported, only P5 binary format is supported +error_invalid_size = Invalid size format +error_pgm_parse = PGM parsing error: +error_synchronized_map = Syncing maps failed +error_relocate = The relocation instruction failed to be issued +error_restart = The body program restarts failed +error_abandon_mapping = Abandoned mapping failed +error_auto_back = Automatically returning to the previous point failed +error_restart_system = Rebooting the system failed +error_initializing_system = Initializing the underlying system failed +error_set_forkLegs= The operation of the fork leg to avoid obstacles failed \ No newline at end of file diff --git a/src/main/resources/language/error/error_zh_CN.properties b/src/main/resources/language/error/error_zh_CN.properties new file mode 100644 index 0000000..438c3c3 --- /dev/null +++ b/src/main/resources/language/error/error_zh_CN.properties @@ -0,0 +1,28 @@ +error_request_vehicle = 访问车体接口异常 +error_request_rcs = 访问RCS接口异常 +error_starting_mapping = 开始建图失败 +error_cut_manually = 切手动失败 +error_cut_automatically = 切自动失败 +error_end_mapping = 结束建图失败 +error_set_station = 设置站点失败 +error_get_Maps = 获取地图列表失败 +error_deploy_maps = 部署地图失败 +error_app_maps = 应用地图失败 +error_get_map_pack = 获取地图包失败 +error_map_pack_isNull = "地图包数据为空,扫描失败。请重新建图" +error_map_pgm = 地图PGM转换失败 +error_yaml_isNull = .YAML文件内容为空 +error_parsing = YAML解析失败: +error_process_map_pack = 处理地图包数据失败: +error_png_convert_transparency = .png点云图将灰度图像转换为透明背景图像失败: +error_p5 = 不支持的PGM格式,仅支持P5二进制格式 +error_invalid_size = 无效的尺寸格式 +error_pgm_parse = PGM解析错误: +error_synchronized_map = 同步地图失败 +error_relocate = 重定位指令下发失败 +error_restart = 车体程序重启失败 +error_abandon_mapping = 放弃建图失败 +error_auto_back = 自动返回上一个点失败 +error_restart_system = 重启系统失败 +error_initializing_system = 初始化底层系统失败 +error_set_forkLegs= 操作叉腿避障失败 \ No newline at end of file diff --git a/src/main/resources/language/task/task.properties b/src/main/resources/language/task/task.properties new file mode 100644 index 0000000..2b35d59 --- /dev/null +++ b/src/main/resources/language/task/task.properties @@ -0,0 +1,5 @@ +task_Id_isNull=任务不存在!【{0}】 +task_Is_exist=当前有正在执行中的任务! +task_Is_not_exist=当前没有任务在执行中! +not_return_point = 没有返回点! +task_cannot_be_enforced = 任务链操作类型无法执行,请重新选择 \ No newline at end of file diff --git a/src/main/resources/language/task/task_en_US.properties b/src/main/resources/language/task/task_en_US.properties new file mode 100644 index 0000000..81dc72b --- /dev/null +++ b/src/main/resources/language/task/task_en_US.properties @@ -0,0 +1,5 @@ +task_Id_isNull=The task with the task code of {0} was not found +task_Is_exist=There are tasks in progress! +task_Is_not_exist=No tasks are currently in progress! +not_return_point = There is no return point! +task_cannot_be_enforced = The task chain operation type cannot be executed, please select it again \ No newline at end of file diff --git a/src/main/resources/language/task/task_zh_CN.properties b/src/main/resources/language/task/task_zh_CN.properties new file mode 100644 index 0000000..0c367b8 --- /dev/null +++ b/src/main/resources/language/task/task_zh_CN.properties @@ -0,0 +1,5 @@ +task_Id_isNull=未找到任务编码为{0}的任务 +task_Is_exist=当前有正在执行中的任务! +task_Is_not_exist=当前没有任务在执行中! +not_return_point = 没有返回点! +task_cannot_be_enforced = 任务链操作类型无法执行,请重新选择 \ No newline at end of file diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..493c4a4 --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + INFO + + + ${log.pattern} + ${log.charset} + + + + + + + + + + + ${LOG_HOME}/root/%d{yyyy-MM-dd}.%i.log + + 15 + + 20MB + + 1GB + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + ${log.charset} + + + + + + + + + + + + + + + + + + + + + 0 + 256 + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/org/nl/qrobot/QRobotApplicationTests.java b/src/test/java/org/nl/qrobot/QRobotApplicationTests.java new file mode 100644 index 0000000..853c144 --- /dev/null +++ b/src/test/java/org/nl/qrobot/QRobotApplicationTests.java @@ -0,0 +1,13 @@ +package org.nl.qrobot; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class QRobotApplicationTests { + + @Test + void contextLoads() { + } + +}