当前位置:   article > 正文

springboot实现文件上传_springboot 上传文件

springboot 上传文件

SpringBoot默认静态资源访问方式

首先想到的就是可以通过SpringBoot通常访问静态资源的方式,当访问:项目根路径 + / + 静态文件名时,SpringBoot会依次去类路径下的四个静态资源目录下查找(默认配置)。

资源文件resources目录下建立如下四个目录:

重启Spring boot,访问
http://localhost:8080/1.jpg
http://localhost:8080/2.jpg
http://localhost:8080/3.jpg
http://localhost:8080/4.jpg

上传的文件应该存储在哪?怎么访问?

1.文件存储在哪?
前文所说外部用户可通过url访问服务器资源文件resources目录下的静态资源,但若是将上传的文件都保存在resources相关目录下,将会导致后续打包过大,程序和代码不分离,无法查看等问题。

解决方案:文件上传到服务器某个目录,然后SpringBoot配置虚拟路径,映射到此目录。

2.怎么访问?
通过WebMvcConfigurer 的addResourceHandlers将匹配上虚拟路径的url映射到文件上传到服务器的目录,这样就可以通过url来获取服务器上的静态资源了。

示例代码
代码仓库github路径

目标:windows本地测试,将文件上传到 D:\develop\work\project\myblog\myblog-file-upload\fileStorage 目录下,然后通过http://localhost:8080/files/文件名 访问。

配置类

  1. @Configuration
  2. public class WebMvcConfig implements WebMvcConfigurer {
  3. @Autowired
  4. FileServiceImpl fileService;
  5. @Override
  6. public void addResourceHandlers(ResourceHandlerRegistry registry) {
  7. //将匹配上/files/**虚拟路径的url映射到文件上传到服务器的目录,获取静态资源
  8. registry.addResourceHandler("/" + fileService.pathPattern + "/**").addResourceLocations("file:" + fileService.filePath);
  9. WebMvcConfigurer.super.addResourceHandlers(registry);
  10. }
  11. }

controller

  1. @RestController
  2. @RequestMapping("/file")
  3. public class FileController {
  4. @Autowired
  5. private FileServiceImpl fileService;
  6. @PostMapping("/upload")
  7. public FileUploadResponse upload(@RequestParam("file") MultipartFile file) {
  8. return fileService.upload(file);
  9. }
  10. }

上传文件目录创建好后,主要通过 file.transferTo(new File(absolutePath)) 完成。

Service

  1. @Slf4j
  2. @Service
  3. public class FileServiceImpl {
  4. //拦截的url,虚拟路径
  5. public String pathPattern = "files";
  6. //自己设置的目录
  7. private static final String fileDir = "fileStorage";
  8. //上传文件存放目录 = 工作目录绝对路径 + 自己设置的目录,也可以直接自己指定服务器目录
  9. //windows本地测试
  10. //绝对路径: D:\develop\work\project\myblog\myblog-file-upload\fileStorage\202302021010345680.jpg
  11. //System.getProperty("user.dir") D:\develop\work\project\myblog\myblog-file-upload
  12. //fileDir fileStorage
  13. //fileName 202302021010345680.jpg
  14. public String filePath = System.getProperty("user.dir") + File.separator + fileDir + File.separator;
  15. private static final AtomicInteger SUFFIX = new AtomicInteger(0);
  16. @Value(value = "${file.upload.suffix:jpg,jpeg,png,bmp,xls,xlsx,pdf}")
  17. private String fileUploadSuffix;
  18. public FileUploadResponse upload(MultipartFile file) {
  19. FileUploadResponse result = new FileUploadResponse();
  20. if (file.isEmpty()) {
  21. log.error("the file to be uploaded is empty");
  22. return result;
  23. }
  24. List<String> suffixList = Lists.newArrayList(fileUploadSuffix.split(","));
  25. try {
  26. //校验文件后缀
  27. String originalFilename = file.getOriginalFilename();
  28. String suffix = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
  29. if (!suffixList.contains(suffix)) {
  30. log.error("unsupported file format");
  31. return result;
  32. }
  33. //首次需生成目录
  34. File folder = new File(filePath);
  35. if (!folder.exists()) {
  36. folder.mkdirs();
  37. }
  38. String fileName = timeFormat(System.currentTimeMillis()) + SUFFIX.getAndIncrement() + "." + suffix;
  39. String absolutePath = filePath + fileName;
  40. log.info("absolutePath is {}", absolutePath);
  41. file.transferTo(new File(absolutePath));
  42. String separator = "/";
  43. String path = separator + pathPattern + separator + fileName;
  44. result.setPath(path);
  45. result.setFileName(fileName);
  46. } catch (Exception e) {
  47. log.error("the file upload error occurred. e ", e);
  48. }
  49. return result;
  50. }
  51. public static String timeFormat(Long time) {
  52. if (Objects.isNull(time)) {
  53. return null;
  54. }
  55. DateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
  56. return sdf.format(time);
  57. }
  58. }

测试

