📤 文件上传

ThinkAdmin 框架对文件上传功能进行了精心封装,提供安全、高效的文件上传解决方案。

📚 基础概念

🤔 基本介绍

文件上传是指用户将本地文件(图片、文档、视频等)通过浏览器上传到服务器的过程。ThinkAdmin 提供了完整的文件上传解决方案。

📋 上传流程

  1. 用户选择文件:在浏览器中选择要上传的文件
  2. 前端验证:检查文件类型、大小等
  3. 提交到服务器:通过 HTTP POST 请求提交文件
  4. 服务器处理:验证文件、保存文件、返回结果
  5. 返回给用户:显示上传结果(成功或失败)

🔒 安全考虑

  • 文件类型验证:只允许上传指定类型的文件
  • 文件大小限制:限制文件大小,防止服务器资源耗尽
  • 文件名处理:使用 hash 命名,防止文件名冲突和路径遍历攻击
  • 权限验证:只有登录用户才能上传文件

📖 相关概念

  • MIME 类型:文件的媒体类型,如 image/jpegapplication/pdf
  • 文件扩展名:文件名后缀,如 .jpg.pdf
  • 文件大小:文件占用的字节数
  • 文件 hash:文件的唯一标识,用于判断文件是否重复

🚀 主要功能

  • 多存储支持: 支持本地存储和多种云存储方案
  • 安全上传: 所有上传接口需要用户登录验证
  • 前端集成: 基于 admin.js$(element).uploadFile() 方法
  • 接口协同: 与 admin/api.upload/ 接口协同工作
  • 配置灵活: 支持后台动态配置存储参数
  • 性能优化: 支持 CDN 加速和文件秒传

📋 存储对比

存储方式不占服务器空间上传大文件支持CDN加速是否推荐
本地服务器存储
自建 Alist 存储✔️可配✔️
七牛云空间存储✔️✔️✔️✔️
又拍云USS存储✔️✔️✔️✔️
阿里云OSS存储✔️✔️✔️✔️
腾讯云COS存储✔️✔️✔️✔️

🌟 云存储

带宽优化

  • 本地存储问题: 文件加载消耗服务器带宽
  • 并发限制: 带宽达到上限影响其他用户访问
  • 用户体验: 等待时间过长影响用户体验
  • 服务器压力: 大量文件请求会增加服务器负载

云存储优势

  • 带宽释放: 文件加载不占用网站带宽,释放服务器资源
  • 性能提升: 云空间强大的存储和传输能力
  • CDN 加速: 支持 CDN 加速,提升加载速度,全球访问更快
  • 成本优化: 降低服务器带宽成本,按需付费更经济
  • 高可用性: 云存储提供高可用性和数据冗余保护
  • 扩展性强: 存储容量可随时扩展,无需担心空间不足

📤 上传接口

文件上传通过 admin/api.upload/ 接口处理,该接口需要用户登录验证。ThinkAdmin 已经内置了完整的文件上传接口,开发者可以直接使用,也可以自定义上传逻辑。

🔗 接口地址

标准上传接口

  • 接口路径: /admin/api.upload/index
  • 请求方法: POST
  • 需要登录: ✅ 所有上传接口都需要用户登录验证
  • 返回格式: JSON

为什么需要登录验证?

  • 防止未授权用户上传恶意文件
  • 记录上传操作日志,便于追踪
  • 控制上传权限,不同用户可能有不同的上传限制

📋 接口返回格式

上传成功后,接口会返回标准的 JSON 格式数据:

{
    "code": 1,
    "info": "上传成功",
    "data": {
        "url": "https://example.com/upload/image.jpg",
        "key": "image/ab/cd1234567890abcdef1234567890ab.jpg",
        "hash": "cd1234567890abcdef1234567890ab",
        "file": "image.jpg"
    }
}

返回字段说明

  • code: 状态码,1 表示成功,0 表示失败
  • info: 提示信息
  • data.url: 文件的完整访问地址(可以直接在浏览器中打开)
  • data.key: 文件的存储路径(用于后续操作,如删除)
  • data.hash: 文件的 hash 值(用于判断文件是否重复)
  • data.file: 原始文件名

💻 后端处理示例

ThinkAdmin 已经内置了文件上传接口,但如果你需要自定义上传逻辑,可以参考以下示例:

