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
在新工单中引用
屏蔽一个用户