今天我们来学习下文件的上传与下载吧,方便我们以后项目中使用哦!
1、文件上传
使用Apache Commons fileupload组件依赖Commons io组件的文件复制功能,常用方法如图所示:


2、文件下载
处理流程如图:

- 第一步:创建数据表(一个表中每行数据存放两个文件为例,包括文件id,文件名,文件类别,其中也可以存放普通文件的title和id,如果涉及多个文件,可以使用一个主表,多个子表,一对多的关系存放,将文件放在一个表,其余普通文件字段放在一个表)
CREATE TABLE IF NOT EXISTS upload_info(
upload_id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(100),
file_id_1 VARCHAR(100),
file_name_1 VARCHAR(100),
file_type_1 VARCHAR(100),
file_id_2 VARCHAR(100),
file_name_2 VARCHAR(100),
file_type_2 VARCHAR(100)
);
- 第二步:编写实体类UploadInfo.java
此时将单个文件类和其他文件类单独存放,用数组存放单个文件信息
package com.yueqian.store.domain;
public class UploadInfo {
private int uploadId;
private String title;
private FileInfo[] fileInfos = new FileInfo[2];
public int getUploadId() {
return uploadId;
}
public void setUploadId(int uploadId) {
this.uploadId = uploadId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public FileInfo[] getFileInfos() {
return fileInfos;
}
public void addFileInfos(int index, FileInfo fileInfo) {
if (index >= 0 && index < 2) {
this.fileInfos[index] = fileInfo;
}
}
}
编写fileInfo.java
package com.yueqian.store.domain;
public class FileInfo {
private String fileId;
private String fileName;
private String fileType;
public String getFileId() {
return fileId;
}
public void setFileId(String fileId) {
this.fileId = fileId;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getFileType() {
return fileType;
}
public void setFileType(String fileType) {
this.fileType = fileType;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return super.toString();
}
}
- 第三步:编写jsp(简单测试)
<ul>
<li><a href='${pageContext.request.contextPath}/file/upload.jsp'>上传文件</a></li>
<li><a href='${pageContext.request.contextPath}/file/list'>查看并下载文件</a></li>
</ul>
- 第四步:编写servlet
此时我们需要在项目目录下创建temp目录,用来临时存放上传文件超过设置阈值时字节数,超过设置的阈值便会保存到硬盘中,否则在内存中存放。系统会在项目下自动生成供上传保存的文件夹uload目录
package com.yueqian.store.controller;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.util.URLEncoder;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;
import com.yueqian.store.domain.FileInfo;
import com.yueqian.store.domain.UploadInfo;
import com.yueqian.store.service.UploadService;
@WebServlet({ "/file/upload", "/file/download", "/file/list" })
public class FileServlet extends BaseServlet {
private static final long serialVersionUID = 1L;
private UploadService uploadService = new UploadService();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String uri = req.getRequestURI();
if (uri.contains("/file/list")) {
List<UploadInfo> uploadList = this.uploadService.findAllFile();
req.setAttribute("uploadList", uploadList);
req.getRequestDispatcher("/file/list.jsp").forward(req, resp);
} else if (uri.contains("/file/download")) {
String fileId = super.paseParamter("fileId", req, String.class);
// 创建供下载的目录
File uploadDir = new File(req.getServletContext().getRealPath("/upload"));
if (fileId != null) {
// 创建下载的文件(将文件复制到文件夹下)为了响应文件长度
File file = new File(uploadDir, fileId);
if (file.exists()) {
FileInfo fileInfo = this.uploadService.findFileById(fileId);
if (fileInfo != null) {
// 返回响应的文件类型和长度
resp.setContentType(fileInfo.getFileType());
resp.setContentLengthLong(file.length());
String encodeFileName = null;
//将文件名成进行编码(先用url编码,再按照utf-8编码)
encodeFileName = java.net.URLEncoder.encode(fileInfo.getFileName(),"UTF-8");
//设置响应头信息(Content-Disposition类型设置为attachment,表示以附件形式进行下载,征得用户同意才可以下载,不打开文件,默认(online)是能打开的文件浏览器自动打卡)
//同时设置下载文件时使用真实文件名
//filename*=utf-8''"+encodeFileName 表示对编码以后的中文文件名进行解码(先进行url解码、再进行utf-8解码)
//浏览器默认先找这个解码属性,现在的新版浏览器已经对编码之后的中文文件名默认进行解码了,所以也可以不需要设置解码操作
resp.setHeader("Content-Disposition", "attachment;filename="+encodeFileName+";filename*=utf-8''"+encodeFileName);
// 将文件保存到输出流中
FileUtils.copyFile(file, resp.getOutputStream());
}
}
}
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 查看保存的文件uploadDir是否存在,不存在则创建
File uploadDir = new File(req.getServletContext().getRealPath("/upload"));
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
// 1、创建磁盘文件列表工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
// 2、设置工厂处理的大小(表示10k以上的文件存入硬盘处理,10k以内的文件在内存中处理)
factory.setSizeThreshold(10240);
// 3、创建文件存放硬盘的位置(此位置只能在上下文路径中获取,其他地方用户部署在linux上无法访问)文件超过阈值,就会被存放到临时目录
String realPath = req.getServletContext().getRealPath("/temp");
System.out.println(realPath + "===========================");
File file = new File(realPath);
// 4、将文件的存放位置设置进工厂
factory.setRepository(file);
// 5、创建ServletUploadFile对象
ServletFileUpload sfu = new ServletFileUpload(factory);
// 设置上传文件的大小
sfu.setFileSizeMax(5 * 1024 * 1024);// 设置单个文件上传耳朵大小 5M (默认单个文件为2M)
sfu.setSizeMax(10 * 1024 * 1024);// 设置总的上传文件的大小 10M (默认总的文件为2M)
//ServletFileUpload解析多部分表单的每个部分的请求头时,设置的解码格式
//当获取上传文件的中文文件名(请求头)时,出现乱码,可以设置此解码格式,8.0默认采用utf-8解码
sfu.setHeaderEncoding("utf-8");
int count = 0;// 记录将文件保存到数据库影响的行数
String exceptionMsg = null;
try {
// 6、解析请求,返回列表对象
List<FileItem> itemList = sfu.parseRequest(req);
String parthName = null, fileName = null, fileType = null, fileId = null;
UploadInfo uploadInfo = null;
int index = 0;
if (itemList != null && itemList.size() > 0) {
uploadInfo = new UploadInfo();
for (FileItem item : itemList) {
// 判断文件是普通文件还是多表单文件
if (item.isFormField()) {
// 普通文件
parthName = item.getFieldName(); // 获取普通文件字段名
if ("title".equals(parthName)) {
//多部分表单数据,每个部分的请求正文的值(普通文件的值)的解码方式
uploadInfo.setTitle(item.getString("UTF-8")); // 对获取的文件名进行解码取值,并存放到对象中
}
} else {
// 多表单文件(因为不同的浏览器,文件名写法不一样,只要真实的文件名,前面不需要路径,所以对文件名进行解析)
fileName = parseFileName(item.getName()); // 获取文件名
fileId = generateField(); // 将文件名保存时起别名,防止保存时文件名重复覆盖问题
fileType = item.getContentType(); // 获取文件类型
parthName = item.getFieldName();// 获得参数名
InputStream is = item.getInputStream(); // 获取文件输入流
// 判断是否选择了上传选择,如果没有选择,则不进行提交(判断输入字节大小和文件名长度是否等于0)
if (is.available() == 0 || fileName.length() == 0) {
continue;
}
FileOutputStream fos = new FileOutputStream(new File(uploadDir, fileId)); // 获取文件输出流
// 将文件输入流保存到文件(工具栏实现)
// FileUtils.copyInputStreamToFile(is, new File(uploadDir,fileName));
// 传统方法实现文件的复制
byte[] bytes = new byte[1024];
int len = 0;
while (-1 != (len = is.read(bytes))) {
fos.write(bytes, 0, len);
}
fos.close();
is.close();
FileInfo fileInfo = new FileInfo();
fileInfo.setFileId(fileId);
fileInfo.setFileName(fileName);
fileInfo.setFileType(fileType);
uploadInfo.addFileInfos(index++, fileInfo);
}
}
// 将信息存放到数据库中
count = this.uploadService.saveInfo(uploadInfo);
}
} catch (FileUploadException e) {
e.printStackTrace();
}
resp.sendRedirect(req.getContextPath() + "/file/upload.jsp?count=" + count); // 重定向到上传文件页面
}
}
- 第三步:编写service
package com.yueqian.store.service;
import java.sql.SQLException;
import java.util.List;
import com.yueqian.store.common.DBUtils;
import com.yueqian.store.dao.UploadDao;
import com.yueqian.store.domain.FileInfo;
import com.yueqian.store.domain.UploadInfo;
/**
* 保存文件
* @author LinChi
*
*/
public class UploadService {
private UploadDao uploadDao = new UploadDao();
public int saveInfo(UploadInfo uploadInfo) {
int count = 0;
try {
count = this.uploadDao.saveInfo(uploadInfo);
DBUtils.commit();
}catch(SQLException e) {
DBUtils.rollback();
e.printStackTrace();
}
return count;
}
/**
* 查询所有文件列表
* @return
*/
public List<UploadInfo> findAllFile(){
List<UploadInfo> list = null;
try {
list = this.uploadDao.findAllFile();
DBUtils.commit();
}catch(Exception e) {
DBUtils.rollback();
e.getStackTrace();
}
return list;
}
/**
* 根据文件id查看文件信息
* @param fileId
* @return
*/
public FileInfo findFileById(String fileId) {
FileInfo info = null;
try {
info = this.uploadDao.findFileById(fileId);
DBUtils.commit();
}catch(Exception e) {
DBUtils.rollback();
e.getStackTrace();
}
return info;
}
}
- 第五步:编写dao
package com.yueqian.store.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
import com.yueqian.store.common.DBUtils;
import com.yueqian.store.domain.FileInfo;
import com.yueqian.store.domain.UploadInfo;
/**
* 上传文件持久层
*
* @author
*
*/
public class UploadDao {
public int saveInfo(UploadInfo uploadInfo) throws SQLException {
Connection conn = DBUtils.getConnection();
PreparedStatement pstm = null;
int count = 0;
try {
pstm = conn.prepareStatement(
"INSERT INTO upload_info(title,file_id_1,file_name_1,file_type_1,file_id_2,file_name_2,file_type_2) VALUES(?,?,?,?,?,?,?)");
pstm.setString(1, uploadInfo.getTitle());
if (uploadInfo.getFileInfos()[0] != null) {
pstm.setString(2, uploadInfo.getFileInfos()[0].getFileId());
pstm.setString(3, uploadInfo.getFileInfos()[0].getFileName());
pstm.setString(4, uploadInfo.getFileInfos()[0].getFileType());
} else {
pstm.setNull(2, Types.VARCHAR);
pstm.setNull(3, Types.VARCHAR);
pstm.setNull(4, Types.VARCHAR);
}
if (uploadInfo.getFileInfos()[1] != null) {
pstm.setString(5, uploadInfo.getFileInfos()[1].getFileId());
pstm.setString(6, uploadInfo.getFileInfos()[1].getFileName());
pstm.setString(7, uploadInfo.getFileInfos()[1].getFileType());
} else {
pstm.setNull(5, Types.VARCHAR);
pstm.setNull(6, Types.VARCHAR);
pstm.setNull(7, Types.VARCHAR);
}
count = pstm.executeUpdate();
} finally {
DBUtils.close(null, pstm);
}
return count;
}
/**
* 查询所有文件列表
*
* @return
* @throws SQLException
*/
public List<UploadInfo> findAllFile() throws SQLException {
Connection conn = DBUtils.getConnection();
PreparedStatement pstm = null;
ResultSet rs = null;
List<UploadInfo> list = new ArrayList<UploadInfo>();
UploadInfo uploadInfo = null;
try {
pstm = conn.prepareStatement(
"SELECT u.`upload_id`,u.`title`,u.`file_id_1`,u.`file_name_1`,u.`file_type_1`,u.`file_id_2`,u.`file_name_2`,u.`file_type_2`\r\n"
+ "FROM upload_info u");
rs = pstm.executeQuery();
String fileName;
while (rs.next()) {
uploadInfo = new UploadInfo();
uploadInfo.setUploadId(rs.getInt(1));
uploadInfo.setTitle(rs.getString(2));
fileName = rs.getString("file_name_1");
if (fileName != null) {
FileInfo fileInfo = new FileInfo();
fileInfo.setFileId(rs.getString(3));
fileInfo.setFileName(rs.getString(4));
fileInfo.setFileType(rs.getString(5));
uploadInfo.addFileInfos(0, fileInfo);
}
fileName = rs.getString("file_name_2");
if (fileName != null) {
FileInfo fileInfo = new FileInfo();
fileInfo.setFileId(rs.getString(6));
fileInfo.setFileName(rs.getString(7));
fileInfo.setFileType(rs.getString(8));
uploadInfo.addFileInfos(1, fileInfo);
}
list.add(uploadInfo);
}
} finally {
DBUtils.close(rs, pstm);
}
return list;
}
/**
* 根据文件id查看文件信息
*
* @return
* @throws SQLException
*/
public FileInfo findFileById(String fileId) throws SQLException {
Connection conn = DBUtils.getConnection();
PreparedStatement pstm = null;
FileInfo fileInfo = null;
ResultSet rs = null;
try {
pstm = conn.prepareStatement("SELECT u1.file_id_1,u1.file_name_1,u1.file_type_1 FROM upload_info u1 WHERE u1.file_id_1=? UNION SELECT u2.file_id_2,u2.file_name_2,u2.file_type_2 FROM upload_info u2 WHERE u2.file_id_2=?");
pstm.setString(1, fileId);
pstm.setString(2, fileId);
rs = pstm.executeQuery();
if (rs.next()) {
fileInfo = new FileInfo();
fileInfo.setFileId(rs.getString(1));
fileInfo.setFileName(rs.getString(2));
fileInfo.setFileType(rs.getString(3));
}
} finally {
DBUtils.close(rs, pstm);
}
return fileInfo;
}
}
- 第六步:编写upload.jsp文件
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body>
<h2>文件上传</h2>
<form action="<%=request.getContextPath()%>/file/upload" method="post" enctype = "multipart/form-data">
标题:<input type="text" name="title" ><br/>
文件1:<input type="file" name="attachment"><br/>
文件2:<input type="file" name="attachment"><br/>
<input type="submit" value = "提交">
<%
String count = request.getParameter("count");
if(count != null && count.length()>0){
int fileCount = Integer.parseInt(count);
if(fileCount>0){
out.print("成功上传了"+fileCount+"文件!");
}else{
out.print("上传文件失败!");
}
}
%>
</form>
</body>
</html>
- 第七步:编写list.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import="java.util.*,com.yueqian.store.domain.*" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>文件查看列表</title>
</head>
<body>
<h2>文件上传列表</h2>
<%
List<UploadInfo> list = (List<UploadInfo>)request.getAttribute("uploadList");
list.forEach(System.out::print);
if(list != null && list.size()>0){
%>
<ul>
<%
for(UploadInfo info:list){
%>
<li>
标题:<%=info.getTitle() %><br/>
<% if(info.getFileInfos()[0]!=null){ %>
<a href="<%=request.getContextPath()%>/file/download?fileId=<%=info.getFileInfos()[0].getFileId()%>"><%=info.getFileInfos()[0].getFileName() %></a><br/>
<%} %>
<% if(info.getFileInfos()[1]!=null){ %>
<a href="<%=request.getContextPath()%>/file/download?fileId=<%=info.getFileInfos()[1].getFileId()%>"><%=info.getFileInfos()[1].getFileName() %></a>
<%} %>
</li>
<%} %>
</ul>
<%} %>
</body>
</html>
- 其他数据库工具类
package com.yueqian.store.common;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;
public class DBUtils {
private static final String URL = "jdbc:mysql://127.0.0.1:3306/store?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false";
private static final String USERNAME = "root";
private static final String PASSWORD = "xxx";
private static BasicDataSource ds = null;
public static void init() {
// 加载驱动
try {
// 方式一
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
// 方式二 根据传入字符串形式的类名加载该类 (加载类时调用静态代码块加载驱动)
// Class.forName("com.mysql.cj.jdbc.Driver()");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 创建数据库连接池
ds = new BasicDataSource();
// 设置连接信息
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setUrl(URL);
ds.setUsername(USERNAME);
ds.setPassword(PASSWORD);
// 设置连接池信息
// 最大空闲连接数
ds.setMaxIdle(30);
// 最小空闲连接数
ds.setMinIdle(2);
// 设置初始连接数
ds.setInitialSize(2);
// 创建连接时最大等待时间
ds.setMaxWaitMillis(4000);// 毫秒
// 从数据源中拿到的连接,关闭其自动提交的事务
ds.setDefaultAutoCommit(false);
}
// 定义连接保存在ThreadLocal类中,将当前线程对象作为key,保存连接conn对象,避免多线程访问业务层和dao层导致使用的conn连接不一致问题
// service里绑定每个线程对象的连接,调用的dao层获取该处理线程对象的连接
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
/**
* 连接数据库
*
* @return
*/
public static Connection getConnection() {
//先从threadLocal中获得连接,threadLocal类似map存放,ThreadLocal中是以键为当前线程对象,值为conn连接存放的。
Connection conn = threadLocal.get();
//如果连接为null,从连接池中获取连接
if (conn == null) {
try {
conn = ds.getConnection();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//将得到的连接设置到threadLocal中
threadLocal.set(conn);
}
return conn;
}
/**
* 关闭连接
*
* @param rs
* @param stmt
* @param conn
*/
public static void close(ResultSet rs, Statement stmt) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
// 提交事务
public static void commit() {
Connection conn = threadLocal.get();
if(conn != null){
try {
conn.commit();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
//关闭连接
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//清理当前线程所绑定的连接
threadLocal.remove();
}
}
}
// 回滚事务
public static void rollback() {
Connection conn = threadLocal.get();
if(conn != null){
try {
conn.rollback();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
//关闭连接
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//清理当前线程所绑定的连接
threadLocal.remove();
}
}
}
/**
* 关闭数据库连接池
*/
public static void closeDataSourse() {
if(ds != null) {
try {
ds.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
传统方式实现文件的快速复制
方式一:
InputStream is = item.getInputStream(); // 获取文件输入流
FileOutputStream fos = new FileOutputStream(new File(uploadDir, fileId)); // 获取文件输出流
byte[] bytes = new byte[1024];
int len = 0;
while (-1 != (len = is.read(bytes))) {
fos.write(bytes, 0, len);
}
fos.close();
is.close();
具体文件操作请参考另一篇io博客:juejin.cn/post/684490…
方式二:使用Commons io组件文件工具类快速实现文件的复制
// 将文件输入流保存到文件(工具栏实现)
FileUtils.copyInputStreamToFile(is, new File(uploadDir,fileName));
// 将文件保存到输出流中
FileUtils.copyFile(file, resp.getOutputStream());
这个项目中我们学到了文件上传与复制,采用两种方式实现,既不要忘了IO的基本操作,也要使用快速复制文件的工具类。get到的小伙伴点赞👍+关注吧!!!