关于使用 fuelphp 文件上传的注意事项

大家好,
我是开发团队野生队的成员 Mandai。
使用 fuelphp 上传文件非常简单,通常情况下,直接从文档复制粘贴代码就能正常工作。
但是,我在尝试修改代码时遇到了很多问题,所以我编写了这份指南。
准备文件上传(HTML + CSS + JS)
在实现文件上传功能之前,请准备好前端材料,例如 HTML。
我们先来看HTML代码。
这是一个位于form标签内的input标签。
<form id="form" name="form" method="post" enctype="multipart/form-data"><input type="file" name="f" class="hidden" id="f" multiple="multiple"><label for="f"><span class="btn">文件选择</span><span class="selected" id="f_selected">未选择</span></label></form>
接下来是CSS。
input.hidden { /* display: none;*/ } label > span.btn { display: inline-block; border: 1px solid #999; font: 13.333px arial; height: 21px; line-height: 21px; padding-left: 0.25em; padding-right: 0.25em; border-radius: 2px; background: -webkit-linear-gradient(#fcfcfc, #dcdcdc); background: linear-gradient(#fcfcfc, #dcdcdc); } label > span.btn:active { background: -webkit-linear-gradient(#dcdcdc, #fcfcfc); background: linear-gradient(#dcdcdc, #fcfcfc); } label > span.btn:hover { border: 1px solid #666; } label > span.selected { display: inline-block; font: 13.333px arial; height: 21px; line-height: 21px; } label { -moz-user-select: none; -khtml-user-select: none; -webkit-user-select: none; user-select: none; }
最后,是 JavaScript。
(function(){ var f = document.getElementById('f'); var label_selected = document.getElementById(f.id + '_selected'); f.addEventListener('change', function(){ if (f.files.length == 0){ label_selected.textContent = '未选择'; } else if (f.files.length == 1){ label_selected.textContent = f.files[0].name; } else { label_selected.textContent = f.files.length + '文件'; } }); })();
如果你用浏览器查看,它看起来会是这样。我推荐使用 Chrome
浏览器。原因显而易见。
所以,这里有两个文件上传按钮。
两个按钮都可以使用。
左边是实物,右边是贴了标签的复制品。我
只是想这么做而已。
不同浏览器对表单组件的显示方式有所不同,而且我们也希望文件上传按钮本身的设计能够与整体风格保持一致,因此这种技巧非常实用。
使用 CSS + JS 可以实现大致相同的行为和外观,在这种情况下,它类似于 Chrome 的表单组件。
如果在 CSS 中给 input 标签添加“display: none”,它的行为将与普通表单完全相同。
(此处已注释掉。)
重要的是不要忘记表单标签中的 enctype 属性。
正面已经准备就绪。
实施 fuelphp(准备工作)
在实施之前,我们将准备各种设置和模型。
FuelPHP 采用配置优先的设计理念,因此您可以通过配置文件大幅改变其行为。
首先,在上传之前,请准备好要上传的配置文件。
原始配置文件位于“fuel/core/config/upload.php”,因此请将其复制到“fuel/app/config”并更改设置。
必需的设置是路径。
如果将保存位置设置为公开位置,下载机制会很简单,但将包含个人信息的 PDF 文件存储在公开位置很危险,因为如果 URL 被知晓,就可以随意下载。
如果您需要控制访问权限,可以创建一个类似“files above public”这样的目录并进行指定。
这样一来,外部就无法访问该目录,您也无法下载它,因此可能会增加一些开销。不过,您可以通过创建一个单独的控制器来生成下载 URL 来解决这个问题。
这与文件上传的主题略有偏离,所以我希望以后有机会专门写一篇文章来探讨这个问题。
这次,我们将在与 public 目录同级(直接位于项目根目录下)创建一个 files 目录,并将文件保存在那里。
此外,通过在配置文件中将 randomize 设置为 true,将要保存的文件名设置为 32 个字符的 MD5 哈希值。
修改后的 config/upload.php 文件现在如下所示:
return array( 'path' => '/var/www/html/test/files', 'randomize' => true, );
接下来,我们创建一个模型。
它的主要作用是保存文件名,文件名将与一个哈希值一起保存,从而使原始文件名无法识别。
用于创建上传表以保存数据的 SQL 语句如下:
CREATE TABLE `uploads` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `origin_name` varchar(256) NOT NULL, `file_name` varchar(256) NOT NULL, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', PRIMARY KEY (`id`), KEY `idx_file_name` (`file_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
执行完这条 SQL 语句后,最快的方法是使用以下 oil 命令创建模型:
php /path/to/oil r fromdb:model uploads
另外,由于我们将使用 ORM 包,因此需要修改 config.php 文件中的 always_load 项。
取消注释 packages 部分即可使其自动加载。
fuelphp 实现(控制器)
接下来我们将实现控制器在数据发布时的行为。
控制器中编写的源代码如下:
function post_index() { $data = []; $this->template->title = 'test'; /* * 随机化后,文件名将如下所示: * [0-9a-f]{32}.[ext] * 由于原始文件名将保存在单独的字段中,因此在保存之前,我们将引入一个处理,将文件名转换为哈希值 */ Upload::register('before', function(&$file) { $file['filename'] = substr($file['filename'], 0, 32); $file['path'] .= $file['filename'][0]. '/'. $file['filename'][1]. '/'; }); if (Upload::is_valid()) { Upload::save(); foreach (Upload::get_files() as $file) { Model_Upload::add($file); } } foreach (Upload::get_errors() as $file) { // 错误处理 } $this->template->content = View::forge('upload/index', $data); }
如果在 upload.php 的配置中将“auto_process”设置为 true(默认值),则无需显式编写 Upload::process() 函数。
文档,当“auto_process”为 true 时,如果运行 Upload::process() 函数,它将运行两次。
由于配置可以通过 Upload::process() 覆盖,如果文件上传处理在多个地方进行描述,最好将 auto_process 设置为 false 并每次都运行 Upload::process()。
Upload::register() 方法用于在保存文件之前中断上传过程。
第一个参数可以从 'validate'、'before' 或 'after' 中选择。
在本例中,我们选择 'before',因为我们希望在保存之前更改文件名和保存路径。
如果在 config/upload.php 文件中将 randomize 设置为 true,文件名将是一个 32 个字符的哈希值,但会保留原始文件扩展名。
为了将文件名统一为 32 个字符,我们添加了一个将文件名转换为哈希值的流程。
此外,对于保存目标位置,系统会使用哈希值的前两个字符创建一个目录,并将其设置为路径。
如果上传的文件数量较少,则此操作并非特别必要;但如果上传的文件数量很大,例如存储数万个文件,则读写响应速度会显著下降。
为了解决这个问题,我们添加了处理机制,将文件数量分散到单个目录中。
Upload::register() 函数闭包中的 $file 参数指向 fuel/vendor/fuelphp/upload/src/File.php 文件中编写的类,似乎无法从外部访问该类。
如果在保存之前编辑该类,则可以修改保存路径和文件。
如果你导出文件 $file,你会看到类似这样的内容:
Fuel\Upload\File::__set_state(array( 'container' => array ( 'element' => 'f', 'filename' => 'e9477391cbeefc40e69f7e1bc24868d2', 'name' => 'test.html', 'type' => 'text/html', 'tmp_name' => '/tmp/phpDsDmiL', 'error' => 0, 'size' => 335, 'extension' => 'html', 'basename' => 'test', 'mimetype' => 'text/html', 'path' => '/home/vagrant/workspace/test/files/e/9/', ), 'index' => 0, 'errors' => array ( ), 'config' => array ( 'langCallback' => '\\Upload::lang_callback', 'moveCallback' => NULL, 'max_size' => 0, 'max_length' => 0, 'ext_whitelist' => array ( ), 'ext_blacklist' => array ( ), 'type_whitelist' => array ( ), 'type_blacklist' => array ( ), 'mime_whitelist' => array ( ), 'mime_blacklist' => array ( ), 'prefix' => '', 'suffix' => '', 'extension' => '', 'randomize' => true, 'normalize' => false, 'normalize_separator' => '_', 'change_case' => false, 'path' => '/var/www/html/test/files', 'create_path' => true, 'path_chmod' => 511, 'file_chmod' => 438, 'auto_rename' => true, 'new_name' => false, 'overwrite' => false, ), 'isValidated' => true, 'isValid' => true, 'callbacks' => array ( 'before_validation' => array ( ), 'after_validation' => array ( ), 'before_save' => array ( 0 => Closure::__set_state(array( )), ), 'after_save' => array ( ), ), ))
这有点令人困惑,但 Upload::get_files() 获取的文件信息并非上述类,而只是 Fuel\Upload\File 的容器部分。
此外,即使修改 Upload::get_files() 中的信息,也无法更改实际的文件信息,因此似乎只能在 before 事件中修改上传文件的信息。
文档的注册方法部分对此有更详细的解释
就这样。
0