Java + Groovy计费引擎详解

Java + Groovy计费引擎详解:从入门到实战

前言

计费引擎是电信、金融、SaaS等行业的核心系统之一,需要处理复杂多变的计费规则。传统的Java硬编码方式难以应对频繁变化的业务规则,而Groovy作为JVM上的动态脚本语言,与Java无缝集成,可以实现规则的热加载和动态配置。本文将深入讲解如何使用Java + Groovy构建一个灵活、高性能的计费引擎。

一、计费引擎概述

1.1 什么是计费引擎

计费引擎(Billing Engine)是一个负责根据业务规则计算费用的核心组件,广泛应用于:

复制代码
计费引擎应用场景
┌─────────────────────────────────────────┐
│  电信行业                                │
│  - 通话计费                              │
│  - 流量计费                              │
│  - 套餐计费                              │
├─────────────────────────────────────────┤
│  金融行业                                │
│  - 手续费计算                            │
│  - 利息计算                              │
│  - 分期计费                              │
├─────────────────────────────────────────┤
│  云计算/SaaS                             │
│  - 按量计费                              │
│  - 订阅计费                              │
│  - 资源使用计费                          │
├─────────────────────────────────────────┤
│  电商行业                                │
│  - 优惠计算                              │
│  - 运费计算                              │
│  - 会员折扣                              │
└─────────────────────────────────────────┘

1.2 为什么选择Java + Groovy

复制代码
Java + Groovy优势
┌─────────────────────────────────────────┐
│  Java优势                                │
│  ✓ 高性能、稳定可靠                      │
│  ✓ 丰富的生态系统                        │
│  ✓ 强类型、编译期检查                    │
│  ✓ 企业级框架支持                        │
├─────────────────────────────────────────┤
│  Groovy优势                              │
│  ✓ 动态语言、灵活简洁                    │
│  ✓ 与Java无缝集成                        │
│  ✓ 支持脚本热加载                        │
│  ✓ DSL友好、易读易写                     │
├─────────────────────────────────────────┤
│  组合优势                                │
│  ✓ Java负责框架和核心逻辑                │
│  ✓ Groovy负责动态规则和配置              │
│  ✓ 规则变更无需重启服务                  │
│  ✓ 业务人员可直接编写规则                │
└─────────────────────────────────────────┘

1.3 计费引擎架构

markdown 复制代码
计费引擎架构图
┌─────────────────────────────────────────┐
│          业务层(API)                   │
│  - 计费请求接口                          │
│  - 规则管理接口                          │
└──────────────┬──────────────────────────┘
               ▼
┌─────────────────────────────────────────┐
│        计费引擎核心(Java)              │
│  ┌───────────────────────────────────┐ │
│  │  规则加载器                        │ │
│  │  - 从数据库加载规则                │ │
│  │  - Groovy脚本编译                  │ │
│  │  - 规则缓存管理                    │ │
│  └───────────────────────────────────┘ │
│  ┌───────────────────────────────────┐ │
│  │  计费执行器                        │ │
│  │  - 规则匹配                        │ │
│  │  - 计费计算                        │ │
│  │  - 结果聚合                        │ │
│  └───────────────────────────────────┘ │
└──────────────┬──────────────────────────┘
               ▼
┌─────────────────────────────────────────┐
│      规则层(Groovy脚本)                │
│  - 基础规则                              │
│  - 套餐规则                              │
│  - 优惠规则                              │
│  - 阶梯计费规则                          │
└──────────────┬──────────────────────────┘
               ▼
┌─────────────────────────────────────────┐
│          数据层                          │
│  - 规则配置表                            │
│  - 计费记录表                            │
│  - 用户套餐表                            │
└─────────────────────────────────────────┘

二、环境搭建

2.1 Maven依赖

xml 复制代码
<dependencies>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Groovy -->
    <dependency>
        <groupId>org.codehaus.groovy</groupId>
        <artifactId>groovy-all</artifactId>
        <version>3.0.9</version>
        <type>pom</type>
    </dependency>

    <!-- Groovy模板引擎 -->
    <dependency>
        <groupId>org.codehaus.groovy</groupId>
        <artifactId>groovy-templates</artifactId>
        <version>3.0.9</version>
    </dependency>

    <!-- MyBatis Plus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.3</version>
    </dependency>

    <!-- Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

    <!-- MySQL -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

2.2 项目结构

bash 复制代码
billing-engine/
├── src/main/java/
│   └── com/example/billing/
│       ├── BillingApplication.java
│       ├── controller/
│       │   ├── BillingController.java
│       │   └── RuleController.java
│       ├── service/
│       │   ├── BillingService.java
│       │   └── RuleService.java
│       ├── engine/
│       │   ├── BillingEngine.java
│       │   ├── RuleLoader.java
│       │   └── RuleExecutor.java
│       ├── model/
│       │   ├── BillingRequest.java
│       │   ├── BillingResult.java
│       │   └── Rule.java
│       └── config/
│           └── GroovyConfig.java
├── src/main/resources/
│   ├── application.yml
│   └── rules/               # Groovy规则脚本目录
│       ├── base_rule.groovy
│       ├── package_rule.groovy
│       └── discount_rule.groovy
└── pom.xml

2.3 配置文件

yaml 复制代码
# application.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/billing?useSSL=false&serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

  redis:
    host: localhost
    port: 6379

# 计费引擎配置
billing:
  rule:
    path: classpath:rules/          # Groovy脚本路径
    cache-enabled: true              # 启用规则缓存
    cache-ttl: 3600                  # 缓存过期时间(秒)
    hot-reload: true                 # 热加载开关

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

三、核心组件实现

3.1 数据模型

java 复制代码
/**
 * 计费请求
 */
@Data
@Builder
public class BillingRequest {
    /** 用户ID */
    private Long userId;

    /** 业务类型(通话、流量、短信等) */
    private String serviceType;

    /** 使用量 */
    private BigDecimal usage;

    /** 使用时长(秒) */
    private Long duration;

    /** 计费时间 */
    private LocalDateTime billingTime;

    /** 扩展参数 */
    private Map<String, Object> extraParams;
}

/**
 * 计费结果
 */
@Data
@Builder
public class BillingResult {
    /** 用户ID */
    private Long userId;

    /** 原始费用 */
    private BigDecimal originalFee;

    /** 折扣金额 */
    private BigDecimal discountAmount;

    /** 最终费用 */
    private BigDecimal finalFee;

    /** 应用的规则列表 */
    private List<String> appliedRules;

    /** 计费明细 */
    private List<BillingDetail> details;

    /** 计费时间 */
    private LocalDateTime billingTime;
}

/**
 * 计费明细
 */
@Data
@AllArgsConstructor
public class BillingDetail {
    /** 规则名称 */
    private String ruleName;

    /** 描述 */
    private String description;

    /** 金额 */
    private BigDecimal amount;
}

/**
 * 计费规则实体
 */
@Data
@TableName("billing_rule")
public class Rule {
    @TableId(type = IdType.AUTO)
    private Long id;

    /** 规则名称 */
    private String ruleName;

    /** 规则类型(BASE/PACKAGE/DISCOUNT/LADDER) */
    private String ruleType;

    /** 业务类型 */
    private String serviceType;

    /** 规则脚本(Groovy) */
    private String ruleScript;

    /** 优先级(数字越大优先级越高) */
    private Integer priority;

    /** 是否启用 */
    private Boolean enabled;

    /** 生效时间 */
    private LocalDateTime effectiveTime;

    /** 失效时间 */
    private LocalDateTime expiryTime;

