文件上传与下载

一、文件上传
1、客户端操作 ---- 提供文件上传输入项
upload.jsp 提供form 表单 
注意:
1) input file 必须含有name 属性
2) form 必须要以 post方式提交 
3) 设置form表单 编码类型 multipart/form-data  (默认 application/x-www-form-urlencoded)


application/x-www-form-urlencoded 格式 : key=value&key=value&key=value....


2、服务器操作 ---- 需要程序,解析上传的文件内容
request 提供 getInputStream ,通过该方法可以获得 HTTP请求体内容  -------- 完全可以手动解析请求体 ,完成文件上传 (麻烦)


文件上传开发框架 Apache 提供 commons-fileupload 框架 ,和 jsp-smartupload 起名 


JSP model1 完成文件上传,都使用 jsp-smartupload 组件 
JSP model2 以后 ,完成文件上传,都使用commons-fileupload 组件  ---- Struts框架默认文件上传引擎都是 commons-fileupload


Servlet3.0 规范中,提供文件上传的支持 


二、使用commons-fileupload 进行开发 
1、下载jar包 commons-fileupload、commons-io  (upload 依赖 io )
2、编程实现 
创建DiskFileItemFactory 工厂
创建 解析核心类 ServletFileUpload 
解析request 获得 FileItem 的 List集合
遍历集合 获得每个 FileItem , 判断是否为文件上传 isFormField  ---- true 不是文件上传 false 是文件上传 


普通域 : getFieldName 获得name属性   getString 获得value属性
上传域 : getName 获得文件名称  getInputStream 获得上传文件内容 


 
注意问题 :
1、如果用户 没有上传文件 ---- 判断用户是否上传文件 
if (filename == null || filename.trim().length() == 0) {
throw new RuntimeException("用户没有上传文件!");
}
2、早期浏览器 ,在上传文件时,服务器获得客户端完整文件路径 ,而不只是文件名 
int index = filename.lastIndexOf("\\");
if (index != -1) {
// 找到了 \\
filename = filename.substring(index + 1);
}


三、 fileupload  API详解 
1、DiskFileItemFactory 
public void setSizeThreshold(int sizeThreshold) 设置内存缓存区大小
public void setRepository(java.io.File repository) 设置临时文件存放位置


上传文件时,内存一定要存在缓存区 ,默认10K ,小于10K的文件,保存在内存缓冲区 足够了,不会产生临时文件 (内存缓冲区 ---- 目标文件)
上传文件 大于 内存缓存区,产生临时文件 保存  repository 目录 (内存缓冲区 ---- 临时文件 ---- 目标文件 ) 好处 确保目标文件完整性 


临时文件存在上传后删除问题 ,通过fileItem.delete 在上传后删除 


2、ServletFileUpload 
static boolean isMultipartContent(javax.servlet.http.HttpServletRequest request) 判断form 是否为multipart/form-data 编码类型 
List parseRequest(javax.servlet.http.HttpServletRequest request) 解析request 获得 List<FileItem> 


void setFileSizeMax(long fileSizeMax) 设置单个文件大小  / void  setSizeMax(long sizeMax) 设置请求总大小


void setHeaderEncoding(java.lang.String encoding)  解决上传文件名 乱码问题  ***** 


void setProgressListener(ProgressListener pListener) 设置文件上传进度监听,获得文件上传进度信息 


3、FileItem  代表请求中一个数据部分 
boolean isFormField 判断是否为文件上传项,true 不是文件上传 false 是文件上传 


普通项:getFieldName 获得name属性  getString 获得value 属性 ------ value乱码 getString("utf-8")
*** 因为文件上传 采用multipart/form-data 而不是传统url编码,所有getParameter setCharacterEncoding 都将没有效果 


上传项:getName 文件名  getInputStream 文件内容流  delete 删除临时文件 


