当前位置:   article > 正文

vue3+华为云obs(springboot)分段上传_obs 分段回传

obs 分段回传

vue3+华为云obs(springboot)分段上传

springboot

pom依赖
 <dependency>
    <groupId>com.huaweicloud</groupId>
    <artifactId>esdk-obs-java</artifactId>
    <version>3.23.5</version>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
代码块
package com.pig4cloud.pig.admin.controller;
import com.obs.services.ObsClient;
import com.obs.services.model.*;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 切片上传
 */
@RestController
@AllArgsConstructor
@RequestMapping("/upload")
public class SysUploadController {

    private final ObsClient obsClient;

    /**
     * 启动上传任务
     */
    @GetMapping("/getUploadId/{objectKey}")
    public String getUploadId(@PathVariable("objectKey")String objectKey) {
        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest("<桶名称>", objectKey);
        ObjectMetadata metadata = new ObjectMetadata();
        metadata.addUserMetadata("property", "property-value");
        metadata.setContentType("text/plain");
        request.setMetadata(metadata);
        InitiateMultipartUploadResult result = obsClient.initiateMultipartUpload(request);
        String uploadId = result.getUploadId();
        return uploadId;
    }

    /**
     * 分片上传
     */
    @PostMapping("/chunk")
    public Map<String,String>  splitFileUpload(
            @RequestParam("objectKey")String objectKey,
            @RequestParam("file") MultipartFile file,
            @RequestParam("chunk") int chunk,
            @RequestParam("uploadId") String uploadId) throws Exception {
        File file1 =  multipartFileToFile(file);
        Map<String,String> map = uploadChunk(uploadId, file1, chunk, objectKey);
        return map;
    }

    /**
     * 合并上传
     */
    @PostMapping("/completeUpload")
    public CompleteMultipartUploadResult completeUpload(
            @RequestParam("objectKey")String objectKey,
            @RequestParam("uploadId") String uploadId,
            @RequestBody List<Map<String,String>> mapList
    ) {
        List<PartEtag> partEtags = new ArrayList<>();
        for(Map<String,String> map: mapList ){
            PartEtag part1 = new PartEtag();
            part1.setPartNumber(Integer.valueOf(map.get("partNumber")));
            part1.seteTag(map.get("etag"));
            partEtags.add(part1);
        }
        CompleteMultipartUploadRequest request = new CompleteMultipartUploadRequest(
                "<桶名称>", objectKey, uploadId, partEtags);
        CompleteMultipartUploadResult result = obsClient.completeMultipartUpload(request);
        return result;
    }

    /**
     * 取消任务上传
     */
    @GetMapping("/cancelUpload")
    public void cancelUpload(
        @RequestParam("objectKey")String objectKey,
        @RequestParam("uploadId") String uploadId
    ){
        AbortMultipartUploadRequest request = new AbortMultipartUploadRequest("<桶名称>", objectKey, uploadId);
        obsClient.abortMultipartUpload(request);
    }

    public Map<String,String> uploadChunk(String uploadId, File file,int chunk, String objectKey){
        // Endpoint以北京四为例,其他地区请按实际情况填写。
        Map<String,String> map = new HashMap<>();
        UploadPartRequest request = new UploadPartRequest("<桶名称>", objectKey);
        request.setUploadId(uploadId);
        request.setPartNumber(chunk);
        request.setFile(file);
        request.setPartSize(5 * 1024 * 1024L);
        UploadPartResult result = obsClient.uploadPart(request);
        map.put("etag",result.getEtag());
        map.put("partNumber",String.valueOf(result.getPartNumber()));
        return map;
    }

    /**
     * MultipartFile 转 File
     *
     * @param file
     * @throws Exception
     */
    public static File multipartFileToFile(MultipartFile file) throws Exception {

        File toFile = null;
        if (file.equals("") || file.getSize() <= 0) {
            file = null;
        } else {
            InputStream ins = null;
            ins = file.getInputStream();
            toFile = new File(file.getOriginalFilename());
            inputStreamToFile(ins, toFile);
            ins.close();
        }
        return toFile;
    }

