# engine **Repository Path**: fiberphp/engine ## Basic Information - **Project Name**: engine - **Description**: ⚙️ FiberPHP 业务规则引擎 —— 促销与运费计算引擎,17 种促销规则 + 7 种运费规则,电商业务扩展,开箱即用。 - **Primary Language**: PHP - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-23 - **Last Updated**: 2026-11-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 规则引擎 (Rule Engine) 一个灵活、可扩展的规则引擎,支持促销规则和运费规则的计算。 ## 目录结构 ``` src/ ├── Engine.php # 统一入口 (FiberPHP\Engine\Engine) ├── Helper.php # 工具类 (类名解析、购物车创建等) ├── Common/ # 公共组件 │ ├── Contract/ # 接口与抽象基类 │ │ ├── CalculatorInterface.php │ │ ├── AbstractCalculator.php │ │ ├── RuleInterface.php │ │ ├── AbstractRule.php │ │ ├── ValidatorInterface.php │ │ └── AbstractValidator.php │ ├── Model/ # 数据模型 │ │ ├── Cart.php │ │ └── Member.php │ └── Exception/ # 异常处理 │ └── EngineException.php ├── Promotion/ # 促销规则引擎 │ ├── Promotion.php # 促销计算核心 │ ├── Rule/ # 促销规则 │ │ ├── AbstractRule.php │ │ ├── TieredReduction.php # 阶梯满减 │ │ ├── TieredDiscount.php # 阶梯满折 │ │ ├── TieredQuantityReduction.php # 阶梯数量立减 │ │ ├── TieredQuantityDiscount.php # 阶梯数量折扣 │ │ ├── TieredNthItemDiscount.php # 阶梯第N件折扣 │ │ ├── TieredNthItemReduction.php # 阶梯第N件立减 │ │ ├── RankDiscount.php # 会员等级折扣 │ │ ├── RankReduction.php # 会员等级立减 │ │ ├── Coupon.php # 优惠券 │ │ ├── PointsRedemption.php # 积分抵扣 │ │ ├── PointsMultiplier.php # 积分翻倍 │ │ ├── MemberDayDiscount.php # 会员日折扣 │ │ ├── BirthdayDiscount.php # 生日折扣 │ │ ├── HolidayDiscount.php # 节日折扣 │ │ ├── TimeSlotPricing.php # 时段定价 │ │ ├── BuyGift.php # 买赠规则 │ │ ├── FullGift.php # 满赠规则 │ │ ├── GiftSelection.php # 赠品选择 │ │ └── TradeIn.php # 满额换购 │ ├── Calculator/ # 计算策略 │ │ ├── Independent.php # 独立计算(叠加) │ │ ├── Sequential.php # 顺序计算(依次减免) │ │ └── Lock.php # 锁定最优(选最优) │ └── Validate/ # 促销规则验证器 │ ├── AbstractValidator.php │ ├── TieredReduction.php │ ├── TieredDiscount.php │ ├── TieredQuantityReduction.php │ ├── TieredQuantityDiscount.php │ ├── TieredNthItemDiscount.php │ ├── TieredNthItemReduction.php │ ├── RankDiscount.php │ ├── RankReduction.php │ ├── Coupon.php │ ├── PointsRedemption.php │ ├── PointsMultiplier.php │ ├── MemberDayDiscount.php │ ├── BirthdayDiscount.php │ ├── HolidayDiscount.php │ ├── TimeSlotPricing.php │ ├── BuyGift.php │ ├── FullGift.php │ ├── GiftSelection.php │ └── TradeIn.php └── Freight/ # 运费规则引擎 ├── Freight.php # 运费计算核心 ├── Rule/ # 运费规则 │ ├── AbstractRule.php │ ├── FullFree.php # 满额包邮 │ ├── WeightBased.php # 按重量计费 │ ├── RegionBased.php # 按地区计费 │ ├── MemberFree.php # 会员免运费 │ ├── PointsDeduction.php # 积分抵扣运费 │ ├── Discount.php # 运费折扣 │ └── ItemSpecific.php # 商品级别运费 ├── Calculator/ # 计算策略 │ ├── Independent.php # 独立计算(叠加) │ ├── Sequential.php # 顺序计算 │ └── Lock.php # 锁定最优 └── Validate/ # 运费规则验证器 ├── AbstractValidator.php ├── FullFree.php ├── WeightBased.php ├── RegionBased.php ├── MemberFree.php ├── PointsDeduction.php ├── Discount.php └── ItemSpecific.php ``` ## 快速开始 ### 安装 使用 Composer 安装: ```bash composer require fiberphp/engine ``` ### 促销计算 ```php use FiberPHP\Engine\Engine; // 购物车数据 $cart = [ ['name' => '商品A', 'price' => 200, 'quantity' => 2, 'tags' => ['electronics']], ['name' => '商品B', 'price' => 100, 'quantity' => 1, 'tags' => ['clothing']], ]; // 会员等级 $rankId = 2; // 规则配置(使用下划线命名) $promotionRules = [ 'TieredReduction' => [ 'tiers' => [300 => 50, 500 => 100], 'activity_id' => 1001 ] ]; // 计算优惠 $result = Engine::calcPromotion($cart, $rankId, $promotionRules); // 输出结果 echo "原价: {$result['original']} 元\n"; echo "优惠: {$result['discount']} 元\n"; echo "实付: {$result['final']} 元\n"; ``` ### 运费计算 ```php use FiberPHP\Engine\Engine; // 购物车数据(包含重量) $cart = [ ['name' => '商品A', 'price' => 200, 'quantity' => 1, 'weight' => 2.5], ['name' => '商品B', 'price' => 100, 'quantity' => 1, 'weight' => 1.0], ]; // 运费规则配置 $freightRules = [ 'FullFree' => [ 'threshold' => 200, 'activity_id' => 3001 ] ]; // 计算运费(支持计算模式) $result = Engine::calcFreight($cart, $rankId, $freightRules, $storeId, $memberInfo, $region, 'lock'); // 输出结果 echo "运费原价: {$result['original']} 元\n"; echo "运费优惠: {$result['discount']} 元\n"; echo "实际运费: {$result['final']} 元\n"; ``` ### 订单总额计算 ```php use FiberPHP\Engine\Engine; // 计算订单总额(促销+运费) $result = Engine::calcOrder( $cart, $rankId, $promotionRules, $freightRules, 'independent', // 促销计算模式 $storeId, $memberInfo, $region, 'lock' // 运费计算模式 ); // 订单汇总 echo "订单原价: {$result['order']['original_total']} 元\n"; echo "总优惠: {$result['order']['total_discount']} 元\n"; echo "应付总额: {$result['order']['final_total']} 元\n"; ``` ## 促销规则配置 ### 1. 阶梯满减 (TieredReduction) ```php 'TieredReduction' => [ 'tiers' => [ 300 => 50, // 满300减50 500 => 100, // 满500减100 800 => 180, // 满800减180 ], 'tags' => ['electronics'], // 适用商品标签(可选) 'priority' => 1, // 优先级(可选) 'activity_id' => 1001 // 活动ID ] ``` ### 2. 阶梯满折 (TieredDiscount) ```php 'TieredDiscount' => [ 'tiers' => [ 300 => 0.9, // 满300打9折 500 => 0.85, // 满500打85折 ], 'tags' => ['clothing'], 'activity_id' => 1002 ] ``` ### 3. 会员等级折扣 (RankDiscount) ```php 'RankDiscount' => [ 'discounts' => [ 1 => 0.95, // 等级1会员打95折 2 => 0.9, // 等级2会员打9折 3 => 0.85, // 等级3会员打85折 ], 'activity_id' => 1003 ] ``` ### 4. 优惠券 (Coupon) ```php 'Coupon' => [ 'type' => 'full_reduction', // full_reduction:满减, discount:折扣, direct:直减 'threshold' => 200, // 门槛金额 'discount' => 30, // 优惠金额/折扣率 'code' => 'COUPON2024', // 优惠券码 'activity_id' => 1004 ] ``` ### 5. 积分抵扣 (PointsRedemption) ```php 'PointsRedemption' => [ 'points_rate' => 0.01, // 1积分抵0.01元 'min_points' => 100, // 最低抵扣积分 'max_points' => 5000, // 最高抵扣积分 'max_percent' => 0.5, // 最高抵扣比例(0-1) 'activity_id' => 1005 ] ``` ### 6. 生日折扣 (BirthdayDiscount) ```php 'BirthdayDiscount' => [ 'discount_rate' => 0.8, // 8折 'days' => 7, // 生日前后共7天 'applicable_ranks' => [1,2,3], 'tags' => ['gift'], 'activity_id' => 1006 ] ``` ## 运费规则配置 ### 1. 满额包邮 (FullFree) ```php 'FullFree' => [ 'threshold' => 200, // 满200元包邮 'activity_id' => 3001 ] ``` ### 2. 按重量计费 (WeightBased) ```php 'WeightBased' => [ 'first_weight' => 1, // 首重(kg) 'first_price' => 10, // 首重价格 'additional_weight' => 1, // 续重单位(kg) 'additional_price' => 5, // 续重价格 'activity_id' => 3002 ] ``` ### 3. 按地区计费 (RegionBased) ```php 'RegionBased' => [ 'regions' => [ '北京' => ['base_price' => 10, 'additional_kg_price' => 2], '上海' => ['base_price' => 12, 'additional_kg_price' => 3], '广州' => ['base_price' => 8, 'additional_kg_price' => 1.5], ], 'activity_id' => 3003 ] ``` ### 4. 会员免运费 (MemberFree) ```php 'MemberFree' => [ 'applicable_ranks' => [2, 3], // 等级2和3会员免运费 'activity_id' => 3004 ] ``` ### 5. 积分抵扣运费 (PointsDeduction) ```php 'PointsDeduction' => [ 'points_rate' => 0.01, // 1积分抵0.01元运费 'max_percent' => 0.5, // 最高抵扣50% 'activity_id' => 3005 ] ``` ### 6. 运费折扣 (Discount) ```php 'Discount' => [ 'discount_rate' => 0.8, // 运费8折 'activity_id' => 3006 ] ``` ### 7. 商品级别运费 (ItemSpecific) ```php 'ItemSpecific' => [ 'rules' => [ ['product_id' => 1001, 'freight' => 15], // 商品1001固定运费15元 ['category' => 'clothing', 'freight' => 8], // 服装类每件8元 ['category' => 'fresh', 'rate' => 0.1], // 生鲜类按金额10%计费 ], 'activity_id' => 3007 ] ``` ## 计算模式 ### 促销计算模式 | 模式 | 说明 | |-----------------|------------------------------------------------------------| | **independent** | 所有规则独立计算,优惠可以叠加 | | **sequential** | 按规则优先级依次计算,后一个规则基于前一个规则计算后的金额 | | **lock** | 选择优惠最大的规则生效,其他规则不生效 | ```php // 使用不同计算模式 $result = Engine::calcPromotion($cart, $rankId, $rules, 'lock'); ``` ### 运费计算模式 运费规则同样支持三种计算模式,与促销规则保持一致: | 模式 | 说明 | 适用场景 | |-----------------|------------------------------------------------------------|----------------------------| | **independent** | 所有规则独立计算,优惠可以叠加 | 多种运费优惠可同时享受 | | **sequential** | 按规则优先级依次计算,后一个规则基于前一个规则计算后的金额 | 需要按顺序应用运费优惠 | | **lock** | 选择优惠最大的规则生效,其他规则不生效 | 多个运费规则互斥,择优选取 | ```php // 使用 lock 模式选择最优运费规则 $result = Engine::calcFreight($cart, $rankId, $rules, $storeId, $memberInfo, $region, 'lock'); ``` #### 计算模式示例 **场景:购物车总价 350 元,总重量 5kg,会员等级2** 运费规则配置: ```php $freightRules = [ 'FullFree' => [ 'threshold' => 200, // 满200包邮 'activity_id' => 3001 ], 'MemberFree' => [ 'applicable_ranks' => [2, 3], // 等级2会员免运费 'activity_id' => 3004 ], 'Discount' => [ 'discount_rate' => 0.8, // 运费8折 'activity_id' => 3006 ] ]; ``` **independent 模式(叠加优惠)**: - 满额包邮:运费减25元(原价25元) - 会员免运费:运费减25元 - 运费8折:运费减5元 - 总优惠:55元(注:实际取最优,避免运费为负) **lock 模式(锁定最优)**: - 选择优惠最大的规则(满额包邮或会员免运费,优惠25元) - 其他规则不生效 **sequential 模式(顺序计算)**: - 按优先级依次应用规则 - 后一个规则基于前一个规则的结果计算 ## 返回结果结构 ### 促销计算结果 ```php $result = [ 'original' => 500, // 原价 'discount' => 100, // 优惠金额 'final' => 400, // 实付金额 'details' => [ // 优惠详情列表 [ 'activity_id' => 1001, 'activity_type' => 'TieredReduction', 'discount' => 50, 'description' => '满300减50' ] ] ]; ``` ### 运费计算结果 ```php $result = [ 'original' => 20, // 运费原价 'discount' => 10, // 运费优惠 'final' => 10, // 实际运费 'weight' => 3.5, // 总重量 'details' => [ // 运费详情列表 [ 'activity_id' => 3001, 'activity_type' => 'FullFree', 'discount' => 0, 'description' => '未达到包邮门槛' ] ] ]; ``` ### 订单总额计算结果 ```php $result = [ 'promotion' => [...], // 促销计算结果 'freight' => [...], // 运费计算结果 'order' => [ 'original_total' => 520, // 订单原价(商品+运费) 'total_discount' => 110, // 总优惠 'final_total' => 410 // 应付总额 ] ]; ``` ## 商品级别支持 所有运费规则均支持商品级别筛选,通过 `applicable_tags` 配置来限制适用的商品范围。 ### 基于标签的筛选 ```php 'WeightBased' => [ 'first_weight' => 1, 'first_price' => 15, 'applicable_tags' => ['heavy'], // 仅适用于标记为 heavy 的商品 'activity_id' => 3002 ] ``` ### 商品级别运费规则 `ItemSpecific` 规则支持为不同商品或分类设置不同运费: ```php 'ItemSpecific' => [ 'rules' => [ // 按商品ID匹配 ['product_id' => 1001, 'freight' => 15], // 按分类(标签)匹配,固定运费 ['category' => 'clothing', 'freight' => 8], // 按分类匹配,按金额比例计费 ['category' => 'fresh', 'rate' => 0.1], // 按SKU匹配 ['sku' => 'SKU001', 'freight' => 12], ], 'activity_id' => 3007 ] ``` ### 规则匹配优先级 `ItemSpecific` 规则的匹配顺序: 1. 按商品ID匹配(`product_id`) 2. 按SKU匹配(`sku`) 3. 按分类/标签匹配(`category`) ### 运费计算方式 `ItemSpecific` 支持两种计费方式: - **固定运费**:设置 `freight` 参数,按件计费 - **比例计费**:设置 `rate` 参数,按商品金额的百分比计费 ### 适用商品筛选示例 ```php // 购物车包含多种类型商品 $cart = [ ['id' => 1001, 'name' => '电子产品', 'price' => 500, 'quantity' => 1, 'tags' => ['electronics'], 'weight' => 2.0], ['id' => 1002, 'name' => '服装', 'price' => 100, 'quantity' => 2, 'tags' => ['clothing'], 'weight' => 0.5], ['id' => 1003, 'name' => '生鲜', 'price' => 50, 'quantity' => 3, 'tags' => ['fresh'], 'weight' => 1.0], ]; // 商品级别运费规则 $freightRules = [ 'ItemSpecific' => [ 'rules' => [ ['product_id' => 1001, 'freight' => 15], // 电子产品固定运费15元 ['category' => 'clothing', 'freight' => 8], // 服装类每件8元 ['category' => 'fresh', 'rate' => 0.1], // 生鲜类按金额10%计费 ], 'activity_id' => 3007 ] ]; // 计算结果:15 + (2*8) + (3*50*0.1) = 15 + 16 + 15 = 46元 ``` ## 添加自定义规则 ### 促销规则 1. 创建规则类,继承 `AbstractRule`: ```php namespace FiberPHP\Engine\Promotion\Rule; use FiberPHP\Engine\Common\Model\Cart; use FiberPHP\Engine\Common\Model\Member; class CustomRule extends AbstractRule { public function __construct(array $config = []) { parent::__construct($config); $this->ruleType = 'CustomRule'; } public function apply(Cart $cart, Member $user, array $eligibleIndexes = []): array { // 实现规则逻辑 return $this->result($discount, $description); } } ``` 2. 在 `Promotion/Promotion.php` 的 `RULE_MAP` 中注册规则。 ### 运费规则 1. 创建规则类,继承 `AbstractRule`: ```php namespace FiberPHP\Engine\Freight\Rule; use FiberPHP\Engine\Common\Model\Cart; class CustomFreightRule extends AbstractRule { public function __construct(array $config = []) { parent::__construct($config); $this->ruleType = 'CustomFreightRule'; } public function apply(Cart $cart, Member $user, array $eligibleIndexes = []): array { // 实现运费计算逻辑 return $this->freightResult($freight, $discount, $description); } } ``` 2. 在 `Freight/Freight.php` 的 `RULE_MAP` 中注册规则。 ## 命名规范 - 数组字段名:下划线命名(如 `activity_id`, `start_date`) - 对象属性:驼峰命名(如 `$this->activityId`, `$this->startDate`) - 全局函数:下划线命名(如 `qualify_class()`, `create_cart()`) - 日期格式:`Y-m-d H:i:s` - 折扣率范围:0-1(1表示原价,小于1表示折扣) ## 测试 ```bash # 安装依赖 composer install # 运行所有测试 composer test ``` ### 测试文件结构 ``` tests/ ├── PromotionTest.php # 促销规则测试 ├── FreightTest.php # 运费规则测试 └── EngineTest.php # 引擎综合测试 ``` ### 测试示例 **促销规则测试**: ```php public function testTieredReduction() { $cart = [ ['name' => '商品A', 'price' => 100, 'quantity' => 3, 'tags' => ['clothing']], ]; $rules = [ 'TieredReduction' => [ 'tiers' => [200 => 30, 500 => 80], 'activity_id' => 1001 ] ]; $result = Engine::calcPromotion($cart, 0, $rules); $this->assertEquals(300, $result['original']); $this->assertEquals(30, $result['discount']); $this->assertEquals(270, $result['final']); } ``` **运费规则测试**: ```php public function testWeightBased() { $cart = [ ['name' => '商品A', 'price' => 100, 'quantity' => 2, 'weight' => 3], ]; $rules = [ 'WeightBased' => [ 'first_weight' => 1, 'first_price' => 10, 'additional_weight' => 1, 'additional_price' => 5, 'activity_id' => 2001 ] ]; $result = Engine::calcFreight($cart, 0, $rules); $this->assertEquals(20, $result['final']); } ``` **订单总额测试**: ```php public function testCalcOrder() { $cart = [ ['name' => '商品A', 'price' => 150, 'quantity' => 2, 'weight' => 2, 'tags' => ['clothing']], ]; $promotionRules = ['TieredReduction' => ['tiers' => [200 => 30], 'activity_id' => 1001]]; $freightRules = ['WeightBased' => ['first_weight' => 1, 'first_price' => 10, 'additional_weight' => 1, 'additional_price' => 5, 'activity_id' => 2001]]; $result = Engine::calcOrder($cart, 0, $promotionRules, $freightRules); $this->assertEquals(300, $result['promotion']['original']); $this->assertEquals(30, $result['promotion']['discount']); $this->assertEquals(15, $result['freight']['final']); $this->assertEquals(285, $result['order']['final_total']); } ``` **异常测试**: ```php public function testUnknownRule() { $this->expectException(EngineException::class); $this->expectExceptionCode(1001); $cart = [['name' => '商品', 'price' => 100, 'quantity' => 1]]; $rules = ['UnknownRule' => []]; Engine::calcPromotion($cart, 0, $rules); } ``` ## 注意事项 1. 规则配置使用关联数组格式,键名使用下划线命名 2. 商品标签匹配采用交集方式,商品需包含规则指定的所有标签 3. 日期格式统一使用 `Y-m-d H:i:s` 4. 折扣率范围为 0-1,1 表示原价,小于1表示折扣 ## License MIT