四、 上传文件保存问题 
1、安全问题 
如果上传文件 直接存放WebRoot目录下 (或者除 WEB-INF、META-INF 其它子目录下) ----- 客户端可以通过URL 直接访问   (例如:商品中商品图片,新闻图片)
如果上传文件,不想让用户可以直接通过url访问,将上传文件 保存 WebRoot/WEB-INF 或者 不受tomcat服务器管理的目录 (d:\file) --- 必须通过服务器端程序才能访问(安全)


2、同一个目录下同名文件,产生上传覆盖效果  ,后上传文件会覆盖之前同名文件 
UUID 保证文件名唯一 


3、同一目录下文件数量过多问题  --- 采用目录分离算法 
按用户分离目录、按照时间分离(月份)、按照固定文件数量分离 ------ 按照hashcode 编码分离 


五、文件上传的进度监控
ProgressListener 监控文件上传的进度 ,监听器习惯上通过匿名内部类设置 


pBytesRead 已经上传大小
pContentLength 总大小
pItems 当前是form第几个元素 


文件上传进度条 --- 一定要使用 AJAX 
原理:ProgressListener  将文件上传进度 保存Session、Application 范围, 通过另一个程序获得进度,在页面显示进度条 


速度 = 已经上传大小/已用时间 
剩余时间 = 剩余大小/速度 


===================================================================================================================================================
六、文件下载 两种方式
不需要引入第三方jar包,只需要通过Servlet 程序将文件内容,用response输出流 写到客户端 


方式一:通过超链接 下载文件 
浏览器识别的格式 会直接打开 , 不识别格式弹出下载 窗口  ---- 缺省Servlet (org.apache.catalina.servlets.DefaultServlet)


方式二:通过Servlet程序 下载文件
不管浏览器是否识别文件格式,都要弹出下载窗口 


将文件内容用response输出流写出,还需要设置两个头信息 Content-Type  Content-Disposition 
ServletContext 接口中 提供方法 getMimeType(filename) 根据文件名 获得 MIME 类型 


response.setContentType(getServletContext().getMimeType(filename));
response.setHeader("Content-Disposition", "attachment;filename="+ filename);


七、文件下载综合案例 
服务器端指定目录下方的所有文件的下载 


1、显示目录下所有文件 ----- 树的遍历 
2、点击文件 进行下载  ---- 存在下载文件名乱码 问题 
IE(及其它浏览器) 显示中文 下载附件 ---- 需要 url编码 
火狐 显示中文 下载附件 --- 需要 BASE64 编码 
if (agent.contains("Firefox")) {
// 火狐浏览器
filename = "=?utf-8?B?"
+ new BASE64Encoder().encode(filename.getBytes("utf-8"))
+ "?=";
} else {
// IE 及其他浏览器
filename = URLEncoder.encode(filename, "utf-8");
filename = filename.replace("+", " ");
}


重点:1) 广度非递归遍历 2) c:url 对中文参数进行编码 3) 下载附件时中文附件名 编码问题 




八、内省的使用 
内省 是一套 JDK提供 用于操作JavaBean对象 ,基于反射 API 


1、通过Introspector 获得 BeanInfo
2、通过BeanInfo 获得 Class对应 属性描述器 PropertyDescriptor 
属性 由getter 和setter 方法确定 


例如 getName()  ---- 存在name 属性、 setAge() --- 存放age属性


3、PropertyDescriptor 获得 write和read 的method对象 


<jsp:setProperty property="*" /> 自动将请求数据 封装对应 java对象属性中 


想在Servlet中 实现 <jsp:setProperty property="*" /> 相同效果 ----- 自己实现内省太麻烦 (使用 Apache 提供 BeanUtils)




使用BeanUtils
1) 下载 jar包 beanutils  logging 
2) BeanUtils.populate(person, request.getParameterMap());


============================================================================================================================
九、支持上传和下载 
1、允许用户上传文件 (需要将文件相关信息保存到数据库)
2、查看上传文件列表
3、提供用户下载


数据库、工程、导入jar、配置文件 


create database day20;
use day20;


