news 2026/6/16 12:17:57

CRMEB Pro 秒杀商品发布避坑:普通商品一上活动,库存和 SKU 为什么最容易炸?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CRMEB Pro 秒杀商品发布避坑:普通商品一上活动,库存和 SKU 为什么最容易炸?

摘要

很多秒杀二开问题都出在发布商品这一刻:运营选了一个普通商品,改了秒杀价和限量,前台看起来能卖,但真实库存、SKU 库存、活动库存、普通商品库存之间没有处理好。结果可能是活动商品库存卖完了,普通商品还有库存;或者普通商品库存不够,活动却还能继续下单。

CRMEB Pro 的秒杀商品发布不是复制一条商品记录那么简单。它会校验原商品状态、排除特殊商品、复制商品信息、复制 SKU、计算限量、写入活动描述,并把秒杀 SKU 的限量写进缓存。第二篇就拆这条链路。

1. 后台发布秒杀商品入口

秒杀商品后台 Controller 在:

app/controller/admin/v1/marketing/seckill/StoreSeckill.php

保存入口是save($id)

publicfunctionsave($id){$data=$this->request->postMore([[['product_id','d'],0],[['title','s'],''],[['info','s'],''],[['unit_name','s'],''],['images',[]],[['give_integral','d'],0],['section_time',[]],[['is_hot','d'],0],[['status','d'],0],[['num','d'],0],[['once_num','d'],0],[['time_id','d'],0],[['temp_id','d'],0],[['sort','d'],0],[['description','s'],''],['attrs',[]],['items',[]],['copy',0],['is_support_refund',1],['delivery_type',[]],['freight',1],['postage',0],['custom_form',''],['system_form_id',0],['product_type',0],]);$this->validate($data,\app\validate\admin\marketing\StoreSeckillValidate::class,'save');}

这里比较关键的字段是:

product_id 原普通商品 ID section_time 活动日期范围 time_id 秒杀时间段 num 每人总限购 once_num 单次下单限购 attrs/items 秒杀规格和 SKU copy 是否复制活动

二开时不要绕过这个 Controller 直接写秒杀表,因为发布秒杀商品后面还有一串库存、描述、SKU、缓存同步。

2. 保存前先拦过期活动和限购异常

Controller 里先判断活动结束日期:

if($data['section_time']){[$start_time,$end_time]=$data['section_time'];if(strtotime($end_time)+86400<time()){return$this->fail('活动结束时间不能小于当前时间');}}

注意这里用了+ 86400,也就是结束日期当天还算有效。比如结束日期是2026-06-16,活动会覆盖到当天 23:59:59 的语义。

再判断限购:

if($data['num']<$data['once_num']){return$this->fail('限制单次购买数量不能大于总购买数量');}

这两个校验很朴素,但非常关键。否则前台可能出现:

每人最多买 1 件 单次却允许买 3 件

下单校验再严格,也会让运营和用户看到矛盾配置。

3. 活动结束后不能直接编辑

如果是编辑旧活动,Controller 会读取原秒杀商品:

if($id){$seckill=$this->services->get((int)$id);if(!$seckill){return$this->fail('数据不存在');}}

然后限制编辑结束活动:

if($data['copy']==0&&$seckill){if(($seckill['stop_time']+86400)<time()){return$this->fail('活动已结束,请重新添加或复制');}}

这块适合做二开保护:已结束活动不要再改库存、价格、限购。因为历史订单已经产生,改旧活动很容易影响统计和售后追溯。

如果用户点“复制”,会重新创建:

if($data['copy']==1){$id=0;unset($data['copy']);}

二开建议:做“活动复用模板”时,也走复制逻辑,不要原地改旧数据。

4. Service 先校验原商品

真正保存落在:

app/services/activity/seckill/StoreSeckillServices.php saveData()

第一步是查原商品:

$storeProductServices=app()->make(StoreProductServices::class);$productInfo=$storeProductServices->getOne(['is_del'=>0,'is_verify'=>1,'id'=>$data['product_id'],]);if(!$productInfo){thrownewAdminException('原商品已移入回收站');}

这说明秒杀商品必须依赖一个正常、已审核、未删除的普通商品。

接着排除特殊商品:

if($productInfo['is_vip_product']){thrownewAdminException("【{$productInfo["store_name"]}】是svip专享");}if($productInfo['is_presale_product']){thrownewAdminException("【{$productInfo["store_name"]}】是预售商品");}

为什么要排除?因为 SVIP 专享、预售本身就有独立价格、权益或履约规则。直接叠加秒杀,会让价格、库存、发货、售后规则变复杂。

5. 普通商品字段会被搬到秒杀商品

Service 会把部分原商品字段复制过来:

