Spring Boot实现文件云存储-下篇

226 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第13天,点击查看活动详情

前言

上一章了讲解了云存储的方法的后端上传,并且详细的分析了该方案存在的问题,本文将讲解前端如何直连OSS上传文件。

前端上传流程

图片.png

  1. 用户向应用服务器申请Policy请求
  2. 应用服务器返回上传Policy和签名给用户。
  3. 用户发送上传请求到OSS

具体实现

前端发送Policy请求

$.ajax({
  url: "/app/common/oss/getOssPolicyKey",
  type: "post",
  data: {
  "ossPath" : ossPath
  },
  success: function(data) {
    ...
  }
});

后端接口实现

 @GetMapping("getOssPolicyKey")
    public Map<String, String> getOssPolicyKey(HttpServletResponse resp,HttpServletRequest request)
    {
        // 获取上传目录
        String ossPath = request.getParameter("ossPath");
        logger.info("enter oss getOssPolicyKey, ossPath:{}", ossPath);
        
        // 获取OSS授权
        String endpoint = ossProperties.getEndpoint();
        String accessId = ossProperties.getAccessKeyId();
        String accessKey = ossProperties.getAccessKeySecret();
        String bucket = ossProperties.getBucket();
        //文件大小
        String fileMaxSize = ossProperties.getFileMaxSize();
        //过期时间
        int expire = ossProperties.getExpire();
        String host = "https://" + bucket + "." + endpoint;
        OSSClient client = new OSSClient(endpoint, accessId, accessKey);
        resp.setHeader("Access-Control-Allow-Origin", "*");

        Map<String, String> respMap = new LinkedHashMap<String, String>();
        
        try
        {
            long expireTime = expire * 60;
            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, ossPath);

            String postPolicy = client.generatePostPolicy(expiration,
                    policyConds);
            byte[] binaryData = postPolicy.getBytes("utf-8");
            String encodedPolicy = BinaryUtil.toBase64String(binaryData);
            String postSignature = client.calculatePostSignature(postPolicy);
            respMap.put("accessid", accessId);
            respMap.put("accessKey", accessKey);
            respMap.put("policy", encodedPolicy);
            respMap.put("signature", postSignature);
            respMap.put("dir", ossPath);
            respMap.put("host", host);
            respMap.put("expire",
                    DateUtil.formatIso8601Date(expiration));
            respMap.put("fileMaxSize", fileMaxSize);

            logger.info("dir:" + ossPath + ",accessid:" + accessId
                    + ", policy:" + encodedPolicy + ",signature:"
                    + postSignature + ",host:" + host + ",expire:"
                    + DateUtil.formatIso8601Date(expiration));
            
            resp.setHeader("Access-Control-Allow-Origin", "*");
            resp.setHeader("Access-Control-Allow-Methods", "GET, POST");
            logger.info("oss getOssPolicyKey end");
        }
        catch (Exception e)
        {
            logger.error("oss getOssPolicyKey error", e);
        }
        return respMap;
    }

说明:需要设置签名的有效时间、上传文件的大小等参数。

返回结果:

{
    "accessid": "myaccessid",
     "accessKey": "myaccessKey",
     "policy": "eyJleHBpcmF0aW9uIjoiMjAyMi0wNi0wN1QxNDozNToyNy4yMjlaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjAwMF0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCIvdGVzdCJdXX0=",
	"signature": "hGzkGIvv0sSGlr864mozP1CvmLA=",
	"dir": "/test",
	"host": "myhost",
	"expire": "2022-06-07T14:35:27.229Z",
	"fileMaxSize": "5"
}

说明:通过调用后端接口返回相关的信息给前端,前端通过相关参数,调用oss的相关组件上传文件。

plupload上传OSS

前端上传组件采用的是plupload,其官网地址如下:

www.plupload.com/

核心js代码

前端实现的方式有很多种,例如vue、js等,本文采用的是js方式实现

