LikeShop 多商户高级版营销中心:拼团、砍价、秒杀活动的底层实现

简介: LikeShop多商户营销中心采用「基础活动抽象+子类型扩展+统一库存分账联动」架构,统一管控拼团、砍价、秒杀三大活动。通过独立库存池、原子扣减、状态机驱动、订单标记与分账联动等设计,解决超卖、分账错、状态不同步等高频痛点,显著降低维护成本。

多商户平台的营销中心,和单商户完全不是一个概念。

单商户的营销工具是商家自己配置、自己玩;多商户的营销中心是平台提供标准化工具,商家自主创建/报名、平台统一管控,同时还要和商家分账、商品库存、订单流转深度联动。很多做多商户系统的团队,一开始把营销做成「每个活动各写一套逻辑」,结果库存乱、分账错、活动状态不同步,维护成本极高。

LikeShop 多商户高级版的营销中心采用的是「基础活动抽象 + 子类型扩展 + 统一库存分账联动」的架构:拼团、砍价、秒杀共享底层能力,各自实现自身业务规则。这篇文章从通用架构、三个活动的核心实现、共通技术点三个层面拆解,附核心代码与表设计。

一、营销中心通用底层架构

所有营销活动本质上都有共性:绑定商品、设置活动价、占用库存、有时间周期、有状态流转、和订单挂钩。把这些共性抽到底层,子活动只做差异化扩展,是整套架构的核心思路。

1.1 活动抽象基表 + 子类型扩展

采用「基表存公共字段 + 扩展表存类型字段」的设计,避免每个活动重复写一遍商品、时间、状态、商家关联。

-- 营销活动基表(所有类型共用)
CREATE TABLE ls_market_activity (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  merchant_id BIGINT NOT NULL COMMENT '商家ID',
  activity_type TINYINT NOT NULL COMMENT '活动类型:1拼团 2砍价 3秒杀',
  activity_name VARCHAR(64) NOT NULL,
  goods_id BIGINT NOT NULL,
  sku_id BIGINT DEFAULT NULL,

  original_price DECIMAL(10,2) NOT NULL COMMENT '商品原价',
  activity_price DECIMAL(10,2) NOT NULL COMMENT '活动价',
  activity_stock INT NOT NULL COMMENT '活动库存',
  sold_count INT NOT NULL DEFAULT 0 COMMENT '已售数量',

  start_time DATETIME NOT NULL,
  end_time DATETIME NOT NULL,

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

  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)
);

每个活动类型再建一张扩展表,存自身特殊字段:

  • ls_market_groupbuy:拼团人数、团长优惠、成团时效;
  • ls_market_bargain:底价、助力次数、每人砍价区间;
  • ls_market_seckill:限购数量、秒杀场次、限流阈值。

这样设计的好处是:公共逻辑(库存扣减、状态流转、订单关联、分账计算)只写一遍,子活动只实现自身规则,新增活动类型时不用重复造轮子。

1.2 活动库存独立池

营销活动最容易出事故的就是库存。如果活动直接扣普通商品库存,要么活动卖超了影响正常发货,要么活动结束剩余库存没回退,商家后台对不上账。

通用底层采用活动独立库存池机制:

  1. 活动审核通过时,从普通 SKU 库存中划拨对应数量到活动库存;
  2. 活动下单只扣活动库存,不影响正常销售库存;
  3. 订单取消、退款、活动结束剩余,自动回退到普通库存。

库存扣减用条件更新保证原子性,这是防超卖的基础:

@Service
public class ActivityStockService {
   

    /**
     * 扣减活动库存(原子操作)
     * @return 受影响行数 > 0 表示扣减成功
     */
    public int deductStock(Long activityId, int num) {
   
        // 关键:WHERE 带上 stock >= num 条件
        // 数据库行锁保证并发安全
        return jdbcTemplate.update(
            "UPDATE ls_market_activity " +
            "SET activity_stock = activity_stock - ?, sold_count = sold_count + ? " +
            "WHERE id = ? AND activity_stock >= ?",
            num, num, activityId, num
        );
    }

