1、引入fastdfs

com.github.tobato

fastdfs-client

1.25.2-RELEASE

cn.hutool

hutool-all

4.0.12

2、编写控制层Controller

@GetMapping("/check_before_upload")

@ApiOperation("分片上传前的检测")

public RespMsgBean checkBeforeUpload(@RequestParam("userId") Long userId, @RequestParam("fileMd5") String fileMd5) {

return fileService.checkFile(userId, fileMd5);

}

@PostMapping("/upload_big_file_chunk")

@ApiOperation("分片上传大文件")

public RespMsgBean uploadBigFileChunk(@RequestParam("file") @ApiParam(value="文件",required=true) MultipartFile file,

@RequestParam("userId") @ApiParam(value="用户id",required=true) Long userId,

@RequestParam("fileMd5") @ApiParam(value="文件MD5值",required=true) String fileMd5,

@RequestParam("fileName") @ApiParam(value="文件名称",required=true) String fileName,

@RequestParam("totalChunks") @ApiParam(value="总块数",required=true) Integer totalChunks,

@RequestParam("chunkNumber") @ApiParam(value="当前块数",required=true) Integer chunkNumber,

@RequestParam("currentChunkSize") @ApiParam(value="当前块的大小",required=true) Integer currentChunkSize,

@RequestParam("bizId") @ApiParam(value="业务Id",required=true)String bizId,

@RequestParam("bizCode") @ApiParam(value="业务编码",required=true)String bizCode) {

return fileService.uploadBigFileChunk(file, userId, fileMd5, fileName, totalChunks, chunkNumber, currentChunkSize, bizId, bizCode);

}

3、编写业务接口以及实现类

package com.xxxx.cloud.platfrom.common.file.service.impl;

import cn.hutool.core.convert.Convert;

import cn.hutool.core.io.FileUtil;

import cn.hutool.core.util.StrUtil;

import com.alibaba.fastjson.JSONObject;

import com.xxxx.cloud.platfrom.common.pojo.protocol.RespMsgBean;

import com.github.tobato.fastdfs.domain.StorePath;

import com.github.tobato.fastdfs.service.AppendFileStorageClient;

import com.google.gson.Gson;

import org.apache.commons.lang.StringUtils;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.data.redis.core.RedisTemplate;

import org.springframework.stereotype.Service;

import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;

import javax.imageio.ImageIO;

import javax.servlet.http.HttpServletRequest;

import java.awt.image.BufferedImage;

import java.io.*;

import java.net.URLDecoder;

import java.util.ArrayList;

import java.util.List;

import java.util.Objects;

import java.util.Set;

import java.util.stream.Collectors;

/**

* 〈一句话功能简述〉

* 〈文件接口实现〉

* @author xxxx

* @create 2019/7/1

* @since 1.0.0

*/

@Service

