FASTDFS 文件系统分布式 支持断点续传

10 篇文章 0 订阅
3 篇文章 0 订阅

文件上传系统

最近公司在做分布式的文件存储系统,但问题出现了,文件系统使用哪一款?
最后决定用fastdfs在做,但是在springboot集成的时候出现了问题,前端使用vue组件在上传时候,因为并发比较高,直接将服务器给搞死了.原本打算将文件一块块的分片上传后再合并,然后在到fastdfs中,但集群的时候,文件上传到不同的服务器中.则没有办法在一个服务其中合并.所以最后决定将文件块直接向fastdfs中写,但是问题又来了,fastdfs创建一个文件,不能创建一块空间,只能在后边进行追加.这样的话,我们在服务器端,就不能多线程向里面跑数据了.因为必须要有顺序的上传.故此,前端只能先传第一块在传第二块,这样的传递.后端使用redis将文件信息保存,左锁机制.
最后其他服务调用我们oss服务需要将本地的一个文件保存到fastdfs中,openfeign 基本不支持,需要引入一些扩展包,进行处理,模拟表单.然后在上传.
一波三折啊.最后实现了.
下面是我的代码,高手别喷哦!!!^^

OSS系统代码
这是系统目录结构结构

import com.github.tobato.fastdfs.FdfsClientConfig;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableMBeanExport;
import org.springframework.context.annotation.Import;
import org.springframework.jmx.support.RegistrationPolicy;

@Configuration
@Import(FdfsClientConfig.class)
// Jmx重复注册bean的问题
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
public class DfsConfig {
}
package com.techhero.component.oss.config;

import cn.hutool.core.io.FileUtil;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.domain.proto.storage.DownloadByteArray;
import com.github.tobato.fastdfs.service.AppendFileStorageClient;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.*;
import java.nio.charset.Charset;

/**
 * 文件上传工具类
 */
@Slf4j
@Component
public class FastDFSClient {
    
    @Resource
    private FastFileStorageClient storageClient;
    
    @Resource
    private AppendFileStorageClient appendFileStorageClient;
    
    /**
     * 分片文件上传
     * @param group        文件上传组
     * @param inputStream  上传文件流
     * @param fileSize     当前块大小
     * @param fileName     文件名称
     * @param path         文件追加路径
     * @param offset       偏移量
     * @return
     */
    public String appendUpload(String group,InputStream inputStream,Long fileSize,String fileName,String path,Long offset){
        /*如果当前块是第一块则先添加文件*/
        if(StringUtils.isBlank(path)){
            StorePath storePath = appendFileStorageClient.uploadAppenderFile(group, inputStream, fileSize, FileUtil.extName(fileName));
            path  = storePath.getPath();
        }else{
            appendFileStorageClient.modifyFile(group, path, inputStream, fileSize,offset);
        }
        return path;
    }
    
    /*创建增量断点续传文件*/
    public String createAppendFile(String group,InputStream inputStream,Long fileSize,String fileName){
        StorePath storePath = appendFileStorageClient.uploadAppenderFile(group, inputStream, fileSize, FileUtil.extName(fileName));
        return storePath.getPath();
    }
    
    /**
     * 修改文件块
     * @param group 组名
     * @param path 路径
     * @param inputStream 文件流
     * @param fileSize 文件大小
     * @param offset 偏移量
     */
    public void modifyAppendFile(String group,String path,InputStream inputStream,Long fileSize,Long offset){
        appendFileStorageClient.modifyFile(group, path, inputStream, fileSize,offset);
    }
    
    /**
     * 上传文件
     */
    public String uploadImg(MultipartFile multipartFile) throws Exception {
        String originalFilename = multipartFile.getOriginalFilename().
                substring(multipartFile.getOriginalFilename().
                        lastIndexOf(".") + 1);
        StorePath storePath = this.storageClient.uploadImageAndCrtThumbImage(
                multipartFile.getInputStream(),
                multipartFile.getSize(), originalFilename, null);
        return storePath.getFullPath();
    }
    
    
    /**
     * 上传文件
     * @param multipartFile 文件对象
     * @return 文件访问地址
     * @throws IOException
     */
    public String uploadFile(MultipartFile multipartFile) throws IOException {
        String originalFilename = multipartFile.getOriginalFilename().
                substring(multipartFile.getOriginalFilename().
                        lastIndexOf(".") + 1);
        StorePath       storePath   = storageClient.uploadFile(multipartFile.getInputStream(),multipartFile.getSize(), originalFilename,null);
        return storePath.getFullPath();
    }
    
    /**
     * 上传文件
     * @param file 文件对象
     * @return 文件访问地址
     * @throws IOException
     */
    public String uploadFile(File file) throws IOException {
        FileInputStream inputStream = new FileInputStream (file);
        StorePath       storePath   = storageClient.uploadFile(inputStream,file.length(), FilenameUtils.getExtension(file.getName()),null);
        return storePath.getFullPath();
    }
    
    /**
     * 将一段字符串生成一个文件上传
     * @param content 文件内容
     * @param fileExtension
     * @return
     */
    public String uploadFile(String content, String fileExtension) {
        byte[]               buff      = content.getBytes(Charset.forName("UTF-8"));
        ByteArrayInputStream stream    = new ByteArrayInputStream(buff);
        StorePath            storePath = storageClient.uploadFile(stream,buff.length, fileExtension,null);
        return storePath.getFullPath();
    }
    