<?php
declare(strict_types=1);

namespace app\admin\controller\api;

use think\admin\Controller;
use think\admin\Storage;

/**
 * 文件上传接口(自定义示例)
 * @class Upload
 * @package app\admin\controller\api
 */
class Upload extends Controller
{
    /**
     * 文件上传处理
     * @login true  // 需要登录验证
     */
    public function index()
    {
        // 1. 获取上传的文件
        $file = $this->request->file('file');
        if (empty($file)) {
            $this->error('请选择要上传的文件');
        }
        
        // 2. 验证文件类型(可选,系统已内置验证)
        $allowExts = str2arr(sysconf('storage.allow_exts', 'jpg,png,gif'));
        $ext = strtolower($file->extension());
        if (!in_array($ext, $allowExts)) {
            $this->error('不支持的文件类型!');
        }
        
        // 3. 验证文件大小(可选,系统已内置验证)
        $maxSize = sysconf('storage.max_size', 2097152);  // 默认 2MB
        if ($file->getSize() > $maxSize) {
            $this->error('文件大小超过限制!');
        }
        
        // 4. 生成文件名称(使用 hash 命名,防止冲突)
        $filename = Storage::name($file->getPathname(), $ext, 'upload');
        // 生成规则:upload/ab/cd1234567890abcdef1234567890ab.jpg
        // 其中 ab/cd 是 hash 的前两位,用于目录分散
        
        // 5. 上传文件到存储(自动选择配置的存储方式)
        $result = Storage::instance()->set(
            $filename,
            file_get_contents($file->getPathname())
        );
        
        // 6. 返回结果
        if ($result) {
            $this->success('上传成功', $result);
        } else {
            $this->error('上传失败,请稍后重试!');
        }
    }
}

代码说明

  • $this->request->file('file'): 获取上传的文件对象
  • Storage::name(): 生成安全的文件名称(使用 hash 命名)
  • Storage::instance()->set(): 保存文件到配置的存储位置(本地或云存储)
  • $this->success(): 返回成功响应
  • $this->error(): 返回错误响应

📋 前端使用

ThinkAdmin 在 admin.js 中封装了文件上传功能,提供了简单易用的前端接口。开发者无需关心底层实现,只需调用相应的方法即可实现文件上传。

🎯 快速使用

ThinkAdmin 提供了三种快速上传方法,适用于常见场景:

// 方式1:上传单个视频
$(InputElement).uploadOneVideo();
// 适用于:视频上传场景

// 方式2:上传单个图片
$(InputElement).uploadOneImage();
// 适用于:头像、封面图等单图上传

// 方式3:上传多张图片
$(InputElement).uploadMultipleImage();
// 适用于:相册、商品图片等多图上传

使用示例

<!-- 单图片上传示例 -->
<input type="text" name="avatar" value="">
<script>
    // 初始化单图片上传
    $('[name=avatar]').uploadOneImage();
    
    // 监听上传完成事件
    $('[name=avatar]').on('change', function() {
        console.log('图片上传成功,地址:', this.value);
        // 可以在这里更新页面预览
    });
</script>

🔧 HTML 属性方式

除了 JavaScript 方法,还可以通过 HTML 的 data-* 属性来配置上传功能:

<!-- 基础用法 -->
<input type="text" name="FileInput">
<a data-file 
   data-type="png,jpg,jpeg" 
   data-field="FileInput">
   上传文件
</a>

属性说明

  • data-file: 标识这是一个上传按钮
  • data-type: 指定允许的文件类型(多个用逗号分隔)
  • data-field: 指定上传成功后,文件地址保存到哪个 input 元素

📡 上传事件

ThinkAdmin 提供了完整的上传事件系统,可以在上传的各个阶段执行自定义逻辑:

<input type="text" name="FileInput">
<a data-file data-type="png,jpg,jpeg" data-field="FileInput">上传文件</a>

