当前位置:   article > 正文

【Spring Boot深度实践】打造高效安全的文件上传服务:从JWT鉴权到持久化存储_spring安全的文件上传

spring安全的文件上传


        在当前数字化时代,文件上传功能是各类Web应用中不可或缺的一部分。本文将为您展示如何利用Spring Boot框架及其优秀生态构建一个优雅且安全的文件上传服务——`UploadFileServiceImp`。此服务不仅实现了批量处理上传请求、确保文件安全存储,还通过JWT令牌提取用户ID,并将文件访问地址妥善保存至数据库中。

        首先,我们关注`UploadFileServiceImp`的核心实现逻辑。该类作为服务接口`UploadFileService`的实现,注入了HttpServletRequest对象以捕获客户端请求中的重要信息,如JWT令牌。通过Hutool库解析令牌,获取并转换用户的唯一标识(ID),确保文件上传与用户身份紧密关联,提高了系统安全性。

        代码中使用了 请导包使用此代码

        hutool包
        lombok包

uploadFilesCollectionImp   Collection实现类

  1. /**
  2. * @author 青衫烟雨客
  3. * @description 文件上传控制器实现类,负责处理客户端通过HTTP POST方式发起的多文件上传请求,并将上传的文件持久化保存到服务器指定目录。
  4. * 使用Spring MVC框架中的`@RequestMapping("/upload")`注解定义了文件上传API的基础路径。
  5. * @since 2024/02/03
  6. */
  7. @Slf4j
  8. @RestController
  9. @RequestMapping("/upload")
  10. public class uploadFilesCollectionImp {
  11. @Autowired
  12. private uploadFileServiceImp uploadFileServiceImp;
  13. /**
  14. * 处理POST方法的多文件上传请求,接收一个MultipartFile类型的数组参数。
  15. *
  16. * @param files 客户端提交的待上传文件集合
  17. * @return Result 对象封装了上传结果信息。如果所有文件上传成功,则返回包含每个文件完整访问地址的Result.success对象;
  18. * 若有文件上传失败,则返回Result.error对象并附带错误提示信息。
  19. */
  20. @PostMapping
  21. public R upload(@RequestParam("files") MultipartFile[] files) {
  22. try {
  23. List<String> uploadFileUrls = uploadFileServiceImp.uploadFile(files);
  24. // 如果上传成功,则返回包含文件URL的R.success对象
  25. if (!CollectionUtils.isEmpty(uploadFileUrls)) {
  26. return R.success(uploadFileUrls);
  27. }
  28. // 如果上传失败且uploadFileUrls为空(假设这代表失败)
  29. return R.error("文件上传失败");
  30. } catch (RuntimeException e) {
  31. // 捕获自定义异常,如文件上传过程中可能出现的问题
  32. log.error("文件上传时发生错误:{}", e.getMessage());
  33. return R.error("文件上传失败:" + e.getMessage());
  34. } catch (Exception e) {
  35. // 其他未知异常
  36. log.error("文件上传时发生未知错误:{}", e.getMessage());
  37. return R.error("文件上传过程中出现未知错误");
  38. }
  39. }
  40. }

UploadFileService  Service接口层

  1. /**
  2. * @author 青衫烟雨客
  3. * @description 文件上传服务接口,定义了处理文件批量上传业务逻辑的方法。
  4. * @date 创建时间: 2024/2/7
  5. */
  6. public interface UploadFileService {
  7. /**
  8. * 批量上传文件方法。该方法接收一个MultipartFile数组,代表用户要上传的多个文件,
  9. * 并返回一个包含所有成功上传文件访问链接地址的字符串列表。
  10. *
  11. * @param files MultipartFile[] 用户选择并提交的多个待上传文件对象
  12. * @return List<String> 成功上传文件的网络访问链接地址列表,每个地址对应一个上传成功的文件
  13. */
  14. List<String> uploadFile(MultipartFile[] files);
  15. }

