# fileTransfer
**Repository Path**: Xixi22/fileTransfer
## Basic Information
- **Project Name**: fileTransfer
- **Description**: No description available
- **Primary Language**: Java
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2020-06-16
- **Last Updated**: 2020-12-19
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
@[TOC](文件上传下载功能实现)
## 零、流程

## 一、准备工作
### 1、创建空项目
没有使用Maven,jar包需要手动下载导入
创建最普通的webapp module:

### 2、导包
对于文件上传,浏览器 再上传的过程中讲文件以流的形式提交到服务器,一般采用apache的开源工具common-fileupload这个文件上传组件,依赖于 common-io这个包。
- 下载jar包:commons-io和common-fileupload
- 新建lib目录,放入两个包,并添加到类库:
(或者从structure添加libraries)

注意:设置打包

### 3、配置Tomcat
很简单,AddConfiguration导入Tomcat地址,修改发布路径Deployment即可。
测试是否启动成功
## 二、文件上传的注意事项(调优)
1. 为保证服务器安全,上传文件应该放在外界无法访问的目录下,入WEB-INF目录下。
2. 为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名,e.g. UUID,加后缀(时间戳【不安全】),加密md5,(复杂)位运算等。
3. 要限制上传文件的最大值。
4. 可以限制上传文件的类型,再收到上传文件名时,判断后缀名是否合法。
## 三、需要用到的类详解
**ServletFileUpload**负责处理上传文件的文件数据,并将表单中每个输入的项封装成一个**FileItem**对象,在使用**ServletfileUpload**对象解析请求时需要**DiskFileItemFactory**对象。所以,我们需要在进行解析工作前构造好**DiskFileItemFactory**对象,通过**ServletFileUpload**对象的构造方法或**setFileItemFactory()**方法设置**ServletFileUpload**对象的**FileItem**属性。
### FileItem类
- 在HTML页面input必须有name
```jsp
```
- 表单如果包含一个文件上传输入项的话,这个表单的enctype属性就必须设置为**mulipart/form-data**:
```jsp
```
- 浏览器表单的类型如果为**mulipart/form-data**,在服务器端想获取数据就要通过流
- 常用方法介绍
```java
//isFormField方法用于判断FileItem类对象封装的数据是一个普通文本表单字段,还是一个文件表单字段,如果是普通表单字段则返回true,否则返回false。因此,可以使用该方法判断是否为普通表单域,还是文件上传表单域。
boolean isFormField()
//getName方法用于获得文件上传字段中的文件名。
// 注意IE或FireFox中获取的文件名是不一样的,IE中是绝对路径,FireFox中只是文件名。
2. String getName()
// getFieldName方法用于返回表单标签name属性的值。如上例中的value。
3. String getFieldName()
// write方法用于将FileItem对象中保存的主体内容保存到某个指定的文件中。如果FileItem对象中的主体内容是保存在某个临时文件中,该方法顺利完成后,临时文件有可能会被清除。该方法也可将普通表单字段内容写入到一个文件中,但它主要用途是将上传的文件内容保存在本地文件系统中。
4. void write(File file)
// getString方法用于将FileItem对象中保存的数据流内容以一个字符串返回
5. String getString()
// getContentType 方法用于获得上传文件的类型,即表单字段元素描述头属性“Content-Type”的值,如“image/jpeg”。如果FileItem类对象对应的是普通表单字段,该方法将返回null。
6. String getContentType()
// isInMemory方法用来判断FileItem对象封装的数据内容是存储在内存中,还是存储在临时文件中,如果存储在内存中则返回true,否则返回false。
7. boolean isInMemory()
// delete方法用来清空FileItem类对象中存放的主体内容,如果主体内容被保存在临时文件中,delete方法将删除该临时文件。
8. void delete()
// 以流的形式返回上传文件的数据内容。
9. InputStream getInputStream()
// 返回该上传文件的大小(以字节为单位)。
10. long getSize()
```
### ServletFileUpload类
ServletFileUpload负责处理上传文件数据,并将表单中每个输入的项封装成一个**FileItem**对象,使用其**parseRequest(HttpServletRequest)**方法可以将通过表单中每一个HTML标签提交的数据封装成一个File Item对象,然后以List列表的形式返回。使用该方法处理上传文件简单易用。
## 四、代码编写
0. 杂乱版
```java
// 1. 创建DiskFileItemFactory对象,处理文件上传路径或者大小限制的文件
DiskFileItemFactory factory = new DiskFileItemFactory();
// 1.1.通过这个工场设置一个缓冲区,当上传文件大小超过缓冲区,将放到临时文件:
// 可以不写,有默认值
// factory.setSizeThreshold(1024*1024); // 1M
// factory.setRepository(file); //临时目录的保存目录file
// 2. 获取ServletFileUpload(处理乱码问题等设置都有默认的)
ServletFileUpload upload = new ServletFileUpload(factory);
// 3.处理上传的文件
// 把前端请求解析,封装成一个FileItem对象,需要从servletFileupload对象中获取
List fileItems = upload.parseRequest(request);
for (FileItem fileItem : fileItems) {
// item:每个表单对象
if(fileItem.isFormField()) {// 判断上传的文件时普通表单还是带文件的表单
String name = fileItem.getFieldName();
String value = fileItem.getString("UTF-8");
System.out.println(name+":"+value);
} else { //如果时文件表单(需要做处理了!)
//==================处理文件=============
String uploadFileName = fileItem.getName();
// 可能存在文件名不合法,直接跳过不做处理
if(uploadFileName.trim().equals("") || uploadFileName == null){
continue;
}
// 获得上传文件名 /images/cat/miao.png
String filename = uploadFileName.substring(uploadFileName.lastIndexOf("/") + 1);
// 获得文件后缀名
String fileExtName = uploadFileName.substring(uploadFileName.lastIndexOf(".") + 1);
// 如果后缀名不是需要的直接return,告知用户文件类型错误
// 使用uuid保证文件名唯一:
// 【网络传输中的东西都需要序列化!!!】
// POJO实体类,如果要在多个电脑上运行,传输==》需要把对象序列化了
// implements Serializable:标记接口(空的),JVM-->本地方法栈 native--》
// JNI = java native Interface本地化接口
String uuidPath = UUID.randomUUID().toString();
//==================存放地址=============
// uploadPath:存放地址
// 文件真是存在的路径 realPath
String realPath = uploadPath+"/"+uuidPath;
// 给每个文件创建一个对应的文件夹
File realPathFile = new File(realPath);
if(!realPathFile.exists()){
realPathFile.mkdir();
}
//==================文件传输=============
// 获得文件上传的流
InputStream inputStream = fileItem.getInputStream();
//创建一个文件输出流
// realPath = 真实文件夹
// 差了一个文件,加上输出文件名字+“/”+uuidFileName
FileOutputStream fos = new FileOutputStream(realPath + "/" + filename);
//创建缓冲区
byte[] buffer = new byte[1024*1024];
// 判断是否读取完毕
int len = 0;
// 如果大于0说明还存在数据
while((len = inputStream.read(buffer)) > 0){
fos.write(buffer,0,len);
}
// 关闭流
fos.close();
inputStream.close();
String msg = "文件上传成功!";
fileItem.delete();
}
}
} catch (FileUploadException e) {
e.printStackTrace();
}
```
1、函数封装版
```java
public class FileServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 判断上传的文件时普通表单还是带文件的表单
if (!ServletFileUpload.isMultipartContent(request)) {
return; //如果不是,说明这是一个普通表单,直接返回
}
// 创建上传文件的保存路径,建议在WEB-INF路径下,安全,用户无法直接访问上传的文件
String uploadPath = this.getServletContext().getRealPath("/WEB-INF/upload");
File uploadFile = new File(uploadPath);
if (!uploadFile.exists()) {
uploadFile.mkdir();// 创建这个目录
}
// 缓存,临时路径,假设文件超过了预期大小,就把它放到一个临时文件中,过几天自动删除,或者提醒用户转为永久
// qq文件下载
String tmpPath = this.getServletContext().getRealPath("/WEB-INF/tmp");
File tmpFile = new File(tmpPath);
if (!tmpFile.exists()) {
tmpFile.mkdir();// 创建临时目录
}
/*
* ServletFileUpload负责处理上传文件的文件数据,并将表单中每个输入的项封装成一个FileItem对象,
* 在使用ServletfileUpload对象解析请求时需要DiskFileItemFactory对象。
* 进行解析工作前构造好DiskFileItemFactory对象,通过ServletFileUpload对象的构造方法或setFileItemFactory()方法
* 设置ServletFileUpload对象的FileItem属性。
* */
// 处理上传的文件,一半都需要通过流来处理,可以使用request.getInputStram(),原生态的文件上传流(麻烦)
// 这里使用Apache的文件上传组件来实现,common-fileupload,它需要依赖于common-io组件
// request.getInputStream();
try {
// 1. 创建DiskFileItemFactory对象,处理文件上传路径或者大小限制的文件
DiskFileItemFactory factory = getDiskFileItemFactory(tmpFile);
// 2. 获取ServletFileUpload
ServletFileUpload upload = getServletFileUpload(factory);
// 3.处理上传的文件
String msg = uploadParseRequest(upload, request, uploadPath);
// servlet请求转发消息msg到info.jsp
request.setAttribute("msg", msg);
request.getRequestDispatcher("info.jsp").forward(request, response);
} catch (FileUploadException e) {
e.printStackTrace();
}
}
public static DiskFileItemFactory getDiskFileItemFactory(File file) {
// 1. 创建DiskFileItemFactory对象,处理文件上传路径或者大小限制的文件
DiskFileItemFactory factory = new DiskFileItemFactory();
// 通过这个工场设置一个缓冲区,当上传文件大小超过缓冲区,将放到临时文件:
// 可以不写,有默认值
factory.setSizeThreshold(1024 * 1024); // 1M
factory.setRepository(file); //临时目录的保存目录tmpFile
return factory;
}
public static ServletFileUpload getServletFileUpload(DiskFileItemFactory factory) {
// 2. 获取ServletFileUpload(处理乱码问题等设置都有默认的)
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setProgressListener(new ProgressListener() {
@Override
// 已经读取到的文件大小,文件大小,item
public void update(long l, long l1, int i) {
System.out.println("总大小:" + l + "已上传:" + l1);
}
});
// 处理乱码问题
upload.setHeaderEncoding("UTF-8");
// 设置单个文件的最大值
upload.setFileSizeMax(1024 * 1025 * 10);
// 设置总共能上传的文件大小
upload.setSizeMax(1024 * 1025 * 10);
return upload;
}
public static String uploadParseRequest(ServletFileUpload upload, HttpServletRequest request, String uploadPath) throws FileUploadException, IOException {
String msg = "";
// 3.处理上传的文件
// 把前端请求解析,封装成一个FileItem对象,需要从servletFileupload对象中获取
List fileItems = upload.parseRequest(request);
for (FileItem fileItem : fileItems) {
// item:每个表单对象
if (fileItem.isFormField()) {// 判断上传的文件时普通表单还是带文件的表单
String name = fileItem.getFieldName();
String value = fileItem.getString("UTF-8");
System.out.println(name + ":" + value);
} else { //如果时文件表单(需要做处理了!)
//==================处理文件=============
// 拿到文件名
String uploadFileName = fileItem.getName();
// 可能存在文件名不合法,直接跳过不做处理
if (uploadFileName.trim().equals("") || uploadFileName == null) {
continue;
}
// 获得上传文件名 /images/cat/miao.png
String filename = uploadFileName.substring(uploadFileName.lastIndexOf("/") + 1);
// 获得文件后缀名
String fileExtName = uploadFileName.substring(uploadFileName.lastIndexOf(".") + 1);
// 如果后缀名不是需要的直接return,告知用户文件类型错误
System.out.println("文件信息[文件名" + filename + "---文件类型" + fileExtName + "]");
// 使用uuid保证文件名唯一:
// 【网络传输中的东西都需要序列化!!!】
// POJO实体类,如果要在多个电脑上运行,传输==》需要把对象序列化了
// implements Serializable:标记接口(空的),JVM-->本地方法栈 native--》
// JNI = java native Interface本地化接口
String uuidPath = UUID.randomUUID().toString();
//==================存放地址=============
// uploadPath:存放地址
// 文件真是存在的路径 realPath
String realPath = uploadPath + "/" + uuidPath;
// 给每个文件创建一个对应的文件夹
File realPathFile = new File(realPath);
if (!realPathFile.exists()) {
realPathFile.mkdir();
}
//==================文件传输=============
// 获得文件上传的流
InputStream inputStream = fileItem.getInputStream();
//创建一个文件输出流
// realPath = 真实文件夹
// 差了一个文件,加上输出文件名字+“/”+uuidFileName
FileOutputStream fos = new FileOutputStream(realPath + "/" + filename);
//创建缓冲区
byte[] buffer = new byte[1024 * 1024];
// 判断是否读取完毕
int len = 0;
// 如果大于0说明还存在数据
while ((len = inputStream.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
// 关闭流
fos.close();
inputStream.close();
msg = "文件上传成功!";
fileItem.delete();
}
}
return msg;
}
}
```