实现原理,前端控制每次上传1mb,后端接受1mb,并记录该分片下标,返回给前端还未上传的下标,直到所有的都上传完成
controller
@ApiOperation(value = "上传视频", notes = "上传视频", httpMethod = "POST", response = WebResult.class)
@PostMapping(value = "/uploadVideo", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)//
public AjaxResult uploadVideo(@RequestParam(name = "file") MultipartFile file,@RequestParam(name = "chunkIndex") Integer chunkIndex,@RequestParam(name = "md5") String md5,@RequestParam(name = "totalFileSize") Long totalFileSize,@RequestParam(name = "fileName") String fileName,@RequestParam(name = "userName") String userName,HttpServletRequest request) throws Exception {if (null == file || file.isEmpty()) {return AjaxResult.error("文件内容不能为空!");}return fileSystemService.uploadVideo(file, chunkIndex, md5, fileName, userName);}
service
public AjaxResult uploadVideo(MultipartFile file, Integer chunkIndex, String md5, String fileName, String userName) throws IOException {//创建存放文件夹String date = DateTime.now().toString("yyyyMMdd");String dirPath = ConstantUtils.FILE_VIDEO_PATH + userName + "/" + date + "/" + md5 + "/";File dirFile = new File(dirPath);if (!dirFile.exists()){dirFile.mkdirs();}//分区文件String relativeFilePath = dirPath + fileName;File tempFile = new File(relativeFilePath);RandomAccessFile rw = new RandomAccessFile(tempFile, "rw");//定位到分片的偏移量rw.seek(Long.parseLong(ConstantUtils.FILE_VIDEO_CHUNK_SIZE) * chunkIndex);//写入分片数据rw.write(file.getBytes());//关闭流rw.close();//读取已经分片的集合Set<Object> hasChunkList = new HashSet<>();String hasChunkKey = ConstantUtils.CHUNK_PREFIX + md5;if (redisHelper.hasKey(hasChunkKey)){Object o = redisHelper.get(hasChunkKey);JSONArray array = JSONUtil.parseArray(o);hasChunkList.addAll(array);}hasChunkList.add(chunkIndex);//最新分片下标跟新到redisredisHelper.set(hasChunkKey,hasChunkList);HashMap<String, Object> map = new HashMap<>();map.put("url",userName + "/" + date + "/" + md5 + "/" + fileName);map.put("hasList",hasChunkList);return AjaxResult.success(map);}
ConstantUtils
package com.ruoyi.file.utils;import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;/*** @author csb* @description: 获取配置文件常量数据* @date 2023/9/20*/// spring初始化bean的时候,如果bean实现了InitializingBean接口,
// 会自动调用afterPropertiesSet方法
@Component
public class ConstantUtils implements InitializingBean {@Value("${file.video.videoPath}")String videoPath;@Value("${file.video.chunkSize}")String videoChunkSize;public static String FILE_VIDEO_PATH;public static String FILE_VIDEO_CHUNK_SIZE;public static String CHUNK_PREFIX = "FILE_VIDEO";@Overridepublic void afterPropertiesSet() throws Exception {FILE_VIDEO_PATH = videoPath;FILE_VIDEO_CHUNK_SIZE = videoChunkSize;}
}
RedisHelper
package com.ruoyi.file.utils;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;@Component
public class RedisHelper {private static final Logger logger = LoggerFactory.getLogger(RedisHelper.class);public static final int CACHE_TIME_1_YEAR = 60 * 60 * 24 * 365;@Resourceprivate RedisTemplate<Object, Object> redisTemplate;/*** 读取缓存** @param key* @return*/public Object get(final String key) {return redisTemplate.opsForValue().get(key);}/*** 写入缓存*/public boolean set(final String key, Object value) {boolean result = false;try {redisTemplate.opsForValue().set(key, value);result = true;} catch (Exception e) {logger.warn("", e);}return result;}/*** 写入缓存*/public boolean set(final String key, Object value, int seconds) {boolean result = false;try {if (seconds > 0) {redisTemplate.opsForValue().set(key, value, seconds, TimeUnit.SECONDS);} else {redisTemplate.opsForValue().set(key, value);}result = true;} catch (Exception e) {logger.warn("", e);}return result;}public boolean hasKey(String key) {return redisTemplate.hasKey(key);}public boolean exist(final String key) {return redisTemplate.hasKey(key);}/*** 更新缓存*/public boolean getAndSet(final String key, Object value) {boolean result = false;try {redisTemplate.opsForValue().getAndSet(key, value);result = true;} catch (Exception e) {logger.warn("", e);}return result;}/*** 删除缓存*/public boolean delete(final String key) {boolean result = false;try {redisTemplate.delete(key);result = true;} catch (Exception e) {logger.warn("", e);}return result;}/*** 1.Redis设置多个值* 命令:HMSET myhash total 15 success 0 time "2019-12-01 11:10:15"* 2.当一个任务完成之后,把成功的次数加1* 命令:HINCRBY myhash success 1* <p>* 当前有多少个计算(有点过几次计算)* 1.列表中添加值* 命令:RPUSH mylist "hello"* 2.获取列表中所有元素* 命令:LRANGE mylist 0 -1*//*** 设置hash值,同时设置多个属性** @param key 键* @param map 多个属性值用map封装* @return*/public boolean hmset(final String key, Map<String, Object> map) {boolean result = false;try {HashOperations<Object, Object, Object> opsForHash = redisTemplate.opsForHash();opsForHash.putAll(key, map);result = true;} catch (Exception e) {logger.warn("", e);}return result;}/*** 自增值,给hash值某个属性自增** @param key 键* @param field 要自增的属性* @param num 自增值的大小,可以为正数负数* @return*/public boolean hincrby(final String key, String field, Integer num) {boolean result = false;try {HashOperations<Object, Object, Object> opsForHash = redisTemplate.opsForHash();opsForHash.increment(key, field, num);result = true;} catch (Exception e) {logger.warn("", e);}return result;}/*** 自增值,给hash值某个属性自增1** @param key 键* @param field 要自增的属性* @return*/public boolean hincrby(final String key, String field) {boolean result = false;try {HashOperations<Object, Object, Object> opsForHash = redisTemplate.opsForHash();opsForHash.increment(key, field, 1);result = true;} catch (Exception e) {logger.warn("", e);}return result;}/*** 获取hash中的所有数据** @param key* @return*/public Map<Object, Object> hgetall(final String key) {Map<Object, Object> entries = new HashMap<>();try {HashOperations<Object, Object, Object> opsForHash = redisTemplate.opsForHash();entries = opsForHash.entries(key);return entries;} catch (Exception e) {logger.warn("", e);}return entries;}/*** list操作,队列右侧添加值** @param key* @param value* @return*/public boolean rpush(final String key, Object value) {boolean result = false;try {ListOperations<Object, Object> opsForList = redisTemplate.opsForList();opsForList.rightPush(key, value);result = true;} catch (Exception e) {logger.warn("", e);}return result;}/*** 获取列表中的所有元素** @param key* @return*/public List<Object> lrange(final String key) {List<Object> range = new ArrayList<>();try {ListOperations<Object, Object> opsForList = redisTemplate.opsForList();range = opsForList.range(key, 0, -1);return range;} catch (Exception e) {logger.warn("", e);}return range;}/*** 删除list中的值** @param key list的key* @param value 要删除的list中的value* @return*/public boolean lrem(final String key, Object value) {boolean result = false;try {ListOperations<Object, Object> opsForList = redisTemplate.opsForList();opsForList.remove(key, 0, value);result = true;} catch (Exception e) {logger.warn("", e);}return result;}/*** 设置键的过期时间* @param key 键* @param expiredTimeSecond 过期时间(秒)* @return*/public boolean setKeyExpiredTime(String key, Long expiredTimeSecond){return this.redisTemplate.expire(key, expiredTimeSecond,TimeUnit.SECONDS);}}