阿里云平台OSS对象存储

 

OSS即“OpenStorageService”,概念上没啥新意,就是本地存储搬到阿里云平台上了,单个存储对象大小可以达到5G,看了下阿里的OSS教程java版本,

使用原生js和servlet实现,这基本就能看不能用,为了做点通用的,我们来玩一把OSS,抓下时髦的尾巴。

 

准备:

Idea2019.03/Gradle6.0.1/JDK11.0.4/Lombok0.28/SpringBoot2.2.4RELEASE/mybatisPlus3.3.0/Soul2.1.2/Dubbo2.7.5/Mysql8.0.11/Vue2.5/OSS

难度: 新手--战士--老兵--大师

目标:

1.Vue前端+Java后端结合实现OSS图片对象存储

步骤:

为了遇见各种问题,同时保持时效性,我尽量使用最新的软件版本。代码地址:https://github.com/xiexiaobiao/vehicle-shop-admin

1 OSS简介

阿里云官网资料太多,请君移步,我就不搬运了。

2 分析原理

很多时候,我们上传文件都是前端把待传数据先给后端应用服务器,然后由后端Server再分类存储到比如Mysql,Mongo等等,由于现代的前端功能已十分强大,

当然可以直接由前端上传文件直接到OSS,后端Server只需要告诉上传到哪里以及如何上传即可,这样可以降低后端负载, 流程如下:

 

  1. 用户每次需要上传时,web端先向应用服务器请求上传Policy(就是OSS存储地址和目录、账号密码信息、签名、过期信息等)和                                                                                                                      回调(上传过程和结果信息反馈给需要的服务器,做展示/记录等用,户上传完文件后,不会直接得到返回结果,而是先通知应用服务器,再把结果转达给用户)。
  2. 应用服务器返回给web端上传Policy和回调设置。
  3. web端直接向OSS发送文件上传请求,并上传文件。
  4. OSS根据用户的回调设置,发送回调请求给应用服务器。
  5. 应用服务器返回响应给OSS。
  6. OSS将应用服务器返回的内容返回给web端。

这里有个模式问题,其实如果把OSS相关的Policy信息直接写在前端代码里,然后直接上传文件,也可以的,只是安全性会让人心慌,算是方案一,

如果仍然由后端提供Policy而不要回调信息,直接返回信息给web端就是方案二,上面的方式就是方案三了,我这里以方案三说事。

3 看前端代码

使用elementUI的upload组件,具体可以见elementUI的官方组件介绍,了解各属性含义:src/components/Upload/singleUpload.vue

    <el-upload
      ref="upload"
      :action="useOss?ossUploadUrl:minioUploadUrl"
      :limit="1"
      :data="useOss?dataObj:null"
      list-type="picture"
      :multiple="false"
      :file-list="fileList"
      :auto-upload= "autoUpload"
      :show-file-list="true"
      :before-upload="beforeUpload"
      :on-remove="handleRemove"
      :on-success="handleUploadSuccess"
      :on-preview="handlePreview">
      <el-button slot="trigger" size="small" type="primary">选取文件</el-button>
      <!--以下为如果手动触发上传的按钮-->
 <!-- <el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload">上传到服务器</el-button>-->
      <div slot="tip" class="el-upload__tip">只能上传一张jpg/png文件,且不超过10MB</div>
    </el-upload>

上面代码的核心要点:

  • 并通过:auto-upload设置为自动提交,即只要选择好文件后就自动提交,当然也可以先选好文件,再通过按钮触发提交,见我注释掉的部分。
  • :before-upload="beforeUpload" 即真实开始上传文件前做的工作:
beforeUpload(file) {
        let _self = this;
        if(!this.useOss){
          //不使用oss不需要获取策略
          returntrue;
        }
        returnnewPromise((resolve, reject) => {
          policy().then(response => {
            // 返回的对象,多一层data封装,故写为response.data.data
            _self.dataObj.policy = response.data.data.policy;
            _self.dataObj.signature = response.data.data.signature;
            _self.dataObj.ossaccessKeyId = response.data.data.accessKeyId;
            _self.dataObj.key = response.data.data.dir + '/'+this.fileNameUUID +'${filename}';
            _self.dataObj.dir = response.data.data.dir;
            _self.dataObj.host = response.data.data.host;
            // _self.dataObj.callback = response.data.data.callback;
            resolve(true)
          }).catch(err => {
            console.log(err)
            reject(false)
          })
        })
      },

