分数计算
模板方法统一缓存与锁流程,策略模式封装不同计分算法,简单工厂按需选择策略实现。
代码思路
采用模板方法模式对评分处理的通用流程进行统一抽象,将数据库读取、缓存操作、异常处理等共性步骤固化于抽象模板中,确保流程一致性与代码复用。具体评分逻辑根据 GradeInputType
枚举值进行分类处理,不同类型对应差异化的得分计算规则。通过简单工厂模式,依据传入的 GradeInputType
实例化相应的具体分数计算器,实现计算策略的动态获取。在模板流程执行过程中,数据库读取完成后,交由对应类型的分数计算器完成具体的得分计算,从而实现通用流程与业务逻辑的解耦,提升系统的可扩展性与可维护性。
分数计算封装(模板方法+策略模式)
java
/**
* 得分计算
*/
@Slf4j
public abstract class BaseScore {
private final RedisCache redisCache;
private final AppEduProjectStandardMapper mapper;
private final ObjectMapper objectMapper;
private final RedissonClient redissonClient;
private static final String PROJECT_STANDARD_LOCK = "project_standard";
public BaseScore(RedisCache redisCache, AppEduProjectStandardMapper mapper,
ObjectMapper objectMapper, RedissonClient redissonClient) {
this.redisCache = redisCache;
this.mapper = mapper;
this.objectMapper = objectMapper;
this.redissonClient = redissonClient;
}
/**
* 获取得分
*
* @param dto 成绩信息
* @param examProject 考试项目信息
* @return
*/
public abstract BigDecimal getScore(ExamScoreDto dto, EduExamProject examProject);
/**
* 获取实现计算类型
*
* @return
*/
public abstract GradeInputType getType();
/**
* 构建成绩标准map
*
* @param list
* @return
*/
protected abstract Map<Object, Object> buildStandardMap(List<EduProjectStandard> list);
/**
* 获取项目评分标准
*
* @param dto 评分dto
* @return
*/
protected Map<Object, Object> getProjectStandard(ExamScoreDto dto) {
// 获取redis缓存
Map<Object, Object> map = getProjectStandardFromRedis(dto);
if (map != null) {
return map;
}
/**
* 添加分布式锁
* 防止同时间多个请求同时同时获取项目评分标准
*/
RLock lock = redissonClient.getLock(PROJECT_STANDARD_LOCK);
try {
lock.tryLock(10, 5, TimeUnit.SECONDS);
// 再次校验缓存
map = getProjectStandardFromRedis(dto);
if (map != null) {
return map;
}
// 获取数据库
map = getProjectStandardFromDataBase(dto);
return map;
}
catch (InterruptedException e) {
log.error("获取项目评分标准异常:{}", e);
Thread.currentThread().interrupt();
throw new ServiceException("获取项目评分标准失败");
}
catch (Exception e) {
log.error("获取项目评分标准异常:{}", e);
throw new ServiceException(e.getMessage());
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
/**
* 从redis获取项目评分标准
*
* @param projectId
* @return
*/
private Map<Object, Object> getProjectStandardFromRedis(ExamScoreDto projectId) {
String json = redisCache.getCacheObject(buildKey(projectId));
if (json != null) {
try {
// 将JSON字符串反序列化为
return objectMapper.readValue(json, new TypeReference<Map<Object, Object>>() {});
} catch (Exception e) {
log.error("项目评分标准反序列化失败:{}", e);
throw new ServiceException("项目评分标准反序列化失败");
}
}
return null;
}
/**
* 解析考试成绩
*
* @param performance
* @param clazz
* @param <T>
* @return
*/
public <T> T parsePerformance(Object performance, Class<T> clazz) {
try {
return objectMapper.convertValue(performance, clazz);
} catch (Exception e) {
log.error("考试成绩值转换异常:{}", e);
throw new ServiceException("考试成绩值格式错误 无法转换");
}
}
/**
* 从数据库获取项目评分标准存入redis
*
* @param dto
* @return
*/
private Map<Object, Object> getProjectStandardFromDataBase(ExamScoreDto dto) {
MPJLambdaWrapper<EduProjectStandard> wrapper = new MPJLambdaWrapper<>();
wrapper
.eq(EduProjectStandard::getProjectId, dto.getProjectId())
.eq(EduProjectStandard::getGradeId, dto.getGradeId())
.eq(EduProjectStandard::getGender, dto.getGender())
.eq(EduProjectStandard::getIsValid, ScoreValid.VALID.getCode());
List<EduProjectStandard> selectList = mapper.selectList(wrapper);
if (selectList == null || selectList.isEmpty()) {
throw new ServiceException("项目评分标准不存在");
}
Map<Object, Object> map = buildStandardMap(selectList);
String key = buildKey(dto);
saveToRedis(key, map);
return map;
}
/**
* 缓存到redis
*
* @param key
* @param map
* @return
*/
private void saveToRedis(String key, Map<Object, Object> map) {
try {
// 将Map序列化为JSON字符串
String jsonStr = objectMapper.writeValueAsString(map);
redisCache.setCacheObject(key, jsonStr, Constants.PROJECT_STANDARD_EXPIRE_TIME, TimeUnit.MINUTES);
} catch (JsonProcessingException e) {
log.error("项目评分标准序列化失败:{}", e);
throw new ServiceException("项目评分标准序列化失败");
}
}
/**
* 构建redis key
*
* @param dto 项目id
* @return
*/
private static String buildKey(ExamScoreDto dto) {
return Constants.PROJECT_STANDARD + dto.getProjectId() + "-" + dto.getGradeId() + "-" + dto.getGender();
}
}
java
/**
* @Description : 直接录入类得分
* @Author : LiuJun
* @Date: 2025/3/17 10:29
*/
@Slf4j
@Component("directInputScore")
public class DirectInputScore extends BaseScore{
@Autowired
public DirectInputScore(RedisCache redisCache, AppEduProjectStandardMapper mapper,
ObjectMapper objectMapper, RedissonClient redissonClient) {
super(redisCache, mapper, objectMapper, redissonClient);
}
@Override
public BigDecimal getScore(ExamScoreDto dto, EduExamProject examProject) {
BigDecimal performance = parsePerformance(dto.getPerformance(), BigDecimal.class);
Map<Object, Object> projectStandard = getProjectStandard(dto);
// ...得分具体计算过程
}
/**
* @return
*/
@Override
public GradeInputType getType() {
return GradeInputType.DIRECT_INPUT;
}
/**
* 构建成绩标准map
*
* @param list
* @return
*/
@Override
protected Map<Object, Object> buildStandardMap(List<EduProjectStandard> list) {
Map<Object, Object> map = new TreeMap<>();
list.forEach(standard -> map.put(standard.getInputGrade(), standard.getScore()));
return map;
}
}
java
/**
* @Description : 指数类得分
* @Author : LiuJun
* @Date: 2025/3/17 10:33
*/
@Slf4j
@Component("indexScore")
public class IndexScore extends BaseScore {
@Autowired
public IndexScore(RedisCache redisCache, AppEduProjectStandardMapper mapper,ObjectMapper objectMapper, RedissonClient redissonClient) {
super(redisCache, mapper, objectMapper, redissonClient);
}
@Override
public BigDecimal getScore(ExamScoreDto dto, EduExamProject examProject) {
BigDecimal performance = parsePerformance(dto.getPerformance(), BigDecimal.class);
Map<Object, Object> projectStandard = getProjectStandard(dto);
// ...得分具体计算过程
}
@Override
public GradeInputType getType() {
return GradeInputType.INDEX;
}
@Override
protected Map<Object, Object> buildStandardMap(List<EduProjectStandard> list) {
Map<Object, Object> map = new TreeMap<>();
list.forEach(standard -> map.put(standard.getIndexGrade(), standard.getScore()));
return map;
}
}
得分计算(简单工厂)
使用SpringIoc自动注入
java
@Component
public class ScoreFactory {
private final List<BaseScore> baseScoreList;
public ScoreFactory(List<BaseScore> baseScoreList) {
this.baseScoreList = baseScoreList;
}
public BaseScore getBean(GradeInputType type) {
for (BaseScore score : baseScoreList) {
if (score.getType() == type) {
return score;
}
}
return null;
}
}
计算得分调用
java
public R<String> getScore(ExamScoreDto examScoreDto) {
EduExamProject examProject = examProjectService.getById(examScoreDto.getProjectId());
GradeInputType gradeInputType = GradeInputType.valueOf(examProject.getProjectType());
BaseScore score = scoreFactory.getBean(gradeInputType);
BigDecimal scoreValue = score.getScore(examScoreDto, examProject);
return R.ok(scoreValue.toPlainString());
}