diff --git a/src/main/java/com/filesystem/controller/FileController.java b/src/main/java/com/filesystem/controller/FileController.java index 4fd85ba..78e2869 100644 --- a/src/main/java/com/filesystem/controller/FileController.java +++ b/src/main/java/com/filesystem/controller/FileController.java @@ -5,23 +5,17 @@ import com.filesystem.entity.FileShare; import com.filesystem.security.UserPrincipal; import com.filesystem.service.FileService; import com.filesystem.utils.ApiResult; +import com.filesystem.utils.CommonUtil; +import com.filesystem.utils.FileUtil; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; 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.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; 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.Map; @@ -95,8 +89,8 @@ public class FileController { public ApiResult createFolder( @AuthenticationPrincipal UserPrincipal principal, @RequestBody Map request) { - String name = (String) request.get("name"); - Long parentId = request.get("parentId") != null ? Long.valueOf(request.get("parentId").toString()) : null; + String name = CommonUtil.getString(request, "name"); + Long parentId = CommonUtil.getLong(request, "parentId"); FileEntity folder = fileService.createFolder(name, principal.getUserId(), parentId); return ApiResult.success("创建成功", folder); } @@ -148,7 +142,7 @@ public class FileController { } /** - * 下载文件(返回 ResponseEntity 用于二进制响应) + * 下载文件 */ @GetMapping("/{id}/download") public ResponseEntity downloadFile( @@ -159,18 +153,14 @@ public class FileController { if (file == null) return ResponseEntity.notFound().build(); byte[] content = fileService.getFileContent(file); if (content == null) return ResponseEntity.notFound().build(); - String encodedName = URLEncoder.encode(file.getName(), StandardCharsets.UTF_8).replace("+", "%20"); - return ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + encodedName + "\"") - .contentType(MediaType.APPLICATION_OCTET_STREAM) - .body(content); + return FileUtil.buildDownloadResponse(content, file.getName()); } catch (RuntimeException e) { return ResponseEntity.badRequest().build(); } } /** - * 预览文件(返回 ResponseEntity 用于二进制响应) + * 预览文件 */ @GetMapping("/{id}/preview") public ResponseEntity previewFile( @@ -181,11 +171,7 @@ public class FileController { if (file == null) return ResponseEntity.notFound().build(); byte[] content = fileService.getFileContent(file); if (content == null) return ResponseEntity.notFound().build(); - String encodedName = URLEncoder.encode(file.getName(), StandardCharsets.UTF_8).replace("+", "%20"); - return ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + encodedName + "\"") - .contentType(getMediaType(file.getName())) - .body(content); + return FileUtil.buildPreviewResponse(content, file.getName()); } catch (RuntimeException e) { return ResponseEntity.badRequest().build(); } @@ -197,8 +183,8 @@ public class FileController { @PathVariable Long id, @RequestBody Map request) { try { - Long shareToUserId = Long.valueOf(request.get("userId").toString()); - String permission = (String) request.getOrDefault("permission", "view"); + Long shareToUserId = CommonUtil.getLong(request, "userId"); + String permission = CommonUtil.getString(request, "permission", "view"); FileShare share = fileService.shareFile(id, principal.getUserId(), shareToUserId, permission); return ApiResult.success("共享成功", share); } catch (RuntimeException e) { @@ -219,43 +205,21 @@ public class FileController { } /** - * 获取头像图片(返回 ResponseEntity 用于二进制响应) + * 获取头像图片 */ @GetMapping("/avatar/**") public ResponseEntity getAvatar(jakarta.servlet.http.HttpServletRequest request) throws IOException { String uri = request.getRequestURI(); String prefix = "/api/files/avatar/"; String relativePath = uri.substring(uri.indexOf(prefix) + prefix.length()); - // relativePath = "2026/04/xxx.jpg" - Path filePath = Paths.get(storagePath).toAbsolutePath().resolve("avatars").resolve(relativePath); - if (!Files.exists(filePath)) { + java.nio.file.Path filePath = java.nio.file.Paths.get(storagePath).toAbsolutePath().resolve("avatars").resolve(relativePath); + if (!java.nio.file.Files.exists(filePath)) { 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 ext = fileName.contains(".") ? fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase() : "jpg"; - 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; - }; + return FileUtil.buildImageResponse(content, fileName); } @PutMapping("/{id}/rename") @@ -264,7 +228,7 @@ public class FileController { @PathVariable Long id, @RequestBody Map request) { String newName = request.get("name"); - if (newName == null || newName.trim().isEmpty()) { + if (CommonUtil.isBlank(newName)) { return ApiResult.error("名称不能为空"); } try { @@ -280,11 +244,7 @@ public class FileController { @AuthenticationPrincipal UserPrincipal principal, @PathVariable Long id, @RequestBody Map request) { - Object folderIdObj = request.get("folderId"); - Long folderId = null; - if (folderIdObj != null && !folderIdObj.toString().isEmpty() && !"null".equals(folderIdObj.toString()) && !"undefined".equals(folderIdObj.toString())) { - folderId = Long.parseLong(folderIdObj.toString()); - } + Long folderId = CommonUtil.getLong(request, "folderId"); try { fileService.moveFile(id, principal.getUserId(), folderId); return ApiResult.success("移动成功"); @@ -294,48 +254,27 @@ public class FileController { } /** - * 批量下载(返回 ZIP 文件,使用 HttpServletResponse 直接写入) + * 批量下载 */ @PostMapping("/batchDownload") public void batchDownload( @AuthenticationPrincipal UserPrincipal principal, @RequestBody Map request, HttpServletResponse response) throws IOException { - Object idsObj = request.get("ids"); - if (idsObj == null) { - response.setContentType("application/json;charset=UTF-8"); - response.getWriter().write("{\"code\":400,\"message\":\"请选择要下载的文件\"}"); - return; - } - - List 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)); - } - } - } + List ids = CommonUtil.parseIds(request.get("ids")); if (ids.isEmpty()) { - response.setContentType("application/json;charset=UTF-8"); - response.getWriter().write("{\"code\":400,\"message\":\"请选择要下载的文件\"}"); + com.filesystem.utils.ServletUtil.writeErrorJson(response, 400, "请选择要下载的文件"); return; } try { byte[] zipBytes = fileService.createZipArchive(ids, principal.getUserId()); - - response.setContentType("application/zip"); - response.setHeader("Content-Disposition", "attachment; filename=\"download.zip\""); - response.setContentLength(zipBytes.length); + com.filesystem.utils.ServletUtil.setZipDownloadHeaders(response, zipBytes.length); response.getOutputStream().write(zipBytes); response.getOutputStream().flush(); } catch (RuntimeException e) { - response.setContentType("application/json;charset=UTF-8"); - response.getWriter().write("{\"code\":400,\"message\":\"" + e.getMessage().replace("\"", "\\\"") + "\"}"); + com.filesystem.utils.ServletUtil.writeErrorJson(response, 400, e.getMessage()); } } @@ -352,20 +291,10 @@ public class FileController { public ApiResult batchCancelShare( @AuthenticationPrincipal UserPrincipal principal, @RequestBody Map request) { - Object idsObj = request.get("ids"); - if (idsObj == null) { + List ids = CommonUtil.parseIds(request.get("ids")); + if (ids.isEmpty()) { return ApiResult.error("请选择要取消共享的文件"); } - List 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) { fileService.cancelShare(fileId, principal.getUserId()); } @@ -376,20 +305,10 @@ public class FileController { public ApiResult batchRestore( @AuthenticationPrincipal UserPrincipal principal, @RequestBody Map request) { - Object idsObj = request.get("ids"); - if (idsObj == null) { + List ids = CommonUtil.parseIds(request.get("ids")); + if (ids.isEmpty()) { return ApiResult.error("请选择要还原的文件"); } - List 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) { fileService.restoreFile(fileId, principal.getUserId()); } diff --git a/src/main/java/com/filesystem/controller/MessageController.java b/src/main/java/com/filesystem/controller/MessageController.java index 83dfc77..01b9a20 100644 --- a/src/main/java/com/filesystem/controller/MessageController.java +++ b/src/main/java/com/filesystem/controller/MessageController.java @@ -6,24 +6,22 @@ import com.filesystem.security.UserPrincipal; import com.filesystem.service.MessageService; import com.filesystem.service.UserService; 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.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; 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.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import jakarta.servlet.http.HttpServletRequest; 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.time.LocalDateTime; -import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; import java.util.LinkedHashMap; @@ -41,8 +39,6 @@ public class MessageController { @Value("${file.storage.path:./uploads}") private String storagePath; - private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy/MM"); - // ==================== 聊天文件上传 ==================== @PostMapping("/upload") @@ -55,10 +51,9 @@ public class MessageController { } String originalName = file.getOriginalFilename(); - String ext = originalName != null && originalName.contains(".") - ? originalName.substring(originalName.lastIndexOf(".")) : ""; + String ext = FileUtil.getExtensionWithDot(originalName); 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); if (!Files.exists(targetDir)) { @@ -72,7 +67,7 @@ public class MessageController { return ApiResult.success("上传成功", Map.of("url", fileUrl)); } - // ==================== 聊天文件访问(返回 ResponseEntity 用于二进制响应)==================== + // ==================== 聊天文件访问 ==================== @GetMapping("/file/**") public ResponseEntity getChatFile(HttpServletRequest request) throws IOException { @@ -80,7 +75,6 @@ public class MessageController { String prefix = "/api/messages/file/"; String relativePath = uri.substring(uri.indexOf(prefix) + prefix.length()); - // relativePath = "2026/04/xxx.jpg" int lastSlash = relativePath.lastIndexOf('/'); String datePath = relativePath.substring(0, lastSlash); String fileName = relativePath.substring(lastSlash + 1); @@ -93,31 +87,11 @@ public class MessageController { } byte[] content = Files.readAllBytes(filePath); - String ext = fileName.contains(".") ? fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase() : ""; - - boolean isImage = Set.of("png", "jpg", "jpeg", "gif", "webp", "bmp", "svg").contains(ext); - - String contentType = switch (ext) { - 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); + boolean isImage = FileUtil.isImage(fileName); + + return isImage + ? FileUtil.buildImageResponse(content, fileName) + : FileUtil.buildDownloadResponse(content, fileName); } // ==================== 消息收发 ==================== @@ -158,7 +132,7 @@ public class MessageController { m.put("fileName", msg.getFileName()); m.put("fileSize", msg.getFileSize()); 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()); if (fromUser != null) { @@ -196,12 +170,11 @@ public class MessageController { public ApiResult> sendMessage( @AuthenticationPrincipal UserPrincipal principal, @RequestBody Map request) { - Long toUserId = Long.valueOf(request.get("toUserId").toString()); - String content = (String) request.get("content"); - String type = (String) request.getOrDefault("type", "text"); - String fileName = (String) request.get("fileName"); - Object fileSizeObj = request.get("fileSize"); - Long fileSize = fileSizeObj != null ? Long.valueOf(fileSizeObj.toString()) : null; + Long toUserId = CommonUtil.getLong(request, "toUserId"); + String content = CommonUtil.getString(request, "content"); + String type = CommonUtil.getString(request, "type", "text"); + String fileName = CommonUtil.getString(request, "fileName"); + Long fileSize = CommonUtil.getLong(request, "fileSize"); Message message = messageService.sendMessage(principal.getUserId(), toUserId, content, type); @@ -219,7 +192,7 @@ public class MessageController { result.put("type", message.getType()); result.put("fileName", message.getFileName()); 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()); if (fromUser != null) { @@ -241,7 +214,6 @@ public class MessageController { public ApiResult>> getUnreadList(@AuthenticationPrincipal UserPrincipal principal) { List unreadMessages = messageService.getUnreadMessages(principal.getUserId()); - // 按发送人分组 Map> grouped = unreadMessages.stream() .collect(Collectors.groupingBy(Message::getFromUserId, LinkedHashMap::new, Collectors.toList())); @@ -249,7 +221,7 @@ public class MessageController { for (Map.Entry> entry : grouped.entrySet()) { Long fromUserId = entry.getKey(); List msgs = entry.getValue(); - Message lastMsg = msgs.get(0); // 已按时间倒序,第一条就是最新的 + Message lastMsg = msgs.get(0); User fromUser = userService.findById(fromUserId); Map item = new HashMap<>(); @@ -259,7 +231,7 @@ public class MessageController { ? "[图片]" : (lastMsg.getType() != null && lastMsg.getType().equals("file") ? "[文件]" : (lastMsg.getContent() != null && lastMsg.getContent().length() > 30 ? 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) { item.put("username", fromUser.getUsername()); item.put("nickname", fromUser.getNickname() != null ? fromUser.getNickname() : fromUser.getUsername()); diff --git a/src/main/java/com/filesystem/controller/UserController.java b/src/main/java/com/filesystem/controller/UserController.java index f585c3f..24a8ab2 100644 --- a/src/main/java/com/filesystem/controller/UserController.java +++ b/src/main/java/com/filesystem/controller/UserController.java @@ -4,6 +4,8 @@ import com.filesystem.entity.User; import com.filesystem.security.UserPrincipal; import com.filesystem.service.UserService; import com.filesystem.utils.ApiResult; +import com.filesystem.utils.CommonUtil; +import com.filesystem.utils.FileUtil; import jakarta.annotation.Resource; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -14,8 +16,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; @@ -29,8 +29,6 @@ public class UserController { @Value("${file.storage.path:./uploads}") private String storagePath; - private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy/MM"); - /** * 获取所有可用用户(用于文件共享等场景) */ @@ -38,7 +36,7 @@ public class UserController { public ApiResult>> getAllUsers(@AuthenticationPrincipal UserPrincipal principal) { List users = userService.getAllUsersExcept(principal.getUserId()); List> result = users.stream() - .filter(u -> u.getStatus() == 1) // 只返回启用状态的用户 + .filter(u -> u.getStatus() == 1) .map(u -> { Map m = new HashMap<>(); m.put("id", u.getId()); @@ -64,20 +62,14 @@ public class UserController { return ApiResult.error("请选择图片"); } - // 限制文件大小 2MB if (file.getSize() > 2 * 1024 * 1024) { return ApiResult.error("图片大小不能超过2MB"); } - // 保存文件 - String originalFilename = file.getOriginalFilename(); - String ext = originalFilename != null && originalFilename.contains(".") - ? originalFilename.substring(originalFilename.lastIndexOf(".")) - : ".jpg"; + String ext = FileUtil.getExtensionWithDot(file.getOriginalFilename()); 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); if (!Files.exists(uploadPath)) { Files.createDirectories(uploadPath); @@ -86,7 +78,6 @@ public class UserController { Path filePath = uploadPath.resolve(fileName); Files.copy(file.getInputStream(), filePath); - // 更新用户头像 String avatarUrl = "/api/files/avatar/" + datePath + "/" + fileName; userService.updateAvatar(principal.getUserId(), avatarUrl); @@ -118,11 +109,13 @@ public class UserController { @AuthenticationPrincipal UserPrincipal principal, @RequestBody Map request) { try { - String nickname = request.get("nickname"); - String signature = request.get("signature"); - String phone = request.get("phone"); - String email = request.get("email"); - userService.updateProfile(principal.getUserId(), nickname, signature, phone, email); + userService.updateProfile( + principal.getUserId(), + request.get("nickname"), + request.get("signature"), + request.get("phone"), + request.get("email") + ); return ApiResult.success("更新成功"); } catch (RuntimeException e) { return ApiResult.error(e.getMessage()); @@ -139,7 +132,7 @@ public class UserController { String oldPassword = request.get("oldPassword"); String newPassword = request.get("newPassword"); - if (oldPassword == null || oldPassword.isEmpty() || newPassword == null || newPassword.isEmpty()) { + if (CommonUtil.isBlank(oldPassword) || CommonUtil.isBlank(newPassword)) { return ApiResult.error("请填写完整信息"); } diff --git a/src/main/java/com/filesystem/utils/CommonUtil.java b/src/main/java/com/filesystem/utils/CommonUtil.java new file mode 100644 index 0000000..289df4c --- /dev/null +++ b/src/main/java/com/filesystem/utils/CommonUtil.java @@ -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、List、Number、String 等多种格式 + */ + public static List parseIds(Object idsObj) { + List 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 parseIdsFromRequest(Map request, String key) { + return parseIds(request.get(key)); + } + + // ========== 参数提取 ========== + + /** + * 从 Map 中获取 Long 值 + */ + public static Long getLong(Map 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 map, String key) { + Object value = map.get(key); + return value != null ? value.toString() : null; + } + + /** + * 从 Map 中获取指定默认值的 String + */ + public static String getString(Map 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); + } +} diff --git a/src/main/java/com/filesystem/utils/FileUtil.java b/src/main/java/com/filesystem/utils/FileUtil.java new file mode 100644 index 0000000..8b50a96 --- /dev/null +++ b/src/main/java/com/filesystem/utils/FileUtil.java @@ -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 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 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 buildImageResponse(byte[] content, String fileName) { + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType(getImageContentType(fileName))) + .header(HttpHeaders.CACHE_CONTROL, "max-age=86400") + .body(content); + } +} diff --git a/src/main/java/com/filesystem/utils/ServletUtil.java b/src/main/java/com/filesystem/utils/ServletUtil.java new file mode 100644 index 0000000..a1a9f91 --- /dev/null +++ b/src/main/java/com/filesystem/utils/ServletUtil.java @@ -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"); + } +}