赞
踩
以前的文件上传都是前端将文件流上传到服务端后,服务端进行处理,返回给前端地址,这种方式适合传统单体的系统架构,不将文件服务作为一个单独的服务进行部署,在现在流行的分布式大环境下,传统的文件存储方式已经不适用,文件存储服务以第三方服务的形式,直接调用即可。例如阿里云 OSS,七牛云,腾讯云的COS 等等。这些第三方的文件服务就为我们提供了存储解决方案,既然我们已经不将文件存储在服务端,那我们也没有必要先让前端将文件流传递到服务端,再由服务端将文件上传到第三方文件服务。
下面是博主基于springboot搭建的一套完整的oss直传的DEMO
码云地址 :https://gitee.com/jack_whh/oss-policy-springboot.git
码云地址
流程如下图所示:
当用户要上传一个文件到OSS,而且希望将上传的结果返回给应用服务器时,需要设置一个回调函数,将请求告知应用服务器。用户上传完文件后,不会直接得到返回结果,而是先通知应用服务器,再把结果转达给用户。
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.10.2</version>
</dependency>
前期设置
- 创建bucket 快捷入口
- 修改CORS
获取policy的主要代码
public Map<String, String> getPolicy() { // host的格式为 bucketname.endpoint final String host = "http://" + ossProperties.getBucket() + "." + ossProperties.getEndpoint(); // 直传有效截止时间 long expireEndTime = System.currentTimeMillis() + (ossProperties.getExpireTime() * 1000); Date expiration = new Date(expireEndTime); PolicyConditions policyConditions = new PolicyConditions(); // 设置可上传文件的大小 policyConditions.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, ossProperties.getMin(), ossProperties.getMax()); // 设置上传文件的前缀、可忽略 policyConditions.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir); // 生成policy String postPolicy = ossClient.generatePostPolicy(expiration, policyConditions); byte[] binaryData = postPolicy.getBytes(StandardCharsets.UTF_8); String encodedPolicy = BinaryUtil.toBase64String(binaryData); String postSignature = ossClient.calculatePostSignature(postPolicy); // 封装policy等信息 Map<String, String> aliOssPolicy = new HashMap<>(); aliOssPolicy.put("ossAccessKeyId", ossProperties.getAccessId()); aliOssPolicy.put("policy", encodedPolicy); aliOssPolicy.put("signature", postSignature); aliOssPolicy.put("dir", dir); aliOssPolicy.put("host", host); aliOssPolicy.put("expire", String.valueOf(expireEndTime / 1000)); aliOssPolicy.put("callback", getCallBackBody()); return aliOssPolicy; } private String getCallBackBody() { Map<String, String> map = new HashMap<>(); map.put("callbackUrl", ossProperties.getCallbackUrl()); map.put("callbackBody", "filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}"); map.put("callbackBodyType", "application/x-www-form-urlencoded"); String s = JSON.toJSONString(map); String base64CallbackBody = null; try { base64CallbackBody = BinaryUtil.toBase64String(s.getBytes("utf-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return base64CallbackBody; }
{
"accessid":"6MKO******4AUk44",
"host":"http://post-test.oss-cn-hangzhou.aliyuncs.com",
"policy":"eyJleHBpcmF0aW9uIjoiMjAxNS0xMS0wNVQyMDo1Mjoy******Jjdb25kaXRpb25zIjpbWyJjdb250ZW50LWxlbmd0aC1yYW5nZSIsMCwxMDQ4NTc2MDAwXSxbInN0YXJ0cy13aXRoIiwiJGtleSIsInVzZXItZGlyXC8iXV19",
"signature":"VsxOcOudx******z93CLaXPz+4s=",
"expire":1446727949,
"callback":"eyJjYWxsYmFja1VybCI6Imh0dHA6Ly9vc3MtZGVtby5hbGl5dW5jcy5jdb206MjM0NTAiLCJjYWxsYmFja0hvc3QiOiJvc3MtZGVtby5hbGl5dW5jcy5jdb20iLCJjYWxsYmFja0JvZHkiOiJmaWxlbmFtZT0ke29iamVjdH0mc2l6ZT0ke3NpemV9Jm1pbWVUeXBlPSR7bWltZVR5cGV9JmhlaWdodD0ke2ltYWdlSW5mby5oZWlnaHR9JndpZHRoPSR7aW1hZ2VJdbmZvLndpZHRofSIsImNhbGxiYWNrQm9keVR5cGUiOiJhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQifQ==",
"dir":"user-dirs/"
}
accessid 密钥key
host oss的上传地址
signature 签名
expire 密钥过期时间
callback 回调的base64编码
dir 要上传的指定文件夹
{"callbackUrl":"http://oss-demo.aliyuncs.com:23450",
"callbackBody":"filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}",
"callbackBodyType":"application/x-www-form-urlencoded"}
内容解析如下:
上传成功后,oss向服务端发送的回调地址,需是公网地址
。前端代码,这里使用vue+element-ui 写的一个小页面
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <!-- import CSS --> <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"> <title>上传图片</title> </head> <body> <div id="app"> <div class="div-center-class"> <el-upload class="upload-demo" action="#" drag :http-request="httpRequestHandle" > <i class="el-icon-upload"></i> <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> <div class="el-upload__tip" slot="tip">只能上传jpg/png文件,且不超过500kb</div> </el-upload> <div v-if="imgUrl"> <img :src="imgUrl"></img> </div> </div> </div> </body> <!-- import Vue before Element --> <script src="https://unpkg.com/vue/dist/vue.js"></script> <!-- import JavaScript --> <script src="https://unpkg.com/element-ui/lib/index.js"></script> <!--import axios --> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <script> new Vue({ el: '#app', data: function () { return { imgUrl: '' } }, methods: { getpolicy(file) { _that = this axios.get('ali/oss/getpolicy') .then(function (response) { let {ossAccessKeyId, policy, signature, host, callback} = response.data.data; let formData = new FormData(); formData.append("key", `${new Date().getTime()}_${file.name}`); formData.append("success_action_status", 200); // 让服务端返回200,不设置则默认返回204。 formData.append("OssAccessKeyId", ossAccessKeyId); formData.append("policy", policy); formData.append("signature", signature); formData.append("callback", callback); formData.append("file", file); // 必须放在最后 // 发送 POST 请求 _that.axiosPost("post", host, formData).then(function (res) { _that.imgUrl = res }) }) }, httpRequestHandle(options) { let {file} = options; this.getpolicy(file); }, //封装 //axios封装post请求 axiosPost(method, url, data) { let result = axios({ method: method, url: url, data: data }).then(resp => { return resp.data.data; }).catch(error => { return "exception=" + error; }); return result; } } }) </script> <style> .div-center-class { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); } </style> </html>
请求路由需和上面的callbackurl一致。
@PostMapping("/ali/oss/callback")
public R ossCallback(HttpServletRequest request) throws IOException {
// request 只能获取一次
String body = ossComponent.GetPostBody(request.getInputStream(), Integer.parseInt(request.getHeader("content-length")));
String[] strings = body.split("&");
String filename = strings[0].split("=")[1];
boolean b = ossComponent.VerifyOSSCallbackRequest(request,body);
if (b) {
String url = ossComponent.getUrl(filename);
return R.success(url);
}else {
return R.error("上传失败");
}
}
/** * 验证上传回调的Request * * @param request * @return * @throws NumberFormatException * @throws IOException */ public 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; } /** * 获取Post消息体 * * @param is * @param contentLen * @return */ 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 ""; } /** * 验证RSA * * @param content * @param sign * @param publicKey * @return */ private 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; } /** * 获取public key * * @param url * @return */ @SuppressWarnings({"finally"}) private 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; } }
/** * 获得url链接 * * @param key * @return */ public String getUrl(String key) { if (StringUtils.isBlank(key)) { return ""; } Date expiration = new Date(System.currentTimeMillis() + 3600L * 1000 * 24 * 365 * 10); URL url = ossClient.generatePresignedUrl(ossProperties.getBucket(), key, expiration); if (url != null) { return url.toString(); } return null; }
key 为上传文件的文件名。
这里可以设置图片链接的有限期。
====================================================================
下面是博主基于springboot搭建的一套完成的oss直传的DEMO
码云地址 :https://gitee.com/jack_whh/oss-policy-springboot.git
码云地址
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。