模型库
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()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 ZZX9599
					ZZX9599