1 文件上传
html中通过<input type="file"/>可以向服务器上传文件。不过后台需要手动解析请求,比较复杂,所以可以使用smartupload或apache的fileupload组件进行文件的上传。smartupload据网友测试,在上传大文件时不稳定,所以还是使用fileupload的吧,毕竟apache出品。
本例子中使用的jar包:
- commons-fileupload-1.3.2.jar
- commons-io-2.5.jar(fileupload依赖)
前端jsp页面:
<div class="upload">
<form action="UploadFileServlet" method="POST" enctype="multipart/form-data">
上传文件:<input type="file" name="uploadFile">
<input type="submit" value="upload">上传
<input type="reset" value="reset">重置
</form>
</div>
其中有几个需要注意的点:
- form表单的enctype必须为"multipart/form-data"。
- <input type="file" name="uploadFile"/> 中必须有name属性,因为在fileupload中会根据fieldName解析上传的文件。
- method必须为POST方法。
- 如果多文件上传的话,file类型的name必须为不同的名称。
服务端需要建立一个UploadFileServlet来处理请求。
关键的doPost方法:
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
// 首先检测是否是文件上传,主要依据enctype的值来判定
if (!ServletFileUpload.isMultipartContent(request)) {
PrintWriter writer = response.getWriter();
writer.write("Error 不是文件上传,表单必须包含 enctype='multipart/form-data'");
writer.flush();
writer.close();
return;
}
DiskFileItemFactory factory = new DiskFileItemFactory();
// 设置在内存中的缓存大小,如果超过了则保存到临时文件。
factory.setSizeThreshold(MEMORY_THRESHOLD);
System.err.println(System.getProperty("java.io.tmpdir"));
// 设置临时文件夹的目录
factory.setRepository(new File(System.getProperty("java.io.tmpdir")));
ServletFileUpload upload = new ServletFileUpload(factory);
// 设置单个文件大小的最大值
upload.setFileSizeMax(MAX_FILE_SIZE);
// 设置上传文件总量的最大值,包括所有文件和表单的总和
upload.setSizeMax(MAX_REQUEST_SIZE);
File uploadDir = new File(UPLOAD_PATH);
if (!uploadDir.exists()) {
uploadDir.mkdir();
}
try {
// 解析request
List<FileItem> formItems = upload.parseRequest(request);
if (formItems != null && formItems.size() > 0) {
for (FileItem item : formItems) {
// 如果不是普通的formField则就是上传的文件
if (!item.isFormField()) {
String fileName = item.getName();
File storeFile = new File(UPLOAD_PATH + File.separator
+ fileName);
System.err.println(storeFile.getAbsolutePath());
item.write(storeFile);
item.delete();
} else {
System.err.println(item.getString());
}
}
}
request.setAttribute("message", "成功");
request.getRequestDispatcher("result.jsp").forward(request,
response);
} catch (Exception e) {
// TODO: handle exception
request.setAttribute("message", e.getMessage());
request.getRequestDispatcher("result.jsp").forward(request,
response);
e.printStackTrace();
return;
}
}
这样即可实现文件的上传。
需要改进的地方
- 没有上传进度
- 限制文件大小
- 限制文件类型
对于第2点,可以利用fileupload的抛出异常解决。
catch(FileUploadBase.FileSizeLimitExceededException e){
request.setAttribute("message", "单个文件大小超过限制");
request.getRequestDispatcher("result.jsp").forward(request,
response);
e.printStackTrace();
return;
} catch(FileUploadBase.SizeLimitExceededException e){
request.setAttribute("message", "上传文件总大小超过限制");
request.getRequestDispatcher("result.jsp").forward(request,
response);
e.printStackTrace();
return;
}
对于第3点,可以在页面用js进行校验。
<form action="UploadFileServlet" method="POST"
enctype="multipart/form-data" onsubmit="return check_file()">
上传文件:<input type="file" name="uploadFile" id="uploadFile"> <input
type="submit" value="upload">上传 <input type="reset"
value="reset">重置
</form>
<script>
function check_file() {
var fileName = document.getElementById("uploadFile").value;
var suffix = fileName.substr(fileName.lastIndexOf(".") + 1);
if (suffix !== "exe") {
alert("只能上传exe文件");
return false;
}
}
</script>
对于第1点,fileupload可以添加监听器,监听上传进度。
//设置上传进度的监听器
ProgressListener progressListener = new ProgressListener() {
public void update(long pBytesRead, long pContentLength, int pItems) {
System.out.println("We are currently reading item " + pItems);
if (pContentLength == -1) {
System.out.println("So far, " + pBytesRead
+ " bytes have been read.");
} else {
System.out.println("So far, " + pBytesRead + " of "
+ pContentLength + " bytes have been read.");
uploadPercent = (double) pBytesRead / pContentLength;
System.err.println(uploadPercent);
}
}
};
upload.setProgressListener(progressListener);
虽然服务端添加了监听器,可以在console或者Log里打印上传进度,但我们想要的是让用户看到上传进度。所以需要把进度返回给用户。
要解决的问题主要有两个:
- 进度信息如何保存
- 前台如何获取
其中的一种方案是利用session。我们将上传进度保存在session里,前台通过ajax方法定时获取上传的进度,因为每个用户是一个session,不同的用户session不同。这样当不同的用户同时上传文件时,依然可以正确的获得上传进
度,不会获取到其他用户上传文件的进度。
具体首先有一个简单的保存上传进度的实体类:
public class UploadStatus {
private double percent;
public double getPercent() {
return percent;
}
public void setPercent(double percent) {
this.percent = percent;
}
}
然后有一个监听的类,实现了ProgressListener接口
public class UploadListener implements ProgressListener{
private UploadStatus status;
public UploadListener(UploadStatus status) {
// TODO Auto-generated constructor stub
this.status = status;
}
@Override
public void update(long pBytesRead, long pContentLength, int pItems) {
// TODO Auto-generated method stub
double uploadPercent = (double) pBytesRead / pContentLength;
status.setPercent(uploadPercent);
}
}
在doPost方法中:
//设置上传进度的监听器
UploadStatus status = new UploadStatus();
UploadListener listener = new UploadListener(status);
upload.setProgressListener(listener);
request.getSession(true).setAttribute("uploadStatus", status);
最后在doGet方法中,返回上传进度。
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
response.setHeader("Cache-Control", "no-store");
response.setDateHeader("Expires", 0);
response.setHeader("Pragrma", "no-cache");
PrintWriter writer = response.getWriter();
UploadStatus status = (UploadStatus)(request.getSession().getAttribute("uploadStatus"));
if(status != null){
writer.write("上传进度:" + status.getPercent());
}else{
writer.write("没有上传信息");
}
writer.flush();
writer.close();
}
当用户上传时可以访问doGet方法即可获取进度。
前台页面可以这样写:
其中form的target属性可以防止页面跳转。
<body>
<iframe width=0 height=0 name="uploadFrame"></iframe>
<form action="UploadFileServlet" method="POST"
enctype="multipart/form-data" target="uploadFrame" onsubmit="getStatus()">
上传文件:<input type="file" name="uploadFile" id="uploadFile"> <input
type="submit" value="upload">上传 <input type="reset"
value="reset">重置
</form>
<span>上传进度:</span><span id="progress"></span>
<script src="jquery-1.11.2.js"></script>
<script>
var finished ;
function check_file() {
var fileName = document.getElementById("uploadFile").value;
var suffix = fileName.substr(fileName.lastIndexOf(".") + 1);
if (suffix !== "exe") {
//alert("只能上传exe文件");
//return false;
}
finished = false;
return true;
}
function getStatus(){
finished = false;
console.log("finished = " + finished)
showStatus();
}
function showStatus(){
console.log("showstatus finished = " + finished)
if(finished === true) return;
$.ajax({
url:'UploadFileServlet',
type:'GET',
success:function(data){
$('#progress').text(data);
if(data == '1.0'){
finished = true;
}
},
error:function(data){
alert(data);
}
});
setTimeout(showStatus,1000);
}
</script>
</body>
最后 完整的程序UploadFile.zip
除了使用form表单上传文件这种方式,也可以利用html5的特性使用ajax上传文件。
下面是主要代码。
var formData = new FormData();
formData.append('file', $('#input')[0].files[0]);
$.ajax({
url: contextPath + '/file/upload?savedName=' + savedName,
type: 'POST',
data: formData,
cache: false,
contentType: false,
processData: false,
xhr: function () {
var xhr = $.ajaxSettings.xhr();
if (xhr.upload) {
xhr.upload.addEventListener("progress", function (evt) {
var percent = parseInt((evt.loaded / evt.total) * 100) + '%';
console.log(percent);
}, false);
return xhr;
}
}
}).done(function(e){})
首先初始化formData对象,然后将真正的file添加到formData里,然后创建一个ajax请求,type是POST,
data: formData,
cache: false,
contentType: false,
processData: false,
这几个属性很重要,具体解释请百度之。
上传进度则使用如下的方式获得:
xhr: function () {
var xhr = $.ajaxSettings.xhr();
if (xhr.upload) {
xhr.upload.addEventListener("progress", function (evt) {
var percent = parseInt((evt.loaded / evt.total) * 100) + '%';
console.log(percent);
}, false);
return xhr;
}
}
这里注意这个是浏览器上传的进度,在服务端还有写入磁盘的时间,所以即便达到100%了服务端可能也并没有完全完成保存操作,不过这个时间差可以忽略。
注意如果出于页面美化的目的,需要隐藏input框,那么一般会使用
display:none
这个css属性,但由于浏览器安全性的限制,这种方式无法奏效,这里提供一个tricky的方法,给input框设置一个position:absolute;top:-9999px;
,这样既可以隐藏input,又可以触发选择文件的操作。
2 文件下载
文件下载相对比较简单
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
String id = request.getParameter("id");
//app.properties 路径
DFileItem dao = new DFileItem();
String basePath = request.getSession().getServletContext().getRealPath("");
String configPath = basePath+Constants.CONFIG_PATH;
String uploadPath = Configuration.getInstance(configPath).getProperty(Constants.FILE_STORAGE_PATH);
FileItem item = dao.queryFileItem(id);
if(item == null){
PrintWriter writer = response.getWriter();
writer.write("文件已经被删除");
writer.close();
return;
}
String name = item.getFileName();
response.setContentType("application/octet-stream");
String downloadName = URLEncoder.encode(name,"UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-Disposition", "attachment;filename*=utf-8'zh_cn'"+downloadName);
File file = new File(uploadPath+item.getUploadTime());
response.setContentLength((int)file.length());
FileInputStream in = new FileInputStream(file);
OutputStream out = response.getOutputStream();
byte[] buffer = new byte[8192];
int len = 0;
while((len = in.read(buffer)) != -1){
out.write(buffer, 0, len);
}
out.flush();
out.close();
in.close();
}
注意response 的contentType和header的设置即可。
最终一个完整的文件上传下载的例子FileServer