前言
最近嚴查security, 導致原來暴露出去的s3不能用了,不允許public的s3,暫時的折中方案是自己做跳轉。於是需要在SpringMVC中實現文件下載功能。
關於文件存儲的設計
文件存儲通常用作對象存儲,業界標准就是AWS s3, 國內的七牛也差不多。不想自建的話,采用這種第三方存儲是很方便的。但是,有寫地方需要注意。
安全問題
就像這次整改遇到的,權限問題大概是對象存儲必須具備的。s3的權限特別多和復雜,可以做到認證user訪問; 指定ip訪問; 指定IAM Role訪問; 指定第三方登陸比如Facebook,google的認證,設置自己的認證,這里是指Cognito。
地址路徑的健壯性
review代碼的時候發現了幾個嚴重的問題,地址問題尤為重要,簡直就是bug一樣。首先,db存儲的文件路徑不應該包含域名前綴,像這次整改圖片存儲就導致以前db里的數據不能用了。db只能存儲相對路徑,即當指定改類型前綴后,變化的部分路徑。。 然后就是 需要一個域名,對於公開的地址,需要一個域名來維護,而不是直接指定當前的文件服務器。比如一個公開的s3可能是這樣的:https://mybucket.s3.amazonaws.com/keyprefix/key. 如果我們變更了s3的bucket,那么這個地址就廢棄了,這個很有可能發生的。因此,用一個我們自己的域名指向s3可以屏蔽這個細節。同理,如果寫死了文件服務器的地址,當文件服務器變更的時候,公開的文件將全部失效。
如何使用SpringMVC下載文件
我們可以簡單的在HttpServletResponse
的OutputStream里寫入我們的文件流,這樣就可以實現文件下載。但這個做法感覺有點太直接了,推薦使用Spring的ResponseEntity來做。
@RequestMapping(value = "/static/filename")
public ResponseEntity<InputStreamResource>(HttpServletResponse response) {
final ObjectMetadata objectMetadata = s3Object.getObjectMetadata();
return ResponseEntity.ok()
.header("Access-Control-Allow-Origin", "*")
.cacheControl(CacheControl.maxAge(maxAge, TimeUnit.DAYS).cachePublic())
.allow(HttpMethod.GET, HttpMethod.OPTIONS)
.contentLength(objectMetadata.getContentLength())
.contentType(MediaType.valueOf(objectMetadata.getContentType()))
.body(new InputStreamResource(s3Object.getObjectContent()));
}
- 問題核心在於返回
ResponseEntity<InputStreamResource>
ResponseEntity
是SpringMVC里統一封裝的返回值response信息InputStreamResource
則是接收一個輸入流InputStream的結果集- 然后可以設置瀏覽器緩存,這個對用戶刷新頁面挺重要的
- 對於圖片和js等,需要設置contentType為png或者js等