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)) {