近况

上次更新都是去年12月份了,没有继续更新的原因是在忙其他的考试,我对这个行业确实感觉迷茫了
论工资,像我这样普通开发人员也拿不了多少,论时间,没有时间去学习其他的东西,整天忙于业务,
再加上感觉行业越来越不行了,还是感觉不太想继续这样了,一个工作如果给不了金钱,又给不了时间去学习
我不知道我在图什么,当然如果以后不干这行了并不代表我不学计算机了,我还是会学习一些自己喜欢的东西
我没什么理想,也没什么特别想要的,我自身还留下的就只剩一点求知欲,探索欲了,想多看看书吧
想去大学图书馆工作,可惜自己还是太差了,要努力啊,继续加油吧,未来的路还很长,一切慢慢来

今天来总结一下上传下载的代码吧,之前也写过好几次这功能,我准备把它们全部搬到同一篇文章中
顺便加点新的理解和实现方法,最好做到以后有需要直接拷贝直接用,努力做到代码扩展性非常好。

最新的上传下载功能代码

文件上传下载要思考的规则

文件上传
	0,存储文件的根路径和文件允许的类型数据要配置在配置文件中
	1,可以上传多个文件,除了携带文件数据,还需要拿到其他参数(前端要使用formData方式上传)
	2,要检查文件是否符合要求,例如文件类型,文件是否为空,上传的文件与保存路径中文件是否重复,
		如果重复,可以根据需求不同,可以覆盖原文件,可以重写文件名,可以禁止上传
		重复判断的标准是根据文件名判断,文件名相同,md5不同为重复,文件名不同,md5相同则不为重复
		更高标准是md5和文件名都不能相同,文件名不同并且md5不同才能上传
		如果仅仅根据md5判断重复,文件名不同,但md5相同的文件不能保存,这个没必要如此,不符合普遍认知。
	3,文件的完整存储路径可能需要一定的规则,例如一部分使用时间戳或者唯一字段保证路径的唯一性,这个还是看需求
	4,判断上传文件的类型不能通过后缀名判断,而是应该通过file.getContentType()判断
	5,拼接文件路径时,保存文件的根路径要使用绝对路径
		File rootDirectoryFile = new File(rootPath.toString());
		String targetPath = rootDirectoryFile.getAbsolutePath() + File.separator + file.getOriginalFilename();
文件下载
	文件路径应该保存在数据库中,根据文件路径判断文件是否存在,根据文件路径来下载对应的文件
	后端返回给前端文件有几种情况:
		* 前端传递文件路径,后端读取本地文件,返回文件流,前端获取文件流转成url并将其保存在img标签的src路径下,方可查看文件
		* 前端传递文件路径,后端将文件数据循环读取写到响应输出流中,而不是一次性读取到内存。
		* 根据前端提供的网络文件的地址和保存路径,后端拿到网络文件流,将文件保存在文件路径的目录中
		* 网络文件获取到服务器后,经服务器处理后响应给前端

文件下载
1,这里写了一个非常简单的文件下载代码,并不一定适用

@RequestMapping(value = "/downLoad",produces = {MediaType.IMAGE_JPEG_VALUE,MediaType.IMAGE_PNG_VALUE})
public Result<Object> downLoadFile(@RequestParam("filePath") String filePath,HttpServletResponse response)
  throws IOException {
  FileUtil.writeToStream(filePath,response.getOutputStream());
  return Result.OK();
}

2,这是另一个文件下载,实现方式较为常见,可以设置response的一些内容

