|
|
@@ -0,0 +1,328 @@
|
|
|
+package com.fs.live.service.impl;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import com.fs.common.exception.base.BaseException;
|
|
|
+import com.fs.his.domain.FsUser;
|
|
|
+import com.fs.his.domain.FsUserIntegralLogs;
|
|
|
+import com.fs.his.mapper.FsUserIntegralLogsMapper;
|
|
|
+import com.fs.his.mapper.FsUserMapper;
|
|
|
+import com.fs.live.domain.Live;
|
|
|
+import com.fs.live.domain.LiveCompletionPointsRecord;
|
|
|
+import com.fs.live.mapper.LiveCompletionPointsRecordMapper;
|
|
|
+import com.fs.live.service.ILiveCompletionPointsRecordService;
|
|
|
+import com.fs.live.service.ILiveService;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
+import java.time.LocalDate;
|
|
|
+import java.time.ZoneId;
|
|
|
+import java.time.temporal.ChronoUnit;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 直播完课积分记录Service业务层处理
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class LiveCompletionPointsRecordServiceImpl implements ILiveCompletionPointsRecordService {
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private LiveCompletionPointsRecordMapper recordMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ILiveService liveService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private FsUserMapper fsUserMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private FsUserIntegralLogsMapper fsUserIntegralLogsMapper;
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查并创建完课记录(由定时任务调用)
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void checkAndCreateCompletionRecord(Long liveId, Long userId, Long watchDuration) {
|
|
|
+ try {
|
|
|
+ // 1. 获取直播信息和配置
|
|
|
+ Live live = liveService.selectLiveByLiveId(liveId);
|
|
|
+ if (live == null) {
|
|
|
+ log.warn("直播间不存在, liveId={}", liveId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 从数据库获取完课积分配置
|
|
|
+ CompletionPointsConfig config = getCompletionPointsConfig(live);
|
|
|
+
|
|
|
+ // 检查是否开启完课积分功能
|
|
|
+ if (!config.isEnabled()) {
|
|
|
+ log.debug("直播间未开启完课积分功能, liveId={}", liveId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查配置完整性
|
|
|
+ Integer completionRate = config.getCompletionRate();
|
|
|
+ int[] pointsConfig = config.getPointsConfig();
|
|
|
+
|
|
|
+ if (completionRate == null || pointsConfig == null || pointsConfig.length == 0) {
|
|
|
+ log.warn("完课积分配置不完整, liveId={}, completionRate={}, pointsConfig={}",
|
|
|
+ liveId, completionRate, pointsConfig);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 获取视频总时长(秒)
|
|
|
+ Long videoDuration = live.getDuration();
|
|
|
+ if (videoDuration == null || videoDuration <= 0) {
|
|
|
+ log.warn("直播间视频时长无效, liveId={}, duration={}", liveId, videoDuration);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 计算完课比例
|
|
|
+ BigDecimal watchRate = BigDecimal.valueOf(watchDuration)
|
|
|
+ .multiply(BigDecimal.valueOf(100))
|
|
|
+ .divide(BigDecimal.valueOf(videoDuration), 2, RoundingMode.HALF_UP);
|
|
|
+
|
|
|
+ // 5. 判断是否达到完课标准
|
|
|
+ if (watchRate.compareTo(BigDecimal.valueOf(completionRate)) < 0) {
|
|
|
+ log.debug("观看时长未达到完课标准, liveId={}, userId={}, watchRate={}%, required={}%",
|
|
|
+ liveId, userId, watchRate, completionRate);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 检查今天是否已有完课记录
|
|
|
+ LocalDate today = LocalDate.now();
|
|
|
+ Date currentDate = Date.from(today.atStartOfDay(ZoneId.systemDefault()).toInstant());
|
|
|
+
|
|
|
+ LiveCompletionPointsRecord todayRecord = recordMapper.selectByUserAndDate(liveId, userId, currentDate);
|
|
|
+ if (todayRecord != null) {
|
|
|
+ log.debug("今天已有完课记录, liveId={}, userId={}", liveId, userId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 7. 查询最近一次完课记录,计算连续天数
|
|
|
+ LiveCompletionPointsRecord latestRecord = recordMapper.selectLatestByUser(liveId, userId);
|
|
|
+ int continuousDays = 1;
|
|
|
+
|
|
|
+ if (latestRecord != null) {
|
|
|
+ LocalDate lastDate = latestRecord.getCurrentCompletionDate()
|
|
|
+ .toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
|
|
+
|
|
|
+ long daysBetween = ChronoUnit.DAYS.between(lastDate, today);
|
|
|
+
|
|
|
+ if (daysBetween == 1) {
|
|
|
+ // 昨天完课了,连续天数+1
|
|
|
+ continuousDays = latestRecord.getContinuousDays() + 1;
|
|
|
+ } else if (daysBetween > 1) {
|
|
|
+ // 中断了,重新开始
|
|
|
+ continuousDays = 1;
|
|
|
+ } else {
|
|
|
+ // daysBetween == 0 说明今天已经有记录了(理论上不会进入这里,因为前面已经检查过)
|
|
|
+ log.warn("异常情况: 今天已有完课记录, liveId={}, userId={}", liveId, userId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 8. 计算积分
|
|
|
+ int points = calculatePoints(continuousDays, pointsConfig);
|
|
|
+
|
|
|
+ // 9. 创建完课记录
|
|
|
+ LiveCompletionPointsRecord record = new LiveCompletionPointsRecord();
|
|
|
+ record.setLiveId(liveId);
|
|
|
+ record.setUserId(userId);
|
|
|
+ record.setWatchDuration(watchDuration);
|
|
|
+ record.setVideoDuration(videoDuration);
|
|
|
+ record.setCompletionRate(watchRate);
|
|
|
+ record.setContinuousDays(continuousDays);
|
|
|
+ record.setPointsAwarded(points);
|
|
|
+ record.setCurrentCompletionDate(currentDate);
|
|
|
+ record.setReceiveStatus(0); // 未领取
|
|
|
+
|
|
|
+ if (latestRecord != null) {
|
|
|
+ record.setLastCompletionDate(latestRecord.getCurrentCompletionDate());
|
|
|
+ }
|
|
|
+
|
|
|
+ recordMapper.insertRecord(record);
|
|
|
+
|
|
|
+ log.info("创建完课记录成功, liveId={}, userId={}, continuousDays={}, points={}",
|
|
|
+ liveId, userId, continuousDays, points);
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("检查并创建完课记录失败, liveId={}, userId={}", liveId, userId, e);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 用户领取完课积分
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public LiveCompletionPointsRecord receiveCompletionPoints(Long recordId, Long userId) {
|
|
|
+ // 1. 查询完课记录
|
|
|
+ LiveCompletionPointsRecord record = recordMapper.selectById(recordId);
|
|
|
+ if (record == null) {
|
|
|
+ throw new BaseException("完课记录不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 校验用户
|
|
|
+ if (!record.getUserId().equals(userId)) {
|
|
|
+ throw new BaseException("无权领取该完课积分");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 校验领取状态
|
|
|
+ if (record.getReceiveStatus() == 1) {
|
|
|
+ throw new BaseException("该完课积分已领取");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 更新用户积分
|
|
|
+ FsUser user = fsUserMapper.selectFsUserByUserId(userId);
|
|
|
+ if (user == null) {
|
|
|
+ throw new BaseException("用户不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ Long currentIntegral = user.getIntegral() != null ? user.getIntegral() : 0L;
|
|
|
+ Long newIntegral = currentIntegral + record.getPointsAwarded();
|
|
|
+
|
|
|
+ FsUser updateUser = new FsUser();
|
|
|
+ updateUser.setUserId(userId);
|
|
|
+ updateUser.setIntegral(newIntegral);
|
|
|
+ fsUserMapper.updateFsUser(updateUser);
|
|
|
+
|
|
|
+ // 5. 记录积分变动日志
|
|
|
+ FsUserIntegralLogs integralLog = new FsUserIntegralLogs();
|
|
|
+ integralLog.setUserId(userId);
|
|
|
+ integralLog.setIntegral(Long.valueOf(record.getPointsAwarded()));
|
|
|
+ integralLog.setBalance(newIntegral);
|
|
|
+ integralLog.setLogType(5); // 5-直播完课积分
|
|
|
+ integralLog.setBusinessId("live_completion_" + recordId); // 业务ID:直播完课记录ID
|
|
|
+ integralLog.setBusinessType(5); // 5-直播完课
|
|
|
+ integralLog.setStatus(1);
|
|
|
+ integralLog.setCreateTime(new Date());
|
|
|
+ fsUserIntegralLogsMapper.insertFsUserIntegralLogs(integralLog);
|
|
|
+
|
|
|
+ // 6. 更新完课记录状态
|
|
|
+ LiveCompletionPointsRecord updateRecord = new LiveCompletionPointsRecord();
|
|
|
+ updateRecord.setId(recordId);
|
|
|
+ updateRecord.setReceiveStatus(1);
|
|
|
+ updateRecord.setReceiveTime(new Date());
|
|
|
+ recordMapper.updateRecord(updateRecord);
|
|
|
+
|
|
|
+ // 7. 返回更新后的记录
|
|
|
+ record.setReceiveStatus(1);
|
|
|
+ record.setReceiveTime(new Date());
|
|
|
+
|
|
|
+ log.info("用户领取完课积分成功, userId={}, recordId={}, points={}", userId, recordId, record.getPointsAwarded());
|
|
|
+
|
|
|
+ return record;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取用户未领取的完课记录
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public List<LiveCompletionPointsRecord> getUserUnreceivedRecords(Long liveId, Long userId) {
|
|
|
+ return recordMapper.selectUnreceivedByUser(liveId, userId);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询用户积分领取记录
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public List<LiveCompletionPointsRecord> getUserRecords(Long liveId, Long userId) {
|
|
|
+ return recordMapper.selectRecordsByUser(liveId, userId);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从直播配置中获取完课积分配置
|
|
|
+ */
|
|
|
+ private CompletionPointsConfig getCompletionPointsConfig(Live live) {
|
|
|
+ CompletionPointsConfig config = new CompletionPointsConfig();
|
|
|
+ config.setEnabled(false);
|
|
|
+ config.setCompletionRate(null);
|
|
|
+ config.setPointsConfig(null);
|
|
|
+
|
|
|
+ String configJson = live.getConfigJson();
|
|
|
+ if (configJson == null || configJson.isEmpty()) {
|
|
|
+ return config;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ JSONObject jsonConfig = JSON.parseObject(configJson);
|
|
|
+
|
|
|
+ config.setEnabled(jsonConfig.getBooleanValue("enabled"));
|
|
|
+
|
|
|
+ Integer rate = jsonConfig.getInteger("completionRate");
|
|
|
+ if (rate != null && rate > 0 && rate <= 100) {
|
|
|
+ config.setCompletionRate(rate);
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Integer> pointsList = jsonConfig.getObject("pointsConfig", List.class);
|
|
|
+ if (pointsList != null && !pointsList.isEmpty()) {
|
|
|
+ config.setPointsConfig(pointsList.stream().mapToInt(Integer::intValue).toArray());
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("解析完课积分配置失败, liveId={}, 配置未生效", live.getLiveId(), e);
|
|
|
+ }
|
|
|
+
|
|
|
+ return config;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算积分
|
|
|
+ * 根据连续天数和积分配置计算应得积分
|
|
|
+ * @param continuousDays 连续完课天数
|
|
|
+ * @param pointsConfig 积分配置数组
|
|
|
+ * @return 应得积分
|
|
|
+ */
|
|
|
+ private int calculatePoints(int continuousDays, int[] pointsConfig) {
|
|
|
+ if (continuousDays <= 0) {
|
|
|
+ return pointsConfig[0];
|
|
|
+ }
|
|
|
+ if (continuousDays > pointsConfig.length) {
|
|
|
+ // 超过配置天数,使用最后一天的积分
|
|
|
+ return pointsConfig[pointsConfig.length - 1];
|
|
|
+ }
|
|
|
+ return pointsConfig[continuousDays - 1];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 完课积分配置内部类
|
|
|
+ */
|
|
|
+ private static class CompletionPointsConfig {
|
|
|
+ private boolean enabled;
|
|
|
+ private Integer completionRate;
|
|
|
+ private int[] pointsConfig;
|
|
|
+
|
|
|
+ public boolean isEnabled() {
|
|
|
+ return enabled;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setEnabled(boolean enabled) {
|
|
|
+ this.enabled = enabled;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Integer getCompletionRate() {
|
|
|
+ return completionRate;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setCompletionRate(Integer completionRate) {
|
|
|
+ this.completionRate = completionRate;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int[] getPointsConfig() {
|
|
|
+ return pointsConfig;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setPointsConfig(int[] pointsConfig) {
|
|
|
+ this.pointsConfig = pointsConfig;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|