Swoft 文件上传

文件上传在HTTP服务器中是最常见的一种的业务需求,Swoft本身为文件上传提供了支持。

业务需求

现在需要附件上传的功能,前端使用AJAX对图片进行上传,为此需要提供一个接口,接口的路径为admin/attachment/upload,接收HTTP POST提交的文件上传信息。

实现流程

  1. 使用HTTP服务器中Request类提供的file()方法接口POST发送过来的文件上传数据
  2. file()方法中获取上传文件相关数据
  3. 使用moveTo()方法将上传过来的临时文件转移到目标位置

关键点

  • 获取表单数据
  • 获取上传数据
  • 移动临时文件

最终效果

4933701-84dec488e8916451.png
附件管理

数据结构

CREATE TABLE `web_attachment` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '名称',
  `type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '附件类型',
  `mimetype` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '媒体类型',
  `hash` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '文件哈希',
  `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '文件地址',
  `size` int(10) unsigned DEFAULT '0' COMMENT '文件大小',
  `storage` enum('local','upyun','qiniu') CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT 'local' COMMENT '存储位置',
  `create_time` int(10) unsigned DEFAULT '0' COMMENT '创建日期',
  `create_date` datetime DEFAULT NULL COMMENT '创建日期',
  `update_time` int(10) unsigned DEFAULT '0' COMMENT '更新时间',
  `update_date` datetime DEFAULT NULL COMMENT '更新日期',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='附件表';

实现代码

<?php
namespace App\Controllers\Admin;

use App\Models\Entity\WebAttachment;
use Swoft\Http\Message\Server\Request;
use Swoft\Http\Message\Server\Response;
use Swoft\Http\Server\Bean\Annotation\Controller;
use Swoft\Http\Server\Bean\Annotation\RequestMapping;
use Swoft\Http\Server\Bean\Annotation\RequestMethod;
use Swoft\View\Bean\Annotation\View;

/**
 * 控制器 附件
 * Class GroupController
 * @Controller(prefix="/admin/attachment")
 * @package App\Controllers\Admin
 */
class AttachmentController
{
    /**
     * @RequestMapping(route="upload", method={RequestMethod::POST})
     * @param Request $request
     */
    public function upload(Request $request)
    {
        if(!$request->isAjax()){
            return response()->json(["error"=>1]);
        }

        $file = $request->file("file");
        if(empty($file)){
            return response()->json(["error"=>1]);
        }

        $arr = $file->toArray();
        if(isset($arr["error"]) && $arr["error"] != 0){
            return response()->json(["error"=>1]);
        }
        $error = $arr["error"];
        $name = $arr["name"];
        $type = $arr["type"];
        $size = $arr["size"];

        $hash = sha1(time().uniqid());
        $ext = end(explode("/",$type));
        $filename = $hash.".".$ext;

        $datedir = date("Ymd");
        $target_dir = BASE_PATH."/public/upload/{$datedir}";
        if(!is_dir($target_dir)){
            mkdir($target_dir);
        }

        $target_path = $target_dir."/".$filename;
        $result = $file->moveTo($target_path);

        $url = "/upload/{$datedir}/{$filename}";

        $time = time();
        $date = date("Y-m-d H:i:s", $time);

        $model = new WebAttachment();
        $model["name"] = $name;
        $model["type"] = "image";
        $model["url"] = $url;
        $model["hash"] = $hash;
        $model["mimetype"] = $type;
        $model["size"] = $size;
        $model["storage"] = 'local';
        $model["create_time"] = $time;
        $model["create_date"] = $date;
        $pk = $model->save()->getResult();
        if($pk > 0){
            return response()->json(["error"=>0, "hash"=>$hash]);
        }else{
            return response()->json(["error"=>1]);
        }
    }
}

接收表单数据

使用HTTPRequest对象中的file()方法接收表单POST的数据

$file = $request->file("file");

首先看下file()方法,位于swoft\wendor\http-message\src\Server\Concerns\InteractsWithInput.php文件中。

/**
 * Retrieve a upload item from the request
 * 从HTTP的Request请求中检索一个文件上传项
 * @param string|null $key
 * @param null        $default
 * @return array|\Swoft\Web\UploadedFile|null
 */
public function file(string $key = null, $default = null)
{
    if (is_null($key)) {
        return $this->getUploadedFiles();
    }
    return $this->getUploadedFiles()[$key] ?? $default;
}

file()方法会从HTTPRequest请求中检索一个文件上传项,方法接收一个字符串的key的键名和一个默认值default

string $key = null // null值是NULL类型的唯一值,可表示尚未被赋值,同unset()后的效果一样。

$default = null

这里的键名key实际上指的是前端表单file上传文本域中name属性的值,这里可以将变量$key视为upload

<input type="file" name="upload" value="" />