public class FileServiceImpl implements FileService {

private Logger logger = LoggerFactory.getLogger(FileServiceImpl.class);

@Override

public RespMsgBean checkFile(Long userId, String fileMd5) {

RespMsgBean backInfo = new RespMsgBean();

if (StrUtil.isEmpty(fileMd5)) {

return backInfo.failure("fileMd5不能为空");

}

if (userId == null) {

return backInfo.failure("userId不能为空");

}

String userIdStr = userId.toString();

CheckFileDto checkFileDto = new CheckFileDto();

// 查询是否有相同md5文件已存在,已存在直接返回

FileDo fileDo = fileDao.findOneByColumn("scode", fileMd5);

if (fileDo != null) {

FileVo fileVo = doToVo(fileDo);

return backInfo.success("文件已存在", fileVo);

} else {

// 查询锁占用

String lockName = UpLoadConstant.currLocks + fileMd5;

Long lock = JedisConfig.incrBy(lockName, 1);

String lockOwner = UpLoadConstant.lockOwner + fileMd5;

String chunkCurrkey = UpLoadConstant.chunkCurr + fileMd5;

if (lock > 1) {

checkFileDto.setLock(1);

// 检查是否为锁的拥有者,如果是放行

String oWner = JedisConfig.getString(lockOwner);

if (StrUtil.isEmpty(oWner)) {

return backInfo.failure("无法获取文件锁拥有者");

} else {

if (oWner.equals(userIdStr)) {

String chunkCurr = JedisConfig.getString(chunkCurrkey);

if (StrUtil.isEmpty(chunkCurr)) {

return backInfo.failure("无法获取当前文件chunkCurr");

}

checkFileDto.setChunk(Convert.toInt(chunkCurr));

return backInfo.success("", null);

} else {

return backInfo.failure("当前文件已有人在上传,您暂无法上传该文件");

}

}

} else {

// 初始化锁.分块

JedisConfig.setString(lockOwner, userIdStr);

// 第一块索引是0,与前端保持一致

JedisConfig.setString(chunkCurrkey, "0");

checkFileDto.setChunk(0);

return backInfo.success("", null);

}

}

}

@Override

public RespMsgBean uploadBigFileChunk(MultipartFile file, Long userId, String fileMd5, String fileName, Integer chunks, Integer chunk, Integer chunkSize, String bizId, String bizCode) {

RespMsgBean backInfo = new RespMsgBean();

ServiceAssert.isTrue(!file.isEmpty(), 0, "文件不能为空");

ServiceAssert.notNull(userId, 0, "用户id不能为空");

ServiceAssert.isTrue(StringUtil.isNotBlank(fileMd5), 0, "文件fd5不能为空");

ServiceAssert.isTrue(!"undefined".equals(fileMd5), 0, "文件fd5不能为undefined");

ServiceAssert.isTrue(StringUtil.isNotBlank(fileName), 0, "文件名称不能为空");

ServiceAssert.isTrue(chunks != null && chunk != null && chunkSize != null, 0, "文件块数有误");

// 存储在fastdfs不带组的路径

String noGroupPath = "";

logger.info("当前文件的Md5:{}", fileMd5);

String chunkLockName = UpLoadConstant.chunkLock + fileMd5;

// 真正的拥有者

boolean currOwner = false;

Integer currentChunkInFront = 0;

try {

if (chunk == null) {

chunk = 0;

}

if (chunks == null) {

chunks = 1;

}

Long lock = JedisConfig.incrBy(chunkLockName, 1);

if (lock > 1){

logger.info("请求块锁失败");

return backInfo.failure("请求块锁失败");

}

// 写入锁的当前拥有者

currOwner = true;

// redis中记录当前应该传第几块(从0开始)

String currentChunkKey = UpLoadConstant.chunkCurr + fileMd5;

String currentChunkInRedisStr = JedisConfig.getString(currentChunkKey);

Integer currentChunkSize = chunkSize;

logger.info("当前块的大小:{}", currentChunkSize);

if (StrUtil.isEmpty(currentChunkInRedisStr)) {

logger.info("无法获取当前文件chunkCurr");

return backInfo.failure("无法获取当前文件chunkCurr");

}

Integer currentChunkInRedis = Convert.toInt(currentChunkInRedisStr);

currentChunkInFront = chunk;

if (currentChunkInFront < currentChunkInRedis) {

logger.info("当前文件块已上传");

return backInfo.failure("当前文件块已上传", "001");

} else if (currentChunkInFront > currentChunkInRedis) {

logger.info("当前文件块需要等待上传,稍后请重试");

return backInfo.failure("当前文件块需要等待上传,稍后请重试");

}

logger.info("***********开始上传第{}块**********", currentChunkInRedis);

StorePath path = null;

if (!file.isEmpty()) {

try {

if (currentChunkInFront == 0) {

JedisConfig.setString(currentChunkKey, Convert.toStr(currentChunkInRedis + 1));

logger.info("{}:redis块+1", currentChunkInFront);

try {

path = defaultAppendFileStorageClient.uploadAppenderFile(UpLoadConstant.DEFAULT_GROUP, file.getInputStream(),

file.getSize(), FileUtil.extName(fileName));

// 记录第一个分片上传的大小

JedisConfig.setString(UpLoadConstant.fastDfsSize + fileMd5, String.valueOf(currentChunkSize));

logger.info("{}:更新完fastDfs", currentChunkInFront);

if (path == null) {

JedisConfig.setString(currentChunkKey, Convert.toStr(currentChunkInRedis));

logger.info("获取远程文件路径出错");

return backInfo.failure("获取远程文件路径出错");

}

} catch (Exception e) {

JedisConfig.setString(currentChunkKey, Convert.toStr(currentChunkInRedis));

logger.error("初次上传远程文件出错", e);

return new RespMsgBean().failure("上传远程服务器文件出错");

}

noGroupPath = path.getPath();

JedisConfig.setString(UpLoadConstant.fastDfsPath + fileMd5, path.getPath());

logger.info("上传文件 result = {}", path);

} else {

JedisConfig.setString(currentChunkKey, Convert.toStr(currentChunkInRedis + 1));

logger.info("{}:redis块+1", currentChunkInFront);

noGroupPath = JedisConfig.getString(UpLoadConstant.fastDfsPath + fileMd5);

if (noGroupPath == null) {

logger.info("无法获取已上传服务器文件地址");

return new RespMsgBean().failure("无法获取已上传服务器文件地址");

}

try {

String alreadySize = JedisConfig.getString(UpLoadConstant.fastDfsSize + fileMd5);

// 追加方式实际实用如果中途出错多次,可能会出现重复追加情况,这里改成修改模式,即时多次传来重复文件块,依然可以保证文件拼接正确

defaultAppendFileStorageClient.modifyFile(UpLoadConstant.DEFAULT_GROUP, noGroupPath, file.getInputStream(),

file.getSize(), Long.parseLong(alreadySize));

// 记录分片上传的大小

JedisConfig.setString(UpLoadConstant.fastDfsSize + fileMd5, String.valueOf(Long.parseLong(alreadySize) + currentChunkSize));

logger.info("{}:更新完fastdfs", currentChunkInFront);

} catch (Exception e) {

JedisConfig.setString(currentChunkKey, Convert.toStr(currentChunkInRedis));

logger.error("更新远程文件出错", e);

return new RespMsgBean().failure("更新远程文件出错");

}

}

if (currentChunkInFront + 1 == chunks) {

// 最后一块,清空upload,写入数据库

Long size = Long.parseLong(JedisConfig.getString(UpLoadConstant.fastDfsSize + fileMd5));

// 持久化上传完成文件,也可以存储在mysql中

noGroupPath = JedisConfig.getString(UpLoadConstant.fastDfsPath + fileMd5);

String url = UpLoadConstant.DEFAULT_GROUP + "/" + noGroupPath;

FileDo fileDo = new FileDo(fileName, url, "", size, bizId, bizCode);

fileDo.setCreateUser(userId);

fileDo.setUpdateUser(userId);

FileVo fileVo = saveFileDo4BigFile(fileDo, fileMd5);

String[] deleteKeys = new String[]{UpLoadConstant.chunkCurr + fileMd5,

UpLoadConstant.fastDfsPath + fileMd5,

UpLoadConstant.currLocks + fileMd5,

UpLoadConstant.lockOwner + fileMd5,

UpLoadConstant.fastDfsSize + fileMd5

};

JedisConfig.delKeys(deleteKeys);

logger.info("***********正常结束**********");

return new RespMsgBean().success(fileVo);

}

} catch (Exception e) {

logger.error("上传文件错误", e);

return new RespMsgBean().failure("上传错误 " + e.getMessage());

}

}

} finally {

// 锁的当前拥有者才能释放块上传锁

if (currOwner) {

JedisConfig.setString(chunkLockName, "0");

}

}

logger.info("***********第{}块上传成功**********", currentChunkInFront);

return backInfo.success("第" + currentChunkInFront + "块上传成功");

}

}

