From 4b3bf7a46ca2ca4f785603d4ea22983f43afc92e Mon Sep 17 00:00:00 2001 From: API Changelog Bot Date: Wed, 6 May 2026 18:25:05 +0800 Subject: [PATCH] =?UTF-8?q?fix(agency):=20=E8=B5=84=E8=B4=A8=E9=99=84?= =?UTF-8?q?=E4=BB=B6=E4=B8=8A=E4=BC=A0=E5=BF=85=E9=A1=BB=E5=85=88=20OSS=20?= =?UTF-8?q?=E6=8B=BF=20url=20=E5=86=8D=20JSON=20=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E5=85=83=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 前端 mmg @ POST /admin/travel-agency/{agencyId}/qualification 报 HttpMediaTypeNotSupportedException — 后端期望 application/json (fileUrl 字段是 OSS URL), 前端目前直接 multipart 把文件流当 body. 改成两步: 先 POST /admin/sys/oss/upload 拿 url, 再 POST 资质接口带 fileUrl JSON. 后端 round-trip 已验证, 无需改后端. --- ...gency_qualification_upload_content_type.md | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 changelogs/2026-05/06_fix_agency_qualification_upload_content_type.md diff --git a/changelogs/2026-05/06_fix_agency_qualification_upload_content_type.md b/changelogs/2026-05/06_fix_agency_qualification_upload_content_type.md new file mode 100644 index 0000000..386377b --- /dev/null +++ b/changelogs/2026-05/06_fix_agency_qualification_upload_content_type.md @@ -0,0 +1,143 @@ +# fix(agency): 旅行社资质附件上传必须先调通用 OSS 接口拿 url, 再以 application/json 提交元数据 + +**日期**: 2026-05-06 18:30 +**通知对象**: @mmg (前端) — **必须改前端「公司资质 - 新增/编辑」请求方式** +**关联 PR**: 无 (后端无需改动, 接口设计与项目内其它「URL 字段」类接口一致) +**测试服 round-trip**: ✅ JSON 路径创建成功 / multipart 路径复现 500 + +--- + +## ⚠️ 一句话摘要 + +**`POST /admin/travel-agency/{agencyId}/qualification` 后端接的是 `application/json`, 前端目前直接发 `multipart/form-data`(把文件流当成 body) → Spring MVC 抛 `HttpMediaTypeNotSupportedException` 返回 500。前端必须改成两步上传: 先调通用 OSS 上传接口拿 url, 再 POST 资质接口带 fileUrl 等元数据 JSON。** + +--- + +## 一、报错信息 + +``` +服务器内部错误[HttpMediaTypeNotSupportedException]: +Content type 'multipart/form-data;boundary=----WebKitFormBoundaryW7TegwvnmSqm3boT;charset=UTF-8' not supported +``` + +触发路径: `POST /admin/travel-agency/2051922156798779394/qualification` + +--- + +## 二、后端契约 (不变) + +``` +POST /admin/travel-agency/{agencyId}/qualification +Content-Type: application/json +Authorization: Bearer +``` + +请求 body (`AgencyQualificationSaveReqVO`): + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `name` | String(64) | ✅ | 资质名称, 例: `旅行社业务经营许可证` / `营业执照` | +| `qualificationType` | String(32) | ✅ | 字典 `agency_qualification_type`: `BUSINESS_LICENSE` / `TRAVEL_AGENCY_LICENSE` / `VALUE_ADDED_TELECOM_LICENSE` | +| `fileUrl` | String | ✅ | **OSS 永久 URL (从通用上传接口拿)**, **不是文件流** | +| `fileType` | String | 否 | 字典 `agency_qualification_file_type`: `PDF` / `IMAGE` / `OTHER` | +| `issueDate` | LocalDate | 否 | 颁发日期, `2024-01-01` | +| `expireDate` | LocalDate | 否 | 过期日期, `null=长期有效` | +| `sortOrder` | Integer | 否 | 排序值 | +| `remark` | String | 否 | 备注 | + +> 编辑接口 `PUT /admin/travel-agency/{agencyId}/qualification/{qualificationId}` 同样 JSON, body 同上。 + +--- + +## 三、前端正确调用方式 (两步) + +### 步骤 1 - 上传文件到 OSS, 拿到永久 URL + +``` +POST /admin/sys/oss/upload +Content-Type: multipart/form-data +Authorization: Bearer +form-data: file= +``` + +返回: + +```json +{ + "code": 200, + "data": { + "id": 123, + "url": "https://hulalv-private.oss-cn-shanghai.aliyuncs.com/xxx/yyy.pdf", + "name": "营业执照.pdf", + "size": 102400 + } +} +``` + +> 这是项目内通用 OSS 上传接口, 富文本/素材库已经在用, 复用即可 — `hl-ui/src/api/material.js#uploadFile` 应该就是这个。 + +### 步骤 2 - 提交资质元数据 (带上一步拿到的 url) + +``` +POST /admin/travel-agency/{agencyId}/qualification +Content-Type: application/json +Authorization: Bearer +``` + +```json +{ + "name": "营业执照", + "qualificationType": "BUSINESS_LICENSE", + "fileUrl": "https://hulalv-private.oss-cn-shanghai.aliyuncs.com/xxx/yyy.pdf", + "fileType": "PDF", + "issueDate": "2024-01-01", + "expireDate": "2029-12-31", + "sortOrder": 100, + "remark": "营业期限至 2029-12-31" +} +``` + +返回: + +```json +{ "code": 200, "data": 2051971194931859458 } +``` + +`data` 是新建的 `qualificationId`, 编辑/删除接口需要带它。 + +--- + +## 四、为什么不让后端直接接 multipart + +1. **设计一致性**: 项目内「URL 字段」类接口(产品图、合同附件元数据、酒店封面等)统一约定 `fileUrl: String`, 文件上传统一走 `/admin/sys/oss/upload`, 业务接口只接 JSON 元数据。 +2. **OSS 直传降本**: 走通用上传接口可以走未来的 OSS STS 直传链路, 业务接口不需要重复封装文件流。 +3. **后续 P1-11 任务**: `fileUrl` 在 RespVO 中要转 30 min 临时签名 URL (私有 bucket), 这套也是基于「永久 url 落库」的两步设计。 + +> 项目里另一个接口 `POST /admin/contract/scheme/{schemeId}/attachments` 直接接 multipart 是历史模式, 新接口都按上面两步走。 + +--- + +## 五、Round-trip 验证记录 + +| 测试 | Content-Type | 期望 | 实际 | +|------|------|------|------| +| 1 | `application/json` | 创建成功 | ✅ HTTP 200 + `code:200` + `data:2051971194931859458` (已清理) | +| 2 | `multipart/form-data` (前端目前的发法) | 复现报错 | ✅ HTTP 200 + `code:500` (前端看到的报错) | + +测试服: `https://api.test.1814.love:9443` +agencyId: `2051922156798779394` +admin token: 真实登录 (admin / Admin@123456) + +--- + +## 六、改动范围 (前端) + +| 模块 | 改动 | +|------|------| +| `hl-ui/src/api/agency.js` (或同名文件) 资质 CRUD 函数 | 新增/编辑请求体改成 JSON, 不再 FormData | +| 「公司详情 - 资质附件」组件 | 上传组件先调 OSS 拿 url, 再调资质接口提交元数据 | +| 删除/列表/排序接口 | 不受影响, 已 round-trip 通过 | + +--- + +**有不清楚的字段或字典值, 直接 @ wx 加补。**