一、原生积分体系数据表拆解
1.1 积分数据存储结构
LikeShop 的积分数据存储在 ls_user 表的 user_integral 字段中,该字段记录用户当前可用积分余额。积分变动记录在 ls_account_log 表中,change_type 字段标识变动类型(如 801 表示签到赠送积分),change_amount 记录变动数量,left_amount 记录变动后剩余总额。
1.2 积分明细接口
积分明细通过 /shopapi/account_log/lists 接口获取,返回数据结构包含 change_type(变动类型)、change_amount(变动数量)、action(动作类型)、type_desc(变动类型描述)和 remark(备注)等字段。
1.3 积分获取规则现状
原生系统支持以下积分获取方式:
-
消费奖励:会员下单时赠送积分,支持设置积分赠送触发事件(订单付款、订单发货、订单完成、订单超过售后期),赠送比例按订单实付金额计算,比率必须为整数。所有商品消费都赠送,包括营销商品,但积分商城的商品仅支持兑换,不额外赠送积分。
-
签到赠送:用户每天签到一次可获得积分奖励,支持设置连续签到奖励(连续天数规则不可重复),签到中断后重新计算连续天数。
-
注册奖励:奖励包含积分、余额、优惠券。
1.4 积分消耗规则现状
-
积分抵扣:通过积分营销设置积分抵现比率,用于支付时抵扣金额。需要同时在商品管理的销售设置中勾选该商品是否能使用积分抵扣,否则积分抵扣设置对该商品无效。
-
积分商城:以积分作为购买凭证的商品兑换平台,支持兑换类型为"商品"(可发货)和"红包"(无需物流,付完款直接完成)。
1.5 营销模块介入订单的架构
LikeShop 的营销功能统一放在 app/common/model/ 和 app/shopapi/logic/ 下。营销模块不直接操作订单,而是通过规则校验和价格计算的方式接入主交易链路。一次下单请求中,营销逻辑的介入点如下:用户提交订单 → OrderLogic::create() → ① 优惠券校验与抵扣计算 → ② 积分抵扣计算(如果开启)→ ③ 分销关系绑定与佣金预计算 → 生成订单,写入营销相关快照数据。营销模块的关键设计原则是:营销逻辑不修改订单核心流程,只通过扩展点注入。
1.6 原生积分体系的局限
-
获取规则单一:消费积分仅支持按固定整数比率赠送,无法按订单金额比例灵活计算,也无法针对不同商品类别设置差异化赠送比例
-
抵扣玩法受限:积分抵扣需逐商品手动开启,缺乏全局统一的抵扣上限、抵扣比例控制
-
无积分过期机制:积分永久有效,长期积累导致积分贬值、用户兑换意愿低
-
积分商品兑换链路粗糙:积分商城仅支持纯积分兑换,不支持积分+现金混合支付
二、按订单金额比例返积分改造
2.1 需求分析
原生系统的消费奖励按"订单实付金额 × 整数比率"赠送积分,比率必须是整数,无法实现如"每消费 1 元赠送 1.5 积分"或"不同商品分类按不同比率赠送"的精细化规则。改造目标是实现按订单金额比例灵活返积分,支持自定义比率精度和商品分类维度的差异化配置。
2.2 数据库扩展
在积分规则配置中新增精度控制字段和分类维度支持。建议新增一张消费积分规则配置表:
sql
CREATE TABLE `ls_integral_consume_rule` (
`id` int UNSIGNED NOT NULL AUTO_INCREMENT,
`rule_name` varchar(100) NOT NULL COMMENT '规则名称',
`ratio` decimal(10,2) NOT NULL DEFAULT 1.00 COMMENT '赠送比率:每元赠送积分数',
`ratio_type` tinyint NOT NULL DEFAULT 1 COMMENT '比率类型:1-统一比率,2-按分类比率',
`cat_ratios` text COMMENT '分类比率配置,JSON格式:{"cat_id": ratio}',
`trigger_event` tinyint NOT NULL DEFAULT 1 COMMENT '触发事件:1-订单付款,2-订单发货,3-订单完成,4-超过售后期',
`max_integral` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '单笔订单赠送积分上限,0为不限',
`is_enable` tinyint NOT NULL DEFAULT 1 COMMENT '是否启用',
`create_time` int UNSIGNED NOT NULL DEFAULT 0,
`update_time` int UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消费积分赠送规则表';
同时在 ls_account_log 表的 change_type 枚举中新增类型值,用于标识新的积分获取场景。如果要新增一种积分获取方式,建议在 ls_account_log 的 change_type 枚举中定义一个新的类型值,然后新增对应的 Logic 类或扩展现有 Logic。
2.3 积分计算逻辑改造
在订单完成回调逻辑中,替换原生固定比率计算方式。核心流程:
text
function calcConsumeIntegral($orderId) {
$order = OrderModel::find($orderId);
$rule = IntegralConsumeRuleModel::where('is_enable', 1)
->where('trigger_event', $this->getCurrentTriggerEvent())
->find();
if (!$rule) return 0;
if ($rule['ratio_type'] == 2) {
// 按商品分类差异化计算
$totalIntegral = 0;
foreach ($order['order_goods'] as $goods) {
$catRatio = json_decode($rule['cat_ratios'], true)[$goods['cat_id']] ?? $rule['ratio'];
$totalIntegral += floor($goods['goods_price'] * $goods['num'] * $catRatio);
}
} else {
// 统一比率计算
$totalIntegral = floor($order['order_amount'] * $rule['ratio']);
}
// 上限控制
if ($rule['max_integral'] > 0) {
$totalIntegral = min($totalIntegral, $rule['max_integral']);
}
return $totalIntegral;
}
计算完成后,调用积分写入服务,在 ls_account_log 表中插入一条记录,change_type 使用新定义的类型值,remark 记录订单号和赠送规则。
2.4 后台配置
在"营销 → 消费奖励"页面增加以下配置项:
-
比率类型:单选,统一比率 / 按商品分类比率
-
赠送比率:小数输入,支持两位小数(如 1.50 表示每元赠送 1.5 积分)
-
分类比率:当比率类型选择"按分类"时,展示分类列表供逐项配置比率
-
单笔上限:数字输入,0 表示不限
-
触发事件:多选,订单付款 / 订单发货 / 订单完成 / 超过售后期
三、积分 + 现金混合支付改造
3.1 需求分析
原生积分抵扣需要逐商品手动开启,且抵扣金额受限于积分营销中设置的抵现比率,无法灵活控制单笔订单的抵扣上限和比例。改造目标是实现积分 + 现金混合支付:用户下单时可自主选择使用多少积分抵扣,剩余金额通过现金支付。
3.2 数据库扩展
在 ls_order 表中新增积分抵扣相关字段(若原生已有 consume_point_fee 字段则复用扩展):
sql
ALTER TABLE `ls_order`
ADD COLUMN `integral_deduct_amount` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '积分抵扣金额' AFTER `order_amount`,
ADD COLUMN `integral_deduct_num` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '抵扣使用的积分数量' AFTER `integral_deduct_amount`;
在积分营销配置中新增抵扣控制参数,建议新增配置表或扩展现有积分设置:
sql
CREATE TABLE `ls_integral_deduct_config` (
`id` int UNSIGNED NOT NULL AUTO_INCREMENT,
`ratio` decimal(10,2) NOT NULL DEFAULT 100.00 COMMENT '兑换比率:多少积分抵扣1元',
`max_deduct_percent` tinyint NOT NULL DEFAULT 50 COMMENT '最高抵扣比例(占订单金额的百分比)',
`max_deduct_amount` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '单笔最高抵扣金额,0为不限',
`use_with_coupon` tinyint NOT NULL DEFAULT 1 COMMENT '是否允许与优惠券同时使用',
`global_enable` tinyint NOT NULL DEFAULT 0 COMMENT '全局开关:是否所有商品默认开启积分抵扣',
`is_enable` tinyint NOT NULL DEFAULT 1 COMMENT '是否启用',
`create_time` int UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分抵扣配置表';
3.3 混合支付核心逻辑
在 OrderLogic::create() 的积分抵扣计算环节中,改造原生的固定抵扣逻辑:
第一步:计算最大可用积分
text
function calcMaxDeductIntegral($userId, $orderAmount) {
$config = IntegralDeductConfigModel::where('is_enable', 1)->find();
$userIntegral = UserModel::where('id', $userId)->value('user_integral');
// 按最高抵扣比例计算订单可抵扣金额上限
$maxDeductAmount = $orderAmount * $config['max_deduct_percent'] / 100;
// 按单笔上限二次限制
if ($config['max_deduct_amount'] > 0) {
$maxDeductAmount = min($maxDeductAmount, $config['max_deduct_amount']);
}
// 按兑换比率换算所需积分
$maxDeductIntegral = floor($maxDeductAmount * $config['ratio']);
// 不超过用户实际持有积分
return min($maxDeductIntegral, $userIntegral);
}
第二步:前端下单页展示
在订单确认页接口中增加以下返回字段:
-
user_integral:用户可用积分 -
max_deduct_integral:本单最大可用积分 -
max_deduct_amount:最大可抵扣金额 -
deduct_ratio:兑换比率 -
integral_deduct_enabled:当前订单是否允许积分抵扣
用户通过滑块或输入框自主选择使用积分数,前端实时计算抵扣后的应付金额。
第三步:订单创建时写入抵扣快照
在订单生成的快照数据中写入 integral_deduct_amount 和 integral_deduct_num,同时调用积分扣减服务,在 ls_account_log 表中写入一条 change_type 为积分消耗类型的记录。
3.4 与优惠券的抵扣顺序
原生系统的价格计算顺序为:先计算优惠券抵扣,再计算积分抵扣。改造时需保持这一顺序的一致性。当 use_with_coupon = 0 时,若订单使用了优惠券,则禁止使用积分抵扣。在 Price Engine 中增加校验:如果优惠券已应用且配置不允许叠加,则从可用积分抵扣列表中剔除。
3.5 退款时的积分返还
订单发生退款时,需按比例返还已使用的积分。在退款逻辑中增加积分返还处理:根据退款金额占订单总额的比例,计算应返还的积分数量,写入 ls_account_log 一条积分返还记录。注意:如果积分在退款时已过期,则不予返还。
3.6 后台配置
在"营销 → 积分抵扣"页面增加以下配置项:
-
兑换比率:小数输入,如 100 表示 100 积分抵扣 1 元
-
最高抵扣比例:数字输入(百分比),如 50 表示最多抵扣订单金额的 50%
-
单笔最高抵扣金额:数字输入,0 表示不限
-
是否允许与优惠券同时使用:开关
-
全局默认开启:开关,开启后所有商品默认支持积分抵扣,商品销售设置中可单独关闭
四、积分过期自动清理改造
4.1 需求分析
原生系统积分永久有效,缺乏过期机制。改造目标是实现积分按有效期自动过期清理,支持设置统一的有效期天数,并通过定时任务定期扫描过期积分、扣减用户余额、写入过期记录。
4.2 数据库扩展
在 ls_account_log 表中新增过期相关字段:
sql
ALTER TABLE `ls_account_log`
ADD COLUMN `expiration_time` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '积分过期时间,0为永不过期' AFTER `left_amount`,
ADD COLUMN `is_expired` tinyint NOT NULL DEFAULT 0 COMMENT '是否已过期:0-未过期,1-已过期' AFTER `expiration_time`;
新增积分过期规则配置表:
sql
CREATE TABLE `ls_integral_expire_config` (
`id` int UNSIGNED NOT NULL AUTO_INCREMENT,
`expire_days` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '积分有效天数,0为永不过期',
`expire_type` tinyint NOT NULL DEFAULT 1 COMMENT '过期类型:1-按获取时间逐笔过期,2-自然年年底统一过期',
`notice_days` int UNSIGNED NOT NULL DEFAULT 7 COMMENT '过期前多少天发送提醒,0为不提醒',
`is_enable` tinyint NOT NULL DEFAULT 0 COMMENT '是否启用',
`create_time` int UNSIGNED NOT NULL DEFAULT 0,
`update_time` int UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分过期规则配置表';
4.3 积分写入时设置过期时间
在所有积分获取逻辑(签到、消费、注册奖励等)写入 ls_account_log 时,根据过期规则配置设置 expiration_time:
-
逐笔过期模式 :
expiration_time = time() + expire_days * 86400 -
自然年过期模式 :
expiration_time = strtotime(date('Y') + 1 . '-01-01 00:00:00') -
永不过期 :
expiration_time = 0
4.4 定时任务实现
LikeShop 支持在管理后台配置和开关定时任务,设置路径为「系统设置 → 系统维护 → 定时任务」。利用这一机制创建积分过期清理任务。
第一步:新增定时任务
在后台定时任务配置中新增一个任务,执行频率为每日凌晨执行一次:
text
任务名称:积分过期自动清理
执行频率:每天 00:00
执行命令:php think integral:expire
第二步:编写命令行处理逻辑
在 app/command/ 目录下新增 IntegralExpire.php 命令类:
php
class IntegralExpire extends Command
{
protected function configure()
{
$this->setName('integral:expire')
->setDescription('积分过期自动清理');
}
protected function execute(Input $input, Output $output)
{
$config = IntegralExpireConfigModel::where('is_enable', 1)->find();
if (!$config || $config['expire_days'] <= 0) {
$output->writeln('积分过期功能未启用');
return;
}
$now = time();
// 查询已过期且未处理的积分记录
$expiredLogs = AccountLogModel::where('change_type', 'in', [积分获取类型列表])
->where('expiration_time', '>', 0)
->where('expiration_time', '<=', $now)
->where('is_expired', 0)
->where('change_amount', '>', 0)
->select();
$userExpiredMap = [];
foreach ($expiredLogs as $log) {
// 计算该笔记录的剩余积分(需扣除已被使用的部分)
$remaining = $this->calcRemainingIntegral($log);
if ($remaining <= 0) continue;
$userExpiredMap[$log['user_id']] = ($userExpiredMap[$log['user_id']] ?? 0) + $remaining;
AccountLogModel::where('id', $log['id'])->update(['is_expired' => 1]);
}
// 批量扣减用户积分
foreach ($userExpiredMap as $userId => $expiredIntegral) {
Db::startTrans();
try {
$user = UserModel::lock(true)->find($userId);
$actualExpired = min($expiredIntegral, $user['user_integral']);
UserModel::where('id', $userId)
->dec('user_integral', $actualExpired)
->update();
AccountLogModel::create([
'user_id' => $userId,
'change_type' => 积分过期扣减类型,
'change_amount' => -$actualExpired,
'left_amount' => $user['user_integral'] - $actualExpired,
'remark' => '积分过期自动扣减',
'create_time' => $now,
]);
Db::commit();
} catch (\Exception $e) {
Db::rollback();
$output->writeln("用户 {$userId} 积分过期处理失败:" . $e->getMessage());
}
}
$output->writeln('积分过期清理完成,共处理 ' . count($userExpiredMap) . ' 个用户');
}
/**
* 计算某笔积分记录的剩余可用积分
* 简化策略:按先进先出(FIFO)原则,先获取的积分优先被消耗
*/
private function calcRemainingIntegral($log)
{
// 获取该用户在该笔记录之后的所有消耗记录总额
$consumeTotal = AccountLogModel::where('user_id', $log['user_id'])
->where('change_type', 'in', [积分消耗类型列表])
->where('create_time', '>=', $log['create_time'])
->sum('change_amount');
// 剩余 = 获取积分 - 被消耗部分(简化计算,实际需按 FIFO 精确匹配)
return max(0, $log['change_amount'] + $consumeTotal);
}
}
第三步:过期前提醒
对于设置了 notice_days 的配置,在定时任务中额外增加提醒逻辑:查询 expiration_time 在 now 到 now + notice_days * 86400 之间的积分记录,按用户汇总即将过期的积分数,通过站内消息或短信通知用户。
4.5 后台配置
在"营销 → 积分设置"页面增加"积分过期"配置区块:
-
启用积分过期:开关
-
有效天数:数字输入,0 表示永不过期
-
过期类型:单选,逐笔过期 / 自然年过期
-
过期前提醒天数:数字输入,0 表示不提醒
五、积分商品兑换二开
5.1 积分商品数据模型
原生积分商城的商品管理包含商品名称、库存、兑换类型(商品/红包)、兑换积分、商品状态、排序等字段。改造需要在此基础上扩展混合支付能力。
在积分商品关联表中新增支付相关字段:
sql
ALTER TABLE `ls_integral_goods`
ADD COLUMN `pay_type` tinyint NOT NULL DEFAULT 1 COMMENT '支付类型:1-纯积分兑换,2-积分+现金混合' AFTER `exchange_integral`,
ADD COLUMN `cash_price` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '混合支付时的现金部分金额' AFTER `pay_type`,
ADD COLUMN `max_exchange_num` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '每人限兑次数,0为不限' AFTER `cash_price`,
ADD COLUMN `exchange_start_time` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '兑换开始时间,0为不限' AFTER `max_exchange_num`,
ADD COLUMN `exchange_end_time` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '兑换结束时间,0为不限' AFTER `exchange_start_time`;
5.2 兑换流程改造
第一步:兑换前校验
在积分商品兑换接口中增加校验逻辑:
-
校验用户积分是否满足纯积分兑换要求(
pay_type = 1)或满足积分部分要求(pay_type = 2) -
校验兑换时间是否在允许范围内
-
校验用户已兑换次数是否超过
max_exchange_num限制 -
校验商品库存是否充足
第二步:混合支付订单生成
当 pay_type = 2 时,用户提交兑换请求后:
-
立即扣减积分部分(使用积分扣减服务写入
ls_account_log) -
生成一个现金支付订单,订单金额为
cash_price -
用户完成现金支付后,订单状态流转为"待发货"(兑换类型为商品)或"已完成"(兑换类型为红包)
-
如果现金支付超时未完成,退回已扣减的积分并取消订单
第三步:订单与积分商城的关联
在 ls_order 表中通过 order_type 字段区分普通订单和积分商城订单,积分商城订单的 integral_deduct_num 记录纯积分消耗量,order_amount 记录现金部分金额。
5.3 兑换记录与核销
兑换记录表需记录:用户ID、积分商品ID、兑换类型、消耗积分、现金金额、订单ID、兑换状态、核销状态、兑换时间等。对于兑换类型为"商品"的订单,支持发货操作;对于兑换类型为"红包"的订单,付完款直接是已完成状态。
核销场景下,商城核销员可在个人中心看到门店核销入口,对于非核销员则隐藏入口,PC商城个人中心不支持门店核销入口,核销员只能在移动端商城进行核销。
5.4 后台配置
在"应用中心 → 积分商城"的商品新增/编辑页面增加以下配置:
-
支付类型:单选,纯积分兑换 / 积分+现金混合
-
兑换积分:数字输入
-
现金部分:当支付类型选择"混合"时,输入现金金额
-
每人限兑次数:数字输入,0 表示不限
-
兑换时间范围:日期时间选择器,留空表示不限
六、完整开发步骤总结
6.1 数据库变更清单
| 操作 | 表名 | 变更内容 |
|---|---|---|
| ALTER | ls_account_log | 新增 expiration_time、is_expired 字段 |
| ALTER | ls_order | 新增 integral_deduct_amount、integral_deduct_num 字段 |
| ALTER | ls_integral_goods | 新增 pay_type、cash_price、max_exchange_num、exchange_start_time、exchange_end_time 字段 |
| CREATE | ls_integral_consume_rule | 新建消费积分赠送规则表 |
| CREATE | ls_integral_deduct_config | 新建积分抵扣配置表 |
| CREATE | ls_integral_expire_config | 新建积分过期规则配置表 |
6.2 后端开发清单
| 模块 | 文件/类 | 工作内容 |
|---|---|---|
| 数据层 | IntegralConsumeRuleModel | 新建 Model |
| 数据层 | IntegralDeductConfigModel | 新建 Model |
| 数据层 | IntegralExpireConfigModel | 新建 Model |
| 数据层 | AccountLogModel | 新增字段映射 |
| 业务层 | IntegralService | 新建 Service,统一封装积分增减、过期计算逻辑 |
| 业务层 | OrderLogic | 改造积分抵扣计算环节,接入混合支付逻辑 |
| 业务层 | IntegralLogic | 改造消费积分计算,支持按比例和分类差异化 |
| 队列/命令 | IntegralExpire | 新建命令行任务,处理积分过期清理 |
| 事件 | OrderCompleteEvent | 订单完成后触发积分赠送事件 |
| 后台接口 | IntegralController | 新增消费规则、抵扣配置、过期配置的管理接口 |
| 后台接口 | IntegralGoodsController | 扩展商品编辑接口,支持混合支付字段的读写 |
6.3 后台前端开发清单
| 页面 | 工作内容 |
|---|---|
| 营销 → 消费奖励 | 增加比率类型、赠送比率(小数)、分类比率配置、单笔上限等表单项 |
| 营销 → 积分抵扣 | 增加兑换比率、最高抵扣比例、单笔上限、优惠券叠加开关 |
| 营销 → 积分设置 | 增加积分过期配置区块(启用开关、有效天数、过期类型、提醒天数) |
| 应用中心 → 积分商城 → 商品编辑 | 增加支付类型、现金部分、限兑次数、兑换时间范围等表单项 |
| 系统设置 → 定时任务 | 新增"积分过期自动清理"任务记录 |
6.4 开发注意事项
二开规范方面 :遵循 LikeShop 的"扩展优先"原则,新增能力优于修改原逻辑。所有扩展逻辑集中在独立的 Service 中,不修改核心的 OrderLogic 等文件中的主流程,仅在预留的营销扩展点注入积分计算逻辑。如果要新增一种积分获取方式,建议在 ls_account_log 的 change_type 枚举中定义一个新的类型值,然后新增对应的 Logic 类或扩展现有 Logic。
数据一致性方面 :积分扣减和写入必须使用数据库事务 + 行锁(lock(true)),防止并发下积分计算错误。积分过期清理任务涉及大量写操作,建议分批处理(如每批 500 条),避免长时间锁表。
退款处理方面:订单退款时,需同步处理已赠送积分和已抵扣积分的回退。如果积分在退款时已过期,则不予返还。积分商城混合支付订单的现金退款,需同步退回已扣减的积分。
性能优化方面 :积分变动记录表建议建立 user_id + create_time 联合索引,便于按用户和时间范围查询。积分过期清理任务建议在低峰期(凌晨)执行。