文件上传是算是java web项目中基操了,主要业务就是将文件通过IO流上传到服务器的某一个特定的文件夹下.浏览器在上传的过程中是将文件以流的形式提交到服务器端的,如果直接使用Servlet获取上传文件的输入流然后再解析里面的请求参数是比较麻烦,所以一般选择采用apache的开源工具common-fileupload这个文件上传组件, 为了方便显示上传之后的显示相应数据, 在此项目中还需要一个转换数据格式的包 json-org, 具体如下图:
项目的包结构如下图(开发环境是 Idea):
在正式开始之前我们先来梳理一下思路, web项目一般分为前端与后端, 前端负责与用户的交互操作, 后端负责处理业务逻辑, 在此项目中也不例外, 前端通过表单提交文件, 后端接收文件并存储在服务器上, 理清思路后就可以正式开始了.
- 前端
upload.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style>
.main{
}
.main input{
margin: 20px;
}
.main input[type='submit']{
background-color: cornflowerblue;
color: #fff;
border: 1px solid #ccc;
border-radius: 1px;
padding: 8px 18px;
}
</style>
</head>
<body>
<div class='main'>
<form method="post" action="uploadFile" enctype="multipart/form-data">
<input type="file" name="fileUpload" /> <br>
<input type="submit" value="上传" /> <br>
</form>
</div>
</body>
</html>
效果图:
代码很简单, 就是一个赤裸裸的文件上传表单域, 前端的内容到这就结束了.
- 后端
1. 自定义文件描述类
FileUploadInfo.java
package my;
import java.io.File;
public class FileUploadInfo {
public String realName; // 原始文件名
public String suffix; // 后缀
public String tmpFileName; // 服务器临时文件名
public File tmpFile; // 文件实体
public long fileSize; // 文件大小
}
该类描述了一个上传文件的基本信息.
2. 创建 Servlet
FileUploadService.java
FileUploadService.java
package my;
import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.util.Streams;
import org.json.JSONObject;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
public class FileUploadService extends HttpServlet {
// 文件上传所在目录
File tmpDir;
@Override
public void init() throws ServletException {
// 绝对路径
File webRoot = new File(getServletContext().getRealPath("/"));
tmpDir = new File(webRoot, "upload");
tmpDir.mkdirs(); // 上传进来的文件存放在 WebRoot/upload 下
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
JSONObject jresp = new JSONObject();
try {
Object data = doUpload(request, response); // 执行上传
jresp.put("error", 0);
jresp.put("reason", "ok");
if (data!= null) {
jresp.put("data", data);
}
} catch (Exception e) {
e.printStackTrace();
}
response.setCharacterEncoding("UTF-8");
response.setContentType("text/plain");
PrintWriter out = response.getWriter();
out.write(jresp.toString(2));
out.close();
}
// 上传
private Object doUpload(HttpServletRequest request, HttpServletResponse response) throws Exception {
// ServletFileUpload : commons包里提供的工具类
// 判断请求编码是否为 mutipart/form-data
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if (!isMultipart) {
throw new Exception("请求编码必须为: mutipart/form-data !");
}
request.setCharacterEncoding("UTF-8");
ServletFileUpload upload = new ServletFileUpload();
FileUploadInfo info = new FileUploadInfo();
// 表单域迭代器
FileItemIterator iter = upload.getItemIterator(request);
while (iter.hasNext())
{
// 表单域
FileItemStream item = iter.next();
String fieldName = item.getFieldName(); // 表单标签的name属性的值
InputStream inStream = item.openStream(); // 允许读取
// 判断普通的表单域, 还是文件上传表单域
if (item.isFormField()) {
// 普通表单域 直接读取值
// 指的是<input> <select> <textarea> 输入控件的值, 字符串类型
String fieldValue = Streams.asString(inStream, "UTF-8");
printLog("表单域: " + fieldName + "=" + fieldValue);
} else {
// 文件域, 值 <file> 控件的值, 值文件的数据
// 生成唯一的文件名
info.realName = item.getName();
info.suffix = fileSuffix(info.realName); // 获取后缀
info.tmpFileName = createTmpFileName(info.suffix); // 服务器临时文件名
info.tmpFile = new File(tmpDir, info.tmpFileName);
info.fileSize = 0; // 文件大小
printLog("文件上传开始: " + info.realName + ">>" + info.tmpFile);
// 从 FieldStream 读取数据, 保存到目标文件
info.tmpFile.getParentFile().mkdirs();
// 输出流把文件写入本地, 可以自动创建文件, 但不能创建目录
FileOutputStream fileStream = new FileOutputStream(info.tmpFile);
try {
// 从请求里读取文件数据, 保存到本地文件
info.fileSize = copy(inStream, fileStream);
} finally {
try {inStream.close();} catch (Exception e){}
try {fileStream.close();} catch (Exception e){}
}
printLog("文件上传完成: " + info.realName + ", 大小: " + info.fileSize);
}
}
// storePath: 如/upload/1231238723478.jpg
// url: 相对项目URL, 前面加载ContextPath
String storePath = "/upload/" + info.tmpFileName;
String url = getServletContext().getContextPath() + storePath;
// 返回一个 JSON
JSONObject result = new JSONObject();
result.put("storePath", storePath);
result.put("url", url);
return result;
}
private long copy(InputStream in, OutputStream out) throws IOException {
long count = 0;
byte[] buf = new byte[8192];
int i = 0;
while (true)
{
// 一次读取不超过buf.length字节的数据, 并存储到buf中, 返回值为实际读取的字节数, 到达文件末尾返回-1
int n = in.read(buf);
if (n < 0) {
break;
}
if (n == 0) {
continue;
}
out.write(buf, 0, n);
System.out.println("第 " + i++ + " 次" + "读取了 " + n + "的数据");
count += n;
}
return count;
}
// 打印输出
private void printLog(String message)
{
System.out.println(message);
}
// 得到文件后缀名
private String fileSuffix(String fileName)
{
int p = fileName.lastIndexOf('.');
if (p > 0) {
return fileName.substring(p+1).toLowerCase();
}
return "";
}
// 得到一个不重复的临时文件名
private String createTmpFileName(String suffix)
{
String dateStr = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date());
String name = dateStr + "-" + createUUID() + "." + suffix;
return name;
}
// 产生唯一id
private String createUUID() {
String s = UUID.randomUUID().toString();
String s2 = s.substring(0, 8) + s.substring(9, 13) + s.substring(14, 18) + s.substring(19, 23)+s.substring(24);
return s2.toUpperCase();
}
}
注意看注释, 文件上传的逻辑大致可以总结为:
- 在服务器上创建存放上传文件的目录, 此例中的临时目录是: web/upload.
- 从 HttpServletRequest 中取出文件, 并做判断防止上传的文件太大或是病毒文件.
- 使用 InputStram 打开一个输入流句柄, 用来读取文件内容.
- 使用 FileOutputStream 打开一个输出流句柄, 用来把上传文件写入本地, 并重命名上传文件, 防止重名.
- 关闭 InputStram , FileOutputStream 句柄, 防止浪费内存资源.
3. 配置 web.xml
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>FileUploadService</servlet-name>
<servlet-class>my.FileUploadService</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FileUploadService</servlet-name>
<url-pattern>/uploadFile</url-pattern>
</servlet-mapping>
</web-app>
done