前端直传的好处:减轻应用服务器的压力,将压力分给了 oss 这一点特别是在上传大文件时特别明显的,php 是要消耗很大一部分内存去处理前端分片上传来的文件再传输给 oss,如果文件特别大,耗时长 nginx 会直接 502
我们没必要去调整 nginx 的超时时间把路走窄了。直接由客户端直传 oss 吧。
完成后效果图
#### 因为 `dcat-admin` 是高度封装的。改它的组件基本不现实,不过 `$form->view()` 方法可以引入一个视图文件。我的想法是用 vue 封装一个上传的组件,然后通过该方法引入。正好 laravel 提供了前端脚手架 laravel mix 整合了 vue。
* laravel 版本 7.x
开始一套梭
composer require laravel/ui --dev //安装前端脚手架
php artisan ui vue //生成vue基本脚手架
npm install
npm install ali-oss --save //安装 oss js-sdk
npm install clipboard --save //安装 复制插件
npm run watch //命令监视热加载、编译
使用 element-ui 的组件,所以我们引入它
npm i element-ui -S
在`app.js`文件全局加载
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
import Clipboard from 'clipboard';
Vue.prototype.Clipboard=Clipboard;
并新建组件 `OssFile`
<template>
<div>
<el-upload
class="upload-demo"
action=""
ref="upload"
:file-list="fileList"
:limit="2"
:on-change="handleChange"
:on-remove="handleRemove"
:auto-upload="false"
accept=""
>
<el-button slot="trigger" size="small" type="primary">选取文件</el-button>
<el-button style="margin-left: 10px;" size="small" type="success" @click="submitForm">直传oss</el-button>
<el-button style="margin-left: 10px;" size="small" type="success" @click="resumeUpload">继续</el-button>
<el-button style="margin-left: 10px;" size="small" type="success" @click="stopUplosd">暂停</el-button>
<el-button style="margin-left: 10px;" size="small" type="success" @click="abortMultipartUpload">清除切片</el-button>
</el-upload>
<el-progress :percentage="percentage" :status="uploadStatus"></el-progress>
<span
class="copybtn"
@click="copy"
:data-clipboard-text="fileName"
>
{{ fileName }}
</span>
</div>
</template>
<script>
import Clipboard from 'clipboard';
let OSS = require('ali-oss') // 引入ali-oss插件
const client = new OSS({
region: 'oss-cn-shenzhen',//根据那你的Bucket地点来填写
accessKeyId: '',//自己账户的accessKeyId
accessKeySecret: '',//自己账户的accessKeySecret
bucket: '',//bucket名字
});
export default {
name: "OssFile",
data () {
return {
fileName:"",
fileList:[],
file: null,
tempCheckpoint: null, // 用来缓存当前切片内容
uploadId: '',
uploadStatus: null, // 进度条上传状态
percentage: 0, // 进度条百分比
uploadName: '', //Object所在Bucket的完整路径
}
},
mounted() {
window.addEventListener('online', this.resumeUpload);
},
methods: {
copy()
{
var clipboard = new Clipboard(".copybtn");
clipboard.on("success", (e) => {
this.$message({
message: '复制成功',
type: 'success'
});
// 释放内存
clipboard.destroy();
});
clipboard.on("error", (e) => {
// 不支持复制
this.$message({
message: '该浏览器不支持自动复制',
type: 'success'
});
// 释放内存
clipboard.destroy();
});
},
// 点击上传至服务器
submitForm(file) {
this.multipartUpload();
},
// 取消分片上传事件
async abortMultipartUpload() {
window.removeEventListener('online', this.resumeUpload)
const name = this.uploadName; // Object所在Bucket的完整路径。
const uploadId = this.upload; // 分片上传uploadId。
const result = await client.abortMultipartUpload(name, uploadId);
console.log(result, '=======清除切片====');
},
// 暂停分片上传。
stopUplosd () {
window.removeEventListener('online', this.resumeUpload) // 暂停时清除时间监听
let result = client.cancel();
console.log( result, '---------暂停上传-----------')
},
// 切片上传
async multipartUpload () {
if (!this.file) {
this.$message.error('请选择文件')
return
}
console.log("this.uploadStatus",this.file, this.uploadStatus);
console.log("文件列表:"+this.fileList)
console.log("文件:"+this.file)
this.percentage = 0
try {
//object-name可以自定义为文件名(例如file.txt)或目录(例如abc/test/file.txt)的形式,实现将文件上传至当前Bucket或Bucket下的指定目录。
let result = await client.multipartUpload(this.file.name, this.file, {
headers: {
'Content-Disposition': 'inline',
'Content-Type': this.file.type //注意:根据图片或者文件的后缀来设置,我试验用的‘.png’的图片,具体为什么下文解释
},
progress: (p, checkpoint) => {
this.tempCheckpoint = checkpoint;
this.upload = checkpoint.uploadId
this.uploadName = checkpoint.name
this.percentage = p * 100
// console.log(p, checkpoint, this.percentage, '---------uploadId-----------')
// 断点记录点。浏览器重启后无法直接继续上传,您需要手动触发上传操作。
},
meta: { year: 2020, people: 'dev' },
mime: this.file.type
});
console.log(result, this.percentage, 'result= 切片上传完毕=');
this.$nextTick(()=>{
this.fileName = 'https://image.mythinkcar.cn/'+result.name
})
console.log(this.fileName)
} catch (e) {
console.log(e)
window.addEventListener('online', this.resumeUpload) // 该监听放在断网的异常处理
// 捕获超时异常。
if (e.code === 'ConnectionTimeoutError') { // 请求超时异常处理
this.uploadStatus = 'exception'
console.log("TimeoutError");
}
}
},
// 恢复上传。
async resumeUpload () {
window.removeEventListener('online', this.resumeUpload)
if (!this.tempCheckpoint) {
this.$message.error('请先上传')
return
}
this.uploadStatus = null
try {
let result = await client.multipartUpload(this.file.name, this.file, {
headers: {
'Content-Disposition': 'inline',
'Content-Type': this.file.type //注意:根据图片或者文件的后缀来设置,我试验用的‘.png’的图片,具体为什么下文解释
},
progress: (p, checkpoint) => {
this.percentage = p * 100
console.log(p, checkpoint, 'checkpoint----恢复上传的切片信息-------')
this.tempCheckpoint = checkpoint;
},
checkpoint: this.tempCheckpoint,
meta: { year: 2020, people: 'dev' },
mime: this.file.type
})
console.log(result, 'result-=-=-恢复上传完毕')
} catch (e) {
console.log(e, 'e-=-=-');
}
},
// 选择文件发生改变
handleChange(file, fileList) {
this.fileList = fileList.filter(row => row.uid == file.uid)
this.file = file.raw
// 文件改变时上传
// this.submitForm(file)
},
handleRemove(file, fileList) {
this.percentage = 0 //进度条置空
this.fileList = []
},
}
}
</script>
<style>
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 150px;
height: 150px;
line-height: 150px;
text-align: center;
}
.avatar {
width: 150px;
height: 150px;
display: block;
}
</style>
app/admin/bootstrap.js
中引入组件
Vue.component('oss-file', require('./components/uploads/OssFile.vue').default);
`resources/views` 目录新建 `oss.blade.php` 引入组件 `<oss-file></oss-file>`
<link rel="stylesheet" href="{{mix('css/app.css')}}">
<div id="app">
<div class="container">
<oss-file></oss-file>
</div>
</div>
<script src="{{mix('js/app.js')}}"></script>
最后
$form->html(view('uploads.oss'));
$form->text('link','直传后填入地址');
完美解决~~~
碰到的问题
- oss 跨域问题
- oss 出现 RequestId 错误处理方法 新增 `ETag x-oss-request-id`
参考文章
—
vue+element 存在兼容问题。后续 jq 重写了
{{--<link rel="stylesheet" type="text/css" href="{{asset('/oss/style.css')}}"/>--}}
<style>
.progress{
margin-top:2px;
width: 200px;
height: 14px;
margin-bottom: 10px;
overflow: hidden;
border-radius: 4px;
}
.progress-bar{
background-color: rgb(92, 184, 92);
background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.14902) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.14902) 50%, rgba(255, 255, 255, 0.14902) 75%, transparent 75%, transparent);
background-size: 40px 40px;
box-shadow: rgba(0, 0, 0, 0.14902) 0px -1px 0px 0px inset;
box-sizing: border-box;
color: rgb(255, 255, 255);
display: block;
float: left;
font-size: 12px;
height: 20px;
line-height: 20px;
text-align: center;
transition-delay: 0s;
transition-duration: 0.6s;
transition-property: width;
transition-timing-function: ease;
width: 266.188px;
}
</style>
<div id="ossfile"></div>
<br/>
<div id="container">
<a id="selectfiles" href="javascript:void(0);" class=' btn-xs btn-info'>选择文件</a>
<a id="postfiles" href="javascript:void(0);" class=' btn-xs btn-success'>开始上传</a>
</div>
<br/>
<pre id="console"></pre>
<p> </p>
<script>
var policyText = {
"expiration": "2029-01-01T12:00:00.000Z", //设置该Policy的失效时间,超过这个失效时间之后,就没有办法通过这个policy上传文件了
"conditions": [
["content-length-range", 0, 1048576000] // 设置上传文件的大小限制
]
};
accessid= "{{env('OSS_ACCESS_KEY')}}";
accesskey= "{{env('OSS_SECRET_KEY')}}";
host = 'http://xxx.oss-cn-shenzhen.aliyuncs.com';
var policyBase64 = Base64.encode(JSON.stringify(policyText))
message = policyBase64
var bytes = Crypto.HMAC(Crypto.SHA1, message, accesskey, { asBytes: true }) ;
var signature = Crypto.util.bytesToBase64(bytes);
var uploader = new plupload.Uploader({
runtimes : 'html5,flash,silverlight,html4',
browse_button : 'selectfiles',
//runtimes : 'flash',
container: document.getElementById('container'),
flash_swf_url : 'lib/plupload-2.1.2/js/Moxie.swf',
silverlight_xap_url : 'lib/plupload-2.1.2/js/Moxie.xap',
url : host,
multipart_params: {
'Filename': '${filename}',
'key' : '${filename}',
'policy': policyBase64,
'OSSAccessKeyId': accessid,
'success_action_status' : '200', //让服务端返回200,不然,默认会返回204
'signature': signature,
},
init: {
PostInit: function() {
// document.getElementById('ossfile').innerHTML = '';
document.getElementById('postfiles').onclick = function() {
uploader.start();
return false;
};
},
FilesAdded: function(up, files) {
plupload.each(files, function(file) {
document.getElementById('ossfile').innerHTML += '<div id="' + file.id + '">' + file.name + ' (' + plupload.formatSize(file.size) + ')<b></b>'
+'<div class="progress"><div class="progress-bar" style="width: 0%"></div></div>'
+'</div>';
});
},
UploadProgress: function(up, file) {
var d = document.getElementById(file.id);
d.getElementsByTagName('b')[0].innerHTML = '<span>' + file.percent + "%</span>";
var prog = d.getElementsByTagName('div')[0];
var progBar = prog.getElementsByTagName('div')[0]
progBar.style.width= 2*file.percent+'px';
progBar.setAttribute('aria-valuenow', file.percent);
},
FileUploaded: function(up, file, info) {
//alert(info.status)
if (info.status >= 200 || info.status < 200)
{
console.log(up,file,info)
document.getElementById(file.id).getElementsByTagName('b')[0].innerHTML = 'http://image.mythinkcar.cn/'+file.name;
}
else
{
document.getElementById(file.id).getElementsByTagName('b')[0].innerHTML = info.response;
}
},
Error: function(up, err) {
document.getElementById('console').appendChild(document.createTextNode("\nError xml:" + err.response));
}
}
});
uploader.init();
</script>
app/admin/bootstrap.js
加入 oss 的一些 js 文件
Admin::js('/oss/lib/crypto1/crypto/crypto.js');
Admin::js('/oss/lib/crypto1/hmac/hmac.js');
Admin::js('/oss/lib/crypto1/sha1/sha1.js');
Admin::js('/oss/lib/base64.js');
Admin::js('/oss/lib/plupload-2.1.2/js/plupload.full.min.js');
....
转载自https://learnku.com/articles/57456