    //获取流文件
    private static void inputStreamToFile(InputStream ins, File file) {
        try {
            OutputStream os = new FileOutputStream(file);
            int bytesRead = 0;
            byte[] buffer = new byte[8192];
            while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            os.close();
            ins.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
  • 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
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
obs初始化类
package com.pig4cloud.pig.admin.config;
import com.obs.services.ObsClient;
import com.pig4cloud.plugin.oss.OssProperties;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@AllArgsConstructor
public class ObsConfiguration {

    // 请在yml里配置或使用 @Value 单独获取
    private final OssProperties ossProperties;

    @Bean
    public ObsClient obsClient() {
        return new ObsClient(ossProperties.getAccessKey(),
                ossProperties.getSecretKey(),
                ossProperties.getEndpoint());
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
vue3
<template>
    <div class="upload-container">
        <div class="fileUpload">
            <div class="item-upload">
                <span v-if="required" class="required">*</span>
                <el-button type="primary" οnclick="document.getElementById('fpfileName').click()">文件选择</el-button>
                <input  type="file" id="fpfileName" class="file2" style="display: none" @change="selectFile" />
            </div>
            <div v-if="showFileName" class="item-info">
                {{fileName}}
            </div>
            <div v-else class="item-info">
                支持{{getAcceptType()}}文件格式
            </div>
            <div  v-if="progress>0" class="item-cancel">
                <el-button style="margin-top: 5px" size="small" type="danger" @click="cancelUploadInfo">X</el-button>
            </div>
        </div>
        <div class="item-process" v-if="progress>0">
            <el-progress :text-inside="true" :stroke-width="20" :percentage="progress"/>
        </div>
        <div v-if="requiredValid" class="item-valid">
            请上传文件
        </div>
    </div>

</template>
<script>
    import { getUploadId , uploadFileInfo, completeUpload, cancelUpload, getFileUrl} from "@/api/admin/client.js"
    import { defineComponent, reactive, toRefs,  onMounted } from "vue";
    import { ElMessage } from 'element-plus'
    export default defineComponent({
        name: "file-upload",
        props:{
            //文件接受类型
            accept:{
                type: Array,
                default: ['doc','docx','pdf']
            },
            //是否必填
            required:{
                type: Boolean,
                default: false
            },
            //文件限制大小
            fileSize:{
                type: Number,
                default: 500
            }
        },
        setup(props) {
            const data =reactive({
                file: null,
                fileName: "",
                fileSize: 0,
                progress: 0,
                objectKey: "",
                chunkSize: 5*1024 * 1024,
                totalChunks: 0,
                currentChunk: 1,
                uploadId: "",
                partArr: [],
                showFileName: false,
                bucketName: "",
                objectKeyName: "",
                required: props.required,
                requiredValid: false
            })
            // 选择文件
            const selectFile = (event)=>{
                data.file = event.target.files[0];
                event.target.value = ''
                if(data?.file?.size){
                    const fileSize = Math.round(data?.file?.size/1024/1024*100)/100
                    if(fileSize > props.fileSize){
                        ElMessage.warning("文件大小超过上限"+props.fileSize+"MB")
                        return
                    }
                    data.fileSize = fileSize
                    console.log(data.fileSize)
                    const fileName = data.file.name
                    const index = fileName.lastIndexOf(".");
                    const fileType = fileName.substring(index+1, fileName.length)
                    if(!props.accept.includes(fileType)){
                        ElMessage.warning("文件类型不支持")
                        return
                    }
                    data.progress = 0;
                    data.fileName = data.file.name
                    data.showFileName = true
                    data.totalChunks = Math.ceil(data.file.size / data.chunkSize)
                    data.objectKey = guid()+"."+fileType
                    getUploadId(data.objectKey).then((res)=>{
                        data.uploadId = res.data
                        data.progress = 1;
                        uploadFile();
                    })
                }
            }

            // 文件上传
            const uploadFile = ()=>{
                const index = data.currentChunk -1
                const start = index * data.chunkSize;
                const end = Math.min((index + 1) * data.chunkSize, data.file.size);
                const formData = new FormData()
                formData.append('file', data.file.slice(start, end))
                formData.append('chunk', data.currentChunk)
                formData.append('objectKey', data.objectKey)
                formData.append('uploadId', data.uploadId)
                // 调用后端接口上传切片数据
                uploadFileInfo(formData).then((res) => {
                    data.partArr.push(res.data)
                    console.log(data.progress)
                    data.currentChunk++;
                    data.progress = Math.floor((data.currentChunk / data.totalChunks) * 100);
                    if (data.currentChunk <= data.totalChunks) {
                        uploadFile();
                    } else {
                        // 所有切片上传完成
                        data.progress = 99;
                        console.log(data.progress)
                        complete();
                    }
                }).catch((error) => {
                    initParam()
                    console.error('切片上传失败:', error);
                    ElMessage.warning("文件上传异常或上传已取消")
                });
            }

            // 上传完成并合并分段上传
            const complete = () =>{
                completeUpload(data.partArr,data.objectKey,data.uploadId).then((res) => {
                    console.log('文件上传完成');
                    console.log(res);
                    data.bucketName = res.data.bucketName
                    data.objectKeyName = res.data.objectKey
                    data.progress = 100;
                    initParam()
                }).catch((error) => {
                    console.error('文件上传失败:', error);
                    ElMessage.error("文件上传失败")
                });
            }

            // 取消上传
            const cancelUploadInfo = () =>{
                if(data.progress !== 100){
                    cancelUpload(data.objectKey,data.uploadId).then(()=>{
                        data.progress = 0;
                        data.showFileName = false
                        initParam()
                    })
                }else{
                    data.progress = 0;
                    data.showFileName = false
                    initParam()
                }

            }

            const getAcceptType = () =>{
                let str = "";
                if(props.accept){
                    for(let item of props.accept){
                        str += item +","
                    }
                }
                return str.substr(0,str.length-1)
            }

            // 构建uuid
            const guid =() => {
                return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
                    var r = Math.random() * 16 | 0,
                        v = c == 'x' ? r : (r & 0x3 | 0x8);
                    return v.toString(16);
                });
            }

            // 初始化参数
            const initParam = ()=>{
                data.file = null;
                data.objectKey = ""
                data.totalChunks = 0
                data.currentChunk = 1
                data.uploadId = ""
                data.partArr = []
                data.requiredValid = false
            }

            // 必传校验
            const validRequired = ()=>{
                if(data.bucketName && data.objectKeyName){
                    data.requiredValid = false
                    return true
                }else{
                    data.requiredValid = true
                    return false
                }
            }

            // 返回上传结果 桶名称、文件名、文件大小
            const getUploadInfo = () =>{
                return {
                    bucketName: data.bucketName,
                    fileName: data.objectKeyName,
                    fileSize: data.fileSize
                }
            }

            // 初始化
            onMounted(()=>{
                data.progress = 0;
            })

            return{
                ...toRefs(data),
                selectFile,
                uploadFile,
                getAcceptType,
                validRequired,
                getUploadInfo,
                cancelUploadInfo
            }
        }
    })
</script>
<style scoped>
    .upload-container{
        .fileUpload {
            display: flex;
            flex-direction: row;
            padding: 0px 8px;
            .item-upload {
                width: 100px;
                display: flex;
                flex-direction: row;
                align-items: center;
                justify-content: center;
                .required{
                    color: red;
                    padding: 0px 5px;
                }
            }
            .item-info {
                flex: 1;
                padding-top: 12px;
                font-size: 13px;
            }
            .item-cancel{
                width: 30px;
                padding-right: 20px;
            }
        }
        .item-process{
            margin-top: 5px;
            padding: 0px 23px;
            box-sizing: border-box;
        }
        .item-valid{
            color: red;
            padding: 0px 10px;
            font-size: 13px;
        }
    }
</style>
  • 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
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
接口调用
// 获取uploadId
export function getUploadId(objectKey) {
  return request({
    url: '/admin/upload/getUploadId/' + objectKey,
    method: 'get'
  })
}

// 分段上传
export function uploadFileInfo( data) {
  return request({
    url: '/admin/upload/chunk',
    method: 'post',
    headers:{'Content-Type': 'multipart/form-data'},
    data: data
  })
}

// 分段合并
export function completeUpload( data,objectKey,uploadId) {
  return request({
    url: '/admin/upload/completeUpload?objectKey='+objectKey+'&uploadId='+uploadId,
    method: 'post',
    data: data
  })
}

// 取消上传
export function cancelUpload(objectKey,uploadId) {
  return request({
    url: '/admin/upload/cancelUpload?objectKey='+objectKey+'&uploadId='+uploadId,
    method: 'get'
  })
}
  • 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
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小惠珠哦/article/detail/873335
推荐阅读
相关标签
  

闽ICP备14008679号