FastDFS结合vue-simple-uploader实现断点续传

参考:https://gitee.com/zwlan/renewFastdfs

1. maven依赖

<!--<fast.clent.version>1.26.2</fast.clent.version>-->
<!--<hutool.all.version>4.0.12</hutool.all.version>-->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>${hutool.all.version}</version>
</dependency>

<dependency>
    <groupId>com.github.tobato</groupId>
    <artifactId>fastdfs-client</artifactId>
    <version>${fast.clent.version}</version>
</dependency>

2. 部分配置

#redis

spring:  
  redis:
    open: true  # 是否开启redis缓存  true开启   false关闭
    database: 0
    host: xx.xx.xx.xx # ip
    port: xxxx # port
    password:   # 密码(默认为空)
    timeout: 6000ms  # 连接超时时长(毫秒)
    jedis:
      pool:
        max-active: 1000  # 连接池最大连接数(使用负值表示没有限制)
        max-wait: -1ms      # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-idle: 10      # 连接池中的最大空闲连接
        min-idle: 5       # 连接池中的最小空闲连接

#FastDFS Client
fdfs:
  so-timeout: 1501
  connect-timeout: 601
  thumb-image: #缩略图生成参数
    width: 150
    height: 150
  tracker-list: #TrackerList参数,支持多个
    - xx.xx.xx.xx:22122  #ip:port
  pool:
    max-total: 153
    jmx-name-base: 1
    jmx-name-prefix: 1

# nginx 反向代理之后的查看资源路径
fastDFS:
  file:
    server:
      url: http://xx.xx.xx.xx:8091 # ip:port
    show:
      url: http://xx.xx.xx.xx:8090 

#FastDFS临时上传路径
file:
  upload:
    temp:
      path: /data0/fastdfs/temp

3. 代码

package cn.longrace.wisdom.modules.uploader.controller;

import cn.hutool.core.io.FileUtil;
import cn.longrace.wisdom.common.utils.RedisUtils;
import cn.longrace.wisdom.common.vo.R;
import cn.longrace.wisdom.modules.sys.controller.AbstractController;
import cn.longrace.wisdom.modules.uploader.common.UpLoadConstant;
import cn.longrace.wisdom.modules.uploader.entity.Chunk;
import cn.longrace.wisdom.modules.uploader.entity.FileInfo;
import cn.longrace.wisdom.modules.uploader.service.ChunkService;
import cn.longrace.wisdom.modules.uploader.service.FileInfoService;
import cn.longrace.wisdom.modules.uploader.util.FileUtils;
import com.github.tobato.fastdfs.domain.StorePath;
import com.github.tobato.fastdfs.service.AppendFileStorageClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Random;

import static cn.longrace.wisdom.modules.uploader.util.FileUtils.generatePath;

/**
 * @author 
 * @date
 */
@RestController
@RequestMapping("/new/uploader")
@Slf4j
public class NewUploadController extends AbstractController {

    @Value("${fastDFS.file.server.url}")
    private String fileUploadUrl;

    @Value("${fastDFS.file.show.url}")
    private String showFileUploadUrl;

    @Value("${file.upload.temp.path}")
    private String uploadFolder;

    @Autowired
    private AppendFileStorageClient appendFileStorageClient;

    private Long curriculumId;

    @Resource
    private ChunkService chunkService;

    @Autowired
    RedisUtils redisUtils;

    @Autowired
    FileInfoService fileInfoService;

