fix(agency): 资质附件上传必须先 OSS 拿 url 再 JSON 提交元数据
前端 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 已验证, 无需改后端.
这个提交包含在:
父节点
a747412fc3
当前提交
4b3bf7a46c
@ -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 <token>
|
||||
```
|
||||
|
||||
请求 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 <token>
|
||||
form-data: file=<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 <token>
|
||||
```
|
||||
|
||||
```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 加补。**
|
||||
正在加载...
x
在新工单中引用
屏蔽一个用户