zoukankan      html  css  js  c++  java
  • 文件上传和下载

    一、文件上传和下载的原理

    文件上传原理:
    第一、文件上传时,读取文件的二进制进行MD5加密,将该密钥作为开关提交给服务器;若服务器有该密钥,证明文件的内容一模一样,这样就达到了秒上传的功能。
    第二、若服务器没有该密钥,则将文件进行分片上传(分片的时候,会读取文件的大小,并且指定每个分片上传的大小,这样可以获取分片的数量);同时客户端会给分片设置其索引值,到时服务器可以根据索引值进行重组,避免了文件的损伤。

    1.考虑到浏览器右键点击保存图片(有图片的名称和扩展名),程序中可以设置request中相关的属性,将名称和扩展名赋予相关属性。
    2.客户端上传文件的名称和扩展名有可能与服务器中的文件重名,这样会覆盖掉原文件,因此我们要赋予文件的名称是自定义的且唯一的,可以使用Uuid。
    3.为了便于管理,可以在数据库中创建一张存储文件相关信息的表。
    4.客户端上传的文件内容,服务器有可能有,因而我们需要用到文件上传原理的第一步,避免服务器内存存储过快耗尽。

    severlet中的request和response对象中有请求流和相应流,若我们使用到服务器封装到的流,不需要我们手动关闭,因为浏览器/服务器在断开连接时,会释放资源。但是,若是我们自定义的流,那么需要手动关闭;又由于关闭流是文件上传下载时基本会使用到的,所以我们可以将其包装成一个工具类。

    servlet中有默认的文件上传的大小,这样就需要在配置文件中设置我们需要让其上传的最大的文件大小。

    文件上传一般使用post请求,文件下载一般是get/post都给可以(涉及到了标签获取图片路径以及浏览器右键点击图片可以打开图片,这是使用的是地址栏传参)

    二、例子(springboot)

    2.1 在application.properties配置文件上传的大小
    #文件上传限制
    spring.servlet.multipart.max-file-size=20MB
    spring.servlet.multipart.max-request-size=20MB
    

    文件上传的过程:
    1. 组装文件上传存储的路径以及文件的名称(唯一标识uuid)和扩展名
    2. 将文件写到该路径下
    3. 由于@RequestMapping使用{},只能获取最后一个/的后面的字符串,而存储的路径也有/,所以要先进行编码(使用Base64)
    4. 将路径返回给客户端(将路径是可以直接在浏览器的地址栏中直接打开的,因而需要传IP、端口号等,再拼上使用Base64进行编码的文件名称和扩展名)

    2.2 配置application-config.properties可供改变的参数
    #统一的文件地址前缀(服务器存储上传文件的路径前缀)
    localhost.perfix=D:/file_data
    #文件下载路径的前缀
    download.url.prefix=http://localhost:9001/file/download
    
    2.3 controller层
    package cn.kooun.controller;
    
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.multipart.MultipartFile;
    
    import cn.kooun.service.FileService;
    
    /**
     * 	文件controller
     * @author HuangJingNa
     * @date 2019年12月26日 下午2:30:59
     *
     */
    @RestController
    @RequestMapping("file")
    public class FileController {
    	@Autowired
    	private FileService fileService;
    	
    	/**
    	 * 	文件上传
    	 * 	方式一:MultipartHttpServletRequest request.getFile(键名)获取文件相关内容
    	 * 	方式二:使用MultipartFile,但是变量名一定要为键名
    	 * @author HuangJingNa
    	 * @date 2019年12月26日 下午2:36:17
    	 *
    	 * @return
    	 * @throws Exception 
    	 */
    	@PostMapping("upload")
    	public Object upload(MultipartFile file) throws Exception {
    		return fileService.upload(file);
    	}
    	/**
    	 * 	文件下载
    	 * @author HuangJingNa
    	 * @date 2019年12月26日 下午4:02:07
    	 *
    	 * @return
    	 * @throws Exception 
    	 */
    	@RequestMapping("download/{path}")
    	public Object download(
    			@PathVariable String path, 
    			HttpServletResponse response) throws Exception {
    		return fileService.download(path, response);
    	}
    }
    
    2.4 service层
    package cn.kooun.service;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    
    import javax.servlet.ServletOutputStream;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.util.StringUtils;
    import org.springframework.web.multipart.MultipartFile;
    
    import cn.kooun.common.Base64Utils;
    import cn.kooun.common.FileUtils;
    import cn.kooun.common.UuidUtils;
    import cn.kooun.common.result.ResultUtils;
    import cn.kooun.mapper.FileDBMapper;
    import cn.kooun.pojo.table.FileDB;
    
    /**
     * 	文件service
     * @author HuangJingNa
     * @date 2019年12月26日 下午2:48:17
     *
     */
    @Service
    @Transactional
    public class FileService {
    	@Autowired
    	private FileDBMapper fileDBMapper;
    	@Value("${localhost.perfix}")
    	private String localhostPerfix;
    	@Value("${download.url.prefix}")
    	private String downloadUrlPrefix;
    
    	/**
    	 * 	文件上传
    	 * @author HuangJingNa
    	 * @date 2019年12月26日 下午2:48:44
    	 *
    	 * @param file
    	 * @return
    	 * @throws Exception 
    	 */
    	public Object upload(MultipartFile file) throws Exception {
    		FileOutputStream fos = null;
    		StringBuilder customUrl = new StringBuilder(downloadUrlPrefix);
    		try {
    			//将上传的文件写入到服务器中
    			//本地存储路径没有,则创建
    			File localhostPerfixFile = new File(localhostPerfix);
    			if(!localhostPerfixFile.exists()) {
    				localhostPerfixFile.mkdir();
    			}
    			//文件名称(为了不让上传的文件名称覆盖了本地文件,使用uuid)
    			String filePrefix = UuidUtils.getUuid();
    			//文件扩展名
    			String originalFilename = file.getOriginalFilename();
    			String fileSuffix = this.getFileSuffix(originalFilename);
    			//组装本地文件存储路径
    			String filePath = new StringBuilder(localhostPerfix)
    					.append("/")
    					.append(filePrefix)
    					.append(fileSuffix)
    					.toString();
    			fos = new FileOutputStream(filePath);
    			fos.write(file.getBytes());
    			//将文件的名称和扩展名保存到request的请求头中,便于之后客户端获取
    			//因为使用{}只能获取最后一个/的字符串
    			//为了让客户端获取到图片路径,需要使用base64进行加密和解密
    			// .../d:/file_data/xxx.xxx
    			customUrl.append("/")
    					 .append(Base64Utils.encoder(filePath));
    			//为了便于管理,将文件相关内容插入数据库中(涉及事务回滚)
    			this.saveUpload(file, customUrl, filePrefix, fileSuffix);
    		} finally {
    			//关闭资源
    			FileUtils.close(fos);
    		}
    		
    		//将文件存储路径返回(加密的)
    		return ResultUtils.success(customUrl.toString());
    	}
    	/**
    	 * 	将上传的文件插入数据库中
    	 * @author HuangJingNa
    	 * @date 2019年12月26日 下午3:48:43
    	 *
    	 * @param file
    	 * @param customUrl
    	 * @param filePrefix
    	 * @param fileSuffix
    	 */
    	private void saveUpload(
    			MultipartFile file, 
    			StringBuilder customUrl, 
    			String filePrefix, 
    			String fileSuffix) {
    		//封装FileDB类
    		FileDB fileDB = new FileDB();
    		fileDB.setId(UuidUtils.getUuid());
    		fileDB.setUrl(customUrl.toString().replace(downloadUrlPrefix, ""));
    		fileDB.setType(file.getContentType());
    		fileDB.setSize(file.getSize());
    		fileDB.setName(filePrefix + fileSuffix);
    		fileDBMapper.insertSelective(fileDB);
    	}
    
    	/**
    	 * 	获取扩展名
    	 * @author HuangJingNa
    	 * @date 2019年12月26日 下午3:18:33
    	 *
    	 * @param originalFilename
    	 * @return
    	 */
    	private String getFileSuffix(String originalFilename) {
    		//通过分隔符.获取扩展名
    		String[] split = originalFilename.split("\.");
    		String fileSuffix = split.length == 2 ? "." + split[split.length-1]:"";
    		return fileSuffix;
    	}
    	/**
    	 * 	文件下载
    	 * @author HuangJingNa
    	 * @date 2019年12月26日 下午4:04:05
    	 *
    	 * @param path
    	 * @param response
    	 * @return
    	 * @throws Exception 
    	 */
    	public Object download(String path, HttpServletResponse response) throws Exception {
    		//数据校验(校验路径是否合法)
    		Object result = this.checkPath(path);
    		if(result != null) {
    			return result;
    		}
    		FileInputStream fis = null;
    		try {
    			//存在该路径,读取文件
    			String filePath = Base64Utils.decoder(path);
    			fis = new FileInputStream(filePath);
    			byte[] b = new byte[1024 * 8 * 8];
    			int len;
    			//将文件渲染在页面上(使用响应流写到页面上)
    			ServletOutputStream os = response.getOutputStream();
    			while((len = fis.read(b)) != -1) {
    				os.write(b, 0, len);
    			}
    			//刷新流
    			os.flush();
    		} finally {
    			FileUtils.close(fis);
    		}
    		return null;
    	}
    	/**
    	 * 	文件下载数据校验
    	 * @author HuangJingNa
    	 * @date 2019年12月26日 下午4:15:11
    	 *
    	 * @param path
    	 * @return
    	 */
    	private Object checkPath(String path) {
    		if(StringUtils.isEmpty(path)) {
    			return ResultUtils.error("系统繁忙,请联系管理员~");
    		}
    		//根据文件路径判断是否有该文件
    		String[] split = path.split("/");
    		if(!fileDBMapper.isExistFileByFileUrl("/" + split[split.length - 1])) {
    			return ResultUtils.error("系统繁忙,请联系管理员~");
    		}
    		return null;
    	}
    
    }
    
    2.5 Base64Utils
    package cn.kooun.common;
    
    import java.util.Base64;
    
    /**
     * 	base64加密解密工具类
     * @author HuangJingNa
     * @date 2019年12月26日 下午3:28:41
     *
     */
    public class Base64Utils {
    
    	/**
    	 * 	加密
    	 * @author HuangJingNa
    	 * @date 2019年12月26日 下午3:33:47
    	 *
    	 * @param filePath
    	 * @return
    	 */
    	public static String encoder(String value) {
    		try {
    			return Base64.getEncoder().encodeToString(value.getBytes());
    		} catch (Exception e) {
    			return value;
    		}
    	}
    	/**
    	 * 	解密
    	 * @author HuangJingNa
    	 * @date 2019年12月26日 下午3:36:05
    	 *
    	 * @param value
    	 * @return
    	 */
    	public static String decoder(String value) {
    		try {
    			return new String(Base64.getDecoder().decode(value.getBytes()));
    		} catch (Exception e) {
    			return value;
    		}
    	}
    }
    
    2.6 FileUtils工具类
    package cn.kooun.common;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    
    /**
     * 	文件工具类
     * @author HuangJingNa
     * @date 2019年12月26日 下午3:22:13
     *
     */
    public class FileUtils {
    	/**
    	 * 	关闭资源
    	 * @author HuangJingNa
    	 * @date 2019年12月26日 下午3:23:30
    	 *
    	 * @param is
    	 */
    	public static void close(InputStream is) {
    		if(is != null) {
    			try {
    				is.close();
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    	/**
    	 * 	关闭资源
    	 * @author HuangJingNa
    	 * @date 2019年12月26日 下午3:23:30
    	 *
    	 * @param is
    	 */
    	public static void close(OutputStream os) {
    		if(os != null) {
    			try {
    				os.close();
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    
    2.7 mapper层
    package cn.kooun.mapper;
    
    import org.apache.ibatis.annotations.Param;
    import org.apache.ibatis.annotations.Select;
    
    import cn.kooun.pojo.table.FileDB;
    import tk.mybatis.mapper.common.Mapper;
    
    /**
     * 	文件mapper
     * @author HuangJingNa
     * @date 2019年12月26日 下午3:41:13
     *
     */
    public interface FileDBMapper extends Mapper<FileDB>{
    	/**
    	 * 	根据文件路径判断是否有该文件
    	 * @author HuangJingNa
    	 * @date 2019年12月26日 下午4:18:52
    	 *
    	 * @param path
    	 * @return
    	 */
    	@Select("SELECT" + 
    			"	CASE f.id WHEN 0 THEN 0 ELSE 1 END" + 
    			" FROM" + 
    			"	f_file f" + 
    			" WHERE" + 
    			"	f.url = #{path}")
    	boolean isExistFileByFileUrl(@Param("path")String path);
        
    }
    

    注意:以上示例没有解决若文件内容相同,但文件名不同,不需要再次上传文件,否则损耗服务器的资源。

    三、数据库

    f_file表.jpg

    f_file索引.jpg

  • 相关阅读:
    86. Partition List
    2. Add Two Numbers
    55. Jump Game
    70. Climbing Stairs
    53. Maximum Subarray
    64. Minimum Path Sum
    122. Best Time to Buy and Sell Stock II
    以场景为中心的产品设计方法
    那些产品经理犯过最大的错
    Axure教程:如何使用动态面板?动态面板功能详解
  • 原文地址:https://www.cnblogs.com/nadou/p/14004599.html
Copyright © 2011-2022 走看看