前言 最近在学习SpringBoot,发现了很多有趣的知识。学习上面也有了新的理解,例如在SpringBoot中简单的文件上传和下载其实都已经很简单了,没必要像以前Servlet中那么复杂了,但是很多教程还在使用原来那些古老的api来处理(HttpServletRequest、HttpServletResponse或其之类),就显得有些不合时宜了。
这里我准备了一个简单的图片廊demo,用来演示文件的上传和下载这一块,它的功能很简单,基本没有什么实际使用价值,但是作为一个小玩具来练手还是很好的。
声明: 我本人对于前端不熟悉,所以这里的前端代码是从菜鸟教程那里复制过来的(有一说一,菜鸟教程真的很有用!)
基于SpringBoot的简易图片廊
演示GIF
后端接口部分
因为最近我的IDEA商业版没法用了,我就回归了eclipse了,但是使用eclipse写SpringBoot也没啥大问题,因为只是学习,所以就只导入了web的依赖。
这里后端文件部分有4个接口:
查看图片
@GetMapping("/files")
public R listFile() {
File file = new File(baseDir);
if (file.exists()) {
return R.success(file.list());
}
return R.fail("没有任何文件"); // 返回一个空的字符串数组
}
这里我没有使用数据库,只是在所有文件存入一个指定的目录中。所以,这个查询接口也就很简单了,只是直接枚举出指定目录下面所有文件的名字。
Postman测试截图
获取图片
@GetMapping("/file/{filename}")
public ResponseEntity<byte[]> getFile(@PathVariable("filename") String filename) {
File file = new File(baseDir, filename);
if (file.exists()) {
byte[] fileData = null;
try {
fileData = Files.readAllBytes(file.toPath());
return ResponseEntity.ok()
.header("Content-Type", MediaType.IMAGE_JPEG_VALUE) // 设置必要的内容类型,否则浏览器无法解析
.body(fileData);
} catch (IOException e) {
e.printStackTrace();
}
}
ObjectMapper mapper = new ObjectMapper();
byte[] res = null; // 这里应该给它一个初始值,防止发生异常,导致响应没有信息,但是这里太简单了,可以不考虑
try {
res = mapper.writeValueAsBytes(R.fail("寻遍天涯,一无所获"));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE) // 这里应该是指定了 UTF-8,如果乱码的话,就直接评价一个 charset=UTF-8
.body(res);
}
这里我想实现的优雅一点,但是感觉这样其实又复杂了。使用这个ResponseEntity类,因为它可以很方便的设置首部,例如Content-Type,对于我的开发很方便。它的主要逻辑是:如果图片存在则返回改图片的二进制数据,否则返回自定义的响应值。
Postman接口测试成功返回图片截图
Postman接口测试失败截图
注意:这个接口其实就是404,只是我想要显得文艺一些,设计成了这样!
上传图片 formdata
@PostMapping("/file")
public R uploadFile(@RequestParam("filename") String fileName, @RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
int index = fileName.lastIndexOf(".");
if (index != -1) {
fileName = UUID.randomUUID().toString() + fileName.substring(index);
} else {
fileName = UUID.randomUUID().toString() + ".jpg";
}
try {
file.transferTo(new File(baseDir, fileName));
return R.success("文件上传成功");
} catch (IOException e) {
e.printStackTrace();
return R.error(e.toString());
}
}
return R.fail("文件上传失败");
}
这个就是SpringBoot的上传文件写法了,是不是很简洁!这样就够了,根本用不着那些古老的servlet api了(至少你不用写它了!)
Postman上传图片接口测试成功截图
测试图片
上传成功后指定目录下的该图片
注意: 这里我给它的命名其实是UUID那种非常长的命名,但是你仔细看我前面查看图片接口返回的数据的命名格式其实和这个是不一样的,因为我测试图片的话,一张一张上传就太费事了,索性直接复制进去了(或者自己写一个程序去网络上面采集图片)。
Postman访问上传成功的图片
上传图片 binary
/**
* 目前来说很少使用的一种方式,因为web表单是无法发送这个请求的,只有ajax可以或者是非web端的应用。
* */
@PostMapping("/binary")
public R file(@RequestBody byte[] fileData) {
String fileName = UUID.randomUUID().toString() + ".jpg";
try {
Files.write(new File(baseDir, fileName).toPath(), fileData);
return R.success("文件上传成功");
} catch (IOException e) {
e.printStackTrace();
return R.error(e.toString()); // 这里不使用 e.getMessage()
}
}
Postman测试以二进制形式上传文件
注意:以这种形式上传文件的话,文件名这些信息就会丢失,只有文件的原始数据是保存的。虽然,单纯的表单无法发送这样的请求,但是我觉得它作为一个知识点还是很不错的。据说,ajax2.0或者其它客户端程序可以只用该接口。
测试图片
上传成功后指定目录下的该图片
删除图片
@DeleteMapping("/file/{filename}")
public R delete(@PathVariable("filename") String fileName) {
File file = new File(baseDir, fileName);
if (file.exists()) {
// 这里要考虑并发吗?
boolean flag = file.delete();
if (flag) {
return R.success("文件删除成功");
}
}
return R.fail("文件删除失败");
}
删除图片的接口,它的功能很简单,传入参数为文件名,然后调用文件系统的api直接删除图片即可。我没有做过真实的开发,但是我觉得真实的开发也不是这样处理的,但是逻辑应该是差不多的。这里来测试一下,删除刚才使用二进制方式上传的那种图片吧。
Postman删除图片接口测试截图
Postman查看图片接口测试截图
图片已经被删除了,所以这里的返回结果是没有找到。
完整代码
package request_learn.controller;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
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.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import request_learn.util.R;
/**
* 文件的增 删 查接口
* */
@RestController
@RequestMapping
public class FileImageController {
@Value("D:/DBC/file")
private String baseDir; // 图片存储的路径
@GetMapping("/files")
public R listFile() {
File file = new File(baseDir);
if (file.exists()) {
return R.success(file.list());
}
return R.fail("没有任何文件"); // 返回一个空的字符串数组
}
@GetMapping("/file/{filename}")
public ResponseEntity<byte[]> getFile(@PathVariable("filename") String filename) {
File file = new File(baseDir, filename);
if (file.exists()) {
byte[] fileData = null;
try {
fileData = Files.readAllBytes(file.toPath());
return ResponseEntity.ok()
.header("Content-Type", MediaType.IMAGE_JPEG_VALUE) // 设置必要的内容类型,否则浏览器无法解析
.body(fileData);
} catch (IOException e) {
e.printStackTrace();
}
}
ObjectMapper mapper = new ObjectMapper();
byte[] res = null; // 这里应该给它一个初始值,防止发生异常,导致响应没有信息,但是这里太简单了,可以不考虑
try {
res = mapper.writeValueAsBytes(R.fail("寻遍天涯,一无所获"));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE) // 这里应该是指定了 UTF-8,如果乱码的话,就直接评价一个 charset=UTF-8
.body(res);
}
@PostMapping("/file")
public R uploadFile(@RequestParam("filename") String fileName, @RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
int index = fileName.lastIndexOf(".");
if (index != -1) {
fileName = UUID.randomUUID().toString() + fileName.substring(index);
} else {
fileName = UUID.randomUUID().toString() + ".jpg";
}
try {
file.transferTo(new File(baseDir, fileName));
return R.success("文件上传成功");
} catch (IOException e) {
e.printStackTrace();
return R.error(e.toString());
}
}
return R.fail("文件上传失败");
}
/**
* 目前来说很少使用的一种方式,因为web表单是无法发送这个请求的,只有ajax可以或者是非web端的应用。
* */
@PostMapping("/binary")
public R file(@RequestBody byte[] fileData) {
String fileName = UUID.randomUUID().toString() + ".jpg";
try {
Files.write(new File(baseDir, fileName).toPath(), fileData);
return R.success("文件上传成功");
} catch (IOException e) {
e.printStackTrace();
return R.error(e.toString()); // 这里不使用 e.getMessage()
}
}
@DeleteMapping("/file/{filename}")
public R delete(@PathVariable("filename") String fileName) {
File file = new File(baseDir, fileName);
if (file.exists()) {
// 这里要考虑并发吗?
boolean flag = file.delete();
if (flag) {
return R.success("文件删除成功");
}
}
return R.fail("文件删除失败");
}
}
自定义响应体
这里使用了两个工具类,用来包装程序返回得响应。
响应状态枚举类
package request_learn.util;
public enum Result {
SUCCESS(100, "操作成功"),
FAIL(200, "操作失败"),
ERROR(300, "未知异常");
Result(int code, String msg) {
this.code = code;
this.msg = msg;
}
private int code;
private String msg;
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
响应体包装类
package request_learn.util;
public class R {
private Result result;
private Object data;
private R(Result result, Object data) {
this.result = result;
this.data = data;
}
public static R success(Object data) {
return new R(Result.SUCCESS, data);
}
public static R fail(Object data) {
return new R(Result.FAIL, data);
}
public static R error(Object data) {
return new R(Result.ERROR, data);
}
public Result getResult() {
return result;
}
public Object getData() {
return data;
}
}
前端页面部分
我不生产前端代码,我只是菜鸟教程的搬运工!
这个前端代码是 菜鸟教程CSS中的图片廊和Vue教程(ajax+模板语法) 的一个结合体,复制+粘贴其实才是我最拿手的技能!嘿嘿。同时为了躲避烦人的跨域问题,我直接把该文件作为SpringBoot项目的静态资源了。这样处于同一个域下面的访问,就不会有跨域问题了。
resources目录
注意:这里这个vue.min.js,你也可以使用CDN的方式引入,省的自己去下载了。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程图片廊</title>
<style>
div.img {
margin: 5px;
border: 1px solid #ccc;
float: left;
width: 180px;
}
div.img:hover {
border: 1px solid #777;
}
div.img img {
width: 100%;
height: auto;
}
div.desc {
padding: 15px;
text-align: center;
}
</style>
<script src="/vue.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
<div class="responsive" v-for="filename in filenames">
<div class="img">
<a target="_blank" v-bind:href='"http://localhost:8080/file/"+filename'>
<img v-bind:src='"http://localhost:8080/file/"+filename' alt="美女图片" width="400" height="300">
</a>
<div class="desc">这是测试图片</div>
</div>
</div>
</div>
<script>
new Vue({
el: '#app',
data () {
return {
filenames: null
}
},
mounted () {
axios
.get('/files')
.then(response => this.filenames = response.data.data)
.catch(function (error) {
// 请求失败处理
console.log(error);
});
}
})
</script>
</body>
</html>
index.html
点击查看大图
demo地址
总结
这是个人学习之余,利用闲暇时间做的一个玩具demo,现在感觉使用SpringBoot的参数绑定很顺手了。我以前的一个作业就是利用python完成了类似的项目,不过当时用到了数据库记录图片的路径。当时虽然也完成了,当时显然没有现在这么流畅的,这个后端我只写了很短的时间,因为几个接口我都是很熟悉的,倒是前端,真是难死人了。这大概就是木桶原理吧,不过也说明分工开发的必要性!
转载:https://blog.csdn.net/qq_40734247/article/details/110410916