    /** 创建时间 */
    private LocalDateTime createdAt;

    /** 更新时间 */
    private LocalDateTime updatedAt;
}

/**
 * 用户套餐
 */
@Data
@TableName("user_package")
public class UserPackage {
    @TableId(type = IdType.AUTO)
    private Long id;

    /** 用户ID */
    private Long userId;

    /** 套餐类型 */
    private String packageType;

    /** 套餐名称 */
    private String packageName;

    /** 剩余量 */
    private BigDecimal remainingQuota;

    /** 套餐总量 */
    private BigDecimal totalQuota;

    /** 生效时间 */
    private LocalDateTime effectiveTime;

    /** 失效时间 */
    private LocalDateTime expiryTime;
}

3.2 Groovy配置

java 复制代码
/**
 * Groovy配置类
 */
@Configuration
public class GroovyConfig {

    /**
     * 配置GroovyShell
     */
    @Bean
    public GroovyShell groovyShell() {
        CompilerConfiguration config = new CompilerConfiguration();
        config.setScriptBaseClass(DelegatingScript.class.getName());

        return new GroovyShell(
            Thread.currentThread().getContextClassLoader(),
            new Binding(),
            config
        );
    }

    /**
     * 配置GroovyClassLoader
     */
    @Bean
    public GroovyClassLoader groovyClassLoader() {
        CompilerConfiguration config = new CompilerConfiguration();
        config.setSourceEncoding("UTF-8");

        return new GroovyClassLoader(
            Thread.currentThread().getContextClassLoader(),
            config
        );
    }
}

3.3 规则加载器

java 复制代码
/**
 * 规则加载器
 */
@Component
@Slf4j
public class RuleLoader {

    @Autowired
    private GroovyShell groovyShell;

    @Autowired
    private GroovyClassLoader groovyClassLoader;

    @Autowired
    private RuleMapper ruleMapper;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Value("${billing.rule.cache-enabled:true}")
    private boolean cacheEnabled;

    @Value("${billing.rule.cache-ttl:3600}")
    private long cacheTtl;

    private static final String RULE_CACHE_PREFIX = "billing:rule:";

    /**
     * 加载规则脚本
     */
    public Script loadRule(Long ruleId) {
        // 从缓存获取
        if (cacheEnabled) {
            Script cached = getCachedRule(ruleId);
            if (cached != null) {
                return cached;
            }
        }

        // 从数据库加载
        Rule rule = ruleMapper.selectById(ruleId);
        if (rule == null || !rule.getEnabled()) {
            throw new RuntimeException("规则不存在或已禁用: " + ruleId);
        }

        // 编译Groovy脚本
        Script script = compileScript(rule.getRuleScript());

        // 缓存规则
        if (cacheEnabled) {
            cacheRule(ruleId, script);
        }

        return script;
    }

    /**
     * 加载指定类型的所有规则
     */
    public List<Script> loadRulesByType(String serviceType, String ruleType) {
        QueryWrapper<Rule> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("service_type", serviceType)
                   .eq("rule_type", ruleType)
                   .eq("enabled", true)
                   .le("effective_time", LocalDateTime.now())
                   .ge("expiry_time", LocalDateTime.now())
                   .orderByDesc("priority");

        List<Rule> rules = ruleMapper.selectList(queryWrapper);

        return rules.stream()
                   .map(rule -> compileScript(rule.getRuleScript()))
                   .collect(Collectors.toList());
    }

    /**
     * 编译Groovy脚本
     */
    private Script compileScript(String scriptText) {
        try {
            return groovyShell.parse(scriptText);
        } catch (Exception e) {
            log.error("Groovy脚本编译失败", e);
            throw new RuntimeException("脚本编译失败: " + e.getMessage());
        }
    }

    /**
     * 从缓存获取规则
     */
    private Script getCachedRule(Long ruleId) {
        String cacheKey = RULE_CACHE_PREFIX + ruleId;
        return (Script) redisTemplate.opsForValue().get(cacheKey);
    }

    /**
     * 缓存规则
     */
    private void cacheRule(Long ruleId, Script script) {
        String cacheKey = RULE_CACHE_PREFIX + ruleId;
        redisTemplate.opsForValue().set(
            cacheKey,
            script,
            cacheTtl,
            TimeUnit.SECONDS
        );
    }

    /**
     * 清除规则缓存
     */
    public void clearRuleCache(Long ruleId) {
        String cacheKey = RULE_CACHE_PREFIX + ruleId;
        redisTemplate.delete(cacheKey);
    }

    /**
     * 清除所有规则缓存
     */
    public void clearAllRuleCache() {
        Set<String> keys = redisTemplate.keys(RULE_CACHE_PREFIX + "*");
        if (keys != null && !keys.isEmpty()) {
            redisTemplate.delete(keys);
        }
    }
}

3.4 规则执行器

java 复制代码
/**
 * 规则执行器
 */
@Component
@Slf4j
public class RuleExecutor {

    @Autowired
    private RuleLoader ruleLoader;

    /**
     * 执行单个规则
     */
    public Object executeRule(Script script, Map<String, Object> context) {
        try {
            // 设置上下文变量
            Binding binding = new Binding(context);
            script.setBinding(binding);

            // 执行脚本
            Object result = script.run();

            log.debug("规则执行成功,结果: {}", result);
            return result;

        } catch (Exception e) {
            log.error("规则执行失败", e);
            throw new RuntimeException("规则执行失败: " + e.getMessage());
        }
    }

    /**
     * 执行多个规则并聚合结果
     */
    public List<Object> executeRules(List<Script> scripts, Map<String, Object> context) {
        return scripts.stream()
                     .map(script -> executeRule(script, context))
                     .collect(Collectors.toList());
    }

    /**
     * 执行规则链(规则间有依赖)
     */
    public Object executeRuleChain(List<Script> scripts, Map<String, Object> context) {
        Object result = null;

        for (Script script : scripts) {
            // 将上一个规则的结果放入上下文
            if (result != null) {
                context.put("previousResult", result);
            }

            result = executeRule(script, context);

            // 如果规则返回false,中断执行
            if (result instanceof Boolean && !(Boolean) result) {
                log.debug("规则链中断");
                break;
            }
        }

        return result;
    }
}

3.5 计费引擎核心

java 复制代码
/**
 * 计费引擎核心
 */
@Service
@Slf4j
public class BillingEngine {

    @Autowired
    private RuleLoader ruleLoader;

    @Autowired
    private RuleExecutor ruleExecutor;

    @Autowired
    private UserPackageMapper userPackageMapper;

    /**
     * 执行计费
     */
    public BillingResult calculate(BillingRequest request) {
        log.info("开始计费,用户: {}, 业务类型: {}",
                request.getUserId(), request.getServiceType());

        // 构建计费上下文
        Map<String, Object> context = buildContext(request);

        // 加载规则
        List<Script> rules = ruleLoader.loadRulesByType(
            request.getServiceType(),
            null
        );

        if (rules.isEmpty()) {
            log.warn("未找到适用的计费规则");
            return createDefaultResult(request);
        }

        // 初始化计费结果
        BillingResult result = BillingResult.builder()
            .userId(request.getUserId())
            .originalFee(BigDecimal.ZERO)
            .discountAmount(BigDecimal.ZERO)
            .finalFee(BigDecimal.ZERO)
            .appliedRules(new ArrayList<>())
            .details(new ArrayList<>())
            .billingTime(LocalDateTime.now())
            .build();

        // 执行规则
        for (Script rule : rules) {
            try {
                context.put("result", result);
                ruleExecutor.executeRule(rule, context);
            } catch (Exception e) {
                log.error("规则执行失败", e);
            }
        }

        // 计算最终费用
        result.setFinalFee(
            result.getOriginalFee().subtract(result.getDiscountAmount())
        );

        log.info("计费完成,最终费用: {}", result.getFinalFee());
        return result;
    }

