JavaWeb---文件上传与下载

283 阅读10分钟

今天我们来学习下文件的上传与下载吧,方便我们以后项目中使用哦!

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到的小伙伴点赞👍+关注吧!!!