Become a sponsor

概述
文件上传接口将文件保存到临时目录,返回文件 URL。前端拿到 URL 后提交表单时传给后端,BaseLogic 在 add/update 时自动迁移到正式目录。通过 type 参数可区分上传类型(文件/图片)。
| 项目 | 说明 |
|---|---|
| URL | POST /api/upload/uploadFile |
| Content-Type | multipart/form-data |
| 权限 | 无需认证(公开接口) |
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
file | File | 是 | - | 文件二进制数据 |
type | string | 否 | file | 上传类型:file(所有合法文件)/ image(仅图片) |
{
"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"
}
}| 字段 | 类型 | 说明 | 示例 |
|---|---|---|---|
fileUrl | string | 完整 URL(含域名) | https://cdn.example.com/temp/... |
filePath | string | 相对路径 | temp/20260920/a1b2c3d4e5f6.jpg |
originalName | string | 原始文件名 | photo.jpg |
fileSize | number | 文件大小(字节) | 102400 |
fileExtension | string | 扩展名 | jpg |
fileType | string | MIME 类型 | image/jpeg |
fileName | string | 服务器文件名(随机) | a1b2c3d4e5f6.jpg |
| type 值 | 说明 | 允许的后缀 | 适用场景 |
|---|---|---|---|
file(默认) | 所有合法文件 | 由 config/file.php 的 file_ext 控制 | 文档、压缩包等 |
image | 仅图片 | 由 config/file.php 的 image_ext 控制 | 头像、封面等 |
file_ext 默认值:
gif, jpg, jpeg, png, bmp, webp, svg,
doc, docx, xls, xlsx, ppt, pptx,
pdf, zip, rar, txt, csv,
mp3, mp4, sql, js, cssimage_ext 默认值:
gif, jpg, jpeg, png, bmp, webp, svg{
"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 |
<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>// 上传文件
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' },
});// 前端上传后拿到 fileUrl,提交表单时传给后端
await exampleAdd({
name: '测试',
avatar: 'https://cdn.example.com/temp/20260920/abc.jpg',
});
// 后端 BaseLogic::add() 自动:
// 1. 检测到 avatar 是临时文件 URL
// 2. 调用 save_file() 迁移到正式目录
// 3. 去掉域名,只存相对路径入库<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>前端上传 → temp/日期/xxx.jpg(临时目录)
│
▼ 提交表单(携带 URL)
BaseLogic::add()
│
├─ processFileFieldsOnSave()
│ └─ save_file() → 正式目录/日期/xxx.jpg
│
└─ 数据库存储相对路径
│
▼ 读取时
processFileFieldsOnRead()
└─ get_file_url() → 拼接域名返回完整 URL; .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 文件 |
原因: 上传接口需要认证,但请求头未携带 Token。
解决: 确保请求头包含 Authorization: Bearer <token>。
原因: 文件扩展名不在白名单中。
解决: 检查 .env 中 FILE.FILE_EXT 或 FILE.IMAGE_EXT 配置。
原因: DOMAIN_URL 配置错误或 Nginx 未配置静态文件处理。
解决: 检查 .env 中 FILE.DOMAIN_URL 是否正确,Nginx 是否配置了 /uploads/ 的 alias。
原因: PHP 或 Nginx 限制了上传大小。
解决:
; php.ini
upload_max_filesize = 20M
post_max_size = 25M
; .env
FILE.MAX_SIZE = 20# nginx.conf
client_max_body_size 20M;