feat(file): 新增文件上传校验与错误码

- 支持图片格式与大小限制(最大5MB)
- 补充 FILE_NAME_ERROR、FILE_TYPE_ERROR、FILE_SIZE_EXCEED 错误码
- 移除 FileController.upload 的异常声明,统一由 BusinessException 处理
This commit is contained in:
2025-12-10 13:46:48 +08:00
parent f4dc692e3b
commit 5d2c5fa508
3 changed files with 55 additions and 9 deletions

View File

@@ -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,"密码或邮箱错误" ),

View File

@@ -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<String> upload(@RequestParam("file") MultipartFile file) throws Exception {
public BaseResponse<String> upload(@RequestParam("file") MultipartFile file){
String fileUrl = fileService.upload(file);
return ResultUtils.success(fileUrl);
}

View File

@@ -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<String> ALLOWED_IMAGE_TYPES = Arrays.asList(
"image/jpeg", "image/jpg", "image/png", "image/gif",
"image/bmp", "image/webp", "image/svg+xml"
);
// 允许的图片扩展名
private static final List<String> 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();
}
}