    /**
     * 下载文件
     *
     * @param fileUrl 文件URL
     * @return 文件字节
     * @throws IOException
     */
    public byte[] downloadFile(String fileUrl) throws IOException {
        if(fileUrl.startsWith("/")){
            fileUrl=fileUrl.substring(fileUrl.indexOf("/")+1);
        }
        String group = fileUrl.substring(0, fileUrl.indexOf("/"));
        String            path              = fileUrl.substring(fileUrl.indexOf("/") + 1);
        DownloadByteArray downloadByteArray = new DownloadByteArray();
        byte[]            bytes             = storageClient.downloadFile(group, path, downloadByteArray);
        return bytes;
    }
    
    /**
     * 删除文件
     */
    public Boolean deleteFile(String fileUrl) {
        if (StringUtils.isEmpty(fileUrl)) {
            log.info("fileUrl == >>文件路径为空...");
            return Boolean.FALSE;
        }
        try {
            StorePath storePath = StorePath.parseFromUrl(fileUrl);
            storageClient.deleteFile(storePath.getGroup(), storePath.getPath());
            return Boolean.TRUE;
        } catch (Exception e) {
            log.error("[文件服务-文件删除失败]", e);
            return Boolean.FALSE;
        }
    }
}

package com.techhero.component.oss.constant;

public interface UploadConstant {
    
    /*缓存过期时间*/
    long REDIS_EXPIRE = 60L * 60L * 12L;
    
    /*Redis存储KEY*/
    String FILE_UPLOAD = "FILE_UPLOAD:";
    
    /*当前文件路径*/
    String FILE_UPLOAD_PATH = FILE_UPLOAD + "PATH:";
    
    /*当前文件已上传大小*/
    String FILE_UPLOAD_SIZE = FILE_UPLOAD + "SIZE:";
    
    /*当前最大块号*/
    String FILE_UPLOAD_CHUNK = FILE_UPLOAD + "CHUNK:";
    
}

package com.techhero.component.oss.controller;

import com.techhero.common.model.oss.MultipartFileChunk;
import com.techhero.common.utils.req.ResBean;
import com.techhero.component.oss.service.FileUploadService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;

@Slf4j
@RestController
@RequestMapping("big")
public class BigFileUploadController {
    
    @Resource
    private FileUploadService fileUploadService;
   
    
    /**
     * 分片文件上传
     */
    @PostMapping("trunkUpload")
    public ResBean trunkUpload(MultipartFileChunk multipartFileChunk){
        return fileUploadService.trunkUpload(multipartFileChunk);
    }
    
    /**
     * 分片文件上传
     */
    @PostMapping(value = "feignUpload",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public ResBean feignUpload(MultipartFile file,
                               String identifier,
                               Long chunkSize,
                               Long totalChunks,
                               Long totalSize,
                               Long chunkNumber,
                               Long currentChunkSize,
                               String fileName
                               ){
        return fileUploadService.trunkUpload(new MultipartFileChunk(identifier,chunkSize,totalChunks,totalSize,chunkNumber,currentChunkSize,fileName,file));
    }
    
    /**
     * 获取文件路径
     */
    @GetMapping("getFilePath")
    public ResBean getFilePath(@RequestParam String identifier){
        return fileUploadService.getFilePath(identifier);
    }


}

package com.techhero.component.oss.controller;

import com.techhero.common.model.oss.MultipartFileChunk;
import com.techhero.common.utils.req.ResBean;
import com.techhero.component.oss.service.FileUploadService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * 文件上传接口
 */
@Slf4j
@RestController
@RequestMapping("small")
public class SmallFileController {
    
    /*上传文件位置*/
    @Value("${fdfs.file.path}")
    private String filePath;
    
    /*文件临时目录*/
    @Value("${fdfs.file.tempPath}")
    private String filePathTemp;
    
    @Resource
    private FileUploadService fileUploadService;
    
    /**
     * 文件上传
     * http://xxx/group1/M00/00/00/wKhIgl0n4AKABxQEABhlMYw_3Lo825.png
     */
    @PostMapping(value = "/uploadFile")
    public ResBean uploadFile(@RequestParam("file") MultipartFile file) {
        return fileUploadService.uploadFile(file);
    }
    
    /**
     * 文件删除
     */
    @GetMapping(value = "/deleteByPath")
    public ResBean deleteByPath(@RequestParam("filePath") String filePath) {
        return fileUploadService.delFileByPath(filePath);
    }
    
    /**
     * 文件下载
     * @param filePath 文件路径
     */
    @GetMapping("/download")
    public void downloadFile(@RequestParam("filePath") String filePath, HttpServletResponse response) throws Exception {
        fileUploadService.downloadFile(filePath,response);
    }
   
    
    /**
     * 分片上传(大文件)
     */
    @PostMapping("upload")
    public ResBean upload(MultipartFileChunk chunk, HttpServletRequest request) throws IOException {
        
        boolean isMultipart = ServletFileUpload.isMultipartContent(request);
        if (isMultipart) {
            MultipartFile file = chunk.getFile();
            
            if (file == null) {
                return ResBean.failed("文件未选择");
            }
            
            Long chunkNumber = chunk.getChunkNumber();
            if (chunkNumber == null) {
                chunkNumber = 0L;
            }
            
            File outFile = new File(filePathTemp + File.separator + chunk.getIdentifier(), chunkNumber + ".part");
    
            if(outFile.exists()){
                return ResBean.success("附件上传成功");
            }
            
            InputStream inputStream = file.getInputStream();
            FileUtils.copyInputStreamToFile(inputStream, outFile);
            return ResBean.success("附件上传成功");
        }
        
        return ResBean.failed("没有文件");
    }
    