UploadFileService  Service实现类

  1. /**
  2. * @author 青衫烟雨客
  3. * @date 2024/1/2
  4. * @description 实现文件上传服务接口的类,负责处理文件上传逻辑并存储到服务器,并将文件访问地址存入数据库
  5. */
  6. @Slf4j
  7. @Service
  8. public class uploadFileServiceImp implements UploadFileService {
  9. // Spring自动注入HttpServletRequest对象以获取请求信息
  10. @Autowired
  11. private HttpServletRequest request;
  12. // 注入自定义工具类FileUtils实例,用于进行文件操作
  13. @Autowired
  14. private FileUtils fileUtils;
  15. // 注入上传链接地址服务实现类,用于保存上传文件的访问链接
  16. @Autowired
  17. private UploadLinkAddressService uploadLinkAddressServiceImp;
  18. // 从配置文件中读取上传服务的基础URL
  19. @Value("${upload.service-url}")
  20. private String serviceUrlBase;
  21. // 从配置文件中读取HTTP协议头
  22. @Value("${upload.http}")
  23. private String httpProtocol;
  24. //配置中读取上传文件夹路径
  25. @Value("${upload.path}")
  26. private String Path;
  27. /**
  28. * 从JWT中提取用户ID
  29. *
  30. * @param token JWT令牌
  31. * @return 用户ID字符串
  32. */
  33. private String extractUserIdFromToken(String token) {
  34. try {
  35. JWT jwt = JWTUtil.parseToken(token);
  36. jwt.getHeader(JWTHeader.TYPE);
  37. return jwt.getPayload("id").toString();
  38. } catch (Exception e) {
  39. log.error("解析或获取token中的用户ID时出错: {}", e.getMessage());
  40. return null;
  41. }
  42. }
  43. /**
  44. * 单个文件上传处理逻辑
  45. *
  46. * @param file MultipartFile对象
  47. * @param userId 用户ID字符串
  48. * @return 上传成功后的文件完整访问地址,若失败则返回null
  49. */
  50. private String handleSingleFileUpload(MultipartFile file, String userId) {
  51. try {
  52. // 检查文件有效性
  53. if (file.isEmpty()) {
  54. return null;
  55. }
  56. // 获取文件内容字节数组
  57. byte[] bytes = file.getBytes();
  58. // 获取当前服务器IP地址
  59. String serverIp = GetServiceIp.getPublicIp();
  60. // 设置目标文件存储路径(基于用户ID)
  61. Path path = Paths.get(fileUtils.getUploadFolderPath(userId));
  62. // 确保目录存在,如果不存在则创建
  63. if (!Files.exists(path)) {
  64. Files.createDirectories(path);
  65. }
  66. // 获取上传文件的扩展名
  67. String extension = FileUtils.getFileExtension(file);
  68. // 生成新的唯一文件名(UUID+扩展名)
  69. String newFileName = IdUtil.simpleUUID() + extension;
  70. // 将文件保存到指定目录下
  71. FileUtils.getFileByBytes(bytes, fileUtils.getUploadFolderPath(userId), newFileName);
  72. // 构建文件的内部访问路径
  73. String fullFilePath = httpProtocol + serverIp + ':' + serviceUrlBase + Path + userId + '/' + newFileName;
  74. // 将文件访问地址保存到数据库或其他持久化存储中
  75. uploadLinkAddressServiceImp.addLinkAddress(userId, fullFilePath);
  76. return fullFilePath;
  77. } catch (Exception e) {
  78. log.error("上传文件[{}]时发生错误: {}", file.getOriginalFilename(), e.getMessage());
  79. return null;
  80. }
  81. }
  82. /**
  83. * 批量处理文件上传请求,并返回上传成功文件的详细访问地址列表。
  84. *
  85. * @param files 用户上传的MultipartFile数组
  86. * @return List<String> 包含已上传文件完整访问地址的列表
  87. */
  88. @Override
  89. public List<String> uploadFile(MultipartFile[] files) {
  90. List<String> uploadedFileDetails = new ArrayList<>();
  91. String userId;
  92. // 获取并验证用户ID
  93. String token = request.getHeader("token");
  94. if (token != null) {
  95. userId = extractUserIdFromToken(token);
  96. if (userId == null) {
  97. uploadedFileDetails.add("上传文件获取token失败");
  98. return uploadedFileDetails;
  99. }
  100. } else {
  101. uploadedFileDetails.add("未找到有效的token");
  102. return uploadedFileDetails;
  103. }
  104. // 遍历所有待上传的文件
  105. for (MultipartFile file : files) {
  106. String fullFilePath = handleSingleFileUpload(file, userId);
  107. if (fullFilePath != null) {
  108. uploadedFileDetails.add(fullFilePath);
  109. } else {
  110. uploadedFileDetails.add("部分文件上传失败,请检查后重试");
  111. break;
  112. }
  113. }
  114. // 检查上传成功的文件数量与原始提交文件总数是否一致
  115. if (uploadedFileDetails.size() == files.length) {
  116. log.info("所有文件上传成功");
  117. } else {
  118. int failedCount = files.length - uploadedFileDetails.size();
  119. log.error("{}个文件上传失败", failedCount);
  120. uploadedFileDetails.add("部分文件上传失败");
  121. }
  122. return uploadedFileDetails;
  123. }
  124. }

