Skip to content

分数计算

模板方法统一缓存与锁流程,策略模式封装不同计分算法,简单工厂按需选择策略实现。

代码思路

采用模板方法模式对评分处理的通用流程进行统一抽象,将数据库读取、缓存操作、异常处理等共性步骤固化于抽象模板中,确保流程一致性与代码复用。具体评分逻辑根据 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());
}
最近更新