之前做项目 , 写的几个图片上传的代码 , 都是存储在本地系统服务器里面的 , 后来发现, 这样的存储方式有一个很大的弊端 , 那就是系统服务器里面存储大量的静态图片资源 , 由于大量的图片资源会很大程度上消耗服务器资源 . 网站运行时也会大量限制服务器系统的运行速度 .
当然 , 在小型的项目应用中 , 假如系统中需要的图片/文件资源量不大的情况下 , 可以采用在服务器中划分出一片静态空间进行图片管理 , 系统后续的文件/图片资源的维护只需要维护这一个文件夹就好了 . 当然 , 这只限于比较小且图片/文件需求量不大的项目中 . 毕竟这另一方面也可以大量的节省系统使用开销.
在其他的项目应用中 , 如果系统图片/文件使用需求比较大的情况下 , 可以采用单独搭建一台服务器 , 单独用于存储并管理系统上传的文件资源
这种处理方式优点是在于 :
1.系统本身服务器和文件资源管理服务器相互独立 , 两台服务器都上线部署 , 系统调用文件/图片资源的时候可以直接根据静态资源服务器中的路径来进行调用 .
2. 系统的运行速度和效率得到了很大的提升
3. 对于系统所用到的文件/图片资源在独立的服务器中进行管理和查询也比较便捷 .
相对而言的缺点在于:
单独部署一台静态服务器的费用会增加不少. 这种处理方式比较适用于中大型项目中或者是对于文件/图片资源需求量比较大的项目中.
这次文件/图片上传用到的是OSS服务器 , 上传组件是用的Puploader上传组件. 后台框架用的是springBoot框架 , 前端框架是thymeleaf框架.
这里的图片上传是作为一个公共组件来封装的 , 也就是先写一个上传的页面 , 对应的后台代码也是封装到与该页面对应的controller中.
所需要引用的插件:
上传公共html代码部分
1 <!DOCTYPE html> 2 <html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/web/thymeleaf/layout" 3 layout:decorator="/base/freeLayout"><!-->基础layer.js的引用可以放在公共引用中<--> 4 <head> 5 <title>上传</title> 6 <link rel="stylesheet" th:href="@{/css/common/upload.css}"> 7 </head> 8 <body> 9 <div layout:fragment="content" class="content"> 10 <form name="theform" style="display:none;"> 11 <input type="hidden" th:value="${uploadVo.fileType}" id="fileType" /><!-->调用上传组件时传参 : 上传文件类型 , 可以灵活的对需要上传的文件进行过滤 , 具体的过滤在<--> 12 <input type="hidden" th:value="${uploadVo.maxSize}" id="maxSize" /><!-->上传文件的大小size , 可在调用该组件的时候进行传值 , 在这里进行显示<--> 13 <input type="hidden" th:value="${uploadVo.maxFiles}" id="maxFiles" /><!-->一次性可以上传的文件的数量 , 可用于单文件/多文件过滤<--> 14 <input type="radio" name="myradio" value="local_name" /> 上传文件名字保持本地文件名字 15 <input type="radio" name="myradio" value="random_name" checked=true /> 上传文件名字是随机文件名, 后缀保留 16 </form> 17 18 <h4>您所选择的文件列表:</h4> 19 <div id="ossfile">你的浏览器不支持flash,Silverlight或者HTML5!</div> 20 21 <br/> 22 23 <div id="container"> 24 <a id="selectfiles" href="javascript:void(0);" class=\'btn\'>选择文件</a> 25 <a id="postfiles" href="javascript:void(0);" class=\'btn\'>开始上传</a> 26 </div> 27 28 <pre id="console"></pre><!-->上传过程中到的异常在这里进行显示<--> 29 30 <p> </p> 31 <script type="text/javascript" th:src="@{/assets/plupload-2.1.2/js/plupload.full.min.js}"></script> 32 <script type="text/javascript" th:src="@{/js/common/upload.js}"></script> 33 </div> 34 </body> 35 </html>
JS部分代码:
1 accessid = \'\'; 2 accesskey = \'\'; 3 host = \'\'; 4 policyBase64 = \'\'; 5 signature = \'\'; 6 callbackbody = \'\'; 7 filename = \'\'; 8 key = \'\'; 9 expire = 0; 10 g_object_name = \'\'; 11 g_object_name_type = \'\';//以上变量值都是从后台controller中生命过来的. 12 now = timestamp = Date.parse(new Date()) / 1000; 13 14 var maxFiles = $(\'#maxFiles\').val(); // 最大文件个数 , 读取页面中回显的最大文件数 ,为空 , 取默认值1 , 不为空, 当前值 15 if (/^\d+$/.test(maxFiles)) { 16 maxFiles = parseInt(maxFiles); 17 } else { 18 maxFiles = 1; 19 } 20 21 function send_request()//xmlhttpRequest请求调用后台controller方法 , 获取静态资源数据 22 { 23 var xmlhttp = null; 24 if (window.XMLHttpRequest)//判断当前浏览器版本 , IE7+之后的版本支持xmlHttpRequest对象 25 { 26 xmlhttp=new XMLHttpRequest(); 27 } 28 else if (window.ActiveXObject)//IE6-之前的版本不支持 , ,使用该方法声明对象 29 { 30 xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); 31 } 32 33 if (xmlhttp!=null) 34 { 35 //serverUrl = \'./php/get.php\' 36 serverUrl = \'/common/upload/sign\'//后台服务静态资源请求路径 37 xmlhttp.open( "GET", serverUrl, false );//调用后台方法 38 xmlhttp.send( null ); 39 return xmlhttp.responseText//调用后台服务返回的静态资源数据封装到responseText属性中 40 } 41 else 42 { 43 alert("Your browser does not support XMLHTTP."); 44 } 45 }; 46 47 function check_object_radio() {//获取HTML中的单选按钮 , 判断上传文件名称是保持原文件名还是随机生成文件名 48 var tt = document.getElementsByName(\'myradio\'); 49 for (var i = 0; i < tt.length ; i++ ) 50 { 51 if(tt[i].checked) 52 { 53 g_object_name_type = tt[i].value;//获取选择的单选按钮的值 54 break; 55 } 56 } 57 } 58 59 function get_signature()//该方法调用上面的请求获取基础资源数据的方法 , 并把资源中的数据与JS中上面声明的变量一一对应进行赋值 60 { 61 //可以判断当前expire是否超过了当前时间,如果超过了当前时间,就重新取一下.3s 做为缓冲 62 now = timestamp = Date.parse(new Date()) / 1000; 63 if (expire < now + 3) 64 { 65 body = send_request() 66 var obj = eval ("(" + body + ")"); 67 host = obj[\'host\'] 68 policyBase64 = obj[\'policy\'] 69 accessid = obj[\'accessid\'] 70 signature = obj[\'signature\'] 71 expire = parseInt(obj[\'expire\']) 72 callbackbody = obj[\'callback\'] 73 key = obj[\'dir\'] 74 return true; 75 } 76 return false; 77 }; 78 79 function random_string(len) {//给文件生成随机名称 : 字母+数字 80 len = len || 32; 81 var chars = \'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678\'; 82 var maxPos = chars.length; 83 var pwd = \'\'; 84 for (i = 0; i < len; i++) { 85 pwd += chars.charAt(Math.floor(Math.random() * maxPos)); 86 } 87 return pwd; 88 } 89 90 function get_suffix(filename) {//获取文件后缀 ---也就是文件类型 91 pos = filename.lastIndexOf(\'.\') 92 suffix = \'\' 93 if (pos != -1) { 94 suffix = filename.substring(pos) 95 } 96 return suffix; 97 } 98 99 function calculate_object_name(filename)//拼接上传文件名 : 文件上传目录+长度为10的文件名称(根据单选按钮判断是原文件名还是随机生成的文件名)+文件后缀 100 { 101 if (g_object_name_type == \'local_name\') 102 { 103 g_object_name += "${filename}" 104 } 105 else if (g_object_name_type == \'random_name\') 106 { 107 suffix = get_suffix(filename) 108 g_object_name = key + random_string(10) + suffix 109 } 110 return \'\' 111 } 112 113 function get_uploaded_object_name(filename)//得到生成的文件索引地址 , 即对象名 114 { 115 if (g_object_name_type == \'local_name\') 116 { 117 tmp_name = g_object_name 118 tmp_name = tmp_name.replace("${filename}", filename); 119 return tmp_name 120 } 121 else if(g_object_name_type == \'random_name\') 122 { 123 return g_object_name 124 } 125 } 126 127 function set_upload_param(up, filename, ret)//设置上传参数 : 128 { 129 if (ret == false)//假如基础变量参数为空/不存在 , 重新获取 130 { 131 ret = get_signature() 132 } 133 g_object_name = key; 134 if (filename != \'\') { suffix = get_suffix(filename) 135 calculate_object_name(filename) 136 } 137 new_multipart_params = { 138 \'key\' : g_object_name, 139 \'policy\': policyBase64, 140 \'OSSAccessKeyId\': accessid, 141 \'success_action_status\' : \'200\', //让服务端返回200,不然,默认会返回204 142 \'callback\' : callbackbody, 143 \'signature\': signature, 144 }; 145 146 up.setOption({ 147 \'url\': host, 148 \'multipart_params\': new_multipart_params 149 }); 150 151 up.start(); 152 } 153 154 // 默认文件类型及最大尺寸 155 var fileType = $(\'#fileType\').val(); // 1代表图片,2代表视频 156 var mimeTypes = fileType == "1" ? 157 { title : "Image files", extensions : "jpg,gif,png,bmp" } : 158 { title : "Video files", extensions : "mp4" }; 159 var maxSize = $(\'#maxSize\').val(); // 默认最大10mb 160 if (util.isEmpty(maxSize)) { 161 maxSize = "10mb"; 162 } 163 var uploader = new plupload.Uploader({//根据Puploader插件创建上传对象upload 164 runtimes : \'html5,flash,silverlight,html4\', 165 browse_button : \'selectfiles\', 166 //multi_selection: false, 167 container: document.getElementById(\'container\'), 168 flash_swf_url : \'assets/plupload-2.1.2/js/Moxie.swf\', 169 silverlight_xap_url : \'assets/plupload-2.1.2/js/Moxie.xap\', 170 url : \'http://oss.aliyuncs.com\', 171 filters: {//过滤文件类型和文件数目 172 mime_types : [ //只允许上传图片和视频文件 173 mimeTypes 174 ], 175 max_file_size : maxSize, //最大只能上传10mb的文件 176 prevent_duplicates : true //不允许选取重复文件 177 }, 178 179 init: {//初始化 180 PostInit: function() { 181 document.getElementById(\'ossfile\').innerHTML = \'\'; 182 document.getElementById(\'postfiles\').onclick = function() { 183 set_upload_param(uploader, \'\', false); 184 return false; 185 }; 186 }, 187 188 FilesAdded: function(up, files) {//添加文件 189 if (uploader.files.length > maxFiles) { // 最多上传文件个数 190 util.dialog.alert(\'只允许上传\' + maxFiles + \'个文件\'); 191 return; 192 } 193 plupload.each(files, function(file) { 194 document.getElementById(\'ossfile\').innerHTML += \'<div id="\' + file.id + \'">\' + file.name + \' (\' + plupload.formatSize(file.size) + \')<b></b>\' 195 +\'<div class="progress"><div class="progress-bar" style="width: 0%"></div></div>\' 196 +\'<input type="hidden" class="fileId" value="" /><input type="hidden" class="fileUrl" value="" />\' 197 +\'</div>\'; 198 }); 199 }, 200 201 BeforeUpload: function(up, file) {//上传之前设置上传参数 , 每个文件都要进行上传 202 check_object_radio(); 203 set_upload_param(up, file.name, true); 204 }, 205 206 UploadProgress: function(up, file) {//上传进度 207 var d = document.getElementById(file.id); 208 d.getElementsByTagName(\'b\')[0].innerHTML = \'<span>\' + file.percent + "%</span>"; 209 var prog = d.getElementsByTagName(\'div\')[0]; 210 var progBar = prog.getElementsByTagName(\'div\')[0] 211 progBar.style.width= 2*file.percent+\'px\'; 212 progBar.setAttribute(\'aria-valuenow\', file.percent); 213 }, 214 215 FileUploaded: function(up, file, info) {//执行上传文件 , 返回上传成功的新的文件ID和文件保存URL , 便于数据回显 和与其他数据对象绑定 216 if (info.status == 200) { 217 var fileName = get_uploaded_object_name(file.name); 218 util.ajax(\'/common/upload/save\', {fileName : fileName}, function(result) { 219 if (result.success) { 220 document.getElementById(file.id).getElementsByTagName(\'b\')[0].innerHTML = \'上传成功\' // + \',回调服务器返回的内容是:\' + info.response; 221 // document.getElementById(file.id).getElementsByTagName(\'b\')[0].innerHTML = \'上传成功,文件名:\' + fileName; // + \',回调服务器返回的内容是:\' + info.response; 222 $(document.getElementById(file.id)).find(\'.fileId\').attr("value",result.fileId); 223 $(document.getElementById(file.id)).find(\'.fileUrl\').attr("value",result.fileUrl); 224 } 225 }); 226 } 227 else if (info.status == 203) 228 { 229 document.getElementById(file.id).getElementsByTagName(\'b\')[0].innerHTML = \'上传到OSS成功,但是oss访问用户设置的上传回调服务器失败,失败原因是:\' + info.response; 230 } 231 else 232 { 233 document.getElementById(file.id).getElementsByTagName(\'b\')[0].innerHTML = info.response; 234 } 235 }, 236 237 Error: function(up, err) { 238 if (err.code == -600) { 239 document.getElementById(\'console\').appendChild(document.createTextNode("\n选择的文件太大了,可以根据应用情况,在upload.js 设置一下上传的最大大小")); 240 } 241 else if (err.code == -601) { 242 document.getElementById(\'console\').appendChild(document.createTextNode("\n选择的文件后缀不对,可以根据应用情况,在upload.js进行设置可允许的上传文件类型")); 243 } 244 else if (err.code == -602) { 245 document.getElementById(\'console\').appendChild(document.createTextNode("\n这个文件已经上传过一遍了")); 246 } 247 else 248 { 249 document.getElementById(\'console\').appendChild(document.createTextNode("\nError xml:" + err.response)); 250 } 251 } 252 } 253 }); 254 255 uploader.init();//方法运行 . 调用该上传页面 , 整体上传JS---function启动 256 257 258 function find() { 259 if( $(".fileId").eq(0).attr("value") == "" || $(".fileId").eq(0).attr("value") == undefined ){ 260 261 return ; 262 }else{ 263 var img = []; 264 for( var i = 0 ;i < $(".fileId").length ; i++){ 265 var arr = 266 { 267 "id":$(".fileId").eq(i).attr("value"), 268 "url":$(".fileUrl").eq(i).attr("value") 269 } 270 img.push(arr); 271 } 272 img = JSON.stringify(img); 273 274 return img; 275 } 276 277 }
后台方法:
package com.yunyu.admin.controller.common; @Controller public class UploadController extends AbstractController { private static final String DEFAULT_VIEW = "/common/upload"; @Autowired UploadService uploadService; @Value("${staticServerUrl}") String staticServerUrl; @Value("${endpoint}") String endpoint; @Value("${bucket}") String bucket; @Value("${accessId}") String accessId; @Value("${accessKey}") String accessKey; @RequestMapping("/common/upload") public String index(UploadVo uploadVo) { return DEFAULT_VIEW; } @ResponseBody @RequestMapping("/common/upload/sign")//获取静态资源文件并进行校验 public String sign(HttpServletResponse response) { String dir = generateDir(); String host = "http://" + bucket + "." + endpoint; OSSClient client = new OSSClient(endpoint, accessId, accessKey); try { long expireTime = 120;与当前服务器系统时间进行校验 , 是毫秒数 , 数据较小时容易出现上传失败 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 = client.generatePostPolicy(expiration, policyConds); byte[] binaryData = postPolicy.getBytes("utf-8"); String encodedPolicy = BinaryUtil.toBase64String(binaryData); String postSignature = client.calculatePostSignature(postPolicy); Map<String, String> respMap = new LinkedHashMap<String, String>(); respMap.put("accessid", accessId); respMap.put("policy", encodedPolicy); respMap.put("signature", postSignature); //respMap.put("expire", formatISO8601Date(expiration)); respMap.put("dir", dir); respMap.put("host", host); respMap.put("expire", String.valueOf(expireEndTime / 1000)); JSONObject ja1 = JSONObject.fromObject(respMap); System.out.println(ja1.toString()); response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "GET, POST"); responseSign(request, response, ja1.toString()); } catch (Exception e) { System.out.println(e.getMessage()); } return null; } /** * 生成上传目录 * @return 上传目录 */ private String generateDir() { StringBuffer dir = new StringBuffer("upload/"); // 格式:...upload/{spId}/{accountId}/{yyyyMMdd}/ SpInfo spInfo = getSpInfo(); LoginInfo loginInfo = getLoginInfo(); if (spInfo != null && spInfo.getSpId() != null) { dir.append(spInfo.getSpId()).append("/"); } if (loginInfo != null && loginInfo.getAccountId() > 0) { dir.append(loginInfo.getAccountId()).append("/"); } String yyyyMMdd = DateUtil.format(new Date(), DateUtil.FMTYMD); dir.append(yyyyMMdd).append("/"); return dir.toString(); } private void responseSign(HttpServletRequest request, HttpServletResponse response, String results) throws IOException { String callbackFunName = request.getParameter("callback"); if (callbackFunName==null || callbackFunName.equalsIgnoreCase("")) response.getWriter().println(results); else response.getWriter().println(callbackFunName + "( "+results+" )"); response.setStatus(HttpServletResponse.SC_OK); response.flushBuffer(); } @ResponseBody @RequestMapping("/common/upload/save") public RestResponse save(@RequestBody UploadVo uploadVo) { RestResponse res = new RestResponse(); // 组装fileUrl String fileUrl = staticServerUrl + uploadVo.getFileName(); // 保存文件信息 Long fileId = uploadService.saveImgInfo(fileUrl); res.put("fileId", fileId); res.put("fileUrl", fileUrl); return res; } @RequestMapping("/common/upload/saveAs") public String saveAs(HttpServletRequest request, HttpServletResponse response) { Uploader up = null; try { // 设置编码格式 request.setCharacterEncoding("utf-8"); response.setCharacterEncoding("utf-8"); // 上传保存到指定位置 up = new Uploader(request); up.setSavePath("upload"); String[] fileType = {".gif" , ".png" , ".jpg" , ".jpeg" , ".bmp"}; up.setAllowFiles(fileType); up.setMaxSize(10000); //单位KB up.upload(); } catch (UnsupportedEncodingException ee) { ee.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } // 返回上传后的图片信息 if (up != null) { String callback = request.getParameter("callback"); StringBuffer result = new StringBuffer(); result.append("{\"name\":\"").append(up.getFileName()); result.append("\", \"originalName\": \"").append(up.getOriginalName()); result.append("\", \"size\": ").append(up.getSize()); result.append(", \"state\": \"").append(up.getState()); result.append("\", \"type\": \"").append(up.getType()); result.append("\", \"url\": \"").append(up.getUrl()); result.append("\"}"); String jsonString = result.toString().replaceAll( "\\\\", "\\\\" ); System.out.println("result--->" + jsonString); try { if( callback == null ){ response.getWriter().print(result); }else{ response.getWriter().print("<script>"+ callback +"(" + result + ")</script>"); } } catch (IOException e) { e.printStackTrace(); } } return null; } @ResponseBody @RequestMapping("/common/upload/callback") public String callback(HttpServletRequest request, HttpServletResponse response) throws IOException { String ossCallbackBody = GetPostBody(request.getInputStream(), Integer.parseInt(request.getHeader("content-length"))); boolean ret = VerifyOSSCallbackRequest(request, ossCallbackBody); System.out.println("verify result:" + ret); System.out.println("OSS Callback Body:" + ossCallbackBody); if (ret) { response(request, response, "{\"Status\":\"OK\"}", HttpServletResponse.SC_OK); } else { response(request, response, "{\"Status\":\"verdify not ok\"}", HttpServletResponse.SC_BAD_REQUEST); } return null; } @SuppressWarnings({ "finally" }) public String executeGet(String url) { BufferedReader in = null; String content = null; try { // 定义HttpClient @SuppressWarnings("resource") DefaultHttpClient client = new DefaultHttpClient(); // 实例化HTTP方法 HttpGet request = new HttpGet(); request.setURI(new URI(url)); HttpResponse response = client.execute(request); in = new BufferedReader(new InputStreamReader(response.getEntity().getContent())); StringBuffer sb = new StringBuffer(""); String line = ""; String NL = System.getProperty("line.separator"); while ((line = in.readLine()) != null) { sb.append(line + NL); } in.close(); content = sb.toString(); } catch (Exception e) { } finally { if (in != null) { try { in.close();// 最后要关闭BufferedReader } catch (Exception e) { e.printStackTrace(); } } return content; } } public String GetPostBody(InputStream is, int contentLen) { if (contentLen > 0) { int readLen = 0; int readLengthThisTime = 0; byte[] message = new byte[contentLen]; try { while (readLen != contentLen) { readLengthThisTime = is.read(message, readLen, contentLen - readLen); if (readLengthThisTime == -1) {// Should not happen. break; } readLen += readLengthThisTime; } return new String(message); } catch (IOException e) { } } return ""; } protected boolean VerifyOSSCallbackRequest(HttpServletRequest request, String ossCallbackBody) throws NumberFormatException, IOException { boolean ret = false; String autorizationInput = new String(request.getHeader("Authorization")); String pubKeyInput = request.getHeader("x-oss-pub-key-url"); byte[] authorization = BinaryUtil.fromBase64String(autorizationInput); byte[] pubKey = BinaryUtil.fromBase64String(pubKeyInput); String pubKeyAddr = new String(pubKey); if (!pubKeyAddr.startsWith("http://gosspublic.alicdn.com/") && !pubKeyAddr.startsWith("https://gosspublic.alicdn.com/")) { System.out.println("pub key addr must be oss addrss"); return false; } String retString = executeGet(pubKeyAddr); retString = retString.replace("-----BEGIN PUBLIC KEY-----", ""); retString = retString.replace("-----END PUBLIC KEY-----", ""); String queryString = request.getQueryString(); String uri = request.getRequestURI(); String decodeUri = java.net.URLDecoder.decode(uri, "UTF-8"); String authStr = decodeUri; if (queryString != null && !queryString.equals("")) { authStr += "?" + queryString; } authStr += "\n" + ossCallbackBody; ret = doCheck(authStr, authorization, retString); return ret; } public static boolean doCheck(String content, byte[] sign, String publicKey) { try { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); byte[] encodedKey = BinaryUtil.fromBase64String(publicKey); PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey)); java.security.Signature signature = java.security.Signature.getInstance("MD5withRSA"); signature.initVerify(pubKey); signature.update(content.getBytes()); boolean bverify = signature.verify(sign); return bverify; } catch (Exception e) { e.printStackTrace(); } return false; } private void response(HttpServletRequest request, HttpServletResponse response, String results, int status) throws IOException { String callbackFunName = request.getParameter("callback"); response.addHeader("Content-Length", String.valueOf(results.length())); if (callbackFunName == null || callbackFunName.equalsIgnoreCase("")) response.getWriter().println(results); else response.getWriter().println(callbackFunName + "( " + results + " )"); response.setStatus(status); response.flushBuffer(); } }