三、文件的上传和下载
1 文件上传
文件上传要求form表单的请求方式必须为post,并且添加属性enctype="multipart/form-data" SpringMVC中将上传的文件封装到MultipartFile对象中,通过此对象可以获取文件相关信息 上传步骤:
1.1 添加依赖
<!--用于文件上传:-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
1.2 在SpringMVC的配置文件中添加配置:
<!-- 文件上传需要用到的bean,这里的代码不需要修改-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
1.3 控制器方法
共用的前端页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>文件上传</title>
</head>
<body>
<%--
文件上传的要求:
1、method必须是post请求
2、form表单属性中的enctype="multipart/form-data"
--%>
<form method="post" enctype="multipart/form-data" action="uploadFile3">
<%-- 这里的name可以随便取值,但是在映射的方法填写形参时,值要和这里的name值相同 --%>
请选择要上传的文件:<input type="file" name="file"><br>
<input type="submit" value="提交" >
</form>
</body>
</html>
方法一:使用io流
@Controller
public class UploadController {
// 这里的形参值要和视图文件中的input type="file"标签中name的属性值相同
// <input type="file" name="file"><br>
@PostMapping("uploadFile")
public String uploadFile(MultipartFile file, HttpServletRequest request){
// 获取文件的旧(原始)名字
String oldname = file.getOriginalFilename();
// 输出文件原始的名字
System.out.println("file = " + file.getOriginalFilename());
// HttpServletRequest request用于获取当前项目上下文路径中的一个upload的文件夹
String realPath = request.getServletContext().getRealPath("/upload");
System.out.println("realPath = " + realPath);
// 先将该路径转化为一个file类,并判断该类是否存在,如果不存在就创建这个路径
File file1 = new File(realPath);
if (!file1.exists()){
file1.mkdirs();
}
// 取一个新的名字
String s = UUID.randomUUID().toString();
// 获取原来原始名字的后缀
int i = oldname.lastIndexOf(".");
String suffixName = oldname.substring(i);
System.out.println("原始文件名字的后缀suffixName = " + suffixName);
// 随机数+原始名字后缀获得一个不会重复的新名字
String newname = s + suffixName;
// 将图片上传到刚才的文件夹中
try {
// 输出流:用于写入文件
// File.separator就是代表文件的分隔符,就相当于是\
OutputStream outputStream = new FileOutputStream(file1+File.separator+newname);
// 当前的文件识别为输入流
InputStream inputStream = file.getInputStream();
// 将输入流中的东西写入到输出流中
int length = 0;
while ((length=inputStream.read())!=-1){
outputStream.write(length);
}
outputStream.flush();
outputStream.close();
inputStream.close();
// 文件上传之后只需要将路径保存到数据库即可
String picturePath = realPath+File.separator+newname;
} catch (IOException e) {
e.printStackTrace();
}
return "index";
}
方法二: 使用file.transferto
@Controller
public class UploadController {
// 这里的形参值要和视图文件中的input type="file"标签中name的属性值相同
// <input type="file" name="file"><br>
@PostMapping("uploadFile2")
public String uploadFile2(MultipartFile file, HttpServletRequest request){
// 获取文件的旧(原始)名字
String oldname = file.getOriginalFilename();
// 输出文件原始的名字
System.out.println("file = " + file.getOriginalFilename());
// HttpServletRequest request用于获取当前项目上下文路径中的一个upload的文件夹
String realPath = request.getServletContext().getRealPath("/upload");
System.out.println("realPath = " + realPath);
// 先将该路径转化为一个file类,并判断该类是否存在,如果不存在就创建这个路径
File file1 = new File(realPath);
if (!file1.exists()){
file1.mkdirs();
}
// 取一个新的名字
String s = UUID.randomUUID().toString();
// 获取原来原始名字的后缀
int i = oldname.lastIndexOf(".");
String suffixName = oldname.substring(i);
System.out.println("原始文件名字的后缀suffixName = " + suffixName);
// 随机数+原始名字后缀获得一个不会重复的新名字
String newname = s + suffixName;
// 将图片上传到刚才的文件夹中
File file2 = new File(realPath+File.separator+newname);
// 将刚要上传的文件上传到这个路径里面
try {
file.transferTo(file2);
} catch (IOException e) {
e.printStackTrace();
}
return "index";
}
}
方法3:阿里云文件上传
前两种方法,只能将文件上传到文件路径中,但是假如删掉项目的target,那么上传的文件也会丢失,这样的数据并不安全,所以我们可以将文件上传到云端,这里选择的是阿里云文件
开通阿里云的对象存储功能
1)搜索阿里云 https://www.aliyun.com/
2)登录后找到对象存储进行开通,这里有免费的使用空间;
3)进入到oss控制台,点击创建Bucket
记下Bucket名称和Endpoint
读写权限可以设置为公共读写
4)点击创建一个新的AccessSecret,
记下AccessKey ID 和 AccessKey Secret
5)添加阿里云文件上传的依赖
官网入门文档-->SDK示例-->Java-->安装
<!-- 阿里云 oss存储文件,如果java版本在9以上,还需要添加别的依赖-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.0</version>
</dependency>
</dependencies>
6)编写代码
阿里云官网有示例代码:
然后选择SDK示例-->Java-->上传文件-->简单上传
编写好的代码如下:
@Controller
public class UploadController {
@PostMapping("uploadFile3")
public String uploadFile3(MultipartFile file, HttpServletRequest request){
// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
// String endpoint = "https://oss-cn-shanghai.aliyuncs.com";
// 这里不加这个https也行
String endpoint = "oss-cn-shanghai.aliyuncs.com";
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
String accessKeyId = "这里是我的accessKeyId ";
String accessKeySecret = "这里是我的secret";
// 填写Bucket名称,例如examplebucket。
String bucketName = "这里是我的bucketName ";
// 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
String oldname = file.getOriginalFilename();
// 取一个新的名字
String s = UUID.randomUUID().toString();
// 获取原来原始名字的后缀
int i = oldname.lastIndexOf(".");
String suffixName = oldname.substring(i);
System.out.println("原始文件名字的后缀suffixName = " + suffixName);
// 随机数+原始名字后缀获得一个不会重复的新名字
String newname = s + suffixName;
String objectName = newname;
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
try {
// 填写字符串。
String content = "Hello OSS";
// 创建PutObjectRequest对象。
//这里的第三个参数就是要上传的文件放入输入流,file就是当前要上传的文件名。
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, file.getInputStream());
// 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。
// ObjectMetadata metadata = new ObjectMetadata();
// metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
// metadata.setObjectAcl(CannedAccessControlList.Private);
// putObjectRequest.setMetadata(metadata);
// 上传字符串。
ossClient.putObject(putObjectRequest);
String picturePath = "https://"+bucketName+"."+endpoint+"/"+oldname;
System.out.println("文件的地址是:picturePath = " + picturePath);
// 图片地址 https://leiwenlin.oss-cn-shanghai.aliyuncs.com/%E4%BA%8C%E5%AF%B8%E7%99%BD%E5%BA%95.jpg
} catch (OSSException oe) {
System.out.println("Caught an OSSException, which means your request made it to OSS, "
+ "but was rejected with an error response for some reason.");
System.out.println("Error Message:" + oe.getErrorMessage());
System.out.println("Error Code:" + oe.getErrorCode());
System.out.println("Request ID:" + oe.getRequestId());
System.out.println("Host ID:" + oe.getHostId());
} catch (ClientException ce) {
System.out.println("Caught an ClientException, which means the client encountered "
+ "a serious internal problem while trying to communicate with OSS, "
+ "such as not being able to access the network.");
System.out.println("Error Message:" + ce.getMessage());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
return "index";
}
}
方法4:layui界面做文件上传
文件上传的时候,需要有<input typr="file",name="file"/>但是这里没有写明,
其实源码中他的input就是这么写的,所以在方法中接收时,MultipartFile的别名就是file
另外注意:layui中发出的请求所接受的返回值都应该是一个LayData类型的数据,也就是code,msg,data类型的数据
这里跳转页面后,最后需要页面返回的数据中要有一个code值,否则话文件可以上传成功,但是会提示上传接口异常
前端页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<%--
${pageContext.request.contextPath}这是获取上下文的路径,相当于是获取了当前的项目名
这里要引入css样式,也要引入js的路径
--%>
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/layui-v2.7.6/layui/css/layui.css">
<%-- 在下面的代码中也可以引入js代码--%>
<%-- <script type="application/javascript" src="${pageContext.request.contextPath}/static/layui-v2.7.6/layui/layui.js"></script>--%>
</head>
<body>
<div class="layui-upload">
<%--
文件上传的时候,需要有<input typr="file",name="file"/>
但是这里没有写明,其实源码中他的input就是这么写的,所以在方法中接收时,MultipartFile的别名就是file
--%>
<button type="button" class="layui-btn" id="test1">上传图片</button>
<div class="layui-upload-list">
<img class="layui-upload-img" id="demo1" style="height: 100px;width: 100px;border-radius: 50%">
<p id="demoText"></p>
</div>
<div style="width: 95px;">
<div class="layui-progress layui-progress-big" lay-showpercent="yes" lay-filter="demo">
<div class="layui-progress-bar" lay-percent=""></div>
</div>
</div>
</div>
<a name="list-progress"> </a>
<%--引入js代码--%>
<script type="application/javascript" src="${pageContext.request.contextPath}/static/layui-v2.7.6/layui/layui.js"></script>
<script>
<%--
另外注意:layui中发出的请求所接受的返回值都应该是一个LayData类型的数据,也就是code,msg,data类型的数据
这里跳转页面后,最后需要页面返回的数据中要有一个code值,否则话文件可以上传成功,但是会提示上传接口异常
--%>
layui.use(['upload', 'element', 'layer'], function() {
var $ = layui.jquery
, upload = layui.upload
, element = layui.element
, layer = layui.layer;
//常规使用 - 普通图片上传
var uploadInst = upload.render({
elem: '#test1'
, url: '${pageContext.request.contextPath}/uploadFile4' //此处用的是第三方的 http 请求演示,实际使用时改成您自己的上传接口即可。
, before: function (obj) {
//预读本地文件示例,不支持ie8
obj.preview(function (index, file, result) {
$('#demo1').attr('src', result); //图片链接(base64)
});
element.progress('demo', '0%'); //进度条复位
layer.msg('上传中', {icon: 16, time: 0});
}
, done: function (res) {
//如果上传失败
if (res.code > 0) {
return layer.msg('上传失败');
}
//上传成功的一些操作
//……
$('#demoText').html(''); //置空上传失败的状态
}
, error: function () {
//演示失败状态,并实现重传
var demoText = $('#demoText');
demoText.html('<span style="color: #FF5722;">上传失败</span> <a class="layui-btn layui-btn-xs demo-reload">重试</a>');
demoText.find('.demo-reload').on('click', function () {
uploadInst.upload();
});
}
//进度条
, progress: function (n, elem, e) {
element.progress('demo', n + '%'); //可配合 layui 进度条元素使用
if (n == 100) {
layer.msg('上传完毕', {icon: 1});
}
}
});
});
</script>
</body>
</html>
不变的代码可以设置为常量:
public class AliyunUtil {
public static final String END_POINT = "oss-cn-shanghai.aliyuncs.com";
public static final String ACCESS_KEY_ID = "your accessKeyId";
public static final String ACCESS_KEY_SECRET = "your accessKeySecret";
public static final String BUCKET_NAME = "your bucketName ";
}
后台代码:
@Controller
public class UploadController {
@PostMapping("uploadFile4")
public LayData uploadFile4(MultipartFile file, HttpServletRequest request){
// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
// String endpoint = "https://oss-cn-shanghai.aliyuncs.com";
// 这里不加这个https也行
String endpoint = AliyunUtil.END_POINT;
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
String accessKeyId = AliyunUtil.ACCESS_KEY_ID;
String accessKeySecret = AliyunUtil.ACCESS_KEY_SECRET;
// 填写Bucket名称,例如examplebucket。
String bucketName = AliyunUtil.BUCKET_NAME;
// 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
String oldname = file.getOriginalFilename();
// 取一个新的名字
String s = UUID.randomUUID().toString();
// 获取原来原始名字的后缀
int i = oldname.lastIndexOf(".");
String suffixName = oldname.substring(i);
System.out.println("原始文件名字的后缀suffixName = " + suffixName);
// 随机数+原始名字后缀获得一个不会重复的新名字
String newname = s + suffixName;
String objectName = newname;
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
try {
// 创建PutObjectRequest对象。
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, file.getInputStream());
// 上传字符串。
ossClient.putObject(putObjectRequest);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
String picturePath = "https://"+bucketName+"."+endpoint+"/"+oldname;
// 这里需要返回的是一个LayData类型的数据,否则layui接受的数据格式不对,会导致异常
return new LayData(picturePath);
}
}
2 文件下载
// 下载
@RequestMapping(value="/download",method= RequestMethod.GET) //匹配的是href中的download请求
public ResponseEntity<byte[]> download(HttpServletRequest request, @RequestParam("filename") String filename) throws IOException{
String downloadFilePath=request.getServletContext().getRealPath("/upload");
File file = new File(downloadFilePath+File.separator+filename);//新建一个文件
HttpHeaders headers = new HttpHeaders();//http头信息
String downloadFileName = new String(filename.getBytes("UTF-8"),"iso-8859-1");//设置编码
headers.setContentDispositionFormData("attachment", downloadFileName);
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
//MediaType:互联网媒介类型 contentType:具体请求中的媒体类型信息
return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file),headers, HttpStatus.CREATED);
}