
简介本资源为基于PHPMySQL实现的服装商城购物系统课程设计项目编号100013121面向计算机相关专业学生、Web开发初学者及需要完成课程设计或毕业设计的开发者。系统涵盖用户注册登录、男装女装及推荐商品浏览、购物车增删改查、后台订单与库存管理等模块并涉及数据库表结构设计、SQL查询优化、预编译防注入、响应式布局等关键知识点可作为学习PHP动态网站开发与MySQL数据库应用的完整实践案例。压缩包共260个文件包含10个PHP核心页面、1个SQL建库脚本、9个HTML与9个CSS样式文件、21个JavaScript脚本以及大量jpg、png、gif图片素材和字体文件另附项目说明书文档整体约5.54MB目录结构清晰便于按模块查阅与二次开发。目前已有393人学习下载适合需要完整项目源码、数据库脚本与开发文档的读者参考借鉴。1. 服装商城购物系统从建表到下单PHPMySQL 这套组合还能怎么打服装商城购物系统这个题目几乎每个 PHP 从业者都在简历或接单列表里见过。它不新鲜但每年仍有大量中小商家、独立站卖家、校园项目在用它跑真实业务——因为 PHPMySQL 的部署成本低到一台 2 核 4G 的机器就能撑起日均几千单而服装这个品类天然适合「多 SKU、多尺码、多颜色」的库存模型正好把关系型数据库的强项压满。问题在于网上流传的所谓「PHP 服装商城源码」大多是半成品商品表没有规格维度下单不锁库存支付回调不验签上线三天就被薅穿。这篇笔记不讲概念只讲一套能真正跑起来的服装商城购物系统该怎么设计表、怎么写核心链路、怎么避开那些让项目翻车的坑。适合手里有 PHP 基础、想接服装类私活或做独立站但被库存和订单一致性折磨过的开发者。2. 服装商城的表结构为什么通用电商模板一上来就错服装商城和普通电商最大的区别在 SKU 维度。一件 T 恤有 S/M/L/XL 四个尺码每个尺码又有黑/白/灰三种颜色组合起来就是 12 个可售单元。通用电商模板通常只建goods和goods_attr两张表把规格塞进一个 JSON 字段结果就是库存扣减时无法行级锁定超卖成了必然。我一般会拆成四张表productSPU 层、product_skuSKU 层带独立库存和价格、attribute属性字典、sku_attributeSKU 与属性的映射。这样每个 SKU 有独立主键扣库存时UPDATE ... WHERE sku_id ? AND stock ?就能靠 InnoDB 行锁保证原子性。2.1 四张核心表的字段设计与索引取舍先看 SPU 表它只存商品公共信息不存任何规格相关字段CREATE TABLE product ( id int unsigned NOT NULL AUTO_INCREMENT, title varchar(120) NOT NULL COMMENT 商品标题, category_id int unsigned NOT NULL DEFAULT 0, main_image varchar(255) NOT NULL DEFAULT , detail text COMMENT 详情HTML, status tinyint NOT NULL DEFAULT 1 COMMENT 1上架 0下架, created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category_status (category_id,status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;SKU 表是整套系统的命脉库存、价格、销量都挂在这里CREATE TABLE product_sku ( id int unsigned NOT NULL AUTO_INCREMENT, product_id int unsigned NOT NULL, sku_code varchar(64) NOT NULL COMMENT 商家编码, price decimal(10,2) NOT NULL DEFAULT 0.00, stock int NOT NULL DEFAULT 0, sales int NOT NULL DEFAULT 0, spec_json json DEFAULT NULL COMMENT 冗余规格快照便于展示, PRIMARY KEY (id), UNIQUE KEY uk_sku_code (sku_code), KEY idx_product (product_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;属性字典和映射表负责把「颜色:黑」这种可读文本和 SKU 关联起来CREATE TABLE attribute ( id int unsigned NOT NULL AUTO_INCREMENT, name varchar(32) NOT NULL COMMENT 如 颜色/尺码, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE sku_attribute ( sku_id int unsigned NOT NULL, attr_id int unsigned NOT NULL, value varchar(32) NOT NULL COMMENT 如 黑色/XL, PRIMARY KEY (sku_id,attr_id), KEY idx_attr_value (attr_id,value) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;参数说明spec_json是冗余字段下单时直接把它快照进订单表避免商品改规格后历史订单显示错乱sku_code加唯一索引防止运营重复导入stock用int而非unsigned因为退货回补时可能出现临时负数靠业务层拦截比数据库报错更可控。索引上idx_category_status支撑前台分类列表idx_product支撑商品详情页拉取所有 SKU这两个查询是服装商城最高频的读操作必须走覆盖索引。2.2 用一条 SQL 拉出商品详情页的全部规格前台详情页需要一次性拿到 SPU 信息、所有 SKU、以及每个 SKU 的属性组合。常见做法是三次查询在 PHP 里拼装但服装商城 SKU 多三次查询在并发下容易放大延迟。我一般用两条 SQL 解决先查 SPU再用IN批量查 SKU 和属性。?php // $pdo 为已建立的 PDO 连接字符集 utf8mb4 $productId (int)$_GET[id]; $stmt $pdo-prepare(SELECT id,title,main_image,detail FROM product WHERE id? AND status1); $stmt-execute([$productId]); $product $stmt-fetch(PDO::FETCH_ASSOC); if (!$product) { exit(商品不存在或已下架); } $stmt $pdo-prepare(SELECT id,price,stock,spec_json FROM product_sku WHERE product_id? ORDER BY id); $stmt-execute([$productId]); $skus $stmt-fetchAll(PDO::FETCH_ASSOC); $skuIds array_column($skus, id); if ($skuIds) { $in implode(,, array_fill(0, count($skuIds), ?)); $stmt $pdo-prepare(SELECT sku_id,attr_id,value FROM sku_attribute WHERE sku_id IN ($in)); $stmt-execute($skuIds); $attrs []; foreach ($stmt-fetchAll(PDO::FETCH_ASSOC) as $row) { $attrs[$row[sku_id]][] $row; } foreach ($skus as $sku) { $sku[attrs] $attrs[$sku[id]] ?? []; } unset($sku); } // 输出给模板$product 与 $skus逻辑说明array_fill生成占位符避免 SQL 注入IN查询把 N 次属性查询压成 1 次。参数上ORDER BY id保证 SKU 展示顺序稳定运营调整时不会跳位。失败时先看$skus是否为空——如果为空但 SPU 存在说明 SKU 没导入或product_id对不上这是新手最常踩的坑别急着怀疑 PDO 配置。3. 下单链路库存扣减、订单落库与支付回调怎么串下单是服装商城最容易出事故的地方。秒杀场景下100 件库存被 300 个请求同时读到如果先SELECT stock再UPDATE stock 原值-1必然超卖。正确做法是把判断和扣减合并成一条原子 SQL靠 InnoDB 行锁串行化同一 SKU 的并发扣减。订单落库要和扣库存放在同一个事务里支付回调则必须做幂等否则用户重复点击或支付平台重试就会生成多笔已支付订单。3.1 用事务 条件更新锁住库存下面这段是下单核心省略了购物车校验和地址校验聚焦库存与订单?php $pdo-beginTransaction(); try { $skuId (int)$_POST[sku_id]; $qty (int)$_POST[qty]; if ($qty 1 || $qty 99) { throw new Exception(数量非法); } // 原子扣减stock qty 才更新返回受影响行数 $stmt $pdo-prepare(UPDATE product_sku SET stock stock - ?, sales sales ? WHERE id ? AND stock ?); $stmt-execute([$qty, $qty, $skuId, $qty]); if ($stmt-rowCount() 0) { throw new Exception(库存不足); } // 读取扣减后的价格防止下单瞬间改价 $stmt $pdo-prepare(SELECT price FROM product_sku WHERE id ?); $stmt-execute([$skuId]); $price $stmt-fetchColumn(); $orderNo date(YmdHis) . mt_rand(1000, 9999); $stmt $pdo-prepare(INSERT INTO orders (order_no, user_id, sku_id, qty, amount, status, created_at) VALUES (?,?,?,?,?,0,NOW())); $stmt-execute([$orderNo, $userId, $skuId, $qty, $price * $qty]); $pdo-commit(); echo json_encode([code 0, order_no $orderNo]); } catch (Exception $e) { $pdo-rollBack(); echo json_encode([code 1, msg $e-getMessage()]); }参数说明stock ?是防超卖的关键条件rowCount()为 0 直接抛异常回滚sales同步累加方便按销量排序status0表示待支付。注意price必须在扣减后重新查不能信任前端传值否则改价接口一上线就被刷。事务隔离级别用默认的 REPEATABLE READ 即可UPDATE本身会加排他锁不需要额外SELECT ... FOR UPDATE后者反而增加死锁概率。3.2 支付回调的幂等处理与订单状态机支付平台回调可能重复推送必须用订单号做幂等。常见做法是给orders表加pay_no唯一索引回调时先UPDATE ... WHERE order_no? AND status0受影响行数为 0 说明已处理过直接返回成功。?php // 支付回调入口$raw 为回调原始数据$sign 为平台签名 $orderNo $raw[out_trade_no]; $payNo $raw[transaction_id]; $pdo-beginTransaction(); try { $stmt $pdo-prepare(UPDATE orders SET status1, pay_no?, paid_atNOW() WHERE order_no? AND status0); $stmt-execute([$payNo, $orderNo]); if ($stmt-rowCount() 0) { // 已处理或订单不存在幂等返回 $pdo-commit(); exit(success); } // 可在此触发发货队列、短信通知 $pdo-commit(); exit(success); } catch (Exception $e) { $pdo-rollBack(); exit(fail); }逻辑说明status0作为状态机守卫只有待支付订单能被改成已支付pay_no唯一索引兜底即使并发回调同时进来数据库也会拒绝第二条。参数上paid_at用于对账exit(success)的字符串必须和支付平台约定一致否则平台会持续重推。失败时先查orders表该订单的status如果已是 1 却还在重推说明平台没收到成功响应检查exit前是否有输出缓冲或 BOM 头。4. 避坑与排查服装商城上线后最常炸的五个点这套系统我在不同项目里部署过多次下面五个问题几乎每次都会遇到按「现象 → 原因 → 解决」记录省得你半夜被电话叫醒。现象一商品详情页 SKU 价格全部显示 0。原因通常是product_sku导入时price字段没给默认值或 CSV 列错位MySQL 在非严格模式下静默写入 0。解决把sql_mode设为STRICT_TRANS_TABLES导入前用SELECT COUNT(*) FROM product_sku WHERE price0排查运营后台加价格必填校验。现象二下单提示库存不足但后台看库存还有。原因是stock字段被设成了unsigned退货回补时stock stock - 1在库存为 0 时触发数据库报错事务回滚导致后续正常扣减也失败。解决stock用有符号int业务层在扣减前判断退货回补走独立逻辑并记录流水。现象三支付成功但订单还是待支付。回调地址被 Nginx 重写规则拦截或者 PHP 入口有session_start()导致响应前输出警告平台判定失败。解决回调入口独立文件关闭 sessionexit(success)前不要有任何 echo用tail -f看 Nginx access log 确认回调是否到达。现象四并发下单出现负库存。说明扣减 SQL 没带stock ?条件或者用了SELECT再UPDATE的两步写法。解决统一改成条件更新并在压测环境用ab -n 500 -c 50模拟并发验证stock最终值。现象五订单列表分页越翻越慢。LIMIT 100000, 20在订单量过百万后扫描大量行。解决改用游标分页WHERE id 上一页最后id ORDER BY id DESC LIMIT 20并给user_id加索引支撑按用户查询。提示上线前务必把orders表的order_no和pay_no都加上唯一索引这两个索引是数据一致性的最后一道防线比任何业务代码都可靠。5. 把服装商城跑得更稳两个我压箱底的技巧第一个技巧是给 SKU 库存加一层 Redis 预扣。MySQL 行锁在几百并发下没问题但服装商城做限时活动时同一 SKU 可能瞬间涌入几千请求全部打到数据库会让连接池耗尽。我一般在下单前用 Redis 的DECRBY做预扣扣到负数直接拒绝扣成功再走 MySQL 事务事务失败时INCRBY回补。这样数据库只处理真正能下单的请求吞吐量能提升一个数量级。注意 Redis 预扣和 MySQL 扣减之间要加一个短过期时间的标记防止回补遗漏。第二个技巧是订单表按月分表。服装商城订单增长快单表过千万后即使有索引统计报表也会拖慢主库。我习惯用orders_202601这种命名PHP 层根据created_at路由查询时按月份 UNION。分表后对账脚本要同步改否则财务月底会发现数据对不上。这两个技巧都不复杂但需要在上线前就规划好事后补的代价是停机迁移。我自己踩过最狠的一次是活动前忘了给 Redis 预扣加回补逻辑结果 MySQL 事务失败后库存凭空少了两百件运营对着后台骂了半小时。从那以后任何涉及库存的代码我都先写回补再写扣减这个习惯救了我很多次。希望帮到你。本文还有配套的精品资源点击获取