| | |
| | | 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.GetMapping; |
| | | 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 long VIDEO_MAX_SIZE = 200 * 1024 * 1024L; // 200MB |
| | | private static final Set<String> ALLOWED_TYPES = new HashSet<>( |
| | | Arrays.asList("image/jpeg", "image/png", "image/gif", "image/webp") |
| | | ); |
| | | private static final Set<String> ALLOWED_VIDEO_TYPES = new HashSet<>( |
| | | Arrays.asList("video/mp4", "video/quicktime", "video/x-msvideo", "video/x-matroska", "video/webm") |
| | | ); |
| | | |
| | | @Value("${app.upload.dir:uploads}") |
| | | private String uploadDir; |
| | | private final ObsUploadService obsUploadService; |
| | | private final UploadProgressService uploadProgressService; |
| | | |
| | | @Value("${app.upload.path-prefix:/uploads}") |
| | | private String pathPrefix; |
| | | public UploadController(ObsUploadService obsUploadService, UploadProgressService uploadProgressService) { |
| | | this.obsUploadService = obsUploadService; |
| | | this.uploadProgressService = uploadProgressService; |
| | | } |
| | | |
| | | /** |
| | | * 图片上传 |
| | |
| | | */ |
| | | @PostMapping("/upload/image") |
| | | public ApiResponse<UploadResult> uploadImage( |
| | | @RequestParam("file") MultipartFile file, |
| | | HttpServletRequest request) throws IOException { |
| | | @RequestParam("file") MultipartFile file) throws IOException { |
| | | return ApiResponse.ok(storeFile(file, MAX_SIZE, ALLOWED_TYPES, "仅支持 JPG、PNG、GIF、WebP 格式", "image", null)); |
| | | } |
| | | |
| | | // 校验文件非空 |
| | | @PostMapping("/upload/video") |
| | | public ApiResponse<UploadResult> uploadVideo( |
| | | @RequestParam("file") MultipartFile file, |
| | | @RequestParam(value = "uploadTaskId", required = false) String uploadTaskId) throws IOException { |
| | | return ApiResponse.ok(storeFile(file, VIDEO_MAX_SIZE, ALLOWED_VIDEO_TYPES, "仅支持 MP4、MOV、AVI、MKV、WebM 格式", "video", uploadTaskId)); |
| | | } |
| | | |
| | | @GetMapping("/upload/video/progress") |
| | | public ApiResponse<UploadProgressInfo> uploadVideoProgress( |
| | | @RequestParam("taskId") String taskId) { |
| | | return ApiResponse.ok(uploadProgressService.get(taskId)); |
| | | } |
| | | |
| | | private UploadResult storeFile(MultipartFile file, |
| | | long maxSize, |
| | | Set<String> allowedTypes, |
| | | String typeMessage, |
| | | String bizPath, |
| | | String uploadTaskId) throws IOException { |
| | | if (file == null || file.isEmpty()) { |
| | | throw new IllegalArgumentException("请选择要上传的文件"); |
| | | } |
| | | |
| | | // 校验文件大小 |
| | | if (file.getSize() > MAX_SIZE) { |
| | | throw new IllegalArgumentException("文件大小不能超过 5MB"); |
| | | if (file.getSize() > maxSize) { |
| | | throw new IllegalArgumentException("文件大小超出限制"); |
| | | } |
| | | |
| | | // 校验文件类型 |
| | | String contentType = file.getContentType(); |
| | | if (contentType == null || !ALLOWED_TYPES.contains(contentType)) { |
| | | throw new IllegalArgumentException("仅支持 JPG、PNG、GIF、WebP 格式"); |
| | | if (contentType == null || !allowedTypes.contains(contentType)) { |
| | | throw new IllegalArgumentException(typeMessage); |
| | | } |
| | | |
| | | // 生成子目录(按日期)和唯一文件名 |
| | | 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)); |
| | | UploadFileResult uploadResult = obsUploadService.upload(file, bizPath, uploadTaskId); |
| | | return new UploadResult(uploadResult.getUrl(), uploadResult.getFileName()); |
| | | } |
| | | |
| | | public static class UploadResult { |
| | | private final String url; |
| | | private final String fileName; |
| | | |
| | | public UploadResult(String url) { |
| | | this(url, null); |
| | | } |
| | | |
| | | public UploadResult(String url, String fileName) { |
| | | this.url = url; |
| | | this.fileName = fileName; |
| | | } |
| | | |
| | | public String getUrl() { |
| | | return url; |
| | | } |
| | | |
| | | public String getFileName() { |
| | | return fileName; |
| | | } |
| | | } |
| | | } |