模型库
This commit is contained in:
@ -4,17 +4,22 @@ 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.lang.Rational;
|
||||
import com.drew.metadata.Directory;
|
||||
import com.drew.metadata.Metadata;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.*;
|
||||
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.drew.metadata.MetadataException;
|
||||
import com.drew.metadata.exif.GpsDirectory;
|
||||
import com.yj.earth.business.domain.FileInfo;
|
||||
import com.yj.earth.business.service.FileInfoService;
|
||||
import com.yj.earth.common.util.ApiResponse;
|
||||
@ -46,41 +51,22 @@ public class FileInfoController {
|
||||
@Resource
|
||||
private FileInfoService fileInfoService;
|
||||
|
||||
@Value("${file.upload.path}")
|
||||
private String uploadPath;
|
||||
|
||||
// 获取项目根目录
|
||||
private String getProjectRootPath() {
|
||||
return System.getProperty("user.dir");
|
||||
}
|
||||
|
||||
// 获取完整的上传目录路径
|
||||
private String getFullUploadPath() {
|
||||
// 拼接项目根目录和配置的上传路径
|
||||
return getProjectRootPath() + File.separator + uploadPath;
|
||||
}
|
||||
|
||||
@Operation(summary = "文件上传")
|
||||
@PostMapping("/upload")
|
||||
public ApiResponse uploadFiles(@Parameter(description = "上传的文件数组", required = true) @RequestParam("files") MultipartFile[] files) throws IOException {
|
||||
|
||||
// 校验文件数组是否为空
|
||||
if (files == null || files.length == 0) {
|
||||
return ApiResponse.failure("上传文件不能为空");
|
||||
}
|
||||
|
||||
// 获取完整的上传目录路径
|
||||
String fullUploadPath = getFullUploadPath();
|
||||
|
||||
String fullUploadPath = fileInfoService.getFullUploadPath();
|
||||
List<FileInfoVo> fileInfoVoList = new ArrayList<>();
|
||||
|
||||
// 遍历处理每个文件
|
||||
for (MultipartFile file : files) {
|
||||
// 跳过空文件
|
||||
if (file.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取原始文件名和后缀
|
||||
String originalFilename = file.getOriginalFilename();
|
||||
String fileSuffix = FileUtil.extName(originalFilename);
|
||||
@ -106,7 +92,7 @@ public class FileInfoController {
|
||||
// 查询有没有文件名一样并且 MD5 也一样的数据
|
||||
LambdaQueryWrapper<FileInfo> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(FileInfo::getFileName, originalFilename).eq(FileInfo::getFileMd5, fileMd5);
|
||||
if (fileInfoService.count(queryWrapper) > 0) { // 修复了此处的bug、添加了queryWrapper参数
|
||||
if (fileInfoService.count(queryWrapper) > 0) {
|
||||
return ApiResponse.failure("已存在文件名相同且内容完全一致的文件");
|
||||
}
|
||||
|
||||
@ -116,7 +102,7 @@ public class FileInfoController {
|
||||
fileInfo.setFileSuffix(fileSuffix);
|
||||
fileInfo.setContentType(contentType);
|
||||
fileInfo.setFileSize(file.getSize());
|
||||
fileInfo.setFilePath(uniqueFileName); // 只保存相对文件名、不保存完整路径
|
||||
fileInfo.setFilePath(uniqueFileName);
|
||||
fileInfo.setFileMd5(fileMd5);
|
||||
|
||||
// 保存文件信息并获取ID
|
||||
@ -152,7 +138,7 @@ public class FileInfoController {
|
||||
}
|
||||
|
||||
// 构建完整文件路径
|
||||
String fullPath = getFullUploadPath() + File.separator + fileInfo.getFilePath();
|
||||
String fullPath = fileInfoService.getFullUploadPath() + File.separator + fileInfo.getFilePath();
|
||||
File file = new File(fullPath);
|
||||
|
||||
// 校验文件是否存在
|
||||
@ -185,7 +171,7 @@ public class FileInfoController {
|
||||
}
|
||||
|
||||
// 构建完整文件路径
|
||||
String fullPath = getFullUploadPath() + File.separator + fileInfo.getFilePath();
|
||||
String fullPath = fileInfoService.getFullUploadPath() + File.separator + fileInfo.getFilePath();
|
||||
File file = new File(fullPath);
|
||||
|
||||
// 校验文件是否存在
|
||||
@ -204,100 +190,59 @@ public class FileInfoController {
|
||||
}
|
||||
}
|
||||
|
||||
public String handleLocationImageUpload(MultipartFile file) {
|
||||
@Operation(summary = "本地文件预览")
|
||||
@GetMapping("/previewLocal")
|
||||
public void previewLocalFile(@Parameter(description = "本地文件绝对路径") @RequestParam String fileAbsolutePath, HttpServletResponse response) {
|
||||
Path targetFilePath = null;
|
||||
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 JsonUtil.mapToJson(result);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("文件上传失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
// 标准化路径
|
||||
targetFilePath = Paths.get(fileAbsolutePath).toRealPath();
|
||||
|
||||
/**
|
||||
* 计算文件的 MD5
|
||||
*/
|
||||
private String calculateFileMd5(File file) throws IOException {
|
||||
try (InputStream is = new FileInputStream(file)) {
|
||||
return DigestUtils.md5DigestAsHex(is);
|
||||
}
|
||||
}
|
||||
// 校验文件合法性:是否存在、是否为普通文件
|
||||
BasicFileAttributes fileAttr = Files.readAttributes(targetFilePath, BasicFileAttributes.class);
|
||||
if (!fileAttr.isRegularFile()) {
|
||||
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
||||
fileInfoService.writeResponseMessage(response, "目标路径不是有效的文件");
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取图片的EXIF元数据(包括定位信息)
|
||||
*/
|
||||
private Map<String, Object> extractImageMetadata(InputStream inputStream) {
|
||||
try {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
Metadata metadata = ImageMetadataReader.readMetadata(inputStream);
|
||||
// 设置预览响应头
|
||||
String fileName = targetFilePath.getFileName().toString();
|
||||
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
|
||||
String contentType = Files.probeContentType(targetFilePath);
|
||||
|
||||
// 遍历所有元数据目录
|
||||
for (Directory directory : metadata.getDirectories()) {
|
||||
String directoryName = directory.getName();
|
||||
Map<String, String> directoryTags = new HashMap<>();
|
||||
// 对于文本类型文件、指定字符编码
|
||||
if (contentType != null && contentType.startsWith("text/")) {
|
||||
response.setContentType(contentType + "; charset=UTF-8");
|
||||
} else {
|
||||
response.setContentType(contentType != null ? contentType : "application/octet-stream");
|
||||
}
|
||||
|
||||
// 提取当前目录下的所有标签
|
||||
for (com.drew.metadata.Tag tag : directory.getTags()) {
|
||||
directoryTags.put(tag.getTagName(), tag.getDescription());
|
||||
}
|
||||
|
||||
// 存储当前目录的所有标签
|
||||
if (!directoryTags.isEmpty()) {
|
||||
result.put(directoryName, directoryTags);
|
||||
response.setContentLengthLong(fileAttr.size());
|
||||
// 关键修改:将attachment改为inline实现预览
|
||||
response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
|
||||
"inline; filename=\"" + encodedFileName + "\"; filename*=UTF-8''" + encodedFileName);
|
||||
|
||||
// 写入文件流
|
||||
try (InputStream inputStream = Files.newInputStream(targetFilePath);
|
||||
OutputStream outputStream = response.getOutputStream()) {
|
||||
byte[] buffer = new byte[1024 * 8];
|
||||
int len;
|
||||
while ((len = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, len);
|
||||
}
|
||||
outputStream.flush();
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (ImageProcessingException | IOException e) {
|
||||
return Collections.emptyMap();
|
||||
} catch (NoSuchFileException e) {
|
||||
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||
fileInfoService.writeResponseMessage(response, "文件不存在:" + fileAbsolutePath);
|
||||
} catch (SecurityException e) {
|
||||
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
||||
fileInfoService.writeResponseMessage(response, "访问拒绝:无权限读取该文件");
|
||||
} catch (Exception e) {
|
||||
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
fileInfoService.writeResponseMessage(response, "预览失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,65 +0,0 @@
|
||||
package com.yj.earth.business.controller;
|
||||
|
||||
import com.yj.earth.business.service.ModelService;
|
||||
import com.yj.earth.business.service.ModelTypeService;
|
||||
import com.yj.earth.common.util.ApiResponse;
|
||||
import com.yj.earth.datasource.DatabaseManager;
|
||||
import com.yj.earth.design.Model;
|
||||
import com.yj.earth.design.ModelType;
|
||||
import com.yj.earth.dto.model.CreateModelFileDto;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
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;
|
||||
import java.io.File;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
|
||||
@Tag(name = "模型相关")
|
||||
@RestController
|
||||
@RequestMapping("/model")
|
||||
public class ModelController {
|
||||
@Resource
|
||||
private ModelService modelService;
|
||||
@Resource
|
||||
private ModelTypeService modelTypeService;
|
||||
|
||||
@Operation(summary = "创建模型库")
|
||||
@PostMapping("/createModelFile")
|
||||
public ApiResponse createModelFile(@RequestBody CreateModelFileDto createModelFileDto) {
|
||||
try {
|
||||
// 获取参数并处理文件路径
|
||||
String folderPath = createModelFileDto.getFolderPath();
|
||||
String modelFileName = createModelFileDto.getModelFileName() + ".model";
|
||||
// 创建文件夹(如果不存在)
|
||||
File folder = new File(folderPath);
|
||||
if (!folder.exists()) {
|
||||
boolean folderCreated = folder.mkdirs();
|
||||
if (!folderCreated) {
|
||||
return ApiResponse.failure("无法创建文件夹: " + folderPath);
|
||||
}
|
||||
}
|
||||
// 构建完整文件路径
|
||||
String filePath = folderPath + File.separator + modelFileName;
|
||||
// 加载 SQLite 驱动并创建数据库文件
|
||||
Class.forName("org.sqlite.JDBC");
|
||||
// SQLite会自动创建不存在的数据库文件
|
||||
try (Connection connection = DriverManager.getConnection("jdbc:sqlite:" + filePath)) {
|
||||
if (connection != null) {
|
||||
// 初始化表结构
|
||||
DatabaseManager.createTablesForEntities(DatabaseManager.DatabaseType.SQLITE, ModelType.class, connection);
|
||||
DatabaseManager.createTablesForEntities(DatabaseManager.DatabaseType.SQLITE, Model.class, connection);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return ApiResponse.failure("创建模型库失败: " + e.getMessage());
|
||||
}
|
||||
return ApiResponse.failure("未知错误");
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
package com.yj.earth.business.controller;
|
||||
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Tag(name = "模型类型管理")
|
||||
@RestController
|
||||
@RequestMapping("/modelType")
|
||||
public class ModelTypeController {
|
||||
|
||||
}
|
||||
@ -2,20 +2,8 @@ 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.*;
|
||||
import com.yj.earth.common.service.SourceDataGenerator;
|
||||
@ -24,6 +12,7 @@ import com.yj.earth.common.util.ApiResponse;
|
||||
import com.yj.earth.common.util.JsonUtil;
|
||||
import com.yj.earth.common.util.MapUtil;
|
||||
import com.yj.earth.dto.source.*;
|
||||
import com.yj.earth.params.Point;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@ -34,9 +23,6 @@ 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;
|
||||
@ -62,12 +48,6 @@ public class SourceController {
|
||||
@Resource
|
||||
private FileInfoService fileInfoService;
|
||||
|
||||
@Resource
|
||||
private FileInfoController fileInfoControllerl;
|
||||
|
||||
@Value("${file.upload.path}")
|
||||
private String uploadPath;
|
||||
|
||||
@PostMapping("/addDirectory")
|
||||
@Operation(summary = "新增目录资源")
|
||||
public ApiResponse addDirectory(@RequestBody AddDirectoryDto addDirectoryDto) {
|
||||
@ -223,23 +203,75 @@ public class SourceController {
|
||||
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(value = "params", required = false) @Parameter(description = "参数") String params,
|
||||
@RequestParam(value = "sourceType", required = false) @Parameter(description = "资源类型") String sourceType,
|
||||
@RequestParam("files") @Parameter(description = "带有定位的图片文件", required = true) MultipartFile[] files) {
|
||||
// 验证并转换参数
|
||||
sourceParamsValidator.validateAndConvert(
|
||||
sourceType,
|
||||
JsonUtil.jsonToMap(params)
|
||||
);
|
||||
|
||||
List<Source> sources = new ArrayList<>();
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
String detail = fileInfoControllerl.handleLocationImageUpload(files[i]);
|
||||
Map<String, Object> dataMap = fileInfoService.handleLocationImageUpload(files[i]);
|
||||
// 构建并保存资源对象
|
||||
Source source = new Source();
|
||||
source.setId(ids.get(i));
|
||||
source.setSourceName(files[i].getOriginalFilename());
|
||||
source.setParentId(parentId);
|
||||
source.setSourceType("locationImage");
|
||||
source.setSourceType(sourceType);
|
||||
source.setTreeIndex(treeIndex);
|
||||
source.setDetail(detail);
|
||||
|
||||
// 转换为对象
|
||||
Point point = JsonUtil.mapToObject(JsonUtil.jsonToMap(params), Point.class);
|
||||
point.setId(ids.get(i));
|
||||
Point.Position position = new Point.Position();
|
||||
point.setName(files[i].getOriginalFilename());
|
||||
point.getLabel().setText(files[i].getOriginalFilename());
|
||||
Object lonObj = dataMap.get("lon");
|
||||
if (lonObj != null && lonObj instanceof Double) {
|
||||
position.setLng((Double) lonObj);
|
||||
}
|
||||
|
||||
Object latObj = dataMap.get("lat");
|
||||
if (latObj != null && latObj instanceof Double) {
|
||||
position.setLat((Double) latObj);
|
||||
}
|
||||
|
||||
Object altObj = dataMap.get("alt");
|
||||
if (altObj != null && altObj instanceof Double) {
|
||||
position.setAlt((Double) altObj);
|
||||
}
|
||||
point.setPosition(position);
|
||||
|
||||
if ("linkImage".equals(sourceType)) {
|
||||
// 设置地址
|
||||
List<Point.Attribute.Link.LinkContent> list = new ArrayList<>();
|
||||
Point.Attribute.Link.LinkContent linkContent = new Point.Attribute.Link.LinkContent();
|
||||
linkContent.setName("带定位照片");
|
||||
linkContent.setUrl(dataMap.get("url").toString());
|
||||
list.add(linkContent);
|
||||
point.getAttribute().getLink().setContent(list);
|
||||
} else {
|
||||
List<Point.Attribute.Vr.VrContent> list = new ArrayList<>();
|
||||
Point.Attribute.Vr.VrContent vrContent = new Point.Attribute.Vr.VrContent();
|
||||
vrContent.setName("带全景照片");
|
||||
vrContent.setUrl(dataMap.get("url").toString());
|
||||
list.add(vrContent);
|
||||
point.getAttribute().getVr().setContent(list);
|
||||
}
|
||||
|
||||
// 将 vrImage 转化为 JSON
|
||||
source.setParams(JsonUtil.toJson(point));
|
||||
source.setIsShow(SHOW);
|
||||
sourceService.save(source);
|
||||
// 添加资源到该用户的角色下
|
||||
roleSourceService.addRoleSource(userService.getById(StpUtil.getLoginIdAsString()).getRoleId(), source.getId());
|
||||
|
||||
sources.add(source);
|
||||
}
|
||||
return ApiResponse.success(null);
|
||||
return ApiResponse.success(sources);
|
||||
}
|
||||
|
||||
@GetMapping("/type")
|
||||
|
||||
@ -38,7 +38,7 @@ public class TacticalCalculationController {
|
||||
// 同向而行:时间 = 距离 / (速度差)
|
||||
double speedDiff = Math.abs(input.getSpeed1() - input.getSpeed2());
|
||||
if (speedDiff <= 0) {
|
||||
throw new IllegalArgumentException("同向而行时,速度不能相等或后方速度小于前方");
|
||||
throw new IllegalArgumentException("同向而行时、速度不能相等或后方速度小于前方");
|
||||
}
|
||||
meetTimeHours = input.getInitialDistance() / speedDiff;
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user