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

【EasyPan】文件上传、文件秒传、文件转码、文件合并、异步转码、视频切割分析

【EasyPan】项目常见问题解答(自用&持续更新中…)汇总版

文件上传方法解析

一、方法总览

@Transactional(rollbackFor = Exception.class)
public UploadResultDto uploadFile(...)

核心能力

  • 秒传验证:通过MD5+文件大小实现文件秒传
  • 分片处理:支持大文件分块上传与合并
  • 空间管理:实时校验用户存储空间
  • 事务保障:数据库操作原子性
  • 异步转码:视频/图片文件后台处理
  • 自动重命名:同名文件自动添加序号

二、模块化解析

1. 秒传处理模块

// 首片触发秒传检查
if (chunkIndex == 0) {List<FileInfo> dbFileList = this.fileInfoMapper.selectList(infoQuery);if (!dbFileList.isEmpty()) {// 空间校验if (dbFile.getFileSize() + spaceDto.getUseSpace() > spaceDto.getTotalSpace()) // 创建引用记录dbFile.setFileId(fileId);this.fileInfoMapper.insert(dbFile);// 返回秒传结果resultDto.setStatus(UploadStatusEnums.UPLOAD_SECONDS.getCode());}
}

设计亮点

  • 仅首片触发查询,减少数据库压力
  • 复用已有文件的物理存储路径(file_path)
  • 原子化更新用户空间

2. 分片处理模块

// 分片暂存逻辑
String tempFolderName = appConfig.getProjectFolder() + Constants.FILE_FOLDER_TEMP;
File newFile = new File(tempFolderName.getPath() + "/" + chunkIndex);
file.transferTo(newFile);// 临时空间记录
redisComponent.saveFileTempSize(webUserDto.getUserId(), fileId, file.getSize());// 合并条件判断
if (chunkIndex < chunks - 1) {return resultDto.setStatus(UploadStatusEnums.UPLOADING.getCode());
}

关键技术

  • 分片按序号存储:用户ID+文件ID/chunkIndex
  • Redis记录分片累计大小
  • 10MB缓冲区减少IO次数

3. 文件入库模块

FileInfo fileInfo = new FileInfo();
fileInfo.setFilePath(month + "/" + realFileName); // 按月份分目录
fileInfo.setStatus(FileStatusEnums.TRANSFER.getStatus()); // 转码中状态
this.fileInfoMapper.insert(fileInfo);// 事务提交后触发异步操作
TransactionSynchronizationManager.registerSynchronization(() -> {fileInfoService.transferFile(fileId, webUserDto);
});

创新设计

  • 文件路径动态生成:年月目录/用户ID文件ID.后缀
  • 状态机管理:TRANSFER->USING/TRANSFER_FAIL
  • 事务边界控制:确保数据入库后再转码

4. 转码处理模块

@Async
public void transferFile(...) {// 合并分片union(dirPath, targetFilePath); // 视频处理if (FileTypeEnums.VIDEO == fileTypeEnum) {cutFile4Video(); // HLS切片createCover4Video(); // 生成封面}// 更新文件状态updateInfo.setStatus(FileStatusEnums.USING.getStatus());fileInfoMapper.updateFileStatusWithOldStatus(...);
}

核心技术

  • FFmpeg视频转码:MP4->TS切片+m3u8索引
  • 缩略图生成:视频首帧+图片缩放
  • 异常恢复机制:失败状态可重新触发

三、流程主副线分析

主线流程

存在
不存在
上传首片
秒传检查
创建引用
接收分片
是否末片
合并文件
异步转码
返回上传中

副线处理(异常路径)

异常类型处理方式技术实现
空间不足立即终止Redis实时校验
MD5冲突重新计算文件内容比对
分片丢失断点续传Redis记录进度
转码失败状态标记人工介入恢复

四、时序图解析

用户 前端 服务层 Redis 数据库 文件存储 上传分片0 uploadFile(0) 秒传查询 返回文件记录 空间校验 插入引用记录 返回秒传成功 保存分片0 记录分片大小 返回继续上传 上传分片N uploadFile(N) 保存分片N 更新分片大小 上传末片 uploadFile(last) 合并分片 写入文件记录 更新空间使用 返回上传完成 启动异步转码 alt [秒传成功] [需要上传] 用户 前端 服务层 Redis 数据库 文件存储

五、性能优化策略

  1. 分片并行上传

    • 支持多分片并发上传
    • 分片大小动态调整(2MB-10MB)
  2. 内存管理

    byte[] b = new byte[1024 * 10]; // 10KB缓冲区
    
  3. 存储优化

    • 临时文件自动清理
    • 视频文件HLS自适应码率
  4. Redis优化

    redisUtils.setex(key, value, 1小时); // 临时数据自动过期
    
  5. 异步队列

    • 转码任务进入线程池
    • 失败任务重试机制

六、安全防护措施

  1. 校验机制

    • MD5+大小双校验防碰撞
    • 文件后缀白名单校验
  2. 防篡改保护

    if (!FileStatusEnums.TRANSFER.getStatus().equals(fileInfo.getStatus())) {return; // 状态校验
    }
    
  3. 临时文件清理

    finally {FileUtils.deleteDirectory(tempFileFolder);
    }
    

文件秒传处理模块深度解析

一、核心机制图解

用户上传文件
首片检查
MD5+大小查询
存在相同文件?
创建引用记录
继续分片上传
更新用户空间
返回秒传成功

二、代码模块拆解

1. 触发条件判断

// 仅在首片上传时触发秒传检查
if (chunkIndex == 0) {// 核心处理逻辑
}

设计考量:避免每个分片都进行数据库查询,减少系统压力

2. 秒传核验核心

FileInfoQuery infoQuery = new FileInfoQuery();
infoQuery.setFileMd5(fileMd5); // MD5指纹
infoQuery.setSimplePage(new SimplePage(0, 1)); // 限制查询1条
infoQuery.setStatus(FileStatusEnums.USING.getStatus()); // 仅检查可用文件
List<FileInfo> dbFileList = this.fileInfoMapper.selectList(infoQuery);

技术要点

  • 分页查询避免全表扫描
  • 状态过滤确保文件可用

3. 空间校验逻辑

if (dbFile.getFileSize() + spaceDto.getUseSpace() > spaceDto.getTotalSpace()) {throw new BusinessException("空间不足");
}

双重保障机制

  1. Redis实时校验:毫秒级响应
  2. 数据库事务保障:最终一致性

4. 引用记录创建

dbFile.setFileId(fileId); // 生成新文件ID
dbFile.setFilePid(filePid); // 继承目录结构
dbFile.setUserId(webUserDto.getUserId()); // 绑定新用户
dbFile.setCreateTime(curDate); // 更新时间戳
fileName = autoRename(filePid, userId, fileName); // 自动重命名
this.fileInfoMapper.insert(dbFile); // 创建新记录

创新设计

  • 物理文件复用:file_path直接引用原文件
  • 逻辑记录独立:文件树结构、权限信息隔离

5. 空间更新操作

private void updateUserSpace(SessionWebUserDto webUserDto, Long useSpace) {// 数据库更新userInfoMapper.updateUserSpace(userId, useSpace, null);// Redis更新spaceDto.setUseSpace(spaceDto.getUseSpace() + useSpace);redisComponent.saveUserSpaceUse(userId, spaceDto);
}

双写策略

  • Redis:高频访问数据缓存,保证实时性
  • MySQL:持久化存储,保证可靠性

三、异常处理机制

1. 碰撞处理流程

try {// 秒传核心逻辑
} catch (BusinessException e) {// 空间不足等业务异常logger.error("文件上传失败", e);throw e; 
} finally {// 清理临时文件
}

异常类型

  • CODE_904:存储空间不足
  • SQLIntegrityConstraintViolationException:唯一约束冲突

2. 事务回滚保障

@Transactional(rollbackFor = Exception.class)
public UploadResultDto uploadFile(...) {// 整个操作在事务中执行
}

原子性保证:出现任何异常时,引用记录创建和空间更新操作同时回滚

四、时序图解析

用户 前端 服务层 Redis 数据库 上传首片 请求秒传检查 获取用户空间数据 查询MD5记录 返回文件信息 创建引用记录 更新空间使用 返回秒传成功 返回继续上传 alt [存在相同文件] [需要上传] 展示操作结果 用户 前端 服务层 Redis 数据库

五、代码

    /*** 上传文件(含秒传处理)* 事务注解确保数据一致性:当空间不足时回滚数据库操作*/@Override@Transactional(rollbackFor = Exception.class)public UploadResultDto uploadFile(SessionWebUserDto webUserDto, String fileId, MultipartFile file, String fileName,String filePid, String fileMd5, Integer chunkIndex, Integer chunks) {// 初始化上传结果对象UploadResultDto resultDto = new UploadResultDto();try {// 生成文件唯一ID(类似网盘分享链接的短ID)if (StringTools.isEmpty(fileId)) {fileId = StringTools.getRandomString(Constants.LENGTH_10); // 生成10位随机字符串}resultDto.setFileId(fileId);// 获取用户空间使用情况(Redis缓存优化查询性能)UserSpaceDto spaceDto = redisComponent.getUserSpaceUse(webUserDto.getUserId());//==================== 秒传处理核心逻辑 ====================//if (chunkIndex == 0) { // 仅在上传第一个分片时触发秒传检查// 构建MD5查询条件(命中idx_md5_size索引)FileInfoQuery infoQuery = new FileInfoQuery();infoQuery.setFileMd5(fileMd5);          // MD5指纹infoQuery.setSimplePage(new SimplePage(0, 1)); // 限制返回1条记录infoQuery.setStatus(FileStatusEnums.USING.getStatus()); // 只查询可用文件// 查询数据库是否存在相同文件List<FileInfo> dbFileList = this.fileInfoMapper.selectList(infoQuery);if (!dbFileList.isEmpty()) {FileInfo dbFile = dbFileList.get(0);// 双重空间校验(Redis缓存校验 + 数据库最终校验)if (dbFile.getFileSize() + spaceDto.getUseSpace() > spaceDto.getTotalSpace()) {throw new BusinessException(ResponseCodeEnum.CODE_904); // 空间不足异常}//==================== 创建秒传文件记录 ====================//// 复用文件实体(类似创建快捷方式)dbFile.setFileId(fileId);            // 新文件IDdbFile.setFilePid(filePid);          // 继承目录结构dbFile.setUserId(webUserDto.getUserId()); // 绑定当前用户dbFile.setCreateTime(new Date());    // 重置时间戳dbFile.setStatus(FileStatusEnums.USING.getStatus()); // 设置可用状态dbFile.setDelFlag(FileDelFlagEnums.USING.getFlag()); // 删除标记// 自动重命名处理(类似"文件名(1).txt"的生成逻辑)fileName = autoRename(filePid, webUserDto.getUserId(), fileName);dbFile.setFileName(fileName); // 写入数据库(实际是创建新的元数据记录)this.fileInfoMapper.insert(dbFile);// 更新用户空间使用量(原子操作)updateUserSpace(webUserDto, dbFile.getFileSize());// 返回秒传成功状态码resultDto.setStatus(UploadStatusEnums.UPLOAD_SECONDS.getCode());return resultDto;}}//==================== 正常上传流程继续执行 ====================//// ...(后续为普通上传处理逻辑)} catch (BusinessException e) {// 特殊异常处理(包含空间不足等业务异常)logger.error("文件上传失败", e);throw e; // 抛出异常触发事务回滚}return resultDto;}/*** 自动重命名策略(防止同一目录下文件重名)* 实现逻辑类似Windows的"同名文件(1)"处理*/private String autoRename(String filePid, String userId, String fileName) {FileInfoQuery query = new FileInfoQuery();query.setUserId(userId);query.setFilePid(filePid);query.setDelFlag(FileDelFlagEnums.USING.getFlag());query.setFileName(fileName);// 查询同名文件数量(命中idx_user_pid_name索引)Integer count = this.fileInfoMapper.selectCount(query);if (count > 0) {// 调用字符串工具生成带序号的文件名(如"文档(1).pdf")return StringTools.rename(fileName); }return fileName;}/*** 用户空间更新(双写策略)* 先更新数据库 -> 再更新Redis缓存*/private void updateUserSpace(SessionWebUserDto webUserDto, Long useSpace) {// 数据库更新(使用乐观锁防止超卖)int count = userInfoMapper.updateUserSpace(webUserDto.getUserId(), useSpace, null);if (count == 0) { // 更新行数为0表示空间不足throw new BusinessException(ResponseCodeEnum.CODE_904);}// Redis缓存更新(保证读取性能)UserSpaceDto spaceDto = redisComponent.getUserSpaceUse(webUserDto.getUserId());spaceDto.setUseSpace(spaceDto.getUseSpace() + useSpace);redisComponent.saveUserSpaceUse(webUserDto.getUserId(), spaceDto);}

文件转码模块深度解析

一、核心流程图示

转码中
非转码中
视频
图片
获取文件信息
状态校验
合并临时文件
终止流程
文件类型判断
视频切片
生成缩略图
更新封面路径
更新文件状态

二、代码模块拆解

1. 状态校验模块

if (fileInfo == null || !FileStatusEnums.TRANSFER.getStatus().equals(fileInfo.getStatus())) {return;
}

功能:确保只有处于"转码中"状态的文件才会被处理
设计考量:防止重复处理或无效操作

2. 路径生成模块

String targetFolderName = appConfig.getProjectFolder() + Constants.FILE_FOLDER_FILE;
File targetFolder = new File(targetFolderName + "/" + month);

目录结构

project_root/
├── file/
│   └── 202309/
│       └── user123_file456.mp4

优化点:按月份分目录存储,避免单目录文件过多

3. 文件合并模块

union(fileFolder.getPath(), targetFilePath, true);

实现要点

  • 使用RandomAccessFile进行随机读写
  • 10KB缓冲区平衡内存与IO效率
  • 自动清理临时目录(delSource=true)

三、关键技术实现

1. 视频处理流程

// 视频转TS格式
final String CMD_TRANSFER_2TS = "ffmpeg -y -i %s -vcodec copy -acodec copy -vbsf h264_mp4toannexb %s";
// 生成HLS切片
final String CMD_CUT_TS = "ffmpeg -i %s -c copy -map 0 -f segment -segment_list %s -segment_time 30 %s/%s_%%4d.ts";

输出结构

video.mp4
├── video.m3u8
├── video_0001.ts
├── video_0002.ts
└── video_0003.ts

2. 缩略图生成

// 视频封面截取
ScaleFilter.createCover4Video(源文件, 150px, 输出路径);
// 图片缩略图生成
ScaleFilter.createThumbnailWidthFFmpeg(源文件, 150px, 输出路径);

降级策略:缩略图生成失败时直接复制原文件

3. 乐观锁实现

UPDATE file_info 
SET status = #{newStatus} 
WHERE file_id = #{fileId} AND status = #{oldStatus}

并发控制:确保只有初始状态为TRANSFER的记录能被更新

四、异常处理机制

1. 错误日志记录

catch (Exception e) {logger.error("文件转码失败,文件ID:{},userId:{}", fileId, webUserDto.getUserId(), e);transferSuccess = false;
}

2. 状态回滚

finally {updateInfo.setStatus(transferSuccess ? USING : TRANSFER_FAIL);fileInfoMapper.updateFileStatusWithOldStatus(...);
}

五、代码

/*** 文件转码处理服务* 使用@Async实现异步处理,避免阻塞主线程*/
@Async
public void transferFile(String fileId, SessionWebUserDto webUserDto) {// 初始化转码结果标识Boolean transferSuccess = true;String targetFilePath = null;  // 最终文件存储路径String cover = null;           // 封面图路径FileTypeEnums fileTypeEnum = null; // 文件类型枚举// 1. 查询文件基础信息FileInfo fileInfo = this.fileInfoMapper.selectByFileIdAndUserId(fileId, webUserDto.getUserId());try {// 2. 状态校验(双重检查)if (fileInfo == null || !FileStatusEnums.TRANSFER.getStatus().equals(fileInfo.getStatus())) {return; // 非转码状态文件直接返回}// 3. 准备文件存储路径// 临时文件目录:/temp/userId_fileId/String tempFolderName = appConfig.getProjectFolder() + Constants.FILE_FOLDER_TEMP;String currentUserFolderName = webUserDto.getUserId() + fileId;File fileFolder = new File(tempFolderName + currentUserFolderName);// 4. 解析文件信息String fileSuffix = StringTools.getFileSuffix(fileInfo.getFileName()); // 获取文件后缀String month = DateUtil.format(fileInfo.getCreateTime(), DateTimePatternEnum.YYYYMM.getPattern());// 5. 创建目标目录(按年月分类)// 最终存储路径:/file/yyyyMM/String targetFolderName = appConfig.getProjectFolder() + Constants.FILE_FOLDER_FILE;File targetFolder = new File(targetFolderName + "/" + month);if (!targetFolder.exists()) {targetFolder.mkdirs(); // 不存在则创建目录}// 6. 合并分片文件String realFileName = currentUserFolderName + fileSuffix; // 构建唯一文件名targetFilePath = targetFolder.getPath() + "/" + realFileName;union(fileFolder.getPath(), targetFilePath, fileInfo.getFileName(), true);// 7. 根据文件类型进行特殊处理fileTypeEnum = FileTypeEnums.getFileTypeBySuffix(fileSuffix);// 7.1 视频文件处理if (FileTypeEnums.VIDEO == fileTypeEnum) {// 视频切片(HLS协议)cutFile4Video(fileId, targetFilePath);// 生成视频封面(首帧截图)cover = month + "/" + currentUserFolderName + Constants.IMAGE_PNG_SUFFIX;String coverPath = targetFolderName + "/" + cover;ScaleFilter.createCover4Video(new File(targetFilePath), Constants.LENGTH_150, new File(coverPath));} // 7.2 图片文件处理else if (FileTypeEnums.IMAGE == fileTypeEnum) {// 生成缩略图cover = month + "/" + realFileName.replace(".", "_."); // 缩略图命名规则String coverPath = targetFolderName + "/" + cover;// 尝试用FFmpeg生成缩略图Boolean created = ScaleFilter.createThumbnailWidthFFmpeg(new File(targetFilePath), Constants.LENGTH_150, new File(coverPath), false);// 降级方案:生成失败直接复制原图if (!created) {FileUtils.copyFile(new File(targetFilePath), new File(coverPath));}}} catch (Exception e) {// 8. 异常处理logger.error("文件转码失败,文件ID:{},userId:{}", fileId, webUserDto.getUserId(), e);transferSuccess = false;} finally {// 9. 更新文件状态(使用乐观锁)FileInfo updateInfo = new FileInfo();updateInfo.setFileSize(new File(targetFilePath).length()); // 设置实际文件大小updateInfo.setFileCover(cover); // 设置封面路径updateInfo.setStatus(transferSuccess ? FileStatusEnums.USING.getStatus() : FileStatusEnums.TRANSFER_FAIL.getStatus());/*** 乐观锁实现说明:* UPDATE file_info * SET status = #{newStatus} * WHERE file_id = #{fileId} *   AND user_id = #{userId} *   AND status = #{oldStatus}*   * 确保只有状态为TRANSFER的记录会被更新,防止并发操作导致状态不一致*/fileInfoMapper.updateFileStatusWithOldStatus(fileId, webUserDto.getUserId(), updateInfo, FileStatusEnums.TRANSFER.getStatus());}
}/*** 视频切片处理方法*/
private void cutFile4Video(String fileId, String videoFilePath) {// 创建切片目录(与视频文件同名目录)File tsFolder = new File(videoFilePath.substring(0, videoFilePath.lastIndexOf(".")));if (!tsFolder.exists()) {tsFolder.mkdirs();}// FFmpeg命令模板final String CMD_TRANSFER_2TS = "ffmpeg -y -i %s -vcodec copy -acodec copy -vbsf h264_mp4toannexb %s";final String CMD_CUT_TS = "ffmpeg -i %s -c copy -map 0 -f segment -segment_list %s -segment_time 30 %s/%s_%%4d.ts";// 1. 先转成TS格式String tsPath = tsFolder + "/" + Constants.TS_NAME;String cmd = String.format(CMD_TRANSFER_2TS, videoFilePath, tsPath);ProcessUtils.executeCommand(cmd, false);// 2. 生成HLS切片和m3u8索引cmd = String.format(CMD_CUT_TS, tsPath, tsFolder.getPath() + "/" + Constants.M3U8_NAME, tsFolder.getPath(), fileId);ProcessUtils.executeCommand(cmd, false);// 3. 清理临时TS文件new File(tsPath).delete();
}

文件合并模块深度解析

一、核心流程图示

检查目录存在
目录存在?
初始化目标文件
抛出异常
遍历分片文件
读取分片数据
写入目标文件
是否末片?
清理临时文件

二、代码模块拆解

1. 参数说明

/**• @param dirPath 分片存储目录(如:/temp/user123_file456/)• @param toFilePath 合并后文件路径(如:/file/202309/user123_file456.mp4)• @param fileName 原始文件名(仅用于日志记录)• @param delSource 是否删除源分片(true-合并后自动清理)*/
private void union(String dirPath, String toFilePath, String fileName, Boolean delSource)

2. 文件校验模块

File dir = new File(dirPath);
if (!dir.exists()) {throw new BusinessException("目录不存在"); // 快速失败机制
}

设计考量:前置检查避免无效操作

3. 核心合并逻辑

try (RandomAccessFile writeFile = new RandomAccessFile(targetFile, "rw")) {byte[] buffer = new byte[1024 * 10]; // 10KB缓冲for (int i = 0; i < fileList.length; i++) {try (RandomAccessFile readFile = new RandomAccessFile(chunkFile, "r")) {while ((len = readFile.read(buffer)) != -1) {writeFile.write(buffer, 0, len); // 增量写入}}}
}

关键技术点

  • 使用RandomAccessFile实现随机读写
  • 固定10KB缓冲区平衡内存与IO效率
  • try-with-resource自动关闭资源

三、关键处理逻辑

1. 分片读取机制

while ((len = readFile.read(b)) != -1) {writeFile.write(b, 0, len);
}

工作流程

  1. read(b)从当前文件指针读取数据
  2. 返回实际读取字节数(len),-1表示EOF
  3. write(b,0,len)写入目标文件
  4. 指针自动后移,下次读取继续

2. 异常处理机制

catch (Exception e) {logger.error("合并分片失败", e);throw new BusinessException("合并分片失败"); // 业务异常封装
}
finally {if (null != writeFile) {writeFile.close(); // 确保资源释放}
}

保障措施

  • 记录详细错误日志
  • 异常转换(Exception -> BusinessException)
  • 资源释放兜底

四、代码

/*** 合并分片文件到完整文件* * @param dirPath 分片文件存储目录(格式:/temp/userId_fileId/)* @param toFilePath 合并后的目标文件路径(格式:/file/yyyyMM/userId_fileId.ext)* @param fileName 原始文件名(仅用于日志记录)* @param delSource 是否删除源分片文件(true=合并后自动清理)* * 实现原理:* 1. 顺序读取编号为0-N的分片文件* 2. 使用10KB缓冲区流式合并* 3. 支持事务回滚(异常时中断合并)*/
private void union(String dirPath, String toFilePath, String fileName, Boolean delSource) {// 1. 校验分片目录是否存在File dir = new File(dirPath);if (!dir.exists()) {throw new BusinessException("目录不存在"); // 快速失败}// 2. 获取分片文件列表(按文件名排序)File[] fileList = dir.listFiles();File targetFile = new File(toFilePath);RandomAccessFile writeFile = null;try {// 3. 初始化目标文件(随机访问模式)writeFile = new RandomAccessFile(targetFile, "rw");byte[] buffer = new byte[1024 * 10]; // 10KB缓冲区// 4. 遍历所有分片文件(命名格式:0,1,2...)for (int i = 0; i < fileList.length; i++) {File chunkFile = new File(dirPath + "/" + i);RandomAccessFile readFile = null;try {// 5. 打开当前分片文件(只读模式)readFile = new RandomAccessFile(chunkFile, "r");int bytesRead;// 6. 流式读取分片内容(自动维护文件指针)while ((bytesRead = readFile.read(buffer)) != -1) {// 7. 写入目标文件(追加模式)writeFile.write(buffer, 0, bytesRead);}} catch (Exception e) {logger.error("合并分片[{}]失败", i, e);throw new BusinessException("合并分片失败");} finally {// 8. 确保关闭当前分片文件if (readFile != null) {try {readFile.close();} catch (IOException e) {logger.warn("关闭分片文件失败", e);}}}}} catch (Exception e) {logger.error("合并文件[{}]失败", fileName, e);throw new BusinessException("合并文件" + fileName + "出错了");} finally {// 9. 资源清理工作try {if (writeFile != null) {writeFile.close(); // 关闭目标文件}// 10. 按需删除源分片(事务提交后执行)if (delSource && dir.exists()) {try {FileUtils.deleteDirectory(dir); // 递归删除目录logger.debug("已清理分片目录:{}", dirPath);} catch (IOException e) {logger.error("删除分片目录失败", e);}}} catch (IOException e) {logger.error("关闭文件流失败", e);}}
}/*** 技术要点说明:* 1. 文件指针机制:*    - RandomAccessFile自动维护读取位置指针*    - 每次read()都会从上次结束位置继续读取* * 2. 内存优化:*    - 固定10KB缓冲区避免大内存占用*    - 流式处理支持超大文件合并* * 3. 异常处理:*    - 分片级异常记录具体失败分片编号*    - 文件级异常携带原始文件名* * 4. 资源管理:*    - 使用finally确保文件句柄释放*    - 删除操作放在最后确保主流程完成*/

异步转码机制技术解析

一、代码片段关键逻辑说明

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {@Overridepublic void afterCommit() {fileInfoService.transferFile(fileInfo.getFileId(), webUserDto);}
});

此代码实现了:

  1. 事务边界控制:确保转码操作在数据库事务提交后触发
  2. 异步执行保障:通过Spring代理调用实现真正的异步
  3. 数据可见性:保证转码任务读取到已持久化的文件记录

二、异步转码的必要性

1. 防止事务未提交导致数据不可见

场景同步转码异步转码(当前方案)
事务提交前可能读取到未提交的临时数据不会触发转码任务
事务提交后正常执行但阻塞主线程通过事务回调保证数据可见性

2. 性能优化对比

// 同步方式(伪代码)
@Transactional
public void uploadFile() {saveToDB();          // 耗时1mstranscodeFile();      // 耗时30s → 接口响应延迟30s+
}// 异步方式(当前实现)
@Transactional
public void uploadFile() {saveToDB();          // 耗时1msregisterAsyncTask(); // 耗时0.5ms → 接口响应延迟≈1.5ms
}

三、具体技术实现分析

1. 事务同步器工作原理

App Transaction AsyncTask 开启事务 执行数据库操作 注册同步回调 提交事务 触发afterCommit回调 执行转码任务 App Transaction AsyncTask

2. 关键组件说明

组件作用
TransactionSynchronizationSpring事务同步器接口,提供事务生命周期钩子
afterCommit()事务成功提交后的回调入口点
@Async代理机制通过CGLIB生成代理类,实现线程池任务提交

四、设计优势体现

1. 数据一致性保障

// 文件信息插入语句(事务内)
this.fileInfoMapper.insert(fileInfo); // 转码任务执行时(事务已提交)
FileInfo dbFile = fileInfoMapper.selectByFileId(fileId); // 确保读取到已提交数据

2. 异常处理机制

场景处理方式
事务回滚afterCommit()不会执行,转码任务不会被触发
转码失败通过finally块更新状态为TRANSFER_FAIL,记录详细日志
服务重启通过TRANSFER状态的任务扫描机制进行补偿

五、扩展设计思考

1. 消息队列增强方案

// 事务提交后发送MQ消息
@Transactional
public void uploadFile() {saveToDB();TransactionSynchronizationManager.registerSynchronization(() -> {rocketMQTemplate.sendAsync("transcode_topic", fileId);});
}// 消费者端
@RocketMQMessageListener(topic = "transcode_topic")
public class TranscodeConsumer {public void process(String fileId) {fileService.transferFile(fileId);}
}

2. 分布式事务保障

1. 写数据库
2. 写事务消息
3. 投递消息
4. 完成转码
5. 更新状态
上传事务
主事务
RocketMQ
转码服务
回调通知

该设计通过事务同步机制与异步处理的结合,实现了:

  1. 高响应速度:主流程耗时从秒级降到毫秒级
  2. 数据强一致:通过事务边界控制保证可见性
  3. 资源隔离:转码任务使用独立线程池
  4. 系统可扩展:可平滑升级为分布式任务系统

视频切割处理流程解析

核心处理步骤

1. 准备切片目录

File tsFolder = new File(videoFilePath.substring(0, videoFilePath.lastIndexOf(".")));
tsFolder.mkdirs();  // 创建与视频同名的目录用于存放切片

示例
/data/video.mp4/data/video/ 目录

2. 视频转TS格式(关键步骤)

final String CMD_TRANSFER_2TS = "ffmpeg -y -i %s -vcodec copy -acodec copy -vbsf h264_mp4toannexb %s";

参数说明

参数作用
-vcodec copy视频流直接复制(无重编码)
-acodec copy音频流直接复制
-vbsf h264_mp4toannexb将MP4封装转为TS支持的格式

执行效果
生成临时TS文件:/data/video/index.ts

3. 生成HLS切片(核心处理)

final String CMD_CUT_TS = "ffmpeg -i %s -c copy -map 0 -f segment -segment_list %s -segment_time 30 %s/%s_%%4d.ts";

关键参数

参数作用
-f segment启用分段模式
-segment_time30每段30秒
-segment_listx.m3u8生成索引文件
%4d.ts切片命名格式(0001.ts)

输出结构

/data/video/
├── playlist.m3u8    # HLS主索引文件
├── file_0001.ts    # 第一段切片
├── file_0002.ts    # 第二段切片
└── ...             # 其他切片

4. 清理临时文件

new File(tsPath).delete();  // 删除中间文件index.ts

技术原理图解

原始MP4
转TS格式
切片处理
m3u8索引
0001.ts
0002.ts

典型HLS文件结构

playlist.m3u8 示例
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:30
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:30.000000,
file_0001.ts
#EXTINF:28.000000,
file_0002.ts
#EXT-X-ENDLIST

代码

/*** 视频文件切割处理方法(HLS协议)* @param fileId 文件唯一标识(用于生成切片文件名)* @param videoFilePath 原始视频文件完整路径*/
private void cutFile4Video(String fileId, String videoFilePath) {// 1. 创建切片存储目录(与视频文件同名目录)// 示例:/data/video.mp4 -> /data/video/File tsFolder = new File(videoFilePath.substring(0, videoFilePath.lastIndexOf(".")));if (!tsFolder.exists()) {tsFolder.mkdirs(); // 递归创建多级目录}// 2. 定义FFmpeg命令模板// 命令1:将MP4转换为TS格式(不重新编码)final String CMD_TRANSFER_2TS = "ffmpeg -y -i %s -vcodec copy -acodec copy -vbsf h264_mp4toannexb %s";// 命令2:将TS文件切片并生成m3u8索引final String CMD_CUT_TS = "ffmpeg -i %s -c copy -map 0 -f segment -segment_list %s -segment_time 30 %s/%s_%%4d.ts";// 3. 生成中间TS文件路径// 示例:/data/video/index.tsString tsPath = tsFolder + "/" + Constants.TS_NAME;// 4. 执行格式转换(MP4->TS)// 参数说明:// -y 覆盖输出文件// -vcodec copy 视频流直接复制// -acodec copy 音频流直接复制// -vbsf h264_mp4toannexb 转换视频比特流格式String cmd = String.format(CMD_TRANSFER_2TS, videoFilePath, tsPath);ProcessUtils.executeCommand(cmd, false); // 执行命令行// 5. 执行切片操作并生成m3u8索引// 参数说明:// -c copy 音视频流都不重新编码// -map 0 处理所有数据流// -f segment 启用分段模式// -segment_time 30 每段30秒// -segment_list 生成m3u8索引文件路径// %04d.ts 生成形如0001.ts的切片文件cmd = String.format(CMD_CUT_TS, tsPath, tsFolder.getPath() + "/" + Constants.M3U8_NAME, // m3u8文件路径tsFolder.getPath(), // 切片输出目录fileId // 切片文件名前缀);ProcessUtils.executeCommand(cmd, false);// 6. 清理临时文件(中间TS文件)// 示例:删除/data/video/index.tsnew File(tsPath).delete(); 
}

相关文章:

【EasyPan】文件上传、文件秒传、文件转码、文件合并、异步转码、视频切割分析

【EasyPan】项目常见问题解答&#xff08;自用&持续更新中…&#xff09;汇总版 文件上传方法解析 一、方法总览 Transactional(rollbackFor Exception.class) public UploadResultDto uploadFile(...)核心能力&#xff1a; 秒传验证&#xff1a;通过MD5文件大小实现文…...

React18+ 项目搭建-从初始化、技术选型到开发部署的全流程规划

搭建一个 React 项目需要从项目初始化、技术选型到开发部署的全流程规划。以下是详细步骤和推荐的技术栈&#xff1a; 一、项目初始化 1. 选择脚手架工具 推荐工具&#xff1a; Vite&#xff08;现代轻量级工具&#xff0c;支持 React 模板&#xff0c;速度快&#xff09;&am…...

day3 打卡训练营

循环语句和判断语句之前已经会了&#xff0c;把列表的方法练一练 浙大疏锦行 python训练营介绍...

MySQL VS SQL Server:优缺点全解析

数据库选型、企业协作、技术生态、云数据库 1.1 MySQL优缺点分析 优点 开源免费 社区版完全免费&#xff0c;适合预算有限的企业 允许修改源码定制功能&#xff08;需遵守GPL协议&#xff09; 跨平台兼容性 支持Windows/Linux/macOS&#xff0c;适配混合环境部署 云服务商…...

【CPP】固定大小内存池

程序运行时&#xff0c;通常会频繁地进行内存的分配和释放操作。传统的内存分配方式&#xff08;如使用new和delete运算符&#xff09;可能会导致内存碎片的产生&#xff0c;并且每次分配和释放内存都有一定的时间开销。内存池通过在程序启动时一次性分配一大块内存或一次性分配…...

[数据结构]树和二叉树

概念 树是一种 非线性 的数据结构&#xff0c;它是由 n &#xff08; n>0 &#xff09;个有限结点组成一个具有层次关系的集合。 树形结构中&#xff0c;子树之间不能有交集&#xff0c;否则就不是树形结构 双亲结点或父结点 &#xff1a;若一个结点含有子结点&#xff0c;则…...

监控页面卡顿PerformanceObserver

监控页面卡顿PerformanceObserver 性能观察器掘金 const observer new PerformanceObserver((list) > {}); observer.observe({entryTypes: [longtask], })...

Web开发-JavaEE应用JNDI注入RMI服务LDAP服务DNS服务高版本限制绕过

知识点&#xff1a; 1、安全开发-JavaEE-JNDI注入-LADP&RMI&DNS等 2、安全开发-JavaEE-JNDI注入-项目工具&手工原理等 演示案例-WEB开发-JavaEE-JNDI注入&LDAP&RMI服务&DNS服务&高版本限制绕过 JNDI全称为 Java Naming and DirectoryInterface&am…...

深度学习训练中的显存溢出问题分析与优化:以UNet图像去噪为例

最近在训练一个基于 Tiny-UNet 的图像去噪模型时&#xff0c;我遇到了经典但棘手的错误&#xff1a; RuntimeError: CUDA out of memory。本文记录了我如何从复现、分析&#xff0c;到逐步优化并成功解决该问题的全过程&#xff0c;希望对深度学习开发者有所借鉴。 训练数据&am…...

【Spring】单例模式的创建方式(Bean解析)

在Java中&#xff0c;单例模式&#xff08;Singleton Pattern&#xff09;确保一个类只有一个实例&#xff0c;并提供全局访问点。以下是实现单例的五种常见方式&#xff1a;懒汉式、饿汉式、双重检查锁、静态内部类和枚举&#xff0c;包括代码示例和优缺点分析。 1. 懒汉式&am…...

小小矩阵设计

在电气设计图中&#xff0c;矩阵设计的接线方法是通过结构化布局实现多灵活链接的技术&#xff0c;常用于信号切换、配电调压或更加复杂的控制场景。 今天聊一种在电气图纸中用到的一种简单矩阵接法&#xff0c;一眼就看明白&#xff0c;很大程度简化了程序控制点和继电器的使用…...

IOT项目——双轴追光系统

双轴太阳能追光系统 - ESP32实现 系统概述 这个系统使用&#xff1a; ESP32开发板2个舵机&#xff08;水平方向和垂直方向&#xff09;4个光敏电阻&#xff08;用于检测光照方向&#xff09;适当的电阻&#xff08;用于光敏电阻分压&#xff09; 接线示意图 --------------…...

深度学习基石:神经网络核心知识全解析(一)

神经网络核心知识全解析 一、神经网络概述 神经网络作为机器学习领域的关键算法&#xff0c;在现代生活中发挥着重要作用&#xff0c;广泛应用于图像识别、语音处理、智能推荐等诸多领域&#xff0c;深刻影响着人们的日常生活。它通过模拟人类大脑神经系统的结构和功能&#…...

什么是 金字塔缩放(Multi-scale Input)

金字塔缩放 什么是金字塔缩放(Multi-scale Input)什么场景下会用到金字塔缩放?图像识别目标跟踪图像压缩视频处理如何在计算机程序中实现金字塔缩放?准备数据缩小数据(构建金字塔的上层)存储数据使用数据(在程序中应用金字塔缩放)金字塔缩放的记忆卡片什么是金字塔缩放(M…...

从零开始配置 Zabbix 数据库监控:MySQL 实战指南

Zabbix作为一款开源的分布式监控工具&#xff0c;在监控MySQL数据库方面具有显著优势&#xff0c;能够为数据库的稳定运行、性能优化和故障排查提供全面支持。以下是使用Zabbix监控MySQL数据库的配置。 一、安装 Zabbix Agent 和 MySQL 1. 安装 Zabbix Agent services:zabbix…...

机器学习超参数优化全解析

机器学习超参数优化全解析 摘要 本文全面深入地剖析了机器学习模型中的超参数优化策略&#xff0c;涵盖了从参数与超参数的本质区别&#xff0c;到核心超参数&#xff08;如学习率、批量大小、训练周期&#xff09;的动态调整方法&#xff1b;从自动化超参数优化技术&#xf…...

【算法】双指针8道速通(C++)

1. 移动零 思路&#xff1a; 拿示例1的数据来举例&#xff0c;定义两个指针&#xff0c;cur和dest&#xff0c;cur表示当前遍历的元素&#xff0c;dest以及之前表示非零元素 先用cur去找非零元素&#xff0c;每找到一个非零元素&#xff0c;就让dest的下一个元素与之交换 单个…...

synchronized锁

在了解锁之前我们要先了解对象布局 什么是java对象布局 在JVM中&#xff0c;对象在内存中存储的布局可以分为三块区域&#xff0c;即实例化之后的对象 对象头&#xff1a;分配的空间是固定的12Byte&#xff0c;由Mark Word&#xff08;标记字段&#xff09;和Class Pointer&…...

实训Day-2 流量分析与安全杂项

目录 实训Day-2-1流量分析实战 实训目的 实训任务1 SYN半链接攻击流量分析 实训任务2 SQL注入攻击流量分析一 实训任务3 SQL注入攻击流量分析二 实训任务4 Web入侵溯源一 实训任务5 Web入侵溯源二 ​编辑 实训Day-2-1安全杂项实战 实训目的 实训任务1 流量分析 FTP…...

LOH 怎么进行深度标准化?

The panel of normals is applied by replacing the germline read depth of the sample with the median read depth of samples with the same genotype in our panel. 1.解释: panel of normal 正常组织&#xff0c;用于识别技术噪音 germline read depth: 胚系测序深度。根…...

[预备知识]3. 自动求导机制

自动求导机制 本章节介绍 PyTorch 的自动求导机制&#xff0c;包括计算图、反向传播和梯度计算的原理及实现。 1. 计算图原理 计算图是深度学习框架中的一个核心概念&#xff0c;它描述了计算过程中各个操作之间的依赖关系。 计算图由节点&#xff08;节点&#xff09;和边…...

蓝桥杯中的知识点

总结&#xff1a; 这次考的并不理想 比赛前好多知识点遗漏 但到此为止已经结束了 mod 是 模运算&#xff08;Modulo Operation&#xff09;的缩写&#xff0c;表示求两个数相除后的 余数 10mod31 (a % b) &#xff08;7%21&#xff09; 1e9代表1乘以10的9次方&#xff0c…...

2023蓝帽杯初赛内存取证-6

这里需要用到pslist模块&#xff0c;结合一下查找关键词&#xff1a; vol.py -f memdump.mem --profile Win7SP1x64 pslist | grep -E "VeraCrypt" 将2023-06-20 16:47:41 UTC0000世界标准时间转换成北京时间&#xff1a; 答案&#xff1a;2023-06-21 00:47:41...

《MySQL 核心技能:SQL 查询与数据库概述》

MySQL 核心技能&#xff1a;SQL 查询与数据库概述 一、数据库概述1. 什么是数据库2.为什么要使用数据库3.数据库的相关概念3.1 数据库&#xff08;DB&#xff09;&#xff1a;数据的“仓库”3.2 数据库管理系统&#xff08;DBMS&#xff09;&#xff1a;数据库的“管家”3.3 SQ…...

三维几何变换

一、学习目的 了解几何变换的意义 掌握三维基本几何变换的算法 二、学习内容 在本次试验中&#xff0c;我们实现透视投影和三维几何变换。我们首先定义一个立方体作为我们要进行变换的三维物体。 三、具体代码 &#xff08;1&#xff09;算法实现 // 获取Canvas元素 con…...

TDengine 查询引擎设计

简介 TDengine 作为一个高性能的时序大数据平台&#xff0c;其查询与计算功能是核心组件之一。该平台提供了丰富的查询处理功能&#xff0c;不仅包括常规的聚合查询&#xff0c;还涵盖了时序数据的窗口查询、统计聚合等高级功能。这些查询计算任务需要 taosc、vnode、qnode 和…...

15.第二阶段x64游戏实战-分析怪物血量(遍历周围)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 上一个内容&#xff1a;14.第二阶段x64游戏实战-分析人物的名字 如果想实现自动打怪&#xff0c;那肯定…...

vue浅试(1)

先安装了vue nvm安装详细教程&#xff08;安装nvm、node、npm、cnpm、yarn及环境变量配置&#xff09; 稍微了解了一下cursor ​ cursor的使用 请出我们的老师&#xff1a; 提示词&#xff1a; 你是我的好朋友也是一个前端专家&#xff0c;你能向我传授前端知识&#xff0c…...

VSCode连服务器一直处于Downloading

使用vscode的remote插件连接远程服务器时&#xff0c;部分服务器可能会出现一直处于Downloading VS Code Server的情况 早期的一些教程&#xff0c;如https://blog.csdn.net/chongbin007/article/details/126958840, https://zhuanlan.zhihu.com/p/671718415给出的方法是手动下…...

QGIS实用功能:加载天地图与下载指定区域遥感影像

QGIS 实用功能&#xff1a;加载天地图与下载指定区域遥感影像 目录标题 QGIS 实用功能&#xff1a;加载天地图与下载指定区域遥感影像一、安装天地图插件&#xff0c;开启地图加载之旅二、获取天地图密钥&#xff0c;获取使用权限三、加载天地图服务&#xff0c;查看地图数据四…...

mybatis-plus开发orm

1、mybatis 使用mybatis-generator自动生成代码 这个也是有系统在使用 2、mybatis-plus开发orm--有的系统在使用 MybatisPlus超详细讲解_mybatis-plus-CSDN博客...

ubuntu 交叉编译 macOS 库, 使用 osxcross 搭建 docker 编译 OS X 库

1. ubuntu 交叉编译 macOS 库, 使用 osxcross 搭建 docker 编译 OS X 库 1. ubuntu 交叉编译 macOS 库, 使用 osxcross 搭建 docker 编译 OS X 库 1.1. 安装依赖1.2. 安装 osxcross 及 macOS SDK 1.2.1. 可能错误 1.3. 编译 cmake 类工程1.4. 编译 configure 类工程1.5. 单文件…...

抱佛脚之学SSM四

MyBatis基础 一个接口对应一个映射文件 在映射文件中指定对应接口指定的位置 sql语句中id对应方法名par..参数的类型&#xff0c;resul..返回值的类型 WEB-INF下的文件是受保护的&#xff0c;不能直接访问&#xff0c;只能通过请求转发的方式访问 SqlSessionFactory&#xff1…...

2.5 函数的拓展

1.匿名函数&#xff08;简化代码&#xff09; python中没有这个概念&#xff0c;通过lambda关键字可以简化函数的代码写法 2.lambda表达式 arguments lambda 参数列表 &#xff1a; 函数体 print(aarguments(参数)) #测试lambda #原本代码def sum1(x,y):return xyprint(sum1…...

深度学习--卷积神经网络数据增强

文章目录 一、数据增强1、什么是数据增强&#xff1f;2、为什么需要数据增强&#xff1f; 二、常见的数据增强方法1、图像旋转2、图像翻转3、图像缩放4、图像平移5、图像剪切6、图像亮度、对比度、饱和度调整7、噪声添加8、随机扰动 三、代码实现1、预处理2、使用数据增强增加训…...

Buffer of Thoughts: Thought-Augmented Reasoningwith Large Language Models

CODE: NeurIPS 2024 https://github.com/YangLing0818/buffer-of-thought-llm Abstract 我们介绍了思想缓冲(BoT)&#xff0c;一种新颖而通用的思想增强推理方法&#xff0c;用于提高大型语言模型(大型语言模型)的准确性、效率和鲁棒性。具体来说&#xff0c;我们提出了元缓冲…...

mybatisX动态切换数据源直接执行传入sql

代码 mapper接口中方法定义如下&#xff0c;其中#dbName代表传入的数据源变量&#xff08;取值可参考application.properties中spring.datasource.dynamic.datasource指定的数据源&#xff09; DS("#dbName")List<LinkedHashMap<String, Object>> execu…...

N8N MACOS本地部署流程避坑指南

最近n8n很火&#xff0c;就想在本地部署一个&#xff0c;尝尝鲜&#xff0c;看说明n8n是开源软件&#xff0c;可以在本地部署&#xff0c;于是就尝试部署了下&#xff0c;大概用了1个多小时&#xff0c;把相关的过程记录一下&#xff1a; 1、基础软件包 abcXu-MacBook-m2-Air…...

搜索策略的基本概念

搜索是人工智能中的一个基本问题&#xff0c;是推理不可分割的一部分&#xff0c;它直接关系到智能系统的性能与运行效率&#xff0c;因而尼尔逊把它列为人工智能研究中的四个核心问题之一。在过去40多年中&#xff0c;人工智能界已对搜索技术开展了大量研究&#xff0c;取得了…...

云原生--CNCF-1-云原生计算基金会介绍(云原生生态的发展目标和未来)

1、CNCF定义与背景 云原生计算基金会&#xff08;Cloud Native Computing Foundation&#xff0c;CNCF&#xff09;是由Linux基金会于2015年12月发起成立的非营利组织&#xff0c;旨在推动云原生技术的标准化、开源生态建设和行业协作。其核心目标是通过开源项目和社区协作&am…...

【Chrome插件开发】某视频网站的m4s视频/音频下载方案,及其Chrome插件实现-v250415

文章目录 引言效果v1.0.0 TODO让AI写初稿两条路&#xff1a;在content.js里&#xff0c;还是popup.js里发请求&#xff1f;World in content.js新建项目如何打包background.js&#xff1a;在鼠标右键菜单添加一个选项&#xff0c;点击后通知content.js第一次创建弹窗eslint 9如…...

Nginx:前后端分离配置(静态资源+反向代理)

Nginx 前后端分离配置 [!IMPORTANT] 前端静态资源位置&#xff1a;/www/wwwroot/dist后端部署端口&#xff1a;9999 server {listen 80;server_name www.0ll1.com;location / {root /www/wwwroot/dist;try_files $uri $uri/ /index.html;index index.html index.htm;…...

go中map和slice非线程安全

参考视频&#xff1a;百度 Go二面&#xff1a; map与切片哪个是线程安全的_哔哩哔哩_bilibili go中的map和slice是非线程安全类型的。 非线程安全类型的表现为&#xff1a; 并发调用时会报错并发调用后结果不可预测 go中三种线程安全类型&#xff1a; channel&#xff0c;底…...

第T9周:猫狗识别2

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 第T9周&#xff1a;猫狗识别2 tf.config.list_physical_devices(“GPU”)&#xff0c;用于检测当前系统是否有可用的 GPU&#xff0c;并将结果存入 gpus 变量…...

AlmaLinux 9.5 调整home和根分区大小

在使用linux的过程中, 有时会出现因为安装系统时分区不当导致有的分区空间不足,而有的分区空间过剩的情况.下面本文将讲述解决linux系统AlmaLinux 下调整home和根分区大小的方法。 1、查看现有分区 df -Th2、备份/home中的用户数据 mkdir /backup && mv /home/* /ba…...

[Vue3]绑定props 默认值

前言 使用TS、Vue3组织组件中传入props的通用方式 步骤 步骤1&#xff1a;使用 defineProps 定义 Props 使用interface定义props中各项的类型&#xff1a; // 组件List.vue// 定义 Props 类型和接口 interface ListItem {name: string;time: string;content: {status: numbe…...

【android bluetooth 协议分析 11】【AVDTP详解 1】【宏观感受一下avdtp是个啥东东】

我们先从宏观感受一下avdtp协议是个啥东东&#xff0c; 和 a2dp 是啥关系。 在蓝牙协议中的层次。以及他是如何和 例如l2cap 、sdp、a2dp 配合的。先从宏观把握&#xff0c;我们在逐步展开对 avdtp 的源码分析。 我们先从生活中的小例子感性的认识一下 avdtp 在 蓝牙协议中的作…...

【MATLAB第116期】基于MATLAB的NBRO-XGBoost的SHAP可解释回归模型(敏感性分析方法)

【MATLAB第116期】基于MATLAB的NBRO-XGBoost的SHAP可解释回归模型&#xff08;敏感性分析方法&#xff09; 引言 该文章实现了一个可解释的回归模型&#xff0c;使用NBRO-XGBoost&#xff08;方法可以替换&#xff0c;但是需要有一定的编程基础&#xff09;来预测特征输出。该…...

【Spring】静态代理、动态代理

Java中&#xff0c;代理模式是一种设计模式&#xff0c;用于通过代理对象控制对目标对象的访问。代理可以分为静态代理和动态代理&#xff0c;其中动态代理又包括JDK动态代理和CGLIB动态代理。这些机制在Spring框架中广泛用于AOP&#xff08;面向切面编程&#xff09;、事务管理…...

关于el-table可展开行实现懒加载的方案

场景&#xff1a; 一个流程记录&#xff0c;以表格的形式展示。点击展开表格的某一行&#xff0c;可以看到该流程的详细记录。但是&#xff0c;详细记录数据独立于表格数据&#xff0c;在还没有展开这一行的时候就不去请求这一行的详细数据&#xff0c;以便加快网络请求的速度。…...