Skip to content

8.4 文件上传接口 ​

概述

文件上传接口将文件保存到临时目录,返回文件 URL。前端拿到 URL 后提交表单时传给后端,BaseLogic 在 add/update 时自动迁移到正式目录。通过 type 参数可区分上传类型(文件/图片)。

接口信息 ​

项目说明
URLPOST /api/upload/uploadFile
Content-Typemultipart/form-data
权限无需认证(公开接口)

请求参数 ​

参数类型必填默认值说明
fileFile是-文件二进制数据
typestring否file上传类型:file(所有合法文件)/ image(仅图片)

成功响应 ​

json
{
    "code": 0,
    "ok": true,
    "msg": "上传成功",
    "data": {
        "fileUrl": "https://cdn.example.com/temp/20260920/a1b2c3d4e5f6.jpg",
        "filePath": "temp/20260920/a1b2c3d4e5f6.jpg",
        "originalName": "photo.jpg",
        "fileSize": 102400,
        "fileExtension": "jpg",
        "fileType": "image/jpeg",
        "fileName": "a1b2c3d4e5f6.jpg"
    }
}

响应字段 ​

字段类型说明示例
fileUrlstring完整 URL(含域名)https://cdn.example.com/temp/...
filePathstring相对路径temp/20260920/a1b2c3d4e5f6.jpg
originalNamestring原始文件名photo.jpg
fileSizenumber文件大小(字节)102400
fileExtensionstring扩展名jpg
fileTypestringMIME 类型image/jpeg
fileNamestring服务器文件名(随机)a1b2c3d4e5f6.jpg

type 参数说明 ​

type 值说明允许的后缀适用场景
file(默认)所有合法文件由 config/file.php 的 file_ext 控制文档、压缩包等
image仅图片由 config/file.php 的 image_ext 控制头像、封面等

file_ext 默认值:

text
gif, jpg, jpeg, png, bmp, webp, svg,
doc, docx, xls, xlsx, ppt, pptx,
pdf, zip, rar, txt, csv,
mp3, mp4, sql, js, css

image_ext 默认值:

text
gif, jpg, jpeg, png, bmp, webp, svg

失败响应 ​

json
{
    "code": 1,
    "ok": false,
    "msg": "不允许的文件类型: .exe",
    "data": null
}

常见错误 ​

错误信息原因解决方案
未接收到上传文件表单字段名不是 file检查 FormData 的 key 是否为 file
不允许的文件类型: .xxx后缀不在白名单检查 config/file.php 的 image_ext/file_ext
文件大小超过限制超过 max_size调整 .env 中 FILE.MAX_SIZE 配置
图片文件内容无效图片损坏或非图片伪装重新选择文件
文件上传失败PHP 上传错误检查 php.ini 的 upload_max_filesize

前端使用 ​

Element Plus Upload 组件 ​

vue
<template>
  <!-- 上传文件(默认 type=file) -->
  <el-upload
    :action="'/api/upload/uploadFile'"
    :headers="{ Authorization: `Bearer ${token}` }"
    :on-success="handleUploadSuccess"
    :before-upload="beforeUpload"
  >
    <el-button type="primary">上传文件</el-button>
  </el-upload>

  <!-- 上传图片(指定 type=image) -->
  <el-upload
    :action="'/api/upload/uploadFile?type=image'"
    :headers="{ Authorization: `Bearer ${token}` }"
    :on-success="handleImageSuccess"
    accept="image/*"
  >
    <el-button type="primary">上传图片</el-button>
  </el-upload>
</template>

<script setup>
const token = localStorage.getItem('access_token');

const beforeUpload = (file) => {
  const isLt10M = file.size / 1024 / 1024 < 10;
  if (!isLt10M) {
    ElMessage.error('文件大小不能超过 10MB');
  }
  return isLt10M;
};

const handleUploadSuccess = (response) => {
  if (response.code === 0) {
    form.value.file = response.data.fileUrl;
  }
};

const handleImageSuccess = (response) => {
  if (response.code === 0) {
    form.value.avatar = response.data.fileUrl;
  }
};
</script>

手动上传 ​

typescript
// 上传文件
const formData = new FormData();
formData.append('file', file);

