简单分享下文件下载中心的实现和代码

本文介绍了如何在业务场景中通过异步处理实现文件下载,涉及数据库设计(如`t_file_center`表),以及使用`ExecutorService`进行并发处理,同时提到了EasyExcel和OSS在文件上传过程中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

业务中很常见的场景,就是下载. 而随着业务数据越来越大, 下载的负担也越来越重, 时间越来越久
因此经常会将其做成异步的, 先给前端返回,然后开一个线程去处理. 然后等处理完用户到一个专门的页面下载.

要实现这样的功能, 肯定要把下载的内容存起来:

-- t_train_file_center definition

CREATE TABLE `t_file_center` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `file_name` varchar(200) NOT NULL DEFAULT '' COMMENT '文件名',
  `create_id` varchar(20) NOT NULL DEFAULT '' COMMENT '创建人',
  `create_name` varchar(50) NOT NULL DEFAULT '' COMMENT '创建人名字',
  `status` int(11) NOT NULL DEFAULT '0' COMMENT '状态. 1:进行中, 2:已完成, 3: 已失效, 4:处理失败, -1: 已取消, -2:已删除',
  `file_path` varchar(500) NOT NULL DEFAULT '' COMMENT '文件路径',
  `is_delete` int(11) NOT NULL DEFAULT '0' COMMENT '是否物理删除. 1: 是',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
  `update_id` varchar(30) NOT NULL DEFAULT '' COMMENT '更新人',
  `update_name` varchar(50) NOT NULL DEFAULT '' COMMENT '更新人名字',
  `remark` varchar(1000) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`),
  KEY `file_name_IDX` (`file_name`) USING BTREE,
  KEY `create_id_IDX` (`create_id`) USING BTREE
) ENGINE=InnoDB   DEFAULT CHARSET=utf8mb4 COMMENT='批量文件导出中心';

如果是要实现比如批量压缩后打成压缩包的,还需要一个详情表来记录压缩包里的文件信息

思路

思路大概就是:

  1. 先初始化记录,状态标记为进行中
  2. 返回前端基本信息
  3. 异步处理
  4. 更新结果
  5. 定期删除过期文件(可选)

代码

    /**
     * 处理文件创建,下载, 直接写入服务器版本
     * */
    public String doSaveFile(String filename, Consumer<OutputStream> consumer, ExecutorService executorService) throws IOException {
        String path = "filecenter/tempfile" + FilesUtils.generateDirPathByTimestamp() + "/" + filename;
        //获取当前登录人
        SessionUser user = UserCtxUtil.getCurrentUser();
        //创建文件,我这个是直接在nas上创建.
        File file = fileCommonService.createFile(path);
        //创建文件结束

        //记录文件path,写入表中
        Long id = this.saveTempFile(filename, path, Status.PROCESSING, user);
        if (executorService != null) {
            //开始处理,如果提供了线程池,异步处理
            executorService.execute(() -> process(consumer, file, id, user));
        } else {
            //同步处理
            process(consumer, file, id, user);
        }
        return path;
    }

  /**
     * 记录到文件中心,默认7天后删除文件
     *
     * @param path
     * @return
     */
    @Override
    public Long saveTempFile(String filename, String path, FileCenterEnum.Status status, SessionUser user) {
        FileCenter fileCenter = new FileCenter();
        fileCenter.setFileName(filename);
        fileCenter.setFilePath(path);
        fileCenter.setStatus(status.getCode());
        if (user != null) {
            fileCenter.setCreateId(user.getEmpNumber());
            fileCenter.setCreateName(user.getName());
            fileCenter.setCreateTime(new Date());
            fileCenter.setUpdateId(user.getEmpNumber());
            fileCenter.setUpdateName(user.getName());
        }
        super.save(fileCenter);
        return fileCenter.getId();
    }

    @Override
    public void updateStatus(Long id, FileCenterEnum.Status status, String remark, SessionUser user) {
        FileCenter fileCenter = new FileCenter();
        fileCenter.setId(id);
        fileCenter.setStatus(status.getCode());
        fileCenter.setRemarkremark,1000);
        if (user != null) {
            fileCenter.setUpdateId(user.getEmpNumber());
            fileCenter.setUpdateName(user.getName());
            fileCenter.setUpdateTime(new Date());
        }
        super.updateById(fileCenter);
    }



    /**
     * 处理文件流的写入
     */
    private void process(Consumer<OutputStream> consumer, File file, Long id, SessionUser user) {
        try (OutputStream os = Files.newOutputStream(file.toPath());) {
            log.info("开始处理文件....");
            consumer.accept(os);
            log.info("处理文件结束....");
            this.updateStatus(id, Status.PROCESS_DONE, null, user);
        } catch (Exception e) {
            log.error("写入文件失败:", e);
            this.updateStatus(id, Status.PROCESS_FAILED, "写入失败:" + ExceptionUtil.getMessage(e), user);
        }
    }



       /**
     * (生成文件名用),生成时间戳格式的目录名
     * @return String
     */
    public static String generateDirPathByTimestamp() {
        StringBuilder sb = new StringBuilder();
        Date date = new Date();
        Long stamp = date.getTime();
        String formatStamp = String.format("%015d", stamp);
        for (int i = 0; i < formatStamp.length() / 3; i++) {
            sb.append("/").append(formatStamp.substring(3 * i, 3 * (i + 1)));
        }
        return sb.toString();
    }

文件状态枚举

//文件状态枚举
 public enum Status{
    /**
     *
     */
    DEFAULT(0,"未知"),
    PROCESSING(1,"进行中"),
    PROCESS_DONE(2,"已完成"),
    INVALID(3,"已失效"),
    PROCESS_FAILED(4,"处理失败"),
    CANCELED(-1,"已取消"),
    DELETE(-2,"已删除"),
    ;
    private int code;
    private String  msg;

    Status(int code, String msg) {
      this.code = code;
      this.msg = msg;
    }

    public int getCode() {
      return code;
    }

    public String getMsg() {
      return msg;
    }

    public static Status of(int code){
      for (Status value : values()) {
        if (value.getCode() == code){
          return value;
        }
      }
      return null;
    }
  }

使用示例

//收到请求
    List<List<String>> head;
    List<List<String>> data;
    ExecutorService threadPool;
    doSaveFile("文件导出.xlsx",(os->{
         EasyExcel.write(os)
                .head(head)
                .doWrite(data);
    }),threadPool)
    //返回相应
    return Result;

其他方式

以上是nas或者直接写入服务器磁盘的方式. 如果是要上传到oss,要么可以先创建在服务器本地,写入流后,再上传到oss然后更新状态为成功. 或者process方法里直接采用oss提供的方式写入
那这样process可以考虑换成Supplier:

      private void process(Supplier<ByteArrayOutputStream> supplier, File file, Long id, SessionUser user) {
        try (ByteArrayOutputStream  os = supplier.get()) {
            log.info("开始处理文件....");
            //输出流写入oss输入流
            InputStream inputStream = new ByteArrayInputStream(os.toByteArray());
            log.info("处理文件结束....");
            this.updateStatus(id, Status.PROCESS_DONE, null, user);
        } catch (Exception e) {
            log.error("写入文件失败:", e);
            this.updateStatus(id, Status.PROCESS_FAILED, "写入失败:" + ExceptionUtil.getMessage(e), user);
        }
    }

    //然后由业务来提供输出流
    //比如excel
    ByteArrayOutputStream outputStream = (ByteArrayOutputStream) excelWriter.getOutputStream();
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值