<script>
    $(function () {
        // 1. 捕获文件上传结果(最简单的方式)
        $('[name=FileInput]').on('change', function () {
            // 文件上传成功后,input 的值会自动更新为文件地址
            console.log('文件地址:', this.value);
            // 可以在这里更新页面预览、提交表单等
        });

        // 2. 监听上传过程的各个事件(高级用法)
        $('[data-file]')
            // 文件选择后触发
            .on('upload.choose', function (files) {
                console.log('选择了文件:', files);
                // 可以在这里进行文件预检查
            })
            // 文件 hash 计算完成后触发(用于秒传功能)
            .on('upload.hash', function (event, file) {
                console.log('文件 hash:', file.hash);
                // 可以在这里检查文件是否已存在
            })
            // 上传进度事件(大文件上传时很有用)
            .on('upload.progress', function (event, obj) {
                var percent = obj.number;  // 上传进度 0-100
                console.log('上传进度:', percent + '%');
                // 可以在这里更新进度条
                $('#progress-bar').css('width', percent + '%');
            })
            // 单个文件上传成功(多文件上传时,每个文件成功都会触发)
            .on('upload.done', function (event, obj) {
                console.log('文件上传成功:', obj.file.name);
                console.log('文件地址:', obj.data.url);
                // 可以在这里处理每个文件的上传结果
            })
            // 所有文件上传完成(多文件上传时,所有文件完成后触发)
            .on('upload.complete', function (event) {
                console.log('所有文件上传完成');
                // 可以在这里执行后续操作,如提交表单
            });
    });
</script>

事件说明

  • upload.choose: 用户选择文件后触发,可以用于文件预检查
  • upload.hash: 文件 hash 计算完成后触发,用于秒传功能
  • upload.progress: 上传进度事件,适用于大文件上传
  • upload.done: 单个文件上传成功,多文件上传时每个文件都会触发
  • upload.complete: 所有文件上传完成,适用于多文件上传场景

⚙️ 参数配置

ThinkAdmin 支持通过 HTML 的 data-* 属性来配置上传行为,这种方式简单直观,适合快速开发。

⚠️ 注意事项

  • 如果上传组件包含文件选择按钮(BUTTON),建议将属性直接设置在按钮上
  • 使用 data-field 属性绑定表单元素,上传成功后会自动更新 input 的值并触发 change 事件

📤 上传类型

通过 data-file 属性指定上传类型:

<!-- 单文件上传 -->
<a data-file="one" data-field="FileInput">上传单个文件</a>

<!-- 多文件上传 -->
<a data-file="mut" data-field="FileInput">上传多个文件</a>

<!-- 单图上传(带图片选择器) -->
<a data-file="image" data-field="FileInput">选择图片</a>

<!-- 多图上传(带图片选择器) -->
<a data-file="images" data-field="FileInput">选择多张图片</a>

参数说明

  • data-file="one": 单文件上传,用户只能选择一个文件
  • data-file="mut": 多文件上传,用户可以选择多个文件
  • data-file="image": 单图上传,带图片预览和选择器
  • data-file="images": 多图上传,带图片预览和选择器

🔒 安全限制

通过以下属性配置上传的安全限制:

<!-- 安全模式:文件保存到安全目录,不能直接通过 URL 访问 -->
<a data-file data-safe="true" data-field="FileInput">安全上传</a>

<!-- 限制文件大小:单位 KB,1024 表示 1MB -->
<a data-file data-size="1024" data-field="FileInput">限制 1MB</a>

<!-- 限制文件类型:只允许上传指定后缀的文件 -->
<a data-file data-type="png,jpg,gif,jpeg" data-field="FileInput">只允许图片</a>

<!-- 绑定表单元素:上传成功后,文件地址保存到这里 -->
<a data-file data-field="FileInput" data-type="pdf">上传 PDF</a>

参数说明

  • data-safe="true": 安全模式,文件保存到安全目录(仅本地存储支持)
  • data-size="1024": 限制文件大小,单位 KB
  • data-type="png,jpg": 限制文件类型,多个用逗号分隔
  • data-field="FileInput": 指定上传成功后,文件地址保存到哪个 input

☁️ 存储方式

可以指定使用特定的存储方式,不填写则使用后台默认配置:

<!-- 使用本地服务器存储 -->
<a data-file data-uptype="local" data-field="FileInput">本地存储</a>

<!-- 使用七牛云存储 -->
<a data-file data-uptype="qiniu" data-field="FileInput">七牛云</a>

<!-- 使用腾讯云 COS -->
<a data-file data-uptype="txcos" data-field="FileInput">腾讯云</a>