4、用到的工具类

package com.xxx.cloud.platfrom.common.file.config;

import cn.hutool.core.util.StrUtil;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import redis.clients.jedis.Jedis;

import redis.clients.jedis.JedisPool;

import redis.clients.jedis.JedisPoolConfig;

import java.util.List;

/**

* @Description: JedisConfig

* @ClassName: JedisConfig

* @Author: xxx

* @Date: 2019/12/31 16:23

* @Version: 1.0

*/

public class JedisConfig {

private static Logger logger = LoggerFactory.getLogger(JedisConfig.class);

protected static final ThreadLocalthreadLocalJedis = new ThreadLocal<>();

private static JedisPool jedisPool;

/**

* Redis服务器IP

*/

private static String ADDR_ARRAY = "192.168.1.122";

/**

* Redis的端口号

*/

private static int PORT = 6379;

/**

* 访问密码

*/

private static String AUTH = "123456";

/**

* 可用连接实例的最大数目,默认值为8

* 如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。

*/

private static int MAX_ACTIVE = -1;

/**

* 控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。

*/

private static int MAX_IDLE = 16;

/**

* 等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException;

*/

private static int MAX_WAIT = 1000 * 5;

// 超时时间

private static int TIMEOUT = 1000 * 5;

/**

* 在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;

*/

private static boolean TEST_ON_BORROW = true;

/**

* redis过期时间,以秒为单位

*/

/**

* 一小时

*/

public final static int EXRP_HOUR = 60 * 60;

/**

* 一天

*/

public final static int EXRP_DAY = 60 * 60 * 24;

/**

* 一个月

*/

public final static int EXRP_MONTH = 60 * 60 * 24 * 30;

public JedisConfig() {

}

static {

initialPool();

}

/**

* 初始化Redis连接池,注意一定要在使用前初始化一次,一般在项目启动时初始化就行了

*/

public static JedisPool initialPool() {

JedisPool jp = null;

try {

JedisPoolConfig config = new JedisPoolConfig();

config.setMaxTotal(MAX_ACTIVE);

config.setMaxIdle(MAX_IDLE);

config.setMaxWaitMillis(MAX_WAIT);

config.setTestOnBorrow(TEST_ON_BORROW);

config.setTestOnCreate(true);

config.setTestWhileIdle(true);

config.setTestOnReturn(true);

config.setNumTestsPerEvictionRun(-1);

jp = new JedisPool(config, ADDR_ARRAY, PORT, TIMEOUT, AUTH);

jedisPool = jp;

threadLocalJedis.set(getJedis());

} catch (Exception e) {

e.printStackTrace();

logger.error("redis服务器异常",e);

}

return jp;

}

/**

* 获取Jedis实例,一定先初始化

* @return Jedis

*/

public static Jedis getJedis() {

boolean success = false;

Jedis jedis = null;

int i=0;

while (!success) {

i++;

try {

if (jedisPool != null) {

jedis = threadLocalJedis.get();

if (jedis == null){

jedis = jedisPool.getResource();

}else {

if(!jedis.isConnected() && !jedis.getClient().isBroken()){

threadLocalJedis.set(null);

jedis = jedisPool.getResource();

}

return jedis;

}

}else {

throw new RuntimeException("redis连接池初始化失败");

}

} catch (Exception e) {

System.out.println(Thread.currentThread().getName()+":第"+i+"次获取失败!!!");

success = false;

e.printStackTrace();

logger.error("redis服务器异常",e);

}

if (jedis != null){

success = true;

}

if (i >= 10 && i < 20){

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

if (i >= 20 && i < 30){

try {

Thread.sleep(2000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

if (i >= 30 && i < 40){

try {

Thread.sleep(3000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

if (i >= 40){

System.out.println("redis彻底连不上了~~~~(>_

5、前端代码

点击上传

只能上传jpg/png文件,且不超过500kb

写在最后,参考:https://gitee.com/zwlan/renewFastdfs

fdfs往服务器上传文件超时,FastDFS 实现大文件分片上传相关推荐

  1. nginx 上传 文件超时设置_Nginx大文件上传413和500问题排查总结

    背景 前几天上传一个300MB的文件,发现报错,这里说明一下,用户的请求会通过Nginx代理(负载均衡)到应用服务器. 413问题解决 错误信息为"413 Request Entity To ...

  2. 大文件分片上传前端框架_js实现大文件分片上传的方法

    文件夹上传:从前端到后端 文件上传是 Web 开发肯定会碰到的问题,而文件夹上传则更加难缠.网上关于文件夹上传的资料多集中在前端,缺少对于后端的关注,然后讲某个后端框架文件上传的文章又不会涉及文件夹. ...

  3. 上传问题总结(文件大小检测,大文件上传)

    PHP上传问题总结(文件大小检测,大文件上传) 由于涉及到本地和服务器两方面的安全问题,所以基于input type="file"形式的页面文件上传一直处于一个很尴尬的位置.一方面 ...

  4. java rmi 文件传输_JAVA-RMI实现大文件传输

    在使用java-rmi的过程中,必然会遇到一个文件上传的问题,由于在rmi中无法传输文件流(比如rmi中的方法参数不能是FileInputStream之类的),那么我们只好选择一种折中的办法,就是先用 ...

  5. java 读取大文件内容_java读取大文件

    java一般读取文件时,将文件文内容全部加在到内存,然后读取,但是这种读取方式很明显不适合读取大文件,在进行大文件处理时,考虑到内存有限,采用分次读取的方式. java分次读取文件内容有三种方式, 1 ...

  6. Python组织文件 实践:查找大文件、 用Mb、kb显示文件尺寸 、计算程序运行时间...

    这个小程序很简单原本没有记录下来的必要,但在编写过程中又让我学到了一些新的知识,并且遇到了一些不能解决的问题,然后,然后就很有必要记录一下. 这个程序的关键是获取文件大小,本来用 os.path.ge ...

  7. Python组织文件 实践:查找大文件、 用Mb、kb显示文件尺寸 、计算程序运行时间

    这个小程序很简单原本没有记录下来的必要,但在编写过程中又让我学到了一些新的知识,并且遇到了一些不能解决的问题,然后,然后就很有必要记录一下. 这个程序的关键是获取文件大小,本来用 os.path.ge ...

  8. 大文件表空间+创建大文件表空间+查询数据库表空间类型信息+查询数据库表空间类型信息...

    1用于解决存储文件大小不够的问题 2与普通表空间不同的地方在于大文件表空间只对应唯一一个数据文件或临时文件,普通表空间可最多1022个数据文件或临时文件 3大文件表空间对应文件可达4G个数据块大小,普 ...

  9. python大文件排序_Python如何实现大文件排序?Python大文件排序的实现方法

    Python如何实现大文件排序?Python大文件排序的实现方法 本文实例讲述了Python实现大文件排序的方法.分享给大家供大家参考.具体实现方法如下: import gzip import os ...

最新文章

  1. 如何才能识别市场趋势?[转]
  2. R语言ggplot2可视化更改轴上数字的格式(显示格式)实战
  3. bootstrap表格某一列值相同时_Bootstrap-table实现动态合并相同行(表格同名合并)
  4. 大话WinCE与WinXP应用程序开发的差异性
  5. html个人主页_前端性能优化实践 之 百度App个人主页优化
  6. android客户端设计,Android客户端设计.ppt
  7. jquery图表统计插件-highcharts详解
  8. python pdfminer3k_python 使用pdfminer3k 读取PDF文档的例子
  9. linux 向终端 发送消息,Linux向不同的连接终端窗口发送消息
  10. c#中利用keybd_event函数+自定义软键盘实现中文输入时的问题
  11. grafana对接zabbix
  12. 微服务架构集成RabbitMQ给用户推送消息(发送短信,发送邮件,发送站内信息)
  13. 安装Matlab R2022a/64位
  14. css px em rem % vw vh vm 区别
  15. Linux常用命令练习
  16. fs.mkdir创建目录报错
  17. FreeBSD软件安装(转)
  18. java打印一个正方形(实心)方法不唯一
  19. 黑客文化 何处的中国计算机文化(二)
  20. Cisco Packet Tracer 无线局域网部署

热门文章

  1. 【SpringCloud】Ribbon 负载均衡
  2. 【java】docker容器内使用jstack等命令报错 The VM does not support the attach mechanism
  3. 【HttpClient】httpclient之post 方法(参数为Map类型)
  4. 95-140-108-源码-transform-算子process
  5. 【Java】 定时任务Timer与ScheduledExecutorService的区别
  6. 【Flink】Flink TimeServer 之 timerService().registerProcessingTimeTimer
  7. 20-190-090-安装-Flink集群安装 flink-1.9.0
  8. Spring: Spring AOP 方面/切面(Aspect)
  9. android data文件夹操作
  10. python考研成绩什么时候出来_这里有最新的调剂、成绩公布时间以及复试信息