frontend-bug: 合同方案附件上传 MultipartException 没用 FormData (@mmg)
这个提交包含在:
父节点
8dd2323197
当前提交
1c69282e56
@ -0,0 +1,97 @@
|
|||||||
|
---
|
||||||
|
date: 2026-05-04
|
||||||
|
type: frontend-bug
|
||||||
|
module: contract-scheme-attachment
|
||||||
|
priority: high
|
||||||
|
notify: ["@mmg"]
|
||||||
|
status: pending
|
||||||
|
---
|
||||||
|
|
||||||
|
# 合同方案附件上传 MultipartException — 前端没用 FormData
|
||||||
|
|
||||||
|
## 现象
|
||||||
|
|
||||||
|
POST `/admin/contract/scheme/{schemeId}/attachments` 报错:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"code":500,"message":"服务器内部错误[MultipartException]: Current request is not a multipart request","data":null,"success":false}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 根因
|
||||||
|
|
||||||
|
后端 round-trip 5MB / 中文名 / 30MB PDF curl 上传**全部成功**(已验证),后端 100% OK。
|
||||||
|
|
||||||
|
错误是 **`org.springframework.web.multipart.MultipartException`** — Spring 在期望 multipart 请求体时拿到的不是 multipart 格式。这是前端发请求时 **Content-Type / body 包装错了**。
|
||||||
|
|
||||||
|
最常见原因(按概率):
|
||||||
|
1. **axios 默认 Content-Type=application/json,没让 browser 自动设成 multipart**
|
||||||
|
2. **传了普通 object 不是 FormData,axios JSON.stringify 了**
|
||||||
|
3. **代码里写了 `Content-Type: application/json` 强制覆盖**
|
||||||
|
|
||||||
|
## 修复(前端)
|
||||||
|
|
||||||
|
### 错误写法 ❌
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 错: 直接传对象 → axios 自动 JSON.stringify
|
||||||
|
http.post(`/admin/contract/scheme/${schemeId}/attachments`, {
|
||||||
|
file: fileObject,
|
||||||
|
fileType: 'PDF',
|
||||||
|
sortOrder: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 错: 强制 Content-Type
|
||||||
|
http.post(url, formData, {
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 正确写法 ✅
|
||||||
|
|
||||||
|
```js
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file) // file 必须是 File / Blob 对象
|
||||||
|
formData.append('fileType', 'PDF') // PDF / IMAGE / OTHER
|
||||||
|
formData.append('sortOrder', 0)
|
||||||
|
|
||||||
|
// 关键: Content-Type 不要手动设
|
||||||
|
// 不传 headers, 让 browser 自动加 'multipart/form-data; boundary=----WebKitFormBoundary...'
|
||||||
|
http.post(`/admin/contract/scheme/${schemeId}/attachments`, formData)
|
||||||
|
|
||||||
|
// 或显式覆盖默认 application/json:
|
||||||
|
http.post(url, formData, {
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' } // 让 axios 自动加 boundary
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## 后端接口规约(不变)
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `file` | File (multipart) | ✅ | 文件本体 |
|
||||||
|
| `fileType` | String (form field) | 否 | `PDF` / `IMAGE` / `OTHER`,不传按扩展名推断 |
|
||||||
|
| `sortOrder` | Integer (form field) | 否 | 默认 0 |
|
||||||
|
|
||||||
|
## 验证 curl(后端确认 OK 的请求)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "https://api.test.1814.love:9443/admin/contract/scheme/3/attachments" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-F "file=@文明旅游倡议书.pdf;type=application/pdf" \
|
||||||
|
-F "fileType=PDF" \
|
||||||
|
-F "sortOrder=0"
|
||||||
|
```
|
||||||
|
|
||||||
|
返回:
|
||||||
|
```json
|
||||||
|
{"code":200,"data":{"attachmentId":"...","ossUrl":"...","fileSize":...},"success":true}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 后端配套(已部署测试服)
|
||||||
|
|
||||||
|
| PR | 改动 |
|
||||||
|
|----|------|
|
||||||
|
| #1640 | multipart 上限 50MB;超限友好提示 `code=100100` |
|
||||||
|
| #1641 | generic 500 错误暴露 exception class(本次定位就靠这个) |
|
||||||
|
|
||||||
|
@mmg 看下 hl-ui 里调 `/admin/contract/scheme/.../attachments` 的代码,把对象传参改成 FormData 即可。
|
||||||
正在加载...
x
在新工单中引用
屏蔽一个用户