UploadLinkAddressCollection Collection接口

  1. /**
  2. * @author 青衫烟雨客
  3. * @description 上传链接地址服务接口,定义了与文件链接地址管理相关的业务逻辑方法。
  4. * @date 创建时间: 2024/2/7
  5. */
  6. public interface UploadLinkAddressService {
  7. /**
  8. * 添加新的文件链接地址到数据库中。这个方法会将指定的用户(通过name字段标识)和其上传文件的访问链接关联起来。
  9. *
  10. * @param name 用户名或用户ID,用于标识文件所属的用户
  11. * @param addressLink 文件的网络访问链接地址
  12. */
  13. void addLinkAddress(String name, String addressLink);
  14. /**
  15. * 根据用户ID查询该用户的所有已上传文件链接地址列表。
  16. *
  17. * @return List<FileLinkAddress> 包含所有与给定用户ID相关联的文件链接地址信息的对象列表
  18. */
  19. List<FileLinkAddress> listByID();
  20. int deleteById(Integer id);
  21. }

UploadLinkAddressCollection   Collection实现类

  1. /**
  2. * @author 青衫烟雨客
  3. * @date 2024/1/23
  4. * @description 控制器实现类,处理上传链接地址相关接口请求
  5. */
  6. // 设置该类下所有方法的统一基础路径
  7. @Slf4j
  8. @RestController
  9. public class uploadLinkAddressCollectionImp implements UploadLinkAddressCollection {
  10. // 上传链接地址业务服务实例
  11. @Autowired
  12. private uploadLinkAddressServiceImp uploadLinkAddressServiceImp;
  13. /**
  14. * 处理查询所有上传链接地址的POST请求
  15. *
  16. * @return R 对象,包含查询结果信息
  17. */
  18. @PostMapping("/uploadList")
  19. @Log // 使用自定义的日志注解记录该方法调用
  20. public R linAddressAll() {
  21. // 调用业务层方法获取所有文件链接地址信息
  22. List<FileLinkAddress> fileLinkAddresses = uploadLinkAddressServiceImp.listByID();
  23. // 判断查询结果是否为空
  24. if (!CollectionUtils.isEmpty(fileLinkAddresses)) {
  25. // 如果查询到数据,则返回成功状态并携带查询结果
  26. log.info("查询当前用户照片成功");
  27. return R.success(fileLinkAddresses);
  28. }
  29. // 若查询不到数据,则返回失败状态及提示信息
  30. log.info("查询不到当前用户照片");
  31. return R.error("查询不到当前用户的照片");
  32. }
  33. @DeleteMapping("/deleteId/{id}")
  34. public R deleteById(@PathVariable Integer id) {
  35. int i = uploadLinkAddressServiceImp.deleteById(id);
  36. if (i != 0) {
  37. return R.success();
  38. }
  39. return R.error("删除失败,没有此图片");
  40. }
  41. }

 UploadLinkAddressService  service接口

  1. public interface UploadLinkAddressService {
  2. /**
  3. * 添加新的文件链接地址到数据库中。这个方法会将指定的用户(通过name字段标识)和其上传文件的访问链接关联起来。
  4. *
  5. * @param name 用户名或用户ID,用于标识文件所属的用户
  6. * @param addressLink 文件的网络访问链接地址
  7. */
  8. void addLinkAddress(String name, String addressLink);
  9. /**
  10. * 根据用户ID查询该用户的所有已上传文件链接地址列表。
  11. *
  12. * @return List<FileLinkAddress> 包含所有与给定用户ID相关联的文件链接地址信息的对象列表
  13. */
  14. List<FileLinkAddress> listByID();
  15. int deleteById(Integer id);
  16. }

