Files
yjearth/src/main/java/com/yj/earth/business/controller/FileInfoController.java
2025-09-23 16:45:42 +08:00

251 lines
10 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.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.annotation.CheckAuth;
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.JsonUtil;
import com.yj.earth.vo.FileInfoVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
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.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
@Tag(name = "文件数据管理")
@CheckAuth
@RestController
@RequestMapping("/fileInfo")
public class FileInfoController {
@Resource
private FileInfoService fileInfoService;
@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 = fileInfoService.getFullUploadPath();
List<FileInfoVo> fileInfoVoList = new ArrayList<>();
// 遍历处理每个文件
for (MultipartFile file : files) {
// 跳过空文件
if (file.isEmpty()) {
continue;
}
// 获取原始文件名和后缀
String originalFilename = file.getOriginalFilename();
String fileSuffix = FileUtil.extName(originalFilename);
String contentType = file.getContentType();
// 生成唯一文件名、避免重复
String uniqueFileName = IdUtil.simpleUUID() + "." + fileSuffix;
// 创建文件存储目录(如果不存在则自动创建)
File uploadDir = new File(fullUploadPath);
FileUtil.mkdir(uploadDir);
// 构建完整文件路径
String filePath = fullUploadPath + File.separator + uniqueFileName;
File destFile = new File(filePath);
// 使用Hutool工具类保存文件
FileUtil.writeBytes(file.getBytes(), destFile);
// 计算文件MD5用于校验文件完整性
String fileMd5 = DigestUtil.md5Hex(destFile);
// 查询有没有文件名一样并且 MD5 也一样的数据
LambdaQueryWrapper<FileInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(FileInfo::getFileName, originalFilename).eq(FileInfo::getFileMd5, fileMd5);
if (fileInfoService.count(queryWrapper) > 0) {
return ApiResponse.failure("已存在文件名相同且内容完全一致的文件");
}
// 保存文件信息到数据库
FileInfo fileInfo = new FileInfo();
fileInfo.setFileName(originalFilename);
fileInfo.setFileSuffix(fileSuffix);
fileInfo.setContentType(contentType);
fileInfo.setFileSize(file.getSize());
fileInfo.setFilePath(uniqueFileName);
fileInfo.setFileMd5(fileMd5);
// 保存文件信息并获取ID
fileInfoService.save(fileInfo);
// 构建并设置预览URL和下载URL
String previewUrl = "/fileInfo/preview/" + fileInfo.getId();
String downloadUrl = "/fileInfo/download/" + fileInfo.getId();
FileInfoVo fileInfoVo = new FileInfoVo();
BeanUtils.copyProperties(fileInfo, fileInfoVo);
fileInfoVo.setPreviewUrl(previewUrl);
fileInfoVo.setDownloadUrl(downloadUrl);
fileInfoVoList.add(fileInfoVo);
}
if (fileInfoVoList.isEmpty()) {
return ApiResponse.failure("未成功上传任何文件");
}
return ApiResponse.success(fileInfoVoList);
}
@Operation(summary = "文件下载")
@GetMapping("/download/{id}")
public void downloadFile(@Parameter(description = "文件ID", required = true) @PathVariable String id, HttpServletResponse response) throws IOException {
// 根据ID查询文件信息
FileInfo fileInfo = fileInfoService.getById(id);
if (fileInfo == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
// 构建完整文件路径
String fullPath = fileInfoService.getFullUploadPath() + File.separator + fileInfo.getFilePath();
File file = new File(fullPath);
// 校验文件是否存在
if (!file.exists()) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
// 设置响应头
response.setContentType(fileInfo.getContentType());
response.setContentLengthLong(fileInfo.getFileSize());
String encodedFileName = URLEncoder.encode(fileInfo.getFileName(), StandardCharsets.UTF_8.name());
response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + encodedFileName + "\"; filename*=UTF-8''" + encodedFileName);
// 使用 Hutool 工具类复制文件流到响应输出流
try (OutputStream os = response.getOutputStream()) {
FileUtil.writeToStream(file, os);
}
}
@Operation(summary = "文件预览")
@GetMapping("/preview/{id}")
public void previewFile(@Parameter(description = "文件ID", required = true) @PathVariable String id, HttpServletResponse response) throws IOException {
// 根据ID查询文件信息
FileInfo fileInfo = fileInfoService.getById(id);
if (fileInfo == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
// 构建完整文件路径
String fullPath = fileInfoService.getFullUploadPath() + File.separator + fileInfo.getFilePath();
File file = new File(fullPath);
// 校验文件是否存在
if (!file.exists()) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
// 设置响应头(不设置 attachment、让浏览器直接显示
response.setContentType(fileInfo.getContentType());
response.setContentLengthLong(fileInfo.getFileSize());
// 使用 Hutool 工具类复制文件流到响应输出流
try (OutputStream os = response.getOutputStream()) {
FileUtil.writeToStream(file, os);
}
}
@Operation(summary = "本地文件预览")
@GetMapping("/previewLocal")
public void previewLocalFile(@Parameter(description = "本地文件绝对路径") @RequestParam String fileAbsolutePath, HttpServletResponse response) {
Path targetFilePath = null;
try {
// 标准化路径
targetFilePath = Paths.get(fileAbsolutePath).toRealPath();
// 校验文件合法性:是否存在、是否为普通文件
BasicFileAttributes fileAttr = Files.readAttributes(targetFilePath, BasicFileAttributes.class);
if (!fileAttr.isRegularFile()) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
fileInfoService.writeResponseMessage(response, "目标路径不是有效的文件");
return;
}
// 设置预览响应头
String fileName = targetFilePath.getFileName().toString();
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
String contentType = Files.probeContentType(targetFilePath);
// 对于文本类型文件、指定字符编码
if (contentType != null && contentType.startsWith("text/")) {
response.setContentType(contentType + "; charset=UTF-8");
} else {
response.setContentType(contentType != null ? contentType : "application/octet-stream");
}
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();
}
} 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());
}
}
}