上传篇
前端
如果不用 Vue.js,就如下:
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="file" accept=".txt, .pdf">
<input type="submit">
</form>
注意:一定得指定
enctype
为multipart/form-data
。给<input type="file">
属性accept
限定文件类型,不过这种方式只是通过文件后缀名判断文件类型,有时候不够谨慎。如果需要可以读取文件的魔数(Magic Number)来判断。譬如Java字节码文件开头前4个字节总是0xCA 0xFE 0xBA 0xBE
,这就是魔数。
这有个缺点点击提交后会跳转界面,非常令人反感。所以我们用Vue.js后台异步操作
<template>
<!-- 把上传的文件绑定到 Vue.js 数据里面 -->
<input type="file" name="file" @change="handleFileUpload" accept=".txt, .pdf">
<!-- -->
<input type="submit" @click="uploadFile()">
</template>
<script>
export default {
data() {
return {
selectedFile: undefined,
};
},
methods: {
handleFileUpload(e) {
with (this) {
selectedFile = e.target.files[0];
}
},
uploadFile() {
with (this) {
if (!selectedFile)
return;
let formData = new FormData();
formData.append('file', selectedFile);
axios.post('/upload', formData);
}
}
}
}
</script>
上面我们用了 axios.js的axios.post(url: URI, data: FormData) -> Promise
函数来开启连接上传文件。因为我们指定了FormData
参数,所以不用手动添加enctype=“multipart/form-data”
后端
@RestController
public class UploadController {
@Value("${upload.path}")
private String UPLOAD_PATH;
@PostMapping("/upload")
public void handleUpload(@RequestParam("file") MultipartFile file) throws IOException {
if (file.isEmpty())
return;
final Path path = Paths.get(UPLOAD_PATH + file.getOriginalName());
Files.write(Paths.get(), file.getBytes());
}
}
下载篇
前端
<a href="download/Main.java">下载</a>
这个就不能用Vue.js后台静默了。
后端
@RestController
public class DownloadController {
@Value("${download.path})
private String DOWNLOAD_PATH;
@GetMapping("/download/{filename}")
public ResponseEntity<Resource> downloadFile(@PathVariable("filename") String filename) {
final Path path = Paths.get(downloadPath).resolve(filename).normalize();
try {
Resource res = new UrlResource(path.toUri());
if (res.exists() || res.isReadable()) {
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header("Content-Disposition", "attachment; filename=\"%s\"".formatted(res.getFilename()))
.body(res);
} else {
return ResponseEntity.notFound().build();
}
} catch (MalformedURLException e) {
return ResponseEntity.status(500).build();
}
}
}
原理就是,设置相应报文为:
Content-Type: application/octet-stream
Content-Disposition: attachment; filename="{filename}"
Content-Type
指定响应给客户端的类型,application/octet-stream
就是打开下载界面。不过为什么是八进制流而非二进制流?我是乡下人,真搞不懂。
Content-Disposition
指定以附件的形式下载并保存到本地。
我们设置了三种响应代码
200:找到了文件
404:没有此文件
500:服务器错误
然后我们可以通过获取响应代码来错误回显
async function download() {
const resp = await axios.get('/download/Main.java');
switch (resp.status) {
case 404: callback('没有此文件'); break;
case 500: callback('发生内部错误'); break;
}
}