function initUpload(fileDiv)
 {       
    	var fileMaxSize = $("#"+fileDiv+"_fileMaxSize").val();
        //上传按钮
        var uploadButton = fileDiv + "UploadBtn";
        
        //文件选择按钮
        var selectFileButton = fileDiv + "SelectBtn";
        var uploader = new plupload.Uploader({
            runtimes : 'html5,flash,silverlight,html4',
            browse_button : selectFileButton, 
            filters:{
                  max_file_size : fileMaxSize,
                  prevent_duplicates : false
            },
           
            multi_selection: true,
            container: document.getElementById(fileDiv),
            flash_swf_url : '${ctx}/js/plugins/oss/lib/plupload-2.1.2/js/Moxie.swf',
            silverlight_xap_url : '${ctx}/js/plugins/oss/lib/plupload-2.1.2/js/Moxie.xap',
            url : 'http://oss.aliyuncs.com',
          
            init: {
                PostInit: function() {                                      
                    setInterval(getAccessKey(fileDiv), 9*60*1000);                                        
                     $("#" + uploadButton).click(function(){
                        uploader.start();
                        return false;
                     });
                },

                FilesAdded: function(up, files) {
                    plupload.each(files, function(file) {
                    });
                                    
                    // 打开遮罩
                    $.util.showProgressLoading();
                    
                    //初始化遮罩参数
                    initProgressText(files);                    
                    uploader.start();
                    
                },

                BeforeUpload: function(up, file) {
                    setUploadParams(up, fileDiv, file);
                    //如果数据库当前目录下有相同的文件名  不允许添加
                    var flag = "false";
                    //TableOid
                    var tableOid = $("#" + fileDiv + "_tableOid").val();
                    //TableName
                    var tableName = $("#" + fileDiv + "_tableName").val();
                    var fileName = file.name;
                    var requestUrl = ctxPath+ "/app/sys/dataFile/validateFileNameRepeat";
                    var reqLoadFile = {
                            url : requestUrl,
                            dataType : 'json',
                            type : 'post', 
                            async : false,
                            data : {
                                "tableName" : tableName,
                                "tableOid" : tableOid,
                                "fileName" : fileName
                                
                            },
                            success : function(d) {
                             
                            },
                            error : function(xhr){
                                alert(xhr);
                            }
                    };
                    $.util.ajax(reqLoadFile);
                    if("true" == flag){
                        return false;
                    }
                    up.start();
                    
                },

                UploadProgress: function(up, file) {
                	
                    //获取文件上传进度区域
                    var fileUploadDiv = document.getElementById("test_ProcessDiv");
                    
                    //显示当前进度百分比
                    fileUploadDiv.getElementsByTagName('b')[0].innerHTML = '<span>' + file.percent + "%</span>";
                    var prog = fileUploadDiv.getElementsByTagName('div')[0];
                    var progBar = prog.getElementsByTagName('div')[0];
                    
                    //更新进度条
                    progBar.style.width= file.percent+'%';
                    progBar.setAttribute('aria-valuenow', file.percent);
                },

                FileUploaded: function(up, file, info) {
                    if (info.status == 200)
                    {   
                    	addProgressText();
                        //调用后端接口,将已上传文件记录到DB中.
                        syncFileRecordToDB(up, file, fileDiv);  
                        
                    }
                    else
                    {
                        //上传失败,打印错误信息
                        alert("上传状态:" + info.status + "," + info.response);                       
                        //从待上传区域中删除文件
                        deletePreUploadFile(up, file);
                    }
                },
                
                UploadComplete : function(uploader,files){
                	//刷新资料类型列表
                    loadFileTypeList();

                    //刷新资料文件列表
                    loadFileList();
                	
                    // 去除遮罩层
                	$.util.hideProgressLoading();

                },

                Error: function(up, err) {                    
                    //OSS返回错误信息
                    if(err.response)
                    {
                        alert($(err.response).find("Message").text());
                        //从待上传区域中删除文件
                        deletePreUploadFile(up, err.file);
                        return;
                    }
                    //错误信息
                    if(err.code)
                    {
                        switch(err.code)
                        {
                            case -100: 
                                alert("上传文件错误");
                                break;
                            case -200: 
                                alert("网络发生错误");
                                break;
                            case -300: 
                                alert("磁盘读写错误");
                                break;
                            case -400: 
                                alert("安全问题,上传错误");
                                break;
                            case -500: 
                                alert("初始化错误");
                                break;                              
                            case -600: 
                                alert("文件超过设定大小"+fileMaxSize);
                                break;                              
                            case -601: 
                                alert("文件类型错误");
                                break;
                            case -602: 
                                alert("文件重复");
                                break;
                            case -700: 
                                alert("图片格式错误");
                                break;
                            case -701: 
                                alert("内存错误");
                                break;
                            case -702: 
                                alert("文件过大");
                                break;
                            default:
                                alert("文件上传错误");
                        }
                    }
                }
            }
        });
        
        uploader.init();
    }

说明:相关使用可以参考官方文档。

扩展知识

OSS除了提供存储之外,还提供了多样化的图片处理,例如图片缩放、图片水印、图片压缩等操作,如果我们上传的文件图片比较大,我们可以在引用oss的图片时添加相关参数对图片进行处理,从而减少相关的流量。

设置图片宽度和高度等比例缩放

有时候有些图片不符合显示要求,我们可以对图片进行缩放处理。

https://xx/dish/globalDish/33_20220525163152.jpg?x-oss-process=image/resize,w_100/h_100

w_100,h_100:将图片的宽度和高度缩放到100.

图片添加水印

之前很多实现都是通过相关的工具类来对图片添加水印,其实OSS提供相关的功能实现非常简单。

https://x'x/dish/globalDish/33_20220525163152.jpg?x-oss-process=image/resize,w_400,h_400/watermark,type_d3F5LXplbmhlaQ,size_30,text_55-z5LqV,color_FFFFFF,shadow_50,t_100,g_se,x_10,y_10

  • text:指定文字水印的文字内容,文字内容需进行Base64编码。
  • type:指定文字水印的字体,字体名称需进行Base64编码。默认值:d3F5LXplbmhlaQ
  • color:指定文字水印的文字颜色,参数值为RGB颜色值。
  • size:指定文字水印的文字大小。
  • shadow:指定文字水印的阴影透明度。
  • rotate:指定文字顺时针旋转角度。
  • t:指定图片水印或水印文字的透明度。
  • g:指定水印在图片中的位置。
  • x:坐标
  • y:坐标

base64位文字编码

    /**
     * 水印编码: 将内容编码成Base64
     *
     * @param str
     * @return
     */
    private static String encode(String str){
        byte[] encode = java.util.Base64.getEncoder().encode(str.getBytes());
        try {
            // 1.将结果中的加号(+)替换成短划线(-)  2.将结果中的正斜线(/)替换成下划线(_)  3.将结果中尾部的等号(=)省略
            return new String(encode, "UTF-8").replace("+","-").replace("/","_");
        } catch (UnsupportedEncodingException e) {
            logger.error("encode error",e);
        }
        return str;
    }

总结

本文介绍了阿里云文件存储采用的前端上传的方案,通过此方案能够提升相关性能和节省服务器的流量,在实际的项目中建议采用此方案。