dcat-admin 大檔案上傳(前端直傳解決)

Latent發表於2021-05-21

昨天遇到了一個後端傳輸導致nginx502的問題

今天在調整了程式碼之後,遂將解決方案貼出

前端直傳的好處:減輕應用伺服器的壓力,將壓力分給了oss 這一點特別是在上傳大檔案時特別明顯的,php是要消耗很大一部分記憶體去處理前端分片上傳來的檔案再傳輸給oss,如果檔案特別大,耗時長nginx會直接502 我們沒必要去調整nginx的超時時間把路走窄了。直接由客戶端直傳oss吧。

完成後效果圖

Laravel

Laravel

因為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檔案全域性載入

dcat-admin 大檔案上傳(前端直傳解決)

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.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

參考文章

本作品採用《CC 協議》,轉載必須註明作者和本文連結
不成大牛,不改個簽

相關文章