|
@@ -16,10 +16,7 @@ import com.fs.course.domain.FsUserCourseVideo;
|
|
|
import com.fs.course.mapper.FsUserCoursePeriodDaysMapper;
|
|
import com.fs.course.mapper.FsUserCoursePeriodDaysMapper;
|
|
|
import com.fs.course.mapper.FsUserCoursePeriodMapper;
|
|
import com.fs.course.mapper.FsUserCoursePeriodMapper;
|
|
|
import com.fs.course.mapper.FsUserCourseVideoRedPackageMapper;
|
|
import com.fs.course.mapper.FsUserCourseVideoRedPackageMapper;
|
|
|
-import com.fs.course.param.CompanyRedPacketParam;
|
|
|
|
|
-import com.fs.course.param.CourseAnalysisNoPageParam;
|
|
|
|
|
-import com.fs.course.param.CourseAnalysisParam;
|
|
|
|
|
-import com.fs.course.param.PeriodCountParam;
|
|
|
|
|
|
|
+import com.fs.course.param.*;
|
|
|
import com.fs.course.service.IFsUserCoursePeriodDaysService;
|
|
import com.fs.course.service.IFsUserCoursePeriodDaysService;
|
|
|
import com.fs.course.service.IFsUserCourseVideoService;
|
|
import com.fs.course.service.IFsUserCourseVideoService;
|
|
|
import com.fs.course.vo.FsPeriodCountVO;
|
|
import com.fs.course.vo.FsPeriodCountVO;
|
|
@@ -211,6 +208,223 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
|
|
|
return R.ok();
|
|
return R.ok();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ @Override
|
|
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
|
|
+ public R batchAddCourseSections(BatchAddCourseSectionParam param) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 2. 获取营期信息
|
|
|
|
|
+ FsUserCoursePeriod period = fsUserCoursePeriodMapper.selectFsUserCoursePeriodById(param.getPeriodId());
|
|
|
|
|
+ if (period == null) {
|
|
|
|
|
+ return R.error("营期不存在");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 获取已存在的课程小节
|
|
|
|
|
+ List<FsUserCoursePeriodDays> existingDays = list(new QueryWrapper<FsUserCoursePeriodDays>()
|
|
|
|
|
+ .eq("period_id", param.getPeriodId())
|
|
|
|
|
+ .eq("del_flag", "0")
|
|
|
|
|
+ .orderByAsc("lesson"));
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 计算营期总天数
|
|
|
|
|
+ long totalDays;
|
|
|
|
|
+ if (period.getPeriodType() == 2) {
|
|
|
|
|
+ totalDays = 1;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ totalDays = DateUtil.differenceTime(period.getPeriodStartingTime(), period.getPeriodEndTime(), TimeTypeEnum.DAY);
|
|
|
|
|
+ }
|
|
|
|
|
+ totalDays++;
|
|
|
|
|
+
|
|
|
|
|
+ // 5. 验证是否超过营期范围(开启自由模式时跳过此验证)
|
|
|
|
|
+ if (period.getFreeMode() == null || period.getFreeMode() != 1) {
|
|
|
|
|
+ if (existingDays.size() + param.getVideoIds().size() > totalDays) {
|
|
|
|
|
+ return R.error("添加的课程数量超过营期范围,营期总天数:" + totalDays + ",当前已有:" + existingDays.size() + ",尝试添加:" + param.getVideoIds().size());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 6. 检查重复视频
|
|
|
|
|
+ Set<Long> existingVideoIds = existingDays.stream()
|
|
|
|
|
+ .map(FsUserCoursePeriodDays::getVideoId)
|
|
|
|
|
+ .collect(Collectors.toSet());
|
|
|
|
|
+
|
|
|
|
|
+ List<Long> duplicateVideoIds = param.getVideoIds().stream()
|
|
|
|
|
+ .filter(existingVideoIds::contains)
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+
|
|
|
|
|
+ if (!duplicateVideoIds.isEmpty()) {
|
|
|
|
|
+ if (param.getSkipDuplicate() != null && param.getSkipDuplicate()) {
|
|
|
|
|
+ // 跳过重复的视频
|
|
|
|
|
+ param.getVideoIds().removeAll(duplicateVideoIds);
|
|
|
|
|
+ log.info("跳过重复的视频ID:{}", duplicateVideoIds);
|
|
|
|
|
+ if (param.getVideoIds().isEmpty()) {
|
|
|
|
|
+ return R.ok("所有视频都已存在,跳过添加");
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return R.error("不能添加相同章节,重复的视频ID:" + duplicateVideoIds);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 7. 获取视频详情并按courseSort排序
|
|
|
|
|
+ FsUserCourseVideo queryVideo = new FsUserCourseVideo();
|
|
|
|
|
+ queryVideo.setCourseId(param.getCourseId());
|
|
|
|
|
+ List<FsUserCourseVideo> videoList = fsUserCourseVideoService.selectFsUserCourseVideoListByCourseId(queryVideo);
|
|
|
|
|
+ Map<Long, FsUserCourseVideo> videoMap = PubFun.listToMapByGroupObject(videoList, FsUserCourseVideo::getVideoId);
|
|
|
|
|
+
|
|
|
|
|
+ // 8. 验证视频是否都存在
|
|
|
|
|
+ for (Long videoId : param.getVideoIds()) {
|
|
|
|
|
+ if (!videoMap.containsKey(videoId)) {
|
|
|
|
|
+ return R.error("视频ID:" + videoId + " 不存在于课程中");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 9. 根据courseSort对要添加的视频进行排序(从小到大)
|
|
|
|
|
+ List<FsUserCourseVideo> sortedVideos = param.getVideoIds().stream()
|
|
|
|
|
+ .map(videoMap::get)
|
|
|
|
|
+ .sorted((v1, v2) -> {
|
|
|
|
|
+ Long sort1 = v1.getCourseSort() != null ? v1.getCourseSort() : Long.MAX_VALUE;
|
|
|
|
|
+ Long sort2 = v2.getCourseSort() != null ? v2.getCourseSort() : Long.MAX_VALUE;
|
|
|
|
|
+ return sort1.compareTo(sort2);
|
|
|
|
|
+ })
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+
|
|
|
|
|
+ log.info("按courseSort排序后的视频顺序:");
|
|
|
|
|
+ for (int i = 0; i < sortedVideos.size(); i++) {
|
|
|
|
|
+ FsUserCourseVideo video = sortedVideos.get(i);
|
|
|
|
|
+ log.info(" 序号: {}, 视频ID: {}, 标题: {}, courseSort: {}",
|
|
|
|
|
+ i + 1, video.getVideoId(), video.getTitle(), video.getCourseSort());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 10. 准备添加的小节数据,使用按courseSort排序后的视频列表
|
|
|
|
|
+ List<FsUserCoursePeriodDays> newDays = new ArrayList<>();
|
|
|
|
|
+
|
|
|
|
|
+ // 确定开始课次 - 从现有最大课次+1开始,确保连续性
|
|
|
|
|
+ int startLesson;
|
|
|
|
|
+ LocalDate startDate; // 新增:确定开始日期
|
|
|
|
|
+
|
|
|
|
|
+ if (param.getStartLesson() != null) {
|
|
|
|
|
+ startLesson = param.getStartLesson();
|
|
|
|
|
+ // 如果指定了起始课次,仍然基于营期开始时间计算日期
|
|
|
|
|
+ startDate = period.getPeriodStartingTime().plusDays(startLesson - 1);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 获取现有最大课次,如果没有则从1开始
|
|
|
|
|
+ startLesson = existingDays.stream()
|
|
|
|
|
+ .mapToInt(FsUserCoursePeriodDays::getLesson)
|
|
|
|
|
+ .max()
|
|
|
|
|
+ .orElse(0) + 1;
|
|
|
|
|
+
|
|
|
|
|
+ // 关键修复:获取已存在课程的最后日期,从该日期的下一天开始
|
|
|
|
|
+ if (!existingDays.isEmpty()) {
|
|
|
|
|
+ // 按日期排序,获取最后一个课程的日期
|
|
|
|
|
+ LocalDate lastDate = existingDays.stream()
|
|
|
|
|
+ .map(FsUserCoursePeriodDays::getDayDate)
|
|
|
|
|
+ .filter(date -> date != null)
|
|
|
|
|
+ .max(LocalDate::compareTo)
|
|
|
|
|
+ .orElse(period.getPeriodStartingTime().plusDays(startLesson - 2));
|
|
|
|
|
+
|
|
|
|
|
+ startDate = lastDate.plusDays(1); // 从最后日期的下一天开始
|
|
|
|
|
+ log.info("基于已存在课程的最后日期计算起始日期:最后日期={}, 新起始日期={}", lastDate, startDate);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 如果没有已存在的课程,从营期开始时间计算
|
|
|
|
|
+ startDate = period.getPeriodStartingTime();
|
|
|
|
|
+ log.info("营期暂无课程,从营期开始时间计算起始日期:{}", startDate);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 使用排序后的视频列表进行处理,确保按courseSort顺序添加
|
|
|
|
|
+ for (int i = 0; i < sortedVideos.size(); i++) {
|
|
|
|
|
+ FsUserCourseVideo video = sortedVideos.get(i);
|
|
|
|
|
+ Long videoId = video.getVideoId();
|
|
|
|
|
+
|
|
|
|
|
+ FsUserCoursePeriodDays newDay = new FsUserCoursePeriodDays();
|
|
|
|
|
+ newDay.setPeriodId(param.getPeriodId());
|
|
|
|
|
+ newDay.setCourseId(param.getCourseId());
|
|
|
|
|
+ newDay.setVideoId(videoId);
|
|
|
|
|
+
|
|
|
|
|
+ // 设置课次 - 严格按排序后的顺序递增,确保有序
|
|
|
|
|
+ newDay.setLesson(startLesson + i);
|
|
|
|
|
+
|
|
|
|
|
+ // 计算日期 - 修复:基于起始日期递增,而不是基于课次计算
|
|
|
|
|
+ // 这样可以避免当已存在课程的日期与课次不对应时出现重复日期
|
|
|
|
|
+ LocalDate courseDate = startDate.plusDays(i);
|
|
|
|
|
+ newDay.setDayDate(courseDate);
|
|
|
|
|
+
|
|
|
|
|
+ // 设置时间 - 优先使用参数中的时间,其次使用视频默认时间
|
|
|
|
|
+ if (param.getStartTime() != null) {
|
|
|
|
|
+ newDay.setStartDateTime(LocalDateTime.of(courseDate, param.getStartTime()));
|
|
|
|
|
+ } else if (video.getViewStartTime() != null) {
|
|
|
|
|
+ newDay.setStartDateTime(LocalDateTime.of(courseDate, video.getViewStartTime()));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (param.getEndDateTime() != null) {
|
|
|
|
|
+ newDay.setEndDateTime(LocalDateTime.of(courseDate, param.getEndDateTime()));
|
|
|
|
|
+ } else if (video.getViewEndTime() != null) {
|
|
|
|
|
+ newDay.setEndDateTime(LocalDateTime.of(courseDate, video.getViewEndTime()));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 设置加入时间
|
|
|
|
|
+ SysConfig config = sysConfigMapper.selectConfigByConfigKey("joinTime.switch.config");
|
|
|
|
|
+ if (ObjectUtils.isNotEmpty(config) && config.getConfigValue().equals("1")) {
|
|
|
|
|
+ // 如果开启了配置,使用结束时间作为加入时间
|
|
|
|
|
+ newDay.setLastJoinTime(newDay.getEndDateTime());
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (param.getJoinTime() != null) {
|
|
|
|
|
+ newDay.setLastJoinTime(LocalDateTime.of(courseDate, param.getJoinTime()));
|
|
|
|
|
+ } else if (video.getLastJoinTime() != null) {
|
|
|
|
|
+ newDay.setLastJoinTime(LocalDateTime.of(courseDate, video.getLastJoinTime()));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 设置状态:如果开启自由模式,默认为进行中(status=1)
|
|
|
|
|
+ if (period.getFreeMode() != null && period.getFreeMode() == 1) {
|
|
|
|
|
+ // 自由模式:直接设置为进行中
|
|
|
|
|
+ newDay.setStatus(1);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 固定模式:默认开启今天及以后的两天
|
|
|
|
|
+ LocalDate compareDay = LocalDate.now().plusDays(1);
|
|
|
|
|
+ if (courseDate.isBefore(compareDay)) {
|
|
|
|
|
+ newDay.setStatus(1); // 已开始
|
|
|
|
|
+ } else {
|
|
|
|
|
+ newDay.setStatus(0); // 未开始
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ newDay.setCreateTime(new Date());
|
|
|
|
|
+ newDays.add(newDay);
|
|
|
|
|
+
|
|
|
|
|
+ log.debug("准备添加课程:课次={}, 日期={}, 视频ID={}, 标题={}, courseSort={}",
|
|
|
|
|
+ newDay.getLesson(), newDay.getDayDate(), videoId, video.getTitle(), video.getCourseSort());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 11. 批量保存,确保按courseSort排序的结果保存
|
|
|
|
|
+ if (!newDays.isEmpty()) {
|
|
|
|
|
+ // 再次按课次排序,确保数据库中的顺序正确(按照批量操作排序保障规范)
|
|
|
|
|
+ newDays.sort(Comparator.comparing(FsUserCoursePeriodDays::getLesson));
|
|
|
|
|
+
|
|
|
|
|
+ log.info("即将批量添加课程小节(按courseSort排序):");
|
|
|
|
|
+ for (int i = 0; i < newDays.size(); i++) {
|
|
|
|
|
+ FsUserCoursePeriodDays day = newDays.get(i);
|
|
|
|
|
+ FsUserCourseVideo video = videoMap.get(day.getVideoId());
|
|
|
|
|
+ log.info(" 序号: {}, 课次: {}, 日期: {}, 视频ID: {}, 标题: {}, courseSort: {}",
|
|
|
|
|
+ i + 1, day.getLesson(), day.getDayDate(), day.getVideoId(),
|
|
|
|
|
+ video != null ? video.getTitle() : "Unknown",
|
|
|
|
|
+ video != null ? video.getCourseSort() : "null");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ super.saveBatch(newDays);
|
|
|
|
|
+ log.info("批量添加课程小节成功(按courseSort排序),营期ID:{},添加数量:{},课次范围:{}-{}",
|
|
|
|
|
+ param.getPeriodId(), newDays.size(),
|
|
|
|
|
+ newDays.get(0).getLesson(),
|
|
|
|
|
+ newDays.get(newDays.size() - 1).getLesson());
|
|
|
|
|
+ return R.ok("成功按courseSort排序添加 " + newDays.size() + " 个课程小节,课次范围:" +
|
|
|
|
|
+ newDays.get(0).getLesson() + "-" + newDays.get(newDays.size() - 1).getLesson());
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return R.ok("没有需要添加的课程小节");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("批量添加课程小节失败,营期ID:{},错误信息:{}", param.getPeriodId(), e.getMessage(), e);
|
|
|
|
|
+ throw new RuntimeException("批量添加课程小节失败:" + e.getMessage(), e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
@Override
|
|
@Override
|
|
|
public R updateListCourseData(List<FsUserCoursePeriodDays> entity) {
|
|
public R updateListCourseData(List<FsUserCoursePeriodDays> entity) {
|
|
|
List<FsUserCoursePeriodDays> collect = entity.stream().map(e -> {
|
|
List<FsUserCoursePeriodDays> collect = entity.stream().map(e -> {
|