From a8cd9183c81260974bc8936b7ffa42e46c8a3b55 Mon Sep 17 00:00:00 2001 From: shihongwei <178899525@qq.com> Date: Wed, 27 May 2026 15:34:04 +0800 Subject: [PATCH 1/6] =?UTF-8?q?fix:=20=E9=94=99=E9=A2=98=E5=9B=9E=E9=A1=BE?= =?UTF-8?q?=E7=AD=94=E9=94=99=E6=AC=A1=E6=95=B0=E6=98=BE=E7=A4=BA=E4=B8=8D?= =?UTF-8?q?=E6=AD=A3=E7=A1=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../HotTrainingAnswerRecordServiceImpl.java | 17 +++++++++++++++++ .../HotTrainingAnswerRecordMapper.xml | 5 ++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/hotwj/platform/securityManagement/trainingAnswer/service/impl/HotTrainingAnswerRecordServiceImpl.java b/src/main/java/com/hotwj/platform/securityManagement/trainingAnswer/service/impl/HotTrainingAnswerRecordServiceImpl.java index 1cd1609..58c09da 100644 --- a/src/main/java/com/hotwj/platform/securityManagement/trainingAnswer/service/impl/HotTrainingAnswerRecordServiceImpl.java +++ b/src/main/java/com/hotwj/platform/securityManagement/trainingAnswer/service/impl/HotTrainingAnswerRecordServiceImpl.java @@ -293,6 +293,9 @@ public class HotTrainingAnswerRecordServiceImpl implements IHotTrainingAnswerRec // 更新 HotTrainingAnswerRecord existing = existingMap.get(entity.getQuestionSource()); entity.setId(existing.getId()); + + mergeErrorData(entity, existing); + toUpdate.add(entity); } else { // 插入 @@ -506,4 +509,18 @@ public class HotTrainingAnswerRecordServiceImpl implements IHotTrainingAnswerRec } throw new ServiceException("无法获取提交人姓名,签名校验失败"); } + + /** + * 累加错误次数并拼接最新的错误答案 + * 拼接后的总长度控制在200字符内,保留最新产生的数据(先不要) + * + * @param entity 当前传入的答题实体 + * @param existing 数据库中已存在的答题实体 + */ + private void mergeErrorData(HotTrainingAnswerRecord entity, HotTrainingAnswerRecord existing) { + // 1. 累加错误次数,前端提交一次答题,只会加一次 + int currentErrorCount = entity.getErrorCount() != null ? 1 : 0; + int existingErrorCount = existing.getErrorCount() != null ? existing.getErrorCount() : 0; + entity.setErrorCount(existingErrorCount + currentErrorCount); + } } diff --git a/src/main/resources/mapper/courseAnswer/HotTrainingAnswerRecordMapper.xml b/src/main/resources/mapper/courseAnswer/HotTrainingAnswerRecordMapper.xml index 518bba9..adec4da 100644 --- a/src/main/resources/mapper/courseAnswer/HotTrainingAnswerRecordMapper.xml +++ b/src/main/resources/mapper/courseAnswer/HotTrainingAnswerRecordMapper.xml @@ -140,7 +140,7 @@ From e8f15ae7400405daff9960442f915da44bf27426 Mon Sep 17 00:00:00 2001 From: shihongwei <178899525@qq.com> Date: Wed, 27 May 2026 16:11:25 +0800 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=BC=BA?= =?UTF-8?q?=E5=88=B6=E6=98=BE=E7=A4=BA=E8=AF=BE=E7=A8=8B=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/HotTrainingCourseConfigController.java | 3 ++- .../domain/bo/HotTrainingCourseConfigBo.java | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/hotwj/platform/securityManagement/trainingCourseConfig/controller/HotTrainingCourseConfigController.java b/src/main/java/com/hotwj/platform/securityManagement/trainingCourseConfig/controller/HotTrainingCourseConfigController.java index 2b646cf..439b0ab 100644 --- a/src/main/java/com/hotwj/platform/securityManagement/trainingCourseConfig/controller/HotTrainingCourseConfigController.java +++ b/src/main/java/com/hotwj/platform/securityManagement/trainingCourseConfig/controller/HotTrainingCourseConfigController.java @@ -66,7 +66,8 @@ public class HotTrainingCourseConfigController extends BaseController { @GetMapping("/list") @Operation(summary = "分页查询企业课程配置列表") public TableDataInfo list(HotTrainingCourseConfigBo bo, PageQuery pageQuery) { - if (bo.getTrainingId() != null && StringUtils.isNotBlank(bo.getUserId()) + // TODO + if (!Boolean.TRUE.equals(bo.getSkipIntercept()) && bo.getTrainingId() != null && StringUtils.isNotBlank(bo.getUserId()) && !isDriverTrainingAccessible(bo.getCompanyId(), bo.getTrainingId(), bo.getUserId())) { TableDataInfo result = TableDataInfo.build(Collections.emptyList()); result.setMsg("当前学习计划已关闭或已过期"); diff --git a/src/main/java/com/hotwj/platform/securityManagement/trainingCourseConfig/domain/bo/HotTrainingCourseConfigBo.java b/src/main/java/com/hotwj/platform/securityManagement/trainingCourseConfig/domain/bo/HotTrainingCourseConfigBo.java index de024d4..83bcda5 100644 --- a/src/main/java/com/hotwj/platform/securityManagement/trainingCourseConfig/domain/bo/HotTrainingCourseConfigBo.java +++ b/src/main/java/com/hotwj/platform/securityManagement/trainingCourseConfig/domain/bo/HotTrainingCourseConfigBo.java @@ -82,4 +82,9 @@ public class HotTrainingCourseConfigBo extends BaseEntity { private String updateByName; private String userId; + + /** + * 是否跳过过期/关闭拦截(用于后台详情展示等场景) + */ + private Boolean skipIntercept; } From be53d4b47bf04fd52e84734980c58a53796940d6 Mon Sep 17 00:00:00 2001 From: shihongwei <178899525@qq.com> Date: Wed, 27 May 2026 16:18:44 +0800 Subject: [PATCH 3/6] =?UTF-8?q?fix(trainingCourseRecord):=20=E7=A1=AE?= =?UTF-8?q?=E4=BF=9D=E5=AD=A6=E4=B9=A0=E6=97=B6=E9=95=BF=E5=8D=95=E8=B0=83?= =?UTF-8?q?=E4=B8=8D=E4=B8=8B=E9=99=8D=EF=BC=8C=E9=81=BF=E5=85=8D=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E8=AF=AF=E4=BC=A0=E5=AF=BC=E8=87=B4=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/HotTrainingCourseRecordServiceImpl.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/com/hotwj/platform/securityManagement/trainingCourseRecord/service/impl/HotTrainingCourseRecordServiceImpl.java b/src/main/java/com/hotwj/platform/securityManagement/trainingCourseRecord/service/impl/HotTrainingCourseRecordServiceImpl.java index dfe35f2..c60a413 100644 --- a/src/main/java/com/hotwj/platform/securityManagement/trainingCourseRecord/service/impl/HotTrainingCourseRecordServiceImpl.java +++ b/src/main/java/com/hotwj/platform/securityManagement/trainingCourseRecord/service/impl/HotTrainingCourseRecordServiceImpl.java @@ -186,6 +186,12 @@ public class HotTrainingCourseRecordServiceImpl implements IHotTrainingCourseRec update.setProgressRate(before.getProgressRate()); } } + if (before != null && before.getLearnDurationMin() != null && update.getLearnDurationMin() != null) { + // 确保学习时长(秒)单调不下降,避免前端误传0导致统计为0 + if (update.getLearnDurationMin() < before.getLearnDurationMin()) { + update.setLearnDurationMin(before.getLearnDurationMin()); + } + } } initHourPackageUsageFieldsForUpdate(update, before); validEntityBeforeSave(update); From 1faf21460effd8d089206a2a3d7f31ef81260328 Mon Sep 17 00:00:00 2001 From: shihongwei <178899525@qq.com> Date: Wed, 27 May 2026 17:31:15 +0800 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20=E8=A7=86=E9=A2=91=E5=88=87?= =?UTF-8?q?=E7=89=87=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 +- .../video/config/VideoUploadProperties.java | 7 +- .../impl/VideoChunkUploadServiceImpl.java | 149 ++++++++++++++++-- .../controller/system/SysOssController.java | 3 + .../system/service/ISysOssService.java | 10 ++ .../service/impl/SysOssServiceImpl.java | 30 ++++ 6 files changed, 187 insertions(+), 16 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7971740..d81cc64 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,13 +5,15 @@ FROM bellsoft/liberica-openjdk-rocky:17.0.16-cds LABEL maintainer="shihongwei" +RUN dnf install -y ffmpeg && dnf clean all + RUN mkdir -p /hot-platform/server/logs \ /hot-platform/server/temp \ /hot-platform/skywalking/agent WORKDIR /hot-platform/server -ENV SERVER_PORT=19090 SNAIL_PORT=28080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS="" TZ=Asia/Shanghai +ENV SERVER_PORT=19090 SNAIL_PORT=28080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS="" TZ=Asia/Shanghai VIDEO_UPLOAD_FFMPEG_COMMAND=ffmpeg EXPOSE ${SERVER_PORT} # 暴露 snail job 客户端端口 用于定时任务调度中心通信 diff --git a/src/main/java/com/hotwj/platform/video/config/VideoUploadProperties.java b/src/main/java/com/hotwj/platform/video/config/VideoUploadProperties.java index c5ce6d8..86dab9b 100644 --- a/src/main/java/com/hotwj/platform/video/config/VideoUploadProperties.java +++ b/src/main/java/com/hotwj/platform/video/config/VideoUploadProperties.java @@ -21,15 +21,10 @@ public class VideoUploadProperties { private String workDir = System.getProperty("java.io.tmpdir") + File.separator + "hot-video-upload"; /** - * 对外访问的 HLS 路径前缀。 + * HLS 文件在 OSS 中的对象前缀。 */ private String publicUrlPrefix = "/video-hls"; - /** - * 对外访问的基础域名,可为空。 - */ - private String publicBaseUrl; - /** * FFmpeg 可执行命令。 */ diff --git a/src/main/java/com/hotwj/platform/video/service/impl/VideoChunkUploadServiceImpl.java b/src/main/java/com/hotwj/platform/video/service/impl/VideoChunkUploadServiceImpl.java index 5a692cb..8ff6fe9 100644 --- a/src/main/java/com/hotwj/platform/video/service/impl/VideoChunkUploadServiceImpl.java +++ b/src/main/java/com/hotwj/platform/video/service/impl/VideoChunkUploadServiceImpl.java @@ -13,6 +13,8 @@ import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.redis.utils.RedisUtils; +import org.dromara.system.domain.vo.SysOssVo; +import org.dromara.system.service.ISysOssService; import org.redisson.api.RSet; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @@ -25,6 +27,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.time.LocalDateTime; import java.util.*; +import java.util.concurrent.TimeUnit; /** * 视频分片上传、合并与异步转码服务 @@ -36,8 +39,10 @@ public class VideoChunkUploadServiceImpl implements VideoChunkUploadService { private static final String CHUNK_KEY_PREFIX = "video:chunk:"; private static final String TASK_KEY_PREFIX = "video:task:"; + private static final String RESULT_KEY_PREFIX = "video:result:"; private final VideoUploadProperties properties; + private final ISysOssService ossService; @Override public List checkUploadedChunks(String fileHash) { @@ -67,6 +72,13 @@ public class VideoChunkUploadServiceImpl implements VideoChunkUploadService { @Override public Map mergeAndCreateTask(VideoMergeBo bo) { validateMergeRequest(bo); + validateFfmpegAvailability(); + + String cachedM3u8Url = getResolvedM3u8Url(bo.getFileHash()); + if (StringUtils.isNotBlank(cachedM3u8Url)) { + return Collections.singletonMap("taskId", saveCompletedTask(bo.getFileHash(), bo.getFileName(), cachedM3u8Url)); + } + RSet uploadedSet = RedisUtils.getClient().getSet(CHUNK_KEY_PREFIX + bo.getFileHash()); Set uploaded = uploadedSet.readAll(); for (int i = 0; i < bo.getTotalChunks(); i++) { @@ -99,6 +111,13 @@ public class VideoChunkUploadServiceImpl implements VideoChunkUploadService { if (taskInfo == null) { throw new ServiceException("任务不存在或已过期"); } + if ("completed".equals(taskInfo.getStatus()) && StringUtils.isNotBlank(taskInfo.getFileHash())) { + String resolvedM3u8Url = getResolvedM3u8Url(taskInfo.getFileHash()); + if (StringUtils.isNotBlank(resolvedM3u8Url) && !StringUtils.equals(taskInfo.getM3u8Url(), resolvedM3u8Url)) { + taskInfo.setM3u8Url(resolvedM3u8Url).setUpdateTime(LocalDateTime.now()); + saveTask(taskInfo); + } + } return taskInfo; } @@ -115,14 +134,18 @@ public class VideoChunkUploadServiceImpl implements VideoChunkUploadService { Path playlistPath = outputDir.resolve("index.m3u8"); runFfmpeg(sourceFile, outputDir, playlistPath); + String m3u8Url = uploadHlsDirectoryToOss(fileHash, outputDir); taskInfo.setStatus("completed") - .setM3u8Url(buildPublicM3u8Url(fileHash)) + .setM3u8Url(m3u8Url) .setUpdateTime(LocalDateTime.now()) .setErrorMessage(null); saveTask(taskInfo); + RedisUtils.setCacheObject(RESULT_KEY_PREFIX + fileHash, m3u8Url, properties.taskExpireDuration()); FileUtil.del(getChunkDir(fileHash)); + FileUtil.del(getSourceDir(fileHash)); + FileUtil.del(outputDir.toFile()); log.info("视频转码完成: taskId={}, fileHash={}, playlist={}", taskId, fileHash, playlistPath); } catch (Exception e) { log.error("视频转码失败: taskId={}, fileHash={}, msg={}", taskId, fileHash, e.getMessage(), e); @@ -151,6 +174,33 @@ public class VideoChunkUploadServiceImpl implements VideoChunkUploadService { } } + private void validateFfmpegAvailability() { + String ffmpegCommand = StringUtils.defaultIfBlank(properties.getFfmpegCommand(), "ffmpeg").trim(); + Process process = null; + try { + process = new ProcessBuilder(ffmpegCommand, "-version") + .redirectErrorStream(true) + .start(); + boolean finished = process.waitFor(5, TimeUnit.SECONDS); + if (!finished) { + process.destroyForcibly(); + throw new ServiceException("FFmpeg 校验超时,请检查命令配置或容器环境"); + } + String output; + try (InputStream inputStream = process.getInputStream()) { + output = new String(inputStream.readAllBytes()); + } + if (process.exitValue() != 0) { + throw new ServiceException("FFmpeg 不可用: " + abbreviate(output, 300)); + } + } catch (IOException e) { + throw new ServiceException("FFmpeg 不可用,请检查命令配置: " + ffmpegCommand); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ServiceException("FFmpeg 校验被中断"); + } + } + private void mergeChunksToFile(String fileHash, int totalChunks, Path targetFile) { try { Files.deleteIfExists(targetFile); @@ -167,6 +217,7 @@ public class VideoChunkUploadServiceImpl implements VideoChunkUploadService { } } } catch (IOException e) { + e.printStackTrace(); throw new ServiceException("合并分片失败"); } } @@ -210,16 +261,96 @@ public class VideoChunkUploadServiceImpl implements VideoChunkUploadService { RedisUtils.setCacheObject(TASK_KEY_PREFIX + taskInfo.getTaskId(), taskInfo, properties.taskExpireDuration()); } - private String buildPublicM3u8Url(String fileHash) { - String relativePath = properties.normalizedPublicUrlPrefix() + "/" + fileHash + "/index.m3u8"; - if (StringUtils.isBlank(properties.getPublicBaseUrl())) { - return relativePath; + private String getResolvedM3u8Url(String fileHash) { + String cachedM3u8Url = RedisUtils.getCacheObject(RESULT_KEY_PREFIX + fileHash); + if (isOssUrl(cachedM3u8Url)) { + return cachedM3u8Url; } - String baseUrl = properties.getPublicBaseUrl().trim(); - if (baseUrl.endsWith("/")) { - baseUrl = baseUrl.substring(0, baseUrl.length() - 1); + + Path outputDir = getHlsDir(fileHash).toPath(); + Path playlistPath = outputDir.resolve("index.m3u8"); + if (Files.exists(playlistPath)) { + try { + String uploadedM3u8Url = uploadHlsDirectoryToOss(fileHash, outputDir); + RedisUtils.setCacheObject(RESULT_KEY_PREFIX + fileHash, uploadedM3u8Url, properties.taskExpireDuration()); + return uploadedM3u8Url; + } catch (IOException e) { + throw new ServiceException("上传 HLS 到 OSS 失败"); + } } - return baseUrl + relativePath; + return null; + } + + private String saveCompletedTask(String fileHash, String fileName, String m3u8Url) { + String taskId = "task_" + IdUtil.fastSimpleUUID(); + VideoTranscodeTaskInfo taskInfo = new VideoTranscodeTaskInfo() + .setTaskId(taskId) + .setFileHash(fileHash) + .setFileName(fileName) + .setStatus("completed") + .setM3u8Url(m3u8Url) + .setCreateTime(LocalDateTime.now()) + .setUpdateTime(LocalDateTime.now()); + saveTask(taskInfo); + return taskId; + } + + private String uploadHlsDirectoryToOss(String fileHash, Path outputDir) throws IOException { + if (!Files.exists(outputDir)) { + throw new ServiceException("HLS 输出目录不存在"); + } + List files; + try (var stream = Files.list(outputDir)) { + files = stream + .filter(Files::isRegularFile) + .sorted(Comparator.comparing(path -> path.getFileName().toString())) + .toList(); + } + if (files.isEmpty()) { + throw new ServiceException("未找到待上传的 HLS 文件"); + } + + Path playlistPath = outputDir.resolve("index.m3u8"); + if (!Files.exists(playlistPath)) { + throw new ServiceException("未生成 index.m3u8"); + } + + for (Path path : files) { + if (path.equals(playlistPath)) { + continue; + } + ossService.upload(path.toFile(), buildOssObjectKey(fileHash, path.getFileName().toString()), resolveContentType(path.getFileName().toString())); + } + + SysOssVo playlistOss = ossService.upload( + playlistPath.toFile(), + buildOssObjectKey(fileHash, playlistPath.getFileName().toString()), + resolveContentType(playlistPath.getFileName().toString()) + ); + return playlistOss.getUrl(); + } + + private boolean isOssUrl(String url) { + return StringUtils.isNotBlank(url) && (url.startsWith("http://") || url.startsWith("https://")); + } + + private String buildOssObjectKey(String fileHash, String fileName) { + String prefix = properties.normalizedPublicUrlPrefix(); + if (prefix.startsWith("/")) { + prefix = prefix.substring(1); + } + return prefix + "/" + fileHash + "/" + fileName; + } + + private String resolveContentType(String fileName) { + String lowerFileName = fileName == null ? "" : fileName.toLowerCase(Locale.ROOT); + if (lowerFileName.endsWith(".m3u8")) { + return "application/vnd.apple.mpegurl"; + } + if (lowerFileName.endsWith(".ts")) { + return "video/mp2t"; + } + return "application/octet-stream"; } private void cleanDirectory(Path directory) throws IOException { diff --git a/src/main/java/org/dromara/system/controller/system/SysOssController.java b/src/main/java/org/dromara/system/controller/system/SysOssController.java index f021777..d45fda9 100644 --- a/src/main/java/org/dromara/system/controller/system/SysOssController.java +++ b/src/main/java/org/dromara/system/controller/system/SysOssController.java @@ -47,6 +47,7 @@ public class SysOssController extends BaseController { */ @SaCheckPermission("system:oss:upload") @GetMapping("/checkChunk") + @Deprecated public R checkChunk(String identifier) { return R.ok(ossService.checkChunk(identifier)); } @@ -56,6 +57,7 @@ public class SysOssController extends BaseController { */ @SaCheckPermission("system:oss:upload") @PostMapping(value = "/uploadChunk", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @Deprecated public R uploadChunk(@Validated SysOssChunkBo bo) { ossService.uploadChunk(bo); return R.ok(); @@ -66,6 +68,7 @@ public class SysOssController extends BaseController { */ @SaCheckPermission("system:oss:upload") @PostMapping("/mergeChunk") + @Deprecated public R mergeChunk(@Validated SysOssMergeBo bo) { return R.ok(ossService.mergeChunk(bo)); } diff --git a/src/main/java/org/dromara/system/service/ISysOssService.java b/src/main/java/org/dromara/system/service/ISysOssService.java index f835d1d..2d80143 100644 --- a/src/main/java/org/dromara/system/service/ISysOssService.java +++ b/src/main/java/org/dromara/system/service/ISysOssService.java @@ -86,6 +86,16 @@ public interface ISysOssService { */ SysOssVo upload(File file); + /** + * 按指定对象键上传文件到对象存储服务,并保存文件信息到数据库 + * + * @param file 文件 + * @param objectKey OSS 对象键 + * @param contentType 内容类型 + * @return 上传成功后的 SysOssVo 对象 + */ + SysOssVo upload(File file, String objectKey, String contentType); + /** * 文件下载方法,支持一次性下载完整文件 * diff --git a/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java b/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java index a20e49a..84acb3f 100644 --- a/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java +++ b/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java @@ -353,6 +353,36 @@ public class SysOssServiceImpl implements ISysOssService, OssService { return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult, ext1); } + @Override + public SysOssVo upload(File file, String objectKey, String contentType) { + String originalfileName = file.getName(); + String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length()); + OssClient storage = OssFactory.instance(); + + SysOssExt ext1 = new SysOssExt(); + ext1.setFileSize(file.length()); + ext1.setContentType(contentType); + + Long duration = null; + if (isMediaFile(suffix)) { + if (file.length() > MEDIA_DURATION_PARSE_MAX_SIZE) { + log.info("文件较大,跳过媒体时长解析: name={}, size={} bytes", originalfileName, file.length()); + } else { + try { + duration = getMediaDuration(file); + } catch (Exception e) { + log.warn("解析媒体时长失败: {}", e.getMessage()); + } + } + } + + String resolvedContentType = StringUtils.isNotBlank(contentType) ? contentType : FileUtils.getMimeType(suffix); + UploadResult uploadResult = storage.upload(file.toPath(), objectKey, null, resolvedContentType); + + ext1.setDuration(duration); + return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult, ext1); + } + private boolean isMediaFile(String suffix) { if (StringUtils.isBlank(suffix)) return false; String s = suffix.toLowerCase().replace(".", ""); From b0f0121243349cc291323aa81dc0fd7603c3fb53 Mon Sep 17 00:00:00 2001 From: shihongwei <178899525@qq.com> Date: Wed, 27 May 2026 17:54:52 +0800 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20=E6=9C=88=E5=BA=A6=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=80=BB=E4=BA=BA=E6=95=B0=E4=B8=8D=E5=AF=B9?= =?UTF-8?q?=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../provider/security/TrainingPlanMonthlyStatProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/hotwj/platform/reportStatistics/provider/security/TrainingPlanMonthlyStatProvider.java b/src/main/java/com/hotwj/platform/reportStatistics/provider/security/TrainingPlanMonthlyStatProvider.java index 6fe6bcf..4703a76 100644 --- a/src/main/java/com/hotwj/platform/reportStatistics/provider/security/TrainingPlanMonthlyStatProvider.java +++ b/src/main/java/com/hotwj/platform/reportStatistics/provider/security/TrainingPlanMonthlyStatProvider.java @@ -42,7 +42,7 @@ public class TrainingPlanMonthlyStatProvider implements IReportDataProvider { private final IHotTrainingCourseConfigService hotTrainingCourseConfigService; private final IHotCourseResourceService hotCourseResourceService; private final HotMediaResourceMapper hotMediaResourceMapper; - + @Override public String getTemplateCode() { return "templates/securityManage/onlineEducationMonthlyStatistics.html.vm"; @@ -78,12 +78,12 @@ public class TrainingPlanMonthlyStatProvider implements IReportDataProvider { ctx.put("trainingCourses", buildTrainingCourses(trainingId)); ctx.put("trainingContent", buildTrainingContent(trainingId)); + ctx.put("completedCount", training.getCompleteCount() == null ? 0L : training.getCompleteCount()); List voRows = ledgerReportMapper.selectOnlineTrainingMonthlyRows(companyId, trainingId, normalizedUserIds); if (voRows == null || voRows.isEmpty()) { return ctx; } - ctx.put("completedCount", countCompleted(voRows)); ctx.put("rows", buildRows(voRows, progressMap)); return ctx; } From 9346f2d04dcfb279a1479a95fc1e5a1cbb123922 Mon Sep 17 00:00:00 2001 From: shihongwei <178899525@qq.com> Date: Wed, 27 May 2026 18:43:41 +0800 Subject: [PATCH 6/6] =?UTF-8?q?fix:=20=E6=96=87=E4=BB=B6=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/VideoChunkUploadServiceImpl.java | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/hotwj/platform/video/service/impl/VideoChunkUploadServiceImpl.java b/src/main/java/com/hotwj/platform/video/service/impl/VideoChunkUploadServiceImpl.java index 8ff6fe9..dfdcee4 100644 --- a/src/main/java/com/hotwj/platform/video/service/impl/VideoChunkUploadServiceImpl.java +++ b/src/main/java/com/hotwj/platform/video/service/impl/VideoChunkUploadServiceImpl.java @@ -46,11 +46,7 @@ public class VideoChunkUploadServiceImpl implements VideoChunkUploadService { @Override public List checkUploadedChunks(String fileHash) { - RSet rSet = RedisUtils.getClient().getSet(CHUNK_KEY_PREFIX + fileHash); - Set uploaded = rSet.readAll(); - List result = new ArrayList<>(uploaded); - result.sort(Integer::compareTo); - return result; + return getUploadedChunkIndexes(fileHash); } @Override @@ -79,8 +75,8 @@ public class VideoChunkUploadServiceImpl implements VideoChunkUploadService { return Collections.singletonMap("taskId", saveCompletedTask(bo.getFileHash(), bo.getFileName(), cachedM3u8Url)); } - RSet uploadedSet = RedisUtils.getClient().getSet(CHUNK_KEY_PREFIX + bo.getFileHash()); - Set uploaded = uploadedSet.readAll(); + List uploadedChunkIndexes = getUploadedChunkIndexes(bo.getFileHash()); + Set uploaded = new HashSet<>(uploadedChunkIndexes); for (int i = 0; i < bo.getTotalChunks(); i++) { if (!uploaded.contains(i)) { throw new ServiceException("存在未上传完成的分片,无法合并"); @@ -261,6 +257,30 @@ public class VideoChunkUploadServiceImpl implements VideoChunkUploadService { RedisUtils.setCacheObject(TASK_KEY_PREFIX + taskInfo.getTaskId(), taskInfo, properties.taskExpireDuration()); } + private List getUploadedChunkIndexes(String fileHash) { + File chunkDir = getChunkDir(fileHash); + if (!chunkDir.exists() || !chunkDir.isDirectory()) { + return Collections.emptyList(); + } + File[] chunkFiles = chunkDir.listFiles(); + if (chunkFiles == null || chunkFiles.length == 0) { + return Collections.emptyList(); + } + List result = new ArrayList<>(); + for (File chunkFile : chunkFiles) { + if (chunkFile == null || !chunkFile.isFile()) { + continue; + } + String fileName = chunkFile.getName(); + if (!fileName.matches("\\d+")) { + continue; + } + result.add(Integer.parseInt(fileName)); + } + result.sort(Integer::compareTo); + return result; + } + private String getResolvedM3u8Url(String fileHash) { String cachedM3u8Url = RedisUtils.getCacheObject(RESULT_KEY_PREFIX + fileHash); if (isOssUrl(cachedM3u8Url)) {