    /**
     * 回退活动库存
     */
    public int refundStock(Long activityId, int num) {
   
        return jdbcTemplate.update(
            "UPDATE ls_market_activity " +
            "SET activity_stock = activity_stock + ?, sold_count = sold_count - ? " +
            "WHERE id = ?",
            num, num, activityId
        );
    }
}

1.3 订单标记与分账联动

活动订单必须在订单表打标记,否则后续结算、退款、统计都会乱。

订单表扩展字段:activity_id、activity_type、activity_price。下单时写入,全程跟随订单流转。

分账联动规则:

  • 平台佣金按订单实付金额计算,不按原价;
  • 活动让利部分由商家承担,平台不损失佣金;
  • 退款时按实付比例回退分账,和普通订单逻辑一致。

这样设计避免了一个常见坑:活动订单按原价分账,导致平台佣金减少、财务对账对不上。

1.4 活动状态机

所有活动共用一套状态流转,事件驱动,不允许非法跳转。

public enum ActivityStatus {
   
    DRAFT(0, "草稿"),
    PENDING_AUDIT(1, "待审核"),
    AUDIT_PASS(2, "审核通过"),
    NOT_START(3, "未开始"),
    RUNNING(4, "进行中"),
    ENDED(5, "已结束"),
    CLOSED(6, "已关闭");
}

标准流转路径:

草稿 → 提交审核 → 审核通过 → 未开始 → 时间到自动开始 → 进行中 → 时间到自动结束
                          ↓
                        审核驳回 → 商家修改重提

状态变更统一走 ActivityStateMachine,写日志、触发事件、联动库存,业务代码不允许直接改状态字段。

二、拼团活动的核心实现

拼团是社交属性最强的活动,核心是「成团机制」——人够就成,不够就散,自动退款。

2.1 核心表设计

-- 拼团活动扩展表
CREATE TABLE ls_market_groupbuy (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  activity_id BIGINT NOT NULL UNIQUE,
  group_size INT NOT NULL COMMENT '成团人数',
  leader_discount DECIMAL(5,2) COMMENT '团长额外折扣比例',
  group_timeout INT NOT NULL COMMENT '成团超时时间(小时)',
  INDEX idx_activity_id(activity_id)
);

-- 拼团记录(每个团一条)
CREATE TABLE ls_groupbuy_record (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  activity_id BIGINT NOT NULL,
  group_no VARCHAR(32) NOT NULL COMMENT '团号',
  leader_user_id BIGINT NOT NULL COMMENT '团长用户ID',
  current_num INT NOT NULL DEFAULT 1 COMMENT '当前人数',
  status TINYINT NOT NULL DEFAULT 1 COMMENT '1进行中 2已成团 3已解散',
  create_time DATETIME NOT NULL,
  success_time DATETIME COMMENT '成团时间',
  expire_time DATETIME NOT NULL,
  INDEX idx_activity_id(activity_id),
  INDEX idx_status(status)
);

-- 拼团成员
CREATE TABLE ls_groupbuy_member (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  group_id BIGINT NOT NULL,
  user_id BIGINT NOT NULL,
  order_id BIGINT NOT NULL,
  is_leader TINYINT NOT NULL DEFAULT 0 COMMENT '0成员 1团长',
  join_time DATETIME NOT NULL,
  UNIQUE KEY uk_group_user(group_id, user_id)
);

2.2 参团与成团逻辑

用户下单时选择「去开团」或「去参团」,支付成功后加入对应团:

@Service
public class GroupbuyService {
   