以上通过axios向后端发异步请求,policy()方法我放在其他文件中了,就是通过axios的GET访问去后端获取Policy,然后再赋值到当前的存储变量上,

这样就取得了Policy信息,注意_self.dataObj.key这里的 '/' 不能放 '${filename}' 前,否则oss存储时会自动增加一层目录目录!!只要以上正确,基本就能自动完成上传了。

4 看后端代码

OSS专用的依赖:

compile group: 'com.aliyun.oss', name: 'aliyun-sdk-oss', version: '3.8.1'
compile group: 'com.aliyun', name: 'aliyun-java-sdk-core', version: '4.5.0'
compile group: 'com.aliyun', name: 'aliyun-java-sdk-sts', version: '3.0.1'

 

com.biao.shop.stock.impl.AliyunOssServiceImpl, 即上面的前端请求Policy的处理对象,Controller我就不展示了,就是响应前端REST,然后调用这个服务:

@Service
publicclass AliyunOssServiceImpl implements AliyunOssService {

    privatestaticfinal Logger LOGGER = LoggerFactory.getLogger(AliyunOssServiceImpl.class);
    @Value("${aliyun.oss.policy.expire}")
    privateint ALIYUN_OSS_EXPIRE;
    @Value("${aliyun.oss.maxSize}")
    privateint ALIYUN_OSS_MAX_SIZE;
    @Value("${aliyun.oss.callback}")
    private String ALIYUN_OSS_CALLBACK;
    @Value("${aliyun.oss.bucketName}")
    private String ALIYUN_OSS_BUCKET_NAME;
    @Value("${aliyun.oss.endpoint}")
    private String ALIYUN_OSS_ENDPOINT;
    @Value("${aliyun.oss.dir.prefix}")
    private String ALIYUN_OSS_DIR_PREFIX;

    private OSSClient ossClient;

    @Autowired
    public AliyunOssServiceImpl(OSSClient ossClient) {
        this.ossClient = ossClient;
    }

    @Override
    public ObjectResponse<AliyunPolicy> policy() {

        ObjectResponse<AliyunPolicy> result = new ObjectResponse<>();
        // 存储目录,按天区分
        DateTimeFormatter dtf =   DateTimeFormatter.ofPattern("yyyy/MM/dd");
        String dir = ALIYUN_OSS_DIR_PREFIX + LocalDateTime.now().toLocalDate().format(dtf);
        // 签名有效期
        long expireEndTime = System.currentTimeMillis() + ALIYUN_OSS_EXPIRE * 1000;
        Date expiration = new Date(expireEndTime);
        // 文件大小
        long maxSize = ALIYUN_OSS_MAX_SIZE * 1024 * 1024;
        // 回调
        AliyunCallbackParam callback = new AliyunCallbackParam();
        callback.setCallbackUrl(ALIYUN_OSS_CALLBACK);
        callback.setCallbackBody("filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}");
        callback.setCallbackBodyType("application/x-www-form-urlencoded");
        // 提交节点
        String action = "http://" + ALIYUN_OSS_BUCKET_NAME + "." + ALIYUN_OSS_ENDPOINT;
        try {
            PolicyConditions policyConds = new PolicyConditions();
            policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, maxSize);
            policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
            String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
            byte[] binaryData = postPolicy.getBytes(StandardCharsets.UTF_8);
            String policy = BinaryUtil.toBase64String(binaryData);
            String signature = ossClient.calculatePostSignature(postPolicy);
            // 对"callback"属性进行base64编码编码
            String callbackData = BinaryUtil.toBase64String(
                    JacksonUtil.convertToJson(callback).getBytes(StandardCharsets.UTF_8));
            // 返回结果
            AliyunPolicy aliyunPolicy = new AliyunPolicy();
            aliyunPolicy.setAccessKeyId(ossClient.getCredentialsProvider().getCredentials().getAccessKeyId());
            aliyunPolicy.setPolicy(policy);
            aliyunPolicy.setSignature(signature);
            aliyunPolicy.setExpire(String.valueOf(expireEndTime));
            aliyunPolicy.setDir(dir);
            aliyunPolicy.setCallback(callbackData);
            aliyunPolicy.setHost(action);
            result.setCode(RespStatusEnum.SUCCESS.getCode());
            result.setMessage(RespStatusEnum.SUCCESS.getMessage());
            result.setData(aliyunPolicy);
        } catch (Exception e) {
            LOGGER.error("signature failed !!", e);
        }
        return result;
    }
}