    /**
     * 合并所有分片
     */
    @GetMapping("/merge")
    @ResponseBody
    public ResBean merge(String fileName, String identifier) throws Exception {
        
        File file = new File(filePathTemp + File.separator + identifier);
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            if (files != null && files.length > 0) {
                File partFile = new File(filePath + File.separator + fileName);
                for (int i = 1; i <= files.length; i++) {
                    File s = new File(filePathTemp + File.separator + identifier, i + ".part");
                    try (FileOutputStream destTempFos = new FileOutputStream(partFile, true)) {
                        FileUtils.copyFile(s, destTempFos);
                    }
                }
                //删除临时目录文件
                FileUtils.deleteDirectory(file);
                return ResBean.success(fileName);
            }
        }
        return ResBean.failed("文件上传失败");
    }
    
}

package com.techhero.component.oss.service;

import com.github.tobato.fastdfs.domain.conn.FdfsWebServer;
import com.techhero.common.bean.config.RedisCacheService;
import com.techhero.common.model.oss.MultipartFileChunk;
import com.techhero.common.utils.req.ResBean;
import com.techhero.component.oss.config.FastDFSClient;
import com.techhero.component.oss.constant.UploadConstant;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;

@Slf4j
@Service
public class FileUploadService {
    
    /**
     * 解析路径
     */
    private static final String SEPARATOR = "/";
    
    @Value("${fdfs.groupName:group1}")
    private String groupName;
    
    /**
     * 文件上传客户端
     */
    @Resource
    private FastDFSClient fastDFSClient;
    
    /**
     * 缓存
     */
    @Resource
    private RedisCacheService redisCacheService;
    
    /**
     * web地址配置类
     */
    @Resource
    private FdfsWebServer fdfsWebServer;
    
    /**
     * 简单文件上传
     *
     * @param file 文件
     */
    public ResBean uploadFile(MultipartFile file) {
        try {
            String path = fastDFSClient.uploadFile(file);
            if (!org.springframework.util.StringUtils.isEmpty(path)) {
                if (!path.startsWith("/")) {
                    return ResBean.success("/" + path);
                }
                return ResBean.success(path);
            } else {
                log.info("[文件服务-文件上传失败]@路径为空");
            }
        } catch (Exception e) {
            log.error("[文件上传失败]", e);
        }
        return ResBean.failed("网络正忙,请重试...");
    }
    
    /**
     * 文件路径
     *
     * @param filePath 文件路径
     */
    public ResBean delFileByPath(String filePath) {
        log.info("[文件删除]@ {}", filePath);
        Boolean delFlag = fastDFSClient.deleteFile(filePath);
        return ResBean.validCountBean(delFlag);
    }
    
    /**
     * 文件下载
     *
     * @param filePath 文件路径
     */
    public void downloadFile(String filePath, HttpServletResponse response) throws Exception {
        String fileName = filePath.substring(filePath.lastIndexOf(".") + 1);
        log.debug("[文件下载]-[文件名称={}]", fileName);
        byte[] bytes = fastDFSClient.downloadFile(filePath);
        response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
        response.setCharacterEncoding("UTF-8");
        try (ServletOutputStream outputStream = response.getOutputStream()) {
            outputStream.write(bytes);
            outputStream.flush();
        } catch (Exception e) {
            log.error("[文件下载-异常]", e);
        }
    }
    
    /**
     * 分片文件上传
     *
     * @param multipartFileChunk 分片文件对象
     * @describe 返回路径 M00/00/00/rBID22Da54CEOok0AAAAAJscELM782.exe
     */
    public ResBean trunkUpload(MultipartFileChunk multipartFileChunk) {
        log.info("[分片文件上传-上传数据开始]#\n{}",multipartFileChunk);
        MultipartFile multipartFile = multipartFileChunk.getFile();
        /*总块数*/
        Long totalChunks = multipartFileChunk.getTotalChunks();
        /*当前块角标*/
        Long chunkNumber = multipartFileChunk.getChunkNumber();
        /*文件唯一凭证信息*/
        String identifier = multipartFileChunk.getIdentifier();
        /*当前块大小*/
        Long currentChunkSize = multipartFile.getSize();
        
        /*第一块文件路径标记*/
        String filePath = redisCacheService.get(UploadConstant.FILE_UPLOAD_PATH + identifier);
        
        try (InputStream inputStream = multipartFile.getInputStream()) {
            if (chunkNumber.equals(1L) && filePath == null) {
                /*文件名称*/
                String fileName = multipartFileChunk.getFileName();
                
                String path = fastDFSClient.createAppendFile(groupName, inputStream, currentChunkSize, fileName);
                
                /*存放路径*/
                redisCacheService.set(UploadConstant.FILE_UPLOAD_PATH + identifier, path, UploadConstant.REDIS_EXPIRE);
                /*存放当前文件大小*/
                redisCacheService.set(UploadConstant.FILE_UPLOAD_SIZE + identifier, currentChunkSize.toString(), UploadConstant.REDIS_EXPIRE);
                /*最大块号*/
                redisCacheService.set(UploadConstant.FILE_UPLOAD_CHUNK + identifier, chunkNumber.toString(), UploadConstant.REDIS_EXPIRE);
                return ResBean.success(path);
            }
            
            
            /*-----------下面是文件已经创建,冲第二块开始,则走下边流程------------*/
            
            /*如果第一块没有上传,则其他块不允许上传*/
            if (chunkNumber > 1L && StringUtils.isBlank(filePath)) {
                return ResBean.failed("请等待头文件传输完毕,文件马上上传...");
            }
            
            /*文件最大块号*/
            String fileUploadChunk    = redisCacheService.get(UploadConstant.FILE_UPLOAD_CHUNK + identifier);
            Long   fileUploadChunkMax = Long.parseLong(fileUploadChunk) + 1L;
            if (chunkNumber > 1L
                    && StringUtils.isNotBlank(filePath)
                    && StringUtils.isNotBlank(fileUploadChunk)
                    && chunkNumber.equals(fileUploadChunkMax)) {
                /*文件已上传大小*/
                String fileUploadSizeRedis = redisCacheService.get(UploadConstant.FILE_UPLOAD_SIZE + identifier);
                Long   fileUploadSize      = Long.valueOf(fileUploadSizeRedis);
                fastDFSClient.modifyAppendFile(groupName, filePath, inputStream, currentChunkSize, fileUploadSize);
                
                long historyFileSize = currentChunkSize + fileUploadSize;
                /*当前文件已经上传大小*/
                redisCacheService.set(UploadConstant.FILE_UPLOAD_SIZE + identifier, Long.toString(historyFileSize), UploadConstant.REDIS_EXPIRE);
                /*最大块号*/
                redisCacheService.set(UploadConstant.FILE_UPLOAD_CHUNK + identifier, chunkNumber.toString(), UploadConstant.REDIS_EXPIRE);
                /*如果当前块角标与总块数相等或者大于的情况下则,设定redis设定一下超时时间,防止用户页面跳转后一直没有清理缓存*/
                if (chunkNumber >= totalChunks) {
                    /*这里只需要设定路径缓存即可,因为路径缓存超时时间设定比较早而其他缓存则刚设置,延迟不到一秒,没有并发则忽略延迟*/
                    redisCacheService.expire(UploadConstant.FILE_UPLOAD_PATH, UploadConstant.REDIS_EXPIRE);
                }
                
                return ResBean.success(filePath);
            }
            
        } catch (IOException e) {
            log.error("[分片文件上传-产生流异常]", e);
        }
        return ResBean.failed("当前文件等待上传...");
    }
    
