Spring Boot项目集成Aviator实现表达式动态成本计算功能的完整解决方案
一、Maven依赖配置
<!-- aviator核心库 -->
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>5.3.3</version>
</dependency>
<!-- 校验框架 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
二、配置类设计
1. Aviator引擎配置
@Configuration
public class AviatorConfig {
@Bean
public AviatorEvaluatorInstance aviatorEvaluator() {
AviatorEvaluatorInstance instance = AviatorEvaluator.newInstance();
// 配置引擎参数
instance.setOption(Options.USE_USER_ENV_AS_TOP_ENV_DIRECTLY, true);
instance.setOption(Options.MAX_CACHE_SIZE, 1000); // 表达式缓存数量
instance.setOption(Options.TRACE_EVAL, false); // 生产环境关闭调试
return instance;
}
}
2. 成本规则加载配置
@Configuration
@ConfigurationProperties(prefix = "cost.rules")
public class CostRuleConfig {
// 规则文件存储路径
private String location = "classpath:rules/cost";
// 规则映射表(示例:{ruleKey: 规则文件名})
private Map<String, String> mapping = new HashMap<>();
// Getter/Setter
}
三、领域模型设计
1. 成本计算请求DTO
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CostCalculateRequest {
@NotBlank(message = "成本类型不能为空")
private String costType; // 对应规则类型如:hardware/software
@NotNull(message = "计算参数不能为空")
private Map<String, Object> params; // 输入参数如数量、单价等
}
2. 统一响应DTO
@Data
@AllArgsConstructor
public class ApiResponse<T> {
private int code;
private String message;
private T data;
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "Success", data);
}
}
四、业务层实现
1. 表达式加载策略接口
public interface ExpressionLoader {
/**
* 加载指定规则表达式
* @param ruleKey 规则标识
* @return 编译后的表达式
*/
Expression loadExpression(String ruleKey);
}
2. 文件系统实现(示例)
@Component
@RequiredArgsConstructor
public class FileExpressionLoader implements ExpressionLoader {
private final CostRuleConfig ruleConfig;
private final AviatorEvaluatorInstance evaluator;
@Override
public Expression loadExpression(String ruleKey) {
String fileName = ruleConfig.getMapping().get(ruleKey);
try {
Resource resource = new ClassPathResource(ruleConfig.getLocation() + "/" + fileName);
return evaluator.compile(resource.getFile().getAbsolutePath(), true);
} catch (IOException e) {
throw new BusinessException("规则文件加载失败: " + fileName);
}
}
}
3. 核心计算服务
@Service
@RequiredArgsConstructor
public class CostCalculatorService {
private final ExpressionLoader loader;
private final AviatorEvaluatorInstance evaluator;
/**
* 执行成本计算
* @param request 计算请求
* @return 计算结果(包含明细)
*/
public BigDecimal calculateCost(CostCalculateRequest request) {
// 1. 加载表达式
Expression exp = loader.loadExpression(request.getCostType());
// 2. 构建计算环境
Map<String, Object> env = new HashMap<>(request.getParams());
env.put("log", Math::log); // 注入数学函数
// 3. 执行计算
Object result = exp.execute(env);
// 4. 结果转换
if (result instanceof Number) {
return new BigDecimal(result.toString()).setScale(2, RoundingMode.HALF_UP);
}
throw new BusinessException("计算结果类型异常");
}
}
五、控制层实现
@RestController
@RequestMapping("/api/cost")
@RequiredArgsConstructor
public class CostController {
private final CostCalculatorService calculator;
@PostMapping("/calculate")
public ApiResponse<BigDecimal> calculate(@Valid @RequestBody CostCalculateRequest request) {
try {
BigDecimal result = calculator.calculateCost(request);
return ApiResponse.success(result);
} catch (BusinessException e) {
return new ApiResponse<>(400, e.getMessage(), null);
}
}
}
六、规则文件示例
创建 resources/rules/cost/hardware.av
:
## 硬件成本计算规则
let {
quantity = params.quantity,
unit_price = params.unitPrice,
discount = params.discount ?: 1.0 # 默认不打折
};
# 计算逻辑:数量*单价*折扣 + 固定运输成本
total = quantity * unit_price * discount + 500;
# 返回结果
return total;
七、异常处理增强
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ApiResponse<Void> handleBusinessException(BusinessException ex) {
return new ApiResponse<>(400, ex.getMessage(), null);
}
@ExceptionHandler({ExpressionSyntaxErrorException.class, ExpressionRuntimeException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ApiResponse<Void> handleAviatorException(Exception ex) {
return new ApiResponse<>(400, "规则执行错误: " + ex.getMessage(), null);
}
}
八、安全增强配置
在 application.yml
中添加:
yaml复制aviator:
security:
max-loop-count: 10000 # 防止死循环
forbidden-classes: # 禁用危险类
- java.lang.System
- java.lang.Runtime
max-expression-length: 5000 # 限制表达式长度
九、性能优化建议
- 表达式缓存:利用Aviator内置的编译缓存(已通过
compile
方法的第二个参数启用) - 预热加载:在应用启动时预加载高频规则
java复制@EventListener(ApplicationReadyEvent.class)
public void preloadHotRules() {
List<String> hotRules = Arrays.asList("hardware", "software");
hotRules.forEach(rule ->
calculator.calculateCost(new CostCalculateRequest(rule, new HashMap<>())));
}
- 监控埋点:通过Spring Actuator暴露指标
@Bean
public MeterRegistryCustomizer<MeterRegistry> aviatorMetrics() {
return registry ->
registry.config().commonTags("module", "cost-calculation");
}
十、验证测试用例
@SpringBootTest
public class CostCalculatorTest {
@Autowired
private CostCalculatorService calculator;
@Test
void testHardwareCost() {
Map<String, Object> params = Map.of(
"quantity", 100,
"unitPrice", 250.5,
"discount", 0.9
);
BigDecimal result = calculator.calculateCost(
new CostCalculateRequest("hardware", params));
assertEquals(new BigDecimal("23045.00"), result);
}
}