FastAdmin 文件上传对象存储

项目中文件上传可以说是常见的,随着现在技术发展,大家通常都使用对象存储来存储静态文件,避免服务器需要购买昂贵的带宽。FastAdmin 的插件市场提供了付费的各大云服务商的插件,但他们实现是通过 WEB 直传方式,即浏览器端来直接上传到对象存储,但不方便小程序等的使用,比如微信小程序文件上传就不支持使用 PUT 协议。

其实是付费太贵了,,,为了支持更多的云服务商,我们又用上了 S3 API,基本大厂都支持,只需要配置好相关信息即可。目前使用来看,上传接口都是使用 ajax/upload,所以我们只需要修改这个文件即可完成。

S3 SDK 的预签名生成,是不可以超过 7 天的,坑。

1
$ composer require aws/aws-sdk-php

application\admin\controller\Ajax.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
protected function upload_handle($attachment)
{
if(strpos($attachment->url, '/') !== 0) {
$this->success(__('Uploaded successful'), '', ['url' => $attachment->url, 'fullurl' => $attachment->url]);
}

$path = $attachment->url;

$config = [
'credentials' => [
'key' => 'LTAI5t8ZZcDGw4EVv6dEXujWu', // Secret ID
'secret' => 'rlTofpCylKZwaCPP6Nq8TQPYXu0CUR', // Secret Key
],
'bucket' => 'hongfs-fastadmin-wybid50m9th7i591', // 存储桶名称
'endpoint' => 'https://oss-cn-shenzhen.aliyuncs.com', // Endpoint
'region' => 'cn-shenzhen', // 地域
'version' => '2006-03-01', // 版本号,固定
'domain' => '', // 域名
];

try {
$uploader = new \Aws\S3\MultipartUploader(new \Aws\S3\S3Client($config), ROOT_PATH . 'public' . $path, [
'bucket' => $config['bucket'],
'key' => substr($path, 1),
]);

// 执行上传操作
$result = $uploader->upload();
} catch (\Aws\Exception\MultipartUploadException $e) {
$this->error($e->getMessage());
}

unlink(ROOT_PATH . 'public' . $path);

// 修改附件的 URL
$attachment->url = $result['ObjectURL'];

// 如果有配置域名则需要进行替换
if($config['domain']) {
$attachment->url = $config['doamin'] . parse_url($attachment->url, PHP_URL_PATH);
}

$attachment->save();

$this->success(__('Uploaded successful'), '', ['url' => $attachment->url, 'fullurl' => $attachment->url]);
}
1
2
3
4
5
6
7
8
9
10
/**
* 上传文件
*/
public function upload()
{
...
// 注意:有两个地方需要替换
- $this->success(__('Uploaded successful'), '', ['url' => $attachment->url, 'fullurl' => cdnurl($attachment->url, true)]);
+ $this->upload_handle($attachment);
}

注意:如果云服务商文档中的 endpoint 存在了存储桶名称,比如腾讯云,这需要把这部分去掉。

导入功能依靠本地文件,这块需要自己处理了。

优化版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
if(!function_exists('s3_upload')) {
/**
* 上传到对象存储
* @param string $local_path 本地路径
* @param string $cloud_path 云端路径
* @return string
*/
function s3_upload($local_path, $cloud_path) {
$config = [
'credentials' => [
'key' => config('site.os_access_key_id'), // Secret ID
'secret' => config('site.os_access_key_secret'), // Secret Key
],
'bucket' => config('site.os_bucket'), // 存储桶名称
'endpoint' => config('site.os_endpoint'), // Endpoint
'region' => config('site.os_region'), // 地域
'version' => '2006-03-01', // 版本号,固定
];

if(strpos($cloud_path, '/') === 0) {
$cloud_path = substr($cloud_path, 1);
}

try {
$uploader = new \Aws\S3\MultipartUploader(new \Aws\S3\S3Client($config), $local_path, [
'bucket' => $config['bucket'],
'key' => $cloud_path,
]);

$uploader->upload();
} catch (\Aws\Exception\MultipartUploadException $e) {
// 上传失败也要删除文件
unlink($local_path);

throw $e;
}

// 删除本地文件
if(true) {
unlink($local_path);
}

return s3_url($cloud_path);
}
}

if(!function_exists('s3_url')) {
/**
* 获取对象存储 URL
* @param string $path 路径
* @return string
*/
function s3_url($path) {
$config = [
'bucket' => config('site.os_bucket'), // 存储桶名称
'endpoint' => config('site.os_endpoint'), // Endpoint
'domain' => config('site.os_domain'), // 域名
];

if(strpos($path, '/') !== 0) {
$path = '/' . $path;
}

$path = parse_url($path, PHP_URL_PATH);

if($config['domain']) {
return $config['domain'] . $path;
}

$host = parse_url($config['endpoint'], PHP_URL_HOST);

if(strpos($config['endpoint'], '.myqcloud.com') !== false) {
// 腾讯云 COS 默认域名
return sprintf('https://%s.%s%s', $config['bucket'], $host, $path);
}

return sprintf('https://%s%s', $host, $path);
}
}

if(!function_exists('s3_has')) {
/**
* 对象存储文件是否存在
* @param string $path 路径
* @return bool
*/
function s3_has($path) {
$url = s3_url($path);

if($url === false) {
return false;
}

$header = get_headers($url, true);

return strpos($header[0], '200') !== false;
}
}
往上