$data['product_type']=$productInfo['product_type'];$data['type']=$productInfo['type']??0;$data['relation_id']=$productInfo['relation_id']??0;$custom_form=$productInfo['custom_form']??[];$data['custom_form']=is_array($custom_form)?json_encode($custom_form):$custom_form;$data['system_form_id']=$productInfo['system_form_id']??0;

还会处理标签、保障服务、参数规格:

$store_label_id=$productInfo['store_label_id']??[];$data['store_label_id']=is_array($store_label_id)?implode(',',$store_label_id):$store_label_id;$ensure_id=$productInfo['ensure_id']??[];$data['ensure_id']=is_array($ensure_id)?implode(',',$ensure_id):$ensure_id;$specs=$productInfo['specs']??[];$data['specs']=is_array($specs)?json_encode($specs):$specs;

二开时如果新增商品字段,要先判断它属于哪一类:

展示字段:可以跟随普通商品复制 履约字段:要确认秒杀是否允许继承 价格字段:通常不应该直接继承 库存字段:必须单独处理 权限字段:要重新校验

6. 运费规则会按商品类型重算

如果是虚拟、卡密、核销等特殊商品类型,运费会被强制归零:

if(in_array($data['product_type'],[1,2,3])){$data['freight']=2;$data['temp_id']=0;$data['postage']=0;}else{if($data['freight']==1){$data['temp_id']=0;$data['postage']=0;}elseif($data['freight']==2){$data['temp_id']=0;}elseif($data['freight']==3){$data['postage']=0;}}

并且会校验:

if($data['freight']==2&&!$data['postage']){thrownewAdminException('请设置运费金额');}if($data['freight']==3&&!$data['temp_id']){thrownewAdminException('请选择运费模版');}

很多人做秒杀只盯价格和库存,忽略配送方式。实际上,秒杀订单最后也要走订单履约,运费字段不完整会在下单或支付前暴露。

7. 秒杀价和活动限量来自 SKU

Service 会从attrs里计算秒杀价、划线价和限量:

$description=$data['description'];$detail=$data['attrs'];$items=$data['items'];$data['start_time']=strtotime($data['section_time'][0]);$data['stop_time']=strtotime($data['section_time'][1]);$data['image']=$data['images'][0]??'';$data['images']=json_encode($data['images']);$data['price']=min(array_column($detail,'price'));$data['ot_price']=min(array_column($detail,'ot_price'));$data['quota']=$data['quota_show']=array_sum(array_column($detail,'quota'));$data['stock']=array_sum(array_column($detail,'stock'));

这里有个重点:秒杀活动限量不是商品总库存,而是所有 SKU 的quota求和。

然后会判断活动限量不能超过普通商品库存:

if($data['quota']>$productInfo['stock']){thrownewAdminException('限量不能超过商品库存');}

二开时如果要做“每个 SKU 独立限量”,不能只改主表quota,还要同步改 SKU 的quota和下单校验。

8. 事务里保存商品、详情和 SKU

保存动作在事务里完成:

$id=$this->transaction(function()use($id,$data,$description,$detail,$items,$storeDescriptionServices,$storeProductAttrServices){if($id){$res=$this->dao->update($id,$data);if(!$res){thrownewAdminException('修改失败');}}else{$data['add_time']=time();$res=$this->dao->save($data);if(!$res){thrownewAdminException('添加失败');}$id=(int)$res->id;}$storeDescriptionServices->saveDescription((int)$id,$description,1);$storeProductAttrServices->setItem('store_product_id',$data['product_id']);$skuList=$storeProductAttrServices->validateProductAttr($items,$detail,(int)$id,1);$storeProductAttrServices->reset();$valueGroup=$storeProductAttrServices->saveProductAttr($skuList,(int)$id,1);return$id;});

这里的type = 1表示秒杀商品规格,普通商品一般是type = 0。这能让普通商品 SKU 和活动商品 SKU 分开存储。

9. 发布时会把活动 SKU 限量写入库存缓存

保存完 SKU 后,会把quota_show写进缓存:

$res=true;foreach($valueGroupas$item){if($item['quota_show']){$res=$res&&CacheService::setStock($item['unique'],(int)$item['quota_show']);}}if(!$res){thrownewAdminException('占用库存失败');}

这就是秒杀发布“库存容易炸”的核心点之一:活动 SKU 有自己的unique,库存缓存也按这个unique处理。

如果你二开了 SKU 生成规则、规格组合规则、复制商品规则,一定要确认:

活动 SKU unique 是否唯一 活动 SKU 和普通商品 SKU 是否能通过 suk 对应 quota_show 是否写入缓存 下单扣库存时是否能找到普通商品 SKU

10. 保存后要清缓存

