---
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 即可。
