锋盈数科-知识库 Logo
首页
软件开发
计算机基础
Hello Halo
新手必读
关于本知识库
登录 →
锋盈数科-知识库 Logo
首页 软件开发 计算机基础 Hello Halo 新手必读 关于本知识库
登录
  1. 首页
  2. 软件开发
  3. JAVA
  4. 【Spring Boot深度实践】打造高效安全的文件上传服务:从JWT鉴权到持久化存储

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

0
  • JAVA
  • 发布于 2024-08-15
  • 0 次阅读
黄健
黄健

本文由 简悦 SimpRead 转码, 原文地址 blog.csdn.net

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

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

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

        hutool 包

        lombok 包

uploadFilesCollectionImp Collection 实现类

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

UploadFileService  Service 接口层

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

UploadFileService  Service 实现类

/**
 * @author 青衫烟雨客
 * @date 2024/1/2
 * @description 实现文件上传服务接口的类,负责处理文件上传逻辑并存储到服务器,并将文件访问地址存入数据库
 */
@Slf4j
@Service
public class uploadFileServiceImp implements UploadFileService {
 
    // Spring自动注入HttpServletRequest对象以获取请求信息
    @Autowired
    private HttpServletRequest request;
 
    // 注入自定义工具类FileUtils实例,用于进行文件操作
    @Autowired
    private FileUtils fileUtils;
 
    // 注入上传链接地址服务实现类,用于保存上传文件的访问链接
    @Autowired
    private UploadLinkAddressService uploadLinkAddressServiceImp;
 
    // 从配置文件中读取上传服务的基础URL
    @Value("${upload.service-url}")
    private String serviceUrlBase;
 
    // 从配置文件中读取HTTP协议头
    @Value("${upload.http}")
    private String httpProtocol;
 
    //配置中读取上传文件夹路径
    @Value("${upload.path}")
    private String Path;
 
 
    /**
     * 从JWT中提取用户ID
     *
     * @param token JWT令牌
     * @return 用户ID字符串
     */
    private String extractUserIdFromToken(String token) {
        try {
            JWT jwt = JWTUtil.parseToken(token);
            jwt.getHeader(JWTHeader.TYPE);
            return jwt.getPayload("id").toString();
        } catch (Exception e) {
            log.error("解析或获取token中的用户ID时出错: {}", e.getMessage());
            return null;
        }
    }
 
    /**
     * 单个文件上传处理逻辑
     *
     * @param file   MultipartFile对象
     * @param userId 用户ID字符串
     * @return 上传成功后的文件完整访问地址,若失败则返回null
     */
    private String handleSingleFileUpload(MultipartFile file, String userId) {
        try {
            // 检查文件有效性
            if (file.isEmpty()) {
                return null;
            }
 
            // 获取文件内容字节数组
            byte[] bytes = file.getBytes();
 
            // 获取当前服务器IP地址
            String serverIp = GetServiceIp.getPublicIp();
 
            // 设置目标文件存储路径(基于用户ID)
            Path path = Paths.get(fileUtils.getUploadFolderPath(userId));
 
            // 确保目录存在,如果不存在则创建
            if (!Files.exists(path)) {
                Files.createDirectories(path);
            }
 
            // 获取上传文件的扩展名
            String extension = FileUtils.getFileExtension(file);
 
            // 生成新的唯一文件名(UUID+扩展名)
            String newFileName = IdUtil.simpleUUID() + extension;
 
            // 将文件保存到指定目录下
            FileUtils.getFileByBytes(bytes, fileUtils.getUploadFolderPath(userId), newFileName);
 
            // 构建文件的内部访问路径
            String fullFilePath = httpProtocol + serverIp + ':' + serviceUrlBase + Path + userId + '/' + newFileName;
 
            // 将文件访问地址保存到数据库或其他持久化存储中
            uploadLinkAddressServiceImp.addLinkAddress(userId, fullFilePath);
 
            return fullFilePath;
        } catch (Exception e) {
            log.error("上传文件[{}]时发生错误: {}", file.getOriginalFilename(), e.getMessage());
            return null;
        }
    }
 