create table resources(
   id int primary key auto_increment,
   uuidname varchar(60), 
   realname varchar(40),
   savepath varchar(100),
   uploadtime timestamp,
   description varchar(255)
);


private int id;
private String uuidname;  //上传文件的名称,文件的uuid名
private String realname; //上传文件的真实名称
private String savepath;     //记住文件的位置
private Timestamp uploadtime;     //文件的上传时间
private String description;  //文件的描述


导入 jar : beanutils 、c3p0、dbutils、mysql驱动 、jstl 、fileupload 


cn.itcast.servlet
cn.itcast.domain 


========================================================================================
内容小结 :
1、综合 上传下载案例 
2、指定目录文件下载 (文件目录 遍历、c:url编码参数、不同浏览器编码问题)
3、文件保存问题 (安全路径、唯一文件名、随机目录)


BeanUtils使用 

DownloadListServlet.java

package cn.itcast.servlet;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import sun.misc.BASE64Encoder;

public class DownloadListServlet extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// 获得文件 绝对路径
		String path = request.getParameter("path");
		// get方式乱码
		path = new String(path.getBytes("ISO-8859-1"), "utf-8");
		System.out.println(path);

		// 等价于
		// path = URLEncoder.encode(path, "ISO-8859-1");
		// path = URLDecoder.decode(path, "utf-8");

		// 截取文件名
		String filename = path.substring(path.lastIndexOf("\\") + 1);

		// 设置ContentType
		response.setContentType(getServletContext().getMimeType(filename));

		// 设置ContentDisposition
		// 不同浏览器处理 附件乱码方式不同
		String agent = request.getHeader("user-agent");
		System.out.println(agent);
		if (agent.contains("Firefox")) {
			// 火狐浏览器
			filename = "=?utf-8?B?"
					+ new BASE64Encoder().encode(filename.getBytes("utf-8"))
					+ "?=";
		} else {
			// IE 及其他浏览器
			filename = URLEncoder.encode(filename, "utf-8");
			filename = filename.replace("+", " ");
		}
		response.setHeader("Content-Disposition", "attachment;filename="
				+ filename);

		// 流拷贝
		InputStream in = new BufferedInputStream(new FileInputStream(path));
		OutputStream out = response.getOutputStream();
		int b;
		while ((b = in.read()) != -1) {
			out.write(b);
		}
		in.close();
		out.close();
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doGet(request, response);
	}

}
DownloadServlet.java
package cn.itcast.servlet;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 完成文件下载
 * 
 * @author seawind
 * 
 */
public class DownloadServlet extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// 从请求参数中 获得文件名
		String filename = request.getParameter("filename");
		// 判断文件是否存在
		File file = new File(getServletContext().getRealPath("/download"),
				filename);
		if (file.exists() && file.isFile()) {
			// 设置文件类型
			response.setContentType(getServletContext().getMimeType(filename)); // tomcat/conf/web.xml
			// 设置段体的安排方式 inline 、attachment
			response.setHeader("Content-Disposition", "attachment;filename="
					+ filename);

			// 存在文件
			InputStream in = new BufferedInputStream(new FileInputStream(file));
			OutputStream out = response.getOutputStream();
			int b;
			while ((b = in.read()) != -1) {
				out.write(b);
			}
			in.close();
			out.close();
		} else {
			// 不存在
			throw new RuntimeException("下载的文件不存在!");
		}

	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doGet(request, response);
	}

}
UploadServlet.java
package cn.itcast.servlet;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.UUID;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import cn.itcast.utils.UploadUtils;

/**
 * 文件上传
 * 
 * @author seawind
 * 
 */