/**
* @param path 想要下载的文件的路径
* @param response
* @功能描述 下载文件:
*/
@RequestMapping("/download")
public void download (String path, HttpServletResponse response){
  try {
    // path是指想要下载的文件的路径
    File file = new File(path);
    log.info(file.getPath());
    // 获取文件名
    String filename = file.getName();
    // 获取文件后缀名
    String ext = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
    log.info("文件后缀名:" + ext);

    // 将文件写入输入流
    FileInputStream fileInputStream = new FileInputStream(file);
    InputStream fis = new BufferedInputStream(fileInputStream);
    byte[] buffer = new byte[fis.available()];
    fis.read(buffer);
    fis.close();

    // 清空response
    response.reset();
    // 设置response的Header
    response.setCharacterEncoding("UTF-8");
    //Content-Disposition的作用:告知浏览器以何种方式显示响应返回的文件,用浏览器打开还是以附件的形式下载到本地保存
    //attachment表示以附件方式下载 inline表示在线打开 "Content-Disposition: inline; filename=文件名.mp3"
    // filename表示文件的默认名称,因为网络传输只支持URL编码的相关支付,因此需要将文件名URL编码后进行传输,前端收到后需要反编码才能获取到真正的名称
    response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
    // 告知浏览器文件的大小
    response.addHeader("Content-Length", "" + file.length());
    OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
    response.setContentType("application/octet-stream");
    outputStream.write(buffer);
    outputStream.flush();
  } catch (IOException ex) {
    ex.printStackTrace();
  }
}

3,前端拿到文件流数据,需转成临时url,将该url放在img标签的src上才能展示图片

download({url:'/eams/eamsBusinessFiles/downLoad',parameter:{filePath:filePath,systemCode:this.systemCode}})
.then((res)=>{
        this.antForm.slotLocation = window.URL.createObjectURL(res)
})

文件上传

  • 获取单个文件与少量字段参数

    @PostMapping("/uploadFileWithAddtionalData")
    public String submit(
      @RequestParam MultipartFile file, @RequestParam String name,
      @RequestParam String email, ModelMap modelMap) {
    
        modelMap.addAttribute("name", name);
        modelMap.addAttribute("email", email);
        modelMap.addAttribute("file", file);
        return "fileUploadView";
    }
  • 获取多个文件与少量字段参数

    @PostMapping(value = "/uploads")
    public Result<Object> upload(MultipartHttpServletRequest request){
      String systemCode = request.getParameter("systemCode");
      String archivalCode = request.getParameter("archivalCode");
      List<MultipartFile> files = request.getFiles("file");
    }
  • 获取多个文件与多个字段参数

    public class FormDataWithFile {
        private String name;
        private String email;
        //看别人代码改的,应该可以,,,吧
        private List<MultipartFile> file;
        //private MultipartFile file;
    }
    @PostMapping("/uploadFileModelAttribute")
    public String submit(@ModelAttribute FormDataWithFile formDataWithFile) {
        return "fileUploadView";
    }

具体代码省略了,无非就是

1.从请求中获取必要参数,
2.检查文件是否为空,文件格式是否符合要求
3.判断根目录是否为空,为空则创建根目录
4.如果是上传多文件,在循环中拼接最终文件路径,将其保存到服务器中
5.创建必要的业务对象持久化数据库

