champs-libres / async-uploader-bundle
使用临时URL中间件从浏览器上传文件到Openstack Swift或Amazon S3(稍后)。
Requires
- php-opencloud/openstack: ^3.0
Requires (Dev)
- phpunit/phpunit: ^6.2
This package is auto-updated.
Last update: 2024-09-03 15:23:22 UTC
README
此包帮助管理从浏览器到Openstack Swift服务的文件异步上传。
它避免了直接在服务器上处理文件,这样可以节省磁盘空间和IO、RAM和CPU。
[[目录]]
它是如何工作的?
当前限制
- 仅支持openstack
如果您愿意提供帮助以消除这些限制,请不要犹豫,提出一些合并请求。
安装
此功能与symfony 3.4兼容
注册包
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = array(
// ...
new ChampsLibres\AsyncUploaderBundle\ChampsLibresAsyncUploaderBundle()
);
return $bundles;
}
}
创建将存储文件名的实体
创建实体是最常用的方法,但可能存在其他方法(在redis表中存储名称等)
以下是一个包含一些元数据(例如加密文件的密钥)的示例。
实体必须实现ChampsLibres\AsyncUploaderBundle\Model\AsyncFileInterface
。
namespace Chill\DocStoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use ChampsLibres\AsyncUploaderBundle\Model\AsyncFileInterface;
use ChampsLibres\AsyncUploaderBundle\Validator\Constraints\AsyncFileExists;
/**
* Represent a document stored in an object store
*
*
* @ORM\Entity()
* @ORM\Table("chill_doc.stored_object")
* @AsyncFileExists(
* message="The file is not stored properly"
* )
*/
class StoredObject implements AsyncFileInterface
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="text")
*/
private $filename;
/**
*
* @var \DateTime
* @ORM\Column(type="datetime", name="creation_date")
*/
private $creationDate;
public function __construct()
{
$this->creationDate = new \DateTime();
}
public function getObjectName()
{
return $this->filename;
}
注意:存在一个验证器,在创建或编辑后检查文件在存储库中的存在。此验证器通过此注解添加到类中
* @AsyncFileExists(
* message="The file is not stored properly"
* )
配置openstack容器
创建一个容器来存储您的数据。目前,仅支持v2身份验证(与OVH提供商)。
必须添加两个参数
- 一个临时URL密钥,用于签名URL;
- 允许CORS请求的https头
# load environment variables
swift post mycontainer -m "Temp-URL-Key:mySecretKeyWithAtLeast20Characters"
swift post mycontainer -m "Access-Control-Allow-Origin: https://my.website.com https://my.other.website.com"
为了允许从所有URL(在测试、开发和调试期间更可取)进行CORS请求
swift post mycontainer -m "Access-Control-Allow-Origin: *"
容器必须具有以下数据和元数据
$ swift stat mycontainer
Account: AUTH_abcde
Container: mycontainer
Objects: 0
Bytes: 0
Read ACL:
Write ACL:
Sync To:
Sync Key:
Meta Temp-Url-Key: mySecretKeyWithAtLeast20Characters
Meta Access-Control-Allow-Origin: https://my.website.com https://my.other.website.com
Accept-Ranges: bytes
X-Iplb-Instance: 12308
X-Storage-Policy: PCS
Last-Modified: Tue, 11 Sep 2018 14:52:16 GMT
Content-Type: text/plain; charset=utf-8
进一步参考
- https://docs.openstack.org/swift/latest/cors.html
- https://docs.openstack.org/swift/latest/api/temporary_url_middleware.html
- https://docs.openstack.org/swift/latest/api/form_post_middleware.html
配置包
# app/config/config.yaml
champs_libres_async_uploader:
persistence_checker: 'path.to.your_service'
openstack:
os_username: '%env(OS_USERNAME)%' # Required
os_password: '%env(OS_PASSWORD)%' # Required
os_tenant_id: '%env(OS_TENANT_ID)%' # Required
os_region_name: '%env(OS_REGION_NAME)%' # Required
os_auth_url: '%env(OS_AUTH_URL)%' # Required
temp_url:
temp_url_key: '%env(ASYNC_UPLOAD_TEMP_URL_KEY)%' # Required
container: '%env(ASYNC_UPLOAD_TEMP_URL_CONTAINER)%' #Required
temp_url_base_path: '%env(ASYNC_UPLOAD_TEMP_URL_BASE_PATH)%' # Required. Do not forget a trailing slash
max_post_file_size: 15000000 # 15Mo, exprimés en bytes
max_expires_delay: 180
max_submit_delay: 3600
注意 不要忘记使用参数 temp_url_base_path
时的尾部斜杠
表单中的使用
可以使用AsyncUploaderType
生成一个隐藏字段,并存储文件名
namespace Chill\DocStoreBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use ChampsLibres\AsyncUploaderBundle\Form\Type\AsyncUploaderType;
/**
* Form type which allow to join a document
*
*/
class StoredObjectType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('filename', AsyncUploaderType::class)
;
}
}
浏览器负责
使用此URL获取上传文件的签名
/asyncupload/temp_url/generate/post?expires_delay=180&submit_delay=3600
参数
- expires_delay:签名过期前的延迟,以秒为单位;
- submit_delay:在文件到达Openstack之前检查文件的延迟。此验证应在服务器端完成。
示例响应
{ "method": "POST", "max_file_size": 15000000, "max_file_count": 1, "expires": 1592301124, "submit_delay": 3600, "redirect": "", "prefix": "TUPyhlXvoApgJim", "url": "https://storage.gra.cloud.ovh.net/v1/AUTH_c123456/container/TUPyhlXvoApgJim", "signature": "abcdefghijklmnopqrstuvwxyz01234567890123" }
- 为文件名生成随机后缀;
使用“数据”元素中提供的“前缀”、“密钥”和“签名”将文件推送到Openstack容器中。
文件名将由“数据”元素中提供的“前缀”和第1步中选择的“后缀”组成。
POST请求示例
<--- url given by data --> <-- prefix --> POST /v1/AUTH_c123456/container/TUPyhlXvoApgJim HTTP/1.1
一个文件的示例数据
-----------------------------336081439295927874898201087 Content-Disposition: form-data; name="redirect"
-----------------------------336081439295927874898201087
Content-Disposition: form-data; name="max_file_size"
15000000
-----------------------------336081439295927874898201087
Content-Disposition: form-data; name="max_file_count"
1
-----------------------------336081439295927874898201087
Content-Disposition: form-data; name="expires"
1592300722
-----------------------------336081439295927874898201087
Content-Disposition: form-data; name="signature"
abcdefghijklmnopqrstuvwxyz01234567890123
-----------------------------336081439295927874898201087
Content-Disposition: form-data; name="file"; filename="v0Rl2qr"
Content-Type: application/octet-stream
.
<!-- file come here -->
-----------------------------336081439295927874898201087--
```
- 将文件名存储在隐藏字段中。
实现示例
- 该包有一个示例脚本,该脚本可用于使用 ;
一个示例脚本和用法,它在将文件推送到Openstack容器之前在客户端对文件进行加密
此脚本使用dropzoneJS库。
它允许每个输入只有一个文件。
js请求示例
var
fileInput = ev.target, // the file input element
uniqid = fileInput.name,
fileObject = fileInput.files[0],
asyncFileInput = document.querySelector('input[type="hidden"][data-input-name="'+uniqid+'"]'),
formData = new FormData(),
fileName = asyncupload.makeid(),
jsonData,
objectName,
existingAsyncFileInputValueJson,
url = asyncFileInput.dataset.tempUrl
;
// Get the asyncupload parameters
window.fetch(url)
.then(function(r) {
// handle asyncupload parameters
if (r.ok) {
return r.json();
} else {
throw new Error('not ok');
}
}).then(function(data) {
// upload to openstack swift
if (fileObject.size > data.max_file_size){console.log("Upload file too large");}
formData.append("redirect", data.redirect);
formData.append("max_file_size", data.max_file_size);
formData.append("max_file_count", data.max_file_count);
formData.append("expires", data.expires);
formData.append("signature", data.signature);
formData.append("file", fileObject, fileName);
// prepare the form data which will be used in next step
objectName = data.prefix + fileName;
jsonData = { "object_name": objectName };
return window.fetch(data.url, {
method: 'POST',
mode: 'cors',
body: formData
});
}).then(function(r) {
if (r.ok) {
console.log('Succesfully uploaded');
// Update info in the form, as upload is successful
if (asyncFileInput.value === "") {
existingAsyncFileInputValueJson = { "files": [ jsonData ] };
} else {
existingAsyncFileInputValueJson = JSON.parse(asyncFileInput.value);
existingAsyncFileInputValueJson.files.push(jsonData);
}
asyncFileInput.value = JSON.stringify(existingAsyncFileInputValueJson);
} else {
console.log('bad');
console.log(r.status);
// Handle errors
}
}).catch(function(err) {
/* error :( */
console.log("catch an error: " + err.name + " - " + err.message);
alert("There was an error posting your images. Please try again.");
throw new Error('openstack error: ' + err );
});
检查文件的存在
验证器AsyncFileExists
将在表单提交时检查文件的存在
/**
* Represent a document stored in an object store
*
*
* @ORM\Entity()
* @AsyncFileExists(
* message="The file is not stored properly"
* )
*/
class StoredObject implements AsyncFileInterface
{
}
获取签名的URL
您还可以使用GET请求使用javascript下载文件
GET /asyncupload/temp_url/generate/GET?object_name=abcdefhiI
示例响应
{
"method": "GET",
"url": "https://storage.gra.cloud.ovh.net/v1/AUTH_c611d5d3f457449cb709793003282426/comedienbe/FVsbQVDS0dAIvb4eqWqbDI?temp_url_sig=f10ddb5516f1b1b197a5ce63f98e2056696577c7&temp_url_expires=1592303020"
}
不允许使用 DELETE 和 PUT 方法。
在模板中显示文件(twig 过滤器)
可以使用这些函数获取文件的 URL
{# asyncFile implements AsyncFileInterface or a string (filename) #}
<img src="{{ asyncFile|file_url }}" />
您还可以向服务器发送 GET 请求来获取签名
<!-- the generate_url will be the GET url described in previous section -->
<button data-get-url="{{ asyncFile|generate_url }}">
如果容器是公开的,您可以直接使用对文件的访问
<img src="https://storage.gra.cloud.ovh.net/v1/AUTH_c123456/container/{{ asyncFile }}" />
安全性
在打印文件 URL 签名之前,您应该确保用户确实有权查看它。
不允许从 HTTP 请求中为 DELETE
和 PUT
方法生成签名。您仍然可以从 PHP 代码中生成它。
限制 openstack 容器的使用
有时,用户选择一个文件进行上传,该文件立即上传到 openstack 容器。然后,用户在 UI 中删除该文件,并选择第二个文件(也将上传)并提交表单。
第一个上传的文件仍然记录在容器中。
过了一段时间后,文件可能会使容器膨胀。
为了防止这种情况,您可以注册 POST 签名并将它们存储在队列中。然后,在 submit_delay
过期后,检查前缀下每个文件的存在性,如果文件没有存储在数据库中,则删除它。
当生成签名时,将触发事件 async_uploader.generate_url
。
您可以监听此事件以获取签名的生成并实现自己的逻辑。您应该等待提交延迟。
对上传的文件应用逻辑(图像缩放等)
当生成签名时,将触发事件 async_uploader.generate_url
。
您可以监听此事件以获取签名的生成并实现自己的逻辑。您应该等待提交延迟以确保文件已上传。