总结
其实这和最初的SpringBoot获取静态资源的方式又有点不一样,针对url做拦截,实际上resources目录下并没有files这个文件夹,它只是一个虚拟路径,通过映射转发到文件夹上传目录,在该目录下通过文件名去定位。
另外,如果有用nginx,也可以在其配置中设置转发。至此都是借鉴的Java实现文件上传到服务器本地,并通过url访问_java文件上传 后怎么访问-CSDN博客这位博主的内容·

后续添加补充

第一点是一个小坑,也就是System.getProperty("user.dir"),这个函数是一个 Java 中用于获取当前工作目录的方法。我本地运行出来确实是我项目的根目录,但是上到服务器,打出来的就是/,也就是linux的根目录,因此我决定以"/home/ec2-user/www/wwwroot/online_exam" 这种定值的方式取代System.getProperty("user.dir"),否则我的fileStorage目录就会建立在/这个目录下面,然后根据url访问就失效了。

第二点是我命名新文件的名字采用的是UUID的形式

第三点是我添加了一个附件表,防止重复照片的存入消耗内存,毕竟不是存在三方文件系统上,自己买的系统还是省着点,而且还可以提升一丢丢的效率。

以下是我的代码,由于是在一个github开源项目改的,所有采用的是比较老的mybatis:

pom

  1. //下面工具类需要导入这两个依赖
  2. <dependency>
  3. <groupId>com.google.guava</groupId>
  4. <artifactId>guava</artifactId>
  5. <version>20.0</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>commons-codec</groupId>
  9. <artifactId>commons-codec</artifactId>
  10. <version>1.15</version>
  11. </dependency>

controller

  1. @RequestMapping("/api")
  2. @RestController
  3. public class CommonDataController {
  4. @Autowired
  5. private FileServiceImpl fileService;
  6. @PostMapping("/upload")
  7. public ApiResult upload(@RequestParam("file") MultipartFile file){
  8. return ApiResultHandler.success(fileService.upload(file));
  9. }
  10. }

Fileservice

  1. package com.exam.serviceimpl;
  2. import com.exam.entity.MediaHash;
  3. import com.exam.service.MediaHashService;
  4. import com.exam.util.Md5Utils;
  5. import com.exam.util.UUIDUtils;
  6. import com.google.common.collect.Lists;
  7. import lombok.extern.slf4j.Slf4j;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.beans.factory.annotation.Value;
  10. import org.springframework.stereotype.Service;
  11. import org.springframework.web.multipart.MultipartFile;
  12. import java.io.File;
  13. import java.io.IOException;
  14. import java.text.DateFormat;
  15. import java.text.SimpleDateFormat;
  16. import java.util.List;
  17. import java.util.Objects;
  18. @Slf4j
  19. @Service
  20. public class FileServiceImpl {
  21. @Autowired
  22. private MediaHashService mediaHashService;
  23. //拦截的url,虚拟路径
  24. public String pathPattern = "files";
  25. //自己设置的目录
  26. private static final String fileDir = "fileStorage";
  27. public String filePath = "/home/ec2-user/www/wwwroot/online_exam" + File.separator + fileDir + File.separator;
  28. @Value(value = "${file.upload.suffix:jpg,jpeg,png,bmp,xls,xlsx,pdf}")
  29. private String fileUploadSuffix;
  30. public String upload(MultipartFile multipartFile) {
  31. try {
  32. String md5Val = Md5Utils.md5(multipartFile.getInputStream());
  33. MediaHash mediaHash = mediaHashService.findOne(md5Val);
  34. if (Objects.nonNull(mediaHash)) {
  35. return mediaHash.getUrl();
  36. }
  37. String url = uploadTo(multipartFile);
  38. MediaHash pojo = new MediaHash();
  39. pojo.setUrl(url);
  40. pojo.setMd5Val(md5Val);
  41. mediaHashService.save(pojo);
  42. return url;
  43. } catch (IOException e) {
  44. log.error("upload file error : {}", e.getMessage(), e);
  45. }
  46. return "";
  47. }
  48. public String uploadTo(MultipartFile file) {
  49. String url = null;
  50. if (file.isEmpty()) {
  51. return "the file to be uploaded is empty";
  52. }
  53. List<String> suffixList = Lists.newArrayList(fileUploadSuffix.split(","));
  54. try {
  55. //校验文件后缀
  56. String originalFilename = file.getOriginalFilename();
  57. String suffix = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
  58. if (!suffixList.contains(suffix)) {
  59. return "unsupported file format";
  60. }
  61. //首次需生成目录
  62. File folder = new File(filePath);
  63. if (!folder.exists()) {
  64. folder.mkdirs();
  65. }
  66. String fileName = timeFormat(System.currentTimeMillis()) + UUIDUtils.lowerCaseNoSeparatorUUID() + "." + suffix;
  67. String absolutePath = filePath + fileName;
  68. file.transferTo(new File(absolutePath));
  69. String separator = "/";
  70. String path = separator + pathPattern + separator + fileName;
  71. url = "http://52.25.81.116:8080" + path;
  72. } catch (Exception e) {
  73. log.error("upload file error : {}", e.getMessage(), e);
  74. }
  75. return url;
  76. }
  77. public static String timeFormat(Long time) {
  78. if (Objects.isNull(time)) {
  79. return null;
  80. }
  81. DateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
  82. return sdf.format(time);
  83. }
  84. }