事务结束后会清理和刷新缓存:

$this->dao->cacheTag()->clear();$seckill=$this->dao->get($id,['*'],['descriptions']);$this->dao->cacheUpdate($seckill->toArray());CacheService::redisHandler('product_attr')->clear();

这说明秒杀商品不是保存完数据库就结束。前台详情、SKU、列表都可能走缓存。二开时如果新增字段没有出现在前台,先检查是不是缓存没更新,而不是马上怀疑前端。

11. 删除秒杀商品也要清 SKU 缓存

后台删除秒杀商品时不是物理删除,而是标记:

publicfunctiondelete($id){if(!$id){return$this->fail('缺少参数');}$this->services->update($id,['is_del'=>1]);}

之后会找到秒杀 SKU:

$unique=$storeProductAttrValueServices->value(['product_id'=>$id,'type'=>1],'unique');if($unique){$name='seckill_'.$unique.'_1';$cache=app()->make(CacheService::class);$cache->del($name);}

并清掉单个活动缓存:

$this->services->cacheDelById($id);CacheService::redisHandler('product_attr')->clear();

二开删除逻辑时,不要只改is_del,还要清理秒杀缓存和商品属性缓存。

12. 关键代码/目录说明

app/controller/admin/v1/marketing/seckill/StoreSeckill.php 后台秒杀商品列表、保存、删除、状态修改、统计入口。 app/services/activity/seckill/StoreSeckillServices.php 秒杀商品保存、复制 SKU、库存缓存、前台列表详情、下单校验。 app/dao/activity/seckill/StoreSeckillDao.php 秒杀商品查询、按时间段查询、活动状态过滤。 app/services/product/sku/StoreProductAttrServices.php 规格组合校验、活动商品 SKU 保存。 app/services/product/sku/StoreProductAttrValueServices.php SKU 库存、unique、suk 关联处理。 app/services/product/product/StoreProductServices.php 普通商品校验和普通商品库存扣减入口。

13. 二开注意事项

  1. 秒杀商品必须依赖正常普通商品,不要允许回收站、未审核商品进入活动。
  2. SVIP、预售等特殊商品不要直接叠加秒杀,除非重新设计价格、库存和履约规则。
  3. 活动总限量quota和 SKU 限量quota_show要一起处理。
  4. 活动 SKU 的unique和普通商品 SKU 的suk对应关系不能乱。
  5. 已结束活动建议复制重建,不建议原地改库存和价格。
  6. 保存、删除、改状态后都要清商品属性缓存和秒杀缓存。

标签建议

CRMEB Pro CRMEB 二开 秒杀商品 SKU 库存 商城系统 源码解析 ThinkPHP
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/16 12:14:51

解锁RDP Wrapper极致性能:5个专业配置让远程桌面速度提升300%

解锁RDP Wrapper极致性能&#xff1a;5个专业配置让远程桌面速度提升300% 【免费下载链接】rdpwrap RDP Wrapper Library 项目地址: https://gitcode.com/gh_mirrors/rd/rdpwrap RDP Wrapper Library是突破Windows远程桌面限制的开源神器&#xff0c;让技术爱好者和系统…

作者头像 李华
网站建设 2026/6/16 12:11:23

微信自动回复太慢?聊聊后端多级缓存与防击穿的硬核优化

在研发公司的智能 CRM、自动化私域中台或客服系统时&#xff0c;个人微信自动回复是一个核心组件。 很多后端研发在做关键词自动回复时&#xff0c;逻辑写得很直白&#xff1a;收到用户的消息 Webhook&#xff0c;去数据库里 SELECT 匹配一下关键词&#xff0c;命中就调用接口…

作者头像 李华
网站建设 2026/6/16 12:10:55

MyBatis-Plus 源码分析-多租户支持原理深度解析

文章目录 一、概述 二、多租户支持整体架构 2.1 项目结构 2.2 核心组件关系 2.3 拦截器链执行顺序 三、租户行过滤器(TenantLineInnerInterceptor)详解 3.1 工作原理 3.2 四类 SQL 语句的差异化处理 SELECT 语句处理 INSERT 语句处理 UPDATE 语句处理 DELETE 语句处理 3.3 表…

作者头像 李华
网站建设 2026/6/16 12:02:52

建筑排烟分区挡烟垂壁安装工艺及施工技术规范

一、编制依据与适用范围 1.1 现行强制规范 1. GB 51251-2017《建筑防烟排烟系统技术标准》&#xff08;防烟分区、安装、联动强制条文&#xff09; 2. GA 533-2012《挡烟垂壁》&#xff08;产品材质、尺寸、运行、密封、标识强制标准&#xff09; 3. GB 50116《火灾自动报警系统…

作者头像 李华