|
1 | 1 | package com.yqz.openblog.media.controller; |
2 | 2 |
|
3 | 3 | import com.yqz.openblog.common.ApiResponse; |
| 4 | +import com.yqz.openblog.common.BizException; |
4 | 5 | import com.yqz.openblog.media.dto.MediaCategoryResponse; |
5 | 6 | import com.yqz.openblog.media.dto.MediaListItemResponse; |
6 | 7 | import com.yqz.openblog.media.dto.MediaUploadResponse; |
7 | 8 | import com.yqz.openblog.media.dto.ThumbInfoResponse; |
8 | 9 | import com.yqz.openblog.media.entity.Media; |
9 | 10 | import com.yqz.openblog.media.service.MediaService; |
10 | 11 | import com.baomidou.mybatisplus.core.metadata.IPage; |
| 12 | +import jakarta.servlet.http.HttpServletRequest; |
11 | 13 | import jakarta.servlet.http.HttpServletResponse; |
12 | 14 | import org.springframework.http.HttpHeaders; |
13 | 15 | import org.springframework.http.MediaType; |
| 16 | +import org.springframework.util.StringUtils; |
14 | 17 | import org.springframework.web.bind.annotation.*; |
15 | 18 | import org.springframework.web.multipart.MultipartFile; |
16 | 19 |
|
17 | 20 | import java.io.IOException; |
| 21 | +import java.net.URLDecoder; |
| 22 | +import java.nio.charset.StandardCharsets; |
18 | 23 | import java.util.List; |
19 | 24 |
|
20 | 25 | @RestController |
21 | 26 | @RequestMapping("/api/v1/media") |
22 | 27 | @CrossOrigin(origins = "*") |
23 | 28 | public class MediaController { |
24 | 29 |
|
| 30 | + private static final String FILES_PATH_MARKER = "/api/v1/media/files/"; |
| 31 | + |
25 | 32 | private final MediaService mediaService; |
26 | 33 |
|
27 | 34 | public MediaController(MediaService mediaService) { |
@@ -59,14 +66,29 @@ public ApiResponse<ThumbInfoResponse> thumbInfo(@PathVariable("key") String key) |
59 | 66 | return ApiResponse.ok(mediaService.thumbInfo(key)); |
60 | 67 | } |
61 | 68 |
|
62 | | - @GetMapping(value = "/files/{key:.+}/thumb", produces = MediaType.ALL_VALUE) |
63 | | - public void serveThumb(@PathVariable("key") String key, HttpServletResponse response) throws IOException { |
64 | | - serveFile(key, true, response); |
65 | | - } |
66 | | - |
67 | | - @GetMapping(value = "/files/{key:.+}", produces = MediaType.ALL_VALUE) |
68 | | - public void serveOriginal(@PathVariable("key") String key, HttpServletResponse response) throws IOException { |
69 | | - serveFile(key, false, response); |
| 69 | + /** |
| 70 | + * 媒体文件访问(原图 / 缩略图)。storageKey 可能含 {@code /}(如 {@code general/uuid.jpg}), |
| 71 | + * 须用 {@code /**} 解析路径;Spring Boot 3 默认 PathPattern 下 {@code {key:.+}} 无法跨段匹配。 |
| 72 | + */ |
| 73 | + @GetMapping(value = "/files/**", produces = MediaType.ALL_VALUE) |
| 74 | + public void serveFile(HttpServletRequest request, HttpServletResponse response) throws IOException { |
| 75 | + String uri = request.getRequestURI(); |
| 76 | + int idx = uri.indexOf(FILES_PATH_MARKER); |
| 77 | + if (idx < 0) { |
| 78 | + throw new BizException(4042, "无效媒体路径"); |
| 79 | + } |
| 80 | + String path = uri.substring(idx + FILES_PATH_MARKER.length()); |
| 81 | + int q = path.indexOf('?'); |
| 82 | + if (q >= 0) { |
| 83 | + path = path.substring(0, q); |
| 84 | + } |
| 85 | + path = URLDecoder.decode(path, StandardCharsets.UTF_8); |
| 86 | + boolean thumb = path.endsWith("/thumb"); |
| 87 | + String key = thumb ? path.substring(0, path.length() - "/thumb".length()) : path; |
| 88 | + if (!StringUtils.hasText(key)) { |
| 89 | + throw new BizException(4002, "媒体 key 为空"); |
| 90 | + } |
| 91 | + serveFile(key, thumb, response); |
70 | 92 | } |
71 | 93 |
|
72 | 94 | private void serveFile(String key, boolean thumb, HttpServletResponse response) throws IOException { |
|
0 commit comments