|
| 1 | +# Debug 记录 |
| 2 | + |
| 3 | +本文档用于记录项目开发与线上排查中的典型 Bug:现象、根因、修复方式与验证要点,便于后续同类问题快速定位。 |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## 2025-05-28 · 头像/媒体上传后预览不可见,访问 URL 返回 5001 |
| 8 | + |
| 9 | +### 现象 |
| 10 | + |
| 11 | +- 个人资料页从本地上传头像后,预览区图片不显示。 |
| 12 | +- 保存后访问返回的媒体 URL(如 `https://www.wecode.xin/api/v1/media/files/general/{uuid}.jpg`),响应为 JSON 而非图片: |
| 13 | + |
| 14 | +```json |
| 15 | +{ |
| 16 | + "code": 5001, |
| 17 | + "message": "No static resource api/v1/media/files/general/df42c00b-b840-4e29-87f1-cc7f56ef4dc0.jpg.", |
| 18 | + "data": null, |
| 19 | + "traceId": "..." |
| 20 | +} |
| 21 | +``` |
| 22 | + |
| 23 | +### 根因 |
| 24 | + |
| 25 | +1. **存储 key 含路径分隔符** |
| 26 | + 上传时未指定 `folderId`,`MediaService` 生成的 key 形如 `general/{uuid}.jpg`(`general` 为默认分类前缀)。 |
| 27 | + |
| 28 | +2. **对外 URL 与 key 一致带 `/`** |
| 29 | + 访问地址为:`/api/v1/media/files/general/{uuid}.jpg`,即 `files/` 之后还有多段路径。 |
| 30 | + |
| 31 | +3. **Spring Boot 3 路径匹配不兼容** |
| 32 | + 控制器原先使用: |
| 33 | + |
| 34 | + ```java |
| 35 | + @GetMapping("/files/{key:.+}") |
| 36 | + ``` |
| 37 | + |
| 38 | + 在 **Spring Boot 3 默认的 `PathPatternParser`** 下,`{key:.+}` **不能跨路径段**匹配,往往只能匹配到 `general`,剩余 `/{uuid}.jpg` 无法命中该接口。 |
| 39 | + |
| 40 | +4. **落入静态资源兜底** |
| 41 | + 无 Controller 命中后,请求被当作静态资源处理,抛出 `NoResourceFoundException`(消息为 `No static resource ...`),再被 `GlobalExceptionHandler` 包装为 `code: 5001`。 |
| 42 | + |
| 43 | +### 解决方法 |
| 44 | + |
| 45 | +修改 `MediaController`:将文件访问接口改为通配路径,从 `HttpServletRequest` 的 URI 中解析完整 storageKey。 |
| 46 | + |
| 47 | +```java |
| 48 | +private static final String FILES_PATH_MARKER = "/api/v1/media/files/"; |
| 49 | + |
| 50 | +@GetMapping(value = "/files/**", produces = MediaType.ALL_VALUE) |
| 51 | +public void serveFile(HttpServletRequest request, HttpServletResponse response) throws IOException { |
| 52 | + String uri = request.getRequestURI(); |
| 53 | + int idx = uri.indexOf(FILES_PATH_MARKER); |
| 54 | + // 解析 path,支持原图与 .../thumb 缩略图 |
| 55 | + // key 示例:general/uuid.jpg |
| 56 | + serveFile(key, thumb, response); |
| 57 | +} |
| 58 | +``` |
| 59 | + |
| 60 | +**说明:** |
| 61 | + |
| 62 | +- 原图:`/api/v1/media/files/general/uuid.jpg` → key = `general/uuid.jpg` |
| 63 | +- 缩略图:`/api/v1/media/files/general/uuid.jpg/thumb` → key = `general/uuid.jpg`,`thumb = true` |
| 64 | +- 无需再配置 `spring.mvc.pathmatch.matching-strategy: ant_path_matcher` 作为权宜之计。 |
| 65 | + |
| 66 | +### 涉及文件 |
| 67 | + |
| 68 | +| 文件 | 变更 | |
| 69 | +|------|------| |
| 70 | +| `OpenBlog-business/.../media/controller/MediaController.java` | `/files/{key:.+}` → `/files/**` + URI 解析 | |
| 71 | +| `OpenBlog-business/src/main/resources/application.yaml` | 移除(若曾添加)`ant_path_matcher` 配置 | |
| 72 | + |
| 73 | +### 验证 |
| 74 | + |
| 75 | +1. 重新编译并部署/重启后端。 |
| 76 | +2. 上传头像后,预览 `<img :src="avatarUrl">` 应能正常显示。 |
| 77 | +3. 浏览器直接打开媒体 URL,应返回 **图片**(`Content-Type: image/jpeg` 等),而不是 JSON。 |
| 78 | +4. 附件库、文章封面等使用 `/api/v1/media/files/{category}/{uuid}.ext` 的链接一并恢复。 |
| 79 | + |
| 80 | +### 延伸注意 |
| 81 | + |
| 82 | +- 若修复路径后仍 5001 且报 MinIO 相关错误,需单独检查 `openblog.storage.minio` 连通性与桶内对象路径(`original/general/...`)。 |
| 83 | +- 开发环境 `public-base-url` 指向生产域名时,本地预览会请求生产环境,需保证该环境已部署含本次修复的后端。 |
| 84 | + |
| 85 | +--- |
| 86 | + |
| 87 | +<!-- 新记录请复制上方「## 日期 · 标题」区块,追加在文件末尾 --> |
0 commit comments