public class UploadServlet extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		System.out.println("文件上传 ....");
		// 将请求体 内容打印到 控制台
		// InputStream in = request.getInputStream();
		// int b;
		// while ((b = in.read()) != -1) {
		// System.out.write(b);
		// }
		// System.out.flush();
		// in.close();

		// 完成文件上传
		if (!ServletFileUpload.isMultipartContent(request)) {
			throw new RuntimeException("表单编码类型错误!不是multipart/form-data");
		}

		// 步骤一 创建文件项 工厂
		DiskFileItemFactory factory = new DiskFileItemFactory();
		// 5MB 小于5MB 不会产生临时文件 ,超过5MB才会产生临时文件
		factory.setSizeThreshold(1024 * 1024 * 5);
		// 设置临时文件区 tmp
		factory
				.setRepository(new File(getServletContext().getRealPath("/tmp")));

		// 步骤二 通过工厂 获得 解析核心类 ServletFileUpload
		ServletFileUpload fileUpload = new ServletFileUpload(factory);

		// 设置上传文件不能超过20M
		fileUpload.setFileSizeMax(1024 * 1024 * 20);
		// 解决上传文件名 乱码
		fileUpload.setHeaderEncoding("utf-8");

		// 获得开始上传的时间
		final long start = System.currentTimeMillis();

		// 设置监听器,监控文件上传进度
		fileUpload.setProgressListener(new ProgressListener() {
			@Override
			// pBytesRead 已经上传字节数量
			// pContentLength 上传文件总大小
			// pItems 当前上传文件,是表单的第几个元素
			public void update(long pBytesRead, long pContentLength, int pItems) {
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				if (pBytesRead != 0) {
					System.out.println("上传文件总大小:" + pContentLength + ",已经上传大小:"
							+ pBytesRead + ",当前上传文件是表单第" + pItems + "个元素");
					long now = System.currentTimeMillis();// 当前时间
					long hasUseTime = now - start; // 已用时间

					// 速度
					double speed = ((double) pBytesRead) / hasUseTime; // 单位字节/毫秒

					// 剩余时间
					long restTime = (long) ((pContentLength - pBytesRead) / speed);

					System.out.println("上传速度:" + speed + "KB/S, 剩余时间:"
							+ restTime + "毫秒");
				}
			}
		});

		// 步骤三 解析request
		try {
			List<FileItem> fileItems = fileUpload.parseRequest(request);
			// 步骤四 遍历集合
			for (FileItem fileItem : fileItems) {
				// 判断哪个 fileItem 是文件上传
				if (fileItem.isFormField()) {
					// 不是文件上传
					// 获得 name 和 value
					String name = fileItem.getFieldName();
					String value = fileItem.getString("utf-8");
					System.out.println("这是 普通域  name:" + name + ", value:"
							+ value);
				} else {
					// 是文件上传
					// 获得文件名称
					String filename = fileItem.getName();

					// 判断用户是否上传文件
					if (filename == null || filename.trim().length() == 0) {
						throw new RuntimeException("用户没有上传文件!");
					}
					// 解决早期 浏览器提交客户端文件路径问题,从最后一个\\ 切割
					int index = filename.lastIndexOf("\\");
					if (index != -1) {
						// 找到了 \\
						filename = filename.substring(index + 1);
					}

					System.out.println("上传文件 :" + filename);

					// 获得文件内容
					InputStream in = new BufferedInputStream(fileItem
							.getInputStream());

					// 保证文件名唯一
					filename = UUID.randomUUID() + "_" + filename;

					// 将文件保存到服务器端
					String path = getServletContext().getRealPath("/upload");
					String ramdonDir = UploadUtils.generateRandonDir(filename);
					File dirFile = new File(path + ramdonDir);// 目录不存在
					dirFile.mkdirs();// 创建

					OutputStream out = new BufferedOutputStream(
							new FileOutputStream(new File(dirFile, filename)));

					// 流拷贝
					int b;
					while ((b = in.read()) != -1) {
						out.write(b);
					}

					in.close();
					out.close();

					// 删除临时文件
					fileItem.delete();
				}
			}
		} catch (FileUploadException e) {
			e.printStackTrace();
		}
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doGet(request, response);
	}

	public static void main(String[] args) {
		System.out.println(System.getProperty("java.io.tmpdir"));
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值