最近在做一個基於Flutter的app,算是學習新的移動端開發技術。於是就需要一個後端api接口,其中涉及到了文件上傳,特此記錄下
1.爲什麼自己寫文件上傳
本來我計劃的是,後臺只做數據接口,不做文件存儲,畢竟自己也沒那麼多的服務器資源去存儲。當時想的是用免費的第三方雲存儲解決方案,畢竟之前已經用過了七牛雲。
但是問題來了,免費的雲存儲,老是出問題。比如七牛的,過段時間就會發現,外鏈訪問文件會失效,而且後臺文件管理面板,察看文件也看不了,很坑。
然後又看了有拍雲,和騰訊雲,它們提供的文件存儲都不收費,但是要後收費,當你的文件存儲超過容量時收費。這也還好,畢竟免費的,存儲圖片的話5g也夠用了,但是主要是每天的流量由限制,超出流量收費。你後期不得面臨你的應用中的圖片全部不顯示,作爲定位上線的應用,是不能接受這種風險的。
所以你如果自己玩,完全可以使用免費的雲存儲,要是真的商用,就考慮付費產品。但是我又窮,所以只能自己寫了。
2 後端開發(使用SpringBoot)
就不貼全部的項目代碼了,只貼上傳部分的。因爲使用了靜態資源訪問,所以需要加入模板引擎依賴。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
因爲代碼比較簡單,所以直接controller全部處理完:
package com.mike.controller;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.mike.bean.ApiResult;
import com.mike.bean.FileView;
import com.mike.util.ApiUtils;
import com.mike.util.Tools;
/**
* The class UploadController
*/
@RestController
@RequestMapping("/up")
public class UploadController {
@Value("${baseUrl}")
private String baaseUrl;
@CrossOrigin
@PostMapping("/upload")
public ApiResult uploadFile(@RequestParam("file") MultipartFile file){
if (file.isEmpty()) {
return ApiUtils.err("你沒有選擇上傳文件");
} else if(file.getOriginalFilename().contains("..")||!file.getOriginalFilename().contains(".")){
return ApiUtils.err("文件格式有誤");
} else {
String fileName = file.getOriginalFilename();
//爲防止文件名重複覆蓋
fileName = fileName.replace(".", System.currentTimeMillis()+".");
Path savePath = Paths.get("src/main/resources/static/upload");
try {
Files.copy(file.getInputStream(), savePath.resolve(fileName), StandardCopyOption.REPLACE_EXISTING);
FileView view = new FileView();
view.setName(fileName);
view.setSize(file.getSize());
view.setUrl(baaseUrl+fileName);
view.setUploadDate(Tools.getCurrent());
return ApiUtils.success(view);
} catch (IOException e) {
e.printStackTrace();
return ApiUtils.err("對不起,上傳失敗");
}
}
}
}
在高併發下,還是有非常小的機率出現文件重名,所以使用時間戳也不是好的解決方案。需要能夠生成唯一識別符號,建議使用UUId。
3 前端測試(使用jquery ajax)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<input type="file" id="file">
<button type="button" onclick="up()" name="上傳">上傳</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/zui/1.8.1/lib/jquery/jquery.js"></script>
<script type="text/javascript">
function up(){
var formData = new FormData();
formData.append("file", $("#file")[0].files[0]);
console.log(formData);
$.ajax({
type:'POST',
url:"http://localhost:8080/up/upload",
data:formData,
contentType:false,
processData:false,//這個很有必要,不然不行
dataType:"json",
mimeType:"multipart/form-data",
success:function(data){
if("00"==data.code){
console.log(data.data);
}else{
console.log("error");
}
}
});
}
</script>
</body>
</html>
效果:
訪問上傳後的圖片url:
4 填坑
因爲圖片上傳導了代碼目錄,不是服務器的目錄,所以,新上傳得圖片訪問會報404,需要重起才能訪問。爲了解決這個問題,我們需要增加文件與路徑的映射關係,這樣,就不會出現404
public class FileConfig implements WebMvcConfigurer {
@Value("${serverFilePath}")
private String serverFilePath;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
WebMvcConfigurer.super.addResourceHandlers(registry);
registry.addResourceHandler("/upload/**")
.addResourceLocations(serverFilePath);
}
}
serverFilePath 在properties中配置:
baseUrl=http://localhost:8080/upload/
serverFilePath=file:C:/code/mike-todo/src/main/resources/static/upload/
上線的話,換成服務器上的路徑就好。
5 總結
文件存儲,遠遠不像我寫的這麼簡單。只是應付一般小項目足夠了,如果是大型的項目,就需要專門的文件存儲系統以及服務器端的優化。如果你有什麼問題,可以留言要論。或是關注我的公衆號:mike啥都想搞
,還有其他教學視頻免費領取。