html+vue组件实现阿里云OSS对接

本文涉及的产品
对象存储 OSS,20GB 3个月
对象存储 OSS,恶意文件检测 1000次 1年
对象存储 OSS,内容安全 1000次 1年
简介: html+vue组件实现阿里云OSS对接

任何问题都有解决的办法,无法可想的事是没有的。──爱迪生

问题场景:

一个thymeleaf项目,上传图片、视频是直接使用Java代码保存到服务器上

于是服务器压力激增,网页静态资源变得卡顿

于是

我们需要将图片、视频上传移植到阿里云OSS

阿里云对象存储OSS(Object Storage Service)是阿里云提供的海量、安全、低成本、高持久的云存储服务。

一句话描述:它是一个很棒的存取文件的在线服务器

我们可以使用它进行客户端上传,文件不通过服务端也可以上传,这样减轻了我们服务端压力

我们需要一系列配置才可以对接完成,查阅阿里云OSS官方文档发现:JavaScript客户端签名直传的方式最为简单,但这种方式是有弊端的,客户端通过JavaScript把存取文件的认证信息写在代码里面有泄露的风险

因此

我们采用服务端签名后直传的方式进行上传文件,我们待会儿介绍~

准备工作:

登录阿里云官网。

将鼠标移至产品,单击对象存储 OSS,打开 OSS 产品详情页面。

在 OSS 产品详情页,单击立即开通。

开通服务后,在 OSS 产品详情页单击管理控制台直接进入 OSS 管理控制台界面。

您也可以单击位于官网首页右上方菜单栏的控制台,进入阿里云管理控制台首页,然后单击左侧的对象存储 OSS 菜单进入 OSS 管理控制台界面。

根据官方文档提供步骤视频可以完成开通、创建Bucket、进行授权、配置跨域等

我们到阿里云RAM访问控制台来添加一个用户


填入用户账号信息、勾选编程访问后点击确定


然后我们点击复制,找个地方粘贴下来我们的AccessKey ID以及AccessKey Secret

然后我们点击添加权限为该用户授权

开始对接

官方文档流程图片:

这里可以这么理解:

假设小王接到老板的任务:将这堆货物放到仓库中去,但仓库大门的门禁需要门卡

所以上图中的类比

1:小王去保管室拿门卡

2:小王拿到门卡回来

3:小王搬运货物到仓库,存放货物

因此我们首先先做“保管室”的功能

后端代码实现

这里就是用我们的Java代码在服务端实现:

引入alicloud-oss的依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
    <version>2.2.0.RELEASE</version>
</dependency>

application.yml,注意版本或者依赖不同,配置有可能改变,例如我最下面的Demo使用的boot的依赖(配置方式发生了变化,不过我们代码都还是一样的,只是注意@Value需要写对)

spring:
  cloud:
    alicloud:
      access-key: <你的AccessKey>
      secret-key: <你的SecretKey>
      oss:
        endpoint: <你的endpoint(可以在你的OSS控制台概览看到)>
        bucket: <你的bucket(可以在你的OSS控制台概览看到)>

Controller

package com.ruben.simpleoss.controller;
import com.ruben.simpleoss.service.OssService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.Map;
/**
 * 上传Controller
 *
 * @author <achao1441470436@gmail.com>
 * @since 2021/6/18 17:40
 */
@RestController
public class UploadController {
    @Resource
    private OssService ossService;
    /**
     * oss上传文件
     *
     * @author <achao1441470436@gmail.com>
     * @since 2021/6/17 10:07
     */
    @GetMapping("oss")
    public Map<String, Object> oss() {
        return ossService.getMark();
    }
}

Service

package com.ruben.simpleoss.service;
import java.util.Map;
/**
 * Oss服务层
 *
 * @author <achao1441470436@gmail.com>
 * @since 2021/6/17 10:21
 */
public interface OssService {
    Map<String, Object> getMark();
}

ServiceImpl

package com.ruben.simpleoss.service.impl;
import com.aliyun.oss.OSS;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import com.ruben.simpleoss.service.OssService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
 * @ClassName: UploadServiceImpl
 * @Description: 获取OSS签证
 * @Date: 2020/6/3 21:55
 * *
 * @author: achao<achao1441470436 @ gmail.com>
 * @version: 1.0
 * @since: JDK 1.8
 */