    /**
     * 构建计费上下文
     */
    private Map<String, Object> buildContext(BillingRequest request) {
        Map<String, Object> context = new HashMap<>();

        // 基础信息
        context.put("userId", request.getUserId());
        context.put("serviceType", request.getServiceType());
        context.put("usage", request.getUsage());
        context.put("duration", request.getDuration());
        context.put("billingTime", request.getBillingTime());

        // 扩展参数
        if (request.getExtraParams() != null) {
            context.putAll(request.getExtraParams());
        }

        // 查询用户套餐
        QueryWrapper<UserPackage> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id", request.getUserId())
                   .eq("package_type", request.getServiceType())
                   .le("effective_time", LocalDateTime.now())
                   .ge("expiry_time", LocalDateTime.now());

        UserPackage userPackage = userPackageMapper.selectOne(queryWrapper);
        context.put("userPackage", userPackage);

        // 工具方法
        context.put("log", log);
        context.put("Math", Math.class);
        context.put("BigDecimal", BigDecimal.class);

        return context;
    }

    /**
     * 创建默认结果
     */
    private BillingResult createDefaultResult(BillingRequest request) {
        return BillingResult.builder()
            .userId(request.getUserId())
            .originalFee(BigDecimal.ZERO)
            .discountAmount(BigDecimal.ZERO)
            .finalFee(BigDecimal.ZERO)
            .appliedRules(new ArrayList<>())
            .details(new ArrayList<>())
            .billingTime(LocalDateTime.now())
            .build();
    }
}

四、Groovy规则脚本

4.1 基础计费规则

groovy 复制代码
/**
 * 基础通话计费规则
 * 文件: base_call_rule.groovy
 */
import java.math.BigDecimal
import java.math.RoundingMode

// 获取上下文变量
def duration = duration as Long
def result = result

// 计费单价(元/分钟)
def unitPrice = new BigDecimal("0.15")

// 计算通话分钟数(不足1分钟按1分钟计算)
def minutes = (duration + 59) / 60

// 计算费用
def fee = unitPrice.multiply(new BigDecimal(minutes))
    .setScale(2, RoundingMode.HALF_UP)

// 更新结果
result.originalFee = result.originalFee.add(fee)
result.appliedRules.add("基础通话计费")
result.details.add(
    new com.example.billing.model.BillingDetail(
        "基础通话计费",
        "${minutes}分钟 × ${unitPrice}元/分钟",
        fee
    )
)

log.info("基础计费: ${fee}元")

return true

4.2 套餐优惠规则

groovy 复制代码
/**
 * 套餐优惠规则
 * 文件: package_discount_rule.groovy
 */
import java.math.BigDecimal

// 获取用户套餐
def userPackage = userPackage

if (userPackage == null) {
    log.info("用户无套餐,跳过套餐优惠")
    return true
}

// 检查套餐余量
def usage = usage as BigDecimal
def remaining = userPackage.remainingQuota

if (remaining.compareTo(BigDecimal.ZERO) <= 0) {
    log.info("套餐余量已用完")
    return true
}

// 计算套餐内使用量
def packageUsage = usage.min(remaining)
def overageUsage = usage.subtract(packageUsage)

// 套餐内免费
if (packageUsage.compareTo(BigDecimal.ZERO) > 0) {
    result.appliedRules.add("套餐内免费")
    result.details.add(
        new com.example.billing.model.BillingDetail(
            "套餐内免费",
            "套餐内使用${packageUsage}分钟",
            BigDecimal.ZERO
        )
    )

    // 更新套餐余量
    userPackage.remainingQuota = remaining.subtract(packageUsage)
}

// 超出套餐部分
if (overageUsage.compareTo(BigDecimal.ZERO) > 0) {
    def overagePrice = new BigDecimal("0.20")
    def overageFee = overageUsage.multiply(overagePrice)

    result.originalFee = result.originalFee.add(overageFee)
    result.details.add(
        new com.example.billing.model.BillingDetail(
            "套餐外计费",
            "超出${overageUsage}分钟 × ${overagePrice}元/分钟",
            overageFee
        )
    )
}

return true

4.3 阶梯计费规则

groovy 复制代码
/**
 * 阶梯计费规则
 * 文件: ladder_billing_rule.groovy
 */
import java.math.BigDecimal
import java.math.RoundingMode

// 阶梯定义
def ladders = [
    [limit: 100, price: 0.10],   // 0-100分钟: 0.10元/分钟
    [limit: 300, price: 0.08],   // 101-300分钟: 0.08元/分钟
    [limit: 500, price: 0.06],   // 301-500分钟: 0.06元/分钟
    [limit: null, price: 0.05]   // 500分钟以上: 0.05元/分钟
]

def usage = usage as BigDecimal
def totalFee = BigDecimal.ZERO
def remainingUsage = usage
def usedInLadder = BigDecimal.ZERO

for (ladder in ladders) {
    if (remainingUsage.compareTo(BigDecimal.ZERO) <= 0) {
        break
    }

    def limit = ladder.limit ? new BigDecimal(ladder.limit) : null
    def price = new BigDecimal(ladder.price as String)

    // 计算当前阶梯的使用量
    if (limit == null) {
        // 最后一个阶梯,无上限
        usedInLadder = remainingUsage
    } else {
        // 当前阶梯剩余量
        def ladderRemaining = limit.subtract(
            usage.subtract(remainingUsage)
        )
        usedInLadder = remainingUsage.min(ladderRemaining)
    }

    // 计算当前阶梯费用
    def ladderFee = usedInLadder.multiply(price)
        .setScale(2, RoundingMode.HALF_UP)

    totalFee = totalFee.add(ladderFee)
    remainingUsage = remainingUsage.subtract(usedInLadder)

    result.details.add(
        new com.example.billing.model.BillingDetail(
            "阶梯${ladders.indexOf(ladder) + 1}",
            "${usedInLadder}分钟 × ${price}元/分钟",
            ladderFee
        )
    )
}

result.originalFee = result.originalFee.add(totalFee)
result.appliedRules.add("阶梯计费")

log.info("阶梯计费总额: ${totalFee}元")

return true

4.4 时段优惠规则

groovy 复制代码
/**
 * 时段优惠规则
 * 文件: time_discount_rule.groovy
 */
import java.time.LocalTime

// 获取计费时间
def billingTime = billingTime
def hour = billingTime.hour

// 定义优惠时段和折扣
def timeRanges = [
    [start: 0, end: 6, discount: 0.5, name: "深夜半价"],
    [start: 22, end: 24, discount: 0.7, name: "夜间7折"]
]

// 检查当前时间是否在优惠时段
for (timeRange in timeRanges) {
    if (hour >= timeRange.start && hour < timeRange.end) {
        def discount = new BigDecimal(timeRange.discount as String)
        def discountAmount = result.originalFee
            .multiply(BigDecimal.ONE.subtract(discount))

        result.discountAmount = result.discountAmount.add(discountAmount)
        result.appliedRules.add(timeRange.name)
        result.details.add(
            new com.example.billing.model.BillingDetail(
                timeRange.name,
                "优惠${(1 - timeRange.discount) * 100}%",
                discountAmount.negate()
            )
        )

        log.info("应用时段优惠: ${timeRange.name}, 优惠${discountAmount}元")
        break
    }
}

