Browse Source

统计考卷数据加锁

master
luogw 3 months ago
parent
commit
94dc8bcb0e
  1. 155
      src/main/java/com/project/statistics/domain/service/impl/SaveSaveErrorProneStatisticsDomainServiceImpl.java

155
src/main/java/com/project/statistics/domain/service/impl/SaveSaveErrorProneStatisticsDomainServiceImpl.java

@ -1,7 +1,6 @@
package com.project.statistics.domain.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.project.exam.domain.entity.ExamRecordEntity;
import com.project.question.domain.entity.QuestionEntity;
@ -15,6 +14,7 @@ import com.project.task.domain.enums.ExamModeEnum;
import com.project.task.domain.enums.QuestionTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
@ -36,6 +37,17 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP
@Autowired
private TaskKnowledgePointBaseService taskKnowledgePointBaseService;
@Autowired
private StringRedisTemplate redisTemplate;
private static final String LOCK_PREFIX = "lock:error_prone:";
// 锁自动过期时间5秒,防止死锁(拿到锁后程序崩溃没释放,5s自动失效)
private static final int LOCK_EXPIRE_SECONDS = 5;
// 自旋最大重试次数:最多抢锁10次
private static final int LOCK_RETRY_TIMES = 10;
// 每次抢锁失败后,休眠50ms再重试
private static final long LOCK_RETRY_INTERVAL_MS = 50;
@Override
@Async
public void collectErrorProneStatistics(Long taskId, Integer examMode, List<ExamRecordEntity.QuestionSnapshot> snapshots) {
@ -78,30 +90,47 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP
continue;
}
ErrorProneStatisticsEntity entity = errorProneStatisticsBaseService.lambdaQuery()
.eq(ErrorProneStatisticsEntity::getTaskId, taskId)
.eq(ErrorProneStatisticsEntity::getKpId, kp.getId())
.one();
if (entity == null) {
entity = new ErrorProneStatisticsEntity();
entity.setTaskId(taskId);
entity.setExamMode(ExamModeEnum.GENERATIVE.getValue());
entity.setContent(kp.getContent());
entity.setKpId(kp.getId());
entity.setErrorCount(0);
entity.setAppearCount(0);
String lockKey = LOCK_PREFIX + "gen:" + taskId + ":" + kp.getId();
boolean locked = tryLock(lockKey);
if (!locked) {
log.warn("[易错统计] 获取锁失败, lockKey={}, 题目ID={}", lockKey, questionId);
continue;
}
List<Long> questionIdList = CollectionUtil.isEmpty(entity.getQuestionIdList()) ? new ArrayList<>() : entity.getQuestionIdList();
questionIdList.add(questionId);
entity.setQuestionIdList(questionIdList);
entity.setAppearCount(entity.getAppearCount() + 1);
if (Boolean.FALSE.equals(snapshot.getIsRight())) {
entity.setErrorCount(entity.getErrorCount() + 1);
}
try {
ErrorProneStatisticsEntity entity = errorProneStatisticsBaseService.lambdaQuery()
.eq(ErrorProneStatisticsEntity::getTaskId, taskId)
.eq(ErrorProneStatisticsEntity::getKpId, kp.getId())
.one();
errorProneStatisticsBaseService.saveOrUpdate(entity);
if (entity == null) {
entity = new ErrorProneStatisticsEntity();
entity.setTaskId(taskId);
entity.setExamMode(ExamModeEnum.GENERATIVE.getValue());
entity.setContent(kp.getContent());
entity.setKpId(kp.getId());
entity.setErrorCount(0);
entity.setAppearCount(0);
entity.setQuestionIdList(new ArrayList<>());
}
List<Long> questionIdList = entity.getQuestionIdList();
if (questionIdList == null) {
questionIdList = new ArrayList<>();
}
if (!questionIdList.contains(questionId)) {
questionIdList.add(questionId);
}
entity.setQuestionIdList(questionIdList);
entity.setAppearCount(entity.getAppearCount() + 1);
if (Boolean.FALSE.equals(snapshot.getIsRight())) {
entity.setErrorCount(entity.getErrorCount() + 1);
}
errorProneStatisticsBaseService.saveOrUpdate(entity);
} finally {
unlock(lockKey);
}
}
}
}
@ -121,32 +150,72 @@ public class SaveSaveErrorProneStatisticsDomainServiceImpl implements SaveErrorP
continue;
}
ErrorProneStatisticsEntity entity = errorProneStatisticsBaseService.lambdaQuery()
.eq(ErrorProneStatisticsEntity::getTaskId, taskId)
.eq(ErrorProneStatisticsEntity::getExamMode, snapshot.getQuestionId())
.one();
if (entity == null) {
entity = new ErrorProneStatisticsEntity();
entity.setTaskId(taskId);
entity.setQuestionIdList(List.of(snapshot.getQuestionId()));
entity.setExamMode(ExamModeEnum.CLASSIC.getValue());
entity.setContent(content);
entity.setQuestionType(snapshot.getType());
entity.setErrorCount(0);
entity.setAppearCount(0);
entity.setOptionDetails(buildOptionDetail(snapshot));
String lockKey = LOCK_PREFIX + "classic:" + taskId + ":" + snapshot.getQuestionId();
boolean locked = tryLock(lockKey);
if (!locked) {
log.warn("[易错统计] 获取锁失败, lockKey={}, 题目ID={}", lockKey, snapshot.getQuestionId());
continue;
}
entity.setAppearCount(entity.getAppearCount() + 1);
if (Boolean.FALSE.equals(snapshot.getIsRight())) {
entity.setErrorCount(entity.getErrorCount() + 1);
}
try {
ErrorProneStatisticsEntity entity = errorProneStatisticsBaseService.lambdaQuery()
.eq(ErrorProneStatisticsEntity::getTaskId, taskId)
.eq(ErrorProneStatisticsEntity::getExamMode, ExamModeEnum.CLASSIC.getValue())
.apply("JSON_CONTAINS(question_id_list, CAST({0} AS JSON))", snapshot.getQuestionId())
.one();
updateOptionDetail(entity, snapshot.getUserAnswer());
if (entity == null) {
entity = new ErrorProneStatisticsEntity();
entity.setTaskId(taskId);
entity.setQuestionIdList(new ArrayList<>(List.of(snapshot.getQuestionId())));
entity.setExamMode(ExamModeEnum.CLASSIC.getValue());
entity.setContent(content);
entity.setQuestionType(snapshot.getType());
entity.setErrorCount(0);
entity.setAppearCount(0);
entity.setOptionDetails(buildOptionDetail(snapshot));
}
errorProneStatisticsBaseService.saveOrUpdate(entity);
entity.setAppearCount(entity.getAppearCount() + 1);
if (Boolean.FALSE.equals(snapshot.getIsRight())) {
entity.setErrorCount(entity.getErrorCount() + 1);
}
updateOptionDetail(entity, snapshot.getUserAnswer());
errorProneStatisticsBaseService.saveOrUpdate(entity);
} finally {
unlock(lockKey);
}
}
}
/**
* 尝试获取 Redis 分布式锁带自旋重试
*/
private boolean tryLock(String lockKey) {
int retry = 0;
while (retry < LOCK_RETRY_TIMES) {
Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", LOCK_EXPIRE_SECONDS, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(locked)) {
return true;
}
retry++;
try {
Thread.sleep(LOCK_RETRY_INTERVAL_MS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
return false;
}
/**
* 释放 Redis 分布式锁
*/
private void unlock(String lockKey) {
redisTemplate.delete(lockKey);
}
/**

Loading…
Cancel
Save