模型库

This commit is contained in:
ZZX9599
2025-09-22 17:13:22 +08:00
parent adf375648b
commit 521efbafac
40 changed files with 1177 additions and 523 deletions

View File

@ -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());
}
}
}