FileUtils 类

  1. /**
  2. * 此类提供文件操作相关的工具方法,包括:
  3. * 1. 将本地文件转换成字节数组
  4. * 2. 根据字节数组创建并保存文件
  5. * 3. 获取MultipartFile对象的文件后缀名
  6. */
  7. @Getter
  8. @Slf4j
  9. @Component
  10. public class FileUtils {
  11. @Value("${upload.folder}")
  12. private String UPLOAD_FOLDER;
  13. /**
  14. * 将指定路径下的文件读取并转换成Byte数组
  15. *
  16. * @param path 文件在本地的完整路径
  17. * @return 文件内容的字节数组,若出现异常则返回null
  18. */
  19. public static byte[] getBytesByFile(String path) {
  20. try (FileInputStream fis = new FileInputStream(path);
  21. ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
  22. byte[] buffer = new byte[4096];
  23. int n;
  24. while ((n = fis.read(buffer)) != -1) {
  25. bos.write(buffer, 0, n);
  26. }
  27. return bos.toByteArray();
  28. } catch (IOException e) {
  29. log.error("读取文件转换成字节数组时出错: {}", e.getMessage());
  30. return null;
  31. }
  32. }
  33. /**
  34. * 将字节数组写入到指定目录下,并以给定的文件名创建新文件
  35. *
  36. * @param bytes 要写入文件的字节数组数据
  37. * @param targetPath 目标文件所在目录的路径
  38. * @param fileName 新建文件的名称(含扩展名)
  39. * @throws IOException 当创建目录或写入文件时发生异常
  40. */
  41. public static void getFileByBytes(byte[] bytes, String targetPath, String fileName) throws IOException {
  42. Path targetDir = Paths.get(targetPath);
  43. if (!Files.exists(targetDir)) {
  44. Files.createDirectories(targetDir);
  45. }
  46. Path targetFile = targetDir.resolve(fileName);
  47. try (FileOutputStream fos = new FileOutputStream(targetFile.toFile());
  48. BufferedOutputStream bos = new BufferedOutputStream(fos)) {
  49. bos.write(bytes);
  50. }
  51. }
  52. /**
  53. * 从MultipartFile对象中获取原始文件的扩展名(包含点)
  54. *
  55. * @param file Spring框架提供的用于处理上传文件的对象
  56. * @return 原始文件的扩展名(包含点),例如 ".jpg" 或 ".txt"
  57. */
  58. public static String getFileExtension(MultipartFile file) {
  59. return Objects.requireNonNull(file.getOriginalFilename()).substring(file.getOriginalFilename().lastIndexOf('.'));
  60. }
  61. /**
  62. * 获取上传文件夹路径(根据用户ID)
  63. *
  64. * @param id 用户ID
  65. * @return 完整的上传文件夹路径(包含系统分隔符)
  66. */
  67. public String getUploadFolderPath(String id) {
  68. // 使用常量 UPLOAD_FOLDER 与系统分隔符拼接用户ID,生成上传文件夹路径
  69. return UPLOAD_FOLDER + File.separator + id;
  70. }
  71. }

  UploadLinkAddressService  service实现类

  1. /**
  2. * @author 青衫烟雨客
  3. * @date 2024/1/22
  4. * @description 实现上传链接地址服务接口的类,负责将上传文件的访问链接与用户ID关联并存入数据库,
  5. * 以及根据用户ID查询已上传文件的链接地址列表。
  6. */
  7. @Service
  8. @Slf4j
  9. public class uploadLinkAddressServiceImp implements UploadLinkAddressService {
  10. // Spring自动注入HttpServletRequest对象以获取请求信息
  11. @Autowired
  12. private HttpServletRequest request;
  13. // 注入UploadAddressLinkMapper实例,用于操作数据库
  14. @Autowired
  15. private UploadAddressLinkMapper uploadLinkAddressMapper;
  16. /**
  17. * 添加新的文件访问链接到数据库,并关联给定的用户名(或用户ID)。
  18. *
  19. * @param name 用户名或用户ID标识符
  20. * @param addressLink 上传文件的访问链接
  21. */
  22. @Override
  23. @Log
  24. public void addLinkAddress(String name, String addressLink) {
  25. // 将链接、名称和当前时间戳保存到数据库
  26. uploadLinkAddressMapper.addressLink(name, addressLink, LocalDateTime.now());
  27. }
  28. /**
  29. * 根据用户ID查询该用户所有已上传文件的链接地址列表。
  30. *
  31. * @return List<FileLinkAddress> 包含用户上传文件链接地址信息的对象列表
  32. */
  33. @Override
  34. public List<FileLinkAddress> listByID() {
  35. // 获取用户ID
  36. Object userID = JwtUtils.getUserID(request);
  37. // 查询并返回该用户的所有文件链接地址记录
  38. return uploadLinkAddressMapper.listById((String) userID);
  39. }
  40. /**
  41. * 根据id删除数据
  42. *
  43. * @param id 要删除的数据的id
  44. * @return 删除的行数
  45. */
  46. @Override
  47. public int deleteById(Integer id) {
  48. return uploadLinkAddressMapper.deleteById(id);
  49. }
  50. }