以上我做了个响应的对象封装,ObjectResponse{code,message,data},关于OSS的账号密码信息,我都放在配置文件统一管理;

注意OSSClient要通过Configuration类中生成一下。可以看到以上设置了一系列的OSS存储政策(Policy):存储目录,按天区分;签名有效期;文件大小限制;

回调地址;提交上传的OSS地址等等。

5 再看前端代码:

src/components/Upload/singleUpload.vue:

这是处理每次上传文件完成之后的逻辑,如果设置为可以上传多个文件,那么这个会执行多次。因为OSS存储,如果同目录下文件同名就直接覆盖,

它不会询问大家意见的!所以这里有个小技巧:我通过getFileNameUUID自动生成个随机前缀,绑定到一个本地变量,每次上传成功,都刷新一下,

再混入url和key值,perfect!!

handleUploadSuccess(res, file) {
        this.showFileList = true;
        this.fileList.pop();
        let url = this.dataObj.host + '/' + this.dataObj.dir +  '/' + this.fileNameUUID+ file.name;
        if(!this.useOss){
          //不使用oss直接获取图片路径
          url = res.data.url;
        }
        this.fileList.push({name: this.fileNameUUID + file.name, url: url});
        // 每次上传完成,更新fileNameUUID
        this.getFileNameUUID();
        this.emitInput(this.fileList[0].url);
      },
      /*随机生成文件前缀,防止同名覆盖*/
      getFileNameUUID(){
        this.fileNameUUID = (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(0,4);
      },

 

效果展示,上传中,可以看到进度:

 

完成后,可以直接删除,或者查看大图,如果做的完美一点,点删除,要触发OSS删除事件,我代码省略没写啦。

 

 当然,还有些其他组件,如其他一些封装类Bean,我就不细说啦,直接看我代码即可!

@Data
publicclass AliyunPolicy {
    private String AccessKeyId;
    private String host;
    private String policy;
    private String signature;
    private String expire;
    private String callback;
    private String dir;
}

后记:

1,我买了一套阿里的云产品:ECS 弹性计算服务器,RDS云关系DB,OSS对象存储,把我自己开发的一套前后端都部署上了,

体验就是真羡慕有钱人,起步价的机器比如2G内存,上5个微服务基本就跑满了!在想尽了各种省内存办法又没辙的情形下,我

只能又拉了一台ECS,才架起了网关,注册中心,内存消耗记录:Nacos 0.4G,zookeeper 0.1G,微服务模块5个:authority 200M,

customer 200M,stock 200M,order 200M,business 200M,其他Nginx 100M,souladmin 0.2G,soulbootstrap 0.2G,另外就是

传说Jetty比Tomcat省内存,反正我没看出来,估计是服务数量级不够

2,如果公司money多,还是推荐上这些云服务的,至少不会上演最近的微盟删库但跑路不成功的事件,云服务自动有各种容灾备份

机制,如异地备,定期备,不亦乐乎?

3,关于callback没细说,具体看代码吧,代码是最最好的老师。

 

全文完!


推荐阅读:

 

 

posted @ 2020-02-29 09:53  甲由崽  阅读(919)  评论(0编辑  收藏  举报