[TOC]
文件上传技术
- Servlet3.0
- JSPSmartUpload:嵌入到JSP中完成文件上传.主要用于Model1年代的.
- FileUpload:Apache的文件上传组件.
- Struts2:底层是FileUpload.
文件上传的要素
- 表单的提交的方式必须是POST.
- 表单中需要有文件上传的表单元素:这个元素这个元素必须有name属性和值
<input type=”file” name=”upload”>
- 表单的enctype属性的值必须是
multipart/form-data
.
一、文件上传原理分析
没有设置enctype属性的时候:只能获得文件的名称,而没有文件内容,如下图
二、上传方式
使用Servlet3.0实现文件上传
代码如下
<body>
<h1>文件上传的页面</h1>
<!--
* 表单的提交的方式必须是POST.
* 表单中需要有文件上传的表单元素:这个元素这个元素必须有name属性和值:<input type=”file” name=”upload”>
* 表单的enctype属性的值必须是multipart/form-data.
-->
<form action="${ pageContext.request.contextPath }/UploadServlet" method="post" enctype="multipart/form-data">
<table border="1" width="600">
<tr>
<td>文件描述</td>
<td><input type="text" name="filedesc"></td>
</tr>
<tr>
<td>文件上传</td>
<td><input type="file" name="upload"></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="上传"></td>
</tr>
</table>
</form>
</body>
复制代码
/**
* 文件上传的Servlet
*/
@WebServlet("/UploadServlet")
@MultipartConfig
public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 接收普通数据:
request.setCharacterEncoding("UTF-8");
String filedesc = request.getParameter("filedesc");
System.out.println("文件描述"+filedesc);
// 接收文件:
Part part = request.getPart("upload");
long size = part.getSize();// 获得文件大小:
System.out.println("文件大小:"+size);
String name = part.getName();
System.out.println("文件表单中的name属性的名称"+name);
// 获得文件名:
String header = part.getHeader("Content-Disposition");
int idx = header.lastIndexOf("filename=\"");
String fileName = header.substring(idx+10, header.length()-1);
//IE浏览器不行,加上以下代码
//String[] split = fileName.split("//");
//fileName = split[split.length-1];
System.out.println("文件名:"+fileName);
// 获得文件内容:
InputStream is = part.getInputStream();
// 获得upload的路径:
//项目根路径下不安全,最好存到web-inf目录下
String path = this.getServletContext().getRealPath("/WEB-INF/upload");
// 获得文件的唯一文件名:
String uuidFileName = UUIDUtils.getUUIDFileName(fileName);
String realPath = path+UploadUtils.getPath(uuidFileName);
File file = new File(realPath);
if(!file.exists()){
file.mkdirs();
}
OutputStream os = new FileOutputStream(realPath+"/"+uuidFileName);
byte[] b = new byte[1024];
int len = 0;
while((len = is.read(b))!=-1){
os.write(b, 0, len);
}
is.close();
os.close();
}
复制代码
使用fileUpload实现文件上传
/*
文件的上传:
* 三个要素:
* 提交的方式是POST:
* 表单中需要有<input type=”file” name=”upload”>
* enctype=”multipart/form-data”设置后request.getParamater()就不能用了。
* 文件上传的技术:
* Servlet3.0
* JSPSmartUpload
* FileUpload: 上传文件大小有限制
* commons-fileupload-1.2.1.jar
* commons-io-1.4.jar
* Struts2
* 使用FileUpload的时候:
* 获得磁盘文件工厂对象:
* 通过工厂获得核心解析类:
* 解析request对象 , 返回集合,集合中的内容是分割线分成的每个部分.
* 遍历每个部分:
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
// 创建一个Product的对象:
Product product = new Product();
// 创建磁盘文件项工厂.
DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
// 设置缓存区的大小: 如果文件的大小超过了缓冲区的大小,就会产生临时文件.
diskFileItemFactory.setSizeThreshold(3 * 1024 * 1024);
// 设置临时文件存放的路径:
// diskFileItemFactory.setRepository(repository);
// 获得核心解析类:ServletFileUpload
ServletFileUpload fileUpload = new ServletFileUpload(diskFileItemFactory);
fileUpload.setHeaderEncoding("UTF-8");// 解决中文文件名上传乱码.
// fileUpload.setFileSizeMax(fileSizeMax); // 设置单个文件大小
// fileUpload.setSizeMax(sizeMax); // 设置表单中的所有文件项的文件总大小
// 解析request 返回List集合
List<FileItem> list = fileUpload.parseRequest(request);
// 获得每个部分:
// 将遍历的值存入到一个Map集合中:
Map<String,String> map = new HashMap<String,String>();
String fileName=null;
for (FileItem fileItem : list) {
// 判断普通项和文件上传项:
if(fileItem.isFormField()){
// 普通项
String name = fileItem.getFieldName();
String value = fileItem.getString("UTF-8"); // 解决的是普通项的中文乱码.
System.out.println(name+" "+value);
map.put(name, value);
}else{
// 文件上传项
// 获得文件名:
fileName = fileItem.getName();
System.out.println("文件名:"+fileName);
// 获得文件的输入流:
InputStream is = fileItem.getInputStream();
// 获得文件要上传的路径:
String path = this.getServletContext().getRealPath("/products/1");
OutputStream os = new FileOutputStream(path+"/"+fileName);
int length = 0;
byte[] b= new byte[1024];
while((length = is.read(b))!=-1){
os.write(b, 0, length);
}
is.close();
os.close();
}
}
// 封装数据:
BeanUtils.populate(product, map);
product.setPid(UUIDUtils.getUUID());
product.setPdate(new Date());
product.setPflag(0);
product.setPimage("products/1/"+fileName);
Category category = new Category();
category.setCid(map.get("cid"));
product.setCategory(category);
// 存入到数据库:
ProductService productService = (ProductService) BeanFactory.getBean("productService");
productService.save(product);
// 页面跳转:
response.sendRedirect(request.getContextPath()+"/AdminProductServlet?method=findByPage&currPage=1");
} catch (Exception e) {
e.printStackTrace();
}
}
复制代码
Struts2文件上传
浏览器端注意事项
表单提交方式method=post 表单中必须有一个组件 表单中必须设置enctype=”multipart/form-data”
服务器端
Commons-fileupoad.jar包完成。
Struts2框架使用一个fileupload的interceptor来完成文件上传
上传案例
<form action="${ pageContext.request.contextPath }/upMany" enctype="multipart/form-data" method="post">
<input type="file" name="upload">
<input type="file" name="upload">
<input type="submit" value="上传">
</form>
复制代码
public class UploadActionMany extends ActionSupport{
// extends ActionSupport
private File[] upload;
private String[] uploadContentType;
private String[] uploadFileName;
/*
提供对应的set/get方法
*/
public String upload(){
System.out.println(3);
String path = ServletActionContext.getServletContext().getRealPath("/upload");
try {
for (int i = 0; i < upload.length; i++) {
File file = new File(path,uploadFileName[i]);
FileUtils.copyFile(upload[i], file);
}
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(4);
return null;
}
}
复制代码
注意事项
<!-- 开发模式 -->
<constant name="struts.devMode" value="true"></constant>
<!-- 设置文件上传的大小限制 -->
<constant name="struts.multipart.maxSize" value="40971520"></constant>
<action name="upMany" class="com.lbb.struts2.action.UploadActionMany" method="upload">
<!-- 文件上传出错后的视图 -->
<result name="input">/error.jsp</result>
<interceptor-ref name="fileUpload">
<!-- <param name="maximumSize"></param> --><!-- 设置每一个文件的单独的上传大小 -->
<!-- <param name="allowedTypes"></param> --><!-- 文件的mime类型 -->
<param name="allowedExtensions">txt,jpg,bmp</param><!-- 设置允许的后缀名 -->
</interceptor-ref>
<interceptor-ref name="defaultStack"></interceptor-ref>
</action>
复制代码
input接受错误信息
<s:actionerror/>
<s:fielderror/>
复制代码
三、文件上传问题
文件重名
使用UUID生成随机的唯一文件名
同一目录下文件过多
一个目录下文件过多,导致打开都很慢,更别说是读写操作. 目录分离方案
- 按时间分:一个月一个目录,一个星期一个目录,一天一个目录
- 按数量分:一个目录下存5000个文件,创建一个新的目录,再去存放
- 按用户分:为每个用户创建一个单独目录 存放文件
- 按目录分离算法分 使用唯一文件名.hashCode(); -- 得到一个代表当前这个文件的int类型值. int类型占4个字节32位.可以让hashCode值&0xf; 得到一个int值,用这个int值作为一级目录. 让hashCode右移4位 &0xf ;得到一个int值,用这个int值作为二级目录.依次类推.
public class UploadUtils {
public static String getPath(String uuidFileName){
// 使用唯一文件名.hashCode();
int code1 = uuidFileName.hashCode();
int d1 = code1 & 0xf; // 获得到1级目录.
int code2 = code1 >>> 4;
int d2 = code2 & 0xf; // 获得到2级目录.
return "/"+d1+"/"+d2;
}
}
复制代码
原理图如下
四、文件下载
文件下载的方式
一种:超链接下载.直接将文件的路径写到超链接的href中.---前提:文件类型,浏览器不支持. 二种:手动编写代码的方式完成文件的下载. 设置两个头和一个流: Content-Type:文件的MIME的类型. Content-Disposition :以下载的形式打开文件. InputStream:文件的输入流.
<body>
<h1>文件下载的列表页面</h1>
<h3>超链接的下载</h3>
<a href="/day10/download/hello.txt">hello.txt</a><br/>
<a href="/day10/download/cs10001.jpg">cs10001.jpg</a><br/>
<a href="/day10/download/hello.zip">hello.zip</a><br/>
<h3>手动编码方式下载</h3>
<a href="/day10/DownloadServlet?filename=hello.txt">hello.txt</a><br/>
<a href="/day10/DownloadServlet?filename=cs10001.jpg">cs10001.jpg</a><br/>
<a href="/day10/DownloadServlet?filename=hello.zip">hello.zip</a><br/>
<a href="/day10/DownloadServlet?filename=美女.jpg">美女.jpg</a><br/>
</body>
复制代码
IE浏览器下载中文文件的时候采用的URL的编码.
Firefox浏览器下载中文文件的时候采用的是Base64的编码.
/**
* 文件下载的Servlet
*/
public class DownloadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1.接收参数
String filename = new String(request.getParameter("filename").getBytes("ISO-8859-1"),"UTF-8");
System.out.println(filename);
// 2.完成文件下载:
// 2.1设置Content-Type头
String type = this.getServletContext().getMimeType(filename);
response.setHeader("Content-Type", type);
// 2.3设置文件的InputStream.
String realPath = this.getServletContext().getRealPath("/download/"+filename);
// 根据浏览器的类型处理中文文件的乱码问题:
String agent = request.getHeader("User-Agent");
System.out.println(agent);
if(agent.contains("Firefox")){
filename = base64EncodeFileName(filename);
}else{
filename = URLEncoder.encode(filename,"UTF-8");
}
// 2.2设置Content-Disposition头
response.setHeader("Content-Disposition", "attachment;filename="+filename);
InputStream is = new FileInputStream(realPath);
// 获得response的输出流:
OutputStream os = response.getOutputStream();
int len = 0;
byte[] b = new byte[1024];
while((len = is.read(b))!= -1){
os.write(b, 0, len);
}
is.close();
}
public static String base64EncodeFileName(String fileName) {
BASE64Encoder base64Encoder = new BASE64Encoder();
try {
return "=?UTF-8?B?"
+ new String(base64Encoder.encode(fileName
.getBytes("UTF-8"))) + "?=";
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
复制代码