当前位置: 首页 > news >正文

断点续传+测试方法完整示例

因为看不懂网上的断点续传案例,而且又不能直接复制使用,干脆自己想想写了一个。

上传入参类:

import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;/*** 断点续传接收实体类** @author kwy* @date 2024/11/18*/
@Data
@ApiModel("镜像文件断点续传接收实体类")
public class ImageFileBreakpointResumeDTO {@ApiModelProperty(value = "上传的文件流")@NotNull(message = "上传的文件流不能为空")@JsonIgnoreprivate MultipartFile multipartFile;@ApiModelProperty(value = "云命名空间")@NotBlank(message = "云命名空间不能为空")private String namespace;@ApiModelProperty(value = "镜像名称")@NotBlank(message = "镜像名称不能为空")private String name;@ApiModelProperty(value = "版本号")@NotBlank(message = "版本号不能为空")private String version;@ApiModelProperty(value = "文件上传标识")@NotBlank(message = "文件上传标识不能为空")private String taskId;@ApiModelProperty(value = "分片总数")@NotNull(message = "分片总数不能为空")private Integer numTotal;@ApiModelProperty(value = "上传到第几片")@NotNull(message = "上传到第几片不能为空")private Integer uploadNum;@ApiModelProperty(value = "文件名称")@NotBlank(message = "文件名称不能为空")private String fileName;@ApiModelProperty(value = "是否上传成功", hidden = true)private Boolean status;@ApiModelProperty(value = "分片文件路径", hidden = true)private String filePath;
}

controller层:

  @PostMapping("/breakpointResumeFile")@ApiOperation(value = "断点续传|重试上传镜像文件")public ResponseData<String> breakpointResumeFile(@Validated ImageFileBreakpointResumeDTO dto) {return ResponseData.ok(disImageMgrService.breakpointResumeFile(dto));}@PostMapping("/test")@ApiOperation(value = "断点续传|重试上传镜像文件 - 测试")public ResponseData<Boolean> test() {disImageMgrService.test();return ResponseData.ok();}/*** 为避免本地临时文件过多,清除临时分片文件* ps:请勿在用户上传的期间操作*/@ApiOperation(value = "清除临时分片文件")@PostMapping("/clearItemFile")public ResponseData<Boolean> clearItemFile() {disImageMgrService.clearItemFile();return ResponseData.ok();}

service层:
 接口

   /*** 上传镜像文件** @param dto 文件参数内容* @return 合并时返回dockerFile值,否则返回null*/String breakpointResumeFile(ImageFileBreakpointResumeDTO dto);/*** 测试断点续传*/void test();/*** 为避免本地临时文件过多,清除临时分片文件* ps:请勿在用户上传的期间操作*/void clearItemFile();

实现方法

@Overridepublic String breakpointResumeFile(ImageFileBreakpointResumeDTO dto) {MultipartFile multipartFile = dto.getMultipartFile();// 校验if (multipartFile.getSize() <= 0) {throw new CommonException("无效文件");}String taskName = "IMAGE_BREAKPOINT_RESUME_TASK_ID_" + dto.getTaskId();StringBuilder path = new StringBuilder("/imageBreakpointResumeStorage/");path.append(dto.getNamespace()).append("/").append(dto.getName()).append("/").append(dto.getVersion()).append("/").append(dto.getTaskId());File directory = new File(path.toString());if (!directory.exists()) {directory.mkdirs();}// 本次切片文件String filePath = path + "/" + multipartFile.getOriginalFilename();dto.setFilePath(filePath);Map<String, String> allRecordMap = new HashMap<>();try {// 1.判断任务是否存在(分片存储到临时目录,临时目录应定时清空,以防被垃圾分片占满,完成了再拉下来合并)if (Boolean.FALSE.equals(redisUtil.hasKey(taskName))) {allRecordMap = new HashMap<>();} else {String taskJson = redisUtil.get(taskName, String.class);if (StringUtils.isNotBlank(taskJson)) {allRecordMap = (Map<String, String>) JSONUtil.toBean(taskJson, Map.class);}}// 如果文件片存在,则认为本次是重新上传String recordJson = allRecordMap.get(dto.getUploadNum().toString());if (StringUtils.isNotBlank(recordJson)) {// 删除旧的文件FileUtil.deleteFile(filePath);}// 2.保存分片到临时目录FileUtil.uploadSingleFile(dto.getMultipartFile(), filePath);// 2.1 记录本次分片上传dto.setStatus(true);allRecordMap.put(dto.getUploadNum().toString(), JSONUtil.toJsonStr(dto));Boolean result = redisUtil.set(taskName, JSONUtil.toJsonStr(allRecordMap), 1L, TimeUnit.DAYS);if (Boolean.FALSE.equals(result)) {throw new CommonException("记录本次操作失败");}// 3.判断 文件切片上传成功数===文件总切片数,合并切片到成品目录,返回文件上传成功int successNum = 0;List<ImageFileBreakpointResumeDTO> fileList = new ArrayList<>();for (Map.Entry<String, String> entry : allRecordMap.entrySet()) {String json = entry.getValue();ImageFileBreakpointResumeDTO bean = JSONUtil.toBean(json, ImageFileBreakpointResumeDTO.class);if (Boolean.TRUE.equals(bean.getStatus())) {successNum++;fileList.add(bean);}}if (successNum == dto.getNumTotal()) {fileList.sort(Comparator.comparingInt(ImageFileBreakpointResumeDTO::getUploadNum));File finishFile = new File(path + "/" + dto.getFileName());if (finishFile.exists()) {// 如果存在则先删除FileUtils.forceDelete(finishFile);}for (ImageFileBreakpointResumeDTO item : fileList) {File itemFile = new File(item.getFilePath());FileUtils.writeByteArrayToFile(finishFile, Files.toByteArray(itemFile), true);// 删除临时文件FileUtils.forceDelete(itemFile);}// 删除上传任务redisUtil.del(taskName);return finishFile.getPath();}} catch (Exception e) {dto.setStatus(false);allRecordMap.put(dto.getUploadNum().toString(), JSONUtil.toJsonStr(dto));redisUtil.set(taskName, JSONUtil.toJsonStr(allRecordMap), 1L, TimeUnit.DAYS);LogTraceUtil.error(e);throw new CommonException("上传任务失败", e.getMessage());} finally {System.out.println("--------------------------");System.out.println(JSONUtil.toJsonStr(dto));}return null;}public static File[] splitFile(File file, int numberOfShards) throws IOException {long fileSize = file.length();long shardSize = fileSize / numberOfShards;long remainder = fileSize % numberOfShards;File[] shards = new File[numberOfShards];File shardDir = new File("D:\\work\\shards");if (!shardDir.exists()) {shardDir.mkdirs();}try (FileInputStream fis = new FileInputStream(file)) {byte[] buffer = new byte[1024];for (int i = 0; i < numberOfShards; i++) {File shardFile = new File(shardDir, "shard_" + (i + 1) + ".part");shards[i] = shardFile;try (FileOutputStream fos = new FileOutputStream(shardFile)) {long bytesToWrite = shardSize;if (remainder > 0) {bytesToWrite++;remainder--;}long bytesCopied = 0;while (bytesCopied < bytesToWrite) {int bytesReadThisTime = fis.read(buffer, 0, (int) Math.min(buffer.length, bytesToWrite - bytesCopied));if (bytesReadThisTime == -1) {throw new IOException("Unexpected end of file while writing shard " + (i + 1));}fos.write(buffer, 0, bytesReadThisTime);bytesCopied += bytesReadThisTime;}}}}return shards;}@Overridepublic void test() {File fileToSplit = new File("D:\\work\\凯通科技-协同子系统\\distributed\\协同构建子系统-界面原型.rp");//File fileToSplit = new File("C:\\Users\\kewenyang\\Desktop\\index.vue");try {int index = 4;File[] shards = splitFile(fileToSplit, index);String id = UUID.randomUUID().toString();for (int i = 0; i < index; i++) {File shard = shards[i];MultipartFile multipartFile = IMultipartFileImpl.fileToMultipartFile(shard, MediaType.APPLICATION_OCTET_STREAM_VALUE);if (multipartFile.getSize() <= 0) {continue;}ImageFileBreakpointResumeDTO dto = new ImageFileBreakpointResumeDTO();dto.setUploadNum(i);dto.setNamespace("www.baidu.com");dto.setName("baidu");dto.setVersion("1.0.0");dto.setTaskId(id);dto.setMultipartFile(multipartFile);dto.setNumTotal(index);dto.setFileName("协同构建子系统-界面原型.rp");//dto.setFileName("index.vue");this.breakpointResumeFile(dto);System.out.println("Shard created: " + shard.getAbsolutePath());}} catch (IOException e) {e.printStackTrace();}}@Overridepublic void clearItemFile() {Path rootDir = Paths.get("/imageBreakpointResumeStorage/");try {java.nio.file.Files.walkFileTree(rootDir, new SimpleFileVisitor<Path>() {@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {if (file.toString().endsWith(".part")) {java.nio.file.Files.delete(file);System.out.println("Deleted file: " + file);}return FileVisitResult.CONTINUE;}@Overridepublic FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {// 如果在访问目录内容之前对其进行处理,可以在这里添加逻辑。return FileVisitResult.CONTINUE;}@Overridepublic FileVisitResult visitFileFailed(Path file, IOException exc) {System.err.println("Failed to visit file: " + file + " (reason: " + exc.getMessage() + ")");return FileVisitResult.CONTINUE;}});} catch (IOException e) {throw new CommonException("执行定时清除分片文件失败");}}

工具类:
redisUtil


import cn.hutool.json.JSONObject;
import com.cttnet.common.util.LogTraceUtil;
import com.cttnet.microservices.techteam.distributed.deploy.exception.CommonException;
import com.google.common.collect.Lists;
import io.micrometer.core.instrument.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.modelmapper.ModelMapper;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;/*** redis 工具类** @author kwy* @date 2024/11/20*/
@Component
@Slf4j
public class RedisUtil {@Resourceprivate ModelMapper modelMapper;@Resourceprivate RedisTemplate<String, Object> redisTemplate;// =============================common============================/*** 指定缓存失效时间** @param key    缓存的键* @param second 缓存失效时间(秒)* @return 设置是否成功*/public Boolean expire(String key, Long second) {return expire(key, second, TimeUnit.SECONDS);}/*** 指定缓存失效时间** @param key      缓存的键* @param time     缓存失效时间* @param timeUnit 时间单位* @return 设置是否成功*/public Boolean expire(String key, Long time, TimeUnit timeUnit) {try {if (time > 0) {redisTemplate.expire(key, time, timeUnit);}return true;} catch (Exception e) {LogTraceUtil.error(e);}return false;}/*** 根据key 获取过期时间** @param key 键 不能为null* @return 时间(秒) 返回0代表为永久有效*/public Long getExpire(String key) {return redisTemplate.getExpire(key, TimeUnit.SECONDS);}/*** 判断key是否存在** @param key 键* @return true 存在 false不存在*/public Boolean hasKey(String key) {try {return redisTemplate.hasKey(key);} catch (Exception e) {LogTraceUtil.error(e);}return false;}/*** 设置字符串键值对。* 如果提供的值为null,则将其视为空字符串""进行处理。** @param key   键名,指定要设置的Redis键。* @param value 值,如果为null,则设置为空字符串""。* @return 如果设置成功,则返回true;如果设置失败(例如由于异常),则返回false。*/public Boolean set(String key, Object value) {try {value = null == value ? "" : value;redisTemplate.opsForValue().set(key, value);return true;} catch (Exception e) {LogTraceUtil.error(e);}return false;}/*** 设置带有过期时间的字符串键值对。** @param key      键名,指定要设置的Redis键。* @param value    值,如果值为null,则默认设置为空字符串""。* @param time     过期时间。* @param timeUnit 时间单位。* @return 如果设置成功,则返回true;如果设置失败(例如由于异常),则返回false。*/public Boolean set(String key, Object value, Long time, TimeUnit timeUnit) {try {value = null == value ? "" : value;redisTemplate.opsForValue().set(key, value, time, timeUnit);return true;} catch (Exception e) {LogTraceUtil.error(e);}return false;}/*** 删除缓存** @param key 可以传一个值 或多个*/public void del(String... key) {if (key != null && key.length > 0) {if (key.length == 1) {redisTemplate.delete(key[0]);} else {redisTemplate.delete(Lists.newArrayList(key));}}}/*** 删除对应的value*/public void remove(final String key) {if (Boolean.TRUE.equals(exists(key))) {redisTemplate.delete(key);}}/*** 删除hash结构中的某个字段*/public void hashDel(final String key, String item) {if (Boolean.TRUE.equals(exists(key))) {redisTemplate.opsForHash().delete(key, item);}}/*** 判断缓存中是否有对应的value*/public Boolean exists(final String key) {return redisTemplate.hasKey(key);}// ============================String=============================/*** 设置 String 类型键值对。** @param key   键名。* @param value 值,如果值为空或仅包含空白字符,则将其设置为空字符串。* @return 设置成功返回 true,否则返回 false。*/public Boolean strSet(String key, String value) {try {value = StringUtils.isBlank(value) ? "" : value;redisTemplate.opsForValue().set(key, value);return true;} catch (Exception e) {LogTraceUtil.error(e);}return false;}/*** 设置带过期时间的 String 类型键值对。** @param key      键名。* @param value    值,如果值为空或仅包含空白字符,则将其设置为空字符串。* @param time     过期时间。* @param timeUnit 时间单位。* @return 设置成功返回 true,否则返回 false。*/public Boolean strSet(String key, String value, Long time, TimeUnit timeUnit) {try {value = StringUtils.isBlank(value) ? "" : value;redisTemplate.opsForValue().set(key, value, time, timeUnit);return true;} catch (Exception e) {LogTraceUtil.error(e);}return false;}/*** 根据给定的键名从 Redis 中获取对应的值。** @param key 键名。* @return 如果键名存在,则返回对应的值;如果键名不存在或键名为空,则返回 null。*/public Object get(String key) {if (StringUtils.isBlank(key)) {return null;}return redisTemplate.opsForValue().get(key);}/*** 根据给定的键名从 Redis 中获取对应的值,并将该值转换为指定类型的对象。* 如果值不是基本数据类型(String、Integer、Double、Byte),则使用 JSON 反序列化将其转换为指定类型的对象。** @param key   键名。* @param clazz 指定转换后的对象类型。* @return 如果键名存在,则返回对应的对象;如果键名不存在或键名为空,则返回 null。*/public <T> T get(String key, Class<T> clazz) {if (StringUtils.isBlank(key)) {return null;}if (clazz.equals(String.class)|| clazz.equals(Integer.class)|| clazz.equals(Double.class)|| clazz.equals(Byte.class)) {return (T) redisTemplate.opsForValue().get(key);}JSONObject jsonObject = (JSONObject) redisTemplate.opsForValue().get(key);if (null == jsonObject) {return null;}return jsonObject.toBean(clazz);}/*** 对存储在指定键的数值执行原子递增操作。** @param key   键名,指定要递增的 Redis 键。* @param delta 递增量,表示要增加的值(必须大于0)。* @return 递增后的值。* @throws RuntimeException 如果递增量小于等于0,则抛出此异常。*/public Long incr(String key, Long delta) {if (delta < 0) {throw new CommonException("递增因子必须大于0");}return redisTemplate.opsForValue().increment(key, delta);}/*** 对存储在指定键的数值执行原子递减操作。** @param key   键名,指定要递减的 Redis 键。* @param delta 递减量,表示要减少的值(必须大于0)。* @return 递减后的值。* @throws RuntimeException 如果递减量小于等于0,则抛出此异常。*/public Long decr(String key, Long delta) {if (delta < 0) {throw new CommonException("递减因子必须大于0");}return redisTemplate.opsForValue().increment(key, -delta);}// ================================Map=================================/*** HashGet** @param key  键 不能为null* @param item 项 不能为null* @return 值*/public Object hget(String key, String item) {return redisTemplate.opsForHash().get(key, item);}/*** 获取hashKey对应的所有键值** @param key 键* @return 对应的多个键值*/public Map<Object, Object> hmget(String key) {return redisTemplate.opsForHash().entries(key);}/*** 获取hashKey对应的所有键值** @param key 键* @return 对应的多个键值*/public <K, V> Map<K, V> hmget(String key, Class<K> k, Class<V> v) {Map<Object, Object> entries = redisTemplate.opsForHash().entries(key);return (Map<K, V>) entries;}/*** 获取hashKey对应的所有键值** @param key   键* @param clazz 类* @return 对应的多个键值*/public <T> T hmget(String key, Class<T> clazz) {Map<Object, Object> entries = redisTemplate.opsForHash().entries(key);return modelMapper.map(entries, clazz);}/*** HashSet** @param key 键* @param map 对应多个键值* @return true 成功 false 失败*/public Boolean hmset(String key, Map map) {try {redisTemplate.opsForHash().putAll(key, map);return true;} catch (Exception e) {LogTraceUtil.error(e);}return false;}/*** HashSet 并设置时间** @param key  键* @param map  对应多个键值* @param time 时间(秒)* @return true成功 false失败*/public Boolean hmset(String key, Map<String, Object> map, Long time) {try {redisTemplate.opsForHash().putAll(key, map);if (time > 0) {expire(key, time);}return true;} catch (Exception e) {LogTraceUtil.error(e);}return false;}/*** HashSet 并设置时间** @param key      键* @param map      对应多个键值* @param time     时间* @param timeUnit 单位* @return true成功 false失败*/public Boolean hmset(String key, Map<String, Object> map, Long time, TimeUnit timeUnit) {try {redisTemplate.opsForHash().putAll(key, map);if (time > 0) {expire(key, time, timeUnit);}return true;} catch (Exception e) {LogTraceUtil.error(e);}return false;}/*** 向一张hash表中放入数据,如果不存在将创建** @param key   键* @param item  项* @param value 值* @return true 成功 false失败*/public Boolean hset(String key, String item, Object value) {try {redisTemplate.opsForHash().put(key, item, value);return true;} catch (Exception e) {LogTraceUtil.error(e);return false;}}/*** 向一张hash表中放入数据,如果不存在将创建** @param key   键* @param item  项* @param value 值* @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间* @return true 成功 false失败*/public Boolean hset(String key, String item, Object value, Long time) {try {redisTemplate.opsForHash().put(key, item, value);if (time > 0) {expire(key, time);}return true;} catch (Exception e) {LogTraceUtil.error(e);}return false;}/*** 删除hash表中的值** @param key  键 不能为null* @param item 项 可以使多个 不能为null*/public void hdel(String key, Object... item) {redisTemplate.opsForHash().delete(key, item);}/*** 判断hash表中是否有该项的值** @param key  键 不能为null* @param item 项 不能为null* @return true 存在 false不存在*/public Boolean hHasKey(String key, String item) {return redisTemplate.opsForHash().hasKey(key, item);}/*** 对存储在指定键的哈希表中某个字段的值执行原子递增操作。* 如果哈希表或字段不存在,则会创建一个新的哈希表或字段,并将值初始化为0,然后执行递增操作。** @param key  键名,指定要操作的 Redis 哈希表键。* @param item 字段名,指定要递增的哈希表字段。* @param by   递增量,表示要增加的值(必须大于0)。* @return 递增后的值。*/public double hincr(String key, String item, double by) {return redisTemplate.opsForHash().increment(key, item, by);}/*** hash递减** @param key  键* @param item 项* @param by   要减少记(小于0)* @return 递减后的值。*/public double hdecr(String key, String item, double by) {return redisTemplate.opsForHash().increment(key, item, -by);}// ============================set=============================/*** 根据key获取Set中的所有值** @param key 键* @return 返回一个包含集合中所有元素的集合。如果发生异常,则返回 null。*/public Set<Object> sGet(String key) {try {return redisTemplate.opsForSet().members(key);} catch (Exception e) {LogTraceUtil.error(e);return null;}}/*** 根据value从一个set中查询,是否存在** @param key   键* @param value 值* @return true 存在 false不存在*/public Boolean sHasKey(String key, Object value) {try {return redisTemplate.opsForSet().isMember(key, value);} catch (Exception e) {LogTraceUtil.error(e);return false;}}/*** 将数据放入set缓存** @param key    键* @param values 值 可以是多个* @return 成功个数*/public Long sSet(String key, Object... values) {try {return redisTemplate.opsForSet().add(key, values);} catch (Exception e) {LogTraceUtil.error(e);return 0L;}}/*** 将set数据放入缓存** @param key    键* @param second 时间(秒)* @param values 值 可以是多个* @return 成功个数*/public Long sSetAndTime(String key, Long second, Object... values) {return sSetAndTime(key, second, TimeUnit.SECONDS, values);}/*** 将set数据放入缓存** @param key    键* @param time   时间* @param values 值 可以是多个* @return 成功个数*/public Long sSetAndTime(String key, Long time, TimeUnit timeUnit, Object... values) {try {Long count = redisTemplate.opsForSet().add(key, values);if (time > 0) {expire(key, time, timeUnit);}return count;} catch (Exception e) {LogTraceUtil.error(e);return 0L;}}/*** 获取set缓存的长度** @param key 键* @return 返回集合中元素的数量。如果发生异常,则返回 0。*/public Long sGetSetSize(String key) {try {return redisTemplate.opsForSet().size(key);} catch (Exception e) {LogTraceUtil.error(e);return 0L;}}/*** 移除值为value的** @param key    键* @param values 值 可以是多个* @return 移除的个数*/public Long setRemove(String key, Object... values) {try {return redisTemplate.opsForSet().remove(key, values);} catch (Exception e) {LogTraceUtil.error(e);return 0L;}}// ===============================list=================================/*** 获取list缓存的内容** @param key   键* @param start 开始* @param end   结束 0 到 -1代表所有值* @return 返回一个包含指定范围内元素的列表。如果发生异常,则返回 null。*/public List<Object> lGet(String key, Long start, Long end) {try {return redisTemplate.opsForList().range(key, start, end);} catch (Exception e) {LogTraceUtil.error(e);return null;}}/*** 获取list缓存的长度** @param key 键* @return 返回列表中元素的数量。如果发生异常,则返回 0。*/public Long lGetListSize(String key) {try {return redisTemplate.opsForList().size(key);} catch (Exception e) {LogTraceUtil.error(e);return 0L;}}/*** 通过索引 获取list中的值** @param key   键* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推* @return 返回指定索引处的元素值。如果索引超出范围或发生异常,则返回 null。*/public Object lGetIndex(String key, Long index) {try {return redisTemplate.opsForList().index(key, index);} catch (Exception e) {LogTraceUtil.error(e);return null;}}/*** 将单个对象添加到 Redis 列表的右侧。** @param key   键名,指定要操作的 Redis 列表键。* @param value 要添加到列表末尾的对象。* @return 如果操作成功,则返回 true;否则返回 false。*/public Boolean lSet(String key, Object value) {try {redisTemplate.opsForList().rightPush(key, value);return true;} catch (Exception e) {LogTraceUtil.error(e);return false;}}/*** 将list放入缓存** @param key   键* @param value 值* @param time  时间(秒)* @return 如果操作成功,则返回 true;否则返回 false。*/public Boolean lSet(String key, Object value, Long time) {try {redisTemplate.opsForList().rightPush(key, value);if (time > 0) {expire(key, time);}return true;} catch (Exception e) {LogTraceUtil.error(e);return false;}}/*** 将list放入缓存** @param key   键* @param value 值*/public Boolean lSet(String key, List<Object> value) {try {redisTemplate.opsForList().rightPushAll(key, value);return true;} catch (Exception e) {LogTraceUtil.error(e);return false;}}/*** 将list放入缓存** @param key   键* @param value 值* @param time  时间(秒)* @return 如果操作成功,则返回 true;否则返回 false。*/public Boolean lSet(String key, List<Object> value, Long time) {try {redisTemplate.opsForList().rightPushAll(key, value);if (time > 0) {expire(key, time);}return true;} catch (Exception e) {LogTraceUtil.error(e);return false;}}/*** 根据索引修改list中的某条数据** @param key   键* @param index 索引* @param value 值* @return 如果操作成功,则返回 true;否则返回 false。*/public Boolean lUpdateIndex(String key, Long index, Object value) {try {redisTemplate.opsForList().set(key, index, value);return true;} catch (Exception e) {LogTraceUtil.error(e);return false;}}/*** 移除N个值为value** @param key   键* @param count 移除多少个* @param value 值* @return 移除的个数*/public Long lRemove(String key, Long count, Object value) {try {return redisTemplate.opsForList().remove(key, count, value);} catch (Exception e) {LogTraceUtil.error(e);return 0L;}}/*** 获取固定前缀的key** @param suffix 键名的后缀,用于匹配所有以该后缀开头的键名。* @return 一个包含所有匹配键名的集合。*/public Set<String> getKeySuffix(String suffix) {return redisTemplate.keys(suffix + ":*");}}

文件转换类(用于本地测试)

import com.cttnet.microservices.techteam.distributed.deploy.exception.CommonException;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;/*** 负责将InputStream转换MultipartFile,可以少引一个jar包,本来用的是spring-test-5.2.8中的MockMultipartFile,直接提取出来使用* 见 https://blog.csdn.net/m0_37609579/article/details/100901358** @author kwy* @date 2024/11/20*/
public class IMultipartFileImpl implements MultipartFile {/*** 文件名称*/private final String name;/*** 原始文件名*/private final String originalFilename;/*** 文件内容类型*/@Nullableprivate final String contentType;/*** 文件内容字节数组*/private final byte[] content;/*** Create a new MockMultipartFile with the given content.** @param name    the name of the file* @param content the content of the file*/public IMultipartFileImpl(String name, @Nullable byte[] content) {this(name, "", null, content);}/*** Create a new MockMultipartFile with the given content.** @param name          the name of the file* @param contentStream the content of the file as stream* @throws IOException if reading from the stream failed*/public IMultipartFileImpl(String name, InputStream contentStream) throws IOException {this(name, "", null, FileCopyUtils.copyToByteArray(contentStream));}/*** Create a new MockMultipartFile with the given content.** @param name             the name of the file* @param originalFilename the original filename (as on the client's machine)* @param contentType      the content type (if known)* @param content          the content of the file*/public IMultipartFileImpl(String name, @Nullable String originalFilename, @Nullable String contentType, @Nullable byte[] content) {Assert.hasLength(name, "Name must not be empty");this.name = name;this.originalFilename = (originalFilename != null ? originalFilename : "");this.contentType = contentType;this.content = (content != null ? content : new byte[0]);}/*** Create a new MockMultipartFile with the given content.** @param name             the name of the file* @param originalFilename the original filename (as on the client's machine)* @param contentType      the content type (if known)* @param contentStream    the content of the file as stream* @throws IOException if reading from the stream failed*/public IMultipartFileImpl(String name, @Nullable String originalFilename, @Nullable String contentType, InputStream contentStream)throws IOException {this(name, originalFilename, contentType, FileCopyUtils.copyToByteArray(contentStream));}@Overridepublic String getName() {return this.name;}@Override@NonNullpublic String getOriginalFilename() {return this.originalFilename;}@Override@Nullablepublic String getContentType() {return this.contentType;}@Overridepublic boolean isEmpty() {return (this.content.length == 0);}@Overridepublic long getSize() {return this.content.length;}@Overridepublic byte[] getBytes() throws IOException {return this.content;}@Overridepublic InputStream getInputStream() throws IOException {return new ByteArrayInputStream(this.content);}@Overridepublic void transferTo(File dest) throws IOException {FileCopyUtils.copy(this.content, dest);}/*** File 转 MultipartFile 用完不删** @param file        {@linkplain File}* @param contentType 内容类型* @return {@linkplain MultipartFile}*/public static MultipartFile fileToMultipartFile(File file, String contentType) {try {return new IMultipartFileImpl("file", file.getName(), contentType, Files.newInputStream(file.toPath()));} catch (Exception e) {throw new CommonException("File 转 MultipartFile失败!" + e.getMessage());}}
}

以上代码有后端测试断点续传的接口,本地启动项目,可以直接测试使用,所以如果前端说你有问题,怼他即可。前端实现逻辑,照着后端测试方法的思路实现即可,记录上传分片序号,计算获得上传进度。

如果白嫖过程中发现遗漏或问题的,请在评论区留言,我看到会修正或补充。

相关文章:

断点续传+测试方法完整示例

因为看不懂网上的断点续传案例&#xff0c;而且又不能直接复制使用&#xff0c;干脆自己想想写了一个。 上传入参类&#xff1a; import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProp…...

C#设计模式--状态模式(State Pattern)

状态模式是一种行为设计模式&#xff0c;它允许对象在其内部状态发生变化时改变其行为。这种模式的核心思想是将状态封装在独立的对象中&#xff0c;而不是将状态逻辑散布在整个程序中。 用途 简化复杂的条件逻辑&#xff1a;通过将不同的状态封装在不同的类中&#xff0c;可…...

Excel技巧:如何批量调整excel表格中的图片?

插入到excel表格中的图片大小不一&#xff0c;如何做到每张图片都完美的与单元格大小相同&#xff1f;并且能够根据单元格来改变大小&#xff1f;今天分享&#xff0c;excel表格里的图片如何批量调整大小。 方法如下&#xff1a; 点击表格中的一个图片&#xff0c;然后按住Ct…...

hadoop中导出表与数据的步骤

大家好&#xff0c;我是 V 哥。在Hadoop中导出表与数据&#xff0c;可以通过多种方式实现&#xff0c;包括使用Hive的EXPORT命令、MapReduce作业、Hive查询以及Sqoop工具。下面V 哥将详细介绍这些步骤和一些代码示例&#xff0c;来帮助大家更好理解。 1. 使用Hive的EXPORT命令…...

springBoot中的日志级别在哪里配置

在Spring Boot中&#xff0c;日志级别的配置可以通过多种方式来实现&#xff0c;主要包括在配置文件中设置、使用自定义的logback配置文件&#xff0c;以及在代码中动态配置等。以下是一些具体的配置方法&#xff1a; 一、在配置文件中设置日志级别 Spring Boot默认使用appli…...

17. Threejs案例-Three.js创建多个立方体

17. Threejs案例-Three.js创建多个立方体 实现效果 知识点 WebGLRenderer (WebGL渲染器) WebGLRenderer 是 Three.js 中用于渲染 WebGL 场景的核心类。它负责将场景中的对象渲染到画布上。 构造器 new THREE.WebGLRenderer(parameters) 参数类型描述parametersObject可选…...

数据结构——有序二叉树的删除

在上一篇博客中&#xff0c;我们介绍了有序二叉树的构建、遍历、查找。 数据结构——有序二叉树的构建&遍历&查找-CSDN博客文章浏览阅读707次&#xff0c;点赞18次&#xff0c;收藏6次。因为数据的类型决定数据在内存中的存储形式。left right示意为左右节点其类型也为…...

力扣1401. 圆和矩形是否有重叠

用矢量计算&#xff1a; class Solution { public:bool checkOverlap(int radius, int xCenter, int yCenter, int x1, int y1, int x2, int y2) {//矩形中心float Tx(float)(x1x2)/2;float Ty(float)(y1y2)/2;//强行进行对称操作&#xff0c;只考虑第一象限if(xCenter<Tx)…...

idea连接到docker出现 org.apache.hc.client5.http.ConnectTimeoutException 异常怎么办?

前情提要 我电脑是win11&#xff0c;我安装了centOS7虚拟机&#xff0c;配置linux环境 idea是2024社区免费版本 我就这一步步排查问题&#xff0c;终于发现了是因为我的2375端口没有ipv4开放&#xff0c;只在ipv6开放 踩坑提醒&#xff1a; 对了&#xff0c;一个一个问题排…...

一番赏小程序定制开发,打造全新抽赏体验平台

随着盲盒的热潮来袭&#xff0c;作为传统的潮玩方式一番赏也再次受到了大家的关注&#xff0c;市场热度不断上升&#xff01; 一番赏能够让玩家百分百中奖&#xff0c;商品种类丰富、收藏价值高&#xff0c;拥有各种IP&#xff0c;从而吸引着各个圈子的粉丝玩家&#xff0c;用…...

PHP语法学习(第六天)

&#x1f4a1;依照惯例&#xff0c;回顾一下昨天讲的内容 PHP语法学习(第五天)主要讲了PHP中的常量和运算符的运用。 &#x1f525; 想要学习更多PHP语法相关内容点击“PHP专栏” 今天给大家讲课的角色是&#x1f34d;菠萝吹雪&#xff0c;“我菠萝吹雪吹的不是雪&#xff0c;而…...

按vue组件实例类型实现非侵入式国际化多语言翻译

#vue3##国际化##本地化##international# web界面国际化&#xff0c;I18N&#xff08;Internationalization&#xff0c;国际化&#xff09;&#xff0c;I11L(International&#xff0c;英特纳雄耐尔)&#xff0c;L10N&#xff08;Localization&#xff0c;本地化&#xff09;&…...

2024年认证杯SPSSPRO杯数学建模B题(第一阶段)神经外科手术的定位与导航解题全过程文档及程序

2024年认证杯SPSSPRO杯数学建模 B题 神经外科手术的定位与导航 原题再现&#xff1a; 人的大脑结构非常复杂&#xff0c;内部交织密布着神经和血管&#xff0c;所以在大脑内做手术具有非常高的精细和复杂程度。例如神经外科的肿瘤切除手术或血肿清除手术&#xff0c;通常需要…...

51c视觉~合集24

我自己的原文哦~ https://blog.51cto.com/whaosoft/11870494 #R-Adapter 零样本模型微调新突破&#xff0c;提升鲁棒性与泛化能力 论文提出新颖的Robust Adapter&#xff08;R-Adapter&#xff09;&#xff0c;可以在微调零样本模型用于下游任务的同时解决这两个问题。该方…...

idea启动tomcat服务中文乱码

在idea中启动tomcat服务后部分中文乱码 但是在tomcat日志部分正常 并且在tomcat中中文也是正常 查询大量资料修改idea编码&#xff0c;虚拟机编码、tomcat默认编码、终端默认编码&#xff0c;统统没有效果。 最终发现修改tomcat下文件夹 .\conf\logging.properties 网络上…...

android studio 读写文件操作(应用场景二)

android studio版本&#xff1a;2023.3.1 patch2 例程&#xff1a;readtextviewIDsaveandread 本例程是个过渡例程&#xff0c;如果单是实现下图的目的有更简单的方法&#xff0c;但这个方法是下一步工作的基础&#xff0c;所以一定要做。 例程功能&#xff1a;将两个textvi…...

【数据结构】【线性表】特殊的线性表-字符串

目录 字符串的基本概念 字符串的三要素 字符串的基本概念 串的编码 串的实现及基本运算 顺序串的实现 串的静态数组实现 串的动态数组的实现 顺序存储的四种方案 链式串的实现 基本运算 方案三 方案一 字符串的基本概念 数据结构千千万&#xff0c…...

【AWS re:Invent 2024】一文了解EKS新功能:Amazon EKS Auto Mode

文章目录 一、为什么要使用 Amazon EKS Auto Mode&#xff1f;二、Amazon EKS自动模式特性2.1 持续优化计算成本2.2 迁移集群操作2.3 EKS 自动模式的高级功能 三、EKS Auto 集群快速创建集群配置四、查看来自 API 服务器的指标五、EKS 相关角色权限设置六、参考链接 一、为什么…...

HTTPS的工作过程

1.HTTPS协议原理 1.1HTTPS协议的由来 HTTP在传输网络数据的时候是明文传输的&#xff0c;信息容易被窃听甚至篡改&#xff0c;因此他是一个不安全的协议&#xff08;但效率高&#xff09;。在如今的网络环境中&#xff0c;数据安全是很重要的&#xff08;比如支付密码又或者各…...

Java并发编程学习之从资本家的角度看多线程和并发性(一)

目录 前言前置知识一、单线程时代二、为什么要有多线程&#xff0c;多线程的优点&#xff1f;三、使用多线程会遇到什么问题&#xff1f;四、多线程和并发编程的关系总结 前言 这篇文章是打开Java多线程和并发编程的大门的开始&#xff0c;如标题《从老板的角度看多线程和并发…...

基于STM32设计的智能宠物喂养系统(华为云IOT)_273

文章目录 一、前言1.1 项目介绍【1】项目开发背景【2】设计实现的功能【3】项目硬件模块组成【4】设计意义【5】国内外研究现状【6】摘要1.2 设计思路1.3 系统功能总结1.4 开发工具的选择【1】设备端开发【2】上位机开发1.5 参考文献1.6 系统框架图1.7 系统原理图1.8 实物图1.9…...

Mybatis-Plus的主要API

一、实体类操作相关API BaseMapper<T>接口 功能&#xff1a;这是 MyBatis - Plus 为每个实体类对应的 Mapper 接口提供的基础接口。它提供了一系列基本的 CRUD&#xff08;增删改查&#xff09;操作方法。例如insert(T entity)方法用于插入一条记录&#xff0c;d…...

Pillow:强大的Python图像处理库

目录 一、引言 二、Pillow 库的安装 三、Pillow 库的基本概念 四、图像的读取和保存 五、图像的基本属性 六、图像的裁剪、缩放和旋转 七、图像的颜色调整 八、图像的滤镜效果 九、图像的合成和叠加 十、图像的绘制 十一、示例程序&#xff1a;制作图片水印 十二、…...

Springboot定时任务详解

文章目录 Springboot定时任务详解一、引言二、cron表达式三、使用Scheduled注解1、开启定时任务2、添加定时任务 四、使用TaskScheduler接口1、注入TaskScheduler实例 五、集成Quartz框架1、集成Quartz 六、实际使用示例七、总结 Springboot定时任务详解 一、引言 在现代软件…...

【Linux】环境ChatGLM-4-9B 模型之 openai API 服务

一、摘要 最近看到 Function Call 比较感兴趣,它的核心是赋予大模型能够调用外部API的能力,能够解决大模型功能扩展性问题,允许模型调用外部数据库或API,提供特定领域的详细信息;解决信息实时性问题,模型可以实时获取最新数据;解决数据局限性问题,大模型训练数据虽多但…...

mobi文件转成pdf

将 MOBI 文件转换为 PDF 格式通常涉及两个步骤&#xff1a; 解析 MOBI 文件&#xff1a;需要提取 MOBI 文件的内容&#xff08;文本、图片等&#xff09;。将提取的内容转换为 PDF&#xff1a;将 MOBI 文件的内容渲染到 PDF 格式。 可用工具 kindleunpack 或 mobi&#xff1…...

Linux---对缓冲区的简单理解--第一个系统程序

前序&#xff1a; 首先先理解一下什么是回车与换行&#xff1b;回车和换行是两个概念&#xff0c;它们不是一个东西&#xff1b; 回车:光标回到开始&#xff1b;换行:换到下一行&#xff1b; 如下图&#xff1a; 行缓冲区 如何理解缓冲区问题&#xff1f; 可以认为&#xff0…...

word poi-tl 表格功能增强,实现表格功能垂直合并

目录 问题解决问题poi-tl介绍 功能实现引入依赖模版代码效果图 附加&#xff08;插件实现&#xff09;MergeColumnData 对象MergeGroupData 类ServerMergeTableData 数据信息ServerMergeTablePolicy 合并插件 问题 由于在开发功能需求中&#xff0c;word文档需要垂直合并表格&…...

鸿蒙实现数据管理

目录&#xff1a; 1、鸿蒙实现数据管理的三种方式2、用户首选项3、键值型数据管理3.1、获取KVManager实例&#xff0c;用于管理数据库对象3.2、创建并获取键值数据库3.3、调用put()方法向键值数据库中插入数据3.4、调用get()方法获取指定键的值3.5、调用delete()方法删除指定键…...

图片上传HTML

alioss sky:jwt:# 设置jwt签名加密时使用的秘钥admin-secret-key: itcast# 设置jwt过期时间admin-ttl: 7200000# 设置前端传递过来的令牌名称admin-token-name: tokenalioss:endpoint: ${sky.alioss.endpoint}access-key-id: ${sky.alioss.access-key-id}access-key-secret: $…...

golang 代发邮件支持附件发送,outlook案列,其他邮箱需要替换对应邮箱服务域名

GPT问答实例 import pandas as pd from openai.embeddings_utils import get_embedding, cosine_similarity import openai import os import logging as logger from flask_cors import CORS import os openai.api_key os.getenv(OPENAI_API_KEY)class Chatbot():def parse_…...

输出绝对值

输出绝对值 C语言代码C 代码Java代码Python代码 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 输入一个浮点数&#xff0c;输出这个浮点数的绝对值。 输入 输入一个浮点数&#xff0c;其绝对值不超过10000。 输出 输出这个浮点数的绝对…...

docker desktop打包配置国内镜像地址

打包遇到无法访问外网资源&#xff0c;直接配置国内镜像地址 直接加入如下代码就行&#xff1a; {"builder": {"gc": {"defaultKeepStorage": "20GB","enabled": true}},"experimental": false,"registry-m…...

鸿蒙Next学习-监听指定页面显示/页面生命周期

自定义组件监听页面生命周期 使用无感监听页面路由的能力&#xff0c;能够实现在自定义组件中监听页面的生命周期。 // Index.ets import { uiObserver, router, UIObserver } from kit.ArkUI;Entry Component struct Index {listener: (info: uiObserver.RouterPageInfo) &g…...

计算机网络 —— HTTPS 协议

前一篇文章&#xff1a;计算机网络 —— HTTP 协议&#xff08;详解&#xff09;-CSDN博客 目录 前言 一、HTTPS 协议简介 二、HTTPS 工作过程 1.对称加密 2.非对称加密 3.中间人攻击 4.引入证书 三、HTTPS 常见问题 1.中间人能否篡改证书&#xff1f; 2.中间人能否调…...

Oracle之表空间迁移

问题背景&#xff1a;一个数据表随着时间的累积&#xff0c;导致所在表空间占用很高&#xff0c;里面历史数据可以清除&#xff0c;保留近2个月数据即可 首先通过delete删除了2个月以前的数据。 按网上的教程进行空间压缩&#xff0c;以下sql在表所在用户执行: -- 允许表重新…...

web组态可视化编辑器

随着工业智能制造的发展&#xff0c;工业企业对设备可视化、远程运维的需求日趋强烈&#xff0c;传统的单机版组态软件已经不能满足越来越复杂的控制需求&#xff0c;那么实现web组态可视化界面成为了主要的技术路径。 行业痛点 对于软件服务商来说&#xff0c;将单机版软件转…...

SpringMVC纯注解快速开发

此文章适合具有一定的java基础的同学看哦&#xff0c;如果有看不懂的基本代码还是先补补java基础哦。 此教程带您不使用xml文件而是纯注解开发&#xff0c;易懂、快捷、迅速&#xff0c;从0开始搭建&#xff0c;很快就能构建起一个SpringMVC项目&#xff0c;能学到两种使用tom…...

[读论文] Compositional 3D-aware Video Generation with LLM Director

Abstract 近年来&#xff0c;通过强大的生成模型和大规模互联网数据&#xff0c;文本到视频生成领域取得了显著进展。然而&#xff0c;在生成视频中精确控制单个概念&#xff08;如特定角色的动作和外观、视角的移动&#xff09;方面&#xff0c;仍存在巨大挑战。为此&#xff…...

FFmpeg 4.3 音视频-多路H265监控录放C++开发十八,ffmpeg解复用

为啥要封装和解封装呢&#xff1f; 1.封装就相当于将 h264 和aac 包裹在一起。既能播放声音&#xff0c;也能播放视频 2.在封装的时候没指定编码格式&#xff0c;帧率&#xff0c;时长&#xff0c;等参数&#xff1b;特别是视频&#xff0c;可以将视频帧索引存储&#xff0c;…...

ubuntu系统安装docker

1、 安装必要的依赖 sudo apt install apt-transport-https ca-certificates curl software-properties-common2、添加 Docker 的官方 GPG 密钥 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -3、添加 Docker 的 APT 源 sudo add-apt-repos…...

STM32 BootLoader 刷新项目 (十三) Python上位机介绍

STM32 BootLoader 刷新项目 (十三) Python上位机介绍 大家好&#xff0c;这是我们STM32 BootLoader的最后一篇文章了&#xff0c;讲述用Python写的上位机&#xff0c;也更新了半年时间了&#xff0c;谢谢大家的支持&#xff0c;到目前为止&#xff0c;已经更新了12篇文章了&am…...

美畅物联丨智能监控,高效运维:视频汇聚平台在储能领域的实践探索

在当今全球能源格局不断变化的大背景下&#xff0c;对清洁能源的需求正以惊人的速度增长。储能项目作为平衡能源供需、提升能源利用效率的关键环节&#xff0c;其规模和复杂度也在不断攀升。在储能项目的运营管理过程中&#xff0c;安全监控、设备运维以及数据管理等方面面临着…...

T C P

文章目录 基于UDP应用场景 TCP协议TCP 协议段格式确认应答机制16位窗口大小 下定义32位序号和32位确认序号序号是什么&#xff1f;确认序号 基于UDP应用场景 UDP&#xff0c;tcp这样的协议根本不是直接谈UDP。tcp的应用场景&#xff0c;一定是上层写了应用层协议&#xff0c;所…...

MongoDB的简单使用

MongoDB(文档数据库)的简单使用 MongoDB最好的学习资料就是他的官方文档&#xff1a;SQL 到 MongoDB 的映射图表 - MongoDB 手册 v8.0 1.MongoDB CRUD操作 1.1Insert操作 基本方法&#xff1a; db.collection.insertOne() 将单个文档(document)插入集合中 db.collectio…...

【Exp】# Microsoft Visual C++ Redistributable 各版本下载地址

Microsoft官方页面 https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads Redistributable 2019 X86: https://aka.ms/vs/16/release/VC_redist.x86.exe X64: https://aka.ms/vs/16/release/VC_redist.x64.exe Redistributable 201…...

【MySQL】表的约束

目录 一、非空约束not null 二、默认值约束default 三、列描述comment 四、填充零zerofill 五、主键primary key 六、自增长auto_increment 七、唯一键unique 八、外键foreign key 一、非空约束not null 如果不对一个字段做非空约束&#xff0c;则默认为空。但空数据无…...

c++高级篇(四) ——Linux下IO多路复用之epoll模型

IO多路复用 —— epoll 前言 在之前我们就已经介绍过了select和poll,在作为io多路复用的最后一个的epoll,我们来总结一下它们之间的区别: a select 实现原理 select 通过一个文件描述符集合&#xff08;fd_set&#xff09;来工作&#xff0c;该集合可以包含需要监控的文件…...

基于Java Springboot环境保护生活App且微信小程序

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 微信…...

.NET 9 中 LINQ 新增功能实现过程

本文介绍了.NET 9中LINQ新增功能&#xff0c;包括CountBy、AggregateBy和Index方法,并提供了相关代码示例和输出结果&#xff0c;感兴趣的朋友跟随我一起看看吧 LINQ 介绍 语言集成查询 (LINQ) 是一系列直接将查询功能集成到 C# 语言的技术统称。 数据查询历来都表示为简单的…...