2021-07-05 图片上传功能

  • 在springboot.yaml配置路径信息

    #文件上传设置
    file:
      localPath: C:/Users/OneForAll/Desktop/img
      requestPath: /image/avatar
      imagePath: file:C:/Users/OneForAll/Desktop/img/
  • 通过FileProperties资源类,对配置信息进行绑定

    @Data
    @Configuration
    @ConfigurationProperties("file")
    public class FileProperties {
        /**
         * 文件保存的本地前缀,完整本地(真实)路径=本地前缀+时间目录+随机文件名+文件类型后缀,
         */
        private String localPath;
    
        /**
         * 请求路径前缀,完整请求路径=请求前缀+时间目录+随机文件名+文件类型后缀,
         */
        private String requestPath;
    
        /**
         * 映射路径,在web资源映射处使用
         */
        private String imagePath;
    }
  • UserController接收请求,对请求进行处理后,返回数据给前端

    /*
        用户头像上传,给参数
    */
    @PostMapping("/profile/avatar")
    public Result uploadAvatar(@RequestParam("avatarfile") MultipartFile file, 
    	HttpServletRequest request){
        
        Integer userId = TokenUtil.getAdminUserId(request.getHeader("Authorization"));
        Map<String, Object> map = UploadFileUtil.uploadImage(file);
        if ((int) (map.get("status"))==200&&(boolean)map.get("isImage")){
            User user = new User();
            user.setAvatar((String) map.get("requestUrl"));
            user.setUserId(userId);
            userService.updateById(user);
            return ResultGenerator.genSuccessResult((String) map.get("requestUrl"));
        }else {
            return ResultGenerator.genFailResult("图片上传失败");
        }
    }
  • UploadFileUtil工具类,真正实现图片上传功能

    @Slf4j
    public class UploadFileUtil {
        /**
         *  这里并不能使用自动注入,因为该工具类并不在容器中。
    	 *  所以使用SpringContextUtil工具类,该类实现ApplicationContextAware接口
    	 *  获取IOC容器,即从容器中获取组件。
         */
        private static FileProperties fileProperties = 
    		SpringContextUtil.getBean(FileProperties.class);
    
        /**
         * 获取上传的文件类型
         * @param filename file.getOriginalFilename();
         *
         */
        public static String getFileType(String filename){
            return filename.substring(filename.lastIndexOf("."));
            /*if (filename.contains(".")){
            }else{
                return null;
            }*/
        }
    
        /**
         * 获取时间路径
         *
         */
        public static String getDatePath(){
            return new SimpleDateFormat("yyyy/MM/dd").format(new Date());
        }
    
        /**
         * 获取随机文件名
         *
         */
        public static String getRandomName(){
            return UUID.randomUUID().toString();
        }
    
        /**
         * 判断是否是图片
         *
         */
        public static boolean isImage(MultipartFile uploadFile) {
            if (uploadFile.isEmpty()) {
                return false;
            }
            BufferedImage image = null;
            try {
                image = ImageIO.read(uploadFile.getInputStream());
                if (image == null || image.getWidth() <= 0 || image.getHeight() <= 0) {
                    return false;
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 判断图片后缀是否符合要求
         */
        public static boolean isImageType(String fileType){
            return !fileType.toLowerCase().matches("^.+\\.(jpg|jpge|png|gif)$");
        }
    
        /**
         * 获取文件的大小,以kb为单位
         */
        public static double getFileSize(MultipartFile uploadFile){
            double size = uploadFile.getSize();
            return Math.ceil(size/1024.0);
        }
        
        /**
         * 如果本地目录不存在,则创建该文件夹
         */
        public static void createMkdir(String fileName){
            File file = new File(fileName);
            if (!file.exists()){
                file.mkdirs();
            }
        }
    
        /**
         *  上传图片
         *      1,获取时间路径
         *      2,获取随机文件名
         *      3,获取图片后缀
         *      4,获取图片大小,以kb为单元
         *      5,生成本地目录(本地前缀+时间目录,如果为空,则创建文件夹),
         *          生成完整本地路径(本地目录+随机文件名+文件类型后缀),
         *          生成完整请求路径(用于前端访问)
         *      6,判断文件是否是图片文件,判断文件后缀是否符合图片类型
         *      7,如果是图片文件,则创建目录,再使用完整本地路径,保存文件
         *
         *  图片实际保存地:完整本地(真实)路径=本地目录(本地前缀+时间目录)+随机文件名+文件类型后缀
         *  前端访问路径:完整请求路径=请求前缀+时间目录+随机文件名+文件类型后缀
         *
         *  datePath:时间目录
         *  randomName:随机文件名
         *  fileType:文件类型后缀
         *  fileSize:文件大小,kb为单位
         *  newFileDir:本地目录=本地前缀+时间目录,没有则创建该文件夹
         *  newFile:完整本地路径=本地目录+随机文件名+文件类型后缀
         *  requestUrl:完整请求路径=请求前缀+时间目录+随机文件名+文件类型后缀
         *
         *  本案例中因为前端传递过来的MultipartFile数据,获取文件后缀后,
    	 *	并没有任何后缀,可能是被设置过的,所以方法中直接声明文件后缀是.jpg,
         *  实际上应该由uploadFile.getOriginalFilename()获取
         */
        
    
        public static Map<String,Object> uploadImage(MultipartFile uploadFile){
            Map<String, Object> map = new HashMap<>();
            String datePath = getDatePath();
            String randomName = getRandomName();
            String fileType = ".jpg";
            double fileSize = getFileSize(uploadFile);
            String newFileDir=fileProperties.getLocalPath()+"/"+datePath;
            String newFile = 
    	        fileProperties.getLocalPath()+"/"+datePath+"/"+randomName+fileType;
            String requestUrl = 
    	        fileProperties.getRequestPath()+"/"+datePath+"/"+randomName+fileType;
    
            map.put("fileSize", String.valueOf(fileSize));
            map.put("fileType",fileType);
            map.put("generateFolder",datePath);
            map.put("generateFileName",randomName+fileType);
            map.put("oldName",uploadFile.getOriginalFilename());
    
            log.info("文件后缀"+fileType);
            log.info("文件大小"+fileSize+"kb");
            log.info("完整文件路径"+newFile);
            log.info("完整请求路径"+requestUrl);
            if (isImage(uploadFile)&&isImageType(fileType.substring(1))){
                map.put("isImage",true);
                createMkdir(newFileDir);
                try {
                    uploadFile.transferTo(new File(newFile));
                    map.put("status", 200);
                    map.put("requestUrl", requestUrl);
                    log.info("图片上传成功");
                } catch (IOException e) {
                    map.put("status", 201);
                    e.printStackTrace();
                    log.info("图片上传失败");
                }
            }else {
                map.put("isImage",false);
                map.put("status", 201);
                log.info("图片上传失败");
            }
            return map;
        }
    }
  • 顺便看看SpringContextUtil工具类,用于获取容器中组件

    @Component
    public class SpringContextUtil implements ApplicationContextAware {
    	private static ApplicationContext applicationContext;
    
    	@Override
    	public void setApplicationContext(ApplicationContext applicationContext) 
    		throws BeansException {
    		SpringContextUtil.applicationContext = applicationContext;
    	}
    
    	public static Object getBean(String name) {
    		return applicationContext.getBean(name);
    	}
    	public static <T> T getBean(Class<T> clazz){
    		return applicationContext.getBean(clazz);
    	}
    
    	public static <T> T getBean(String name, Class<T> requiredType) {
    		return applicationContext.getBean(name, requiredType);
    	}
    
    	public static boolean containsBean(String name) {
    		return applicationContext.containsBean(name);
    	}
    
    	public static boolean isSingleton(String name) {
    		return applicationContext.isSingleton(name);
    	}
    
    	public static Class<?> getType(String name) {
    		return applicationContext.getType(name);
    	}
    }
  • 最后配置资源映射信息

    @Configuration
    public class ARSystemWebMvcConfigurer implements WebMvcConfigurer {
    
        @Autowired
        private FileProperties fileProperties;
    
        /*
            配置资源处理映射
         */
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            String imagePath = fileProperties.getImagePath();
            registry.addResourceHandler("/adminApi/image/avatar/**")
    	        .addResourceLocations(imagePath);
        }
    }
  • 上传图片的请求路径

    http://localhost:8081/adminApi/user/profile/avatar
    post请求,携带图片数据,avatarfile属性,使用MultipartFile file接收
  • 前端发出的图片请求路径

    http://localhost:8081/adminApi/image/avatar/2021/07/05/
    	8c2fffe9-52e4-4bc6-af93-21beefa139e0.jpg
    
    get请求,该请求被资源映射器处理,将该请求转换成以下路径:
    
    file:C:/Users/OneForAll/Desktop/img/2021/07/05/
    	8c2fffe9-52e4-4bc6-af93-21beefa139e0.jpg	

2021-04-07 文件上传

  • 前端页面

    <form method="post" th:action="@{/sendFile}"   enctype="multipart/form-data">
        <input name="username" type="text" placeholder="用户名"/><br>
        <input name="password" type="text" placeholder="密码"/><br>
        <input name="headerImg" type="file" /><p th:text="${session.msg}"></p><br>
        <input name="photos" type="file"  multiple/><p th:text="${session.msg2}"></p><br>
        <input type="submit" value="提交">
    </form>
  • 控制器方法

    @RequestMapping("/sendFile")
    public String handleFile(@RequestParam("username") String username,
                             @RequestParam("password") String password,
                             @RequestPart("headerImg") MultipartFile headerImg,
                             @RequestPart("photos") MultipartFile[] photos,
                             HttpSession session) throws IOException {
      System.out.println(username+":"+password+":"+headerImg.getSize()+":"+photos.length);
    
      if (!headerImg.isEmpty()){
        headerImg.transferTo(new 
                             File("C:\\Users\\OneForAll\\Desktop\\text\\"
                                  +headerImg.getOriginalFilename()));
        session.setAttribute("msg","大头照上传成功");
      }else{
        session.setAttribute("msg","大头照上传失败");
      }
      if (photos.length>0){
        for (MultipartFile file:photos){
          file.transferTo(new File("C:\\Users\\OneForAll\\Desktop\\text\\"
                                   +file.getOriginalFilename()));
        }
        session.setAttribute("msg2","相册上传成功");
      }else {
        session.setAttribute("msg2","相册上传失败");
      }
      Enumeration<String> attributeNames = session.getAttributeNames();
      while (attributeNames.hasMoreElements()){
        String s = attributeNames.nextElement();
        Object attribute = session.getAttribute(s);
        System.out.println(s+":"+attribute);
      }
      return "index";
    }
  • 写文件上传的时候碰到了一些问题

    • 之前一直用的JSP,不知道html页面不接收post应答包,也就是说,目标方法接收post请求后
      该目标方法返回值不能再重定向到一个html页面。

    • Thymeleaf完全遵循了MVC规范,不能直接访问到,只能通过转发到Thymeleaf下的页面转发到Thymeleaf下的html页面,控制器方法可以直接返回视图名字符串,或者返回ModelAndView
      重定向是客户端行为,如果重定向了,相当于客户端直接请求thymeleaf的html页面,这样只能访问静态页面
      只有先重定向到控制器,再让控制器帮我们转发到thymeleaf下的html页面

      // 重定向到控制器的方法里
      @RequestMapping("/redirectMain")
      public String redirectMain() {
      	return "redirect:toMain";
      }
      
      // 控制器帮我们转发到thymeleaf
      @RequestMapping("toMain")
      public String forwardToMain(){
      	return "main";
      }
    • 目标方法重定向后,在thymeleaf页面下获取不了session值,解决办法同上。

2021-03-03 SpringMVC的文件上传和下载

1,SpringMVC实现文件的下载

  • 前端页面

    <a href="${pageContext.request.contextPath}/downLoad/万一.png">下载照片</a>
  • 后端控制器方法

    /*
        springmvc文件的下载
    */
    @RequestMapping("/downLoad/{fileName}")
    public ResponseEntity<byte[]> downLoadFile(@PathVariable String fileName) 
      throws IOException {
      //根据文件名,拼接路径,获取文件流,读取到字节数据中
      System.out.println(fileName);
      //方式一  FileInputStream inputStream = new 
      FileInputStream("C:\\Users\\OneForAll\\Desktop\\" + fileName+".png");
    
      String filePath = "C:\\Users\\OneForAll\\Desktop\\"+fileName+".png";
      File file = new File(filePath);
      System.out.println("文件名:"+file.getName());
      FileInputStream inputStream = new FileInputStream(file);
    
      byte[] bytes = new byte[inputStream.available()];
      inputStream.read(bytes);
    
      //自定义响应头,设置响应文件类型,告诉浏览器是二进制文件
      MultiValueMap<String,String> headers = new HttpHeaders();
      //方式一  headers.add("Content-Disposition","attachment;filename="+fileName);
      //方式二,将文件名使用utf-8格式编码,解决下载时中文乱码问题
      headers.add("Content-Disposition","attachment;filename="+ 
                  URLEncoder.encode(file.getName(),"utf-8"));
      //响应状态
      HttpStatus httpStatus = HttpStatus.OK;
      return new ResponseEntity<byte[]>(bytes,headers,httpStatus);
    }

2,SpringMVC实现单文件的上传

  • 导入依赖

    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.3.1</version>
    </dependency>
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.5</version>
    </dependency>
  • 在SpringMVC配置文件中,配置文件上传解析器

    <!--文件上传解析器-->
    <bean id="multipartResolver" 
        class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        
        <!--设置编码字符集-->
        <property name="defaultEncoding" value="utf-8"></property>
        
        <!--spring表达式-->
        <property name="maxUploadSize" value="#{1024*1024*20}"></property>
    </bean>
    文件上传解析器是springmvc的九大组件之一,其id值只能为multipartResolver
  • 前端代码

    ${msg}
    <form method="post" enctype="multipart/form-data" 
        action="${pageContext.request.contextPath}/upload">
        <input type="text" name="username" placeholder="请输入用户名">
        <input type="file" name="upFile">
        <input type="submit" value="提交">
    </form>
  • 后端控制器方法

    /*
    *   springmvc上传文件
    * */
    @RequestMapping("/upload")
    public String upload(@RequestParam("upFile")MultipartFile file,
            @RequestParam("username")String username,
            HttpServletRequest request,
            Model model){
            System.out.println("文件名:"+file.getOriginalFilename());
            String realPath ="D:\\idea\\Project\\SpringMVCDemo2\\
                src\\main\\webapp\\img\\"+file.getOriginalFilename();
            System.out.println(realPath);
            try {
                file.transferTo(new File(realPath));
                model.addAttribute("msg",username+"的文件上传文件成功");
            } catch (IOException e) {
                model.addAttribute("msg",username+"的文件上传文件失败");
                e.printStackTrace();
            }
            return "forward:/index.jsp";
    }

3,SpringMVC实现多文件上传

  • 前端页面

    ${msg2}
    <form method="post" enctype="multipart/form-data" 
        action="${pageContext.request.contextPath}/uploads">
        <input type="text" name="username" placeholder="请输入用户名"><br>
        <input type="file" name="upFile"><br>
        <input type="file" name="upFile"><br>
        <input type="file" name="upFile"><br>
        <input type="file" name="upFile"><br>
        <input type="submit" value="提交">
    </form>
  • 后端控制器方法

    @RequestMapping("/uploads")
    public String uploads(@RequestParam("upFile")MultipartFile[] files,
            @RequestParam("username")String username,
            Model model){
            for (MultipartFile file:files){
                System.out.println("文件名:"+file.getOriginalFilename());
                String realPath = "D:\\idea\\Project\\SpringMVCDemo2\\
                    src\\main\\webapp\\img\\"+file.getOriginalFilename();
                System.out.println(realPath);
                try {
                    file.transferTo(new File(realPath));
                    model.addAttribute("msg2",username+"的文件上传文件成功");
                } catch (IOException e) {
                    model.addAttribute("msg2",username+"的文件上传文件失败");
                    e.printStackTrace();
                }
            }
            return "forward:/index.jsp";
    }