1、文件上传我们用的是Apache的common组件,里面有个fileupload的jar包,因为用到输出输入流,所以需要导入io的jar包。
——要上传文件,form表单里面需要增加enctype属性,并设置为multipart/form-data
。
——我们之前用request.getParameter
来获取form表单里面的值,因为是字符输入流,但是如果设置成上面那样的话,就取不到值了,因为变成字节输入流了。
——所以本质上是我们把所有的内容当做字节输入流获取到,然后再用字节输出流来输出。
——核心代码是:
// Create a factory for disk-based file items
DiskFileItemFactory factory=new DiskFileItemFactory();
// Create a new file upload handler
ServletFileUpload upload=new ServletFileUpload(factory);
try {
List items=upload.parseRequest(request);
for(FileItem item : items){
if(item.isFormField()){
// 普通表单
}else{
// 文件表单
InputStream is=item.getInputStream();
FileOutputStream fos=new FileOutputStream(file);
int len=0;
byte[] b=new byte[1024];
while((len=is.read(b))!=-1){
fos.write(b, 0, len);
}
}
}
} catch (FileUploadException e) {
e.printStackTrace();
}
——需要注意的是,我们存放文件路径的目录最好是在WEB-INF里面,用户无法访问到的路径,否则如果用户上传了一个存有代码的文件,然后访问这个文件相当于执行里面的代码,后果就糟糕了。
——另一个,我们对于文件的存放最好是分流一下,按照日期等方式,本案例中是按照upload/year/month/day/
来存放的。
——还有一个,我们最好修改一下文件名,一个是防止重名,另一个是防止文件名里面有中文字符,引起不必要的麻烦。
——获取filename的时候,在IE等个别浏览器下,获取的是文件的路径,而不是文件名。所以我们需要处理一下这个文件名。如下就是处理的方法。
String filename=item.getName();
if(filename!=null){
filename=FilenameUtils.getName(filename);
}
——另一个要养成的习惯,就是设置上传文件的大小,可以用ServletFileUpload.setFileSizeMax
设置单个文件的限制,或者ServletFileUpload.setSizeMax
设置总的上传大小。设置了大小之后,就需要捕获这个异常,然后处理这个异常,也就是返回错误信息给用户。
——判断文件名是否为空,可以判断yoghurt是否上传了文件。
——DiskFileItemFactory上传时有一个默认缓存,大小是10K,超过10K,就用磁盘作为缓存,存放缓存的目录默认是系统临时目录。当然,你也可以利用factory.setRepository()
来设置缓存目录。
——最后一点,我们在截图里面没有写的代码,就是关闭io流。
fos.close();
is.close();
案例源代码:JavaEE 文件上传代码示例
2、文件上传知识点补充。
——上面说到,我们用的是如果是文件对象,那我们就先获取输入流,然后拼接好文件路径后,用输出流写到文件里面,这就是上传的原理。但是如果用输入输出流的话,我们就需要手动关闭。这里我们可以用另一个FileItem自带的write方法来写入文件。
File file=new File(directory,filename);
try {
item.write(file);
} catch (Exception e) {
e.printStackTrace();
}
// 删除缓存文件
item.delete();
——还有一个头疼的问题就是解决文件名里面中文字符的问题。解决文件的中文名用以下两种方法都行,但第二种优先级高一些。
// 这是我们之前一直用的
request.setCharacterEncoding("UTF-8");
// 这是设置了ServletFileUpload
ServletFileUpload upload=new ServletFileUpload(factory);
upload.setHeaderEncoding("UTF-8");
——那么普通表单的中文字符还是new String方法来解决?当然可以,如下。
new String(item.getString().getBytes("iso-8859-1"),"UTF-8")
但是,FileItem也为我们提供了一个快捷的方式,getString是可以加编码参数的,如下:
// 普通表单
System.out.println(item.getFieldName()+":"+item.getString("UTF-8"));
3、动态添加删除上传按钮。input需要增加name属性,否则取不到值。
<head>
<script type="text/javascript">
function addBtn(){
var d=document.getElementById("newDiv");
d.innerHTML+="文件:";
}
function delBtn(input){
input.parentNode.parentNode.removeChild(input.parentNode);
}
script>
head>
<body>
<form action="${pageContext.request.contextPath }/servlet/FileUploadServlet" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username"/><br>
<div>
文件:<input type="file" name="avatar" /><input type="button" value="添加" onclick="addBtn()" /><br>
div>
<div id="newDiv">
div>
<input type="submit" value="提交"/>
form>
body>
4、下载案例。
——里面需要注意的是中文字符的处理。一个是针对不同浏览器中文文件名的处理。还有一个就是输出内容里面的中文处理。见下面代码。
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//先整一个文件
String filename="销售排行.csv";
// 文件名中文的处理,针对不同浏览器的不同处理
if(request.getHeader("user-agent").toLowerCase().contains("msie")){
filename=URLEncoder.encode(filename, "UTF-8");
}else{
filename=new String(filename.getBytes("UTF-8"),"iso-8859-1");
}
// 告诉浏览器是下载,而不是在浏览器中打开
response.setHeader("content-disposition", "attachment;filename="+filename);
// 下面这种方法,比直接用response.setHeader("content-type",MIME)要好,因为我们一般不记得MINE,下面是根据文件名得到MIME
response.setContentType(this.getServletContext().getMimeType(filename));
// 告诉浏览器的编码格式
response.setCharacterEncoding("UTF-8");
// 整一个输出流和内容(输出到文件中的)
PrintWriter out=response.getWriter();
out.write("美的,2000\n");
out.write("格力,1800\n");
out.write("三星,1400\n");
out.write("苹果,800\n");
out.write("魅族,600\n");
}
5、注意点。
——我们之前在上传的时候,把文件上传到WEB-INF里面了,是保证了安全,但是后续我们自己再使用的时候也不是很方便,所以还是在一般的upload或者media文件夹里就可以了。那么就需要我们在用户上传的时候做一些后缀判断之类的操作,以降低风险。
String extension=FilenameUtils.getExtension(filename);
if("jsp".equal(extension)||"exe".equal(extension)){
……
}
6、实际开发中的逻辑思路。比如在后台新增一本书,除了一些表单填写我啊,需要上传封面图片。
——我们在新增图书的servlet中,自己新建一个Map
,然后FileItem循环判断如果是普通表单的话就把name和value一起put进这个Map,如果是文件上传表单的话,把name和文件路径put到这个Map。最后利用BeanUtils把Map里面的数据都映射到book类中。最后再调用其他业务逻辑,把这个book传递出去。
Book book=new Book();
BeanUtils.populate(book,Map);