    @Transactional(rollbackFor = Exception.class)
    public void joinGroup(Long activityId, Long groupId, Long userId, Long orderId) {
   
        // 1. 校验活动状态
        MarketActivity activity = activityService.getById(activityId);
        if (activity.getStatus() != ActivityStatus.RUNNING.getCode()) {
   
            throw new BizException("活动未开始或已结束");
        }

        // 2. 扣活动库存
        int ok = activityStockService.deductStock(activityId, 1);
        if (ok == 0) {
   
            throw new BizException("活动库存不足");
        }

        // 3. 加入团
        GroupbuyRecord group = recordMapper.selectById(groupId);
        if (group.getStatus() != 1) {
   
            activityStockService.refundStock(activityId, 1);
            throw new BizException("该团已结束或已解散");
        }

        int affected = jdbcTemplate.update(
            "UPDATE ls_groupbuy_record SET current_num = current_num + 1 WHERE id = ? AND status = 1",
            groupId
        );
        if (affected == 0) {
   
            activityStockService.refundStock(activityId, 1);
            throw new BizException("加入失败,请重试");
        }

        // 4. 写入成员记录
        memberMapper.insert(/* 成员数据 */);

        // 5. 判断是否成团
        group = recordMapper.selectById(groupId);
        GroupbuyActivity ext = extMapper.getByActivityId(activityId);
        if (group.getCurrentNum() >= ext.getGroupSize()) {
   
            // 成团:更新状态、通知所有成员
            successGroup(groupId);
        }
    }
}

2.3 超时自动解散

定时任务每小时扫描超时未成团的团:

  • 团状态改为已解散;
  • 所有成员订单自动退款;
  • 活动库存回退;
  • 通知用户拼团失败已退款。

三、砍价活动的核心实现

砍价的核心是「裂变助力」——用户发起,好友帮忙砍,砍到低价后成交。难点在防刷和助力规则。

3.1 核心表设计

-- 砍价活动扩展表
CREATE TABLE ls_market_bargain (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  activity_id BIGINT NOT NULL UNIQUE,
  floor_price DECIMAL(10,2) NOT NULL COMMENT '砍后底价',
  max_help_count INT NOT NULL COMMENT '最多助力人数',
  min_cut DECIMAL(10,2) NOT NULL COMMENT '每人最少砍',
  max_cut DECIMAL(10,2) NOT NULL COMMENT '每人最多砍',
  record_timeout INT NOT NULL COMMENT '砍价记录有效期(小时)',
  INDEX idx_activity_id(activity_id)
);

-- 砍价记录(每个用户每次发起一条)
CREATE TABLE ls_bargain_record (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  activity_id BIGINT NOT NULL,
  user_id BIGINT NOT NULL,
  current_price DECIMAL(10,2) NOT NULL COMMENT '当前价格',
  help_count INT NOT NULL DEFAULT 0 COMMENT '已助力次数',
  status TINYINT NOT NULL DEFAULT 1 COMMENT '1进行中 2已下单 3已过期',
  create_time DATETIME NOT NULL,
  expire_time DATETIME NOT NULL,
  INDEX idx_activity_id(activity_id),
  INDEX idx_user_id(user_id)
);

-- 助力记录
CREATE TABLE ls_bargain_help (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  record_id BIGINT NOT NULL,
  helper_user_id BIGINT NOT NULL,
  cut_amount DECIMAL(10,2) NOT NULL COMMENT '砍掉金额',
  create_time DATETIME NOT NULL,
  UNIQUE KEY uk_record_helper(record_id, helper_user_id)
);

3.2 助力与防刷逻辑

助力是最容易被薅羊毛的环节,必须做多维度校验:

@Service
public class BargainHelpService {
   

    @Transactional(rollbackFor = Exception.class)
    public void help(Long recordId, Long helperUserId) {
   
        BargainRecord record = recordMapper.selectById(recordId);
        if (record.getStatus() != 1) {
   
            throw new BizException("砍价已结束或已过期");
        }

        // 1. 防重复:同一个人不能帮同一个砍价记录砍两次
        if (helpMapper.exists(recordId, helperUserId)) {
   
            throw new BizException("您已经帮TA砍过啦");
        }

        // 2. 防次数超限
        BargainActivity ext = extMapper.getByActivityId(record.getActivityId());
        if (record.getHelpCount() >= ext.getMaxHelpCount()) {
   
            throw new BizException("已达最大助力次数");
        }

        // 3. 随机砍价金额(区间内浮动,防固定金额脚本刷)
        BigDecimal cutAmount = randomCut(ext.getMinCut(), ext.getMaxCut());

        // 4. 不能砍到低于底价
        BigDecimal newPrice = record.getCurrentPrice().subtract(cutAmount);
        if (newPrice.compareTo(ext.getFloorPrice()) < 0) {
   
            cutAmount = record.getCurrentPrice().subtract(ext.getFloorPrice());
            newPrice = ext.getFloorPrice();
        }

        // 5. 更新砍价记录
        jdbcTemplate.update(
            "UPDATE ls_bargain_record SET current_price = ?, help_count = help_count + 1 WHERE id = ?",
            newPrice, recordId
        );

        // 6. 写助力记录
        helpMapper.insert(/* 助力数据 */);
    }