@Service
public class OssServiceImpl implements OssService {
    @Resource
    OSS ossClient;
    @Value("${spring.cloud.alicloud.oss.endpoint}")
    private String endpoint;
    @Value("${spring.cloud.alicloud.oss.bucket}")
    private String bucket;
    @Value("${spring.cloud.alicloud.access-key}")
    private String accessId;
    /**
     * 获取临时签证
     *
     * @return
     */
    @Override
    public Map<String, Object> getMark() {
        String host = "https://" + bucket + "." + endpoint; // host的格式为 bucketname.endpoint
        // callbackUrl为 上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
//        String callbackUrl = "http://88.88.88.88:8888";
        String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        String dir = format + "/"; // 用户上传文件时指定的前缀。
        Map<String, String> respMap = null;
        try {
            long expireTime = 3000;
            long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
            Date expiration = new Date(expireEndTime);
            PolicyConditions policyConds = new PolicyConditions();
            policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
            policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
            String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
            byte[] binaryData = postPolicy.getBytes(StandardCharsets.UTF_8);
            String encodedPolicy = BinaryUtil.toBase64String(binaryData);
            String postSignature = ossClient.calculatePostSignature(postPolicy);
            respMap = new LinkedHashMap<String, String>(8);
            respMap.put("accessid", accessId);
            respMap.put("policy", encodedPolicy);
            respMap.put("signature", postSignature);
            respMap.put("dir", dir);
            respMap.put("host", host);
            respMap.put("expire", String.valueOf(expireEndTime / 1000));
            // respMap.put("expire", formatISO8601Date(expiration));
        } catch (Exception e) {
            // Assert.fail(e.getMessage());
            System.out.println(e.getMessage());
        } finally {
            ossClient.shutdown();
        }
        Map<String, Object> result = new HashMap<>(4);
        result.put("data", respMap);
        result.put("code", 20000);
        return result;
    }
}


然后我们运行访问一下,就像第一步小王去拿门卡了

可以看到拿到我们的临时凭证了,就像第二步小王拿着门卡回来了

然后我们现在要拿着这些凭证去上传文件,就像第三步小王要去存货物

按照PostObject的文档测试一下

上传完成之后我们这里是没有回调的,因为我们这里没有配置callback,如有需要,可以查看Callback文档


上传完成之后我们可以在控制台看到我们上传的文件


然后如果我们在代码里使用文件的url的话,就使用host+/+(key替换掉${filename}占位符为文件名)即可

例如我的hosthttps://will-deprecated.oss-cn-chengdu.aliyuncs.com

加上/再加上key为:2021-06-18/17585793-95b9-4dbd-8dfc-2302d6afa5df_${filename}替换掉文件名就是2021-06-18/17585793-95b9-4dbd-8dfc-2302d6afa5df_partnerhead.jpg(其实我测试过了,不用${filename}占位符,直接使用文件名也可以上传成功)

最终https://will-deprecated.oss-cn-chengdu.aliyuncs.com/2021-06-18/17585793-95b9-4dbd-8dfc-2302d6afa5df_partnerhead.jpg则是我们完整的文件路径

前端代码实现

使用html+vue

主页面,引用组件的地方

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>OSS上传Demo</title>
    <link href="/css/elementUI.css" rel="stylesheet">
    <script src="/js/jquery-3.5.1.min.js"></script>
    <script src="/js/vue.min.js"></script>
    <script src="/js/elementUI.js"></script>
    <script src="/js/singleUpload.js"></script>
</head>
<body>
<div id="myapp">
    <single-upload ref="singleUpload" v-model="imageUrl"></single-upload>
    文件url:{ {imageUrl} }
</div>
<script>
    new Vue({
        el: '#myapp',
        data: {
            imageUrl: ''
        }
    })
</script>
</body>
</html>

自己写的组件:

function policy() {
    return new Promise((resolve, reject) => {
        $.ajax({
            url: '/oss',
            type: 'GET',
            contentType: 'application/json; charset=UTF-8',
            success: function (res) {
                if (res.code == 20000) {
                    resolve(res)
                } else {
                    reject(res)
                }
            },
            error: function (res) {
                reject(res)
            }
        });
    });
}
Vue.component('singleUpload', {
    props: ['value'],
    template: '  <div class="single-upload">' +
        '    <el-upload' +
        '        :before-upload="beforeUpload"' +
        '        :data="dataObj"' +
        '        :file-list="fileList"' +
        '        :multiple="false"' +
        '        :on-preview="handlePreview"' +
        '        :on-remove="handleRemove"' +
        '        :on-success="handleUploadSuccess"' +
        '        :show-file-list="showFileList"' +
        '        :action="dataObj.host"' +
        '        list-type="text"' +
        '        style="display: flex;"' +
        '    >' +
        '    <el-button size="small" type="primary">点击上传</el-button>' +
        '    </el-upload>' +
        '    <el-dialog :modal="false" :visible.sync="dialogVisible">' +
        '      <img width="100%;" v-if="isImg(fileList[0].url)" :src="fileList[0].url" alt="图片找不到了..."/>' +
        '      <video width="900px" controls autoplay muted v-if="isVideo(fileList[0].url)" :src="fileList[0].url" alt="视频找不到了..."/>' +
        '    </el-dialog>' +
        '  </div>',
    data() {
        return {
            dataObj: {
                policy: "",
                signature: "",
                key: "",
                ossaccessKeyId: "",
                dir: "",
                host: ""
                // callback:'',
            },
            dialogVisible: false
        };
    },
    computed: {
        imageUrl() {
            return this.value;
        },
        imageName() {
            if (this.value != null && this.value !== "") {
                return this.value.substr(this.value.lastIndexOf("/") + 1);
            } else {
                return null;
            }
        },
        fileList() {
            return [
                {
                    name: this.imageName,
                    url: this.imageUrl
                }
            ];
        },
        showFileList: {
            get: function () {
                return (
                    this.value !== null && this.value !== "" && this.value !== undefined
                );
            },
            set: function (newValue) {
            }
        }
    },
    methods: {
        isVideo() {
            let fileType = this.getFileType()
            return ~['.mp4', '.avi'].indexOf(fileType)
        },
        isImg() {
            let fileType = this.getFileType()
            return ~['.png', '.jpg', '.jpeg', '.gif'].indexOf(fileType)
        },
        getFileType() {
            let fileType = this.value.substring(this.value.lastIndexOf('.'))
            return fileType
        },
        getUUID() { //生成UUID
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
                return (c === 'x' ? (Math.random() * 16 | 0) : ('r&0x3' | '0x8')).toString(16)
            })
        },
        emitInput(val) {
            this.$emit("input", val);
        },
        handleRemove(file, fileList) {
            this.emitInput("");
        },
        handlePreview(file) {
            this.dialogVisible = true;
        },
        beforeUpload(file) {
            let _self = this;
            return new Promise((resolve, reject) => {
                policy()
                    .then(response => {
                        console.log(response)
                        _self.dataObj.policy = response.data.policy;
                        _self.dataObj.signature = response.data.signature;
                        _self.dataObj.ossaccessKeyId = response.data.accessid;
                        _self.dataObj.key = response.data.dir + this.getUUID() + "_${filename}";
                        _self.dataObj.dir = response.data.dir;
                        _self.dataObj.host = response.data.host;
                        resolve(true);
                    })
                    .catch(err => {
                        reject(false);
                    });
            });
        },
        handleUploadSuccess(res, file) {
            console.log("上传成功...");
            this.showFileList = true;
            this.fileList.pop();
            this.fileList.push({
                name: file.name,
                url:
                    this.dataObj.host +
                    "/" +
                    this.dataObj.key.replace("${filename}", file.name)
            });
            this.emitInput(this.fileList[0].url);
            console.log(this.fileList[0]);
        }
    }
})

注意引入一些文件:

最终效果:





项目源码

本次案例完整项目代码:https://gitee.com/VampireAchao/simple-oss.git

这个项目基本能应对绝大部分场景,它那次请求签证接口是使用的jQueryajax

如果您是纯vue项目,可以看我以往这个项目https://gitee.com/VampireAchao/my-vue-app.git

小结

通过对这次的OSS对接,复习了如何在html页面中引入vueelementUI、引入vue自定义组件、进行第三方服务接入等…

最后,希望本文能对您有所帮助~

相关实践学习
借助OSS搭建在线教育视频课程分享网站
本教程介绍如何基于云服务器ECS和对象存储OSS,搭建一个在线教育视频课程分享网站。
相关文章
|
21天前
|
JavaScript
Vue 父传子组件传参 defineProps
Vue 父传子组件传参 defineProps
|
21天前
|
JavaScript 前端开发
Vue 创建组件
Vue 创建组件
|
25天前
|
JavaScript
vue给table组件添加合计
vue给table组件添加合计
12 0
|
1月前
|
存储 JavaScript BI
vue组件间通信的几个方法
vue组件间通信的几个方法
23 0
|
1月前
|
JavaScript
vue 异步加载组件
vue 异步加载组件
20 3
|
1月前
|
JSON JavaScript 前端开发
【form-generator在线表单生成---vue父组件调用vue弹框组件】
【form-generator在线表单生成---vue父组件调用vue弹框组件】
36 1
|
1月前
|
JavaScript
如何使用Vue的路由实现组件的懒加载和按需加载?
如何使用Vue的路由实现组件的懒加载和按需加载?
29 1
|
21天前
|
JavaScript
vue路由导航守卫(全局守卫、路由独享守卫、组件内守卫)
vue路由导航守卫(全局守卫、路由独享守卫、组件内守卫)
35 0
|
12天前
|
JavaScript
Vue Steps步骤组件用法
Vue Steps步骤组件用法
14 0
|
17天前
|
JavaScript
vue 组件注册
vue 组件注册