return true

4.5 会员折扣规则

groovy 复制代码
/**
 * 会员折扣规则
 * 文件: vip_discount_rule.groovy
 */
import java.math.BigDecimal

// 获取用户会员等级
def vipLevel = extraParams?.vipLevel ?: "NORMAL"

// 会员折扣配置
def vipDiscounts = [
    NORMAL: 1.0,     // 普通用户无折扣
    SILVER: 0.95,    // 银卡95折
    GOLD: 0.90,      // 金卡9折
    PLATINUM: 0.85,  // 白金卡85折
    DIAMOND: 0.80    // 钻石卡8折
]

def discountRate = new BigDecimal(
    vipDiscounts[vipLevel]?.toString() ?: "1.0"
)

// 计算折扣金额
if (discountRate.compareTo(BigDecimal.ONE) < 0) {
    def discountAmount = result.originalFee
        .multiply(BigDecimal.ONE.subtract(discountRate))

    result.discountAmount = result.discountAmount.add(discountAmount)
    result.appliedRules.add("${vipLevel}会员折扣")
    result.details.add(
        new com.example.billing.model.BillingDetail(
            "${vipLevel}会员折扣",
            "${(1 - discountRate) * 100}%优惠",
            discountAmount.negate()
        )
    )

    log.info("应用会员折扣: ${vipLevel}, 折扣率${discountRate}")
}

return true

五、服务层实现

5.1 计费服务

java 复制代码
/**
 * 计费服务
 */
@Service
@Slf4j
public class BillingService {

    @Autowired
    private BillingEngine billingEngine;

    @Autowired
    private BillingRecordMapper billingRecordMapper;

    /**
     * 执行计费
     */
    @Transactional(rollbackFor = Exception.class)
    public BillingResult doBilling(BillingRequest request) {
        // 参数校验
        validateRequest(request);

        // 执行计费
        BillingResult result = billingEngine.calculate(request);

        // 保存计费记录
        saveBillingRecord(request, result);

        return result;
    }

    /**
     * 批量计费
     */
    public List<BillingResult> batchBilling(List<BillingRequest> requests) {
        return requests.stream()
                      .map(this::doBilling)
                      .collect(Collectors.toList());
    }

    /**
     * 异步计费
     */
    @Async
    public CompletableFuture<BillingResult> asyncBilling(BillingRequest request) {
        return CompletableFuture.completedFuture(doBilling(request));
    }

    /**
     * 试算(不保存记录)
     */
    public BillingResult trialBilling(BillingRequest request) {
        validateRequest(request);
        return billingEngine.calculate(request);
    }

    /**
     * 参数校验
     */
    private void validateRequest(BillingRequest request) {
        if (request.getUserId() == null) {
            throw new IllegalArgumentException("用户ID不能为空");
        }
        if (request.getServiceType() == null) {
            throw new IllegalArgumentException("业务类型不能为空");
        }
        if (request.getUsage() == null ||
            request.getUsage().compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("使用量必须大于0");
        }
    }

    /**
     * 保存计费记录
     */
    private void saveBillingRecord(BillingRequest request, BillingResult result) {
        BillingRecord record = new BillingRecord();
        record.setUserId(request.getUserId());
        record.setServiceType(request.getServiceType());
        record.setUsage(request.getUsage());
        record.setDuration(request.getDuration());
        record.setOriginalFee(result.getOriginalFee());
        record.setDiscountAmount(result.getDiscountAmount());
        record.setFinalFee(result.getFinalFee());
        record.setAppliedRules(String.join(",", result.getAppliedRules()));
        record.setBillingTime(result.getBillingTime());
        record.setCreatedAt(LocalDateTime.now());

        billingRecordMapper.insert(record);
    }

    /**
     * 查询计费记录
     */
    public Page<BillingRecord> queryBillingRecords(
            Long userId,
            String serviceType,
            LocalDateTime startTime,
            LocalDateTime endTime,
            int page,
            int size) {

        Page<BillingRecord> pageParam = new Page<>(page, size);
        QueryWrapper<BillingRecord> queryWrapper = new QueryWrapper<>();

        if (userId != null) {
            queryWrapper.eq("user_id", userId);
        }
        if (serviceType != null) {
            queryWrapper.eq("service_type", serviceType);
        }
        if (startTime != null) {
            queryWrapper.ge("billing_time", startTime);
        }
        if (endTime != null) {
            queryWrapper.le("billing_time", endTime);
        }

        queryWrapper.orderByDesc("billing_time");

        return billingRecordMapper.selectPage(pageParam, queryWrapper);
    }

    /**
     * 统计用户消费
     */
    public Map<String, Object> statisticsUserConsumption(
            Long userId,
            LocalDateTime startTime,
            LocalDateTime endTime) {

        QueryWrapper<BillingRecord> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id", userId)
                   .ge("billing_time", startTime)
                   .le("billing_time", endTime);

        List<BillingRecord> records = billingRecordMapper.selectList(queryWrapper);

        BigDecimal totalFee = records.stream()
            .map(BillingRecord::getFinalFee)
            .reduce(BigDecimal.ZERO, BigDecimal::add);

        Map<String, BigDecimal> feeByType = records.stream()
            .collect(Collectors.groupingBy(
                BillingRecord::getServiceType,
                Collectors.reducing(
                    BigDecimal.ZERO,
                    BillingRecord::getFinalFee,
                    BigDecimal::add
                )
            ));

        Map<String, Object> result = new HashMap<>();
        result.put("totalFee", totalFee);
        result.put("totalCount", records.size());
        result.put("feeByType", feeByType);

        return result;
    }
}

5.2 规则服务

java 复制代码
/**
 * 规则管理服务
 */
@Service
@Slf4j
public class RuleService {

    @Autowired
    private RuleMapper ruleMapper;

    @Autowired
    private RuleLoader ruleLoader;

    /**
     * 创建规则
     */
    @Transactional(rollbackFor = Exception.class)
    public Rule createRule(Rule rule) {
        // 校验规则脚本
        validateRuleScript(rule.getRuleScript());

        rule.setCreatedAt(LocalDateTime.now());
        rule.setUpdatedAt(LocalDateTime.now());

        ruleMapper.insert(rule);

        log.info("规则创建成功: {}", rule.getRuleName());
        return rule;
    }

    /**
     * 更新规则
     */
    @Transactional(rollbackFor = Exception.class)
    public Rule updateRule(Long ruleId, Rule rule) {
        Rule existing = ruleMapper.selectById(ruleId);
        if (existing == null) {
            throw new RuntimeException("规则不存在: " + ruleId);
        }

        // 校验规则脚本
        if (rule.getRuleScript() != null) {
            validateRuleScript(rule.getRuleScript());
        }

        rule.setId(ruleId);
        rule.setUpdatedAt(LocalDateTime.now());
        ruleMapper.updateById(rule);

        // 清除缓存
        ruleLoader.clearRuleCache(ruleId);

        log.info("规则更新成功: {}", ruleId);
        return ruleMapper.selectById(ruleId);
    }

    /**
     * 删除规则
     */
    @Transactional(rollbackFor = Exception.class)
    public void deleteRule(Long ruleId) {
        ruleMapper.deleteById(ruleId);
        ruleLoader.clearRuleCache(ruleId);

        log.info("规则删除成功: {}", ruleId);
    }

