评估配置功能
评估计算功能
基础功能:
- 实现自定义表单展示,包含项目的展示和得分规则计算
- 实现所有项目结果的评估结果组合计算和展示
数据库定义
实现原理
类似问卷调查配置表单,数据库有一个脚本保存匹配规则,类似 itemValue.include('option1633763947226'),每一项项目的得分是通过规则引擎匹配规则脚本, 匹配成功则返回该得分,计算评估结果也是和项目选项同一个原理
核心代码
规则引擎
@Slf4j
public class RuleEngineManager {
private ScriptEngine engine;
private Bindings bindings = new SimpleBindings();
private String extendScript = "";
private String ruleScript;
public static RuleEngineManager instance() {
RuleEngineManager instance = new RuleEngineManager();
ScriptEngineManager manager = new ScriptEngineManager();
instance.engine = manager.getEngineByName("JavaScript");
instance.extendScript();
return instance;
}
public RuleEngineManager addParam(String key, Object value) {
bindings.put(key, value);
return this;
}
public RuleEngineManager addParam(Map<String, Object> map) {
bindings.putAll(map);
return this;
}
public RuleEngineManager setRuleScript(String ruleScript) {
this.ruleScript = ruleScript;
return this;
}
@SuppressWarnings("unchecked")
public <T> T eval() {
try {
String evalScript = extendScript + ";" + ruleScript;
log.debug("RuleEngineManager eval script={}, bindings={}", ruleScript, JsonUtil.objectToString(bindings));
return (T) engine.eval(evalScript, bindings);
} catch (ScriptException e) {
log.error("", e);
return null;
}
}
public BigDecimal eval2() {
return new BigDecimal(eval().toString());
}
public boolean match() {
try {
String evalScript = extendScript + ";" + ruleScript;
log.debug("RuleEngineManager eval script={}, bindings={}", ruleScript, JsonUtil.objectToString(bindings));
return (Boolean) engine.eval(evalScript, bindings);
} catch (ScriptException e) {
log.error("", e);
return false;
}
}
private RuleEngineManager extendScript() {
this.extendScript = ScriptConstant.INCLUDE + ";" + ScriptConstant.EXCLUDE;
log.debug("RuleEngineManager extend script = {}", this.extendScript);
return this;
}
public static void main(String[] args) throws ScriptException {
String s = "0.0391*a+0.7917*b+1.3388*c ";
Object result1 = RuleEngineManager.instance()
.addParam("a", new BigDecimal(1))
.addParam("b", new BigDecimal(2))
.addParam("c", new BigDecimal(3))
.setRuleScript(s)
.eval();
System.out.println(new BigDecimal(result1.toString()));
Object result2 = RuleEngineManager.instance()
.addParam("a", 1)
.setRuleScript("function contain(a){ return a.indexOf('a')} contain('aaa')")
.eval();
System.out.println(result2);
Object result3 = RuleEngineManager.instance()
.addParam("a", "0")
.addParam("b", "a")
.setRuleScript("a === '0'")
.eval();
System.out.println(result3);
}
}
js扩展
public class ScriptConstant {
public static String INCLUDE =
"String.prototype.include = function(s) {"
+ " return this.indexOf(s) >= 0"
+ "}";
public static String EXCLUDE =
"String.prototype.exclude = function(s) {"
+ " return this.indexOf(s) < 0"
+ "}";
}
计算引擎
@Slf4j
@Service
public class AssessRuleEngine {
public AssessModelResult eval(AssessModelVO modelVO, Map<String, Object> param) {
// 构建项目编码->项目得分 map
Map<String, Object> resultItemMap = new HashMap<>();
// 循环计算每个项目的得分
modelVO.getItemList().forEach(item -> {
String itemCode = item.getItemCode();
Object itemValue = param.get(itemCode);
AssertUtil.notNull(itemValue, MessageEnum.ASSESS_PARAM_ERROR);
ScoreTypeEnum scoreType = ScoreTypeEnum.getByCode(item.getScoreType());
AssertUtil.notNull(scoreType, MessageEnum.ASSESS_CONFIG_ERROR);
// 项目得分
BigDecimal itemScore = BigDecimal.ZERO;
// 直接得分
if (scoreType == ScoreTypeEnum.RETURN_SCORE) {
// 多选积分求和, 单选单个得分
for (AssessModelOption option : item.getOptionList()) {
if (itemValue.toString().contains(option.getOptionCode())) {
itemScore = itemScore.add(option.getScore());
}
}
log.info("计算项目得分 itemName={}, 直接得分={}", item.getItemName(), itemScore);
}
// 匹配规则 或 表达式
else {
RuleEngineManager engineInstance = RuleEngineManager.instance()
.addParam(DefaultVariableEnum.ITEM_VALUE.getCode(), itemValue);
for (AssessModelRule rule : item.getRuleList()) {
String script = rule.getScoreExp();
boolean match = engineInstance.setRuleScript(script).match();
if (match) {
itemScore = rule.getScore();
break;
}
}
log.info("计算项目得分 itemName={}, 匹配规则={}", item.getItemName(), itemScore);
}
AssertUtil.notNull(itemScore, MessageEnum.ASSESS_CONFIG_ERROR);
resultItemMap.put(itemCode, itemScore);
});
/*
* 计算评估结果
*/
ResultTypeEnum resultType = ResultTypeEnum.getByCode(modelVO.getResultType());
AssertUtil.notNull(resultType, MessageEnum.ASSESS_CONFIG_ERROR);
// 得分求和
if (Objects.equals(resultType, ResultTypeEnum.SUM)) {
BigDecimal totalScore = resultItemMap.keySet().stream()
.map(key -> (BigDecimal) resultItemMap.get(key))
.reduce(BigDecimal::add).get();
resultItemMap.put(DefaultVariableEnum.TOTAL_SCORE.getCode(), totalScore);
log.info("计算项目总得分 totalScore={}", totalScore);
}
// 得分表达式
else if (Objects.equals(resultType, ResultTypeEnum.EXP_TEXT)) {
AssertUtil.notNull(modelVO.getResultExp(), MessageEnum.ASSESS_CONFIG_ERROR);
BigDecimal totalScore = RuleEngineManager.instance()
.addParam(resultItemMap)
.setRuleScript(modelVO.getResultExp())
.eval2();
resultItemMap.put(DefaultVariableEnum.TOTAL_SCORE.getCode(), totalScore);
log.info("计算项目得分表达式 totalScore={}, resultExp = {}", totalScore, modelVO.getResultExp());
}
// 匹配非默认结果
AssessModelResult matchResult = null;
for (AssessModelResult modelResult : modelVO.getResultList()) {
if (modelResult.getDefaultResult() == null || !modelResult.getDefaultResult()) {
String script = modelResult.getResultExp();
boolean match = RuleEngineManager.instance().addParam(resultItemMap)
.setRuleScript(script)
.match();
if (match) {
matchResult = modelResult;
break;
}
} else {
matchResult = modelResult;
}
}
AssertUtil.notNull(matchResult, MessageEnum.ASSESS_CONFIG_ERROR);
// 计算匹配结果自定义值
if (StringUtils.isNotEmpty(matchResult.getCustomExp())) {
String script = matchResult.getCustomExp();
BigDecimal value = RuleEngineManager.instance().addParam(resultItemMap)
.setRuleScript(script)
.eval2();
matchResult.setCustomValue(value);
}
log.info("返回默认结果 modelResult={}", JsonUtil.objectToString(matchResult));
return matchResult;
}
}
项目地址