• 上传文件很费时费力?那是你没用对方式


    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


    前言

    嗨,大家好,我是希留,一个被迫致力于全栈开发的老菜鸟。

    上一篇文章说到上传文件使用云服务商的对象存储,感兴趣的可以阅读该文章:传送门

    发布后有不少伙伴反馈,前后端分离的项目更好的上传方式是使用前端直传的方式。于是我查阅相关文档,连夜把项目里的上传方式改成前端直传了(项目的技术栈是Springboot + Vue),发现上传速度明显提升了。所以这篇文章就来说说,前端直传的方式应该怎么弄呢?


    一、前端直传的优点

    前端数据直传的方式相比较后端上传的方式有不少的优点。

    • 上传速度快。后端上传的方式是用户数据需先上传到应用服务器,之后再上传到COS。而前端直传的方式,用户数据不用通过应用服务器中转,直传到COS。减少了网络请求响应,速度将大大提升。而且COS采用BGP带宽,能保证各地各运营商之间的传输速度。
    • 扩展性好。后端上传的方式会占用带宽,文件上传一多就会占用服务器大量的带宽,导致其它请求阻塞,甚至无法访问等情况。前端直传的方式节约了后端服务器的带宽和负载。
    • 成本低。服务器真正的成本基本上都在带宽上,提升带宽的费用是很贵的。而使用前端直传的方式可以减少文件上传到服务器的带宽,从而降低带宽成本。

    二、实现步骤

    2.1、后端方面

    后端需要提供一个生成临时密钥的接口,具体步骤如下,可参考文档,由于我的后端服务是Java语言,所以这里举例使用的是 Java SDK。

    2.1.1 添加依赖

    <!--腾讯云 COS 临时密钥-->
    <dependency>
        <groupId>com.qcloud</groupId>
        <artifactId>cos-sts_api</artifactId>
        <version>3.1.1</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.1.2 增加接口

    代码如下(示例):

    package com.xiliu.common.controller;
    
    import com.tencent.cloud.CosStsClient;
    import com.tencent.cloud.Response;
    import com.xiliu.common.result.R;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.TreeMap;
    
    /**
     * @author xiliu
     * @description cos存储前端控制器
     * @date 2022/11/13 17:51
     */
    @RestController
    @RequestMapping("/cos")
    public class CosController {
    
        @Value("${cos.secretId}")
        private String secretId;
        @Value("${cos.secretKey}")
        private String secretKey;
        @Value("${cos.regionName}")
        private String regionName;
        @Value("${cos.bucketName}")
        private String bucketName;
    
        @ApiOperation(value = "获取cos临时密钥")
        @GetMapping("/temp-key")
        public R getTempKey() {
            TreeMap<String, Object> config = new TreeMap<String, Object>();
            try {
                // 替换为您的云 api 密钥 SecretId
                config.put("secretId", secretId);
                // 替换为您的云 api 密钥 SecretKey
                config.put("secretKey", secretKey);
                // 临时密钥有效时长,单位是秒,默认 1800 秒,目前主账号最长 2 小时(即 7200 秒),子账号最长 36 小时(即 129600)秒
                config.put("durationSeconds", 1800);
                // 换成您的 bucket
                config.put("bucket", bucketName);
                // 换成 bucket 所在地区
                config.put("region", regionName);
    
                // 只允许用户访问 upload/house 目录下的资源
                config.put("allowPrefixes", new String[] {"upload/house/*"});
    
                // 密钥的权限列表。必须在这里指定本次临时密钥所需要的权限。
                String[] allowActions = new String[] {
                        // 简单上传
                        "name/cos:PutObject",
                        // 表单上传、小程序上传
                        "name/cos:PostObject",
                        // 分块上传
                        "name/cos:InitiateMultipartUpload",
                        "name/cos:ListMultipartUploads",
                        "name/cos:ListParts",
                        "name/cos:UploadPart",
                        "name/cos:CompleteMultipartUpload"
                };
                config.put("allowActions", allowActions);
    
                Response response = CosStsClient.getCredential(config);
                return R.ok(response);
            } catch (Exception e) {
                e.printStackTrace();
                throw new IllegalArgumentException("no valid secret !");
            }
        }
    }
    
    
    • 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

    2.1.3 测试接口

    由于是测试,接口的安全校验我就去掉了,方便测试是否能正常生成临时密钥。结果如下图所示,正常生成了。在这里插入图片描述

    2.2、前端方面

    前端方面,可以参考文档,我使用的前端框架是Vue,具体步骤如下,上传功能可以封装成一个组件,提供给有需要的地方调用。

    2.2.1 安装 cos-js-sdk-v5 依赖

    npm install --save cos-js-sdk-v5
    
    • 1

    2.2.2 新建组件

    在 components 目录下新建一个 upload 目录,在 upload 目录下新建 multiUpload.vue 组件。

    代码示例如下:

    <template> 
      <div>
        <el-upload
          action=""
          list-type="picture-card"
          :file-list="fileList"
          :before-upload="beforeUpload"
          :on-remove="handleRemove"
          :http-request="handleUploadFile"
          :on-preview="handlePreview"
          :limit="maxCount"
          :on-exceed="handleExceed"
        >
          <i class="el-icon-plus"></i>
        </el-upload>
        <!--进度条-->
        <el-progress v-show="showProgress"  :text-inside="true" :stroke-width="15"
           :percentage="progress" status="success"></el-progress>
        <el-dialog :visible.sync="dialogVisible">
          <img width="100%" :src="dialogImageUrl" alt="">
        </el-dialog>
      </div>
    </template>
    <script>
      import COS from 'cos-js-sdk-v5'
      import { getCosTempKey } from '@/api/upload'
    
      export default {
        name: 'multiUpload',
        props: {
          //图片属性数组
          value: Array,
          //最大上传图片数量
          maxCount:{
            type:Number,
            default:9
          }
        },
        data() {
          return {
            // 图片预览
            dialogVisible: false,
            // 图片预览地址
            dialogImageUrl:null,
            // COS
            cosData: {},
            // 进度条的显示
            showProgress: false,
            // 进度条数据
            progress: 0,
            // 文件表单
            fileParams: {
              // 上传的文件目录
              folder: '/upload/house/'
            },
          };
        },
        computed: {
          fileList() {
            let fileList=[];
            for(let i=0;i<this.value.length;i++){
              fileList.push({url:this.value[i]});
            }
            return fileList;
          }
        },
        methods: {
          emitInput(fileList) {
            let value=[];
            for(let i=0;i<fileList.length;i++){
              value.push(fileList[i].url);
            }
            this.$emit('input', value)
          },
          // 移除图片
          handleRemove(file, fileList) {
            this.emitInput(fileList);
          },
          // 预览图片
          handlePreview(file) {
            this.dialogVisible = true;
            this.dialogImageUrl = file.url;
          },
          // 上传预处理
          beforeUpload(file) {
            return new Promise((resolve, reject) => {
              const isImage = file.type.indexOf("image/") != -1;
              const isLt2M = file.size / 1024 / 1024 < 10;
    
              if (!isImage) {
                this.$modal.msgError("文件格式错误,请上传图片类型,如:JPG,PNG后缀的文件。");
                reject(false)
              }
              if (!isLt2M) {
                this.$message.error("上传头像图片大小不能超过 10MB!");
                reject(false)
              }
              // 获取cos临时密钥
              getCosTempKey().then(response => {
                this.cosData = response.data;
                resolve(true)
              }).catch(error => {
                this.$message.error('获取cos临时密钥失败!msg:' + error)
                reject(false)
              })
            })
          },
          // 前端直传文件
          handleUploadFile(file) {
            let that = this;
            // 获取COS实例
            const cos = new COS({
              // 必选参数
              getAuthorization: (options, callback) => {
                const obj = {
                  TmpSecretId: that.cosData.credentials.tmpSecretId,
                  TmpSecretKey: that.cosData.credentials.tmpSecretKey,
                  XCosSecurityToken: that.cosData.credentials.sessionToken,
                  // 时间戳,单位秒,如:1580000000
                  StartTime: that.cosData.startTime,
                  // 时间戳,单位秒,如:1580000900
                  ExpiredTime: that.cosData.expiredTime
                }
                callback(obj)
              }
            });
            // 文件路径和文件名
            let cloudFilePath = this.fileParams.folder + `${+new Date()}` + '_' + file.file.name
    
            // 执行上传服务
            cos.putObject({
              // 你的存储桶名称
              Bucket: 'xiliu-1259663924',
              // 你的存储桶地址
              Region: 'ap-guangzhou',
              // key加上路径写法可以生成文件夹
              Key: cloudFilePath,
              StorageClass: 'STANDARD',
              // 上传文件对象
              Body: file.file,
              onProgress: progressData => {
                if (progressData) {
                  that.showProgress = true
                  that.progress = Math.floor(progressData.percent * 100)
                }
              }
            },(err, data) => {
              if (data && data.statusCode === 200) {
                let uploadResult = `https://${data.Location}`
                that.showProgress = false
                that.$message({message: '上传成功', type: 'success'})
                this.fileList.push({name: file.name,url:uploadResult});
                this.emitInput(this.fileList);
              } else {
                that.$message.error("上传失败,请稍后重试!")
              }
            })
          },
          // 文件超出个数限制
          handleExceed(files, fileList) {
            this.$message({
              message: '最多只能上传'+this.maxCount+'张图片',
              type: 'warning',
              duration:1000
            });
          },
        }
      }
    </script>
    <style>
    
    </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

    2.2.3 使用组件

    代码示例如下:

    <el-form-item label="房屋图片/视频:">
      <multi-upload v-model="selectHousePics"></multi-upload>
    </el-form-item>
    
    • 1
    • 2
    • 3

    效果如下图所示:
    在这里插入图片描述

    总结

    以上就是本文的全部内容了,感谢大家的阅读。

    如果觉得文章对你有帮助,还不忘帮忙点赞、收藏、关注、评论哟,您的支持就是我创作最大的动力!


  • 相关阅读:
    【易错小点记录】坑人的for循环与逻辑或
    css滤镜
    【JavaScript】快速学习JS
    初识EasyAR
    人工智能学习:载入PASCAL VOC数据集(6)
    开源模型应用落地-LangChain高阶-知识图谱助力记忆增强
    el-tree 懒加载数据,展开的节点与查询条件联动
    H5 台球猜位置小游戏
    【数据结构初阶】双链表
    SAP UI5 sap.ui.base.ManagedObject 的构造函数参数讲解
  • 原文地址:https://blog.csdn.net/qq_30859353/article/details/127985121