From 5d2c5fa5084e1398ae33c32f6c524627de727f99 Mon Sep 17 00:00:00 2001 From: ziin Date: Wed, 10 Dec 2025 13:46:48 +0800 Subject: [PATCH] =?UTF-8?q?feat(file):=20=E6=96=B0=E5=A2=9E=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E4=B8=8A=E4=BC=A0=E6=A0=A1=E9=AA=8C=E4=B8=8E=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 支持图片格式与大小限制(最大5MB) - 补充 FILE_NAME_ERROR、FILE_TYPE_ERROR、FILE_SIZE_EXCEED 错误码 - 移除 FileController.upload 的异常声明,统一由 BusinessException 处理 --- .../com/yolo/keyborad/common/ErrorCode.java | 4 +- .../keyborad/controller/FileController.java | 5 +- .../service/impl/FileServiceImpl.java | 55 +++++++++++++++++-- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/yolo/keyborad/common/ErrorCode.java b/src/main/java/com/yolo/keyborad/common/ErrorCode.java index fbedb56..f0943b6 100644 --- a/src/main/java/com/yolo/keyborad/common/ErrorCode.java +++ b/src/main/java/com/yolo/keyborad/common/ErrorCode.java @@ -20,6 +20,9 @@ public enum ErrorCode { OPERATION_ERROR(50001, "操作失败"), APPLE_LOGIN_ERROR(40003, "Apple登录失败"), FILE_IS_EMPTY(40001, "上传文件为空"), + FILE_NAME_ERROR(40002, "文件名错误"), + FILE_TYPE_ERROR(40004, "文件类型不支持,仅支持图片格式"), + FILE_SIZE_EXCEED(40005, "文件大小超出限制,最大支持5MB"), TOKEN_NOT_FOUND(40102, "未能读取到有效用户令牌"), TOKEN_INVALID(40103, "令牌无效"), TOKEN_TIMEOUT(40104, "令牌已过期"), @@ -27,7 +30,6 @@ public enum ErrorCode { TOKEN_KICK_OUT(40107, "令牌已被踢下线"), TOKEN_FREEZE(40108, "令牌已被冻结"), TOKEN_NO_PREFIX(40109, "未按照指定前缀提交令牌"), - FILE_NAME_ERROR(40002, "文件名错误"), USER_NOT_FOUND(40401, "用户不存在"), USER_INFO_UPDATE_FAILED(50002, "用户信息更新失败"), PASSWORD_OR_MAIL_ERROR(50003,"密码或邮箱错误" ), diff --git a/src/main/java/com/yolo/keyborad/controller/FileController.java b/src/main/java/com/yolo/keyborad/controller/FileController.java index 894256a..dbdce1e 100644 --- a/src/main/java/com/yolo/keyborad/controller/FileController.java +++ b/src/main/java/com/yolo/keyborad/controller/FileController.java @@ -2,7 +2,6 @@ package com.yolo.keyborad.controller; import com.yolo.keyborad.common.BaseResponse; import com.yolo.keyborad.common.ResultUtils; -import com.yolo.keyborad.model.dto.AppleLoginReq; import com.yolo.keyborad.service.FileService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -12,8 +11,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import java.io.File; - /* * @author: ziin * @date: 2025/12/2 15:46 @@ -31,7 +28,7 @@ public class FileController { @PostMapping("/upload") @Operation(summary = "上传文件", description = "上传文件接口") @Parameter(name = "file",required = true,description = "上传的文件") - public BaseResponse upload(@RequestParam("file") MultipartFile file) throws Exception { + public BaseResponse upload(@RequestParam("file") MultipartFile file){ String fileUrl = fileService.upload(file); return ResultUtils.success(fileUrl); } diff --git a/src/main/java/com/yolo/keyborad/service/impl/FileServiceImpl.java b/src/main/java/com/yolo/keyborad/service/impl/FileServiceImpl.java index d1908a3..f552746 100644 --- a/src/main/java/com/yolo/keyborad/service/impl/FileServiceImpl.java +++ b/src/main/java/com/yolo/keyborad/service/impl/FileServiceImpl.java @@ -12,6 +12,9 @@ import org.dromara.x.file.storage.spring.SpringFileStorageProperties; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import java.util.Arrays; +import java.util.List; + /* * @author: ziin * @date: 2025/12/2 15:45 @@ -23,24 +26,68 @@ public class FileServiceImpl implements FileService { @Resource private FileStorageService fileStorageService;//注入实列 + // 允许的图片格式 + private static final List ALLOWED_IMAGE_TYPES = Arrays.asList( + "image/jpeg", "image/jpg", "image/png", "image/gif", + "image/bmp", "image/webp", "image/svg+xml" + ); + + // 允许的图片扩展名 + private static final List ALLOWED_IMAGE_EXTENSIONS = Arrays.asList( + ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".svg" + ); + + // 最大文件大小:5MB + private static final long MAX_FILE_SIZE = 5 * 1024 * 1024; + @Override public String upload(MultipartFile file) { + // 1. 检查文件是否为空 if (file == null || file.isEmpty()) { log.error("上传文件为空"); throw new BusinessException(ErrorCode.FILE_IS_EMPTY); } - // 获取原始文件名 + // 2. 检查文件大小 + if (file.getSize() > MAX_FILE_SIZE) { + log.error("文件大小超出限制,文件大小: {} bytes, 限制: {} bytes", file.getSize(), MAX_FILE_SIZE); + throw new BusinessException(ErrorCode.FILE_SIZE_EXCEED); + } + + // 3. 获取原始文件名 String originalFilename = file.getOriginalFilename(); - if (originalFilename == null) { + if (originalFilename == null || originalFilename.trim().isEmpty()) { log.error("无法获取文件名"); throw new BusinessException(ErrorCode.FILE_NAME_ERROR); } - // 获取文件扩展名 - String extension = originalFilename.substring(originalFilename.lastIndexOf(".")); + + // 4. 获取文件扩展名(安全处理) + String extension = ""; + int lastDotIndex = originalFilename.lastIndexOf("."); + if (lastDotIndex > 0 && lastDotIndex < originalFilename.length() - 1) { + extension = originalFilename.substring(lastDotIndex).toLowerCase(); + } else { + log.error("文件名无扩展名: {}", originalFilename); + throw new BusinessException(ErrorCode.FILE_NAME_ERROR); + } + + // 5. 验证文件类型(通过扩展名和Content-Type双重验证) + String contentType = file.getContentType(); + boolean isValidExtension = ALLOWED_IMAGE_EXTENSIONS.contains(extension); + boolean isValidContentType = contentType != null && ALLOWED_IMAGE_TYPES.contains(contentType.toLowerCase()); + + if (!isValidExtension || !isValidContentType) { + log.error("文件类型不支持,文件名: {}, 扩展名: {}, Content-Type: {}", + originalFilename, extension, contentType); + throw new BusinessException(ErrorCode.FILE_TYPE_ERROR); + } + + // 6. 上传文件 FileInfo upload = fileStorageService.of(file) .setSaveFilename(UUID.randomUUID() + extension) .upload(); + + log.info("文件上传成功,原始文件名: {}, URL: {}", originalFilename, upload.getUrl()); return upload.getUrl(); } }