const res = await http.request({
    url: '/upload/uploadFile',
    method: 'POST',
    data: formData,
    headers: { 'Content-Type': 'multipart/form-data' },
});
// res.fileUrl = "https://cdn.example.com/temp/20260920/abc.jpg"

// 上传图片(指定 type=image)
const res2 = await http.request({
    url: '/upload/uploadFile?type=image',
    method: 'POST',
    data: formData,
    headers: { 'Content-Type': 'multipart/form-data' },
});

提交表单时传 URL ​

typescript
// 前端上传后拿到 fileUrl,提交表单时传给后端
await exampleAdd({
    name: '测试',
    avatar: 'https://cdn.example.com/temp/20260920/abc.jpg',
});

// 后端 BaseLogic::add() 自动:
// 1. 检测到 avatar 是临时文件 URL
// 2. 调用 save_file() 迁移到正式目录
// 3. 去掉域名,只存相对路径入库

头像上传示例 ​

vue
<template>
  <div class="avatar-upload">
    <el-avatar :src="form.avatar" :size="80" />
    <el-upload
      :action="'/api/upload/uploadFile?type=image'"
      :headers="{ Authorization: `Bearer ${token}` }"
      :show-file-list="false"
      :on-success="handleAvatarSuccess"
      accept="image/*"
    >
      <el-button size="small">更换头像</el-button>
    </el-upload>
  </div>
</template>

<script setup>
const form = ref({ avatar: '' });

const handleAvatarSuccess = (response) => {
  form.value.avatar = response.data.fileUrl;
};
</script>

上传后处理流程 ​

text
前端上传 → temp/日期/xxx.jpg(临时目录)
    │
    ▼ 提交表单(携带 URL)
BaseLogic::add()
    │
    ├─ processFileFieldsOnSave()
    │   └─ save_file() → 正式目录/日期/xxx.jpg
    │
    └─ 数据库存储相对路径
         │
         ▼ 读取时
    processFileFieldsOnRead()
        └─ get_file_url() → 拼接域名返回完整 URL

配置项 ​

ini
; .env
[FILE]
UPLOAD_DIR = D:/uploads/rxthinkcmf      ; 上传根目录
DOMAIN_URL = https://cdn.example.com    ; 文件访问域名
MAX_SIZE = 10                           ; 单文件最大 10MB
IMAGE_EXT = jpg,jpeg,png,gif,bmp,webp,svg
FILE_EXT = jpg,jpeg,png,gif,bmp,webp,svg,doc,docx,xls,xlsx,ppt,pptx,pdf,zip,rar,txt,csv,mp3,mp4,sql,js,css

安全设计 ​

安全措施说明
文件类型校验同时检查后缀名和 MIME 类型(finfo),防止伪造扩展名
文件大小限制通过 config/file.php 的 max_size 配置
图片内容校验图片类型额外用 getimagesize 验证,防止非图片伪装
随机文件名使用 bin2hex(random_bytes(16)) 防止文件名冲突和路径遍历
临时目录隔离上传文件先存入 temp/ 目录,提交表单后才迁移到正式目录
Nginx 防护上传目录禁止执行 PHP 文件

常见问题 ​

问题 1:上传返回 401 ​

原因: 上传接口需要认证,但请求头未携带 Token。

解决: 确保请求头包含 Authorization: Bearer <token>。

问题 2:上传返回"不允许的文件类型" ​

原因: 文件扩展名不在白名单中。

解决: 检查 .env 中 FILE.FILE_EXT 或 FILE.IMAGE_EXT 配置。

问题 3:上传后图片不显示 ​

原因: DOMAIN_URL 配置错误或 Nginx 未配置静态文件处理。

解决: 检查 .env 中 FILE.DOMAIN_URL 是否正确,Nginx 是否配置了 /uploads/ 的 alias。

问题 4:大文件上传失败 ​

原因: PHP 或 Nginx 限制了上传大小。

解决:

ini
; php.ini
upload_max_filesize = 20M
post_max_size = 25M

; .env
FILE.MAX_SIZE = 20
nginx
# nginx.conf
client_max_body_size 20M;

小蚂蚁云团队 · 提供技术支持