<!-- 使用又拍云 USS -->
<a data-file data-uptype="upyun" data-field="FileInput">又拍云</a>

<!-- 使用阿里云 OSS -->
<a data-file data-uptype="aliyun" data-field="FileInput">阿里云</a>

使用场景

  • 某些文件需要存储到特定位置时,可以指定存储方式
  • 不填写则使用后台系统配置的默认存储方式

🖼️ 图片处理

ThinkAdmin 支持图片压缩和裁剪功能,可以在上传时自动处理:

<!-- 图片压缩:压缩质量 0.1-1.0,值越小越模糊 -->
<a data-file="image" 
   data-quality="0.8" 
   data-field="FileInput">
   压缩上传
</a>

<!-- 限制图片尺寸:自动缩放 -->
<a data-file="image" 
   data-max-width="800" 
   data-max-height="600" 
   data-field="FileInput">
   限制尺寸
</a>

<!-- 图片裁剪:自动裁剪到指定尺寸 -->
<a data-file="image" 
   data-cut-width="400" 
   data-cut-height="400" 
   data-field="FileInput">
   裁剪上传
</a>

参数说明

  • data-quality="0.8": 图片压缩质量,0.1-1.0,值越小越模糊但文件越小
  • data-max-width="800": 限制图片最大宽度(单位 px,不需要写单位)
  • data-max-height="600": 限制图片最大高度(单位 px,不需要写单位)
  • data-cut-width="400": 图片裁剪宽度(单位 px)
  • data-cut-height="400": 图片裁剪高度(单位 px)

实际应用

  • 头像上传:使用 data-cut-widthdata-cut-height 裁剪成正方形
  • 缩略图:使用 data-max-widthdata-max-height 限制尺寸
  • 节省空间:使用 data-quality 压缩图片

📁 存储路径

可以指定文件存储的前缀路径:

<!-- 指定存储前缀 -->
<a data-file data-path="avatar" data-field="FileInput">头像上传</a>
<!-- 文件会保存到:avatar/ab/cd1234567890abcdef1234567890ab.jpg -->

<a data-file data-path="document" data-field="FileInput">文档上传</a>
<!-- 文件会保存到:document/ab/cd1234567890abcdef1234567890ab.pdf -->

使用场景

  • 按业务类型分类存储文件
  • 便于文件管理和清理

📝 完整案例

🖼️ 案例1:头像上传

头像上传通常需要裁剪成正方形,并限制文件大小:

<!-- HTML 部分 -->
<div class="avatar-upload">
    <input type="hidden" name="avatar" value="">
    <img id="avatar-preview" src="/static/images/default-avatar.png" style="width: 100px; height: 100px;">
    <button type="button" 
            data-file="image" 
            data-field="avatar"
            data-type="jpg,png"
            data-size="512"
            data-cut-width="200"
            data-cut-height="200">
        选择头像
    </button>
</div>

<script>
    $(function() {
        // 监听上传成功事件
        $('[name=avatar]').on('change', function() {
            var url = this.value;
            // 更新预览图
            $('#avatar-preview').attr('src', url);
        });
    });
</script>

说明

  • data-cut-width="200"data-cut-height="200": 裁剪成 200x200 的正方形
  • data-size="512": 限制文件大小不超过 512KB
  • data-type="jpg,png": 只允许上传 JPG 和 PNG 格式

📸 案例2:商品图片上传

商品图片通常需要多张,并且需要限制尺寸:

<!-- HTML 部分 -->
<div class="goods-images">
    <input type="hidden" name="images" value="">
    <div id="image-list"></div>
    <button type="button" 
            data-file="images" 
            data-field="images"
            data-type="jpg,png"
            data-max-width="800"
            data-max-height="800">
        添加图片
    </button>
</div>

<script>
    $(function() {
        // 监听上传成功事件
        $('[name=images]').on('change', function() {
            var images = this.value.split(',');
            var html = '';
            images.forEach(function(url) {
                if (url) {
                    html += '<img src="' + url + '" style="width: 100px; margin: 5px;">';
                }
            });
            $('#image-list').html(html);
        });
    });
</script>

说明

  • data-file="images": 多图上传
  • data-max-width="800"data-max-height="800": 自动缩放,最大 800px
  • 多图上传时,文件地址用逗号分隔存储在 input 中