    /**
     * 启用/禁用规则
     */
    @Transactional(rollbackFor = Exception.class)
    public void toggleRule(Long ruleId, boolean enabled) {
        Rule rule = new Rule();
        rule.setId(ruleId);
        rule.setEnabled(enabled);
        rule.setUpdatedAt(LocalDateTime.now());

        ruleMapper.updateById(rule);
        ruleLoader.clearRuleCache(ruleId);

        log.info("规则{}成功: {}", enabled ? "启用" : "禁用", ruleId);
    }

    /**
     * 查询规则列表
     */
    public Page<Rule> queryRules(
            String serviceType,
            String ruleType,
            Boolean enabled,
            int page,
            int size) {

        Page<Rule> pageParam = new Page<>(page, size);
        QueryWrapper<Rule> queryWrapper = new QueryWrapper<>();

        if (serviceType != null) {
            queryWrapper.eq("service_type", serviceType);
        }
        if (ruleType != null) {
            queryWrapper.eq("rule_type", ruleType);
        }
        if (enabled != null) {
            queryWrapper.eq("enabled", enabled);
        }

        queryWrapper.orderByDesc("priority", "created_at");

        return ruleMapper.selectPage(pageParam, queryWrapper);
    }

    /**
     * 测试规则
     */
    public Object testRule(String ruleScript, Map<String, Object> context) {
        validateRuleScript(ruleScript);

        try {
            GroovyShell shell = new GroovyShell();
            Script script = shell.parse(ruleScript);

            Binding binding = new Binding(context);
            script.setBinding(binding);

            return script.run();

        } catch (Exception e) {
            log.error("规则测试失败", e);
            throw new RuntimeException("规则测试失败: " + e.getMessage());
        }
    }

    /**
     * 校验规则脚本
     */
    private void validateRuleScript(String ruleScript) {
        if (ruleScript == null || ruleScript.trim().isEmpty()) {
            throw new IllegalArgumentException("规则脚本不能为空");
        }

        try {
            GroovyShell shell = new GroovyShell();
            shell.parse(ruleScript);
        } catch (Exception e) {
            throw new IllegalArgumentException("规则脚本语法错误: " + e.getMessage());
        }
    }

    /**
     * 导入规则
     */
    @Transactional(rollbackFor = Exception.class)
    public void importRules(List<Rule> rules) {
        for (Rule rule : rules) {
            validateRuleScript(rule.getRuleScript());
            rule.setCreatedAt(LocalDateTime.now());
            rule.setUpdatedAt(LocalDateTime.now());
            ruleMapper.insert(rule);
        }

        log.info("规则导入成功,数量: {}", rules.size());
    }

    /**
     * 导出规则
     */
    public List<Rule> exportRules(String serviceType, String ruleType) {
        QueryWrapper<Rule> queryWrapper = new QueryWrapper<>();

        if (serviceType != null) {
            queryWrapper.eq("service_type", serviceType);
        }
        if (ruleType != null) {
            queryWrapper.eq("rule_type", ruleType);
        }

        return ruleMapper.selectList(queryWrapper);
    }
}

六、控制器层

6.1 计费控制器

java 复制代码
/**
 * 计费控制器
 */
@RestController
@RequestMapping("/api/billing")
@Slf4j
public class BillingController {

    @Autowired
    private BillingService billingService;

    /**
     * 执行计费
     */
    @PostMapping("/calculate")
    public Result<BillingResult> calculate(@RequestBody BillingRequest request) {
        try {
            BillingResult result = billingService.doBilling(request);
            return Result.success(result);
        } catch (Exception e) {
            log.error("计费失败", e);
            return Result.error("计费失败: " + e.getMessage());
        }
    }

    /**
     * 批量计费
     */
    @PostMapping("/batch-calculate")
    public Result<List<BillingResult>> batchCalculate(
            @RequestBody List<BillingRequest> requests) {

        try {
            List<BillingResult> results = billingService.batchBilling(requests);
            return Result.success(results);
        } catch (Exception e) {
            log.error("批量计费失败", e);
            return Result.error("批量计费失败: " + e.getMessage());
        }
    }

    /**
     * 试算
     */
    @PostMapping("/trial")
    public Result<BillingResult> trial(@RequestBody BillingRequest request) {
        try {
            BillingResult result = billingService.trialBilling(request);
            return Result.success(result);
        } catch (Exception e) {
            log.error("试算失败", e);
            return Result.error("试算失败: " + e.getMessage());
        }
    }

    /**
     * 查询计费记录
     */
    @GetMapping("/records")
    public Result<Page<BillingRecord>> queryRecords(
            @RequestParam(required = false) Long userId,
            @RequestParam(required = false) String serviceType,
            @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
                LocalDateTime startTime,
            @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
                LocalDateTime endTime,
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size) {

        Page<BillingRecord> records = billingService.queryBillingRecords(
            userId, serviceType, startTime, endTime, page, size
        );

        return Result.success(records);
    }

    /**
     * 统计用户消费
     */
    @GetMapping("/statistics/{userId}")
    public Result<Map<String, Object>> statistics(
            @PathVariable Long userId,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
                LocalDateTime startTime,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
                LocalDateTime endTime) {

        Map<String, Object> stats = billingService.statisticsUserConsumption(
            userId, startTime, endTime
        );

        return Result.success(stats);
    }
}

6.2 规则控制器

java 复制代码
/**
 * 规则管理控制器
 */
@RestController
@RequestMapping("/api/rules")
@Slf4j
public class RuleController {

    @Autowired
    private RuleService ruleService;

    /**
     * 创建规则
     */
    @PostMapping
    public Result<Rule> createRule(@RequestBody Rule rule) {
        try {
            Rule created = ruleService.createRule(rule);
            return Result.success(created);
        } catch (Exception e) {
            log.error("创建规则失败", e);
            return Result.error("创建规则失败: " + e.getMessage());
        }
    }

    /**
     * 更新规则
     */
    @PutMapping("/{id}")
    public Result<Rule> updateRule(
            @PathVariable Long id,
            @RequestBody Rule rule) {

        try {
            Rule updated = ruleService.updateRule(id, rule);
            return Result.success(updated);
        } catch (Exception e) {
            log.error("更新规则失败", e);
            return Result.error("更新规则失败: " + e.getMessage());
        }
    }

    /**
     * 删除规则
     */
    @DeleteMapping("/{id}")
    public Result<Void> deleteRule(@PathVariable Long id) {
        try {
            ruleService.deleteRule(id);
            return Result.success(null);
        } catch (Exception e) {
            log.error("删除规则失败", e);
            return Result.error("删除规则失败: " + e.getMessage());
        }
    }

    /**
     * 启用/禁用规则
     */
    @PutMapping("/{id}/toggle")
    public Result<Void> toggleRule(
            @PathVariable Long id,
            @RequestParam boolean enabled) {

        try {
            ruleService.toggleRule(id, enabled);
            return Result.success(null);
        } catch (Exception e) {
            log.error("切换规则状态失败", e);
            return Result.error("切换规则状态失败: " + e.getMessage());
        }
    }

    /**
     * 查询规则列表
     */
    @GetMapping
    public Result<Page<Rule>> queryRules(
            @RequestParam(required = false) String serviceType,
            @RequestParam(required = false) String ruleType,
            @RequestParam(required = false) Boolean enabled,
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size) {

        Page<Rule> rules = ruleService.queryRules(
            serviceType, ruleType, enabled, page, size
        );

        return Result.success(rules);
    }

