资源相关
This commit is contained in:
		| @ -49,6 +49,7 @@ public class ServerApp implements CommandLineRunner { | ||||
|         serverInitService.init(); | ||||
|         // 检查默认数据 | ||||
|         serverInitService.checkDefaultData(); | ||||
|         // 打印项目文档地址 | ||||
|         log.info("项目文档地址: {}", "http://" + serverConfig.getHost() + ":" + serverConfig.getPort() + "/doc.html"); | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										12
									
								
								src/main/java/com/yj/earth/annotation/CheckAuth.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/main/java/com/yj/earth/annotation/CheckAuth.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| package com.yj.earth.annotation; | ||||
|  | ||||
| import java.lang.annotation.*; | ||||
|  | ||||
| /** | ||||
|  * 授权验证注解 | ||||
|  */ | ||||
| @Target({ElementType.METHOD}) | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| @Documented | ||||
| public @interface CheckAuth { | ||||
| } | ||||
							
								
								
									
										99
									
								
								src/main/java/com/yj/earth/aspect/AuthAspect.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								src/main/java/com/yj/earth/aspect/AuthAspect.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,99 @@ | ||||
| package com.yj.earth.aspect; | ||||
|  | ||||
| import com.yj.earth.auth.AuthInfo; | ||||
| import com.yj.earth.auth.AuthValidator; | ||||
| import com.yj.earth.common.util.ApiResponse; | ||||
| import com.yj.earth.common.util.ServerUniqueIdUtil; | ||||
| 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.springframework.core.io.ClassPathResource; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.io.FileInputStream; | ||||
| import java.io.IOException; | ||||
| import java.nio.charset.StandardCharsets; | ||||
|  | ||||
| /** | ||||
|  * 授权验证切面 | ||||
|  * 实现授权验证注解的功能 | ||||
|  */ | ||||
| @Aspect | ||||
| @Component | ||||
| public class AuthAspect { | ||||
|  | ||||
|     // 授权文件路径 | ||||
|     private static final String LICENSE_FILE_PATH = "license/yjearth.lic"; | ||||
|  | ||||
|     @Pointcut("@annotation(com.yj.earth.annotation.CheckAuth)") | ||||
|     public void authPointCut() { | ||||
|     } | ||||
|  | ||||
|     @Around("authPointCut()") | ||||
|     public Object around(ProceedingJoinPoint point) throws Throwable { | ||||
|         // 读取授权文件内容 | ||||
|         String authString = readLicenseFile(); | ||||
|  | ||||
|         // 检查授权文件是否存在 | ||||
|         if (authString == null || authString.trim().isEmpty()) { | ||||
|             return ApiResponse.failure("未导入授权"); | ||||
|         } | ||||
|  | ||||
|         // 获取当前服务器硬件信息 | ||||
|         String currentServerHardwareMd5 = ServerUniqueIdUtil.getServerUniqueId(); | ||||
|  | ||||
|         // 验证授权是否有效 | ||||
|         boolean isValid = AuthValidator.validateAuth(authString, currentServerHardwareMd5); | ||||
|         if (!isValid) { | ||||
|             // 进一步判断是未授权还是已过期 | ||||
|             try { | ||||
|                 AuthInfo authInfo = AuthValidator.getAuthInfo(authString); | ||||
|                 // 检查是否过期 | ||||
|                 if (new java.util.Date().after(authInfo.getExpireTime())) { | ||||
|                     return ApiResponse.failure("授权已过期"); | ||||
|                 } else { | ||||
|                     return ApiResponse.failure("未授权"); | ||||
|                 } | ||||
|             } catch (Exception e) { | ||||
|                 return ApiResponse.failure("未授权"); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // 授权有效,继续执行原方法 | ||||
|         return point.proceed(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 读取授权文件内容 | ||||
|      * @return 授权文件内容,如果文件不存在或读取失败则返回null | ||||
|      */ | ||||
|     private String readLicenseFile() { | ||||
|         try { | ||||
|             // 尝试从类路径下读取 | ||||
|             ClassPathResource resource = new ClassPathResource(LICENSE_FILE_PATH); | ||||
|             if (resource.exists()) { | ||||
|                 try (FileInputStream inputStream = new FileInputStream(resource.getFile())) { | ||||
|                     byte[] bytes = new byte[inputStream.available()]; | ||||
|                     inputStream.read(bytes); | ||||
|                     return new String(bytes, StandardCharsets.UTF_8).trim(); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // 尝试从文件系统读取 | ||||
|             File file = new File(LICENSE_FILE_PATH); | ||||
|             if (file.exists() && file.isFile()) { | ||||
|                 try (FileInputStream inputStream = new FileInputStream(file)) { | ||||
|                     byte[] bytes = new byte[inputStream.available()]; | ||||
|                     inputStream.read(bytes); | ||||
|                     return new String(bytes, StandardCharsets.UTF_8).trim(); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return null; | ||||
|         } catch (IOException e) { | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -2,7 +2,6 @@ package com.yj.earth.auth; | ||||
|  | ||||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||
| import com.yj.earth.common.util.ServerUniqueIdUtil; | ||||
|  | ||||
| import javax.crypto.Cipher; | ||||
| import javax.crypto.spec.SecretKeySpec; | ||||
|  | ||||
| @ -1,6 +1,5 @@ | ||||
| package com.yj.earth.auth; | ||||
|  | ||||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||
| import com.yj.earth.common.util.ServerUniqueIdUtil; | ||||
|  | ||||
| @ -100,7 +99,7 @@ public class AuthValidator { | ||||
|     public static void main(String[] args) { | ||||
|         String serverUniqueId = ServerUniqueIdUtil.getServerUniqueId(); | ||||
|         // 生成授权 | ||||
|         String authString = AuthGenerator.generateAuth("标准版", 1000, 30, serverUniqueId); | ||||
|         String authString = AuthGenerator.generateAuth("标准版", 1000, 30, "35A0DF1D05AEAE77E1E2715CC36A7368"); | ||||
|         System.out.println("授权字符串:" + authString); | ||||
|         // 验证授权 | ||||
|         boolean isValid = AuthValidator.validateAuth(authString, serverUniqueId); | ||||
|  | ||||
| @ -2,6 +2,17 @@ package com.yj.earth.business.controller; | ||||
|  | ||||
| import cn.hutool.core.io.FileUtil; | ||||
| import cn.hutool.core.util.IdUtil; | ||||
| import com.drew.imaging.ImageMetadataReader; | ||||
| import com.drew.imaging.ImageProcessingException; | ||||
| import com.drew.metadata.Directory; | ||||
| import com.drew.metadata.Metadata; | ||||
|  | ||||
| import java.io.*; | ||||
| import java.nio.file.Files; | ||||
| import java.nio.file.Path; | ||||
| import java.nio.file.Paths; | ||||
| import java.util.*; | ||||
|  | ||||
| import cn.hutool.crypto.digest.DigestUtil; | ||||
| import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | ||||
| import com.baomidou.mybatisplus.core.metadata.IPage; | ||||
| @ -9,6 +20,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | ||||
| import com.yj.earth.business.domain.FileInfo; | ||||
| import com.yj.earth.business.service.FileInfoService; | ||||
| import com.yj.earth.common.util.ApiResponse; | ||||
| import com.yj.earth.common.util.JsonMapConverter; | ||||
| import com.yj.earth.vo.FileInfoVo; | ||||
| import io.swagger.v3.oas.annotations.Operation; | ||||
| import io.swagger.v3.oas.annotations.Parameter; | ||||
| @ -17,17 +29,16 @@ import jakarta.servlet.http.HttpServletResponse; | ||||
| import org.springframework.beans.BeanUtils; | ||||
| import org.springframework.beans.factory.annotation.Value; | ||||
| import org.springframework.http.HttpHeaders; | ||||
| import org.springframework.util.DigestUtils; | ||||
| import org.springframework.web.bind.annotation.*; | ||||
| import org.springframework.web.multipart.MultipartFile; | ||||
|  | ||||
| import javax.annotation.Resource; | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.io.OutputStream; | ||||
| import java.io.InputStream; | ||||
| import java.net.URLEncoder; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| @Tag(name = "文件数据管理") | ||||
| @ -168,11 +179,7 @@ public class FileInfoController { | ||||
|  | ||||
|     @Operation(summary = "文件预览") | ||||
|     @GetMapping("/preview/{id}") | ||||
|     public void previewFile( | ||||
|             @Parameter(description = "文件ID", required = true) | ||||
|             @PathVariable String id, | ||||
|             HttpServletResponse response) throws IOException { | ||||
|  | ||||
|     public void previewFile(@Parameter(description = "文件ID", required = true) @PathVariable String id, HttpServletResponse response) throws IOException { | ||||
|         // 根据ID查询文件信息 | ||||
|         FileInfo fileInfo = fileInfoService.getById(id); | ||||
|         if (fileInfo == null) { | ||||
| @ -200,42 +207,100 @@ public class FileInfoController { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Operation(summary = "文件列表") | ||||
|     @GetMapping("/list") | ||||
|     public ApiResponse getFileList( | ||||
|             @Parameter(description = "页码", required = true) Integer pageNum, | ||||
|             @Parameter(description = "每页条数", required = true) Integer pageSize, | ||||
|             @Parameter(description = "文件名称") String fileName) { | ||||
|  | ||||
|         // 创建分页对象 | ||||
|         Page<FileInfo> page = new Page<>(pageNum, pageSize); | ||||
|         // 构建查询条件 | ||||
|         LambdaQueryWrapper<FileInfo> queryWrapper = new LambdaQueryWrapper<>(); | ||||
|         if (fileName != null && !fileName.isEmpty()) { | ||||
|             queryWrapper.like(FileInfo::getFileName, fileName); | ||||
|     public String handleLocationImageUpload(MultipartFile file) { | ||||
|         try { | ||||
|             // 校验文件是否为空 | ||||
|             if (file.isEmpty()) { | ||||
|                 throw new IllegalArgumentException("上传文件不能为空"); | ||||
|             } | ||||
|             // 获取文件基本信息 | ||||
|             String originalFilename = file.getOriginalFilename(); | ||||
|             String fileSuffix = FileUtil.extName(originalFilename); | ||||
|             String contentType = file.getContentType(); | ||||
|             // 验证是否为图片文件 | ||||
|             if (contentType == null || !contentType.startsWith("image/")) { | ||||
|                 throw new IllegalArgumentException("请上传图片文件"); | ||||
|             } | ||||
|             // 获取完整的上传目录路径 | ||||
|             Path fullUploadPath = Paths.get(getFullUploadPath()); | ||||
|             // 生成唯一文件名 | ||||
|             String uniqueFileName = UUID.randomUUID().toString().replaceAll("-", "") + "." + fileSuffix; | ||||
|             // 创建文件存储目录 | ||||
|             Files.createDirectories(fullUploadPath); | ||||
|             // 构建完整文件路径并保存文件 | ||||
|             Path destFilePath = fullUploadPath.resolve(uniqueFileName); | ||||
|             // 先将文件保存到目标位置 | ||||
|             file.transferTo(destFilePath); | ||||
|             // 计算文件MD5(使用已保存的文件) | ||||
|             String fileMd5 = calculateFileMd5(destFilePath.toFile()); | ||||
|             // 检查文件是否已存在 | ||||
|             LambdaQueryWrapper<FileInfo> queryWrapper = new LambdaQueryWrapper<>(); | ||||
|             queryWrapper.eq(FileInfo::getFileName, originalFilename) | ||||
|                     .eq(FileInfo::getFileMd5, fileMd5); | ||||
|             if (fileInfoService.count(queryWrapper) > 0) { | ||||
|                 throw new IllegalStateException("已存在相同的图片文件"); | ||||
|             } | ||||
|             // 提取图片元数据(使用已保存的文件,避免使用临时文件) | ||||
|             Map<String, Object> metadata; | ||||
|             try (InputStream is = Files.newInputStream(destFilePath)) { | ||||
|                 metadata = extractImageMetadata(is); | ||||
|             } | ||||
|             // 保存文件信息到数据库 | ||||
|             FileInfo fileInfo = new FileInfo(); | ||||
|             fileInfo.setFileName(originalFilename); | ||||
|             fileInfo.setFileSuffix(fileSuffix); | ||||
|             fileInfo.setContentType(contentType); | ||||
|             fileInfo.setFileSize(file.getSize()); | ||||
|             fileInfo.setFilePath(uniqueFileName); | ||||
|             fileInfo.setFileMd5(fileMd5); | ||||
|             fileInfoService.save(fileInfo); | ||||
|             // 构建并返回结果 | ||||
|             Map<String, Object> result = new HashMap<>(); | ||||
|             result.put("previewUrl", "/fileInfo/preview/" + fileInfo.getId()); | ||||
|             result.put("downloadUrl", "/fileInfo/download/" + fileInfo.getId()); | ||||
|             result.put("metadata", metadata); | ||||
|             return JsonMapConverter.mapToJson(result); | ||||
|         } catch (IOException e) { | ||||
|             throw new RuntimeException("文件上传失败: " + e.getMessage(), e); | ||||
|         } | ||||
|         // 按创建时间倒序排列、最新上传的文件在前 | ||||
|         queryWrapper.orderByDesc(FileInfo::getCreatedAt); | ||||
|     } | ||||
|  | ||||
|         // 执行分页查询 | ||||
|         IPage<FileInfo> fileInfoPage = fileInfoService.page(page, queryWrapper); | ||||
|     /** | ||||
|      * 计算文件的 MD5 | ||||
|      */ | ||||
|     private String calculateFileMd5(File file) throws IOException { | ||||
|         try (InputStream is = new FileInputStream(file)) { | ||||
|             return DigestUtils.md5DigestAsHex(is); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|         // 转换为VO对象并设置URL | ||||
|         List<FileInfoVo> records = fileInfoPage.getRecords().stream().map(fileInfo -> { | ||||
|             FileInfoVo vo = new FileInfoVo(); | ||||
|             BeanUtils.copyProperties(fileInfo, vo); | ||||
|             vo.setPreviewUrl("/fileInfo/preview/" + fileInfo.getId()); | ||||
|             vo.setDownloadUrl("/fileInfo/download/" + fileInfo.getId()); | ||||
|             return vo; | ||||
|         }).collect(Collectors.toList()); | ||||
|     /** | ||||
|      * 提取图片的EXIF元数据(包括定位信息) | ||||
|      */ | ||||
|     private Map<String, Object> extractImageMetadata(InputStream inputStream) { | ||||
|         try { | ||||
|             Map<String, Object> result = new HashMap<>(); | ||||
|             Metadata metadata = ImageMetadataReader.readMetadata(inputStream); | ||||
|  | ||||
|         // 构建分页结果 | ||||
|         Page<FileInfoVo> resultPage = new Page<>(); | ||||
|         resultPage.setRecords(records); | ||||
|         resultPage.setTotal(fileInfoPage.getTotal()); | ||||
|         resultPage.setSize(fileInfoPage.getSize()); | ||||
|         resultPage.setCurrent(fileInfoPage.getCurrent()); | ||||
|         resultPage.setPages(fileInfoPage.getPages()); | ||||
|         return ApiResponse.success(resultPage); | ||||
|             // 遍历所有元数据目录 | ||||
|             for (Directory directory : metadata.getDirectories()) { | ||||
|                 String directoryName = directory.getName(); | ||||
|                 Map<String, String> directoryTags = new HashMap<>(); | ||||
|  | ||||
|                 // 提取当前目录下的所有标签 | ||||
|                 for (com.drew.metadata.Tag tag : directory.getTags()) { | ||||
|                     directoryTags.put(tag.getTagName(), tag.getDescription()); | ||||
|                 } | ||||
|  | ||||
|                 // 存储当前目录的所有标签 | ||||
|                 if (!directoryTags.isEmpty()) { | ||||
|                     result.put(directoryName, directoryTags); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return result; | ||||
|         } catch (ImageProcessingException | IOException e) { | ||||
|             return Collections.emptyMap(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -8,6 +8,7 @@ import com.graphhopper.GraphHopper; | ||||
| import com.graphhopper.ResponsePath; | ||||
| import com.graphhopper.config.Profile; | ||||
| import com.graphhopper.util.shapes.GHPoint; | ||||
| import com.yj.earth.annotation.CheckAuth; | ||||
| import com.yj.earth.business.domain.FileInfo; | ||||
| import com.yj.earth.business.service.FileInfoService; | ||||
| import com.yj.earth.common.config.GraphHopperProperties; | ||||
| @ -51,6 +52,7 @@ public class GraphHopperController { | ||||
|     private final AtomicBoolean isLoaded = new AtomicBoolean(false); | ||||
|  | ||||
|     @Operation(summary = "获取地图列表") | ||||
|     @CheckAuth | ||||
|     @GetMapping("/list") | ||||
|     public ApiResponse list() { | ||||
|         LambdaQueryWrapper<FileInfo> queryWrapper = new LambdaQueryWrapper<>(); | ||||
| @ -60,6 +62,7 @@ public class GraphHopperController { | ||||
|  | ||||
|     @Operation(summary = "加载地图数据") | ||||
|     @PostMapping("/loadMap") | ||||
|     @CheckAuth | ||||
|     public ApiResponse loadMap(@Parameter(description = "文件ID") @RequestParam String fileId) { | ||||
|         // 参数校验 | ||||
|         if (fileId == null) { | ||||
| @ -118,6 +121,7 @@ public class GraphHopperController { | ||||
|  | ||||
|     @Operation(summary = "路径规划") | ||||
|     @PostMapping("/route") | ||||
|     @CheckAuth | ||||
|     public ApiResponse calculateRoute(@RequestBody RouteRequest request) { | ||||
|         // 校验地图是否加载完成 + 实例是否可用 | ||||
|         if (!isLoaded.get() || currentHopper == null) { | ||||
| @ -145,26 +149,36 @@ public class GraphHopperController { | ||||
|  | ||||
|             // 处理错误 | ||||
|             if (response.hasErrors()) { | ||||
|                 return ApiResponse.failure("路径计算失败: " + response.getErrors().toString()); | ||||
|             } | ||||
|                 // 检查是否有超出范围的错误 | ||||
|                 boolean hasOutOfBoundsError = response.getErrors().stream() | ||||
|                         .anyMatch(e -> e instanceof com.graphhopper.util.exceptions.PointOutOfBoundsException); | ||||
|  | ||||
|                 if (hasOutOfBoundsError) { | ||||
|                     // 返回超出范围的特定格式响应 | ||||
|                     return ApiResponse.failure("路径超出地图范围"); | ||||
|                 } else { | ||||
|                     return ApiResponse.failure("路径计算失败: " + response.getErrors().toString()); | ||||
|                 } | ||||
|             } | ||||
|             // 解析结果 | ||||
|             ResponsePath bestPath = response.getBest(); | ||||
|             List<Point> pathPoints = new ArrayList<>(); | ||||
|             bestPath.getPoints().forEach(ghPoint -> | ||||
|                     pathPoints.add(new Point(ghPoint.getLat(), ghPoint.getLon())) | ||||
|             ); | ||||
|  | ||||
|             // 封装返回 | ||||
|             RouteResponse routeResponse = new RouteResponse(bestPath.getDistance() / 1000, (double) (bestPath.getTime() / 60000), pathPoints); | ||||
|             return ApiResponse.success(routeResponse); | ||||
|  | ||||
|         } catch (com.graphhopper.util.exceptions.PointOutOfBoundsException e) { | ||||
|             // 捕获单点超出范围的异常 | ||||
|             return ApiResponse.failure("路径超出地图范围"); | ||||
|         } catch (Exception e) { | ||||
|             return ApiResponse.failure("路径计算异常: " + e.getMessage()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Operation(summary = "获取交通方式") | ||||
|     @CheckAuth | ||||
|     @PostMapping("/profiles") | ||||
|     public ApiResponse profiles() { | ||||
|         return ApiResponse.success(graphHopperProperties.getProfiles()); | ||||
|  | ||||
| @ -2,26 +2,40 @@ package com.yj.earth.business.controller; | ||||
|  | ||||
| import cn.dev33.satoken.stp.StpUtil; | ||||
| import cn.hutool.core.io.FileUtil; | ||||
| import cn.hutool.core.lang.UUID; | ||||
| import cn.hutool.core.util.IdUtil; | ||||
| import cn.hutool.crypto.digest.DigestUtil; | ||||
| import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | ||||
| import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | ||||
| import com.drew.imaging.ImageMetadataReader; | ||||
| import com.drew.imaging.ImageProcessingException; | ||||
| import com.drew.metadata.Directory; | ||||
| import com.drew.metadata.Metadata; | ||||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||
| import com.yj.earth.annotation.CheckAuth; | ||||
| import com.yj.earth.business.domain.FileInfo; | ||||
| import com.yj.earth.business.domain.Role; | ||||
| import com.yj.earth.business.domain.Source; | ||||
| import com.yj.earth.business.service.RoleSourceService; | ||||
| import com.yj.earth.business.service.SourceService; | ||||
| import com.yj.earth.business.service.UserService; | ||||
| import com.yj.earth.business.service.*; | ||||
| import com.yj.earth.common.service.SourceDataGenerator; | ||||
| import com.yj.earth.common.service.SourceParamsValidator; | ||||
| import com.yj.earth.common.util.ApiResponse; | ||||
| import com.yj.earth.common.util.MapUtil; | ||||
| import com.yj.earth.dto.source.*; | ||||
| import io.swagger.v3.oas.annotations.Operation; | ||||
| import io.swagger.v3.oas.annotations.Parameter; | ||||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.beans.BeanUtils; | ||||
| import org.springframework.beans.factory.annotation.Value; | ||||
| import org.springframework.web.bind.annotation.*; | ||||
| import org.springframework.web.multipart.MultipartFile; | ||||
|  | ||||
| import javax.annotation.Resource; | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.util.*; | ||||
|  | ||||
| import static com.yj.earth.common.constant.GlobalConstant.DIRECTORY; | ||||
| @ -39,10 +53,19 @@ public class SourceController { | ||||
|     @Resource | ||||
|     private RoleSourceService roleSourceService; | ||||
|     @Resource | ||||
|     private RoleService roleService; | ||||
|     @Resource | ||||
|     private SourceParamsValidator sourceParamsValidator; | ||||
|  | ||||
|     @Resource | ||||
|     private SourceDataGenerator sourceDataGenerator; | ||||
|     @Resource | ||||
|     private FileInfoService fileInfoService; | ||||
|  | ||||
|     @Resource | ||||
|     private FileInfoController fileInfoControllerl; | ||||
|  | ||||
|     @Value("${file.upload.path}") | ||||
|     private String uploadPath; | ||||
|  | ||||
|     @PostMapping("/addDirectory") | ||||
|     @Operation(summary = "新增目录资源") | ||||
| @ -86,10 +109,10 @@ public class SourceController { | ||||
|         source.setSourceName(sourceName); | ||||
|         source.setParentId(addModelSourceDto.getParentId()); | ||||
|         source.setTreeIndex(addModelSourceDto.getTreeIndex()); | ||||
|         source.setParams(addModelSourceDto.getParams()); | ||||
|         source.setDetail(detail); | ||||
|         source.setSourceType(MapUtil.getString(MapUtil.jsonToMap(detail), "fileType")); | ||||
|         source.setIsShow(SHOW); | ||||
|         source.setParams(addModelSourceDto.getParams()); | ||||
|         sourceService.save(source); | ||||
|         // 添加资源到该用户的角色下 | ||||
|         roleSourceService.addRoleSource(userService.getById(StpUtil.getLoginIdAsString()).getRoleId(), source.getId()); | ||||
| @ -196,6 +219,30 @@ public class SourceController { | ||||
|         return ApiResponse.success(null); | ||||
|     } | ||||
|  | ||||
|     @PostMapping("/uploadLocationImage") | ||||
|     @Operation(summary = "新增定位图片") | ||||
|     public ApiResponse uploadLocationImage(@RequestParam("ids") @Parameter(description = "上传定位图片ID列表") List<String> ids, | ||||
|                                            @RequestParam(value = "parentId", required = false) @Parameter(description = "父节点ID") String parentId, | ||||
|                                            @RequestParam(value = "treeIndex", required = false) @Parameter(description = "树状索引") Integer treeIndex, | ||||
|                                            @RequestParam("files") @Parameter(description = "带有定位的图片文件", required = true) MultipartFile[] files) { | ||||
|         for (int i = 0; i < files.length; i++) { | ||||
|             String detail = fileInfoControllerl.handleLocationImageUpload(files[i]); | ||||
|             // 构建并保存资源对象 | ||||
|             Source source = new Source(); | ||||
|             source.setId(ids.get(i)); | ||||
|             source.setSourceName(files[i].getOriginalFilename()); | ||||
|             source.setParentId(parentId); | ||||
|             source.setSourceType("locationImage"); | ||||
|             source.setTreeIndex(treeIndex); | ||||
|             source.setDetail(detail); | ||||
|             source.setIsShow(SHOW); | ||||
|             sourceService.save(source); | ||||
|             // 添加资源到该用户的角色下 | ||||
|             roleSourceService.addRoleSource(userService.getById(StpUtil.getLoginIdAsString()).getRoleId(), source.getId()); | ||||
|         } | ||||
|         return ApiResponse.success(null); | ||||
|     } | ||||
|  | ||||
|     @GetMapping("/type") | ||||
|     @Operation(summary = "获取已有类型") | ||||
|     public ApiResponse getSupportedSourceTypes() { | ||||
| @ -208,4 +255,61 @@ public class SourceController { | ||||
|     public String getExampleData(String sourceType) throws JsonProcessingException { | ||||
|         return sourceDataGenerator.generateDefaultJson(sourceType); | ||||
|     } | ||||
|  | ||||
|     @Operation(summary = "设置默认数据") | ||||
|     @GetMapping("/default") | ||||
|     public ApiResponse getDefaultData() { | ||||
|         log.info("开始初始化默认资源数据"); | ||||
|         String userId = StpUtil.getLoginIdAsString(); | ||||
|         String roleId = userService.getById(userId).getRoleId(); | ||||
|  | ||||
|         // 创建一级目录 | ||||
|         createSourceIfNotExists("倾斜模型", "directory", null, 0, 1, roleId); | ||||
|         createSourceIfNotExists("人工模型", "directory", null, 0, 1, roleId); | ||||
|         createSourceIfNotExists("卫星底图", "directory", null, 0, 1, roleId); | ||||
|         createSourceIfNotExists("地形", "directory", null, 0, 1, roleId); | ||||
|  | ||||
|         // 创建"在线图源"目录及其子资源 | ||||
|         Source onlineSource = createSourceIfNotExists("在线图源", "directory", null, 0, 1, roleId); | ||||
|         if (onlineSource != null) { | ||||
|             String onlineSourceId = onlineSource.getId(); | ||||
|             createSourceIfNotExists("卫星图", "arcgisWximagery", onlineSourceId, 0, 1, roleId); | ||||
|             createSourceIfNotExists("暗黑地图", "arcgisBlueImagery", onlineSourceId, 0, 1, roleId); | ||||
|             createSourceIfNotExists("路网图", "gdlwImagery", onlineSourceId, 0, 1, roleId); | ||||
|             createSourceIfNotExists("矢量图", "gdslImagery", onlineSourceId, 0, 1, roleId); | ||||
|         } | ||||
|  | ||||
|         log.info("默认资源数据初始化完成"); | ||||
|         return ApiResponse.success(null); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 检查资源是否存在、不存在则创建并关联角色资源 | ||||
|      */ | ||||
|     private Source createSourceIfNotExists(String sourceName, String sourceType, String parentId, int treeIndex, int isShow, String roleId) { | ||||
|         // 检查资源是否已存在(通过名称和父ID组合判断唯一性) | ||||
|         Source existingSource = sourceService.getOne(new LambdaQueryWrapper<Source>() | ||||
|                 .eq(Source::getSourceName, sourceName) | ||||
|                 .eq(parentId != null, Source::getParentId, parentId) | ||||
|                 .isNull(parentId == null, Source::getParentId)); | ||||
|         if (existingSource != null) { | ||||
|             return existingSource; | ||||
|         } | ||||
|         // 不存在则创建新资源 | ||||
|         try { | ||||
|             Source newSource = new Source(); | ||||
|             newSource.setId(cn.hutool.core.lang.UUID.fastUUID().toString(true)); | ||||
|             newSource.setSourceName(sourceName); | ||||
|             newSource.setSourceType(sourceType); | ||||
|             newSource.setParentId(parentId); | ||||
|             newSource.setTreeIndex(treeIndex); | ||||
|             newSource.setIsShow(isShow); | ||||
|             sourceService.save(newSource); | ||||
|             // 关联角色资源 | ||||
|             roleSourceService.addRoleSource(roleId, newSource.getId()); | ||||
|             return newSource; | ||||
|         } catch (Exception e) { | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -0,0 +1,436 @@ | ||||
| package com.yj.earth.business.controller; | ||||
|  | ||||
| import com.yj.earth.common.util.ApiResponse; | ||||
| import io.swagger.v3.oas.annotations.Operation; | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||||
| import jakarta.validation.Valid; | ||||
| import lombok.Data; | ||||
| import org.springframework.format.annotation.DateTimeFormat; | ||||
| 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 java.time.Duration; | ||||
| import java.time.LocalDateTime; | ||||
| import java.time.format.DateTimeFormatter; | ||||
| import java.util.regex.Matcher; | ||||
| import java.util.regex.Pattern; | ||||
|  | ||||
| @Tag(name = "战斗计算相关") | ||||
| @RestController | ||||
| @RequestMapping("/api/tactical") | ||||
| public class TacticalCalculationController { | ||||
|  | ||||
|     // 日期时间格式化器 | ||||
|     private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); | ||||
|  | ||||
|     @PostMapping("/meet") | ||||
|     @Operation(summary = "计算相遇结果") | ||||
|     public ApiResponse calculateMeet(@Valid @RequestBody MeetInputDTO input) { | ||||
|         MeetResultDTO result = new MeetResultDTO(); | ||||
|  | ||||
|         // 计算相遇时间(小时) | ||||
|         double meetTimeHours; | ||||
|         if (input.isSameDirection()) { | ||||
|             // 同向而行:时间 = 距离 / (速度差) | ||||
|             double speedDiff = Math.abs(input.getSpeed1() - input.getSpeed2()); | ||||
|             if (speedDiff <= 0) { | ||||
|                 throw new IllegalArgumentException("同向而行时,速度不能相等或后方速度小于前方"); | ||||
|             } | ||||
|             meetTimeHours = input.getInitialDistance() / speedDiff; | ||||
|         } else { | ||||
|             // 相向而行:时间 = 距离 / (速度和) | ||||
|             meetTimeHours = input.getInitialDistance() / (input.getSpeed1() + input.getSpeed2()); | ||||
|         } | ||||
|  | ||||
|         // 格式化时间结果 | ||||
|         result.setMeetTime(formatDuration(Duration.ofHours((long) meetTimeHours) | ||||
|                 .plusMinutes((long) ((meetTimeHours % 1) * 60)))); | ||||
|  | ||||
|         // 计算相遇点距离 | ||||
|         result.setMeetDistanceFrom1(input.getSpeed1() * meetTimeHours); | ||||
|         result.setMeetDistanceFrom2(input.getSpeed2() * meetTimeHours); | ||||
|  | ||||
|         return ApiResponse.success(result); | ||||
|     } | ||||
|  | ||||
|     @PostMapping("/pursuit") | ||||
|     @Operation(summary = "计算追击结果") | ||||
|     public ApiResponse calculatePursuit(@Valid @RequestBody PursuitInputDTO input) { | ||||
|         PursuitResultDTO result = new PursuitResultDTO(); | ||||
|  | ||||
|         // 检查速度合理性 | ||||
|         if (input.getPursuerSpeed() <= input.getTargetSpeed()) { | ||||
|             throw new IllegalArgumentException("追击速度必须大于目标速度"); | ||||
|         } | ||||
|  | ||||
|         // 计算追击时间(小时) | ||||
|         double pursuitTimeHours = input.getDistance() / (input.getPursuerSpeed() - input.getTargetSpeed()); | ||||
|         result.setPursuitTime(formatDuration(Duration.ofHours((long) pursuitTimeHours) | ||||
|                 .plusMinutes((long) ((pursuitTimeHours % 1) * 60)))); | ||||
|  | ||||
|         // 计算追击距离 | ||||
|         result.setPursuitDistance(input.getPursuerSpeed() * pursuitTimeHours); | ||||
|         return ApiResponse.success(result); | ||||
|     } | ||||
|  | ||||
|     @PostMapping("/formation/person") | ||||
|     @Operation(summary = "计算人员队列长度") | ||||
|     public ApiResponse calculatePersonFormation(@Valid @RequestBody PersonFormationInputDTO input) { | ||||
|         FormationLengthResultDTO result = new FormationLengthResultDTO(); | ||||
|  | ||||
|         // 人员队列长度 = (人数 - 1) * 间距 | ||||
|         if (input.getPersonCount() <= 1) { | ||||
|             result.setTotalLength(0.0); | ||||
|         } else { | ||||
|             result.setTotalLength((input.getPersonCount() - 1) * input.getDistanceBetween()); | ||||
|         } | ||||
|         return ApiResponse.success(result); | ||||
|     } | ||||
|  | ||||
|     @PostMapping("/formation/vehicle") | ||||
|     @Operation(summary = "计算车辆队列长度") | ||||
|     public ApiResponse calculateVehicleFormation(@Valid @RequestBody VehicleFormationInputDTO input) { | ||||
|         FormationLengthResultDTO result = new FormationLengthResultDTO(); | ||||
|  | ||||
|         // 车辆队列长度 = (单辆车长 * 数量) + (间距 * (数量 - 1)) | ||||
|         if (input.getVehicleCount() <= 0) { | ||||
|             result.setTotalLength(0.0); | ||||
|         } else if (input.getVehicleCount() == 1) { | ||||
|             result.setTotalLength(input.getVehicleLength()); | ||||
|         } else { | ||||
|             result.setTotalLength((input.getVehicleLength() * input.getVehicleCount()) + | ||||
|                     (input.getDistanceBetween() * (input.getVehicleCount() - 1))); | ||||
|         } | ||||
|  | ||||
|         return ApiResponse.success(result); | ||||
|     } | ||||
|  | ||||
|     @PostMapping("/formation/gun") | ||||
|     @Operation(summary = "计算火炮队列长度") | ||||
|     public ApiResponse calculateGunFormation(@Valid @RequestBody GunFormationInputDTO input) { | ||||
|         FormationLengthResultDTO result = new FormationLengthResultDTO(); | ||||
|  | ||||
|         // 火炮本身长度 | ||||
|         double gunTotalLength = input.getGunLength() * input.getGunCount(); | ||||
|         // 牵引车辆长度 | ||||
|         double vehicleTotalLength = input.getVehicleLength() * input.getVehicleCount(); | ||||
|         // 车辆间距 | ||||
|         double vehicleDistance = input.getVehicleDistance() * (input.getVehicleCount() - 1); | ||||
|  | ||||
|         // 总长度 | ||||
|         result.setTotalLength(gunTotalLength + vehicleTotalLength + vehicleDistance); | ||||
|         return ApiResponse.success(result); | ||||
|     } | ||||
|  | ||||
|     @PostMapping("/march/time") | ||||
|     @Operation(summary = "计算行军时间") | ||||
|     public ApiResponse calculateMarchTime(@Valid @RequestBody MarchTimeInputDTO input) { | ||||
|         MarchTimeResultDTO result = new MarchTimeResultDTO(); | ||||
|  | ||||
|         // 总行军距离 = 行军距离 + 部队长度(需要完全通过) | ||||
|         double totalDistance = input.getMarchDistance() + input.getFormationLength(); | ||||
|  | ||||
|         // 行军时间(小时)= 总距离 / 速度 + 等待时间 | ||||
|         double marchTimeHours = (totalDistance / input.getSpeed()) + input.getWaitHours(); | ||||
|  | ||||
|         // 格式化行军时间 | ||||
|         result.setTotalMarchTime(formatDuration(Duration.ofHours((long) marchTimeHours) | ||||
|                 .plusMinutes((long) ((marchTimeHours % 1) * 60)))); | ||||
|  | ||||
|         // 计算出发时间(如果提供了要求抵达时间) | ||||
|         if (input.getRequiredArriveTime() != null) { | ||||
|             long hours = (long) marchTimeHours; | ||||
|             long minutes = (long) ((marchTimeHours - hours) * 60); | ||||
|             LocalDateTime departTime = input.getRequiredArriveTime() | ||||
|                     .minusHours(hours) | ||||
|                     .minusMinutes(minutes); | ||||
|             result.setDepartureTime(departTime.format(DATE_TIME_FORMATTER)); | ||||
|         } | ||||
|  | ||||
|         return ApiResponse.success(result); | ||||
|     } | ||||
|  | ||||
|     @PostMapping("/material/consumption") | ||||
|     @Operation(summary = "计算物资消耗") | ||||
|     public ApiResponse calculateMaterialConsumption(@Valid @RequestBody MaterialConsumptionInputDTO input) { | ||||
|         MaterialConsumptionResultDTO result = new MaterialConsumptionResultDTO(); | ||||
|  | ||||
|         switch (input.getMaterialType()) { | ||||
|             case AMMUNITION: | ||||
|                 // 弹药消耗 = 单位基数 * 装备数量 * 时间 | ||||
|                 result.setTotalConsumption(input.getBaseConsumption() * input.getEquipmentCount() * input.getHours()); | ||||
|                 break; | ||||
|             case OIL: | ||||
|                 // 油料消耗 = 单位时间耗油量 * 装备数量 * 时间 | ||||
|                 result.setTotalConsumption(input.getBaseConsumption() * input.getEquipmentCount() * input.getHours()); | ||||
|                 break; | ||||
|             case WEAPON: | ||||
|                 // 武器需求 = 总需求 / (单装备配备数) | ||||
|                 result.setTotalConsumption(Math.ceil(input.getTotalRequirement() / input.getBaseConsumption())); | ||||
|                 break; | ||||
|         } | ||||
|  | ||||
|         return ApiResponse.success(result); | ||||
|     } | ||||
|  | ||||
|     @PostMapping("/unit/formation") | ||||
|     @Operation(summary = "计算部队编成") | ||||
|     public ApiResponse calculateUnitFormation(@Valid @RequestBody UnitFormationInputDTO input) { | ||||
|         UnitFormationResultDTO result = new UnitFormationResultDTO(); | ||||
|  | ||||
|         // 计算编成数量 | ||||
|         result.setFormationCount(Math.ceil(input.getTotalStrength() / input.getUnitStrength())); | ||||
|  | ||||
|         // 计算最后一个编成的兵力 | ||||
|         double lastFormationStrength = input.getTotalStrength() % input.getUnitStrength(); | ||||
|         if (lastFormationStrength <= 0) { | ||||
|             lastFormationStrength = input.getUnitStrength(); | ||||
|         } | ||||
|         result.setLastFormationStrength(lastFormationStrength); | ||||
|         return ApiResponse.success(result); | ||||
|     } | ||||
|  | ||||
|     @PostMapping("/special/crossRiver") | ||||
|     @Operation(summary = "计算渡河时间") | ||||
|     public ApiResponse calculateCrossRiverTime(@Valid @RequestBody CrossRiverInputDTO input) { | ||||
|         SpecialTimeResultDTO result = new SpecialTimeResultDTO(); | ||||
|  | ||||
|         // 计算批次数量 | ||||
|         double batchCount = Math.ceil(input.getPersonCount() / input.getBatchCapacity()); | ||||
|  | ||||
|         // 总时间 = 批次 * 单批次时间 + 准备时间 | ||||
|         double totalHours = (batchCount * input.getBatchHours()) + input.getPrepareHours(); | ||||
|  | ||||
|         // 格式化结果 | ||||
|         result.setTotalTime(formatDuration(Duration.ofHours((long) totalHours) | ||||
|                 .plusMinutes((long) ((totalHours % 1) * 60)))); | ||||
|  | ||||
|         return ApiResponse.success(result); | ||||
|     } | ||||
|  | ||||
|     @PostMapping("/loss/rate") | ||||
|     @Operation(summary = "计算损失率") | ||||
|     public ApiResponse calculateLossRate(@Valid @RequestBody LossRateInputDTO input) { | ||||
|         LossRateResultDTO result = new LossRateResultDTO(); | ||||
|  | ||||
|         if (input.getTotalStrength() <= 0) { | ||||
|             throw new IllegalArgumentException("总兵力必须大于0"); | ||||
|         } | ||||
|  | ||||
|         // 计算损失率 | ||||
|         result.setLossRate((input.getLostStrength() / input.getTotalStrength()) * 100); | ||||
|  | ||||
|         // 计算剩余兵力 | ||||
|         result.setRemainingStrength(input.getTotalStrength() - input.getLostStrength()); | ||||
|         return ApiResponse.success(result); | ||||
|     } | ||||
|  | ||||
|     // 格式化Duration为"X天X时X分X秒" | ||||
|     private String formatDuration(Duration duration) { | ||||
|         long days = duration.toDays(); | ||||
|         long hours = duration.toHours() % 24; | ||||
|         long minutes = duration.toMinutes() % 60; | ||||
|         long seconds = duration.getSeconds() % 60; | ||||
|  | ||||
|         return String.format("%d天%d时%d分%d秒", days, hours, minutes, seconds); | ||||
|     } | ||||
|  | ||||
|     // 解析时间字符串为Duration | ||||
|     private Duration parseTimeString(String timeStr) { | ||||
|         Pattern pattern = Pattern.compile("(?<d>\\d+)天(?<h>\\d+)时(?<m>\\d+)分(?<s>\\d+)秒"); | ||||
|         Matcher matcher = pattern.matcher(timeStr); | ||||
|  | ||||
|         if (matcher.matches()) { | ||||
|             int days = Integer.parseInt(matcher.group("d")); | ||||
|             int hours = Integer.parseInt(matcher.group("h")); | ||||
|             int minutes = Integer.parseInt(matcher.group("m")); | ||||
|             int seconds = Integer.parseInt(matcher.group("s")); | ||||
|  | ||||
|             return Duration.ofDays(days) | ||||
|                     .plusHours(hours) | ||||
|                     .plusMinutes(minutes) | ||||
|                     .plusSeconds(seconds); | ||||
|         } | ||||
|         return Duration.ZERO; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     @Data | ||||
|     public static class MeetInputDTO { | ||||
|         @Schema(description = "初始距离") | ||||
|         private double initialDistance; | ||||
|         @Schema(description = "速度1") | ||||
|         private double speed1; | ||||
|         @Schema(description = "速度2") | ||||
|         private double speed2; | ||||
|         @Schema(description = "是否同向") | ||||
|         private boolean isSameDirection; | ||||
|     } | ||||
|  | ||||
|     @Data | ||||
|     public static class MeetResultDTO { | ||||
|         @Schema(description = "相遇时间") | ||||
|         private String meetTime; | ||||
|         @Schema(description = "距离1的距离") | ||||
|         private double meetDistanceFrom1; | ||||
|         @Schema(description = "距离2的距离") | ||||
|         private double meetDistanceFrom2; | ||||
|     } | ||||
|  | ||||
|     @Data | ||||
|     public static class PursuitInputDTO { | ||||
|         @Schema(description = "初始距离") | ||||
|         private double distance; | ||||
|         @Schema(description = "追击者速度") | ||||
|         private double pursuerSpeed; | ||||
|         @Schema(description = "目标速度") | ||||
|         private double targetSpeed; | ||||
|     } | ||||
|  | ||||
|     @Data | ||||
|     public static class PursuitResultDTO { | ||||
|         @Schema(description = "追击时间") | ||||
|         private String pursuitTime; | ||||
|         @Schema(description = "追击距离") | ||||
|         private double pursuitDistance; | ||||
|     } | ||||
|  | ||||
|     @Data | ||||
|     public static class PersonFormationInputDTO { | ||||
|         @Schema(description = "人数") | ||||
|         private int personCount; | ||||
|         @Schema(description = "人间距") | ||||
|         private double distanceBetween; | ||||
|     } | ||||
|  | ||||
|     @Data | ||||
|     public static class VehicleFormationInputDTO { | ||||
|         @Schema(description = "车辆数量") | ||||
|         private int vehicleCount; | ||||
|         @Schema(description = "车辆长") | ||||
|         private double vehicleLength; | ||||
|         @Schema(description = "车辆距") | ||||
|         private double distanceBetween; | ||||
|     } | ||||
|  | ||||
|     @Data | ||||
|     public static class GunFormationInputDTO { | ||||
|         @Schema(description = "火炮数量") | ||||
|         private int gunCount; | ||||
|         @Schema(description = "单门炮长") | ||||
|         private double gunLength; | ||||
|         @Schema(description = "牵引车辆数量") | ||||
|         private int vehicleCount; | ||||
|         @Schema(description = "单辆车长") | ||||
|         private double vehicleLength; | ||||
|         @Schema(description = "车距") | ||||
|         private double vehicleDistance; | ||||
|     } | ||||
|  | ||||
|     @Data | ||||
|     public static class FormationLengthResultDTO { | ||||
|         @Schema(description = "总长度") | ||||
|         private double totalLength; | ||||
|     } | ||||
|  | ||||
|     @Data | ||||
|     public static class MarchTimeInputDTO { | ||||
|         @Schema(description = "行军距离") | ||||
|         private double marchDistance; | ||||
|         @Schema(description = "部队长度") | ||||
|         private double formationLength; | ||||
|         @Schema(description = "速度") | ||||
|         private double speed; | ||||
|         @Schema(description = "等待时间(小时)") | ||||
|         private double waitHours; | ||||
|         @Schema(description = "要求抵达时间") | ||||
|         @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") | ||||
|         private LocalDateTime requiredArriveTime; | ||||
|     } | ||||
|  | ||||
|     @Data | ||||
|     public static class MarchTimeResultDTO { | ||||
|         @Schema(description = "总行军时间") | ||||
|         private String totalMarchTime; | ||||
|         @Schema(description = "出发时间") | ||||
|         private String departureTime; | ||||
|     } | ||||
|  | ||||
|     @Data | ||||
|     public static class MaterialConsumptionInputDTO { | ||||
|         @Schema(description = "物资类型") | ||||
|         private MaterialType materialType; | ||||
|         @Schema(description = "单位消耗/配备数") | ||||
|         private double baseConsumption; | ||||
|         @Schema(description = "装备数量") | ||||
|         private int equipmentCount; | ||||
|         @Schema(description = "时间(小时)") | ||||
|         private double hours; | ||||
|         @Schema(description = "总需求") | ||||
|         private double totalRequirement; | ||||
|  | ||||
|         public enum MaterialType { | ||||
|             AMMUNITION, OIL, WEAPON | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Data | ||||
|     public static class MaterialConsumptionResultDTO { | ||||
|         @Schema(description = "总消耗量/需求量") | ||||
|         private double totalConsumption; | ||||
|     } | ||||
|  | ||||
|     @Data | ||||
|     public static class UnitFormationInputDTO { | ||||
|         @Schema(description = "总兵力") | ||||
|         private double totalStrength; | ||||
|         @Schema(description = "单位兵力") | ||||
|         private double unitStrength; | ||||
|     } | ||||
|  | ||||
|     @Data | ||||
|     public static class UnitFormationResultDTO { | ||||
|         @Schema(description = "编成数量") | ||||
|         private double formationCount; | ||||
|         @Schema(description = "最后一个编成的兵力") | ||||
|         private double lastFormationStrength; | ||||
|     } | ||||
|  | ||||
|     @Data | ||||
|     public static class CrossRiverInputDTO { | ||||
|         @Schema(description = "总人数") | ||||
|         private int personCount; | ||||
|         @Schema(description = "单批次容量") | ||||
|         private int batchCapacity; | ||||
|         @Schema(description = "单批次时间(小时)") | ||||
|         private double batchHours; | ||||
|         @Schema(description = "准备时间(小时)") | ||||
|         private double prepareHours; | ||||
|     } | ||||
|  | ||||
|     @Data | ||||
|     public static class SpecialTimeResultDTO { | ||||
|         @Schema(description = "总时间") | ||||
|         private String totalTime; | ||||
|     } | ||||
|  | ||||
|     @Data | ||||
|     public static class LossRateInputDTO { | ||||
|         @Schema(description = "总兵力") | ||||
|         private double totalStrength; | ||||
|         @Schema(description = "损失兵力") | ||||
|         private double lostStrength; | ||||
|     } | ||||
|  | ||||
|     @Data | ||||
|     public static class LossRateResultDTO { | ||||
|         @Schema(description = "损失率(百分比)") | ||||
|         private double lossRate; | ||||
|         @Schema(description = "剩余兵力") | ||||
|         private double remainingStrength; | ||||
|     } | ||||
| } | ||||
| @ -1,11 +1,14 @@ | ||||
| package com.yj.earth.common.service; | ||||
|  | ||||
| import cn.dev33.satoken.stp.StpUtil; | ||||
| import cn.hutool.core.lang.UUID; | ||||
| import cn.hutool.crypto.digest.BCrypt; | ||||
| import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | ||||
| import com.yj.earth.business.domain.Role; | ||||
| import com.yj.earth.business.domain.Source; | ||||
| import com.yj.earth.business.domain.User; | ||||
| import com.yj.earth.business.service.RoleService; | ||||
| import com.yj.earth.business.service.RoleSourceService; | ||||
| import com.yj.earth.business.service.SourceService; | ||||
| import com.yj.earth.business.service.UserService; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| @ -23,7 +26,8 @@ public class ServerInitService { | ||||
|     private UserService userService; | ||||
|     @Resource | ||||
|     private RoleService roleService; | ||||
|  | ||||
|     @Resource | ||||
|     private RoleSourceService roleSourceService; | ||||
|     public void init() { | ||||
|         // 查询数据库所有需要加载的资源 | ||||
|         List<Source> list =sourceService.list(new LambdaQueryWrapper<Source>() | ||||
| @ -39,9 +43,14 @@ public class ServerInitService { | ||||
|     } | ||||
|  | ||||
|     public void checkDefaultData() { | ||||
|         checkDefaultUserAndRole(); | ||||
|         checkDefaultSource(); | ||||
|     } | ||||
|  | ||||
|     public void checkDefaultUserAndRole() { | ||||
|         // 查询角色表和用户表是否有数据 | ||||
|         if(roleService.count() == 0 && userService.count() == 0) { | ||||
|             log.info("初始化默认数据"); | ||||
|             log.info("初始化默认用户角色数据"); | ||||
|             // 新增一个管理员角色 | ||||
|             Role adminRole = new Role(); | ||||
|             adminRole.setRoleName("管理员"); | ||||
| @ -64,4 +73,98 @@ public class ServerInitService { | ||||
|             userService.save(user); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public void checkDefaultSource() { | ||||
|         if(sourceService.count() == 0) { | ||||
|             // 查询管理员角色 | ||||
|             Role adminRole = roleService.getOne(new LambdaQueryWrapper<Role>().eq(Role::getRoleName, "管理员")); | ||||
|             log.info("初始化默认资源数据"); | ||||
|             Source source1 = new Source(); | ||||
|             source1.setId(UUID.fastUUID().toString(true)); | ||||
|             source1.setSourceName("倾斜模型"); | ||||
|             source1.setSourceType("directory"); | ||||
|             source1.setTreeIndex(0); | ||||
|             source1.setIsShow(1); | ||||
|             sourceService.save(source1); | ||||
|             roleSourceService.addRoleSource(adminRole.getId(), source1.getId()); | ||||
|  | ||||
|             Source source2 = new Source(); | ||||
|             source2.setId(UUID.fastUUID().toString(true)); | ||||
|             source2.setSourceName("人工模型"); | ||||
|             source2.setSourceType("directory"); | ||||
|             source2.setTreeIndex(0); | ||||
|             source2.setIsShow(1); | ||||
|             sourceService.save(source2); | ||||
|             roleSourceService.addRoleSource(adminRole.getId(), source2.getId()); | ||||
|  | ||||
|             Source source3 = new Source(); | ||||
|             source3.setId(UUID.fastUUID().toString(true)); | ||||
|             source3.setSourceName("卫星底图"); | ||||
|             source3.setSourceType("directory"); | ||||
|             source3.setTreeIndex(0); | ||||
|             source3.setIsShow(1); | ||||
|             sourceService.save(source3); | ||||
|             roleSourceService.addRoleSource(adminRole.getId(), source3.getId()); | ||||
|  | ||||
|             Source source4 = new Source(); | ||||
|             source4.setId(UUID.fastUUID().toString(true)); | ||||
|             source4.setSourceName("地形"); | ||||
|             source4.setSourceType("directory"); | ||||
|             source4.setTreeIndex(0); | ||||
|             source4.setIsShow(1); | ||||
|             sourceService.save(source4); | ||||
|             roleSourceService.addRoleSource(adminRole.getId(), source4.getId()); | ||||
|  | ||||
|             Source source5 = new Source(); | ||||
|             source5.setId(UUID.fastUUID().toString(true)); | ||||
|             source5.setSourceName("在线图源"); | ||||
|             source5.setSourceType("directory"); | ||||
|             source5.setTreeIndex(0); | ||||
|             source5.setIsShow(1); | ||||
|             sourceService.save(source5); | ||||
|             roleSourceService.addRoleSource(adminRole.getId(), source5.getId()); | ||||
|  | ||||
|             Source source6 = new Source(); | ||||
|             source6.setId(UUID.fastUUID().toString(true)); | ||||
|             source6.setSourceName("卫星图"); | ||||
|             source6.setSourceType("arcgisWximagery"); | ||||
|             source6.setParentId(source5.getId()); | ||||
|             source6.setTreeIndex(0); | ||||
|             source6.setIsShow(1); | ||||
|             sourceService.save(source6); | ||||
|             roleSourceService.addRoleSource(adminRole.getId(), source6.getId()); | ||||
|  | ||||
|             Source source7 = new Source(); | ||||
|             source7.setId(UUID.fastUUID().toString(true)); | ||||
|             source7.setSourceName("暗黑地图"); | ||||
|             source7.setSourceType("arcgisBlueImagery"); | ||||
|             source7.setParentId(source5.getId()); | ||||
|             source7.setTreeIndex(0); | ||||
|             source7.setIsShow(1); | ||||
|             sourceService.save(source7); | ||||
|             roleSourceService.addRoleSource(adminRole.getId(), source7.getId()); | ||||
|  | ||||
|             Source source8 = new Source(); | ||||
|             source8.setId(UUID.fastUUID().toString(true)); | ||||
|             source8.setSourceName("路网图"); | ||||
|             source8.setSourceType("gdlwImagery"); | ||||
|             source8.setParentId(source5.getId()); | ||||
|             source8.setTreeIndex(0); | ||||
|             source8.setIsShow(1); | ||||
|             sourceService.save(source8); | ||||
|             roleSourceService.addRoleSource(adminRole.getId(), source8.getId()); | ||||
|  | ||||
|             Source source9 = new Source(); | ||||
|             source9.setId(UUID.fastUUID().toString(true)); | ||||
|             source9.setSourceName("矢量图"); | ||||
|             source9.setSourceType("gdslImagery"); | ||||
|             source9.setParentId(source5.getId()); | ||||
|             source9.setTreeIndex(0); | ||||
|             source9.setIsShow(1); | ||||
|             sourceService.save(source9); | ||||
|             roleSourceService.addRoleSource(adminRole.getId(), source9.getId()); | ||||
|  | ||||
|  | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -206,7 +206,7 @@ public class SourceDataGenerator { | ||||
|             Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments(); | ||||
|             if (actualTypeArguments.length == 2) { | ||||
|                 try { | ||||
|                     // key默认用字符串"key",value按泛型类型设默认值(字符串为"") | ||||
|                     // key默认用字符串"key"、value按泛型类型设默认值(字符串为"") | ||||
|                     Object key = "key"; | ||||
|                     Object value = actualTypeArguments[1] instanceof Class | ||||
|                             ? initializeObject((Class<?>) actualTypeArguments[1]) | ||||
| @ -238,7 +238,7 @@ public class SourceDataGenerator { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取类型默认值:字符串返回空字符串,其他类型按原有逻辑 | ||||
|      * 获取类型默认值:字符串返回空字符串、其他类型按原有逻辑 | ||||
|      */ | ||||
|     private static Object getDefaultValue(Class<?> type) { | ||||
|         if (type.equals(String.class)) { | ||||
|  | ||||
| @ -34,7 +34,7 @@ public class CodeUtil { | ||||
|         } | ||||
|  | ||||
|         // 传入需要生成代码的表名 | ||||
|         Generation("file_info"); | ||||
|         Generation("model"); | ||||
|     } | ||||
|  | ||||
|     public static void Generation(String... tableName) { | ||||
|  | ||||
| @ -5,9 +5,8 @@ import oshi.SystemInfo; | ||||
| import oshi.hardware.*; | ||||
|  | ||||
| import java.security.MessageDigest; | ||||
| import java.security.NoSuchAlgorithmException; | ||||
| import java.util.List; | ||||
| import java.util.HexFormat; | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * 服务器唯一标识工具类 | ||||
|  | ||||
| @ -47,6 +47,8 @@ public class DatabaseManager { | ||||
|         classes.add(Source.class); | ||||
|         classes.add(RoleSource.class); | ||||
|         classes.add(FileInfo.class); | ||||
|         classes.add(ModelType.class); | ||||
|         classes.add(Model.class); | ||||
|         ENTITY_CLASSES = Collections.unmodifiableList(classes); | ||||
|     } | ||||
|  | ||||
|  | ||||
							
								
								
									
										33
									
								
								src/main/java/com/yj/earth/design/Model.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/main/java/com/yj/earth/design/Model.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| package com.yj.earth.design; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
| import java.time.LocalDateTime; | ||||
|  | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| @AllArgsConstructor | ||||
| public class Model { | ||||
|     @Schema(description = "主键") | ||||
|     private String id; | ||||
|     @Schema(description = "模型类型ID") | ||||
|     private String modelTypeId; | ||||
|     @Schema(description = "模型名称") | ||||
|     private String modelName; | ||||
|     @Schema(description = "模型类型") | ||||
|     private String modelType; | ||||
|     @Schema(description = "海报类型") | ||||
|     private String posterType; | ||||
|     @Schema(description = "海报数据") | ||||
|     private byte[] poster; | ||||
|     @Schema(description = "模型数据") | ||||
|     private byte[] data; | ||||
|     @Schema(description = "模型视图") | ||||
|     private String view; | ||||
|     @Schema(description = "创建时间") | ||||
|     private LocalDateTime createdAt; | ||||
|     @Schema(description = "更新时间") | ||||
|     private LocalDateTime updatedAt; | ||||
| } | ||||
							
								
								
									
										20
									
								
								src/main/java/com/yj/earth/design/ModelType.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/main/java/com/yj/earth/design/ModelType.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| package com.yj.earth.design; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.time.LocalDateTime; | ||||
|  | ||||
| @Data | ||||
| public class ModelType { | ||||
|     @Schema(description = "主键") | ||||
|     private String id; | ||||
|     @Schema(description = "模型类型名称") | ||||
|     private String name; | ||||
|     @Schema(description = "父级节点ID") | ||||
|     private String parentId; | ||||
|     @Schema(description = "创建时间") | ||||
|     private LocalDateTime createdAt; | ||||
|     @Schema(description = "更新时间") | ||||
|     private LocalDateTime updatedAt; | ||||
| } | ||||
| @ -0,0 +1,16 @@ | ||||
| package com.yj.earth.dto.source; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| @Data | ||||
| public class UploadLocationImageDto { | ||||
|     @Schema(description = "资源ID列表") | ||||
|     private List<String> ids; | ||||
|     @Schema(description = "父节点ID") | ||||
|     private String parentId; | ||||
|     @Schema(description = "树状索引") | ||||
|     private Integer treeIndex; | ||||
| } | ||||
| @ -31,7 +31,7 @@ public class DiffuseScan { | ||||
|  | ||||
|     @Data | ||||
|     public static class CustomView { | ||||
|         // 空对象,暂无需字段 | ||||
|         // 空对象、暂无需字段 | ||||
|     } | ||||
|  | ||||
|     @Data | ||||
|  | ||||
| @ -6,6 +6,9 @@ import lombok.Data; | ||||
| @Data | ||||
| @SourceType("layer") | ||||
| public class Layer { | ||||
|     private String id; | ||||
|     private String name; | ||||
|     private Boolean show; | ||||
|     private Integer alpha; | ||||
|     private Integer brightness; | ||||
| } | ||||
|  | ||||
| @ -22,7 +22,7 @@ public class RadarScan { | ||||
|  | ||||
|     @Data | ||||
|     public static class CustomView { | ||||
|         // 空对象,暂无需字段 | ||||
|         // 空对象、暂无需字段 | ||||
|     } | ||||
|  | ||||
|     @Data | ||||
|  | ||||
							
								
								
									
										35
									
								
								src/main/java/com/yj/earth/params/Roam.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/main/java/com/yj/earth/params/Roam.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | ||||
| package com.yj.earth.params; | ||||
|  | ||||
| import com.yj.earth.annotation.SourceType; | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| @Data | ||||
| @SourceType("roam") | ||||
| public class Roam { | ||||
|     private String name; | ||||
|     private List<Point> points; | ||||
|     private String repeat; | ||||
|  | ||||
|     @Data | ||||
|     public static class Point { | ||||
|         private int duration; | ||||
|         private Position position; | ||||
|         private Orientation orientation; | ||||
|     } | ||||
|  | ||||
|     @Data | ||||
|     public static class Position { | ||||
|         private double lng; | ||||
|         private double lat; | ||||
|         private double alt; | ||||
|     } | ||||
|  | ||||
|     @Data | ||||
|     public static class Orientation { | ||||
|         private double heading; | ||||
|         private double pitch; | ||||
|         private double roll; | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 ZZX9599
					ZZX9599