文件上传与下载
- 文件上传
- 文件上传的实现
- 表结构设计
- UploadFileServiceImpl的实现
- 上传文件遇到的问题与解决
- 文件下载
文件上传
文件上传的表单中,需要注意的三个地方:
1.表单的请求方式必须为post;
2.表单域必须有file类型;
3.表单的enctype属性值必须:multipart/form-data。
另外,下面简单介绍enctype属性以及其三个值:
- enctype属性:规定form表单发送到服务器时的编码方式
- application/x-www-form-urlencoded:默认编码方式
- multipart/form-data:传输二进制类型的数据,如文件、图片等
- text/plain:传输纯文体,不对特殊字符编码,空格转换为 “ + ”
<!-- 示例 -->
<form enctype="multipart/form-data" action="" method="post">
请选择上传的文件:<input type="file" name=""><br>
<input type="submit" value="上传">
</form>
如图:
文件上传的实现
文件上传的注意事项:
- 为保证服务器安全,上传文件应该放在外界无法访问的目录下(WEB-INF目录下);
- 限制上传文件的最大值(判断大小);
- 限制上传文件的类型(判断验证);
- 为防止文件覆盖现象,文件上传后产生唯一的文件名(UUID实现);
- hash算法分散存储各个文件
如图所示:
了解到文件上传注意事项后,下面进行简单的实现。
表结构设计
uploadfiles表:记录上传文件的信息
id—记录id
filename—文件名
savename—文件保存的名字
desc—文件的描述
type—文件类型
size—文件大小
path—保存路径
time—保存时间
UploadFileServiceImpl的实现
首先,基于MVC思想构建主要结构;接着,创建类UploadFilePropertiesUtils来获取uploadfile.properties内容;最后,创建一个监听器,用来当服务器启动时执行contextInitialized的方法,遍历获取uploadfile.properties内容,并放入UploadFilePropertiesUtils类中的map里。
文件上传的基本思路:
package cn.edu.MVCcase.Model.service;
import cn.edu.MVCcase.JDBC.utils.UploadFilePropertiesUtils;
import cn.edu.MVCcase.Model.dao.FactoryDao;
import cn.edu.MVCcase.Model.dao.UploadFileDao;
import cn.edu.MVCcase.Model.model.UploadFile;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Date;
import java.util.List;
import java.util.UUID;
public class UploadFileServiceImpl implements UploadFileService {
UploadFileDao uploadFileDao = FactoryDao.getUploadFileDao();
private String saveDirectory = UploadFilePropertiesUtils.getInstance().getProperties("savePath");
private String tempDirectory = UploadFilePropertiesUtils.getInstance().getProperties("tempPath");
private String sizeThreshold = UploadFilePropertiesUtils.getInstance().getProperties("sizeThreshold");
private String fileSizeMax = UploadFilePropertiesUtils.getInstance().getProperties("fileSizeMax");
private String sizeMax = UploadFilePropertiesUtils.getInstance().getProperties("sizeMax");
private String fileExpection = UploadFilePropertiesUtils.getInstance().getProperties("fileExpection");
@Override
public void addUploadFile(UploadFile uploadFile) {
//上传文件的信息保存到数据库前,先保存到指定的磁盘目录下
uploadFileDao.addUploadFile(uploadFile);
}
@Override
public List<UploadFile> getUploadFiles() {
return uploadFileDao.getUploadFiles();
}
@Override
public void deleteUploadFile(int id) {
UploadFile uploadFile = uploadFileDao.get(id);
//数据库的文件信息先删除
uploadFileDao.deleteUploadFile(id);
//再将磁盘上的文件删除
deleteFile(uploadFile.getPath() + "\\" + uploadFile.getSavename());
}
@SuppressWarnings("unchecked")
@Override
public void saveUploadFile(HttpServletRequest req, HttpServletResponse resp) {
//先将文件保存到指定的服务器目录中,再获取和创建保存文件的最终目录和临时目录
String savePath = req.getSession().getServletContext().getRealPath(this.saveDirectory);
String tempPath = req.getSession().getServletContext().getRealPath(this.tempDirectory);
File tempFlie = new File(tempPath);
if (!tempFlie.exists()) {
tempFlie.mkdirs(); //若临时目录不存在,则创建一个
}
//创建DiskFileItemFactory工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(Integer.parseInt(this.sizeThreshold)); //若小于100KB的上传文件存储在内存中;反之存储在tempPath
factory.setRepository(tempFlie); //上传文件的临时目录
//创建文件上传的解析器
ServletFileUpload servletFileUpload = new ServletFileUpload(factory);
servletFileUpload.setFileSizeMax(Integer.parseInt(this.fileSizeMax)); //限制单个文件上传的大小
servletFileUpload.setHeaderEncoding("UTF-8"); //防止中文乱码
servletFileUpload.setSizeMax(Integer.parseInt(this.sizeMax)); //限制多个文件同时上传的总和的大小
//接着,解析结果返回一个List<FileItem>集合
String filename = "";
String desc = "";
String type = "";
long size = 0;
String fileTypeException = "";
String saveFilename = "";
String realSavePath = "";
OutputStream outputStream = null;
InputStream inputStream = null;
try {
List<FileItem> fileItemList = servletFileUpload.parseRequest(req);
if (fileItemList != null && fileItemList.size() > 0) {
for (FileItem fileItem:fileItemList) {
if (fileItem.isFormField()) {
//判断该表单项是否是普通类型
desc = fileItem.getString("UTF-8");
//上传文件的信息写入到数据库uploadfiles表中
if (!"".equals(filename)) {
UploadFile uploadFile = new UploadFile();
uploadFile.setFilename(filename);
uploadFile.setSavename(saveFilename);
uploadFile.setDesc(desc);
uploadFile.setType(type);
uploadFile.setSize(size + "");
uploadFile.setPath(realSavePath);
uploadFile.setTime(new Date());
//插入
addUploadFile(uploadFile);
}
} else {
//否则该表单项是file类型
filename = fileItem.getName(); //获取文件名
type = fileItem.getContentType(); //获取文件类型
size = fileItem.getSize(); //获取文件大小
//处理绝对路径问题:浏览器不同可能会获取的filename带有绝对路径,截取最后的文件名
filename = filename.substring(filename.lastIndexOf("\\") + 1);
fileTypeException = filename.substring(filename.lastIndexOf(".") + 1); //获取文件后缀名
//验证后缀的合法性
if(this.fileExpection.indexOf(fileTypeException) == -1) {
throw new RuntimeException("不符合上传文件的类型,请上传后缀名为" + fileExpection + "的文件!!!");
}
//文件流写入保存的目录中
saveFilename = makeFileName(filename);
realSavePath = makePath(saveFilename,savePath);
//创建输出流
outputStream = new FileOutputStream(realSavePath + "\\" + saveFilename);
inputStream = fileItem.getInputStream();
//创建缓存区
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, len);
}
inputStream.close();
outputStream.close();
}
}
}
//删除临时目录下的临时文件
File tempfile = new File(tempPath);
for (File file:tempfile.listFiles()) {
file.delete();
}
} catch (FileUploadBase.FileSizeLimitExceededException e) {
throw new RuntimeException("上传单个文件的大小超出了限制" + Integer.parseInt(this.fileSizeMax)/(1024*1024) + "MB!!!");
} catch (FileUploadBase.SizeLimitExceededException e) {
throw new RuntimeException("上传文件总大小超出了限制" + Integer.parseInt(this.sizeMax)/(1024*1024) + "MB!!!");
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
} finally {
if(inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//判断提交的数据是否为上传表单的数据(enctype="multipart/form-data")
if(!ServletFileUpload.isMultipartContent(req)) {
throw new RuntimeException("上传表单的Form的编码方式不正确!!!");
}
}
@Override
public void deleteFile(String saveUploadFilePath) {
//删除服务器上的上传文件
File file = new File(saveUploadFilePath);
if (file.isFile()) {
file.delete();
}
}
@Override
public UploadFile getUploadFileById(int id) {
return uploadFileDao.get(id);
}
//makeFileName、makePath对应的方法
public String makeFileName(String filename) {
//避免上传的文件同名问题
return UUID.randomUUID().toString() + "_" + filename; //提供的一个自动生成主键 + "_" + filename
}
public String makePath(String saveFilename,String savePath) {
//获得文件名的哈希值并转换为十六进制,确定在哈希表中的索引位置
int hashcode = saveFilename.hashCode();
int dir1 = hashcode & 0xf; //其值在0-15内
int dir2 = (hashcode >> 4) & 0xf; //其值在0-15内
//创建存储文件的新目录
String dir = savePath + "\\" + dir1 + "\\" + dir2;
File file = new File(dir);
if (!file.exists()) {
file.mkdirs();
}
return dir;
}
}
注:上传文件的大小决定存储在最终保存目录还是临时目录下,而文件可以进行删除(若是临时文件即时删除;若不是,需要点击,数据库的文件信息与磁盘上的文件都会删除)。
上传文件遇到的问题与解决
jsp使用FileUpload上传文件设置setFileSizeMax或setSizeMax,若上传文件超过其设置的大小,浏览器链接会被重置。造成这个问题与Tomcat默认设置有关,需要在apache-tomcat-8.5.75\conf\server.xml下修改:
默认,如图:
修改后,如图:
connectionUploadTimeout=“36000000” //上传文件超时改为十个小时
disableUploadTimeout=“false” //关闭浏览器重置
maxSwallowSize=“-1” //吞吐量的最大值为无限大
文件下载
首先,获取文件的绝对路径与原来的文件名;接着,设置content-disposition响应头控制浏览器以下载的方式打开文件;最后下载到本地指定的目录下。
文件下载的基本思路:
private void downloadFile(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取文件的绝对路径
int id = Integer.parseInt(req.getParameter("id"));
UploadFile uploadFile = uploadFileService.getUploadFileById(id);
String path = uploadFile.getPath() + "\\" + uploadFile.getSavename();
//获取原来的文件名
String filename = uploadFile.getFilename();
//获取浏览器的类型
String userAgent = req.getHeader("User-Agent");
//不同浏览器的处理,解决中文乱码的问题
if (userAgent.contains("MSIE") || userAgent.contains("Trident")) {
filename = java.net.URLEncoder.encode(filename,"UTF-8"); //IE或以IE为内核的浏览器
} else {
filename = new String(filename.getBytes("UTF-8"),"ISO-8859-1"); //非IE或以IE为内核的浏览器
}
//设置content-disposition响应头控制浏览器以下载的方式打开文件
resp.setHeader("content-disposition","attachment;filename=" + filename);
//获取要下载的文件输入流
InputStream inputStream = new FileInputStream(path);
int len = 0;
//创建缓冲区
byte[] bytes = new byte[1024];
//通过resp对象获取outputStream输出流对象
OutputStream outputStream = resp.getOutputStream();
//将FileInputStream流对象写入到bytes缓冲区
while ((len=inputStream.read(bytes)) > 0) {
outputStream.write(bytes,0,len);
}
//关闭
inputStream.close();
outputStream.close();
}
结果如图: