文章目录
文件上传
Tips
-
form 的 method 必须是 post,因为get有长度限制
-
form 的 enctype 必须是 multipart/form-data
encType = multipart/form-data 表示提交的数据,以多段(每一个表单项一个数据段)的形式进行拼接,然后以二进制流的形式发送给服务器
原始Servlet实现
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletInputStream inputstream = req.getInputStream();
byte[] buffer = new byte[1024000];
int read = inputstream.read(buffer);
System.out.println(new String(buffer, 0, read));
}
相关jar包
commons-fileupload.jar 需要依赖 commons-io.jar,所以两个包我们都要引入
- commons-fileupload-1.2.1.jar
- commons-io-1.4.jar
常用类
-
ServletFileUpload
类:用于解析上传的数据 -
FileItem
类:表示每一个表单项 -
boolean
ServletFileUpload.isMultipartContent(HttpServletRequest request)
:判断当前上传的数据格式是否是多段的格式: -
public
List
parseRequest(HttpServletRequest request)
:解析上传的数据 -
boolean
FileItem.isFormField()
:判断当前这个表单项,是否是普通的表单项,还是上传的文件类型true 表示普通类型的表单项 false 表示上传的文件类型
-
String
FileItem.getFieldName()
:获取表单项的 name 属性值 -
String
FileItem.getString()
:获取当前表单项的值 -
String
FileItem.getName()
:获取上传的文件名 -
void
FileItem.write( file )
:将上传的文件写到 参数 file 所指向的硬盘位置
示例
<form action="/uploadServlet" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username" /> <br>
头像:<input type="file" name="photo" > <br>
<input type="submit" value="上传">
</form>
实现方式一
缺点:
- 无法通过参数名获取参数值:request.getParameter(“xxx”)
- 文件处理过于复杂
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1 先判断上传的数据是否多段数据(只有是多段的数据,才是文件上传的)
if (ServletFileUpload.isMultipartContent(req)) {
// 创建FileItemFactory工厂实现类
FileItemFactory fileItemFactory = new DiskFileItemFactory();
// 创建用于解析上传数据的工具类ServletFileUpload类
ServletFileUpload servletFileUpload = new ServletFileUpload(fileItemFactory);
try {
// 解析上传的数据,得到每一个表单项FileItem
List<FileItem> list = servletFileUpload.parseRequest(req);
// 循环判断,每一个表单项,是普通类型,还是上传的文件
for (FileItem fileItem : list) {
if (fileItem.isFormField()) { // 普通表单项
System.out.println("表单项的name属性值:" + fileItem.getFieldName());
// 参数UTF-8.解决乱码问题
System.out.println("表单项的value属性值:" + fileItem.getString("UTF-8"));
} else {// 上传的文件
System.out.println("表单项的name属性值:" + fileItem.getFieldName());
System.out.println("上传的文件名:" + fileItem.getName());
// 输出文件
fileItem.write(new File("e:\\" + fileItem.getName()));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
实现方式二:封装
package day07;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MultipartBean {
//存储普通表单项
private Map<String, List<String>> params = new HashMap();
//存储文件格式表单项
private Map<String, MultipartFile> fileMap = new HashMap<>();
public MultipartBean(HttpServletRequest req) {
try {
// 创建FileItemFactory工厂实现类
FileItemFactory fileItemFactory = new DiskFileItemFactory();
// 创建用于解析上传数据的工具类ServletFileUpload类
ServletFileUpload servletFileUpload = new ServletFileUpload(fileItemFactory);
// 解析上传的数据,得到每一个表单项FileItem
List<FileItem> fileItem = servletFileUpload.parseRequest(req);
for (FileItem fileItem : fileItem) {
if(fileItem.isFormField()){// 普通表单项
//表单项的name属性值
String paramName = fileItem.getFieldName();
//表单项的value属性值
String paramValue = fileItem.getString();
//获取params中同名key的value
List<String> value = params.get(paramName);
if(value == null){ //如果本来没有此参数,则创建list格式的value
value = new ArrayList<>();
}
//将此表单项的值添加到刚创建的list中
value.add(paramValue);
//添加至Map中
params.put(paramName,value);
}
if(!fileItem.isFormField()){// 文件格式
//MultipartFile:接收multipart/form-data类型请求方式中即将上传的文件
MultipartFile file = new MultipartFile(fileItem.getName(),fileItem.getInputStream());
fileMap.put(fileItem.getFieldName(),file);
}
}
} catch (Exception e) {
throw new RuntimeException("FileUploadUtil初始化失败",e);
}
}
public String getParameter(String name){
return params.get(name) == null? null : params.get(name).get(0);
}
public String[] getParameterValues(String name){
List<String> values = params.get(name);
if(values == null){
return null;
}
String[] result = new String[values.size()];
for (int i = 0; i < values.size(); i++) {
result[i] = values.get(i);
}
return result;
}
public MultipartFile getFile(String name){
return fileMap.get(name);
}
}
package util;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class MultipartFile {
private String fileName;
private InputStream inputStream;
// 将文件拷贝到distFile
public void transferTo(String destFile) throws IOException {
byte[] buffer = new byte[inputStream.available()];
inputStream.read(buffer);
FileOutputStream fos = new FileOutputStream(destFile);
fos.write(buffer);
fos.flush();
fos.close();
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public InputStream getInputStream() {
return inputStream;
}
public void setInputStream(InputStream inputStream) {
this.inputStream = inputStream;
}
public MultipartFile() {
}
public MultipartFile(String fileName, InputStream file) {
this.fileName = fileName;
this.inputStream = file;
}
}
测试
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// 如果是文件上传, request中将无法获取任何参数,包括非文件类型参数
String value1 = request.getParameter("some");
System.out.println("request->some: " + value1);
MultipartBean bean = new MultipartBean(request);
String some = bean.getParameter("some");
System.out.println("bean->some:" + some);
MultipartFile file = bean.getFile("someFile");
file.transferTo("D:/" + file.getFileName());
}
}
问题及解决方案
-
文件保存位置
应该搭建 FTP/SFTP 服务器 , 使用FTP代码将文件上传至FTP服务器
例如:自己搭建的 FastDFS、阿里 OSS、七牛云
对于目前而言, 存放到某个磁盘下。
-
文件名重复
对于用户上传的文件,一定要进行文件的重命名工作, 要为文件取一个唯一的名字
方法 ① :
new Date().getTime()
/System.currentTimeMillions()
: 高并发下,该值可能不唯一方法 ② :UUID(Universally Unique Identifier) 通用唯一识别码:
毫秒值
+Mac地址
+随机值
public class MultipartFile {
private String fileName;
private InputStream inputStream;
public String randomFileName(){
return UUID.randomUUID().toString() + fileSuffix();
}
public String fileSuffix(){
return this.fileName.substring( fileName.indexOf(".") );
}
}
-
数据库设计
在设计数据库表的时候,如果遇到文件类型
① 数据库中不保存文件内容,而是保存文件的上传路径,存相对路径,相同部分在常量类中或者配置文件中,进行统一管理
② 数据库添加一个字段,用于保存文件的原始名字(选做)
③ 如果一条数据对应多张图片,此时会创建一张 附件表(Annex),专用于保存文件信息
-
上传效率
限流: 限制上传的单文件大小,以及所有文件的合计大小
异步上传: 服务端异步处理数据
前端异步上传: 网页端 使用 Ajax 进行上传 或者 不跟随表单上传。
文件下载
常用 API
-
response.getOutputStream()
:获得字节流 -
servletContext.getResourceAsStream(String path)
:将位于指定路径的资源作为InputStream对象返回 -
servletContext.getMimeType(String file)
:返回指定文件的MIME类型,如果MIME类型未知,则返回null -
response.setContentType()
:设置发送到客户端的响应的内容类型。 -
response.setHeader("Content-Disposition", "attachment; fileName=1.jpg")
Content-Disposition 响应头,表示收到的数据怎么处理
attachment表示附件,表示下载使用
filename= 表示指定下载的文件名
示例
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1、获取要下载的文件名
String downloadFileName = "2.jpg";
// 2、读取要下载的文件内容 (通过ServletContext对象可以读取)
ServletContext servletContext = getServletContext();
// 获取要下载的文件类型
String mimeType = servletContext.getMimeType("/file/" + downloadFileName);
System.out.println("下载的文件类型:" + mimeType);
// 4、在回传前,通过响应头告诉客户端返回的数据类型
resp.setContentType(mimeType);
// 5、还要告诉客户端收到的数据是用于下载使用(还是使用响应头)
// url编码是把汉字转换成为%xx%xx的格式
// User-Agent 请求头,含义为浏览器版本
if (req.getHeader("User-Agent").contains("Firefox")) {
// 如果是火狐浏览器使用Base64编码
resp.setHeader("Content-Disposition", "attachment; filename==?UTF-8?B?"
+ new BASE64Encoder().encode("中国.jpg".getBytes("UTF-8")) + "?=");
} else {
// 如果不是火狐,是IE或谷歌,使用URL编码操作
resp.setHeader("Content-Disposition", "attachment; filename="
+ URLEncoder.encode("中国.jpg", "UTF-8"));
}
InputStream resourceAsStream = servletContext.getResourceAsStream("/file/" + downloadFileName);
// 获取响应的输出流
OutputStream outputStream = resp.getOutputStream();
// 3、把下载的文件内容回传给客户端
// 读取输入流中全部的数据,复制给输出流,输出给客户端
IOUtils.copy(resourceAsStream, outputStream);
}
乱码问题
现象及原因
如果我们要下载的文件是中文名的话,会发现下载无法正确显示出中文名。
原因是在响应头中,不能包含有中文字符,只能包含 ASCII码
IE 和 谷歌浏览器
// 把中文名进行 UTF-8 编码操作。
String str = "attachment; fileName=" + URLEncoder.encode("中文.jpg", "UTF-8");
// 然后把编码后的字符串设置到响应头中
response.setHeader("Content-Disposition", str);
火狐浏览器
火狐浏览器所用的编码是 Base64,这时候需要把请求头
编码成为:
public static void main(String[] args) throws Exception {
String content = "这是需要Base64编码的内容";
// 创建一个Base64编码器
BASE64Encoder base64Encoder = new BASE64Encoder();
// 执行Base64编码操作
String encodedString = base64Encoder.encode(content.getBytes("UTF-8"));
System.out.println( encodedString );//6L+Z5piv6ZyA6KaBQmFzZTY057yW56CB55qE5YaF5a65
// 创建Base64解码器
BASE64Decoder base64Decoder = new BASE64Decoder();
// 解码操作
byte[] bytes = base64Decoder.decodeBuffer(encodedString);
String str = new String(bytes, "UTF-8");
System.out.println(str);//这是需要Base64编码的内容
}.out.println(str);
}