1月23日——培训第53天

今天讲文件的上传……

<form action="fileUploadServlet" enctype="multipart/form-data" method="post">
//文件上传的时候必须指明encodingType,指明按照表单数据装成多个部分,由于文件大小可能比较大,所以提交的方式必须是post!
 名称: <input type="text" name="name"><br>
 文件: <input type="file" name="afile"/>
 <input type="submit" />
</form>

上传的时候要把客户端的东西变成字节流传到服务器那一边,然后服务器接收到字节流之后再写到
自己的硬盘里面去!

但是问题是如何区别上述两个表单区域呢?怎样区分text和file??
如果一次性的上传4、5个文件,这些文件之间的边界如何区分??
应该在每一个参数的前后加上标识……来标识一个域

纯粹用JSP做文件上传是很困难的:

如果使用一个FileUploadServlet来接收表单的话,
public class FileUploadServlet extends HttpServlet
{
 public void doPost(HttpServletRequest request,HttpServletResponse response)
  throws ServletException,IOException
 {
  Enumeration em = request.getHeaderNames() ;//将所有的请求报头拿出来
  while(em.hasMoreElements())
  {
   String name = (String)em.nextElements() ;
   System.out.println(name + " : " + request.getHeader(name));
  }
  //拿出了请求的报头后还得拿到请求体,需要从请求中得到InputStream或者InputReader
  InputStream in = request.getInputStream() ; //得到请求的输入流
  BufferedReader reader = new BufferedReader(new InputStreamReader(in));
  String s = null ;
  while((s=reader.readLine())!=null) //读出请求流中的每一行!
  {
   System.out.println(s) ;
  }

 

 

  
 }
}

这个Servlet的目的是为了观察上面表单页面中提交过来的请求的所有信息,从而分析出上传文件所需要的。
报头中的content-type里面有个boundary,这是浏览器生成的,用来分割不同的表单区域信息……

可以在控制台里面看到text表单和file表单被这个boundary给分割开了,所以上传文件的时候完全可以通过
报头中的boundary得到分隔符,两个“-”加上boundary代表一个体的开始,如果两个“-”加上这个boundary,
后面又加上了两个“-”的话……就是最后结束了。

但是问题是如果你传递上来的是一个二进制文件的话……就是很难搞清楚的乱码了。
文件上传肯定得按照字节流去读取,不能用字符流!

我们做的只是纯文本的文件上传而已……


  String contentType = request.getContentType();//
  if(contentType==null && !contentType.startsWith("multipart"))
  {
   return ;
  }

  String boundary = contentType.substring(contentType.lastIndexOf("=")+1);

  InputStream in = request.getInputStream() ; //得到请求的输入流
  BufferedReader reader = new BufferedReader(new InputStreamReader(in));
  String s = null ;
  while((s=reader.readLine())!=null) //读出请求流中的每一行!
  {
   //boolean newPartBegin = false ;
   if(s.equals("--"+boundary))
   {
   
    newPartBegin = true ;
    continue ;
    
   }

   System.out.println(s) ;

  }

======================================================
如果使用apache开源项目的上传组件的话,事情就会相当的容易了!

在doPost方法中添加如下代码:

DiskFileItemFactory factory = new DiskFileItemFactory() ;
ServletFileUpload upload = new ServletFileUpload(factory) ;
upload.setHeaderEncoding("UTF-8");//注意这里的编码设置要和jsp页面的编码一致才不会出现乱码!
List items = upload.parseRequest(request) ;
Iterator it = items.iterator() ;

while(it.hasNext())
{
 FileItem item = (FileItem)it.next();
 if(!item.isFormField())
 {
  String filename = item.getName().substring(item.getName().lastIndexOf("//")+1);
  //得到文件的名字
  File file = new File("f://"+filename);
  item.write(file) ;
 }
}

===============================================================================
下午课程开始:

看看在Struts中是如何完成文件上传的……
用Struts进行文件上传的时候可以看到Struts中其实也提供了文件上传的组件commons-fileupload.jar
而且:
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html"%>

要使用html标签才可以……

