package com.tcm.training.admin.upload; import com.tcm.training.admin.common.ApiResponse; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import java.io.File; import java.io.IOException; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.UUID; @RestController @RequestMapping({"/api/v1", "/api"}) public class UploadController { private static final long MAX_SIZE = 5 * 1024 * 1024L; // 5MB private static final Set ALLOWED_TYPES = new HashSet<>( Arrays.asList("image/jpeg", "image/png", "image/gif", "image/webp") ); @Value("${app.upload.dir:uploads}") private String uploadDir; @Value("${app.upload.path-prefix:/uploads}") private String pathPrefix; /** * 图片上传 * POST /api/v1/upload/image * @return { url: "访问地址" } */ @PostMapping("/upload/image") public ApiResponse uploadImage( @RequestParam("file") MultipartFile file, HttpServletRequest request) throws IOException { // 校验文件非空 if (file == null || file.isEmpty()) { throw new IllegalArgumentException("请选择要上传的文件"); } // 校验文件大小 if (file.getSize() > MAX_SIZE) { throw new IllegalArgumentException("文件大小不能超过 5MB"); } // 校验文件类型 String contentType = file.getContentType(); if (contentType == null || !ALLOWED_TYPES.contains(contentType)) { throw new IllegalArgumentException("仅支持 JPG、PNG、GIF、WebP 格式"); } // 生成子目录(按日期)和唯一文件名 String dateDir = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")); String originalName = file.getOriginalFilename(); String ext = ""; if (originalName != null && originalName.contains(".")) { ext = originalName.substring(originalName.lastIndexOf(".")); } String fileName = UUID.randomUUID().toString().replace("-", "") + ext; // 创建目录并保存文件(强制绝对路径,规避 transferTo 临时文件问题) File dir = new File(uploadDir, dateDir).getAbsoluteFile(); if (!dir.exists() && !dir.mkdirs()) { throw new IOException("创建上传目录失败: " + dir.getAbsolutePath()); } File dest = new File(dir, fileName); // 用 InputStream 手动拷贝,避免 Spring transferTo 依赖临时文件的问题 try (java.io.InputStream in = file.getInputStream(); java.io.OutputStream out = new java.io.FileOutputStream(dest)) { byte[] buf = new byte[8192]; int n; while ((n = in.read(buf)) != -1) { out.write(buf, 0, n); } } // 构造访问 URL String baseUrl = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort(); String url = baseUrl + pathPrefix + "/" + dateDir + "/" + fileName; return ApiResponse.ok(new UploadResult(url)); } public static class UploadResult { private final String url; public UploadResult(String url) { this.url = url; } public String getUrl() { return url; } } }