📄 案例3:文档上传

文档上传通常需要限制文件类型和大小:

<!-- HTML 部分 -->
<input type="text" name="document" value="" readonly>
<a data-file="one" 
   data-field="document"
   data-type="pdf,doc,docx"
   data-size="5120">
    上传文档(最大 5MB)
</a>

<script>
    $(function() {
        // 监听上传成功事件
        $('[name=document]').on('change', function() {
            console.log('文档上传成功:', this.value);
            // 可以在这里显示文档名称或下载链接
        });
    });
</script>

说明

  • data-type="pdf,doc,docx": 只允许上传 PDF 和 Word 文档
  • data-size="5120": 限制文件大小不超过 5MB(5120KB)

🎬 案例4:视频上传

视频上传通常文件较大,需要显示上传进度:

<!-- HTML 部分 -->
<input type="text" name="video" value="">
<button type="button" 
        data-file="one" 
        data-field="video"
        data-type="mp4,avi,mov"
        data-size="102400">
    上传视频(最大 100MB)
</button>
<div id="progress" style="display: none;">
    <div class="progress-bar" style="width: 0%; background: #5FB878;">0%</div>
</div>

<script>
    $(function() {
        // 监听上传进度
        $('[data-file]').on('upload.progress', function(event, obj) {
            var percent = obj.number;
            $('#progress').show();
            $('.progress-bar').css('width', percent + '%').text(percent + '%');
        });
        
        // 监听上传完成
        $('[data-file]').on('upload.complete', function() {
            $('#progress').hide();
            alert('视频上传完成!');
        });
    });
</script>

说明

  • data-size="102400": 限制文件大小不超过 100MB
  • 使用 upload.progress 事件显示上传进度
  • 使用 upload.complete 事件处理上传完成后的操作

🔒 案例5:安全文件上传

某些敏感文件需要保存到安全目录,不能直接通过 URL 访问:

<!-- HTML 部分 -->
<input type="hidden" name="secure_file" value="">
<a data-file="one" 
   data-field="secure_file"
   data-safe="true"
   data-type="txt,key">
    上传安全文件
</a>

<script>
    $(function() {
        $('[name=secure_file]').on('change', function() {
            // 安全文件不能直接通过 URL 访问
            // 需要通过后端接口验证权限后才能下载
            console.log('安全文件已上传,key:', this.value);
        });
    });
</script>

说明

  • data-safe="true": 启用安全模式,文件保存到安全目录
  • 安全文件不能直接通过 URL 访问,需要通过后端接口验证权限
  • 适用于:密钥文件、配置文件等敏感文件

❓ 常见问题

Q1:上传失败,提示"不支持的文件类型"?

原因:文件类型不在允许列表中。

解决方法

  1. 检查 data-type 属性是否包含该文件类型
  2. 检查后台系统配置中的允许文件类型
  3. 确保文件扩展名正确(如 .jpg 而不是 .jpeg

Q2:上传大文件时很慢?

原因:大文件上传需要时间,这是正常现象。

优化建议

  1. 使用云存储(支持大文件上传)
  2. 显示上传进度(使用 upload.progress 事件)
  3. 考虑使用分片上传(需要自定义实现)

Q3:如何实现文件秒传?

原理:通过文件 hash 值判断文件是否已存在。

实现方式

$('[data-file]').on('upload.hash', function(event, file) {
    // 检查文件是否已存在
    $.post('/admin/api/check-file', {hash: file.hash}, function(result) {
        if (result.exists) {
            // 文件已存在,直接使用已有文件
            $('[name=file]').val(result.url).trigger('change');
        }
    });
});

Q4:如何自定义上传接口?

方法:创建自定义上传控制器,参考"后端处理示例"部分。

注意事项

  • 需要实现登录验证
  • 需要验证文件类型和大小
  • 需要使用 Storage 类保存文件
  • 需要返回标准格式的 JSON 响应

Q5:云存储配置在哪里?

位置:后台管理系统 → 系统管理 → 文件存储配置

配置项

  • 存储类型(本地、七牛云、阿里云等)
  • 访问密钥
  • 存储区域
  • CDN 域名等
最近更新:
Contributors: 邹景立, Anyon