feat(file): 新增文件上传校验与错误码
- 支持图片格式与大小限制(最大5MB) - 补充 FILE_NAME_ERROR、FILE_TYPE_ERROR、FILE_SIZE_EXCEED 错误码 - 移除 FileController.upload 的异常声明,统一由 BusinessException 处理
This commit is contained in:
@@ -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,"密码或邮箱错误" ),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user