| | |
| | | itemVO.setDifficultyName(trimToEmpty(row.getDifficultyName())); |
| | | itemVO.setSalePrice(defaultPrice(row.getSalePrice())); |
| | | itemVO.setLearnUserCount(defaultNumber(row.getLearnUserCount())); |
| | | itemVO.setStudyingCount(defaultNumber(row.getStudyingCount())); |
| | | result.add(itemVO); |
| | | } |
| | | return result; |
| | |
| | | if (courseDetail.getStatus() == null || courseDetail.getStatus().intValue() != COURSE_STATUS_ENABLED) { |
| | | throw new IllegalArgumentException("养生课程不存在"); |
| | | } |
| | | // 进入课程详情即视为一次学习行为,正在学习人数自增;返回值按自增后的口径展示 |
| | | incrementStudyingCount(courseId); |
| | | |
| | | List<HealthCourseVideo> enabledVideos = listEnabledVideos(courseId); |
| | | AppHealthCourseDetailVO detailVO = new AppHealthCourseDetailVO(); |
| | |
| | | detailVO.setDifficultyName(trimToEmpty(courseDetail.getDifficultyName())); |
| | | detailVO.setSalePrice(defaultPrice(courseDetail.getSalePrice())); |
| | | detailVO.setLearnUserCount(defaultNumber(courseDetail.getLearnUserCount())); |
| | | detailVO.setStudyingCount(Integer.valueOf(defaultNumber(courseDetail.getStudyingCount()) + 1)); |
| | | detailVO.setSummary(trimToEmpty(courseDetail.getSummary())); |
| | | detailVO.setTargetUsers(trimToEmpty(courseDetail.getTargetUsers())); |
| | | detailVO.setContent(trimToEmpty(courseDetail.getContent())); |
| | |
| | | itemVO.setTeacherName(trimToEmpty(row.getTeacherName())); |
| | | itemVO.setTeacherAvatar(trimToEmpty(row.getTeacherAvatar())); |
| | | itemVO.setLearnUserCount(defaultNumber(row.getLearnUserCount())); |
| | | itemVO.setStudyingCount(defaultNumber(row.getStudyingCount())); |
| | | itemVO.setFavoritedAt(row.getFavoritedAt()); |
| | | result.add(itemVO); |
| | | } |
| | |
| | | HealthCourse course = requireEnabledCourse(courseId); |
| | | List<HealthCourseVideo> enabledVideos = listEnabledVideos(courseId); |
| | | Map<Long, VideoProgressSnapshot> videoProgressSnapshotMap = buildVideoProgressSnapshotMap(userId, Collections.singletonList(courseId)); |
| | | PurchaseStateSnapshot purchaseStateSnapshot = resolvePurchaseState(courseId); |
| | | |
| | | List<AppMyHealthCourseVideoItemVO> videos = new ArrayList<AppMyHealthCourseVideoItemVO>(enabledVideos.size()); |
| | | Long currentVideoId = null; |
| | | LocalDateTime latestLearnAt = null; |
| | | for (HealthCourseVideo video : enabledVideos) { |
| | | VideoProgressSnapshot snapshot = videoProgressSnapshotMap.get(video.getId()); |
| | | boolean freeAccess = isFreeVideo(video.getSalePrice()); |
| | | boolean purchased = purchaseStateSnapshot.isCourseOwned() |
| | | || purchaseStateSnapshot.getPurchasedVideoIds().contains(video.getId()); |
| | | boolean locked = !freeAccess && !purchased; |
| | | AppMyHealthCourseVideoItemVO itemVO = new AppMyHealthCourseVideoItemVO(); |
| | | itemVO.setVideoId(video.getId()); |
| | | itemVO.setChapterName(trimToEmpty(video.getChapterName())); |
| | | itemVO.setDurationSeconds(defaultNumber(video.getDurationSeconds())); |
| | | itemVO.setDurationText(formatVideoDuration(itemVO.getDurationSeconds())); |
| | | itemVO.setFreeAccess(Boolean.valueOf(freeAccess)); |
| | | itemVO.setPurchased(Boolean.valueOf(purchased)); |
| | | itemVO.setLocked(Boolean.valueOf(locked)); |
| | | itemVO.setVideoUrl(trimToEmpty(video.getVideoUrl())); |
| | | itemVO.setCompleted(Boolean.valueOf(snapshot != null && snapshot.isCompleted())); |
| | | itemVO.setProgressPercent(Integer.valueOf(calculateVideoDisplayProgressPercent( |
| | |
| | | itemVO.setLastPlayPositionSeconds(Integer.valueOf(resolveVideoResumePosition(snapshot))); |
| | | itemVO.setDownloadable(Boolean.valueOf(hasVideoDownloadAccess(userId, courseId, video.getId()))); |
| | | videos.add(itemVO); |
| | | if (snapshot != null && snapshot.getLatestLearnAt() != null |
| | | // 默认播放章节只允许落在当前用户可观看的小节上,未购小节保留展示但不自动选中。 |
| | | if (!locked && snapshot != null && snapshot.getLatestLearnAt() != null |
| | | && (latestLearnAt == null || snapshot.getLatestLearnAt().isAfter(latestLearnAt))) { |
| | | latestLearnAt = snapshot.getLatestLearnAt(); |
| | | currentVideoId = video.getId(); |
| | | } |
| | | } |
| | | if (currentVideoId == null && !enabledVideos.isEmpty()) { |
| | | currentVideoId = enabledVideos.get(0).getId(); |
| | | if (currentVideoId == null) { |
| | | for (AppMyHealthCourseVideoItemVO itemVO : videos) { |
| | | if (!Boolean.TRUE.equals(itemVO.getLocked())) { |
| | | currentVideoId = itemVO.getVideoId(); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | |
| | | AppMyHealthCourseDetailVO detailVO = new AppMyHealthCourseDetailVO(); |
| | |
| | | AppUser user = requireEnabledUser(userId); |
| | | HealthCourse course = requireEnabledCourse(courseId); |
| | | String paymentMethod = normalizePaymentMethod(request == null ? null : request.getPaymentMethod()); |
| | | if (course.getSalePrice() == null || course.getSalePrice().compareTo(BigDecimal.ZERO) <= 0) { |
| | | throw new IllegalArgumentException("当前课程无需购买"); |
| | | } |
| | | CoursePurchaseOrder existingOrder = findLatestActivePaidCourseOrder(userId, courseId); |
| | | if (existingOrder != null) { |
| | | return buildPurchaseResult(existingOrder, true, resolvePurchasedVideoIds(userId, courseId, true)); |
| | |
| | | videoVO.setLocked(Boolean.valueOf(locked)); |
| | | // 已购买章节需要直接拿到视频地址,前端才能切换为下载入口。 |
| | | videoVO.setVideoUrl(trimToEmpty(video.getVideoUrl())); |
| | | videoVO.setCreatedAt(video.getCreatedAt()); |
| | | videoVO.setUpdatedAt(video.getUpdatedAt()); |
| | | result.add(videoVO); |
| | | } |
| | | return result; |
| | |
| | | } |
| | | |
| | | /** |
| | | * 正在学习人数自增 1,进入课程详情时调用。 |
| | | */ |
| | | private void incrementStudyingCount(Long courseId) { |
| | | LambdaUpdateWrapper<HealthCourse> updateWrapper = new LambdaUpdateWrapper<HealthCourse>() |
| | | .eq(HealthCourse::getId, courseId) |
| | | .setSql("studying_count = COALESCE(studying_count, 0) + 1"); |
| | | healthCourseMapper.update(null, updateWrapper); |
| | | } |
| | | |
| | | /** |
| | | * 构造收藏操作返回结果,避免控制器层重复拼装。 |
| | | */ |
| | | private AppHealthCourseFavoriteResultVO buildFavoriteResult(Long courseId, boolean favorited, Integer favoriteCount) { |