重构云文件管理系统
This commit is contained in:
@@ -5,23 +5,17 @@ import com.filesystem.entity.FileShare;
|
|||||||
import com.filesystem.security.UserPrincipal;
|
import com.filesystem.security.UserPrincipal;
|
||||||
import com.filesystem.service.FileService;
|
import com.filesystem.service.FileService;
|
||||||
import com.filesystem.utils.ApiResult;
|
import com.filesystem.utils.ApiResult;
|
||||||
|
import com.filesystem.utils.CommonUtil;
|
||||||
|
import com.filesystem.utils.FileUtil;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -95,8 +89,8 @@ public class FileController {
|
|||||||
public ApiResult<FileEntity> createFolder(
|
public ApiResult<FileEntity> createFolder(
|
||||||
@AuthenticationPrincipal UserPrincipal principal,
|
@AuthenticationPrincipal UserPrincipal principal,
|
||||||
@RequestBody Map<String, Object> request) {
|
@RequestBody Map<String, Object> request) {
|
||||||
String name = (String) request.get("name");
|
String name = CommonUtil.getString(request, "name");
|
||||||
Long parentId = request.get("parentId") != null ? Long.valueOf(request.get("parentId").toString()) : null;
|
Long parentId = CommonUtil.getLong(request, "parentId");
|
||||||
FileEntity folder = fileService.createFolder(name, principal.getUserId(), parentId);
|
FileEntity folder = fileService.createFolder(name, principal.getUserId(), parentId);
|
||||||
return ApiResult.success("创建成功", folder);
|
return ApiResult.success("创建成功", folder);
|
||||||
}
|
}
|
||||||
@@ -148,7 +142,7 @@ public class FileController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载文件(返回 ResponseEntity<byte[]> 用于二进制响应)
|
* 下载文件
|
||||||
*/
|
*/
|
||||||
@GetMapping("/{id}/download")
|
@GetMapping("/{id}/download")
|
||||||
public ResponseEntity<byte[]> downloadFile(
|
public ResponseEntity<byte[]> downloadFile(
|
||||||
@@ -159,18 +153,14 @@ public class FileController {
|
|||||||
if (file == null) return ResponseEntity.notFound().build();
|
if (file == null) return ResponseEntity.notFound().build();
|
||||||
byte[] content = fileService.getFileContent(file);
|
byte[] content = fileService.getFileContent(file);
|
||||||
if (content == null) return ResponseEntity.notFound().build();
|
if (content == null) return ResponseEntity.notFound().build();
|
||||||
String encodedName = URLEncoder.encode(file.getName(), StandardCharsets.UTF_8).replace("+", "%20");
|
return FileUtil.buildDownloadResponse(content, file.getName());
|
||||||
return ResponseEntity.ok()
|
|
||||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + encodedName + "\"")
|
|
||||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
|
||||||
.body(content);
|
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
return ResponseEntity.badRequest().build();
|
return ResponseEntity.badRequest().build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 预览文件(返回 ResponseEntity<byte[]> 用于二进制响应)
|
* 预览文件
|
||||||
*/
|
*/
|
||||||
@GetMapping("/{id}/preview")
|
@GetMapping("/{id}/preview")
|
||||||
public ResponseEntity<byte[]> previewFile(
|
public ResponseEntity<byte[]> previewFile(
|
||||||
@@ -181,11 +171,7 @@ public class FileController {
|
|||||||
if (file == null) return ResponseEntity.notFound().build();
|
if (file == null) return ResponseEntity.notFound().build();
|
||||||
byte[] content = fileService.getFileContent(file);
|
byte[] content = fileService.getFileContent(file);
|
||||||
if (content == null) return ResponseEntity.notFound().build();
|
if (content == null) return ResponseEntity.notFound().build();
|
||||||
String encodedName = URLEncoder.encode(file.getName(), StandardCharsets.UTF_8).replace("+", "%20");
|
return FileUtil.buildPreviewResponse(content, file.getName());
|
||||||
return ResponseEntity.ok()
|
|
||||||
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + encodedName + "\"")
|
|
||||||
.contentType(getMediaType(file.getName()))
|
|
||||||
.body(content);
|
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
return ResponseEntity.badRequest().build();
|
return ResponseEntity.badRequest().build();
|
||||||
}
|
}
|
||||||
@@ -197,8 +183,8 @@ public class FileController {
|
|||||||
@PathVariable Long id,
|
@PathVariable Long id,
|
||||||
@RequestBody Map<String, Object> request) {
|
@RequestBody Map<String, Object> request) {
|
||||||
try {
|
try {
|
||||||
Long shareToUserId = Long.valueOf(request.get("userId").toString());
|
Long shareToUserId = CommonUtil.getLong(request, "userId");
|
||||||
String permission = (String) request.getOrDefault("permission", "view");
|
String permission = CommonUtil.getString(request, "permission", "view");
|
||||||
FileShare share = fileService.shareFile(id, principal.getUserId(), shareToUserId, permission);
|
FileShare share = fileService.shareFile(id, principal.getUserId(), shareToUserId, permission);
|
||||||
return ApiResult.success("共享成功", share);
|
return ApiResult.success("共享成功", share);
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
@@ -219,43 +205,21 @@ public class FileController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取头像图片(返回 ResponseEntity<byte[]> 用于二进制响应)
|
* 获取头像图片
|
||||||
*/
|
*/
|
||||||
@GetMapping("/avatar/**")
|
@GetMapping("/avatar/**")
|
||||||
public ResponseEntity<byte[]> getAvatar(jakarta.servlet.http.HttpServletRequest request) throws IOException {
|
public ResponseEntity<byte[]> getAvatar(jakarta.servlet.http.HttpServletRequest request) throws IOException {
|
||||||
String uri = request.getRequestURI();
|
String uri = request.getRequestURI();
|
||||||
String prefix = "/api/files/avatar/";
|
String prefix = "/api/files/avatar/";
|
||||||
String relativePath = uri.substring(uri.indexOf(prefix) + prefix.length());
|
String relativePath = uri.substring(uri.indexOf(prefix) + prefix.length());
|
||||||
// relativePath = "2026/04/xxx.jpg"
|
|
||||||
|
|
||||||
Path filePath = Paths.get(storagePath).toAbsolutePath().resolve("avatars").resolve(relativePath);
|
java.nio.file.Path filePath = java.nio.file.Paths.get(storagePath).toAbsolutePath().resolve("avatars").resolve(relativePath);
|
||||||
if (!Files.exists(filePath)) {
|
if (!java.nio.file.Files.exists(filePath)) {
|
||||||
return ResponseEntity.notFound().build();
|
return ResponseEntity.notFound().build();
|
||||||
}
|
}
|
||||||
byte[] content = Files.readAllBytes(filePath);
|
byte[] content = java.nio.file.Files.readAllBytes(filePath);
|
||||||
String fileName = relativePath.contains("/") ? relativePath.substring(relativePath.lastIndexOf("/") + 1) : relativePath;
|
String fileName = relativePath.contains("/") ? relativePath.substring(relativePath.lastIndexOf("/") + 1) : relativePath;
|
||||||
String ext = fileName.contains(".") ? fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase() : "jpg";
|
return FileUtil.buildImageResponse(content, fileName);
|
||||||
String contentType = switch (ext) {
|
|
||||||
case "png" -> "image/png";
|
|
||||||
case "gif" -> "image/gif";
|
|
||||||
case "webp" -> "image/webp";
|
|
||||||
default -> "image/jpeg";
|
|
||||||
};
|
|
||||||
return ResponseEntity.ok()
|
|
||||||
.contentType(org.springframework.http.MediaType.parseMediaType(contentType))
|
|
||||||
.header(HttpHeaders.CACHE_CONTROL, "max-age=86400")
|
|
||||||
.body(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
private MediaType getMediaType(String filename) {
|
|
||||||
String ext = filename.contains(".") ? filename.substring(filename.lastIndexOf(".")).toLowerCase() : "";
|
|
||||||
return switch (ext) {
|
|
||||||
case ".jpg", ".jpeg" -> MediaType.IMAGE_JPEG;
|
|
||||||
case ".png" -> MediaType.IMAGE_PNG;
|
|
||||||
case ".gif" -> MediaType.IMAGE_GIF;
|
|
||||||
case ".pdf" -> MediaType.APPLICATION_PDF;
|
|
||||||
default -> MediaType.APPLICATION_OCTET_STREAM;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/{id}/rename")
|
@PutMapping("/{id}/rename")
|
||||||
@@ -264,7 +228,7 @@ public class FileController {
|
|||||||
@PathVariable Long id,
|
@PathVariable Long id,
|
||||||
@RequestBody Map<String, String> request) {
|
@RequestBody Map<String, String> request) {
|
||||||
String newName = request.get("name");
|
String newName = request.get("name");
|
||||||
if (newName == null || newName.trim().isEmpty()) {
|
if (CommonUtil.isBlank(newName)) {
|
||||||
return ApiResult.error("名称不能为空");
|
return ApiResult.error("名称不能为空");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -280,11 +244,7 @@ public class FileController {
|
|||||||
@AuthenticationPrincipal UserPrincipal principal,
|
@AuthenticationPrincipal UserPrincipal principal,
|
||||||
@PathVariable Long id,
|
@PathVariable Long id,
|
||||||
@RequestBody Map<String, Object> request) {
|
@RequestBody Map<String, Object> request) {
|
||||||
Object folderIdObj = request.get("folderId");
|
Long folderId = CommonUtil.getLong(request, "folderId");
|
||||||
Long folderId = null;
|
|
||||||
if (folderIdObj != null && !folderIdObj.toString().isEmpty() && !"null".equals(folderIdObj.toString()) && !"undefined".equals(folderIdObj.toString())) {
|
|
||||||
folderId = Long.parseLong(folderIdObj.toString());
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
fileService.moveFile(id, principal.getUserId(), folderId);
|
fileService.moveFile(id, principal.getUserId(), folderId);
|
||||||
return ApiResult.success("移动成功");
|
return ApiResult.success("移动成功");
|
||||||
@@ -294,48 +254,27 @@ public class FileController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量下载(返回 ZIP 文件,使用 HttpServletResponse 直接写入)
|
* 批量下载
|
||||||
*/
|
*/
|
||||||
@PostMapping("/batchDownload")
|
@PostMapping("/batchDownload")
|
||||||
public void batchDownload(
|
public void batchDownload(
|
||||||
@AuthenticationPrincipal UserPrincipal principal,
|
@AuthenticationPrincipal UserPrincipal principal,
|
||||||
@RequestBody Map<String, Object> request,
|
@RequestBody Map<String, Object> request,
|
||||||
HttpServletResponse response) throws IOException {
|
HttpServletResponse response) throws IOException {
|
||||||
Object idsObj = request.get("ids");
|
List<Long> ids = CommonUtil.parseIds(request.get("ids"));
|
||||||
if (idsObj == null) {
|
|
||||||
response.setContentType("application/json;charset=UTF-8");
|
|
||||||
response.getWriter().write("{\"code\":400,\"message\":\"请选择要下载的文件\"}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Long> ids = new ArrayList<>();
|
|
||||||
if (idsObj instanceof List) {
|
|
||||||
for (Object id : (List<?>) idsObj) {
|
|
||||||
if (id instanceof Number) {
|
|
||||||
ids.add(((Number) id).longValue());
|
|
||||||
} else if (id instanceof String) {
|
|
||||||
ids.add(Long.parseLong((String) id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ids.isEmpty()) {
|
if (ids.isEmpty()) {
|
||||||
response.setContentType("application/json;charset=UTF-8");
|
com.filesystem.utils.ServletUtil.writeErrorJson(response, 400, "请选择要下载的文件");
|
||||||
response.getWriter().write("{\"code\":400,\"message\":\"请选择要下载的文件\"}");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
byte[] zipBytes = fileService.createZipArchive(ids, principal.getUserId());
|
byte[] zipBytes = fileService.createZipArchive(ids, principal.getUserId());
|
||||||
|
com.filesystem.utils.ServletUtil.setZipDownloadHeaders(response, zipBytes.length);
|
||||||
response.setContentType("application/zip");
|
|
||||||
response.setHeader("Content-Disposition", "attachment; filename=\"download.zip\"");
|
|
||||||
response.setContentLength(zipBytes.length);
|
|
||||||
response.getOutputStream().write(zipBytes);
|
response.getOutputStream().write(zipBytes);
|
||||||
response.getOutputStream().flush();
|
response.getOutputStream().flush();
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
response.setContentType("application/json;charset=UTF-8");
|
com.filesystem.utils.ServletUtil.writeErrorJson(response, 400, e.getMessage());
|
||||||
response.getWriter().write("{\"code\":400,\"message\":\"" + e.getMessage().replace("\"", "\\\"") + "\"}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,20 +291,10 @@ public class FileController {
|
|||||||
public ApiResult<Void> batchCancelShare(
|
public ApiResult<Void> batchCancelShare(
|
||||||
@AuthenticationPrincipal UserPrincipal principal,
|
@AuthenticationPrincipal UserPrincipal principal,
|
||||||
@RequestBody Map<String, Object> request) {
|
@RequestBody Map<String, Object> request) {
|
||||||
Object idsObj = request.get("ids");
|
List<Long> ids = CommonUtil.parseIds(request.get("ids"));
|
||||||
if (idsObj == null) {
|
if (ids.isEmpty()) {
|
||||||
return ApiResult.error("请选择要取消共享的文件");
|
return ApiResult.error("请选择要取消共享的文件");
|
||||||
}
|
}
|
||||||
List<Long> ids = new ArrayList<>();
|
|
||||||
if (idsObj instanceof List) {
|
|
||||||
for (Object id : (List<?>) idsObj) {
|
|
||||||
if (id instanceof Number) {
|
|
||||||
ids.add(((Number) id).longValue());
|
|
||||||
} else if (id instanceof String) {
|
|
||||||
ids.add(Long.parseLong((String) id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (Long fileId : ids) {
|
for (Long fileId : ids) {
|
||||||
fileService.cancelShare(fileId, principal.getUserId());
|
fileService.cancelShare(fileId, principal.getUserId());
|
||||||
}
|
}
|
||||||
@@ -376,20 +305,10 @@ public class FileController {
|
|||||||
public ApiResult<Void> batchRestore(
|
public ApiResult<Void> batchRestore(
|
||||||
@AuthenticationPrincipal UserPrincipal principal,
|
@AuthenticationPrincipal UserPrincipal principal,
|
||||||
@RequestBody Map<String, Object> request) {
|
@RequestBody Map<String, Object> request) {
|
||||||
Object idsObj = request.get("ids");
|
List<Long> ids = CommonUtil.parseIds(request.get("ids"));
|
||||||
if (idsObj == null) {
|
if (ids.isEmpty()) {
|
||||||
return ApiResult.error("请选择要还原的文件");
|
return ApiResult.error("请选择要还原的文件");
|
||||||
}
|
}
|
||||||
List<Long> ids = new ArrayList<>();
|
|
||||||
if (idsObj instanceof List) {
|
|
||||||
for (Object id : (List<?>) idsObj) {
|
|
||||||
if (id instanceof Number) {
|
|
||||||
ids.add(((Number) id).longValue());
|
|
||||||
} else if (id instanceof String) {
|
|
||||||
ids.add(Long.parseLong((String) id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (Long fileId : ids) {
|
for (Long fileId : ids) {
|
||||||
fileService.restoreFile(fileId, principal.getUserId());
|
fileService.restoreFile(fileId, principal.getUserId());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,24 +6,22 @@ import com.filesystem.security.UserPrincipal;
|
|||||||
import com.filesystem.service.MessageService;
|
import com.filesystem.service.MessageService;
|
||||||
import com.filesystem.service.UserService;
|
import com.filesystem.service.UserService;
|
||||||
import com.filesystem.utils.ApiResult;
|
import com.filesystem.utils.ApiResult;
|
||||||
|
import com.filesystem.utils.CommonUtil;
|
||||||
|
import com.filesystem.utils.FileUtil;
|
||||||
|
import com.filesystem.utils.ServletUtil;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
@@ -41,8 +39,6 @@ public class MessageController {
|
|||||||
@Value("${file.storage.path:./uploads}")
|
@Value("${file.storage.path:./uploads}")
|
||||||
private String storagePath;
|
private String storagePath;
|
||||||
|
|
||||||
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy/MM");
|
|
||||||
|
|
||||||
// ==================== 聊天文件上传 ====================
|
// ==================== 聊天文件上传 ====================
|
||||||
|
|
||||||
@PostMapping("/upload")
|
@PostMapping("/upload")
|
||||||
@@ -55,10 +51,9 @@ public class MessageController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String originalName = file.getOriginalFilename();
|
String originalName = file.getOriginalFilename();
|
||||||
String ext = originalName != null && originalName.contains(".")
|
String ext = FileUtil.getExtensionWithDot(originalName);
|
||||||
? originalName.substring(originalName.lastIndexOf(".")) : "";
|
|
||||||
String storedName = UUID.randomUUID().toString() + ext;
|
String storedName = UUID.randomUUID().toString() + ext;
|
||||||
String datePath = LocalDateTime.now().format(DATE_FMT);
|
String datePath = CommonUtil.getDatePath();
|
||||||
|
|
||||||
Path targetDir = Paths.get(storagePath).toAbsolutePath().resolve("chat").resolve(datePath);
|
Path targetDir = Paths.get(storagePath).toAbsolutePath().resolve("chat").resolve(datePath);
|
||||||
if (!Files.exists(targetDir)) {
|
if (!Files.exists(targetDir)) {
|
||||||
@@ -72,7 +67,7 @@ public class MessageController {
|
|||||||
return ApiResult.success("上传成功", Map.of("url", fileUrl));
|
return ApiResult.success("上传成功", Map.of("url", fileUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 聊天文件访问(返回 ResponseEntity<byte[]> 用于二进制响应)====================
|
// ==================== 聊天文件访问 ====================
|
||||||
|
|
||||||
@GetMapping("/file/**")
|
@GetMapping("/file/**")
|
||||||
public ResponseEntity<byte[]> getChatFile(HttpServletRequest request) throws IOException {
|
public ResponseEntity<byte[]> getChatFile(HttpServletRequest request) throws IOException {
|
||||||
@@ -80,7 +75,6 @@ public class MessageController {
|
|||||||
String prefix = "/api/messages/file/";
|
String prefix = "/api/messages/file/";
|
||||||
String relativePath = uri.substring(uri.indexOf(prefix) + prefix.length());
|
String relativePath = uri.substring(uri.indexOf(prefix) + prefix.length());
|
||||||
|
|
||||||
// relativePath = "2026/04/xxx.jpg"
|
|
||||||
int lastSlash = relativePath.lastIndexOf('/');
|
int lastSlash = relativePath.lastIndexOf('/');
|
||||||
String datePath = relativePath.substring(0, lastSlash);
|
String datePath = relativePath.substring(0, lastSlash);
|
||||||
String fileName = relativePath.substring(lastSlash + 1);
|
String fileName = relativePath.substring(lastSlash + 1);
|
||||||
@@ -93,31 +87,11 @@ public class MessageController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
byte[] content = Files.readAllBytes(filePath);
|
byte[] content = Files.readAllBytes(filePath);
|
||||||
String ext = fileName.contains(".") ? fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase() : "";
|
boolean isImage = FileUtil.isImage(fileName);
|
||||||
|
|
||||||
boolean isImage = Set.of("png", "jpg", "jpeg", "gif", "webp", "bmp", "svg").contains(ext);
|
return isImage
|
||||||
|
? FileUtil.buildImageResponse(content, fileName)
|
||||||
String contentType = switch (ext) {
|
: FileUtil.buildDownloadResponse(content, fileName);
|
||||||
case "pdf" -> "application/pdf";
|
|
||||||
case "png" -> "image/png";
|
|
||||||
case "gif" -> "image/gif";
|
|
||||||
case "webp" -> "image/webp";
|
|
||||||
case "jpg", "jpeg" -> "image/jpeg";
|
|
||||||
case "bmp" -> "image/bmp";
|
|
||||||
case "svg" -> "image/svg+xml";
|
|
||||||
default -> "application/octet-stream";
|
|
||||||
};
|
|
||||||
|
|
||||||
String encodedName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replace("+", "%20");
|
|
||||||
String disposition = isImage
|
|
||||||
? "inline; filename=\"" + encodedName + "\""
|
|
||||||
: "attachment; filename=\"" + encodedName + "\"";
|
|
||||||
|
|
||||||
return ResponseEntity.ok()
|
|
||||||
.contentType(MediaType.parseMediaType(contentType))
|
|
||||||
.header(HttpHeaders.CONTENT_DISPOSITION, disposition)
|
|
||||||
.header(HttpHeaders.CACHE_CONTROL, "max-age=86400")
|
|
||||||
.body(content);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 消息收发 ====================
|
// ==================== 消息收发 ====================
|
||||||
@@ -158,7 +132,7 @@ public class MessageController {
|
|||||||
m.put("fileName", msg.getFileName());
|
m.put("fileName", msg.getFileName());
|
||||||
m.put("fileSize", msg.getFileSize());
|
m.put("fileSize", msg.getFileSize());
|
||||||
m.put("isRead", msg.getIsRead());
|
m.put("isRead", msg.getIsRead());
|
||||||
m.put("createTime", msg.getCreateTime() != null ? msg.getCreateTime().toString() : "");
|
m.put("createTime", CommonUtil.formatDateTime(msg.getCreateTime()));
|
||||||
|
|
||||||
User fromUser = userMap.get(msg.getFromUserId());
|
User fromUser = userMap.get(msg.getFromUserId());
|
||||||
if (fromUser != null) {
|
if (fromUser != null) {
|
||||||
@@ -196,12 +170,11 @@ public class MessageController {
|
|||||||
public ApiResult<Map<String, Object>> sendMessage(
|
public ApiResult<Map<String, Object>> sendMessage(
|
||||||
@AuthenticationPrincipal UserPrincipal principal,
|
@AuthenticationPrincipal UserPrincipal principal,
|
||||||
@RequestBody Map<String, Object> request) {
|
@RequestBody Map<String, Object> request) {
|
||||||
Long toUserId = Long.valueOf(request.get("toUserId").toString());
|
Long toUserId = CommonUtil.getLong(request, "toUserId");
|
||||||
String content = (String) request.get("content");
|
String content = CommonUtil.getString(request, "content");
|
||||||
String type = (String) request.getOrDefault("type", "text");
|
String type = CommonUtil.getString(request, "type", "text");
|
||||||
String fileName = (String) request.get("fileName");
|
String fileName = CommonUtil.getString(request, "fileName");
|
||||||
Object fileSizeObj = request.get("fileSize");
|
Long fileSize = CommonUtil.getLong(request, "fileSize");
|
||||||
Long fileSize = fileSizeObj != null ? Long.valueOf(fileSizeObj.toString()) : null;
|
|
||||||
|
|
||||||
Message message = messageService.sendMessage(principal.getUserId(), toUserId, content, type);
|
Message message = messageService.sendMessage(principal.getUserId(), toUserId, content, type);
|
||||||
|
|
||||||
@@ -219,7 +192,7 @@ public class MessageController {
|
|||||||
result.put("type", message.getType());
|
result.put("type", message.getType());
|
||||||
result.put("fileName", message.getFileName());
|
result.put("fileName", message.getFileName());
|
||||||
result.put("fileSize", message.getFileSize());
|
result.put("fileSize", message.getFileSize());
|
||||||
result.put("createTime", message.getCreateTime() != null ? message.getCreateTime().toString() : "");
|
result.put("createTime", CommonUtil.formatDateTime(message.getCreateTime()));
|
||||||
|
|
||||||
User fromUser = userService.findById(principal.getUserId());
|
User fromUser = userService.findById(principal.getUserId());
|
||||||
if (fromUser != null) {
|
if (fromUser != null) {
|
||||||
@@ -241,7 +214,6 @@ public class MessageController {
|
|||||||
public ApiResult<List<Map<String, Object>>> getUnreadList(@AuthenticationPrincipal UserPrincipal principal) {
|
public ApiResult<List<Map<String, Object>>> getUnreadList(@AuthenticationPrincipal UserPrincipal principal) {
|
||||||
List<Message> unreadMessages = messageService.getUnreadMessages(principal.getUserId());
|
List<Message> unreadMessages = messageService.getUnreadMessages(principal.getUserId());
|
||||||
|
|
||||||
// 按发送人分组
|
|
||||||
Map<Long, List<Message>> grouped = unreadMessages.stream()
|
Map<Long, List<Message>> grouped = unreadMessages.stream()
|
||||||
.collect(Collectors.groupingBy(Message::getFromUserId, LinkedHashMap::new, Collectors.toList()));
|
.collect(Collectors.groupingBy(Message::getFromUserId, LinkedHashMap::new, Collectors.toList()));
|
||||||
|
|
||||||
@@ -249,7 +221,7 @@ public class MessageController {
|
|||||||
for (Map.Entry<Long, List<Message>> entry : grouped.entrySet()) {
|
for (Map.Entry<Long, List<Message>> entry : grouped.entrySet()) {
|
||||||
Long fromUserId = entry.getKey();
|
Long fromUserId = entry.getKey();
|
||||||
List<Message> msgs = entry.getValue();
|
List<Message> msgs = entry.getValue();
|
||||||
Message lastMsg = msgs.get(0); // 已按时间倒序,第一条就是最新的
|
Message lastMsg = msgs.get(0);
|
||||||
User fromUser = userService.findById(fromUserId);
|
User fromUser = userService.findById(fromUserId);
|
||||||
|
|
||||||
Map<String, Object> item = new HashMap<>();
|
Map<String, Object> item = new HashMap<>();
|
||||||
@@ -259,7 +231,7 @@ public class MessageController {
|
|||||||
? "[图片]" : (lastMsg.getType() != null && lastMsg.getType().equals("file")
|
? "[图片]" : (lastMsg.getType() != null && lastMsg.getType().equals("file")
|
||||||
? "[文件]" : (lastMsg.getContent() != null && lastMsg.getContent().length() > 30
|
? "[文件]" : (lastMsg.getContent() != null && lastMsg.getContent().length() > 30
|
||||||
? lastMsg.getContent().substring(0, 30) + "..." : lastMsg.getContent())));
|
? lastMsg.getContent().substring(0, 30) + "..." : lastMsg.getContent())));
|
||||||
item.put("lastTime", lastMsg.getCreateTime() != null ? lastMsg.getCreateTime().toString() : "");
|
item.put("lastTime", CommonUtil.formatDateTime(lastMsg.getCreateTime()));
|
||||||
if (fromUser != null) {
|
if (fromUser != null) {
|
||||||
item.put("username", fromUser.getUsername());
|
item.put("username", fromUser.getUsername());
|
||||||
item.put("nickname", fromUser.getNickname() != null ? fromUser.getNickname() : fromUser.getUsername());
|
item.put("nickname", fromUser.getNickname() != null ? fromUser.getNickname() : fromUser.getUsername());
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import com.filesystem.entity.User;
|
|||||||
import com.filesystem.security.UserPrincipal;
|
import com.filesystem.security.UserPrincipal;
|
||||||
import com.filesystem.service.UserService;
|
import com.filesystem.service.UserService;
|
||||||
import com.filesystem.utils.ApiResult;
|
import com.filesystem.utils.ApiResult;
|
||||||
|
import com.filesystem.utils.CommonUtil;
|
||||||
|
import com.filesystem.utils.FileUtil;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
@@ -14,8 +16,6 @@ import java.io.IOException;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -29,8 +29,6 @@ public class UserController {
|
|||||||
@Value("${file.storage.path:./uploads}")
|
@Value("${file.storage.path:./uploads}")
|
||||||
private String storagePath;
|
private String storagePath;
|
||||||
|
|
||||||
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy/MM");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有可用用户(用于文件共享等场景)
|
* 获取所有可用用户(用于文件共享等场景)
|
||||||
*/
|
*/
|
||||||
@@ -38,7 +36,7 @@ public class UserController {
|
|||||||
public ApiResult<List<Map<String, Object>>> getAllUsers(@AuthenticationPrincipal UserPrincipal principal) {
|
public ApiResult<List<Map<String, Object>>> getAllUsers(@AuthenticationPrincipal UserPrincipal principal) {
|
||||||
List<User> users = userService.getAllUsersExcept(principal.getUserId());
|
List<User> users = userService.getAllUsersExcept(principal.getUserId());
|
||||||
List<Map<String, Object>> result = users.stream()
|
List<Map<String, Object>> result = users.stream()
|
||||||
.filter(u -> u.getStatus() == 1) // 只返回启用状态的用户
|
.filter(u -> u.getStatus() == 1)
|
||||||
.map(u -> {
|
.map(u -> {
|
||||||
Map<String, Object> m = new HashMap<>();
|
Map<String, Object> m = new HashMap<>();
|
||||||
m.put("id", u.getId());
|
m.put("id", u.getId());
|
||||||
@@ -64,20 +62,14 @@ public class UserController {
|
|||||||
return ApiResult.error("请选择图片");
|
return ApiResult.error("请选择图片");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 限制文件大小 2MB
|
|
||||||
if (file.getSize() > 2 * 1024 * 1024) {
|
if (file.getSize() > 2 * 1024 * 1024) {
|
||||||
return ApiResult.error("图片大小不能超过2MB");
|
return ApiResult.error("图片大小不能超过2MB");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存文件
|
String ext = FileUtil.getExtensionWithDot(file.getOriginalFilename());
|
||||||
String originalFilename = file.getOriginalFilename();
|
|
||||||
String ext = originalFilename != null && originalFilename.contains(".")
|
|
||||||
? originalFilename.substring(originalFilename.lastIndexOf("."))
|
|
||||||
: ".jpg";
|
|
||||||
String fileName = UUID.randomUUID().toString() + ext;
|
String fileName = UUID.randomUUID().toString() + ext;
|
||||||
String datePath = LocalDateTime.now().format(DATE_FMT);
|
String datePath = CommonUtil.getDatePath();
|
||||||
|
|
||||||
// 使用配置文件中的路径 + 日期目录
|
|
||||||
Path uploadPath = Paths.get(storagePath).toAbsolutePath().resolve("avatars").resolve(datePath);
|
Path uploadPath = Paths.get(storagePath).toAbsolutePath().resolve("avatars").resolve(datePath);
|
||||||
if (!Files.exists(uploadPath)) {
|
if (!Files.exists(uploadPath)) {
|
||||||
Files.createDirectories(uploadPath);
|
Files.createDirectories(uploadPath);
|
||||||
@@ -86,7 +78,6 @@ public class UserController {
|
|||||||
Path filePath = uploadPath.resolve(fileName);
|
Path filePath = uploadPath.resolve(fileName);
|
||||||
Files.copy(file.getInputStream(), filePath);
|
Files.copy(file.getInputStream(), filePath);
|
||||||
|
|
||||||
// 更新用户头像
|
|
||||||
String avatarUrl = "/api/files/avatar/" + datePath + "/" + fileName;
|
String avatarUrl = "/api/files/avatar/" + datePath + "/" + fileName;
|
||||||
userService.updateAvatar(principal.getUserId(), avatarUrl);
|
userService.updateAvatar(principal.getUserId(), avatarUrl);
|
||||||
|
|
||||||
@@ -118,11 +109,13 @@ public class UserController {
|
|||||||
@AuthenticationPrincipal UserPrincipal principal,
|
@AuthenticationPrincipal UserPrincipal principal,
|
||||||
@RequestBody Map<String, String> request) {
|
@RequestBody Map<String, String> request) {
|
||||||
try {
|
try {
|
||||||
String nickname = request.get("nickname");
|
userService.updateProfile(
|
||||||
String signature = request.get("signature");
|
principal.getUserId(),
|
||||||
String phone = request.get("phone");
|
request.get("nickname"),
|
||||||
String email = request.get("email");
|
request.get("signature"),
|
||||||
userService.updateProfile(principal.getUserId(), nickname, signature, phone, email);
|
request.get("phone"),
|
||||||
|
request.get("email")
|
||||||
|
);
|
||||||
return ApiResult.success("更新成功");
|
return ApiResult.success("更新成功");
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
return ApiResult.error(e.getMessage());
|
return ApiResult.error(e.getMessage());
|
||||||
@@ -139,7 +132,7 @@ public class UserController {
|
|||||||
String oldPassword = request.get("oldPassword");
|
String oldPassword = request.get("oldPassword");
|
||||||
String newPassword = request.get("newPassword");
|
String newPassword = request.get("newPassword");
|
||||||
|
|
||||||
if (oldPassword == null || oldPassword.isEmpty() || newPassword == null || newPassword.isEmpty()) {
|
if (CommonUtil.isBlank(oldPassword) || CommonUtil.isBlank(newPassword)) {
|
||||||
return ApiResult.error("请填写完整信息");
|
return ApiResult.error("请填写完整信息");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
133
src/main/java/com/filesystem/utils/CommonUtil.java
Normal file
133
src/main/java/com/filesystem/utils/CommonUtil.java
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
package com.filesystem.utils;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用工具类
|
||||||
|
*/
|
||||||
|
public class CommonUtil {
|
||||||
|
|
||||||
|
private CommonUtil() {}
|
||||||
|
|
||||||
|
// ========== 日期格式化 ==========
|
||||||
|
|
||||||
|
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
private static final DateTimeFormatter DATE_PATH_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化日期时间(yyyy-MM-dd HH:mm:ss)
|
||||||
|
*/
|
||||||
|
public static String formatDateTime(LocalDateTime dateTime) {
|
||||||
|
if (dateTime == null) return "";
|
||||||
|
return dateTime.format(DATE_TIME_FORMATTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取日期路径格式(yyyy/MM)用于文件存储路径
|
||||||
|
*/
|
||||||
|
public static String getDatePath() {
|
||||||
|
return LocalDateTime.now().format(DATE_PATH_FORMATTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前时间字符串
|
||||||
|
*/
|
||||||
|
public static String now() {
|
||||||
|
return formatDateTime(LocalDateTime.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== ID 列表解析 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 Map 中解析 ID 列表
|
||||||
|
* 支持 List<Number>、List<String>、Number、String 等多种格式
|
||||||
|
*/
|
||||||
|
public static List<Long> parseIds(Object idsObj) {
|
||||||
|
List<Long> ids = new ArrayList<>();
|
||||||
|
|
||||||
|
if (idsObj == null) {
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idsObj instanceof List) {
|
||||||
|
for (Object id : (List<?>) idsObj) {
|
||||||
|
if (id instanceof Number) {
|
||||||
|
ids.add(((Number) id).longValue());
|
||||||
|
} else if (id instanceof String) {
|
||||||
|
String str = ((String) id).trim();
|
||||||
|
if (!str.isEmpty()) {
|
||||||
|
ids.add(Long.parseLong(str));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (idsObj instanceof Number) {
|
||||||
|
ids.add(((Number) idsObj).longValue());
|
||||||
|
} else if (idsObj instanceof String) {
|
||||||
|
String str = ((String) idsObj).trim();
|
||||||
|
if (!str.isEmpty()) {
|
||||||
|
ids.add(Long.parseLong(str));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从请求体中解析 ID 列表
|
||||||
|
*/
|
||||||
|
public static List<Long> parseIdsFromRequest(Map<String, Object> request, String key) {
|
||||||
|
return parseIds(request.get(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 参数提取 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 Map 中获取 Long 值
|
||||||
|
*/
|
||||||
|
public static Long getLong(Map<String, Object> map, String key) {
|
||||||
|
Object value = map.get(key);
|
||||||
|
if (value == null) return null;
|
||||||
|
if (value instanceof Number) {
|
||||||
|
return ((Number) value).longValue();
|
||||||
|
}
|
||||||
|
String str = value.toString();
|
||||||
|
if (str.isEmpty() || "null".equals(str) || "undefined".equals(str)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Long.parseLong(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 Map 中获取 String 值
|
||||||
|
*/
|
||||||
|
public static String getString(Map<String, Object> map, String key) {
|
||||||
|
Object value = map.get(key);
|
||||||
|
return value != null ? value.toString() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 Map 中获取指定默认值的 String
|
||||||
|
*/
|
||||||
|
public static String getString(Map<String, Object> map, String key, String defaultValue) {
|
||||||
|
String value = getString(map, key);
|
||||||
|
return value != null ? value : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断字符串是否为空或空白
|
||||||
|
*/
|
||||||
|
public static boolean isBlank(String str) {
|
||||||
|
return str == null || str.trim().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断字符串是否不为空
|
||||||
|
*/
|
||||||
|
public static boolean isNotBlank(String str) {
|
||||||
|
return !isBlank(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
161
src/main/java/com/filesystem/utils/FileUtil.java
Normal file
161
src/main/java/com/filesystem/utils/FileUtil.java
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
package com.filesystem.utils;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件相关工具类
|
||||||
|
*/
|
||||||
|
public class FileUtil {
|
||||||
|
|
||||||
|
private FileUtil() {}
|
||||||
|
|
||||||
|
// ========== 文件名编码 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL 编码文件名(用于 Content-Disposition 头)
|
||||||
|
*/
|
||||||
|
public static String encodeFileName(String fileName) {
|
||||||
|
return URLEncoder.encode(fileName, StandardCharsets.UTF_8).replace("+", "%20");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 扩展名提取 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件扩展名(不带点,如 "jpg")
|
||||||
|
*/
|
||||||
|
public static String getExtension(String fileName) {
|
||||||
|
if (fileName == null || !fileName.contains(".")) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件扩展名(带点,如 ".jpg")
|
||||||
|
*/
|
||||||
|
public static String getExtensionWithDot(String fileName) {
|
||||||
|
if (fileName == null || !fileName.contains(".")) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Content-Type 判断 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据文件名获取图片 Content-Type
|
||||||
|
*/
|
||||||
|
public static String getImageContentType(String fileName) {
|
||||||
|
String ext = getExtension(fileName);
|
||||||
|
return switch (ext) {
|
||||||
|
case "png" -> "image/png";
|
||||||
|
case "gif" -> "image/gif";
|
||||||
|
case "webp" -> "image/webp";
|
||||||
|
case "svg" -> "image/svg+xml";
|
||||||
|
case "bmp" -> "image/bmp";
|
||||||
|
case "ico" -> "image/x-icon";
|
||||||
|
default -> "image/jpeg";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据文件名获取 MediaType(用于 Spring ResponseEntity)
|
||||||
|
*/
|
||||||
|
public static MediaType getMediaType(String fileName) {
|
||||||
|
String ext = getExtensionWithDot(fileName);
|
||||||
|
return switch (ext) {
|
||||||
|
case ".jpg", ".jpeg" -> MediaType.IMAGE_JPEG;
|
||||||
|
case ".png" -> MediaType.IMAGE_PNG;
|
||||||
|
case ".gif" -> MediaType.IMAGE_GIF;
|
||||||
|
case ".webp" -> MediaType.parseMediaType("image/webp");
|
||||||
|
case ".pdf" -> MediaType.APPLICATION_PDF;
|
||||||
|
case ".svg" -> MediaType.parseMediaType("image/svg+xml");
|
||||||
|
default -> MediaType.APPLICATION_OCTET_STREAM;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据文件名判断是否为图片
|
||||||
|
*/
|
||||||
|
public static boolean isImage(String fileName) {
|
||||||
|
String ext = getExtension(fileName);
|
||||||
|
return java.util.Set.of("jpg", "jpeg", "png", "gif", "webp", "svg", "bmp", "ico").contains(ext);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 文件类型判断 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据扩展名获取文件类型
|
||||||
|
*/
|
||||||
|
public static String getFileType(String extension) {
|
||||||
|
if (extension == null || extension.isEmpty()) {
|
||||||
|
return "file";
|
||||||
|
}
|
||||||
|
String ext = extension.toLowerCase().replace(".", "");
|
||||||
|
|
||||||
|
// 图片
|
||||||
|
if (java.util.Set.of("jpg", "jpeg", "png", "gif", "webp", "svg", "bmp", "ico").contains(ext)) {
|
||||||
|
return "image";
|
||||||
|
}
|
||||||
|
// 视频
|
||||||
|
if (java.util.Set.of("mp4", "avi", "mov", "wmv", "flv", "mkv", "webm").contains(ext)) {
|
||||||
|
return "video";
|
||||||
|
}
|
||||||
|
// 音频
|
||||||
|
if (java.util.Set.of("mp3", "wav", "flac", "aac", "ogg", "wma").contains(ext)) {
|
||||||
|
return "audio";
|
||||||
|
}
|
||||||
|
// PDF
|
||||||
|
if ("pdf".equals(ext)) {
|
||||||
|
return "pdf";
|
||||||
|
}
|
||||||
|
// 文档
|
||||||
|
if (java.util.Set.of("doc", "docx", "xls", "xlsx", "ppt", "pptx").contains(ext)) {
|
||||||
|
return "document";
|
||||||
|
}
|
||||||
|
// 压缩包
|
||||||
|
if (java.util.Set.of("zip", "rar", "7z", "tar", "gz").contains(ext)) {
|
||||||
|
return "archive";
|
||||||
|
}
|
||||||
|
return "file";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 响应构建 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建文件下载响应
|
||||||
|
*/
|
||||||
|
public static ResponseEntity<byte[]> buildDownloadResponse(byte[] content, String fileName) {
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + encodeFileName(fileName) + "\"")
|
||||||
|
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||||
|
.body(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建文件预览响应
|
||||||
|
*/
|
||||||
|
public static ResponseEntity<byte[]> buildPreviewResponse(byte[] content, String fileName) {
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + encodeFileName(fileName) + "\"")
|
||||||
|
.contentType(getMediaType(fileName))
|
||||||
|
.header(HttpHeaders.CACHE_CONTROL, "max-age=86400")
|
||||||
|
.body(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建图片响应
|
||||||
|
*/
|
||||||
|
public static ResponseEntity<byte[]> buildImageResponse(byte[] content, String fileName) {
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.contentType(MediaType.parseMediaType(getImageContentType(fileName)))
|
||||||
|
.header(HttpHeaders.CACHE_CONTROL, "max-age=86400")
|
||||||
|
.body(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
74
src/main/java/com/filesystem/utils/ServletUtil.java
Normal file
74
src/main/java/com/filesystem/utils/ServletUtil.java
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package com.filesystem.utils;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Servlet 响应工具类
|
||||||
|
*/
|
||||||
|
public class ServletUtil {
|
||||||
|
|
||||||
|
private ServletUtil() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输出 JSON 响应
|
||||||
|
*/
|
||||||
|
public static void writeJson(HttpServletResponse response, int status, String json) throws IOException {
|
||||||
|
response.setStatus(status);
|
||||||
|
response.setContentType("application/json;charset=UTF-8");
|
||||||
|
response.setCharacterEncoding("UTF-8");
|
||||||
|
PrintWriter writer = response.getWriter();
|
||||||
|
writer.write(json);
|
||||||
|
writer.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输出错误 JSON 响应
|
||||||
|
*/
|
||||||
|
public static void writeErrorJson(HttpServletResponse response, int code, String message) throws IOException {
|
||||||
|
String json = String.format("{\"code\":%d,\"message\":\"%s\"}", code, escapeJson(message));
|
||||||
|
writeJson(response, code == 400 ? 400 : code, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输出成功 JSON 响应
|
||||||
|
*/
|
||||||
|
public static void writeSuccessJson(HttpServletResponse response, String message) throws IOException {
|
||||||
|
writeJson(response, 200, String.format("{\"code\":200,\"message\":\"%s\"}", escapeJson(message)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置文件下载响应头
|
||||||
|
*/
|
||||||
|
public static void setDownloadHeaders(HttpServletResponse response, String fileName, long contentLength) {
|
||||||
|
response.setContentType("application/octet-stream");
|
||||||
|
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + FileUtil.encodeFileName(fileName) + "\"");
|
||||||
|
response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(contentLength));
|
||||||
|
response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置 ZIP 下载响应头
|
||||||
|
*/
|
||||||
|
public static void setZipDownloadHeaders(HttpServletResponse response, long contentLength) {
|
||||||
|
response.setContentType("application/zip");
|
||||||
|
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"download.zip\"");
|
||||||
|
response.setContentLengthLong(contentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转义 JSON 字符串中的特殊字符
|
||||||
|
*/
|
||||||
|
public static String escapeJson(String str) {
|
||||||
|
if (str == null) return "";
|
||||||
|
return str.replace("\\", "\\\\")
|
||||||
|
.replace("\"", "\\\"")
|
||||||
|
.replace("\n", "\\n")
|
||||||
|
.replace("\r", "\\r")
|
||||||
|
.replace("\t", "\\t");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user