LikeShop 多商户高级版商城满减满赠活动的自定义规则二开实现

简介: 本文针对LikeShop多商户平台满减满赠二开需求,提出“独立活动体系+统一计算入口+事件联动分账”架构方案。涵盖双层级活动模型、多档阶梯规则、精准分摊与退款回退、审核流程及平滑升级,全程低侵入、高兼容,确保账务准确与运营灵活。

多商户平台的营销活动,和单商户的复杂度不在一个量级。

LikeShop 多商户高级版原生已经具备基础的营销能力,但在实际运营中,平台方往往需要更灵活的满减满赠体系:区分平台级与商家级活动、支持多档阶梯规则、控制优惠叠加范围、跨店满减自动分摊到商家、退款时按比例回退优惠。如果直接在原有订单逻辑里硬加判断,很容易导致计算混乱、分账不准、退款不同步等问题。

这类需求属于典型的营销扩展型二开,最佳实践是独立活动体系 + 统一计算入口 + 事件联动订单分账,不侵入原生订单主流程,以最小侵入成本实现可配置的满减满赠规则。

本文基于 LikeShop 多商户高级版(Java 技术栈),从架构设计、表结构、核心计算逻辑、上下游联动、平滑升级五个维度,给出一套完整的二开落地方案。

一、二开背景与核心目标

1.1 原生营销能力的局限

原生版本的营销工具以单店铺维度为主,在多商户平台级运营场景下存在几个明显短板:

  • 层级单一:缺少「平台级活动」与「商家级活动」的分层,平台无法统一组织跨店大促;
  • 规则简单:多为固定金额优惠,缺少多档阶梯满减、满折、满赠的组合玩法;
  • 叠加模糊:活动之间、活动与优惠券之间能否叠加,缺少精细化的开关控制;
  • 分摊缺失:跨店优惠无法自动按比例分摊到各商家子订单,结算和退款时账务对不上;
  • 审核缺失:商家创建活动缺少平台审核环节,违规活动容易引发合规风险。

1.2 二开核心目标

  1. 双层级活动:支持平台级跨店满减、商家级店铺满减两套体系,各自独立管理;
  2. 多规则类型:覆盖满金额减钱、满金额打折、满金额赠商品三种主流玩法,支持多档阶梯;
  3. 叠加可控:可配置是否与优惠券、其他活动叠加,避免优惠叠加失控;
  4. 自动分摊:跨店优惠按子订单金额比例自动分摊,退款时按分摊额同步回退;
  5. 流程合规:商家活动走平台审核,状态机可控,全程留痕可追溯;
  6. 最小侵入:独立活动表 + 统一计算服务,尽量不修改原生订单与分账核心代码。

二、核心设计思路

2.1 活动双层级模型

层级 发起方 适用范围 优惠承担方 典型场景
平台级活动 平台运营 全平台或指定类目/商家 平台承担(从佣金中抵扣) 618跨店满减、平台大促
商家级活动 入驻商家 本店商品 商家自行承担 店铺满减、店庆活动

2.2 三种规则类型

统一抽象为「满足条件 → 享受优惠」的阶梯模型,三种类型共用一套状态机:

  1. 满减:满 X 元减 Y 元,支持多档(满100减10、满200减30);
  2. 满折:满 X 元打 Y 折,支持多档;
  3. 满赠:满 X 元赠送指定商品,支持多档不同赠品。

2.3 优惠叠加规则

通过配置位精细控制叠加范围,避免优惠失控:

  • 平台活动与商家活动:可配置是否可叠加;
  • 活动与优惠券:可配置是否可叠加;
  • 同类型活动:默认互斥,单笔订单取最优一档;
  • 特殊商品:可排除指定商品不参与活动。

2.4 分摊原则

跨店满减的核心难点是账务分摊,遵循两条原则:

  1. 按金额比例分摊:优惠总额按各商家子订单实付占比拆分;
  2. 误差放最后:分摊四舍五入产生的几分钱误差,放在最后一个子订单消化,保证总额一致。

2.5 活动状态机

和其他营销活动共用一套状态流转,保证平台管控能力:

草稿 → 提交审核 → 审核通过 → 未开始 → 进行中 → 已结束
                ↓
              审核驳回 → 修改重提

三、数据库设计

采用「主表存公共属性 + 规则表存阶梯明细 + 订单关联表存分摊快照」的三层结构,全部为新增表,极少修改原生表。

3.1 活动主表 ls_promotion_full

存储活动基础信息,平台级和商家级都在这一张表,通过 level 字段区分。

CREATE TABLE ls_promotion_full (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  promotion_name VARCHAR(64) NOT NULL COMMENT '活动名称',
  promotion_type TINYINT NOT NULL COMMENT '类型:1满减 2满折 3满赠',
  promotion_level TINYINT NOT NULL COMMENT '层级:1平台级 2商家级',
  merchant_id BIGINT DEFAULT NULL COMMENT '商家ID,平台级为空',

  scope_type TINYINT NOT NULL COMMENT '适用范围:1全部商品 2指定分类 3指定商品',
  scope_ids TEXT COMMENT '范围ID集合,逗号分隔',

  stackable_coupon TINYINT NOT NULL DEFAULT 0 COMMENT '是否可叠加优惠券:0否 1是',
  stackable_other TINYINT NOT NULL DEFAULT 0 COMMENT '是否可叠加其他活动:0否 1是',

  start_time DATETIME NOT NULL,
  end_time DATETIME NOT NULL,

  audit_status TINYINT NOT NULL DEFAULT 0 COMMENT '审核状态:0草稿 1待审 2通过 3驳回',
  status TINYINT NOT NULL DEFAULT 0 COMMENT '活动状态:0未开始 1进行中 2已结束 3关闭',

  create_by BIGINT,
  create_time DATETIME NOT NULL,
  update_time DATETIME NOT NULL,

  INDEX idx_merchant_id(merchant_id),
  INDEX idx_status(status),
  INDEX idx_time(start_time, end_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='满减满赠活动主表';

3.2 活动阶梯规则表 ls_promotion_full_rule

一个活动对应多档阶梯规则,支持逐级递增。

CREATE TABLE ls_promotion_full_rule (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  promotion_id BIGINT NOT NULL,

  threshold_amount DECIMAL(10,2) NOT NULL COMMENT '满足门槛金额',
  discount_type TINYINT NOT NULL COMMENT '优惠类型:1减金额 2折扣 3赠商品',
  discount_value DECIMAL(10,2) NOT NULL COMMENT '优惠值:减钱/折扣率/赠品数量',
  gift_sku_id BIGINT DEFAULT NULL COMMENT '赠品SKU_ID,满赠类型使用',

  sort INT NOT NULL DEFAULT 0 COMMENT '档位排序,金额从低到高',
  create_time DATETIME NOT NULL,

  INDEX idx_promotion_id(promotion_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='活动阶梯规则表';

3.3 订单优惠关联表 ls_order_promotion_full

订单维度的优惠快照,记录使用了哪个活动、分摊了多少金额,是对账和退款回退的依据。

CREATE TABLE ls_order_promotion_full (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  order_id BIGINT NOT NULL COMMENT '子订单ID',
  main_order_id BIGINT DEFAULT NULL COMMENT '主订单ID,跨店场景使用',
  promotion_id BIGINT NOT NULL,
  promotion_level TINYINT NOT NULL,
  promotion_type TINYINT NOT NULL,

  order_amount DECIMAL(10,2) NOT NULL COMMENT '子订单原金额',
  discount_amount DECIMAL(10,2) NOT NULL COMMENT '本子单分摊优惠金额',

  bearer_type TINYINT NOT NULL COMMENT '优惠承担方:1平台 2商家',

  create_time DATETIME NOT NULL,
  INDEX idx_order_id(order_id),
  INDEX idx_main_order_id(main_order_id),
  INDEX idx_promotion_id(promotion_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单活动优惠关联表';

3.4 原生表扩展

仅在订单表增加一个优惠汇总字段,不破坏原有结构:

ALTER TABLE ls_order 
ADD COLUMN promotion_discount DECIMAL(10,2) DEFAULT 0.00 COMMENT '活动优惠总金额'
AFTER discount_amount;

四、核心业务逻辑与代码实现

4.1 活动创建与审核

商家在后台创建活动,选择类型、设置阶梯、指定范围,提交后进入平台审核。

@Service
public class PromotionFullService {

    @Autowired
    private PromotionFullMapper promotionMapper;
    @Autowired
    private PromotionFullRuleMapper ruleMapper;

    @Transactional(rollbackFor = Exception.class)
    public Long create(PromotionFullCreateDTO dto, Long merchantId) {
        // 1. 校验规则合法性:阶梯金额递增、优惠值合理
        validateRules(dto.getRules());

        // 2. 创建活动主记录
        PromotionFull promotion = new PromotionFull();
        promotion.setPromotionName(dto.getName());
        promotion.setPromotionType(dto.getType());
        promotion.setPromotionLevel(2); // 商家级
        promotion.setMerchantId(merchantId);
        promotion.setScopeType(dto.getScopeType());
        promotion.setStackableCoupon(dto.getStackableCoupon());
        promotion.setStartTime(dto.getStartTime());
        promotion.setEndTime(dto.getEndTime());
        promotion.setAuditStatus(1); // 待审核
        promotion.setStatus(0);
        promotionMapper.insert(promotion);

        // 3. 批量插入阶梯规则
        List<PromotionFullRule> rules = dto.getRules().stream()
                .map(r -> {
                    PromotionFullRule rule = new PromotionFullRule();
                    rule.setPromotionId(promotion.getId());
                    rule.setThresholdAmount(r.getThreshold());
                    rule.setDiscountType(r.getDiscountType());
                    rule.setDiscountValue(r.getDiscountValue());
                    rule.setSort(r.getSort());
                    return rule;
                }).collect(Collectors.toList());
        ruleMapper.batchInsert(rules);

        // 4. 通知平台审核
        applicationEventPublisher.publishEvent(new PromotionToAuditEvent(promotion.getId()));

        return promotion.getId();
    }

    private void validateRules(List<PromotionRuleDTO> rules) {
        // 校验阶梯金额递增
        for (int i = 1; i < rules.size(); i++) {
            if (rules.get(i).getThreshold().compareTo(rules.get(i-1).getThreshold()) <= 0) {
                throw new BizException("阶梯金额必须递增");
            }
        }
        // 校验折扣率在0-1之间
        // 校验满赠必须指定赠品SKU
    }
}

4.2 下单优惠计算(核心)

这是整个模块的心脏:用户提交订单时,自动匹配可用活动,计算最优档位,按比例分摊到各子订单。

@Service
public class PromotionCalcService {

    /**
     * 计算订单可用的满减优惠
     * @param mainOrder 主订单信息,包含多个商家子单
     * @return 各子订单分摊的优惠明细
     */
    public List<OrderPromotionFullVO> calculate(MainOrderCalcDTO mainOrder) {
        List<OrderPromotionFullVO> result = new ArrayList<>();
        List<SubOrderCalcDTO> subOrders = mainOrder.getSubOrders();

        // 1. 匹配可用的平台级活动(跨店满减)
        List<PromotionFull> platformPromotions = matchPlatformPromotions(mainOrder);
        if (!platformPromotions.isEmpty()) {
            // 取最优活动
            PromotionFull bestPlatform = selectBestPromotion(platformPromotions, mainOrder.getTotalAmount());
            BigDecimal totalDiscount = calcDiscount(bestPlatform, mainOrder.getTotalAmount());

            // 按子订单金额比例分摊
            result.addAll(allocateDiscountToSubOrders(
                    bestPlatform, totalDiscount, subOrders, 1)); // 1=平台承担
        }

        // 2. 匹配各商家自身的店铺级活动
        for (SubOrderCalcDTO subOrder : subOrders) {
            List<PromotionFull> shopPromotions = matchShopPromotions(subOrder);
            if (shopPromotions.isEmpty()) continue;

            // 校验叠加规则:如果平台活动不可叠加商家活动,则跳过
            if (!platformPromotions.isEmpty() && !bestPlatform.getStackableOther()) {
                continue;
            }

            PromotionFull bestShop = selectBestPromotion(shopPromotions, subOrder.getAmount());
            BigDecimal shopDiscount = calcDiscount(bestShop, subOrder.getAmount());

            OrderPromotionFullVO vo = new OrderPromotionFullVO();
            vo.setOrderId(subOrder.getOrderId());
            vo.setPromotionId(bestShop.getId());
            vo.setPromotionLevel(2);
            vo.setOrderAmount(subOrder.getAmount());
            vo.setDiscountAmount(shopDiscount);
            vo.setBearerType(2); // 商家承担
            result.add(vo);
        }

        return result;
    }

    /**
     * 计算对应档位的优惠金额
     */
    private BigDecimal calcDiscount(PromotionFull promotion, BigDecimal amount) {
        List<PromotionFullRule> rules = getRules(promotion.getId());
        // 找到满足条件的最高档位
        PromotionFullRule matched = null;
        for (PromotionFullRule rule : rules) {
            if (amount.compareTo(rule.getThresholdAmount()) >= 0) {
                matched = rule;
            }
        }
        if (matched == null) return BigDecimal.ZERO;

        switch (promotion.getPromotionType()) {
            case 1: // 满减
                return matched.getDiscountValue();
            case 2: // 满折
                return amount.multiply(BigDecimal.ONE.subtract(matched.getDiscountValue()))
                        .setScale(2, RoundingMode.HALF_UP);
            default:
                return BigDecimal.ZERO;
        }
    }

    /**
     * 按子订单金额比例分摊优惠
     */
    private List<OrderPromotionFullVO> allocateDiscountToSubOrders(
            PromotionFull promotion, BigDecimal totalDiscount,
            List<SubOrderCalcDTO> subOrders, int bearerType) {

        List<OrderPromotionFullVO> list = new ArrayList<>();
        BigDecimal totalAmount = subOrders.stream()
                .map(SubOrderCalcDTO::getAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        BigDecimal allocated = BigDecimal.ZERO;
        for (int i = 0; i < subOrders.size(); i++) {
            SubOrderCalcDTO sub = subOrders.get(i);
            BigDecimal discount;

            if (i == subOrders.size() - 1) {
                // 最后一子单用倒算,吸收精度误差
                discount = totalDiscount.subtract(allocated);
            } else {
                BigDecimal ratio = sub.getAmount().divide(totalAmount, 4, RoundingMode.HALF_UP);
                discount = totalDiscount.multiply(ratio)
                        .setScale(2, RoundingMode.HALF_UP);
                allocated = allocated.add(discount);
            }

            OrderPromotionFullVO vo = new OrderPromotionFullVO();
            vo.setOrderId(sub.getOrderId());
            vo.setPromotionId(promotion.getId());
            vo.setPromotionLevel(1);
            vo.setOrderAmount(sub.getAmount());
            vo.setDiscountAmount(discount);
            vo.setBearerType(bearerType);
            list.add(vo);
        }
        return list;
    }
}

4.3 订单创建写入优惠快照

计算完成后,将优惠明细持久化,作为后续分账、退款、对账的依据。

@Transactional(rollbackFor = Exception.class)
public void saveOrderPromotions(Long mainOrderId, List<OrderPromotionFullVO> details) {
    for (OrderPromotionFullVO detail : details) {
        OrderPromotionFull record = new OrderPromotionFull();
        record.setOrderId(detail.getOrderId());
        record.setMainOrderId(mainOrderId);
        record.setPromotionId(detail.getPromotionId());
        record.setPromotionLevel(detail.getPromotionLevel());
        record.setOrderAmount(detail.getOrderAmount());
        record.setDiscountAmount(detail.getDiscountAmount());
        record.setBearerType(detail.getBearerType());
        record.setCreateTime(new Date());
        orderPromotionMapper.insert(record);

        // 更新子订单优惠汇总字段
        orderMapper.addPromotionDiscount(detail.getOrderId(), detail.getDiscountAmount());
    }
}

五、上下游模块联动

满减满赠不是孤立的营销工具,必须和订单、分账、退款、库存等模块联动,才能保证账务一致。

5.1 分账模块联动

这是多商户场景最关键的联动点,优惠承担方不同,分账处理方式完全不同:

  • 商家承担的优惠:从商家货款中扣除,平台佣金按实付金额计算;
  • 平台承担的优惠:不减少商家货款,平台从佣金收入中抵扣,相当于平台补贴。

分账计算时,统一以「子订单实付金额」为基数,即原价 - 商家优惠 - 平台优惠分摊,再按佣金比例计算平台收入。

5.2 退款模块联动

退款时必须同步回退对应优惠,避免用户多次退款套取优惠:

  • 整单退款:该子订单对应分摊的优惠全额回退,活动名额释放;
  • 部分退款:按退款金额占比,等比例回退优惠金额;
  • 满赠退款:赠品需一并退回,否则扣除赠品金额。

5.3 库存模块联动

  • 满减、满折不影响库存,仅影响金额;
  • 满赠活动的赠品,需要单独扣减赠品 SKU 库存;用户退款时赠品退回,库存同步回补。

5.4 商家权限联动

  • 商家只能创建、查看、管理自己店铺的活动;
  • 平台可以查看全平台活动,执行审核、下架操作;
  • 所有状态变更留操作日志,全程可追溯。

5.5 定时任务联动

  • 活动定时开始、定时结束,自动更新状态;
  • 每日统计各活动的参与订单数、GMV、优惠总金额,输出报表;
  • 到期活动自动下架,不再参与下单计算。

六、平滑升级与兼容方案

6.1 升级步骤

  1. 执行新增表 SQL 与订单扩展字段 SQL,全部为新增操作,不破坏原生数据;
  2. 部署营销模块独立代码,原生订单、分账代码仅做少量扩展;
  3. 后台先灰度开放给部分商家试用,验证计算与分账准确性;
  4. 全量放开后,同步更新商家后台活动创建指引。

6.2 历史数据兼容

  • 历史订单没有活动关联记录,默认视为未参与活动,不影响原有数据;
  • 统计报表支持「含活动」「不含活动」两种口径,保证数据连续性;
  • 商家原有其他营销活动不受影响,叠加规则默认按原生逻辑执行。

6.3 官方版本升级兼容

  • 核心改造以新增表、新增服务为主,极少修改原生核心文件;
  • 官方版本迭代时,重点核对订单金额字段、分账计算逻辑是否有变动,同步适配即可;
  • 独立的计算服务层将营销逻辑与订单逻辑解耦,官方升级对二开部分的影响降到最低。

七、常见坑与避坑建议

1. 优惠叠加失控

现象:平台满减 + 店铺满减 + 优惠券同时生效,优惠总额超过商品金额,出现 0 元单甚至负金额。
避坑:建立叠加规则矩阵,每类活动都配置明确的互斥关系;下单计算前统一校验叠加权限,最终优惠金额设置下限保护。

2. 分摊精度误差

现象:各子订单分摊优惠相加,与总优惠金额差几分钱,导致分账对账对不上。
避坑:最后一个子订单用「总优惠 - 已分摊总额」倒算,把精度误差吸收在最后一笔;对账以优惠总金额为准,不以各子单相加为准。

3. 退款优惠回退不全

现象:用户退部分款,优惠金额没按比例扣回,相当于用户用更少的钱享受了同样的优惠。
避坑:部分退款严格按退款金额占原订单金额的比例,等比例回退优惠;回退后更新订单优惠快照记录。

4. 活动时间边界并发

现象:活动结束瞬间下单,计算时活动有效,提交时活动已结束,导致状态不一致。
避坑:下单扣减库存前,二次校验活动状态与时间;数据库层面用条件更新保证原子性。

5. 商家虚高原价再满减

现象:商家先抬高商品原价,再做满减活动,虚假促销引发投诉。
避坑:平台审核环节增加原价校验,对比商品历史均价;设置活动价格保护期,活动前一段时间内价格不得大幅上调。

结语

多商户的满减满赠,表面看是加个优惠计算,本质是「平台-商家-用户」三方的账务分配问题。

LikeShop 多商户高级版原生已经有扎实的订单与分账底座,二开时不用推翻重构,通过新增独立活动体系、统一计算入口、事件联动上下游的方式,可以用很低的侵入成本实现灵活的营销玩法,同时保证资金账务的准确性。

对二开团队来说,营销类功能最容易越写越乱,核心就是守住「计算统一入口、账务快照留痕、上下游事件联动」这三条原则。架构做清晰了,后续再加拼团、砍价、优惠券等更多营销工具,也都能有序地嵌套进这套体系里。

目录
相关文章
|
云安全 安全 网络安全
|
5天前
|
JavaScript 安全 前端开发
Nodejs Brotli decoder 代码执行漏洞 (CVE-2026-111497)10629
Node.js发布安全更新修复Brotli解码器高危RCE漏洞(CVE-2026-111497),建议受影响版本用户立即升级。
1926 1
|
11天前
|
人工智能 API 开发工具
DeepSeek Harness 神插件:白嫖 13 家大厂模型
`dsh-our-free-model` 是 DeepSeek Harness 神级插件:零配置、零API Key、零账号,聚合13家大厂免费模型(含DeepSeek V4.1 Flash、Kimi K3等),支持本地网关、用量看板与自动降级,真正实现“白嫖即用”。
1863 38
|
8天前
|
Web App开发 人工智能 前端开发
2026 年 AI 浏览器怎么选?Ego Lite、Tabbit、Chrome DevTools MCP 深度对比,开发者到底该用哪个?
2026年AI浏览器选型指南:Ego Lite专注多Agent协同(Mac优先),Tabbit国产首选,原生支持Windows与Codex控制;Chrome DevTools MCP非浏览器而是开发者利器,深度集成Console/Network/Performance调试能力,让AI真正“看懂并验证”前端代码。
1203 3
|
5天前
|
安全 应用服务中间件 Go
PHP2Shell 漏洞拆解:WordPress 与 nginx 为何中招
PHP2Shell 双 RCE 已遭在野利用:WordPress、Drupal 全在射程内,nginx 其实另有洞。拆成因与修复顺序
PHP2Shell 漏洞拆解:WordPress 与 nginx 为何中招
|
12天前
|
JSON 程序员 数据格式
最新版Notepad++下载、安装、配置保姆级教程(附官网安装包)
Notepad++ 是一款免费开源、轻量快速的 Windows 文本编辑器,支持多标签、语法高亮、代码折叠与宏录制,体积小、启动快、资源占用低,兼容 x64/x32/ARM 架构,是程序员和文字工作者的高效利器。
1792 0
|
12天前
|
安全 Linux 芯片
【万字长文】Ollama下载、安装、配置、使用保姆级教程(2026最新图解)
Ollama 是一款免费开源的本地大模型运行工具,支持 DeepSeek、Qwen 等主流模型,一条命令即可下载运行。2026 最新版 v0.35.1 官方安装包(1.47GB)已提供国内直连下载,纯本地、离线可用、无广告、无隐私泄露风险。
1143 4
|
16天前
|
存储 人工智能 安全
千问办公官网入口网页版登录:qwenwork.cn 注册送2000积分、登录送100积分
阿里云千问办公(QwenWork)是AI智能工作平台,支持一句话生成PPT、表格、网页、视频及数据分析。个人免费版送2000积分+每日登录赠100分,含1GB存储、5个发布页;企业版198元/席/月起。阿里千问办公QwenWork官网:https://t.aliyun.com/U/0VCTGt 阿里AI工作平台,一句话完成数据分析、PPT 生成、视频剪辑、网页搭建等复杂任务
|
4天前
|
安全 程序员 应用服务中间件
PHP 被曝两大致命漏洞,请所有程序员抓紧做这件事!
PHP 官方发布了紧急安全更新,一口气修复了两个严重漏洞。如果网站和服务器用了 PHP 应该怎么处理?漏洞的原理是什么?本文给你讲清楚
511 0

热门文章

最新文章