R 实现类

  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. public class R {
  5. private Integer code;//响应码,1 代表成功; 0 代表失败
  6. private String msg; //响应信息 描述字符串
  7. private Object data; //返回的数据
  8. //增删改 成功响应
  9. public static @NotNull R success() {
  10. return new R(1, "success" , null);
  11. }
  12. //查询 成功响应
  13. public static @NotNull R success(Object data) {
  14. return new R(1, "success" , data);
  15. }
  16. //失败响应
  17. public static @NotNull R error(String msg) {
  18. return new R(0, msg, null);
  19. }
  20. }

UploadAddressLinkMapper  Mapper层

  1. @Mapper
  2. public interface UploadAddressLinkMapper {
  3. @Insert("insert into fileLink (name, address, create_time)values (#{name},#{address},#{createTime})")
  4. void addressLink(String name, String address, LocalDateTime createTime);
  5. @Select("select * from fileLink where name=#{id};")
  6. List<FileLinkAddress> listById(String id);
  7. @Delete("delete from filelink where id =#{id};")
  8. int deleteById(Integer id);
  9. }

        注意:在数据库建一个 fileLink  表来存储上传文件的路经用户姓名(用户名是JWT中的token获取到的),地址,上传时间

  1. -- 上传照片表
  2. create table if not exists fileLink
  3. (
  4. id int auto_increment
  5. primary key,
  6. name varchar(1000) not null,
  7. address varchar(500) not null,
  8. create_time datetime null
  9. );

批量处理文件上传的过程中,服务采取了细致入微的错误处理策略。当接收到MultipartFile数组时,会逐一检查每个文件的有效性。若发现空文件,则立即停止上传流程并反馈给调用方。对于每一个待上传的文件,服务执行以下关键步骤:

1. 获取文件内容字节数组。
2. 根据用户ID生成并确保目标存储路径存在,创建必要的目录结构。
3. 为文件生成一个独特的名称,结合UUID和文件扩展名。
4. 使用自定义工具类`FileUtils`将文件内容写入服务器指定位置。
5. 构建内部访问路径,添加至已上传文件的详细访问地址列表。
6. 将生成的文件访问地址持久化存储至数据库中,便于后续检索和管理。

        在完成所有文件上传后,服务会对上传成功的文件数量进行校验,如果与原始提交文件总数一致,则输出成功消息;反之则记录失败次数并提醒用户部分文件上传失败。

        总结来说,本篇所探讨的`UploadFileServiceImp`实现在提供高效稳定的文件上传功能的同时,兼顾了安全性与数据一致性,展示了Spring Boot在实际开发场景下的强大威力。通过巧妙地整合JWT令牌验证、自定义工具类以及数据库操作,这一服务组件无疑为您的项目增添了独特魅力与实用价值。

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

闽ICP备14008679号