    /**
     * 测试规则
     */
    @PostMapping("/test")
    public Result<Object> testRule(@RequestBody Map<String, Object> request) {
        try {
            String ruleScript = (String) request.get("ruleScript");
            @SuppressWarnings("unchecked")
            Map<String, Object> context = (Map<String, Object>) request.get("context");

            Object result = ruleService.testRule(ruleScript, context);
            return Result.success(result);

        } catch (Exception e) {
            log.error("测试规则失败", e);
            return Result.error("测试规则失败: " + e.getMessage());
        }
    }

    /**
     * 导入规则
     */
    @PostMapping("/import")
    public Result<Void> importRules(@RequestBody List<Rule> rules) {
        try {
            ruleService.importRules(rules);
            return Result.success(null);
        } catch (Exception e) {
            log.error("导入规则失败", e);
            return Result.error("导入规则失败: " + e.getMessage());
        }
    }

    /**
     * 导出规则
     */
    @GetMapping("/export")
    public Result<List<Rule>> exportRules(
            @RequestParam(required = false) String serviceType,
            @RequestParam(required = false) String ruleType) {

        List<Rule> rules = ruleService.exportRules(serviceType, ruleType);
        return Result.success(rules);
    }
}

七、实战案例

7.1 案例1:电信通话计费

java 复制代码
/**
 * 电信通话计费场景
 */
@Service
@Slf4j
public class TelecomBillingService {

    @Autowired
    private BillingService billingService;

    /**
     * 处理通话记录并计费
     */
    public BillingResult processCallRecord(CallRecord callRecord) {
        // 构建计费请求
        BillingRequest request = BillingRequest.builder()
            .userId(callRecord.getUserId())
            .serviceType("CALL")
            .duration(callRecord.getDuration())
            .usage(new BigDecimal(callRecord.getDuration() / 60))  // 转换为分钟
            .billingTime(callRecord.getEndTime())
            .extraParams(buildExtraParams(callRecord))
            .build();

        // 执行计费
        return billingService.doBilling(request);
    }

    /**
     * 构建扩展参数
     */
    private Map<String, Object> buildExtraParams(CallRecord callRecord) {
        Map<String, Object> params = new HashMap<>();

        // 通话类型(本地、长途、国际)
        params.put("callType", callRecord.getCallType());

        // 被叫号码
        params.put("calledNumber", callRecord.getCalledNumber());

        // 会员等级
        params.put("vipLevel", callRecord.getVipLevel());

        return params;
    }
}

/**
 * 通话记录
 */
@Data
class CallRecord {
    private Long userId;
    private String callerNumber;
    private String calledNumber;
    private String callType;  // LOCAL/LONG_DISTANCE/INTERNATIONAL
    private LocalDateTime startTime;
    private LocalDateTime endTime;
    private Long duration;    // 秒
    private String vipLevel;
}

对应的Groovy规则

groovy 复制代码
/**
 * 长途通话加价规则
 * 文件: long_distance_call_rule.groovy
 */
import java.math.BigDecimal

def callType = extraParams?.callType

if (callType == "LONG_DISTANCE") {
    // 长途通话加价50%
    def surcharge = result.originalFee.multiply(new BigDecimal("0.5"))

    result.originalFee = result.originalFee.add(surcharge)
    result.appliedRules.add("长途通话加价")
    result.details.add(
        new com.example.billing.model.BillingDetail(
            "长途通话加价",
            "长途附加费50%",
            surcharge
        )
    )

    log.info("应用长途加价: ${surcharge}元")

} else if (callType == "INTERNATIONAL") {
    // 国际通话加价200%
    def surcharge = result.originalFee.multiply(new BigDecimal("2.0"))

    result.originalFee = result.originalFee.add(surcharge)
    result.appliedRules.add("国际通话加价")
    result.details.add(
        new com.example.billing.model.BillingDetail(
            "国际通话加价",
            "国际附加费200%",
            surcharge
        )
    )

    log.info("应用国际加价: ${surcharge}元")
}

return true

7.2 案例2:云服务按量计费

java 复制代码
/**
 * 云服务计费场景
 */
@Service
@Slf4j
public class CloudBillingService {

    @Autowired
    private BillingService billingService;

    /**
     * 计算云资源使用费用
     */
    public BillingResult calculateResourceUsage(ResourceUsage usage) {
        BillingRequest request = BillingRequest.builder()
            .userId(usage.getUserId())
            .serviceType(usage.getResourceType())  // CPU/MEMORY/STORAGE/BANDWIDTH
            .usage(usage.getUsageAmount())
            .billingTime(usage.getUsageTime())
            .extraParams(buildCloudParams(usage))
            .build();

        return billingService.doBilling(request);
    }

    /**
     * 批量计算多种资源
     */
    public List<BillingResult> calculateMultiResources(
            Long userId,
            List<ResourceUsage> usages) {

        return usages.stream()
                    .map(this::calculateResourceUsage)
                    .collect(Collectors.toList());
    }

    private Map<String, Object> buildCloudParams(ResourceUsage usage) {
        Map<String, Object> params = new HashMap<>();
        params.put("region", usage.getRegion());
        params.put("instanceType", usage.getInstanceType());
        params.put("billingMode", usage.getBillingMode());  // HOURLY/DAILY/MONTHLY
        return params;
    }
}

/**
 * 资源使用记录
 */
@Data
class ResourceUsage {
    private Long userId;
    private String resourceType;      // CPU/MEMORY/STORAGE/BANDWIDTH
    private BigDecimal usageAmount;   // 使用量
    private String region;            // 地域
    private String instanceType;      // 实例类型
    private String billingMode;       // 计费模式
    private LocalDateTime usageTime;
}

对应的Groovy规则

groovy 复制代码
/**
 * 云资源阶梯计费规则
 * 文件: cloud_resource_ladder_rule.groovy
 */
import java.math.BigDecimal
import java.math.RoundingMode

def resourceType = serviceType
def usage = usage as BigDecimal
def region = extraParams?.region ?: "cn-beijing"

// 不同资源类型的阶梯定价
def pricingConfig = [
    CPU: [
        [limit: 100, price: 0.50],    // 0-100核时: 0.50元/核时
        [limit: 500, price: 0.45],    // 101-500核时: 0.45元/核时
        [limit: null, price: 0.40]    // 500核时以上: 0.40元/核时
    ],
    MEMORY: [
        [limit: 200, price: 0.25],    // 0-200GB时: 0.25元/GB时
        [limit: 1000, price: 0.22],   // 201-1000GB时: 0.22元/GB时
        [limit: null, price: 0.20]    // 1000GB时以上: 0.20元/GB时
    ],
    STORAGE: [
        [limit: 1000, price: 0.10],   // 0-1TB: 0.10元/GB月
        [limit: 10000, price: 0.08],  // 1-10TB: 0.08元/GB月
        [limit: null, price: 0.06]    // 10TB以上: 0.06元/GB月
    ]
]

def ladders = pricingConfig[resourceType]
if (!ladders) {
    log.warn("未找到资源类型${resourceType}的定价配置")
    return true
}

// 地域价格系数
def regionMultiplier = [
    "cn-beijing": 1.0,
    "cn-shanghai": 1.0,
    "cn-guangzhou": 0.95,
    "us-west": 1.2
][region] ?: 1.0

def totalFee = BigDecimal.ZERO
def remainingUsage = usage