MediaHashService
  1. package com.exam.serviceimpl;
  2. import com.exam.entity.MediaHash;
  3. import com.exam.mapper.MediaHashMapper;
  4. import com.exam.service.MediaHashService;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.stereotype.Service;
  7. @Service
  8. public class MediaHashImpl implements MediaHashService {
  9. @Autowired
  10. private MediaHashMapper mapper;
  11. @Override
  12. public MediaHash findOne(String md5Val) {
  13. return mapper.findOne(md5Val);
  14. }
  15. @Override
  16. public boolean save(MediaHash pojo) {
  17. return mapper.save(pojo);
  18. }
  19. }
  20. //就两个很简单的方法,一个根据md5Val查询MediaHash 对象,一个就是insert,就不贴mapper了

Md5Utils

  1. package com.exam.util;
  2. import org.apache.commons.codec.binary.Hex;
  3. import java.io.FileNotFoundException;
  4. import java.io.IOException;
  5. import java.io.InputStream;
  6. import java.nio.charset.StandardCharsets;
  7. import java.security.MessageDigest;
  8. import java.security.NoSuchAlgorithmException;
  9. public class Md5Utils {
  10. private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
  11. static MessageDigest MD5 = null;
  12. static {
  13. try {
  14. MD5 = MessageDigest.getInstance("MD5");
  15. } catch (NoSuchAlgorithmException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. public static String md5(String plainText, String salt) {
  20. MD5.reset();
  21. MD5.update(plainText.getBytes(StandardCharsets.UTF_8));
  22. MD5.update(salt.getBytes());
  23. byte[] bytes = MD5.digest();
  24. int j = bytes.length;
  25. char[] str = new char[j * 2];
  26. int k = 0;
  27. for (byte b : bytes) {
  28. str[k++] = HEX_DIGITS[b >>> 4 & 0xf];
  29. str[k++] = HEX_DIGITS[b & 0xf];
  30. }
  31. return new String(str);
  32. }
  33. public static String md5(InputStream fileInputStream) {
  34. try {
  35. byte[] buffer = new byte[8192];
  36. int length;
  37. while ((length = fileInputStream.read(buffer)) != -1) {
  38. MD5.reset();
  39. MD5.update(buffer, 0, length);
  40. }
  41. return new String(Hex.encodeHex(MD5.digest()));
  42. } catch (FileNotFoundException e) {
  43. e.printStackTrace();
  44. return null;
  45. } catch (IOException e) {
  46. e.printStackTrace();
  47. return null;
  48. } finally {
  49. try {
  50. if (fileInputStream != null) {
  51. fileInputStream.close();
  52. }
  53. } catch (IOException e) {
  54. e.printStackTrace();
  55. }
  56. }
  57. }
  58. }

UUIDUtils
  1. package com.exam.util;
  2. import java.util.Random;
  3. import java.util.UUID;
  4. public final class UUIDUtils {
  5. public static String[] chars = new String[]{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n",
  6. "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8",
  7. "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
  8. "U", "V", "W", "X", "Y", "Z"};
  9. private UUIDUtils() {
  10. }
  11. /**
  12. * 生成小写的uuid
  13. */
  14. public static String lowerCaseUUID() {
  15. return UUID.randomUUID().toString().toLowerCase();
  16. }
  17. /**
  18. * 生成大写的uuid
  19. */
  20. public static String upperCaseUUID() {
  21. return lowerCaseUUID().toUpperCase();
  22. }
  23. /**
  24. * 生成小写没有分隔符的uuid
  25. */
  26. public static String lowerCaseNoSeparatorUUID() {
  27. return lowerCaseUUID().replace("-", "");
  28. }
  29. /**
  30. * 生成大写没有分隔符的uuid
  31. */
  32. public static String upperCaseNoSeparatorUUID() {
  33. return lowerCaseNoSeparatorUUID().toUpperCase();
  34. }
  35. /**
  36. * 生成短uuid
  37. */
  38. public static String shortUUID() {
  39. StringBuffer shortBuffer = new StringBuffer();
  40. String uuid = UUID.randomUUID().toString().replace("-", "");
  41. for (int i = 0; i < 8; i++) {
  42. String str = uuid.substring(i * 4, i * 4 + 4);
  43. int x = Integer.parseInt(str, 16);
  44. shortBuffer.append(chars[x % 0x3E]);
  45. }
  46. return shortBuffer.toString();
  47. }
  48. /**
  49. * 生成纯数字uuid
  50. */
  51. public static String numUUID(int length) {
  52. Random random = new Random();
  53. StringBuilder s = new StringBuilder();
  54. for (int i = 0; i < length; i++) {
  55. s.append(random.nextInt(9));
  56. }
  57. return s.toString();
  58. }
  59. }

表结构 

然后一个简单的上传接口就完成了。后续有需要可能会再次接入AWS的存储桶来进行文件存储,到时候在来继续写一篇博客

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/902183
推荐阅读
相关标签
  

闽ICP备14008679号