    /**
     * 批量处理文件上传请求,并返回上传成功文件的详细访问地址列表。
     *
     * @param files 用户上传的MultipartFile数组
     * @return List<String> 包含已上传文件完整访问地址的列表
     */
    @Override
    public List<String> uploadFile(MultipartFile[] files) {
        List<String> uploadedFileDetails = new ArrayList<>();
        String userId;
 
        // 获取并验证用户ID
        String token = request.getHeader("token");
        if (token != null) {
            userId = extractUserIdFromToken(token);
            if (userId == null) {
                uploadedFileDetails.add("上传文件获取token失败");
                return uploadedFileDetails;
            }
        } else {
            uploadedFileDetails.add("未找到有效的token");
            return uploadedFileDetails;
        }
 
        // 遍历所有待上传的文件
        for (MultipartFile file : files) {
            String fullFilePath = handleSingleFileUpload(file, userId);
 
            if (fullFilePath != null) {
                uploadedFileDetails.add(fullFilePath);
            } else {
                uploadedFileDetails.add("部分文件上传失败,请检查后重试");
                break;
            }
        }
 
        // 检查上传成功的文件数量与原始提交文件总数是否一致
        if (uploadedFileDetails.size() == files.length) {
            log.info("所有文件上传成功");
        } else {
            int failedCount = files.length - uploadedFileDetails.size();
            log.error("{}个文件上传失败", failedCount);
            uploadedFileDetails.add("部分文件上传失败");
        }
 
        return uploadedFileDetails;
    }
}

UploadLinkAddressCollection Collection 接口

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

UploadLinkAddressCollection   Collection 实现类

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

 UploadLinkAddressService  service 接口

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

FileUtils 类

/**
 * 此类提供文件操作相关的工具方法,包括:
 * 1. 将本地文件转换成字节数组
 * 2. 根据字节数组创建并保存文件
 * 3. 获取MultipartFile对象的文件后缀名
 */
@Getter
@Slf4j
@Component
public class FileUtils {
 
    @Value("${upload.folder}")
    private String UPLOAD_FOLDER;
 
    /**
     * 将指定路径下的文件读取并转换成Byte数组
     *
     * @param path 文件在本地的完整路径
     * @return 文件内容的字节数组,若出现异常则返回null
     */
    public static byte[] getBytesByFile(String path) {
        try (FileInputStream fis = new FileInputStream(path);
             ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[4096];
            int n;
            while ((n = fis.read(buffer)) != -1) {
                bos.write(buffer, 0, n);
            }
            return bos.toByteArray();
        } catch (IOException e) {
            log.error("读取文件转换成字节数组时出错: {}", e.getMessage());
            return null;
        }
    }
 
    /**
     * 将字节数组写入到指定目录下,并以给定的文件名创建新文件
     *
     * @param bytes      要写入文件的字节数组数据
     * @param targetPath 目标文件所在目录的路径
     * @param fileName   新建文件的名称(含扩展名)
     * @throws IOException 当创建目录或写入文件时发生异常
     */
    public static void getFileByBytes(byte[] bytes, String targetPath, String fileName) throws IOException {
        Path targetDir = Paths.get(targetPath);
        if (!Files.exists(targetDir)) {
            Files.createDirectories(targetDir);
        }
        Path targetFile = targetDir.resolve(fileName);
 
        try (FileOutputStream fos = new FileOutputStream(targetFile.toFile());
             BufferedOutputStream bos = new BufferedOutputStream(fos)) {
            bos.write(bytes);
        }
    }
 
    /**
     * 从MultipartFile对象中获取原始文件的扩展名(包含点)
     *
     * @param file Spring框架提供的用于处理上传文件的对象
     * @return 原始文件的扩展名(包含点),例如 ".jpg" 或 ".txt"
     */
    public static String getFileExtension(MultipartFile file) {
        return Objects.requireNonNull(file.getOriginalFilename()).substring(file.getOriginalFilename().lastIndexOf('.'));
    }
 
    /**
     * 获取上传文件夹路径(根据用户ID)
     *
     * @param id 用户ID
     * @return 完整的上传文件夹路径(包含系统分隔符)
     */
    public String getUploadFolderPath(String id) {
        // 使用常量 UPLOAD_FOLDER 与系统分隔符拼接用户ID,生成上传文件夹路径
        return UPLOAD_FOLDER + File.separator + id;
    }
}

  UploadLinkAddressService  service 实现类

/**
 * @author 青衫烟雨客
 * @date 2024/1/22
 * @description 实现上传链接地址服务接口的类,负责将上传文件的访问链接与用户ID关联并存入数据库,
 * 以及根据用户ID查询已上传文件的链接地址列表。
 */
@Service
@Slf4j
public class uploadLinkAddressServiceImp implements UploadLinkAddressService {
 
    // Spring自动注入HttpServletRequest对象以获取请求信息
    @Autowired
    private HttpServletRequest request;
    // 注入UploadAddressLinkMapper实例,用于操作数据库
    @Autowired
    private UploadAddressLinkMapper uploadLinkAddressMapper;
 
 
    /**
     * 添加新的文件访问链接到数据库,并关联给定的用户名(或用户ID)。
     *
     * @param name        用户名或用户ID标识符
     * @param addressLink 上传文件的访问链接
     */
    @Override
    @Log
    public void addLinkAddress(String name, String addressLink) {
        // 将链接、名称和当前时间戳保存到数据库
        uploadLinkAddressMapper.addressLink(name, addressLink, LocalDateTime.now());
    }
 