for (ladder in ladders) {
    if (remainingUsage.compareTo(BigDecimal.ZERO) <= 0) {
        break
    }

    def limit = ladder.limit ? new BigDecimal(ladder.limit) : null
    def basePrice = new BigDecimal(ladder.price as String)
    def price = basePrice.multiply(new BigDecimal(regionMultiplier as String))

    def usedInLadder
    if (limit == null) {
        usedInLadder = remainingUsage
    } else {
        def consumed = usage.subtract(remainingUsage)
        def ladderRemaining = limit.subtract(consumed)
        usedInLadder = remainingUsage.min(ladderRemaining)
    }

    def ladderFee = usedInLadder.multiply(price)
        .setScale(2, RoundingMode.HALF_UP)

    totalFee = totalFee.add(ladderFee)
    remainingUsage = remainingUsage.subtract(usedInLadder)

    result.details.add(
        new com.example.billing.model.BillingDetail(
            "${resourceType}阶梯${ladders.indexOf(ladder) + 1}",
            "${usedInLadder} × ${price}元",
            ladderFee
        )
    )
}

result.originalFee = result.originalFee.add(totalFee)
result.appliedRules.add("${resourceType}阶梯计费")

log.info("${resourceType}计费完成,总额: ${totalFee}元")

return true

7.3 案例3:电商优惠券计费

java 复制代码
/**
 * 电商订单计费场景
 */
@Service
@Slf4j
public class OrderBillingService {

    @Autowired
    private BillingService billingService;

    /**
     * 计算订单费用
     */
    public BillingResult calculateOrderFee(Order order) {
        BillingRequest request = BillingRequest.builder()
            .userId(order.getUserId())
            .serviceType("ORDER")
            .usage(order.getTotalAmount())
            .billingTime(order.getOrderTime())
            .extraParams(buildOrderParams(order))
            .build();

        return billingService.doBilling(request);
    }

    private Map<String, Object> buildOrderParams(Order order) {
        Map<String, Object> params = new HashMap<>();
        params.put("orderItems", order.getItems());
        params.put("coupons", order.getCoupons());
        params.put("vipLevel", order.getVipLevel());
        params.put("shippingAddress", order.getShippingAddress());
        return params;
    }
}

/**
 * 订单
 */
@Data
class Order {
    private Long userId;
    private BigDecimal totalAmount;
    private List<OrderItem> items;
    private List<Coupon> coupons;
    private String vipLevel;
    private String shippingAddress;
    private LocalDateTime orderTime;
}

@Data
class OrderItem {
    private Long productId;
    private String productName;
    private BigDecimal price;
    private Integer quantity;
}

@Data
class Coupon {
    private String couponCode;
    private String couponType;  // FIXED/PERCENT
    private BigDecimal discountValue;
    private BigDecimal minAmount;
}

对应的Groovy规则

groovy 复制代码
/**
 * 优惠券计算规则
 * 文件: coupon_discount_rule.groovy
 */
import java.math.BigDecimal
import java.math.RoundingMode

def coupons = extraParams?.coupons ?: []
def totalDiscount = BigDecimal.ZERO

for (coupon in coupons) {
    def minAmount = new BigDecimal(coupon.minAmount?.toString() ?: "0")

    // 检查是否满足最低消费
    if (result.originalFee.compareTo(minAmount) < 0) {
        log.info("优惠券${coupon.couponCode}不满足最低消费条件")
        continue
    }

    def discount = BigDecimal.ZERO

    if (coupon.couponType == "FIXED") {
        // 固定金额优惠券
        discount = new BigDecimal(coupon.discountValue.toString())

    } else if (coupon.couponType == "PERCENT") {
        // 百分比优惠券
        def percent = new BigDecimal(coupon.discountValue.toString())
        discount = result.originalFee
            .multiply(percent)
            .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP)
    }

    totalDiscount = totalDiscount.add(discount)

    result.details.add(
        new com.example.billing.model.BillingDetail(
            "优惠券",
            coupon.couponCode,
            discount.negate()
        )
    )

    log.info("应用优惠券${coupon.couponCode},优惠${discount}元")
}

if (totalDiscount.compareTo(BigDecimal.ZERO) > 0) {
    result.discountAmount = result.discountAmount.add(totalDiscount)
    result.appliedRules.add("优惠券折扣")
}

return true

八、性能优化

8.1 规则缓存优化

java 复制代码
/**
 * 增强的规则加载器(支持多级缓存)
 */
@Component
@Slf4j
public class EnhancedRuleLoader extends RuleLoader {

    // 本地缓存(Caffeine)
    private final Cache<Long, Script> localCache = Caffeine.newBuilder()
        .maximumSize(1000)
        .expireAfterWrite(10, TimeUnit.MINUTES)
        .recordStats()
        .build();

    /**
     * 多级缓存加载
     */
    @Override
    public Script loadRule(Long ruleId) {
        // L1: 本地缓存
        Script cached = localCache.getIfPresent(ruleId);
        if (cached != null) {
            log.debug("从本地缓存加载规则: {}", ruleId);
            return cached;
        }

        // L2: Redis缓存
        cached = super.getCachedRule(ruleId);
        if (cached != null) {
            localCache.put(ruleId, cached);
            log.debug("从Redis缓存加载规则: {}", ruleId);
            return cached;
        }

        // L3: 数据库
        Script script = super.loadRule(ruleId);
        localCache.put(ruleId, script);

        return script;
    }

    /**
     * 清除本地缓存
     */
    public void clearLocalCache() {
        localCache.invalidateAll();
    }

    /**
     * 获取缓存统计
     */
    public CacheStats getCacheStats() {
        return localCache.stats();
    }
}

8.2 并行计费

java 复制代码
/**
 * 并行计费优化
 */
@Service
@Slf4j
public class ParallelBillingService {

    @Autowired
    private BillingService billingService;

    private final ExecutorService executorService =
        Executors.newFixedThreadPool(10);

    /**
     * 并行批量计费
     */
    public List<BillingResult> parallelBatchBilling(List<BillingRequest> requests) {
        List<CompletableFuture<BillingResult>> futures = requests.stream()
            .map(request -> CompletableFuture.supplyAsync(
                () -> billingService.doBilling(request),
                executorService
            ))
            .collect(Collectors.toList());

        return futures.stream()
                     .map(CompletableFuture::join)
                     .collect(Collectors.toList());
    }

    /**
     * 分批并行计费
     */
    public List<BillingResult> batchParallelBilling(
            List<BillingRequest> requests,
            int batchSize) {

        List<BillingResult> results = new ArrayList<>();

        for (int i = 0; i < requests.size(); i += batchSize) {
            int end = Math.min(i + batchSize, requests.size());
            List<BillingRequest> batch = requests.subList(i, end);

            List<BillingResult> batchResults = parallelBatchBilling(batch);
            results.addAll(batchResults);
        }

        return results;
    }
}

8.3 异步计费

java 复制代码
/**
 * 异步计费配置
 */
@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean(name = "billingExecutor")
    public Executor billingExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(500);
        executor.setThreadNamePrefix("billing-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

/**
 * 异步计费服务
 */
@Service
@Slf4j
public class AsyncBillingService {

    @Autowired
    private BillingService billingService;

    /**
     * 异步计费
     */
    @Async("billingExecutor")
    public CompletableFuture<BillingResult> asyncBilling(BillingRequest request) {
        try {
            BillingResult result = billingService.doBilling(request);
            return CompletableFuture.completedFuture(result);
        } catch (Exception e) {
            log.error("异步计费失败", e);
            return CompletableFuture.failedFuture(e);
        }
    }

    /**
     * 批量异步计费
     */
    public CompletableFuture<List<BillingResult>> batchAsyncBilling(
            List<BillingRequest> requests) {

        List<CompletableFuture<BillingResult>> futures = requests.stream()
            .map(this::asyncBilling)
            .collect(Collectors.toList());

        return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
            .thenApply(v -> futures.stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList()));
    }
}

