0
点赞
收藏
分享

微信扫一扫

文件传输(上传下载)

归零者245号 2022-04-21 阅读 62

文章目录


文件上传

在这里插入图片描述

Tips

  1. form 的 method 必须是 post,因为get有长度限制

  2. 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,所以两个包我们都要引入

  1. commons-fileupload-1.2.1.jar
  2. commons-io-1.4.jar

常用类

  1. ServletFileUpload类:用于解析上传的数据

  2. FileItem类:表示每一个表单项

  3. boolean ServletFileUpload.isMultipartContent(HttpServletRequest request):判断当前上传的数据格式是否是多段的格式:

  4. public List parseRequest(HttpServletRequest request):解析上传的数据

  5. boolean FileItem.isFormField():判断当前这个表单项,是否是普通的表单项,还是上传的文件类型

    true 表示普通类型的表单项 false 表示上传的文件类型

  6. String FileItem.getFieldName():获取表单项的 name 属性值

  7. String FileItem.getString():获取当前表单项的值

  8. String FileItem.getName():获取上传的文件名

  9. 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>

实现方式一

缺点:

  1. 无法通过参数名获取参数值:request.getParameter(“xxx”)
  2. 文件处理过于复杂
    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());
    }
}

问题及解决方案

  1. 文件保存位置

    应该搭建 FTP/SFTP 服务器 , 使用FTP代码将文件上传至FTP服务器

    例如:自己搭建的 FastDFS、阿里 OSS、七牛云

    对于目前而言, 存放到某个磁盘下。

  2. 文件名重复

    对于用户上传的文件,一定要进行文件的重命名工作, 要为文件取一个唯一的名字

    方法 ① :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(".") );
    }
}
  1. 数据库设计

    在设计数据库表的时候,如果遇到文件类型

    ① 数据库中不保存文件内容,而是保存文件的上传路径,存相对路径,相同部分在常量类中或者配置文件中,进行统一管理

    ② 数据库添加一个字段,用于保存文件的原始名字(选做)

    ③ 如果一条数据对应多张图片,此时会创建一张 附件表(Annex),专用于保存文件信息

  2. 上传效率

    限流: 限制上传的单文件大小,以及所有文件的合计大小

    异步上传: 服务端异步处理数据

    前端异步上传: 网页端 使用 Ajax 进行上传 或者 不跟随表单上传。

文件下载

常用 API

  1. response.getOutputStream():获得字节流

  2. servletContext.getResourceAsStream(String path):将位于指定路径的资源作为InputStream对象返回

  3. servletContext.getMimeType(String file):返回指定文件的MIME类型,如果MIME类型未知,则返回null

  4. response.setContentType():设置发送到客户端的响应的内容类型。

  5. 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);
}
举报

相关推荐

0 条评论