    private BigDecimal randomCut(BigDecimal min, BigDecimal max) {
   
        // 区间内随机,保留两位小数
        double range = max.subtract(min).doubleValue();
        double random = Math.random() * range + min.doubleValue();
        return BigDecimal.valueOf(random).setScale(2, RoundingMode.HALF_UP);
    }
}

3.3 过期与下单

  • 砍价记录有有效期,超时未下单自动失效;
  • 用户砍到心仪价格后,可直接下单,下单后扣活动库存,订单标记砍价活动。

四、秒杀活动的核心实现

秒杀的核心是高并发下的超卖防护。瞬间流量是平时的几十上百倍,库存扣减、接口限流、订单时效都要做专门设计。

4.1 秒杀库存池

秒杀采用独立预占库存:活动开始前,从普通库存划拨到秒杀库存池,活动结束剩余自动回退。

好处:

  • 不影响正常销售库存;
  • 超卖只在活动池内,风险可控;
  • 库存数量少,扣减性能更高。

4.2 限流与原子扣减

秒杀的技术重点在防超卖,两层防护:

第一层:入口限流
用令牌桶或计数器限流,超过阈值直接返回“活动火爆,请稍后再试”,避免大量请求打到数据库。

第二层:数据库条件更新
库存扣减用单行条件更新,数据库行锁保证原子性,这是最后一道防线。

@Service
public class SeckillService {
   

