近况
上次更新都是去年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"; }