Spring Boot项目集成Aviator实现成本计算模块

发布于:2025-05-08 ⋅ 阅读:(29) ⋅ 点赞:(0)

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  # 限制表达式长度 

九、性能优化建议

  1. 表达式缓存:利用Aviator内置的编译缓存(已通过compile方法的第二个参数启用)
  2. 预热加载:在应用启动时预加载高频规则
java复制@EventListener(ApplicationReadyEvent.class) 
public void preloadHotRules() {
    List<String> hotRules = Arrays.asList("hardware",  "software");
    hotRules.forEach(rule  -> 
        calculator.calculateCost(new  CostCalculateRequest(rule, new HashMap<>())));
}
  1. 监控埋点:通过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);
    }
}