九、监控与运维

9.1 计费监控

java 复制代码
/**
 * 计费监控
 */
@Component
@Slf4j
public class BillingMonitor {

    private final AtomicLong totalBillingCount = new AtomicLong(0);
    private final AtomicLong successCount = new AtomicLong(0);
    private final AtomicLong failureCount = new AtomicLong(0);
    private final ConcurrentHashMap<String, AtomicLong> billingByType =
        new ConcurrentHashMap<>();

    /**
     * 记录计费成功
     */
    public void recordSuccess(String serviceType) {
        totalBillingCount.incrementAndGet();
        successCount.incrementAndGet();

        billingByType.computeIfAbsent(serviceType, k -> new AtomicLong(0))
                    .incrementAndGet();
    }

    /**
     * 记录计费失败
     */
    public void recordFailure(String serviceType, Exception e) {
        totalBillingCount.incrementAndGet();
        failureCount.incrementAndGet();

        log.error("计费失败 - 类型: {}, 错误: {}", serviceType, e.getMessage());
    }

    /**
     * 获取统计信息
     */
    public Map<String, Object> getStatistics() {
        Map<String, Object> stats = new HashMap<>();
        stats.put("totalCount", totalBillingCount.get());
        stats.put("successCount", successCount.get());
        stats.put("failureCount", failureCount.get());
        stats.put("successRate", calculateSuccessRate());
        stats.put("billingByType", billingByType);

        return stats;
    }

    private double calculateSuccessRate() {
        long total = totalBillingCount.get();
        if (total == 0) {
            return 0.0;
        }
        return (double) successCount.get() / total * 100;
    }

    /**
     * 重置统计
     */
    public void reset() {
        totalBillingCount.set(0);
        successCount.set(0);
        failureCount.set(0);
        billingByType.clear();
    }
}

/**
 * 监控切面
 */
@Aspect
@Component
@Slf4j
public class BillingMonitorAspect {

    @Autowired
    private BillingMonitor billingMonitor;

    @Around("execution(* com.example.billing.service.BillingService.doBilling(..))")
    public Object monitorBilling(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();

        BillingRequest request = (BillingRequest) joinPoint.getArgs()[0];
        String serviceType = request.getServiceType();

        try {
            Object result = joinPoint.proceed();

            long duration = System.currentTimeMillis() - startTime;
            billingMonitor.recordSuccess(serviceType);

            log.info("计费成功 - 类型: {}, 耗时: {}ms", serviceType, duration);

            return result;

        } catch (Exception e) {
            billingMonitor.recordFailure(serviceType, e);
            throw e;
        }
    }
}

9.2 规则热更新

java 复制代码
/**
 * 规则热更新监听器
 */
@Component
@Slf4j
public class RuleHotReloadListener {

    @Autowired
    private RuleLoader ruleLoader;

    @Autowired
    private RedisMessageListenerContainer redisMessageListenerContainer;

    @PostConstruct
    public void init() {
        // 监听规则更新消息
        redisMessageListenerContainer.addMessageListener(
            (message, pattern) -> {
                String ruleId = new String(message.getBody());
                log.info("收到规则更新通知: {}", ruleId);

                // 清除缓存
                ruleLoader.clearRuleCache(Long.parseLong(ruleId));
            },
            new PatternTopic("billing:rule:update")
        );
    }

    /**
     * 发布规则更新消息
     */
    public void publishRuleUpdate(Long ruleId) {
        redisTemplate.convertAndSend("billing:rule:update", ruleId.toString());
    }
}

十、最佳实践

10.1 规则编写规范

复制代码
Groovy规则编写最佳实践
┌─────────────────────────────────────────┐
│  1. 规则结构                             │
│     ✓ 使用清晰的注释                     │
│     ✓ 定义明确的输入输出                 │
│     ✓ 合理的错误处理                     │
│                                          │
│  2. 性能优化                             │
│     ✓ 避免不必要的循环                   │
│     ✓ 合理使用缓存                       │
│     ✓ 减少对象创建                       │
│                                          │
│  3. 可维护性                             │
│     ✓ 规则拆分细粒度                     │
│     ✓ 避免硬编码                         │
│     ✓ 使用配置化参数                     │
│                                          │
│  4. 安全性                               │
│     ✓ 输入参数校验                       │
│     ✓ 防止注入攻击                       │
│     ✓ 限制资源访问                       │
└─────────────────────────────────────────┘

10.2 性能调优建议

复制代码
计费引擎性能优化策略
┌─────────────────────────────────────────┐
│  缓存策略                                │
│  - 多级缓存(本地 + Redis)              │
│  - 合理的TTL设置                         │
│  - 缓存预热                              │
├─────────────────────────────────────────┤
│  并发优化                                │
│  - 使用线程池                            │
│  - 异步处理                              │
│  - 批量计费                              │
├─────────────────────────────────────────┤
│  数据库优化                              │
│  - 索引优化                              │
│  - 分表分库                              │
│  - 读写分离                              │
├─────────────────────────────────────────┤
│  规则优化                                │
│  - 规则编译缓存                          │
│  - 减少规则复杂度                        │
│  - 合理的规则优先级                      │
└─────────────────────────────────────────┘

十一、总结

核心知识点回顾

markdown 复制代码
Java + Groovy计费引擎核心要点
│
├── 架构设计
│   ├── 规则加载器
│   ├── 规则执行器
│   ├── 计费引擎
│   └── 多级缓存
│
├── Groovy集成
│   ├── GroovyShell
│   ├── GroovyClassLoader
│   ├── 动态脚本编译
│   └── 规则热加载
│
├── 计费规则
│   ├── 基础计费
│   ├── 套餐优惠
│   ├── 阶梯计费
│   ├── 时段优惠
│   └── 会员折扣
│
├── 性能优化
│   ├── 规则缓存
│   ├── 并行计费
│   ├── 异步处理
│   └── 批量操作
│
├── 监控运维
│   ├── 计费监控
│   ├── 规则热更新
│   └── 统计分析
│
└── 实战应用
    ├── 电信计费
    ├── 云服务计费
    └── 电商计费

Java + Groovy计费引擎结合了Java的稳定性和Groovy的灵活性,可以构建一个高性能、易维护的计费系统。通过合理的架构设计、规则管理和性能优化,能够满足复杂多变的业务需求。


相关推荐
嘟嘟w1 小时前
JVM(Java 虚拟机):核心原理、内存模型与调优实践
java·开发语言·jvm
合作小小程序员小小店1 小时前
web开发,在线%药店管理%系统,基于Idea,html,css,jQuery,java,ssm,mysql。
java·前端·mysql·jdk·html·intellij-idea
ZHE|张恒1 小时前
设计模式(八)组合模式 — 以树结构统一管理对象层级
java·设计模式·组合模式
TDengine (老段)1 小时前
TDengine 转换函数 CAST 用户手册
java·大数据·数据库·物联网·时序数据库·tdengine·涛思数据
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ1 小时前
java实现校验sql中,表字段在表里是否都存在,不存在的给删除掉
java·sql
编程火箭车1 小时前
【Java SE 基础学习打卡】15 分隔符、标识符与关键字
java·java入门·标识符·关键字·编程基础·分隔符·语法规则
灰色人生qwer1 小时前
idea teminal和 window cmd 输出java version不一致
java·ide·intellij-idea
WayneJoon.H2 小时前
Java反序列化 CC7链分析
java·安全·网络安全·cc链·反序列化
liu_bees2 小时前
Jenkins 中修改 admin 账号密码的正确位置与方法
java·运维·tomcat·jenkins