帝国CMS商城SKU_2.0实战:规格组合、库存扣减与订单联动
2026/9/14 3:59:38 网站建设 项目流程

简介:帝国CMS商城SKU_2.0是一份针对帝国CMS商城系统的SKU功能升级资源包,主要面向需要搭建或改造网店后台的开发者、运维者,解决传统商城难以高效管理商品多规格(如颜色、尺寸、材质),以及由此带来的库存同步与订单自动匹配问题。整个压缩包共16个文件,以PHP业务逻辑文件为主体,配合JS交互、CSS样式和PNG图片资源,另有txt修改说明与doc安装文档;包体仅237KB,结构精简,便于本地对照修改与测试。目前已有107人学习/下载。资源中提供了sku字段输入表单替换代码、订单处理文件改动方案,并覆盖购物车、订单展示、内容发布等多个环节的PHP修改参考,可直接对照现有站点代码进行调整,帮助熟悉帝国CMS的开发者高效完成SKU 2.0功能的集成与二次开发。

1. 帝国CMS商城SKU_2.0要解决的规格与库存痛点

帝国CMS自带商品模型是「一商品一价格一库存」:价格库存是主表数字字段,规格只能塞进正文。卖衣服、手机壳,顾客要先选颜色再选尺码,价格库存跟着组合变,这就是SKU。SKU_2.0不是加个规格字段,而是把每条组合拆成独立货号、价格、库存和图片——红XL缺货但红S有货,单品级控制是商城区别于文章系统的核心。

光靠帝国CMS后台字段管理做不出这套交互,常见做法是挂一张独立SKU子表,再用后台维护页生成组合。下文按一线实施顺序讲:数据表与spec_key设计、组合生成的PHP、前台选择器与下单联动、库存扣减与一致性检查,可直接照抄,也可用于外包验收查漏。

2. 帝国CMS商城SKU_2.0的数据表结构与spec_key设计

2.1 主表扩展:sku_enable与sku_specs两个字段

在帝国CMS后台「管理数据表→字段管理」里,给商品模型的主表加两个字段,一个开关一个定义:

字段名类型用途
sku_enable单选字段,0/1该商品是否走SKU逻辑,0保持原单商品流程
sku_specsTEXT字段规格维度定义,内容为JSON数组

sku_specs里存的是规格的"骨架":每个维度一个对象,name是维度名,options是该维度下的可选值。比如颜色和尺码两个维度:

[ {"name": "颜色", "options": ["黑", "白"]}, {"name": "尺码", "options": ["S", "M", "L"]} ]

字段类型别选帝国CMS的「编辑器」类型,编辑器会包一层HTML,取值还得strip_tags;用纯TEXT文本字段,入库前自己json_encode,读取时json_decode。选择这个结构而不是拆成「颜色字段+尺码字段」,是因为各商品的维度数量和维度名都不一样,没有一套固定列能通用,JSON是二维规格结构下最少妥协的存储方案。

2.2 独立SKU子表:组合、价格、库存、货号一列不缺

维度定义是骨架,真正的价格和库存存在独立子表phome_ecms_sku_list里,不挂在帝国CMS模型上。建表SQL直接进 phpMyAdmin 或帝国CMS后台的SQL执行器跑:

CREATE TABLE phome_ecms_sku_list ( sku_id INT UNSIGNED NOT NULL AUTO_INCREMENT, product_id INT UNSIGNED NOT NULL DEFAULT '0' COMMENT '商品主表ID', spec_json VARCHAR(255) NOT NULL DEFAULT '' COMMENT '规格组合 {"颜色":"黑","尺码":"L"}', spec_key VARCHAR(100) NOT NULL DEFAULT '' COMMENT '规格组合KEY 黑:L', price DECIMAL(10,2) NOT NULL DEFAULT '0.00' COMMENT '销售价', market_price DECIMAL(10,2) NOT NULL DEFAULT '0.00' COMMENT '市场价', stock INT UNSIGNED NOT NULL DEFAULT '0' COMMENT '库存', sku_code VARCHAR(50) NOT NULL DEFAULT '' COMMENT '货号', sku_image VARCHAR(200) NOT NULL DEFAULT '' COMMENT '规格独立图', is_default TINYINT(1) NOT NULL DEFAULT '0', PRIMARY KEY (sku_id), UNIQUE KEY uk_product_spec (product_id, spec_key), KEY idx_product (product_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='帝国CMS商城SKU子表';

逐列说明。spec_json直接存中文KV,订单展示、后台回显都能直接读,不用join规格维度表;spec_key是后半段要讲的连接键,配合product_id的唯一索引,保证重复提交组合不会生成两行;sku_code是货号,线下库存和ERP对账全靠它;sku_image让红色配红图、白色配白图,这是1.0最容易漏、2.0必须留位的列;is_default标记默认选中组合,详情页首次进入时用它展示价格。价格列用DECIMAL(10,2)而不用FLOAT,浮点在累加和比较时有精度问题,商城金额对账会出分差,这个没有商量余地。

2.3 spec_key拼串规则:三端一致的连接键

spec_key是本方案里最该提前定死的规范。后台生成组合、前台JS选择、AJAX接口匹配,三处都要拼出同一个字符串,规则定死为两条:按sku_specs定义的维度顺序取值,用冒号连接且维度名不写入key。「颜色+尺码」两个维度下,黑色L码的key就是黑:L。维度名不进key,key才短,五个维度的规格也不会拼出上百字符的查询串。

也有风险:不同维度出现同名选项时,比如颜色黑和材质黑,黑:黑无法区分维度来源。所以spec_json始终保留完整维度名,spec_key只用于匹配。三端只要有一端改了规则,现象就是「规格选完永远提示缺货」。排查时不用猜,把PHP拼的key和JS拼的key各打一个console日志,肉眼对比比翻SQL快得多。

提示:spec_key的拼接规则一定要写进项目的接口约定文档。换人维护后最容易挂的地方不是复杂SQL,而是前端按自己想法改了拼接顺序。

3. 帝国CMS商城SKU_2.0组合生成的PHP实现与后台接入

3.1 用笛卡尔积把规格维度展开成组合

后台拿到sku_specs之后,第一件事是把两个维度展开成全部组合。常见做法是数组归约做笛卡尔积,不搞递归——维度数超过三层时递归的可读性明显变差,且每一步都要维护中间栈:

function generateSkuCombinations(array $specs): array { $combos = [[]]; foreach ($specs as $spec) { $next = []; foreach ($combos as $combo) { foreach ($spec['options'] as $opt) { $next[] = $combo + [$spec['name'] => $opt]; } } $combos = $next; } return $combos; }

逻辑说明:$combos初始为只含一个空数组的集合;每处理一个维度,就把该维度的每个选项追加到已有组合后面,生成下一轮的全部组合。循环结束后组合数是各维度选项数的乘积,2个颜色×3个尺码生成6条。拿到组合后再按固定维度顺序拼spec_key:

$defs = array_map(fn($s) => $s['name'], $specs); $key = implode(':', array_map(fn($def) => $combo[$def], $defs));

这里不依赖$combo的自然键顺序,而是显式用$defs取维度名,因为json_decode解析出来的关联数组,遍历顺序不一定等于写入时的顺序。显式按维度名取值,key才在各种服务器环境下保持一致。组合数超过1000时,生成函数本身不是瓶颈,瓶颈在后面的逐条INSERT。真遇到 10×10×10 这种三维大规格,先在前端限制每个选项数量,生成前做一次笛卡尔积规模提示,别让运营把一个商品撑出上万条SKU行。

3.2 组合写入用UPSERT,不要先删后插

组合表格提交回来后,老写法是「DELETE WHERE product_id 删光再循环INSERT」。两个问题:删和插之间SKU行是缺失的,并发下前台会间歇性拿不到价格;插入中途报错,整张SKU表就剩半套数据。SKU_2.0的常见做法是INSERT ... ON DUPLICATE KEY UPDATE

$stmt = $pdo->prepare( 'INSERT INTO phome_ecms_sku_list (product_id, spec_json, spec_key, price, market_price, stock, sku_code, sku_image) VALUES (:pid, :json, :key, :price, :market, :stock, :code, :img) ON DUPLICATE KEY UPDATE price = VALUES(price), market_price = VALUES(market_price), stock = VALUES(stock), sku_code = VALUES(sku_code), sku_image = VALUES(sku_image)' ); foreach ($combos as $i => $combo) { $stmt->execute([ ':pid' => $productId, ':json' => json_encode($combo, JSON_UNESCAPED_UNICODE), ':key' => buildSkuKey($combo, $defs), ':price' => (float)$_POST['price'][$i], ':market' => (float)$_POST['market_price'][$i], ':stock' => (int)$_POST['stock'][$i], ':code' => trim($_POST['sku_code'][$i]), ':img' => trim($_POST['sku_image'][$i]), ]); }

参数说明:表单里每个SKU行的input统一用price[]stock[]sku_code[],下标和$combos一一对应,后台JS排序后不会串行;UPSERT依赖建表时的uk_product_spec唯一索引触发,spec_key拼错一行就会整批插歪;图片字段收的是帝国CMS上传组件返回的路径字符串,直接入库,不做附件表关联,删除SKU时记得手动清图片文件。写入后顺带清理被删规格的孤儿行——查子表里 product_id 相同但 spec_key 不在本次提交集合中的记录,库存为0的直接DELETE,有库存的先置0再提示人工确认。

组合写入失败就两类原因,按表排查:

失败现象原因处理
报主键/唯一键冲突重复提交了表单换UPSERT语句,别绕道先删后插
同一组合出现两行前后端spec_key规则不一致统一走buildSkuKey,不手拼字符串
销售价为0.00运营漏填或传了空串表单前端校验+后台二次过滤

3.3 SKU维护页挂进帝国CMS后台菜单

后台维护入口的常见做法是单独建目录e/admin/sku/,页面头部按帝国CMS后台规范加载,和官方后台文件保持同套加载方式:

define('EmpireCMSAdmin', 1); require('../class/connect.php'); require('../class/functions.php'); require('../class/db_sql.php'); require('../class/admin.php');

然后在帝国CMS「系统设置→后台菜单」里加一条菜单记录,URL指到sku/index.php?product_id=xx。这套方式的好处是不改动帝国CMS的核心文件,系统升级时不会冲突。另一种方案是在商品模型的提交处理文件末尾挂重生成逻辑,按下单入口走一遍组合生成——我不建议这么做:规格编辑通常是商品发布后的独立运营动作,绑在保存商品上会把发布流程拖慢,而且模型保存文件升级时容易被官方包覆盖,散落的自改代码不好维护。

4. 帝国CMS商城SKU_2.0前台选择器与AJAX接口联动

4.1 getsku接口的返回结构与参数校验

前台需要一个接口用「product_id + spec_key」查价格和库存。最小实现放在帝国CMS的e/action/目录下,一个PHP文件完事:

require('../class/connect.php'); require('../class/db_sql.php'); header('Content-Type: application/json; charset=utf-8'); $pid = intval($_REQUEST['product_id'] ?? 0); $key = trim((string)($_REQUEST['spec_key'] ?? '')); if ($pid <= 0 || $key === '') { echo json_encode(['code' => 400, 'msg' => '参数错误']); exit; } $key = addslashes($key); $row = $empire->fetch1( 'SELECT price, market_price, stock, sku_code, sku_image FROM phome_ecms_sku_list WHERE product_id=' . $pid . ' AND spec_key=\'' . $key . '\' LIMIT 1' ); echo $row ? json_encode(['code' => 0, 'data' => $row], JSON_UNESCAPED_UNICODE) : json_encode(['code' => 404, 'msg' => 'SKU不存在'], JSON_UNESCAPED_UNICODE);

几个细节:product_id用intval强转、spec_key走addslashes,两个入参都不能直接拼SQL;返回结构固定 code/data/msg,商城接口测试用例就三条——正常组合code=0、不存在的组合code=404、参数非法code=400,前端三种分支都能落到具体UI提示。这个接口设计成无状态JSON后,小程序商城和vue3商城都能直接复用同一份后端,只要前端自己包一层请求封装,规格逻辑不用动。

4.2 规格选择器状态推导与置灰逻辑

帝国CMS详情页模板里直接嵌JS,选择器按维度渲染。核心状态只有两个:已选维度数和当前库存。拼key时按后端同顺序取值:

function currentSpecKey() { return dims.map(name => { const input = document.querySelector(`input[name="spec[${name}"]:checked`); return input ? input.value : ''; }).join(':'); }

参数说明:dims数组来自模板端把sku_specs直接json_encode到script变量;这里故意保留空字符串占位而不过滤,只要key里有一个空段,就和后端所有key都对不上,接口返回404。比起自建「规格未选完」判断,这少一套特殊状态,未选时只展示价格区间:

async function refreshSku() { const key = currentSpecKey(); if (key.split(':').some(v => v === '')) { priceEl.innerText = rangeText; // 来自SKU列表缓存的最低-最高价 return; } const res = await fetch(`/e/action/getsku.php?product_id=${pid}&spec_key=${encodeURIComponent(key)}`); const json = await res.json(); if (json.code === 0) { priceEl.innerText = '¥' + json.data.price; submitBtn.disabled = json.data.stock === 0; stockEl.innerText = json.data.stock > 0 ? '库存' + json.data.stock : '缺货'; } }

rangeText在首次加载详情页时用万能标签把该商品SKU的最低和最高价直接输出到模板变量,不额外发请求。置灰逻辑放在选中每个选项时做:把「已选值+候选值」拼成key,去后台一次性返回的合法SKU集合里查存在性,不存在就给按钮加disabled类。不要每次都发AJAX探库存,选项多的时候请求量是组合数的平方,先本地判定再发最终请求,体验和开销都可控。

4.3 sku_id进购物车和订单的冗余设计

组合确定后,把sku_id写进隐藏域随加购表单提交。购物车表增加sku_id列,并对(userid, product_id, sku_id)建唯一索引:同一商品不同规格是两条独立购物车记录,同规格重复加购走数量累加。订单侧不能只存sku_id,商品改价、SKU下架都是常态,历史订单必须冗余下单时的快照:

冗余字段写入时机使用场景
sku_snapshot下单时json_encode规格文本订单详情回显、售后核对
price_snapshot下单成交价金额以快照为准,不受改价影响
sku_code_snapshot下单货号出库、对账

这层冗余是SKU商城最容易偷懒的地方。没有快照的话,三个月后商品改价,翻订单详情要去SKU表捞现价,捞不到就白屏。2.0把快照写进订单子表,比只在购物车存sku_id的方案多三列,但把「历史事实」和「当前状态」彻底分开,这是商城数据设计的分水岭。

5. 帝国CMS商城SKU_2.0的库存扣减与一致性检查

5.1 SKU子表缓存的写入与失效

详情页每个PV都查SKU子表,流量一上来就是浪费。常见做法是按商品维度整组缓存:Redis里存sku:{product_id},hash的字段是spec_key,值放价格库存的紧凑JSON。TTL设600秒兜底,后台保存SKU时主动删缓存,运营改价后前台立即可见。顺序必须是「先写库、再删缓存」,反过来会出现缓存新、库里旧的不一致。

5.2 条件UPDATE扣减,Redis预扣只做挡板

下单扣库存的底线是条件更新,严格禁止先在PHP里查库存再手动减一:

UPDATE phome_ecms_sku_list SET stock = stock - 1 WHERE sku_id = ? AND stock >= 1

affected_rows为1才继续创建订单,为0直接返回缺货,这条UPDATE是防超卖的最后一道闸。秒杀场景可以加Redis预扣把大部分请求挡在应用层,但预扣和真实扣减之间必然有差,靠定时对账拉平,不能拿Redis的计数当真库存。

5.3 每天跑一遍的SKU一致性检查SQL

上线后最实用的是几张检查SQL。先查出启用了SKU但子表没有组合行的商品:

SELECT p.id, p.title FROM phome_ecms_shop p LEFT JOIN phome_ecms_sku_list s ON s.product_id = p.id WHERE p.sku_enable = 1 GROUP BY p.id HAVING COUNT(s.sku_id) = 0;

再查销售价高于市场价、售价小于1元、同商品货号重复的行,把结果写进每天早上9点的计划任务输出到运营群。SKU_2.0真正跑稳的标志不是功能上线那天,而是这套检查连续一个月零报错。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询