|
@@ -16,6 +16,10 @@ import com.fs.live.domain.LiveCompletionPointsRecord;
|
|
|
import com.fs.live.mapper.LiveCompletionPointsRecordMapper;
|
|
import com.fs.live.mapper.LiveCompletionPointsRecordMapper;
|
|
|
import com.fs.live.service.ILiveCompletionPointsRecordService;
|
|
import com.fs.live.service.ILiveCompletionPointsRecordService;
|
|
|
import com.fs.live.service.ILiveService;
|
|
import com.fs.live.service.ILiveService;
|
|
|
|
|
+import org.redisson.api.RLock;
|
|
|
|
|
+import org.redisson.api.RedissonClient;
|
|
|
|
|
+import org.slf4j.Logger;
|
|
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
import org.springframework.web.bind.annotation.*;
|
|
import org.springframework.web.bind.annotation.*;
|
|
@@ -28,6 +32,7 @@ import java.util.Date;
|
|
|
import java.util.HashMap;
|
|
import java.util.HashMap;
|
|
|
import java.util.List;
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
import java.util.Map;
|
|
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 直播完课积分Controller
|
|
* 直播完课积分Controller
|
|
@@ -36,6 +41,8 @@ import java.util.Map;
|
|
|
@RequestMapping("/app/live/completion")
|
|
@RequestMapping("/app/live/completion")
|
|
|
public class LiveCompletionPointsController extends AppBaseController {
|
|
public class LiveCompletionPointsController extends AppBaseController {
|
|
|
|
|
|
|
|
|
|
+ private static final Logger logger = LoggerFactory.getLogger(LiveCompletionPointsController.class);
|
|
|
|
|
+
|
|
|
@Autowired
|
|
@Autowired
|
|
|
private ILiveCompletionPointsRecordService completionPointsRecordService;
|
|
private ILiveCompletionPointsRecordService completionPointsRecordService;
|
|
|
|
|
|
|
@@ -51,6 +58,9 @@ public class LiveCompletionPointsController extends AppBaseController {
|
|
|
@Autowired
|
|
@Autowired
|
|
|
private LiveCompletionPointsRecordMapper completionPointsRecordMapper;
|
|
private LiveCompletionPointsRecordMapper completionPointsRecordMapper;
|
|
|
|
|
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private RedissonClient redissonClient;
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 领取完课积分
|
|
* 领取完课积分
|
|
|
*/
|
|
*/
|
|
@@ -298,81 +308,107 @@ public class LiveCompletionPointsController extends AppBaseController {
|
|
|
* 没达到,返回报错
|
|
* 没达到,返回报错
|
|
|
*/
|
|
*/
|
|
|
@PostMapping("/receive-points")
|
|
@PostMapping("/receive-points")
|
|
|
- @RepeatSubmit
|
|
|
|
|
public R receivePoints(@RequestParam Long liveId) {
|
|
public R receivePoints(@RequestParam Long liveId) {
|
|
|
Long userId = Long.parseLong(getUserId());
|
|
Long userId = Long.parseLong(getUserId());
|
|
|
|
|
|
|
|
|
|
+ // 创建唯一锁,确保同一个 liveId 和 userId 只能有一个线程在执行
|
|
|
|
|
+ String lockKey = String.format("receivePoints:liveId:%d:userId:%d", liveId, userId);
|
|
|
|
|
+ RLock lock = redissonClient.getLock(lockKey);
|
|
|
|
|
+
|
|
|
try {
|
|
try {
|
|
|
- // 1. 查询当前用户和当前直播间的最近一次完课记录(不限制日期)
|
|
|
|
|
- LiveCompletionPointsRecord record = completionPointsRecordMapper.selectLatestByUserAndLiveId(liveId, userId);
|
|
|
|
|
-
|
|
|
|
|
- if (record == null) {
|
|
|
|
|
- return R.error("您还没有看课记录,无法领取积分");
|
|
|
|
|
|
|
+ // 尝试获取锁,等待时间0秒,锁持有时间15秒
|
|
|
|
|
+ boolean locked = lock.tryLock(0, 15, TimeUnit.SECONDS);
|
|
|
|
|
+ if (!locked) {
|
|
|
|
|
+ logger.warn("获取领取积分锁失败,liveId: {}, userId: {}", liveId, userId);
|
|
|
|
|
+ return R.error("系统繁忙,请稍后重试");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 2. 获取直播间信息和配置
|
|
|
|
|
- Live live = liveService.selectLiveByLiveId(liveId);
|
|
|
|
|
- if (live == null) {
|
|
|
|
|
- return R.error("直播间不存在");
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 1. 查询当前用户和当前直播间的最近一次完课记录(不限制日期)
|
|
|
|
|
+ LiveCompletionPointsRecord record = completionPointsRecordMapper.selectLatestByUserAndLiveId(liveId, userId);
|
|
|
|
|
+
|
|
|
|
|
+ if (record == null) {
|
|
|
|
|
+ return R.error("您还没有看课记录,无法领取积分");
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 3. 检查看课记录里面的时长是否达到完课标准
|
|
|
|
|
- Long watchDuration = record.getWatchDuration();
|
|
|
|
|
- if (watchDuration == null || watchDuration <= 0) {
|
|
|
|
|
- return R.error("您的看课时长不足,无法领取积分");
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 2. 获取直播间信息和配置
|
|
|
|
|
+ Live live = liveService.selectLiveByLiveId(liveId);
|
|
|
|
|
+ if (live == null) {
|
|
|
|
|
+ return R.error("直播间不存在");
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 4. 检查完课比例是否达到标准
|
|
|
|
|
- BigDecimal completionRate = record.getCompletionRate();
|
|
|
|
|
- if (completionRate == null) {
|
|
|
|
|
- // 重新计算完课比例
|
|
|
|
|
- Long videoDuration = live.getDuration();
|
|
|
|
|
- if (videoDuration == null || videoDuration <= 0) {
|
|
|
|
|
- return R.error("直播间视频时长配置错误");
|
|
|
|
|
|
|
+ // 3. 检查看课记录里面的时长是否达到完课标准
|
|
|
|
|
+ Long watchDuration = record.getWatchDuration();
|
|
|
|
|
+ if (watchDuration == null || watchDuration <= 0) {
|
|
|
|
|
+ return R.error("您的看课时长不足,无法领取积分");
|
|
|
}
|
|
}
|
|
|
- completionRate = BigDecimal.valueOf(watchDuration)
|
|
|
|
|
- .multiply(BigDecimal.valueOf(100))
|
|
|
|
|
- .divide(BigDecimal.valueOf(videoDuration), 2, java.math.RoundingMode.HALF_UP);
|
|
|
|
|
- if (completionRate.compareTo(BigDecimal.valueOf(100)) > 0) {
|
|
|
|
|
- completionRate = BigDecimal.valueOf(100);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 检查完课比例是否达到标准
|
|
|
|
|
+ BigDecimal completionRate = record.getCompletionRate();
|
|
|
|
|
+ if (completionRate == null) {
|
|
|
|
|
+ // 重新计算完课比例
|
|
|
|
|
+ Long videoDuration = live.getDuration();
|
|
|
|
|
+ if (videoDuration == null || videoDuration <= 0) {
|
|
|
|
|
+ return R.error("直播间视频时长配置错误");
|
|
|
|
|
+ }
|
|
|
|
|
+ completionRate = BigDecimal.valueOf(watchDuration)
|
|
|
|
|
+ .multiply(BigDecimal.valueOf(100))
|
|
|
|
|
+ .divide(BigDecimal.valueOf(videoDuration), 2, java.math.RoundingMode.HALF_UP);
|
|
|
|
|
+ if (completionRate.compareTo(BigDecimal.valueOf(100)) > 0) {
|
|
|
|
|
+ completionRate = BigDecimal.valueOf(100);
|
|
|
|
|
+ }
|
|
|
|
|
+ record.setCompletionRate(completionRate);
|
|
|
}
|
|
}
|
|
|
- record.setCompletionRate(completionRate);
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- // 5. 从直播间配置获取完课标准
|
|
|
|
|
- String configJson = live.getConfigJson();
|
|
|
|
|
- Integer requiredCompletionRate = null;
|
|
|
|
|
- if (configJson != null && !configJson.isEmpty()) {
|
|
|
|
|
- try {
|
|
|
|
|
- com.alibaba.fastjson.JSONObject jsonConfig = com.alibaba.fastjson.JSON.parseObject(configJson);
|
|
|
|
|
- requiredCompletionRate = jsonConfig.getInteger("completionRate");
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- // 解析失败,忽略
|
|
|
|
|
|
|
+ // 5. 从直播间配置获取完课标准
|
|
|
|
|
+ String configJson = live.getConfigJson();
|
|
|
|
|
+ Integer requiredCompletionRate = null;
|
|
|
|
|
+ if (configJson != null && !configJson.isEmpty()) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ com.alibaba.fastjson.JSONObject jsonConfig = com.alibaba.fastjson.JSON.parseObject(configJson);
|
|
|
|
|
+ requiredCompletionRate = jsonConfig.getInteger("completionRate");
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ // 解析失败,忽略
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- // 6. 判断是否达到完课标准
|
|
|
|
|
- if (requiredCompletionRate != null && completionRate.compareTo(BigDecimal.valueOf(requiredCompletionRate)) < 0) {
|
|
|
|
|
- return R.error("您的完课比例未达到标准(" + requiredCompletionRate + "%),当前完课比例:" + completionRate + "%");
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 6. 判断是否达到完课标准
|
|
|
|
|
+ if (requiredCompletionRate != null && completionRate.compareTo(BigDecimal.valueOf(requiredCompletionRate)) < 0) {
|
|
|
|
|
+ return R.error("您的完课比例未达到标准(" + requiredCompletionRate + "%),当前完课比例:" + completionRate + "%");
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 7. 检查是否已领取
|
|
|
|
|
- if (record.getReceiveStatus() != null && record.getReceiveStatus() == 1) {
|
|
|
|
|
- return R.error("该完课积分已领取");
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 7. 检查是否已领取
|
|
|
|
|
+ if (record.getReceiveStatus() != null && record.getReceiveStatus() == 1) {
|
|
|
|
|
+ return R.error("该完课积分已领取");
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 8. 领取积分(更新看课记录的领取状态,给用户加积分)
|
|
|
|
|
- LiveCompletionPointsRecord receivedRecord = completionPointsRecordService.receiveCompletionPoints(record.getId(), userId);
|
|
|
|
|
|
|
+ // 8. 领取积分(更新看课记录的领取状态,给用户加积分)
|
|
|
|
|
+ LiveCompletionPointsRecord receivedRecord = completionPointsRecordService.receiveCompletionPoints(record.getId(), userId);
|
|
|
|
|
|
|
|
- ReceivePointsVO vo = new ReceivePointsVO();
|
|
|
|
|
- vo.setRecord(receivedRecord);
|
|
|
|
|
- vo.setPoints(receivedRecord.getPointsAwarded());
|
|
|
|
|
- vo.setContinuousDays(receivedRecord.getContinuousDays());
|
|
|
|
|
-
|
|
|
|
|
- return R.ok().put("data", vo);
|
|
|
|
|
- } catch (BaseException e) {
|
|
|
|
|
- return R.error(e.getMessage());
|
|
|
|
|
|
|
+ ReceivePointsVO vo = new ReceivePointsVO();
|
|
|
|
|
+ vo.setRecord(receivedRecord);
|
|
|
|
|
+ vo.setPoints(receivedRecord.getPointsAwarded());
|
|
|
|
|
+ vo.setContinuousDays(receivedRecord.getContinuousDays());
|
|
|
|
|
+
|
|
|
|
|
+ return R.ok().put("data", vo);
|
|
|
|
|
+ } catch (BaseException e) {
|
|
|
|
|
+ return R.error(e.getMessage());
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("领取积分失败,liveId: {}, userId: {}", liveId, userId, e);
|
|
|
|
|
+ return R.error("领取失败: " + e.getMessage());
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ // 释放锁
|
|
|
|
|
+ if (lock.isHeldByCurrentThread()) {
|
|
|
|
|
+ lock.unlock();
|
|
|
|
|
+ logger.debug("领取积分锁已释放,liveId: {}, userId: {}", liveId, userId);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (InterruptedException e) {
|
|
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
|
|
+ logger.error("获取领取积分锁被中断,liveId: {}, userId: {}", liveId, userId, e);
|
|
|
|
|
+ return R.error("系统繁忙,请稍后重试");
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
|
|
+ logger.error("领取积分异常,liveId: {}, userId: {}", liveId, userId, e);
|
|
|
return R.error("领取失败: " + e.getMessage());
|
|
return R.error("领取失败: " + e.getMessage());
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|