    /**
     * 方式一:
     * 功能描述:上传文件
     *
     * @param:
     * @return:
     * @auther: 
     * @date:
     */
    @PostMapping("/chunk")
    public String uploadChunk(Chunk chunk) {
        MultipartFile file = chunk.getFile();
        log.debug("file originName: {}, chunkNumber: {}", file.getOriginalFilename(), chunk.getChunkNumber());

        try {
            String contentType = file.getContentType();
            chunk.setType(contentType);

            byte[] bytes = file.getBytes();
            Path p1 = Paths.get(generatePath(uploadFolder, chunk));
            //文件写入指定路径
            Path p = Files.write(p1, bytes);
            log.debug("文件 {} 写入成功, uuid:{}", chunk.getFilename(), chunk.getIdentifier());
            StorePath path = null;
            try {
                File f = p.toFile();
                if (redisUtils.get(chunk.getIdentifier()) == null) {
                    // 创建可续传的文件
                    path = appendFileStorageClient.uploadAppenderFile(UpLoadConstant.DEFAULT_GROUP,  new FileInputStream(f), f.length(), FileUtil.extName(chunk.getFilename()));
                    redisUtils.set(chunk.getIdentifier(), path.getPath());
                } else {
                    // 最后一个参数记录文件的偏移量
                    appendFileStorageClient.modifyFile(UpLoadConstant.DEFAULT_GROUP, redisUtils.get(chunk.getIdentifier()),  new FileInputStream(f), f.length(),(chunk.getChunkNumber() -1)*(10*1024*1024));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

            // 保存chunk
            Random rn = new Random(1000);
            chunk.setId(rn.nextLong());
            chunk.setCreator(this.getUserId());
            chunk.setCurriculumId(curriculumId);
            chunkService.saveChunk(chunk);

            return "文件上传成功";
        }catch (Exception e) {
            e.printStackTrace();
            return "后端异常...";
        }
    }

    @GetMapping("/chunk")
    public Object checkChunk(Chunk chunk, HttpServletResponse response) {
        curriculumId = chunk.getCurriculumId();
        if (chunkService.checkChunk(chunk)) {
            response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
        }
        return chunk;
    }

    @PostMapping("/mergeFile")
    public R test (FileInfo fileInfo) throws Exception {
        // TODO 截图一----------------------
        String filePath = showFileUploadUrl + "/"+ UpLoadConstant.DEFAULT_GROUP + "/" + redisUtils.get(fileInfo.getIdentifier());
        String filename = fileInfo.getFileName();
        String extension = filename.substring(filename.indexOf('.') + 1, filename.length());
        String finalExtension = "mp4";
        try {
            if (!StringUtils.isEmpty(extension) && extension.equals(finalExtension)) {
                System.out.println("==============视频截图开始================");
                String imgFileName = filename + ".jpg";
                String imgFilePath = uploadFolder + "/" + filename + ".jpg";

                FileUtils.fetchFrame(filePath, imgFilePath);
                File tempImgFile = new File(imgFilePath);
//                FileInputStream fileImgInputStream = new FileInputStream(tempImgFile);
//                MultipartFile multipartImgFile = new MockMultipartFile(imgFileName, imgFileName,
//                        ContentType.APPLICATION_OCTET_STREAM.toString(), fileImgInputStream);
//
//                String jsonImgStr = HttpUtils.upload(fileUploadUrl, multipartImgFile);
//                System.out.println("jsonImgStr-------------------------"+jsonImgStr);
                StorePath path = appendFileStorageClient.uploadAppenderFile(UpLoadConstant.DEFAULT_GROUP,  new FileInputStream(tempImgFile), tempImgFile.length(), FileUtil.extName(imgFileName));
                fileInfo.setImgLocation(UpLoadConstant.DEFAULT_GROUP + "/" + path.getPath());
                tempImgFile.delete();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        System.out.println("==============视频截图结束================");
        // UpLoadConstant.DEFAULT_GROUP 即group1
        fileInfo.setLocation(UpLoadConstant.DEFAULT_GROUP + "/" + redisUtils.get(fileInfo.getIdentifier()));
        this.fileInfoService.insert(fileInfo);
        // 删除redis中文件key
        redisUtils.delete(fileInfo.getIdentifier());
        // 删除临时文件目录
        FileUtils.deleteDir(new File(uploadFolder + "/" + fileInfo.getIdentifier()));
        return R.ok().put("fileInfo",fileInfo);
    }
}

4. 相关实体类

package cn.longrace.wisdom.modules.uploader.entity;

import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.annotations.TableName;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;

import java.io.Serializable;

/**
 * 文件分块大小
 */
@Data
@TableName("lr_teacher_resource_chunk")
public class Chunk implements Serializable {
    private Long id;
    /**
     * 当前文件块,从1开始
     */
    private Integer chunkNumber;
    /**
     * 分块大小
     */
    private Long chunkSize;
    /**
     * 当前分块大小
     */
    private Long currentChunkSize;
    /**
     * 总大小
     */
    private Long totalSize;
    /**
     * 文件标识
     */
    private String identifier;
    /**
     * 文件名
     */
    private String filename;

    /**
     * 相对路径
     */
    private String relativePath;
    /**
     * 总块数
     */
    private Integer totalChunks;
    /**
     * 文件类型
     */
    private String type;

    /**
     * 课程ID
     */
    private Long curriculumId;
    /**
     * 创建人
     */
    private Long creator;
    /**
     * 所属备课ID
     */
    private Long prepareId;
    /**
     * 所属班级ID
     */
    private Long classId;

    @TableField(exist = false)
    private MultipartFile file;
}
package cn.longrace.wisdom.modules.uploader.entity;

import com.baomidou.mybatisplus.annotations.TableName;
import lombok.Data;

import java.io.Serializable;

/**
 * 文件信息实体类
 */
@Data
@TableName("lr_teacher_resource_file_info")
public class FileInfo implements Serializable {
    private Long id;

    /**
     * 文件名称
     */
    private String fileName;
    /**
     * 文件标识
     */
    private String identifier;
    /**
     * 文件总大小
     */
    private Long totalSize;
    /**
     * 文件类型
     */
    private String type;
    /**
     * 文件路径
     */
    private String location;
    /**
     * 视频文件封面路径
     */
    private String imgLocation;
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值