接着通过getUploadedFiles()方法获取表单上传的文件数据

$this->getUploadedFiles()

getUploadedFiles()来源于HTTPRequest类,位于swoft\wendor\http-message\src\Server\Request.php,其原型为:

/**
 * Retrieve normalized file upload data.
 * This method returns upload metadata in a normalized tree, with each leaf
 * an instance of Psr\Http\Message\UploadedFileInterface.
 * These values MAY be prepared from $_FILES or the message body during
 * instantiation, or MAY be injected via withUploadedFiles().
 *
 * @return array An array tree of UploadedFileInterface instances; an empty
 *     array MUST be returned if no data is present.
 */
public function getUploadedFiles()
{
    return $this->uploadedFiles;
}

简单来说,getUploadedFiles()方法就是获取表单上传的所有文件信息,在PHP中提供了全局变量$_FILES用于保存上传文件的数据。注意uploadedFiles属性是复数,说明是一个数组,而且是私有的。

private $uploadedFiles = [];

实际上,通过getUploadedFiles()最终会获取的是Swoft\Http\Message\Upload\UploadedFile类的对象。

获取上传文件数据

UploadedFile类是用来处理文件上传工作的核心,我们通过它来获取上传成功后的临时文件的信息,并将临时文件转移到目标文件。UploadedFile类位于swoft\wendor\http-message\src\Upload\UploadedFile.php文件中。

$file = $request->file("upload");
$arr = $file->toArray();

通过UploadedFile对象的toArray()方法,可以获取上传成功后临时文件相关信息。

public function toArray()
{
    return [
        'name' => $this->getClientFilename(),
        'type' => $this->getClientMediaType(),
        'tmp_file' => $this->tmpFile,
        'error' => $this->getError(),
        'size' => $this->getSize(),
    ];
}

其中主要包括五项数据

  • name 客户端上传文件的原始文件名,注意只是文件名称,包含文件后缀。
  • type 上传文件的媒体类型,也就是MIME (Multipurpose Internet Mail Extensions) 类型。
  • tmp_file 上传成功后保存在服务器中的临时文件路径
  • error 上传错误信息
  • size 原始文件大小,单位字节(byte)。

可以发现$_FILES全局变量中保存的数据与它一致。

$_FILES["upload"]["name"] //被上传文件的名称
$_FILES["upload"]["type"] //被上传文件的类型
$_FILES["upload"]["size"] //被上传文件的大小,以字节计
$_FILES["upload"]["tmp_name"] //存储在服务器的文件的临时副本的名称
$_FILES["upload"]["error"] //由文件上传导致的错误代码

有了这些数据,基本上剩下的工作就是文件转移操作。

移动临时文件

对于原生PHP而言,提供了move_upload_file($tmp_file, $target_path)方法用于移动文件的目标文件夹下。

UploadedFile类提供了moveTo()方法用于移动临时文件

/**
 * Move the uploaded file to a new location.
 * Use this method as an alternative to move_uploaded_file(). This method is
 * guaranteed to work in both SAPI and non-SAPI environments.
 * Implementations must determine which environment they are in, and use the
 * appropriate method (move_uploaded_file(), rename(), or a stream
 * operation) to perform the operation.
 * $targetPath may be an absolute path, or a relative path. If it is a
 * relative path, resolution should be the same as used by PHP's rename()
 * function.
 * The original file or stream MUST be removed on completion.
 * If this method is called more than once, any subsequent calls MUST raise
 * an exception.
 * When used in an SAPI environment where $_FILES is populated, when writing
 * files via moveTo(), is_uploaded_file() and move_uploaded_file() SHOULD be
 * used to ensure permissions and upload status are verified correctly.
 * If you wish to move to a stream, use getStream(), as SAPI operations
 * cannot guarantee writing to stream destinations.
 *
 * @see http://php.net/is_uploaded_file
 * @see http://php.net/move_uploaded_file
 * @param string $targetPath Path to which to move the uploaded file.
 * @throws \InvalidArgumentException if the $targetPath specified is invalid.
 * @throws \RuntimeException on any error during the move operation, or on
 *                           the second or subsequent call to the method.
 */
public function moveTo($targetPath)
{
    $targetPath = App::getAlias($targetPath);
    $this->validateActive();

    if (! $this->isStringNotEmpty($targetPath)) {
        throw new \InvalidArgumentException('Invalid path provided for move operation');
    }

    if ($this->tmpFile) {
        $this->moved = php_sapi_name() == 'cli' ? rename($this->tmpFile, $targetPath) : move_uploaded_file($this->tmpFile, $targetPath);
    }

    if (! $this->moved) {
        throw new \RuntimeException(sprintf('Uploaded file could not be move to %s', $targetPath));
    }
}

未完待续...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值