    /**
     * 根据用户ID查询该用户所有已上传文件的链接地址列表。
     *
     * @return List<FileLinkAddress> 包含用户上传文件链接地址信息的对象列表
     */
    @Override
    public List<FileLinkAddress> listByID() {
 
        // 获取用户ID
        Object userID = JwtUtils.getUserID(request);
 
        // 查询并返回该用户的所有文件链接地址记录
        return uploadLinkAddressMapper.listById((String) userID);
    }
 
    /**
     * 根据id删除数据
     *
     * @param id 要删除的数据的id
     * @return 删除的行数
     */
    @Override
    public int deleteById(Integer id) {
        return uploadLinkAddressMapper.deleteById(id);
 
    }
}

R 实现类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class R {
    private Integer code;//响应码,1 代表成功; 0 代表失败
    private String msg;  //响应信息 描述字符串
    private Object data; //返回的数据
 
    //增删改 成功响应
 
 
    public static @NotNull R success() {
        return new R(1, "success" , null);
    }
 
    //查询 成功响应
 
    public static @NotNull R success(Object data) {
        return new R(1, "success" , data);
    }
 
    //失败响应
 
    public static @NotNull R error(String msg) {
        return new R(0, msg, null);
    }
}

UploadAddressLinkMapper  Mapper 层

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

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

-- 上传照片表
create table if not exists fileLink
(
    id          int auto_increment
        primary key,
    name        varchar(1000) not null,
    address     varchar(500)  not null,
    create_time datetime      null
);

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

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

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

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

标签: #软件开发 1171 #JAVA 991
相关文章

Spring 实现 3 种异步接口 2024-10-18 09:07

大家好,我是苏三~ 如何处理比较耗时的接口? 这题我熟,直接上异步接口,使用 Callable、WebAsyncTask 和 DeferredResult、CompletableFuture等均可实现。 但这些方法有局限性,处理结果仅返回单个值。在某些场景下,如果需要接口异步处理的同时,还持续不断地

重学SpringBoot3-集成Redis(五)之布隆过滤器 2024-10-08 11:24

更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-集成Redis(五)之布隆过滤器 1. 什么是布隆过滤器? * 基本概念 适用场景 2. 使用 Redis 实现布隆过滤器 * 项目依赖 Redis 配置

SpringBoot整合异步任务执行 2024-10-08 11:24

同步任务: 同步任务是在单线程中按顺序执行,每次只有一个任务在执行,不会引发线程安全和数据一致性等 并发问题 同步任务需要等待任务执行完成后才能执行下一个任务,无法同时处理多个任务,响应慢,影响用 户体验 异步任务: 异步任务是在多线程中同时执行,多个任务可以并发执行,同时处理多个请求,响应快,资源

springboot kafka多数据源,通过配置动态加载发送者和消费者 2024-10-08 11:24

前言 最近做项目,需要支持kafka多数据源,实际上我们也可以通过代码固定写死多套kafka集群逻辑,但是如果需要不修改代码扩展呢,因为kafka本身不处理额外逻辑,只是起到削峰,和数据的传递,那么就需要对架构做一定的设计了。 准备test kafka本身非常容易上手,如果我们需要单元测试,引入ja

SpringBoot 集成 Redis 2024-10-08 11:24

一:SpringBoot 集成 Redis ①Redis是一个 NoSQL(not only)数据库, 常作用缓存 Cache 使用。 ②Redis是一个中间件、是一个独立的服务器;常用的数据类型: string , hash ,set ,zset , list ③通过Redis客户端可以使用多种语

SpringBoot整合QQ邮箱 2024-10-08 11:24

SpringBoot可以通过导入依赖的方式集成多种技术,这当然少不了我们常用的邮箱,现在本章演示SpringBoot整合QQ邮箱发送邮件…. 下面按步骤进行: 1.获取QQ邮箱授权码 1.1 登录QQ邮箱 1.2 开启SMTP服务 找到下图中的SMTP服务区域,如果当前账号未开启的话自己手动开启。

目录

IT 外包服务商

  • 意见投递
  • zyf6619

软件开发应用

主菜单

  • 首页
  • 软件开发
  • 计算机基础
  • Hello Halo
  • 新手必读
  • 关于本知识库
Copyright © 2024 your company All Rights Reserved. Powered by Halo.