    /**
     * 获取文件路径
     * @param identifier 文件唯一凭证
     */
    public ResBean getFilePath(String identifier) {
        String filePath = redisCacheService.get(UploadConstant.FILE_UPLOAD_PATH + identifier);
        String[] keys = new String[]{
                UploadConstant.FILE_UPLOAD_PATH + identifier,//路径
                UploadConstant.FILE_UPLOAD_SIZE + identifier,//已上传文件大小
                UploadConstant.FILE_UPLOAD_CHUNK + identifier,//当前已上传文件块
        };
        /*清除redis缓存*/
        redisCacheService.del(keys);
        String fullPath = getFullPath(fdfsWebServer.getWebServerUrl(), groupName, filePath);
        return ResBean.success(fullPath);
    }
    
    /*拼接文件访问路径*/
    private String getFullPath(String webUrl, String groupName, String filePath) {
        return (webUrl + SEPARATOR + groupName + SEPARATOR + filePath).replace(SEPARATOR.concat(SEPARATOR), SEPARATOR);
    }
    
}
server:
  port: 19908
spring:
  autoconfigure:
    exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration
  application:
    name: techhero-component-oss
  profiles:
    active: local
  cloud:
    config:
      fail-fast: true
      discovery:
        serviceId: techhero-config-server
        enabled: true
      profile: ${spring.profiles.active}
      label: ${spring.profiles.active}
security:
  basic:
    enabled: false


---
spring:
  profiles: local
eureka:
  instance:
    prefer-ip-address: true
    lease-renewal-interval-in-seconds: 5
    lease-expiration-duration-in-seconds: 20
  client:
    serviceUrl:
      defaultZone: http://techhero:techhero123@127.0.0.1:10421/eureka
    registry-fetch-interval-seconds: 10

<!-- FastDFS依赖 -->
        <dependency>
            <groupId>com.github.tobato</groupId>
            <artifactId>fastdfs-client</artifactId>
            <version>1.26.5</version>
        </dependency>
 <!--连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${druid.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.starter.version}</version>
        </dependency>
        <!--FEIGN -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
        </dependency>
        <!-- 消息总线 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>
        <!--这个包下面会沾-->
        <dependency>
            <groupId>com.techhero.common</groupId>
            <artifactId>techhero-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

common包依赖

