摘要很多秒杀二开问题都出在发布商品这一刻运营选了一个普通商品改了秒杀价和限量前台看起来能卖但真实库存、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)86400time()){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){$id0;unset($data[copy]);}二开建议做“活动复用模板”时也走复制逻辑不要原地改旧数据。4. Service 先校验原商品真正保存落在app/services/activity/seckill/StoreSeckillServices.php saveData()第一步是查原商品$storeProductServicesapp()-make(StoreProductServices::class);$productInfo$storeProductServices-getOne([is_del0,is_verify1,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. 秒杀价和活动限量来自 SKUService 会从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写进缓存$restrue;foreach($valueGroupas$item){if($item[quota_show]){$res$resCacheService::setStock($item[unique],(int)$item[quota_show]);}}if(!$res){thrownewAdminException(占用库存失败);}这就是秒杀发布“库存容易炸”的核心点之一活动 SKU 有自己的unique库存缓存也按这个unique处理。如果你二开了 SKU 生成规则、规格组合规则、复制商品规则一定要确认活动 SKU unique 是否唯一 活动 SKU 和普通商品 SKU 是否能通过 suk 对应 quota_show 是否写入缓存 下单扣库存时是否能找到普通商品 SKU10. 保存后要清缓存事务结束后会清理和刷新缓存$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_del1]);}之后会找到秒杀 SKU$unique$storeProductAttrValueServices-value([product_id$id,type1],unique);if($unique){$nameseckill_.$unique._1;$cacheapp()-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. 二开注意事项秒杀商品必须依赖正常普通商品不要允许回收站、未审核商品进入活动。SVIP、预售等特殊商品不要直接叠加秒杀除非重新设计价格、库存和履约规则。活动总限量quota和 SKU 限量quota_show要一起处理。活动 SKU 的unique和普通商品 SKU 的suk对应关系不能乱。已结束活动建议复制重建不建议原地改库存和价格。保存、删除、改状态后都要清商品属性缓存和秒杀缓存。标签建议CRMEB Pro CRMEB 二开 秒杀商品 SKU 库存 商城系统 源码解析 ThinkPHP