背景信息
和數據直傳到 OSS 相比,以上方法有三個缺點:
- 上傳慢:用戶數據需先上傳到應用服務器,之后再上傳到OSS。網絡傳輸時間比直傳到OSS多一倍。如果用戶數據不通過應用服務器中轉,而是直傳到OSS,速度將大大提升。而且OSS采用BGP帶寬,能保證各地各運營商之間的傳輸速度。
- 擴展性差:如果后續用戶多了,應用服務器會成為瓶頸。
- 費用高:需要准備多台應用服務器。由於OSS上傳流量是免費的,如果數據直傳到OSS,不通過應用服務器,那么將能省下幾台應用服務器。
技術方案
目前通過 Web 前端技術上傳文件到 OSS,有三種技術方案:
- 利用OSS Browser.js SDK 將文件上傳到 OSS
該方案通過OSS Browser.js SDK直傳數據到 OSS,詳細的 SDK Demo 請參考上傳文件。在網絡條件不好的狀況下可以通過斷點續傳的方式上傳大文件。該方案在個別瀏覽器上有兼容性問題,目前兼容 IE10 及以上版本瀏覽器,主流版本的 Edge、Chrome、Firefox、Safari 瀏覽器,以及大部分的 Android、iOS、WindowsPhone 手機上的瀏覽器。更多信息請參見安裝 Browser.js SDK。說明 Browser.js SDK 已經支持大部分的 OSS API 接口,包含文件管理、自定義域名設置、圖片處理等。
- 使用表單上傳方式,將文件上傳到 OSS
利用 OSS 提供的 PostObject 接口,使用表單上傳方式將文件上傳到 OSS。該方案兼容大部分瀏覽器,但在網絡狀況不好的時候,如果單個文件上傳失敗,只能重試上傳。操作方法請參見 PostObject 上傳方案。說明 關於 PostObject 的詳細介紹請參見 PostObject。
- 通過小程序上傳文件到 OSS
通過小程序,如微信小程序、支付寶小程序等,利用 OSS 提供的 PostObject 接口來實現表單上傳.
本文以Java語言為例,講解在服務端通過Java代碼完成簽名,並且設置上傳回調,然后通過表單直傳數據到OSS。
前提條件
- 應用服務器對應的域名可通過公網訪問。
- 確保應用服務器已經安裝
Java 1.6
以上版本(執行命令java -version
進行查看)。 - 確保PC端瀏覽器支持JavaScript。
步驟1:配置應用服務器
步驟2:配置客戶端
步驟3:修改CORS
客戶端進行表單直傳到OSS時,會從瀏覽器向OSS發送帶有Origin
的請求消息。OSS對帶有Origin
頭的請求消息會進行跨域規則(CORS)的驗證。因此需要為Bucket設置跨域規則以支持Post方法。
步驟4:體驗上傳回調
應用服務器核心代碼解析
應用服務器源碼包含了簽名直傳服務和上傳回調服務兩個功能。
- 簽名直傳服務
簽名直傳服務響應客戶端發送給應用服務器的GET消息,代碼片段如下:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String accessId = "<yourAccessKeyId>"; // 請填寫您的AccessKeyId。 String accessKey = "<yourAccessKeySecret>"; // 請填寫您的AccessKeySecret。 String endpoint = "oss-cn-hangzhou.aliyuncs.com"; // 請填寫您的 endpoint。 String bucket = "bucket-name"; // 請填寫您的 bucketname 。 String host = "https://" + bucket + "." + endpoint; // host的格式為 bucketname.endpoint // callbackUrl為 上傳回調服務器的URL,請將下面的IP和Port配置為您自己的真實信息。 String callbackUrl = "http://88.88.88.88:8888"; String dir = "user-dir-prefix/"; // 用戶上傳文件時指定的前綴。 // 創建OSSClient實例。 OSS ossClient = new OSSClientBuilder().build(endpoint, accessId, accessKey); try { long expireTime = 30; long expireEndTime = System.currentTimeMillis() + expireTime * 1000; Date expiration = new Date(expireEndTime); // PostObject請求最大可支持的文件大小為5 GB,即CONTENT_LENGTH_RANGE為5*1024*1024*1024。 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("utf-8"); String encodedPolicy = BinaryUtil.toBase64String(binaryData); String postSignature = ossClient.calculatePostSignature(postPolicy); Map<String, String> respMap = new LinkedHashMap<String, String>(); 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)); JSONObject jasonCallback = new JSONObject(); jasonCallback.put("callbackUrl", callbackUrl); jasonCallback.put("callbackBody", "filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}"); jasonCallback.put("callbackBodyType", "application/x-www-form-urlencoded"); String base64CallbackBody = BinaryUtil.toBase64String(jasonCallback.toString().getBytes()); respMap.put("callback", base64CallbackBody); JSONObject ja1 = JSONObject.fromObject(respMap); // System.out.println(ja1.toString()); response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "GET, POST"); response(request, response, ja1.toString()); } catch (Exception e) { // Assert.fail(e.getMessage()); System.out.println(e.getMessage()); } finally { ossClient.shutdown(); } }
- 上傳回調服務
上傳回調服務響應OSS發送給應用服務器的POST消息,代碼片段如下:
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("https://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; } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, 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\":\"verify not ok\"}", HttpServletResponse.SC_BAD_REQUEST); } }
本地代碼