<!--Redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--添加spring对cache的支持-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>


        <!--消息总线-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>
        <!--JWT-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>${jjwt.version}</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <dependency>
            <groupId>com.xiaoleilu</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.0.6</version>
        </dependency>
        <!-- TTL -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>transmittable-thread-local</artifactId>
            <version>${ttl.version}</version>
        </dependency>
        <!--切面增强AOP-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!-- 序列化工具 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.46</version>
        </dependency>
        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-mapper-asl</artifactId>
            <version>1.9.13</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.3.0</version>
        </dependency>
        <!-- okhttp -->
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-okhttp</artifactId>
        </dependency>
        <!--<dependency>
            <groupId>com.netflix.feign</groupId>
            <artifactId>feign-okhttp</artifactId>
            <version>8.18.0</version>
        </dependency>-->

        <!--swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>${swagger2.version}</version>
        </dependency>
        <!--配置属性加密工具-->
        <dependency>
            <groupId>com.github.ulisesbocchio</groupId>
            <artifactId>jasypt-spring-boot-starter</artifactId>
            <version>${jasypt.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>${apache.poi.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-examples</artifactId>
            <version>${apache.poi.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-excelant</artifactId>
            <version>${apache.poi.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>${apache.poi.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml-schemas</artifactId>
            <version>${apache.poi.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-scratchpad</artifactId>
            <version>${apache.poi.version}</version>
        </dependency>

        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>${fileupload.version}</version>
        </dependency>
        <!-- spring-boot-starter-data-mongodb -->
        <!--<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>-->
        <!-- 邮件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
        <!--security -->
        <!--<dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
            <version>4.2.5.RELEASE</version>
        </dependency>-->
        <dependency>
            <groupId>com.netflix.zuul</groupId>
            <artifactId>zuul-core</artifactId>
            <version>2.1.3</version>
            <scope>compile</scope>
        </dependency>
        <!--TechHero-->
<!--        <dependency>-->
<!--            <groupId>com.itechhero</groupId>-->
<!--            <artifactId>module-framework-cache</artifactId>-->
<!--            <version>1.0.1-RC1</version>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>com.itechhero</groupId>-->
<!--            <artifactId>module-framework-core</artifactId>-->
<!--            <version>1.0.1-RC1</version>-->
<!--        </dependency>-->
        <!--通用Mapper-->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper</artifactId>
            <version>${tkmapper.version}</version>
        </dependency>
        <!--数据库驱动-->
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ojdbc6</artifactId>
            <version>${oracle.version}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-core</artifactId>
            <version>1.3.6</version>
        </dependency>

        <!--<dependency>
            <groupId>com.techhero.common</groupId>
            <artifactId>techhero-common-cache</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>-->

        <!--原core包中所用到的依赖-->
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
        </dependency>
        <!--原core包中所用到的依赖-->
        <dependency>
            <groupId>net.sf.ezmorph</groupId>
            <artifactId>ezmorph</artifactId>
            <version>1.0.6</version>
        </dependency>
        <!--原core包中所用到的依赖-->
        <dependency>
            <groupId>net.sf.json-lib</groupId>
            <artifactId>json-lib</artifactId>
            <version>2.4</version>
            <type>jar</type>
            <classifier>jdk15</classifier>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.7</version>
        </dependency>
        <!--原core包中所用到的依赖-->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity</artifactId>
        </dependency>
        <!--原core包中所用到的依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
        </dependency>
        <!--原core包中所用到的依赖-->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <!--原core包中所用到的依赖-->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
        </dependency>
        <!--原core包中所用到的依赖-->
        <dependency>
            <groupId>commons-httpclient</groupId>
            <artifactId>commons-httpclient</artifactId>
            <version>3.1</version>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>4.1.1</version>
            <scope>compile</scope>
        </dependency>

        <!--quartz相关依赖-->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>2.3.0</version>
        </dependency>

        <dependency>
            <groupId>io.github.openfeign.form</groupId>
            <artifactId>feign-form</artifactId>
            <version>3.0.3</version>
        </dependency>
        <dependency>
            <groupId>io.github.openfeign.form</groupId>
            <artifactId>feign-form-spring</artifactId>
            <version>3.0.3</version>
        </dependency>

下边是common包写的类,这个是为了其他子工程不需要做任何事儿,只需要依赖于common工程就可以直接feign调用oss系统

用到的类

package com.techhero.common.bean.config.oss;

import com.techhero.common.feigin.OssFeign;
import com.techhero.common.utils.JSONUtil;
import com.techhero.common.utils.db.IdWorker;
import com.techhero.common.utils.req.ResBean;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
 * 全局提供文件上传工具类
 */
@Slf4j
@Component
public class OssClientUtils {
    
    @Resource
    private OssFeign ossFeign;
    
    public static void main(String[] args) {
        long a = 18 / 19;
        System.out.println(a);
    }
    
    /**
     * 分片文件上传
     * @param file 文件对象
     * @param splitSize 分块上传每块多大 1M=1*1024*1024
     * @param retryNum 重试次数(文件上传时候出现上传失败情况,这种情况下,重试多少次)
     * @return 返回响应对象
     */
    public ResBean upload(File file, Long splitSize, int retryNum) {
        String fileName = file.getName();
        
        /*文件总大小*/
        Long fileTotalSize = file.length();
        
        long totalChunks = fileTotalSize / splitSize;
        
        if (fileTotalSize % splitSize > 0 || totalChunks == 0) {
            totalChunks++;
        }
        /*文件唯一标识*/
        String          identifier = IdWorker.getOrderIdByUUId();
        FileItemFactory factory    = new DiskFileItemFactory(16, null);
        
        try (FileInputStream in = new FileInputStream(file)) {
            for (int i = 1; i <= totalChunks; i++) {
                FileItem     item = factory.createItem("file", "multipart/form-data", true, fileName);
                try(OutputStream out = item.getOutputStream()){
                    byte[] b    = new byte[splitSize.intValue()];
                    int    read = in.read(b);
    
                    log.debug("[读取字节长度{}-byte]", read);
                    out.write(b, 0, read);
                    out.flush();
                    MultipartFile multipartFile = new CommonsMultipartFile(item);
                    
                    /*重试机制*/
                    for (int r = 0; r < retryNum; r++) {
                        ResBean resBean = ossFeign.feignUpload(multipartFile, identifier, splitSize, totalChunks, fileTotalSize, (long) i, (long) read, fileName);
                        log.info("[FEIGN文件上传,返回信息]#{}", JSONUtil.objectToJson(resBean));
                        if (resBean.isSuccess()) {
                            log.info("[FEIGN文件上传,上传成功]#{}", resBean.getData());
                            break;
                        }
                        /*如果是最后一次,则返回上传失败*/
                        if (r == retryNum - 1) {
                            return ResBean.failed("[文件上传][" + i + "块上传" + retryNum + "次不成功..]");
                        }
                    }
                }
            }
            ResBean endResBean = ossFeign.getFilePath(identifier);
            if (endResBean.isSuccess()) {
                log.info("[FEIGN文件上传,**文件切片全部**上传成功]#{}", endResBean.getData());
                return endResBean;
            }
            
        } catch (IOException e) {
            log.error("[切割文件FEIGN调用-出现IO异常]", e);
            return ResBean.failed("出现IO异常");
        }
        
        return ResBean.failed("文件上传失败...");
    }
    
}


package com.techhero.common.feigin;

import feign.codec.Encoder;
import com.techhero.common.bean.config.oss.MultipartSupportConfig;
import com.techhero.common.constant.ServiceNameConstant;
import com.techhero.common.feigin.fallback.OssFeignFallback;
import com.techhero.common.utils.req.ResBean;
import feign.form.spring.SpringFormEncoder;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.cloud.netflix.feign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;

@FeignClient(path = "big", value = ServiceNameConstant.OSS_SERVICE, fallback = OssFeignFallback.class, configuration = OssFeign.MultipartSupportConfig.class)
public interface OssFeign {
    
       /*@PostMapping(value="trunkUpload",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    ResBean trunkUpload(@RequestBody MultipartFileChunk multipartFileChunk);*/
    
    /**
     * 分片文件上传
     *
     * @param identifier       唯一标识
     * @param chunkSize        分片大小
     * @param totalChunks      总分片数量
     * @param totalSize        总大小
     * @param chunkNumber      往前分片号
     * @param currentChunkSize 当前分片大小
     * @param fileName         文件名称
     * @param file             文件
     */
    @PostMapping(value = "feignUpload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    ResBean feignUpload(
            @RequestPart("file") MultipartFile file,
            @RequestParam("identifier") String identifier,
            @RequestParam("chunkSize") Long chunkSize,
            @RequestParam("totalChunks") Long totalChunks,
            @RequestParam("totalSize") Long totalSize,
            @RequestParam("chunkNumber") Long chunkNumber,
            @RequestParam("currentChunkSize") Long currentChunkSize,
            @RequestParam("fileName") String fileName
    );
    
    /**
     * 获取文件路径
     *
     * @param identifier 文件唯一标识
     */
    @GetMapping("getFilePath")
    ResBean getFilePath(@RequestParam("identifier") String identifier);
    
    @Configuration
    class MultipartSupportConfig {
        
        @Autowired
        private ObjectFactory<HttpMessageConverters> messageConverters;
        
        @Bean
        public Encoder feignFormEncoder() {
            return new SpringFormEncoder(new SpringEncoder(messageConverters));
        }
    }
}


package com.techhero.common.feigin.fallback;

import com.techhero.common.feigin.OssFeign;
import com.techhero.common.utils.req.ResBean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;

@Slf4j
@Service
public class OssFeignFallback implements OssFeign {
    
    /**
     * @param identifier       唯一标识
     * @param chunkSize        分片大小
     * @param totalChunks      总分片数量
     * @param totalSize        总大小
     * @param chunkNumber      往前分片号
     * @param currentChunkSize 当前分片大小
     * @param fileName         文件名称
     * @param file             文件
     */
    @Override
    public ResBean feignUpload(
            @RequestPart("file") MultipartFile file,
            @RequestParam("identifier") String identifier,
            @RequestParam("chunkSize") Long chunkSize,
            @RequestParam("totalChunks") Long totalChunks,
            @RequestParam("totalSize") Long totalSize,
            @RequestParam("chunkNumber") Long chunkNumber,
            @RequestParam("currentChunkSize") Long currentChunkSize,
            @RequestParam("fileName") String fileName
            ) {
        log.info("[文件上传->分片上传]#远程调用失败&\n{}", identifier);
        return ResBean.failed("[文件上传->分片上传]#远程调用失败,请重试..");
    }
    
    /**
     * 获取文件路径
     *
     * @param identifier 文件唯一标识
     */
    @Override
    public ResBean getFilePath(String identifier) {
        log.info("[文件上传-获取文件全路径]#远程调用失败&\n文件唯一标识={}", identifier);
        return ResBean.failed("[文件上传-获取文件全路径]#远程调用失败,请重试..");
    }
}

package com.techhero.common.model.oss;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.web.multipart.MultipartFile;

import java.io.Serializable;

@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class MultipartFileChunk implements Serializable {
    /*文件标识*/
    private String identifier;
    
    /*分块大小*/
    private Long chunkSize;
    
    /*当前文件总块数*/
    private Long totalChunks;
    
    /*当前文件总大小*/
    private Long totalSize;
    
    /*当前块号*/
    private Long chunkNumber;
    
    /*当前块大小*/
    private Long currentChunkSize;
    
    /*文件名称*/
    private String fileName;
    
    /*文件*/
    private MultipartFile file;
    
}

OK 目前上面的代码就是如此,下面是子工程调用的地方顺便搞出来吧
只需要把OssClientUtils注入到业务类里面即可

package com.techhero.base.transdata.service;


import com.techhero.common.bean.config.oss.OssClientUtils;
import com.techhero.common.utils.JSONUtil;
import com.techhero.common.utils.req.ResBean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.File;

@Slf4j
@Component("fileUpLoadService")
public class FileUpLoadServiceImpl {
    
    @Resource
    private OssClientUtils ossClientUtils;
    
    //@PostConstruct
    public void load(){
        File file=new File("/Users/cnnoter/Downloads/21_06_26 08_42_34.mp4");
        //第一个参数文件对象,第二个参数分片大小这个是10M分片大小,最后边是重试次数
        ResBean upload = ossClientUtils.upload(file, 10 * 1024 * 1024L, 5);
        log.info("传输完成\n{}", JSONUtil.objectToJson(upload));
    }
    
}

前端代码

<template>
  <div>
     <el-button icon="el-icon-circle-plus" type="primary" @click="start">打开上传文件框</el-button>
     <el-dialog
    title="上传"
    :visible.sync="dialogVisible"
    width="50%"
    class="el-dialog-global"
    >
    <div class="sec">
      <uploader :options="options" :file-status-text="statusText" ref="uploader" @file-complete="fileComplete" @complete="complete"></uploader>
    </div>
    <div slot="footer" class="dialog-footer">
      <el-button @click="dialogVisible = false">取 消</el-button>
      <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
    </div>
  </el-dialog>
  </div>
  
</template>

<script>
  import axios from 'axios';
  export default {
    data () {
      return {
        dialogVisible: false,
        options: {
          target: axios.defaults.baseURL + '/oss/big/trunkUpload',
          testChunks: false,
          maxChunkRetries: 1000, //最大自动失败重试上传次数
         simultaneousUploads:1,
          chunkSize: 10 * 1024 * 1024,//分片大小
          // checkChunkUploadedByResponse: function (chunk, message) {
          //   let objMessage = JSON.parse(message);
          //   console.log(objMessage);
          //   console.log(objMessage.success==true);
          //    return objMessage.success==true;
          // },
          processParams(params) {//每一次分片传给后台的参数,params是该方法返回的形参,包含分片信息
            console.log(params, '123');
            params.fileName=params.filename;
            return params;
            // return {//返回一个对象,会添加到每一个分片的请求参数里面
            //   chunkSize: params.chunkSize,
            //   totalSize: params.totalSize,
            //   filename: params.filename,
            //   identifier: params.identifier,
            //   totalChunks: params.totalChunks,
            //   chunkNumber: params.chunkNumber,
            // };
          }
        },
        attrs: {
          accept: 'image/*'
        },
        statusText: {
          success: '成功了',
          error: '出错了',
          uploading: '上传中',
          paused: '暂停中',
          waiting: '等待中'
        }
      }
    },
    methods: {
      start(){
        this.dialogVisible=true;
      },
      complete () {
        console.log('complete', arguments)
      },
      fileComplete () {
        console.log('file complete', arguments)
        const file = arguments[0].file
        let url = '/big/getFilePath?identifier=' + arguments[0].uniqueIdentifier
        this.$httpclient.get(url, {}, res => {
          console.log(res);
        }, 'oss')
      }
    },
    mounted () {
      this.$nextTick(() => {
        window.uploader = this.$refs.uploader.uploader
      })
    }
  }
</script>

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FastDFS是一款分布式文件系统,功能主要包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了文件大容量存储和 高性能访问的问题。FastDFS特别适合以文件为载体的在线服务,如图片、视频、文档等等。 FastDFS作为一款轻量级分布式文件系统,版本V6.01代码量6.3万行。FastDFS用C语言实现,支持Linux、FreeBSD、MacOS等类UNIX系统。FastDFS类似google FS,属于应用级文件系统,不是通 用的文件系统,只能通过专有API访问,目前提供了C和Java SDK,以及PHP扩展SDK。FastDFS为互联网应用量身定做,解决大容量文件存储问题 ,追求高性能和高扩展性。FastDFS可以看做是基于文件的key value存储系统,key为文件ID,value为文件内容,因此称作分布式文件存储服务更 为合适。 FastDFS的架构比较简单,如下图所示: ![architect](images/architect.png) FastDFS特点如下: 1)分组存储,简单灵活; 2)对等结构,不存在单点; 3)文件ID由FastDFS生成,作为文件访问凭证。FastDFS不需要传统的name server或meta server; 4)大 、中、小文件均可以很好支持,可以存储海量小文件; 5)一台storage支持多块磁盘,支持单盘数据恢复; 6)提供了nginx扩展模块,可 以和nginx无缝衔接; 7)支持多线程方式上传和下载文件,支持断点续传; 8)存储服务器上可以保存文件附加属性。
fastdfs基于http协议的分布式文件系统源码,基于go和js,它具有高性能、高可靠、无中心、免维护等优点。 ### 大家担心的是这么简单的文件系统,靠不靠谱,可不可以用于生产环境?答案是肯定的,正因为简单所以高效,因为简单所以稳定。 注意:使用 - 支持curl命令上传 - 支持浏览器上传 - 支持HTTP下载 - 支持多机自动同步 - 支持断点下载 - 支持配置自动生成 - 支持小文件自动合并(减少inode占用) - 支持秒传 - 支持跨域访问 - 支持一键迁移(搬迁) - 支持异地备份(特别是小文件1M以下) - 支持并行体验 - 支持断点续传([tus](https://tus.io/)) - 支持docker部署 - 支持自监控告警 - 支持图片缩放 - 支持google认证码 - 支持自定义认证 - 支持集群文件信息查看 - 使用通用HTTP协议 - 无需专用客户端(支持wget,curl等工具) - 类fastdfs - 高性能 (使用leveldb作为kv库) - 高可靠(设计极其简单,使用成熟组件) - 无中心设计(所有节点都可以同时读写) # 优点 - 无依赖(单一文件) - 自动同步 - 失败自动修复 - 按天分目录方便维护 - 支持不同的场景 - 文件自动去重 - 支持目录自定义 - 支持保留原文件名 - 支持自动生成唯一文件名 - 支持浏览器上传 - 支持查看集群文件信息 - 支持集群监控邮件告警 - 支持小文件自动合并(减少inode占用) - 支持秒传 - 支持图片缩放 - 支持google认证码 - 支持自定义认证 - 支持跨域访问 - 极低资源开销 - 支持断点续传([tus](https://tus.io/)) - 支持docker部署 - 支持一键迁移(从其他系统文件系统迁移过来) - 支持异地备份(特别是小文件) - 支持并行体验(与现有的文件系统并行体验,确认OK再一键迁移) - 支持token下载 token=md5(file_md5+timestamp) - 运维简单,
go-fastdfs是一个基于http协议的分布式文件系统,它基于大道至简的设计理念,一切从简设计,使得它的运维及扩展变得更加简单,它具有高性能、高可靠、无中心、免维护等优点。 特点: 支持curl命令上传 支持浏览器上传 支持HTTP下载 支持多机自动同步 支持断点下载 支持配置自动生成 支持小文件自动合并(减少inode占用) 支持秒传 支持跨域访问 支持一键迁移(搬迁) 支持异地备份(特别是小文件1M以下) 支持并行体验 支持断点续传(tus) 支持docker部署 支持自监控告警 支持图片缩放 支持google认证码 支持自定义认证 支持集群文件信息查看 使用通用HTTP协议 无需专用客户端(支持wget,curl等工具) 类fastdfs 高性能 (使用leveldb作为kv库) 高可靠(设计极其简单,使用成熟组件) 无中心设计(所有节点都可以同时读写) 优点: 无依赖(单一文件) 自动同步 失败自动修复 按天分目录方便维护 支持不同的场景 文件自动去重 支持目录自定义 支持保留原文件名 支持自动生成唯一文件名 支持浏览器上传 支持查看集群文件信息 支持集群监控邮件告警 支持小文件自动合并(减少inode占用) 支持秒传 支持图片缩放 支持google认证码 支持自定义认证 支持跨域访问 极低资源开销 支持断点续传(tus) 支持docker部署 支持一键迁移(从其他系统文件系统迁移过来) 支持异地备份(特别是小文件) 支持并行体验(与现有的文件系统并行体验,确认OK再一键迁移) 支持token下载 token=md5(file_md5+timestamp) 运维简单,只有一个角色(不像fastdfs有三个角色Tracker Server,Storage Server,Client),配置自动生成 每个节点对等(简化运维) 所有节点都可以同时读写   go-fastdfs分布式文件系统 更新日志: v1.4.2 修复图片缩放过大服务出现退出问题
### 回答1: FastDFS是一个开源的轻量级分布式文件系统,用于分布式存储和管理大规模的文件。在使用FastDFS进行断点续传下载时,可以结合Java语言来实现。 在Java中,首先需要引入FastDFS的相关依赖库,比如fastdfs-client-java。然后,通过FastDFSJava API来实现断点续传下载的功能。 具体实现步骤如下: 1. 首先,需要获取待下载文件的文件元数据,如文件名、文件大小等信息,可以通过FastDFS的API来获取。 2. 然后,根据文件元数据和下载路径,创建一个本地文件,用于存储下载的文件内容。 3. 接着,通过FastDFS的API,通过指定的文件偏移量和下载长度来读取文件内容,将文件内容写入到本地文件中。同时,记录已下载的文件长度。 4. 当下载完成时,关闭与FastDFS的连接,并完成断点续传下载的过程。 需要注意的是,在断点续传下载前,需要判断是否已有部分文件已经下载完成,如果是,则可以根据已有的文件长度来设置文件偏移量和下载长度,从而进行断点续传下载。 总而言之,Java可以通过调用FastDFS的API来实现断点续传下载功能,具体的实现步骤包括获取文件元数据、创建本地文件、通过指定的偏移量和长度读取文件内容,并将内容写入到本地文件中,最后完成断点续传下载过程。 ### 回答2: FastDFS是一个开源的轻量级分布式文件系统,它主要用于解决大规模数据存储和分布式文件访问的问题。FastDFS完整的文件传输过程可以分为文件上传和文件下载两部分。而断点续传则是指在网络异常或用户主动中断的情况下,能够恢复之前未传输完成的文件传输。 在Java中实现FastDFS断点续传下载可以通过以下步骤: 1. 首先,通过FastDFS提供的Java客户端API,连接到FastDFS服务器。 2. 检查本地文件系统,判断是否存在已下载的临时文件。 3. 如果存在未下载完成的临时文件,获取已下载字节数并记录。 4. 通过FastDFS的文件下载API,设置偏移量为已下载的字节数。 5. 将下载的文件片段追加到本地临时文件中,并更新已下载的字节数。 6. 重复步骤4和步骤5,直到下载完成。 7. 删除临时文件并保存完整的文件。 在每次下载时,需要记录已下载的字节数,以便下次下载时可以根据该偏移量进行断点续传。可以将该信息保存在本地文件系统的临时文件中,或者通过数据库等方式进行持久化存储。 断点续传的实现可以大大提高文件传输的可靠性和效率,特别是在网络不稳定或文件较大的情况下。通过合理地记录已下载的字节数,并使下载从中断处继续,可以减少重复传输的数据,节省带宽和时间。 总之,使用Java可以通过FastDFS的API实现断点续传下载。这样可以提高文件传输的可靠性和效率,为用户提供更良好的体验。 ### 回答3: FastDFS是一个分布式文件存储系统,它采用了文件切割、分布式存储和文件索引等技术,可以高效地存储和管理大规模的文件。在Java中,可以使用FastDFS提供的客户端API来实现断点续传下载功能。 在实现断点续传下载的过程中,首先需要通过FastDFS客户端API获取文件的元数据信息,包括文件大小和分片信息等。然后,根据已下载的文件大小,确定接下来要从哪个分片开始下载。 接下来,使用Java的IO流技术将分片数据写入本地文件中。为了实现断点续传功能,可以通过设置HTTP请求的Range头字段来指定下载的起始位置,从而避免重复下载已经下载过的部分。 在下载过程中,可以采用多线程的方式进行并发下载,提高下载速度。可以将文件分成多个连续的范围,并为每个范围创建一个线程进行下载。在每个线程下载完成后,将下载的数据写入本地文件中相应的位置。 另外,在断点续传下载的过程中,需要实时记录已下载的文件大小,并及时保存断点信息。这样,当下载中断时,下次继续下载时可以根据已下载的文件大小和分片信息来确定需要继续下载的位置,从而实现断点续传的功能。 总之,通过使用FastDFS的客户端API和Java的IO流技术,可以实现断点续传下载功能。通过合理地划分分片和利用多线程进行并发下载,可以提高下载速度。并通过记录已下载的文件大小和断点信息,实现下载中断后的断点续传

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值