    /**
     * 秒杀下单扣库存
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean seckillOrder(Long activityId, Long userId, int num) {
   
        // 1. 限流校验(令牌桶)
        if (!rateLimiter.tryAcquire()) {
   
            throw new BizException("活动太火爆了,请稍后再试");
        }

        // 2. 限购校验
        if (getUserBuyCount(activityId, userId) + num > getLimitNum(activityId)) {
   
            throw new BizException("超出限购数量");
        }

        // 3. 原子扣库存(核心)
        int affected = jdbcTemplate.update(
            "UPDATE ls_market_activity " +
            "SET activity_stock = activity_stock - ?, sold_count = sold_count + ? " +
            "WHERE id = ? AND activity_stock >= ? AND status = ?",
            num, num, activityId, num, 
            ActivityStatus.RUNNING.getCode()
        );

        if (affected == 0) {
   
            throw new BizException("手慢了,库存不足");
        }

        // 4. 创建秒杀订单
        createSeckillOrder(activityId, userId, num);

        return true;
    }
}

4.3 订单超时释放

秒杀下单后必须设置短超时(通常 5~15 分钟),未付款自动取消,释放库存给其他人抢。

定时任务扫描超时未支付的秒杀订单:取消订单 → 回退活动库存 → 释放限购名额。

五、共通技术点与避坑

5.1 库存幂等与回滚

所有库存操作都要幂等,按订单维度去重。订单取消、退款、活动关闭,都要自动回退对应活动库存,避免库存不一致。

5.2 平台审核机制

商家创建活动后必须走平台审核:

  • 校验原价真实性,禁止虚高原价再打折;
  • 校验活动商品合规,禁售品不能做活动;
  • 校验活动库存,不能大于实际可用库存。

没做审核的话,商家乱标价格、虚假活动,最后投诉和合规风险都是平台的。

5.3 数据统计隔离

每个活动的销量、GMV、参与人数、转化率单独统计,商家后台只看自己的活动数据,平台后台看全平台。数据按 merchant_id 隔离,不能串。

5.4 常见坑

  1. 活动库存和普通库存混淆
    坑:活动直接扣普通库存,结束没回退,商家库存对不上。
    最佳实践:独立活动库存池,全链路闭环。

  2. 拼团/砍价退款不同步
    坑:订单退款了,活动库存没加回去,团人数没减。
    最佳实践:退款事件驱动库存回退、团记录更新。

  3. 秒杀没做原子扣减
    坑:先查后改,并发下超卖。
    最佳实践:条件更新 SQL,把判断和扣减合并成一条语句。

  4. 活动订单分账按原价
    坑:平台佣金按原价算,商家活动让利平台买单。
    最佳实践:一律按实付金额分账,让利商家承担。

  5. 活动状态不做校验
    坑:未开始、已结束的活动也能下单。
    最佳实践:所有操作前置状态校验,状态机统一流转。

结语

多商户营销中心看起来是三个独立的活动功能,本质是一套底层架构 + 三套业务规则。

通用的库存、订单、分账、状态机做扎实了,上面的拼团、砍价、秒杀都只是基于这套底座的规则扩展。LikeShop 多商户高级版的这套设计,核心就是把共性的东西抽到底层,子活动只实现自身差异化逻辑,无论是维护成本还是扩展效率,都比每个活动各写一套高很多。

对做多商户平台的团队来说,营销模块属于「看起来简单,做好很难」的部分。底层架构对了,加活动、加玩法都是顺理成章的事;底层乱了,每个活动都是坑。

目录
相关文章
|
云安全 安全 网络安全
|
4天前
|
JavaScript 安全 前端开发
Nodejs Brotli decoder 代码执行漏洞 (CVE-2026-111497)10629
Node.js发布安全更新修复Brotli解码器高危RCE漏洞(CVE-2026-111497),建议受影响版本用户立即升级。
1865 1
|
10天前
|
人工智能 API 开发工具
DeepSeek Harness 神插件:白嫖 13 家大厂模型
`dsh-our-free-model` 是 DeepSeek Harness 神级插件:零配置、零API Key、零账号,聚合13家大厂免费模型(含DeepSeek V4.1 Flash、Kimi K3等),支持本地网关、用量看板与自动降级,真正实现“白嫖即用”。
1738 37
|
4天前
|
安全 应用服务中间件 Go
PHP2Shell 漏洞拆解:WordPress 与 nginx 为何中招
PHP2Shell 双 RCE 已遭在野利用:WordPress、Drupal 全在射程内,nginx 其实另有洞。拆成因与修复顺序
PHP2Shell 漏洞拆解:WordPress 与 nginx 为何中招
|
7天前
|
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真正“看懂并验证”前端代码。
1046 2
|
12天前
|
安全 Linux 芯片
【万字长文】Ollama下载、安装、配置、使用保姆级教程(2026最新图解)
Ollama 是一款免费开源的本地大模型运行工具,支持 DeepSeek、Qwen 等主流模型,一条命令即可下载运行。2026 最新版 v0.35.1 官方安装包(1.47GB)已提供国内直连下载,纯本地、离线可用、无广告、无隐私泄露风险。
1095 2
|
11天前
|
JSON 程序员 数据格式
最新版Notepad++下载、安装、配置保姆级教程(附官网安装包)
Notepad++ 是一款免费开源、轻量快速的 Windows 文本编辑器,支持多标签、语法高亮、代码折叠与宏录制,体积小、启动快、资源占用低,兼容 x64/x32/ARM 架构,是程序员和文字工作者的高效利器。
1349 0
|
15天前
|
存储 人工智能 安全
千问办公官网入口网页版登录:qwenwork.cn 注册送2000积分、登录送100积分
阿里云千问办公(QwenWork)是AI智能工作平台,支持一句话生成PPT、表格、网页、视频及数据分析。个人免费版送2000积分+每日登录赠100分,含1GB存储、5个发布页;企业版198元/席/月起。阿里千问办公QwenWork官网:https://t.aliyun.com/U/0VCTGt 阿里AI工作平台,一句话完成数据分析、PPT 生成、视频剪辑、网页搭建等复杂任务
|
10天前
|
人工智能 开发者
Qoder CN 大使计划正式开启|无限创造,等你一起
全年免费积分、优先内测、活动支持与更多共创机会,邀请用 Qoder 把想法变成现实的你。
673 6