“mitao”
2026-04-24 b620af46945757c8c578a9f8d3ef472e40544d70
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/live/service/AppLiveCourseService.java
@@ -34,6 +34,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
@@ -138,7 +139,7 @@
        detailVO.setDisplayStatusText(resolveDisplayStatusText(snapshot));
        detailVO.setCountdownTime(resolveCountdownTime(snapshot));
        detailVO.setActionSessionId(actionSession == null ? null : actionSession.getId());
        detailVO.setVideoUrl(actionSession == null ? null : appLiveCourseDisplaySupport.trimToNull(actionSession.getVideoUrl()));
        detailVO.setVideoUrl(resolvePlayableVideoUrl(snapshot, actionSession));
        detailVO.setPlaybackStartTime(playbackStartTime);
        detailVO.setDurationSeconds(actionSession == null ? null : actionSession.getDurationSeconds());
        detailVO.setReserveCount(resolveReserveCount(courseId, sessions));
@@ -206,6 +207,7 @@
        Map<Long, String> normalizedAnswers = validateAndNormalizeAnswers(request.getAnswers(), questions);
        WatchProgressSummary watchSummary = resolveWatchProgress(sessionId, userId, session);
        LocalDateTime answeredAt = LocalDateTime.now();
        boolean allCorrect = true;
        List<LiveSessionQuizRecord> insertedRecords = new ArrayList<LiveSessionQuizRecord>();
        for (LiveSessionQuestion question : questions) {
@@ -222,15 +224,17 @@
            record.setChosenOption(chosenOption);
            record.setIsCorrect(correct ? 1 : 0);
            record.setPointsEarned(0);
            record.setAnsweredAt(LocalDateTime.now());
            record.setAnsweredAt(answeredAt);
            liveSessionQuizRecordMapper.insert(record);
            insertedRecords.add(record);
        }
        boolean pointsAwarded = false;
        boolean dailyCoursePointsAlreadyAwarded = false;
        // 当前业务改为只要三题全部答对就立即发放积分,不再依赖完整观看直播。
        if (allCorrect) {
            pointsAwarded = awardQuizPoints(course, session, userId, insertedRecords, questions);
            dailyCoursePointsAlreadyAwarded = hasAwardedCourseQuizPointsToday(course.getId(), userId, answeredAt);
            pointsAwarded = awardQuizPoints(course, session, userId, insertedRecords, questions, answeredAt);
        }
        AppLiveQuizSubmitResultVO resultVO = new AppLiveQuizSubmitResultVO();
@@ -239,7 +243,7 @@
        resultVO.setWatchCompleted(Boolean.valueOf(watchSummary.isWatchCompleted()));
        resultVO.setPointsAwarded(Boolean.valueOf(pointsAwarded));
        resultVO.setPointsReward(Integer.valueOf(defaultNumber(course.getQuizPoints())));
        resultVO.setMessage(resolveQuizSubmitMessage(allCorrect, pointsAwarded));
        resultVO.setMessage(resolveQuizSubmitMessage(allCorrect, pointsAwarded, dailyCoursePointsAlreadyAwarded));
        return resultVO;
    }
@@ -646,9 +650,13 @@
                                    LiveSession session,
                                    Long userId,
                                    List<LiveSessionQuizRecord> quizRecords,
                                    List<LiveSessionQuestion> questions) {
                                    List<LiveSessionQuestion> questions,
                                    LocalDateTime answeredAt) {
        int pointsReward = defaultNumber(course.getQuizPoints());
        if (pointsReward <= 0 || hasAwardedQuizPoints(quizRecords)) {
            return false;
        }
        if (hasAwardedCourseQuizPointsToday(course.getId(), userId, answeredAt)) {
            return false;
        }
        LiveSessionQuizRecord rewardRecord = resolveRewardRecord(quizRecords, questions);
@@ -662,6 +670,23 @@
        // 积分到账后立即同步流水,避免 APP 积分页与直播观看页状态延迟。
        pointsLedgerAppService.syncLedger();
        return true;
    }
    /**
     * 判断用户在同一直播课的答题自然日内是否已获得过积分奖励。
     */
    private boolean hasAwardedCourseQuizPointsToday(Long courseId, Long userId, LocalDateTime answeredAt) {
        if (courseId == null || userId == null) {
            return false;
        }
        LocalDate answerDate = (answeredAt == null ? LocalDateTime.now() : answeredAt).toLocalDate();
        Integer awardedCount = liveSessionQuizRecordMapper.countAwardedCourseQuizPoints(
                courseId,
                userId,
                answerDate.atStartOfDay(),
                answerDate.plusDays(1).atStartOfDay()
        );
        return awardedCount != null && awardedCount.intValue() > 0;
    }
    /**
@@ -722,12 +747,17 @@
    /**
     * 构造答题提交后的提示文案。
     */
    private String resolveQuizSubmitMessage(boolean allCorrect, boolean pointsAwarded) {
    private String resolveQuizSubmitMessage(boolean allCorrect,
                                            boolean pointsAwarded,
                                            boolean dailyCoursePointsAlreadyAwarded) {
        if (!allCorrect) {
            return "回答错误,本次不获得积分";
        }
        if (pointsAwarded) {
            return "答题成功,积分已到账";
        }
        if (dailyCoursePointsAlreadyAwarded) {
            return "今日该课程积分已获得";
        }
        return "答题成功";
    }
@@ -819,6 +849,17 @@
    }
    /**
     * 解析观看页可播放视频地址,直播结束冷却期只保留答题入口不继续播放视频。
     */
    private String resolvePlayableVideoUrl(AppLiveCourseDisplaySupport.CourseDisplaySnapshot snapshot,
                                           LiveSession actionSession) {
        if (snapshot.isEndedCooldown() || actionSession == null) {
            return null;
        }
        return appLiveCourseDisplaySupport.trimToNull(actionSession.getVideoUrl());
    }
    /**
     * 解析模拟直播回放进度的起播时间,优先使用实际开播时间。
     */
    private LocalDateTime resolvePlaybackStartTime(LiveSession actionSession) {