//注意如果设置了enctype="multipart/form-data",那么再想从请求中用request.getParameter()
//的话,就做不到了。
<body>
 <html:form action="/upload" enctype="multipart/form-data" method="post">//文件可能很大,所以必须post提交
  <html:file property="file" />
  <html:submit/>
 </html:form>
</body>

一个html标签必须得对应一个formbean:
public class upload extends ActionForm
{
 private FormFile file ;

 getter和setter方法略
 
 public ActionErrors validate()
 {}

 public void reset()
 {}

}

新建一个Action://文件上传后还要写数据库,
//但是文件上传和写数据库应该放在一个事务里面去做,二者必须同时成功或同时失败才对。
//需要commons中的dbcp、pools和collections三个包,可以从李浩给的压缩包里面拿……当然别忘了还有数据库的包。
//因为需要配置数据源,所以需要昨天的数据源的配置,具体方法见昨天的日记…………
public ActionForward execute()
{
 String target = "failure" ;
 UploadForm uploadForm = (UploadForm)form ;//form是execute方法传入的参数
 FormFile file = uploadForm.getFile() ;

 String filename = rename(file.getFileName(),request) ;//重命名
 String path = "f://upload//" + filename ;

 FileOutputStream out = new FileOutputStream(path) ;

 InputStream in = file.getInputStream() ;

 //也就是从上面那个输入流里面读,然后再写到一个输出流里面去
 byte[] buf = new byte[512] ;
 int pos = -1 ;

 //如果正常返回那么读了多少就返回多少
 while((pos=in.read(buf,0,512))!=-1)//从buf(缓存)中的0读到512
 {
  out.write(buf,0,pos) ;
  out.flush();
 }
 
 registerData(request,file.getFileName(),path);//这个方法负责插入数据库。数据库的表设计:
 //建立一个新表files:字段有
 //id:Integer
 //filename:varchar(200)
 //path:varchar(300)
 //uploadTime:DATETIME
 //downloadTimes:Integer
 target ="success" ;
 if(out!=null)
 {
  out.close() ;
  out = null ;
 }
 if(in!=null)
 {
  in.close() ;
  in = null ;
 }

 return actionMapping.findForward(target) ;
}

//给文件重命名,可以在application中存储一个计数器,每次存储一个就加1,但是需要考虑同步。
protected String rename(String name,HttpServletRequest request)
{
 DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss") ;//格式化时间,注意如果这里写成小写的hh,那就成了12小时制了。
 String newName = df.format(new Date()) + request.getSession().getId();//取当前时间的日期字符串
 int position = name.lastIndexOf(".");
 if(position!=-1) //说明原文件有扩展名
 {
  newName = newName + name.substring(position) ;//也就是以日期字符串和sessionID相加为新文件名,扩展名为原文件扩展名
 }

 return newName ;

}

protected void registerData(HttpServletRequest request , String filename , String path)
{
 Connection conn = null ;
 PreparedStatement pstmt = null ;
 ResultSet rs = null ;

 DataSource ds = this.getDataSource(request) ;
 try
 {
  conn = ds.getConnection() ;
  pstmt = conn.prepareStatement("insert into files(filename,path,uploadTime,downloadTimes) values(?,?,?,0)");
  pstmt.setSring(1,filename) ;
  pstmt.setSring(2,path) ;
  pstmt.setTimestamp(3,new Timestamp(System.currentTimeMillis()));
  if(pstmt.executeUpdate()!=1)
  {
   throw new SQLException() ;
  }
 }
 catch(Exception e)
 {
  File f = new File(path);
  f.delete() ;//如果出现了上面抛出的异常,也就是数据库插入失败的话,在服务器上把上传上来的文件删除掉,当然不删也可以,
  //只要不插入数据库,那么文件对于客户端就是不可见的。
 }
 finally
 {
  //关闭连接
 }
}

//注意如果要避免中文的乱码的话首先记住在struts-config中的datasources元素配置中的url要加上characterEncoding=gbk这个东西

之后如果上传成功的话我们就通过一个Action来把数据库中文件都拿出来,然后转到ListFile.jsp中去。

所以插入数据库成功后先去一个.do地址,通过这个.do地址找到上面的那个Action(ListFileAction)
在里面做的工作是:

conn = ds.getConnection() ;
pstmt = conn.prepareStatement("select * from files") ;
rs = pstmt.executeQuery() ;
ArrayList files = new ArrayList() ;

//上面这个集合中可以放javaBean或者Map,这两个可以在页面上用表达式语言来访问!
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒") ;
while(rs.next())
{
 Map file = new HashMap() ;
 file.put("id",rs.getString("id"));
 file.put("filename",rs.getString("filename")) ;
 //file.put("path",rs.getString(3)) ;
 file.put("uploadTime",sdf.format(new Date(rs.getTimestamp("uploadTime").getTime())));
 file.put("downloadTimes",rs.getString("downloadTimes")) ;
 files.add(file) ;
}

request.setAttribute("files",files) ;

retrun actionMapping.findForward(""); //然后执行请求转发,到底转到哪里决定于自己在struts-config中的配置!

这样在jsp页面上就可以显示了:

<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic"%>
<body>
 <table> //这里面其实应该有个分页的问题,但是这里没有考虑……
 <logic:iterate id="file" name="files"> //会到作用域里面去找files这个属性,不再像核心标签库里面的需要表达式语言
  <tr> 
   <td>${file.id}</td>
   <td>${file.filename}</td>
   <td>${file.uploadTime}</td>
   <td>${file.downloadTimes}</td>
   <td><a href="download.do?id=${file.id}">下载?</a></td>
  </tr>
 </logic:iterate>
 <table>
</body>

再建立一个DownLoadAction来根据传递过来的id在数据库中找到path,然后处理下载流程,
成功后最好使用redirect回到第一个在数据库中取全部文件的Action。

//下载文件的时候由于文件大小肯定大于输出流的缓存8192Byte,所以写完输出流后绝对不可以再请求转发了,也不能重定向!!!
public ActionForward execute
{
 String id = request.getParameter("id") ;
 //得到id后,可以想办法去根据这个id来取出path

 pstmt= conn.prepareStatement("select path , filename from files where id=?");
 pstmt.setInt(1,Integer.parseInt(id));
 rs = pstmt.executeQuery();
 if(rs.next)
 {
  String path = rs.getString(1) ;
  FileInputStream in = new FileInputStream(path) ;//得到文件的输入流,可以把文件读进来了,应该把生成的响应传回去。
  //如何从响应中去得到输出流是下面需要考虑的问题。这时候需要考虑使用OutputStream而不是writer,因为可能是二进制的
  OutputStream out = response.getOutputStream() ;
  response.setContentType("application/zip"); //
  response.setHeader("content-disposition","attachment;filename=" +
   new String(rs.getString(2).getBytes("GBK"),"iso-8859-1"));
  //因为浏览器解析响应头的时候是以iso-8859-1的形式去显示的,所以转码的时候必须像上面那样。
  //后面的attachment会显示一个提示对话框,来提示你是否下载
  
  byte[] buf = new byte[512] ;
  int size = -1 ;
  while((size=in.read(buf,0,512))!=-1)
  {
   out.write(buf,0,size);
   out.flush() ;
  }
  in.close() ;
  target = "success" ;
 }

 
}

但是响应response里面有个8K的buffer,但是我们传的文件肯定是大于8K了,这时候一旦flush了,就认为响应提交了,但是转向之前肯定要把
response里面的东西清空的,为了让客户端看不到这个页面的东西才这么做的。但由于文件比较大 会flush了好几次,再转向就没有办法了,也就是
被提交的响应没有办法再forward了。

由于重定向是生成一个临时的响应,但是由于下载文件的时候服务器已经给客户端生成了响应,所以这招也不能用了。所以只好干脆哪里都不转,
具体操作起来就是在execute方法里面返回null,也就是还是停留在原来的这个页面上面,但是也不会去刷新这个页面。

其实大文件的上传和下载是分批进行的,至于批量读写的大小取决于上面的缓冲区buf,我们创建的是512字节的缓冲区,这样也就是一次将
512字节的输入流弄到响应里面去,如果是8KB的文件的话,那么大概16次就弄完了。

 

 

http://bt4.5qzone.net/
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值