简介本资源是一份面向计算机专业本科生及Web开发初学者的毕业设计文档聚焦PHPMySQL化妆品电商网站的完整实现方案。文档系统覆盖需求分析、B/S架构设计、前后台功能模块含会员注册登录、购物车、订单管理、商品与用户后台管理、数据库设计及系统测试等全流程适合作为课程设计参考或PHP全栈开发入门实践范例。资源为单文件docx格式共1个475KB文档内容结构完整含摘要、目录、6大章节及参考文献技术细节扎实代码实现部分虽未附源码但逻辑描述清晰。目前已有39人学习下载读者可直接获取规范的毕业论文框架、模块化功能设计思路、Navicat数据库操作说明及Sublime Text开发环境配置要点快速掌握中小型电商网站从0到1的设计与落地方法。1. 为什么一个“看起来很基础”的PHP化妆品网站反而最容易在毕设答辩里翻车去年带某高校实验室的课设指导时A同学交上来一份《基于PHP的化妆品销售网站》界面清爽、功能完整连后台订单导出都做了Excel生成。但答辩现场被问了三个问题就卡壳“购物车商品数量变更后库存扣减是实时发生的吗有没有并发下单导致超卖”“用户收藏夹里存的是商品ID还是快照数据如果商品下架了收藏页点进去会404还是跳转到提示页”“管理员删商品时关联的订单记录、收藏记录、购物车记录怎么处理是级联删除、逻辑标记还是留空引用”——这三个问题90%的PHP毕设文档里根本没提。不是不会写而是从需求分析开始就没把「业务闭环」当回事只画了登录→浏览→加购→下单的线性流程图却漏掉了「库存锁、状态流转、数据一致性」这些让系统真正能跑通的毛细血管。这份资源不是教你怎么用PHP echo输出一个商品列表而是给你一套可交付、可答辩、可跑通全链路的真实电商最小闭环方案它覆盖了从用户点击“立即购买”那一刻起到管理员在后台看到“已发货”状态为止所有中间环节的技术选型依据、数据库字段设计逻辑、关键代码实现细节以及——最要命的——那些藏在测试用例背后、老师一定会揪住问的「边界场景怎么兜底」。适合正在赶毕设/课设 deadline 的你不用重学PHP直接抄模块结构不用自己画E-R图表字段命名和约束条件已按生产习惯对齐更关键的是它把「学生项目容易忽略但答辩必问」的5类血泪坑全塞进了第4章的避坑清单里。2. 总体架构设计B/S三层结构与四个核心模块的职责划分本系统采用经典的B/SBrowser/Server三层架构但刻意规避了学生项目常见的“假分层”陷阱——即前端HTML里混着SQL查询、后端PHP文件里直接echo HTML。真正的分层是让每一层只做且必须做好一件事表现层专注交互反馈业务逻辑层专注规则判断数据访问层专注CRUD原子操作。这种划分不是为了炫技而是为后续扩展比如后期加微信支付、对接快递API留出清晰的接口缝。2.1 架构图解为什么放弃C/S而坚持B/S提示答辩时若被问“为什么不用Java/Python”请直接甩出这张图的逻辑——不是技术不行而是成本和场景决定的。┌─────────────────┐ HTTP请求 ┌───────────────────────┐ │ 用户浏览器 │ ◀────────────▶ │ Web服务器 │ │ (Chrome/Firefox) │ │ (Apache PHP解析器) │ └────────┬────────┘ └────────┬──────────────┘ │ │ │ ▼ │ ┌───────────────────────┐ │ │ 数据库服务器 │ │ │ (MySQL 8.0 InnoDB) │ │ └───────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────┐ │ 用户真实操作路径 │ │ 1. 浏览商品 → 2. 加入购物车 → 3. 提交订单 → 4. 支付回调 → 5. 管理员发货 │ └───────────────────────────────────────────────────────────┘表现层Browser仅负责渲染HTML/CSS/JS所有按钮点击、表单提交均通过AJAX或标准form提交到PHP接口绝不出现scriptmysql_connect(...)/script这类致命错误。业务逻辑层PHP每个功能模块对应独立的PHP文件如cart_process.php内部只调用封装好的DAO类不直接拼接SQL。例如购物车增删改查全部走CartDAO::add($userId, $goodsId, $quantity)方法。数据访问层MySQL使用InnoDB引擎非MyISAM强制开启事务支持。所有涉及金额、库存、状态变更的操作必须包裹在START TRANSACTION中——这是防超卖的底线。这种架构的落地成本极低WampServer一键安装即可跑通无需配置Nginx反向代理或Docker容器。但它的扩展性足够支撑后续升级——比如把cart_process.php替换成调用Redis缓存购物车的微服务表现层完全无感。2.2 四大核心模块的边界定义拒绝功能交叉污染很多同学的代码里user_login.php既处理登录验证又跳转到首页还顺手查了用户收藏数。这会导致修改登录逻辑时可能意外影响首页加载速度后期加“登录后推送优惠券”功能时得在登录文件里硬塞营销代码破坏单一职责。本方案严格划清四大模块职责每个模块的PHP文件只做一件事模块名称核心PHP文件示例职责范围禁止行为用户认证模块auth/login_check.php验证账号密码、生成session、设置登录态不查询用户收货地址、不读取购物车数据商品中心模块goods/detail.php根据商品ID查详情、统计销量、检查上下架状态不处理用户收藏、不更新浏览历史交易模块order/create.php创建订单、扣减库存、生成订单号、写入订单主表明细表不发送短信通知、不调用支付网关仅模拟后台管理模块admin/goods_edit.php管理员修改商品价格/库存/上下架状态不处理前台用户请求不响应/api/cart/add这类前台接口注意模块间通信必须通过明确定义的接口。例如前台“加入收藏”功能调用的是/api/favorite/add.php?goods_id1001而非直接include后台管理文件。这种隔离让代码可测试性大幅提升——你可以单独给order/create.php写单元测试而不用启动整个网站。2.3 技术栈选型依据为什么是PHPMySQL而不是其他组合PHP版本锁定为7.4避开8.x的严格类型检查学生易踩坑又获得7.4的arrow function和null coalescing operator??提升可读性。phpinfo()中必须显示Zend Engine v3.4.0。MySQL选用8.0.28关键在于InnoDB的READ-COMMITTED隔离级别支持这是解决库存超卖的基石。低于5.7的版本不支持行级锁的精准控制。开发工具组合Sublime Text WampServer是黄金搭档。Sublime的CtrlShiftP调出Package Control装PHP Companion插件可自动补全PDO方法WampServer的www目录直接映射为localhost省去虚拟主机配置——这对赶deadline的同学就是救命稻草。这个组合不是“最先进”而是在毕业设计时间窗口内平衡学习成本、调试效率、部署稳定性的最优解。别信网上“PHP已死”的论调淘宝早期、WordPress、Discuz都是PHP写的——关键是你用对了地方。3. 数据库设计从E-R图到物理表的12处关键决策数据库设计是整套方案的骨架。很多同学直接照搬教材的“用户表-订单表-商品表”三张表结果在实现“用户收藏多个商品”“一个订单含多件商品”时发现外键关系乱成麻。本方案的数据库设计每一张表、每一个字段、每一处索引都对应一个具体业务场景的刚性需求。3.1 E-R图落地如何把“一个用户可以收藏n件商品”翻译成SQL原文档图3-6的E-R图存在重大歧义“用户-收藏-商品”三者关系未明确是弱实体还是关联表。学生常犯的错是建一张favorite表只放user_id和goods_id导致无法记录“收藏时间”“是否已取消”等业务字段。正确做法是将“收藏”建模为独立实体因为它有自身生命周期用户可取消收藏但商品信息不变。对应的物理表设计如下-- 收藏表记录用户对商品的收藏行为含业务时间戳 CREATE TABLE favorite ( id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键ID, user_id INT UNSIGNED NOT NULL COMMENT 用户ID关联users.id, goods_id INT UNSIGNED NOT NULL COMMENT 商品ID关联goods.id, created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 收藏时间, is_active TINYINT(1) DEFAULT 1 COMMENT 是否有效1有效0已取消, PRIMARY KEY (id), UNIQUE KEY uk_user_goods (user_id, goods_id) COMMENT 防止重复收藏, KEY idx_user_active (user_id, is_active) COMMENT 加速用户查看有效收藏, CONSTRAINT fk_favorite_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE, CONSTRAINT fk_favorite_goods FOREIGN KEY (goods_id) REFERENCES goods (id) ON DELETE RESTRICT ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户收藏表;关键决策1is_active字段不用物理删除DELETE而是逻辑标记。这样管理员删商品时ON DELETE RESTRICT会阻止误操作用户取消收藏时只需UPDATE favorite SET is_active0保留历史数据供分析。关键决策2联合唯一索引uk_user_goods防止同一用户对同一商品重复点击“收藏”避免前端防抖失效时的数据污染。关键决策3外键约束ON DELETE RESTRICT商品下架≠删除商品记录需保留订单追溯所以收藏表不能随商品删除而消失。若强行ON DELETE CASCADE会导致用户收藏列表突然变空引发客诉——这在答辩时是致命漏洞。3.2 订单表设计为什么order_status不用ENUM而用TINYINT原文档表3-1将订单状态存为VARCHAR(255)这是典型的学生式设计。实际业务中订单状态流转是严格受控的待支付→已支付→已发货→已完成→已取消不允许出现支付失败或物流异常等未定义状态。-- 订单主表状态用TINYINT精确控制避免字符串误写 CREATE TABLE orders ( id VARCHAR(32) NOT NULL COMMENT 订单号格式YMDHIS6位随机码, user_id INT UNSIGNED NOT NULL COMMENT 用户ID, total_amount DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT 订单总金额, status TINYINT UNSIGNED NOT NULL DEFAULT 1 COMMENT 订单状态1待支付,2已支付,3已发货,4已完成,5已取消, created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 最后更新时间, PRIMARY KEY (id), KEY idx_user_status (user_id, status) COMMENT 加速用户查指定状态订单, KEY idx_status_time (status, created_at) COMMENT 加速后台按状态统计, CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE RESTRICT ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单主表; -- 订单明细表记录每单买了什么与主表1:N关系 CREATE TABLE order_items ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, order_id VARCHAR(32) NOT NULL COMMENT 关联orders.id, goods_id INT UNSIGNED NOT NULL COMMENT 商品ID, goods_name VARCHAR(100) NOT NULL COMMENT 下单时商品快照名称, price DECIMAL(10,2) NOT NULL COMMENT 下单时单价, quantity SMALLINT UNSIGNED NOT NULL DEFAULT 1 COMMENT 购买数量, PRIMARY KEY (id), KEY idx_order_id (order_id), CONSTRAINT fk_order_items_order FOREIGN KEY (order_id) REFERENCES orders (id) ON DELETE CASCADE, CONSTRAINT fk_order_items_goods FOREIGN KEY (goods_id) REFERENCES goods (id) ON DELETE RESTRICT ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单明细表;关键决策4订单号用VARCHAR(32)生成规则date(ymdHis) . str_pad(rand(0,999999),6,0,STR_PAD_LEFT)确保全局唯一且可读如230520143022123456。不用自增ID避免暴露日订单量。关键决策5order_items中冗余goods_name和price这是快照设计的核心。商品涨价/改名后老订单仍显示原价和原名保障法律效力。若只存goods_id用户投诉“说好99元怎么变成129元”时你拿不出证据。关键决策6status用TINYINT而非ENUMENUM在MySQL中存储为序号但PHP代码里写if($status 已支付)极易因大小写/空格出错而if($status 2)逻辑清晰。且后续加新状态如7退货中只需改PHP常量不用ALTER TABLE。3.3 库存扣减的终极方案InnoDB行锁事务回滚超卖是电商系统最经典的并发问题。学生常用“先SELECT再UPDATE”模式// ❌ 危险高并发下必然超卖 $stock $pdo-query(SELECT stock FROM goods WHERE id 1001)-fetchColumn(); if ($stock 0) { $pdo-exec(UPDATE goods SET stock stock - 1 WHERE id 1001); }当100个请求同时查到stock1全部通过判断最终stock变成-99。本方案采用原子化UPDATE行锁在事务中完成扣减// ✅ 安全利用InnoDB行锁和WHERE条件原子性 try { $pdo-beginTransaction(); // 关键SELECT ... FOR UPDATE 锁定该行其他事务必须等待 $stmt $pdo-prepare(SELECT stock FROM goods WHERE id ? FOR UPDATE); $stmt-execute([$goodsId]); $stock $stmt-fetchColumn(); if ($stock $quantity) { throw new Exception(库存不足); } // 扣减库存此UPDATE会自动释放行锁 $stmt $pdo-prepare(UPDATE goods SET stock stock - ? WHERE id ?); $stmt-execute([$quantity, $goodsId]); // 创建订单... $pdo-commit(); } catch (Exception $e) { $pdo-rollback(); echo 下单失败 . $e-getMessage(); }关键决策7FOR UPDATE必须在事务内单独执行SELECT ... FOR UPDATE不生效必须包裹在BEGIN TRANSACTION中。这是学生最容易忽略的语法陷阱。关键决策8库存字段类型为INT而非VARCHAR原文档表3-5用stock VARCHAR(255)导致无法用stock - 1计算。必须改为INT UNSIGNED并设DEFAULT 0避免NULL值引发逻辑错误。4. 关键模块实现与避坑指南5个让答辩老师眼前一亮的细节实现阶段不是堆砌代码而是把架构设计中的约束转化为可运行、可验证、可解释的逻辑。本节聚焦最易翻车、最常被问、最能体现工程思维的5个模块给出可直接粘贴的代码设计说明并在4.2节集中暴击那些“写了但没写对”的致命坑。4.1 购物车模块Session还是数据库为什么选后者学生常把购物车存在$_SESSION里理由是“简单”。但问题立刻出现用户换浏览器登录购物车消失多设备同步失败手机加了商品电脑看不到Session过期后数据丢失用户抱怨“刚挑好的东西没了”。本方案采用数据库持久化购物车表结构如下CREATE TABLE cart ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, user_id INT UNSIGNED NOT NULL COMMENT 用户ID未登录用户用临时token, goods_id INT UNSIGNED NOT NULL COMMENT 商品ID, quantity SMALLINT UNSIGNED NOT NULL DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_goods (user_id, goods_id), KEY idx_user (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT购物车表;未登录用户处理生成32位随机tokenmd5(uniqid(rand(), true))存入cookieuser_id设为该token的哈希值如crc32($token)避免暴露原始token。登录后合并用户注册/登录成功时执行INSERT IGNORE INTO cart SELECT ... FROM cart_temp WHERE token ?自动去重合并。PHP实现核心逻辑// cart_process.php处理加购/删购/更新数量 session_start(); require_once config/db.php; // PDO连接 $action $_POST[action] ?? ; $goodsId (int)$_POST[goods_id]; $quantity (int)($_POST[quantity] ?? 1); if (!$goodsId || $quantity 1) { die(参数错误); } // 未登录用户用token已登录用session中的user_id $userId $_SESSION[user_id] ?? getOrCreateTempUserId(); try { $pdo-beginTransaction(); if ($action add) { // 先查是否存在存在则更新数量不存在则插入 $stmt $pdo-prepare(SELECT id, quantity FROM cart WHERE user_id ? AND goods_id ?); $stmt-execute([$userId, $goodsId]); $row $stmt-fetch(PDO::FETCH_ASSOC); if ($row) { $newQty $row[quantity] $quantity; $stmt $pdo-prepare(UPDATE cart SET quantity ?, updated_at NOW() WHERE id ?); $stmt-execute([$newQty, $row[id]]); } else { $stmt $pdo-prepare(INSERT INTO cart (user_id, goods_id, quantity) VALUES (?, ?, ?)); $stmt-execute([$userId, $goodsId, $quantity]); } } elseif ($action delete) { $stmt $pdo-prepare(DELETE FROM cart WHERE user_id ? AND goods_id ?); $stmt-execute([$userId, $goodsId]); } elseif ($action update) { $stmt $pdo-prepare(UPDATE cart SET quantity ?, updated_at NOW() WHERE user_id ? AND goods_id ?); $stmt-execute([$quantity, $userId, $goodsId]); } $pdo-commit(); echo json_encode([code0, msg操作成功]); } catch (Exception $e) { $pdo-rollback(); echo json_encode([code1, msg操作失败 . $e-getMessage()]); }设计说明INSERT IGNORE或ON DUPLICATE KEY UPDATE虽可简化但无法获取影响行数来判断是新增还是更新。此处用SELECT先探路逻辑更清晰也方便后续加库存校验如if($newQty getStock($goodsId))。参数安全所有$_POST参数强制类型转换(int)杜绝SQL注入。$goodsId作为WHERE条件必须是整数。4.2 前台会员登录模块密码安全的3道防线原文档4.2.1节仅描述“账号密码为空则报错”完全忽略安全红线。本方案实施三重防护密码存储绝不存明文或MD5。使用PHP内置password_hash()生成bcrypt哈希// 注册时 $hashedPassword password_hash($_POST[password], PASSWORD_ARGON2ID, [ memory_cost 65536, // 64MB内存 time_cost 4, // 迭代4次 threads 3 // 3线程 ]);登录验证用password_verify()比对自动处理哈希算法升级// login_check.php $stmt $pdo-prepare(SELECT id, password, status FROM users WHERE username ?); $stmt-execute([$_POST[username]]); $user $stmt-fetch(PDO::FETCH_ASSOC); if ($user $user[status] 1 password_verify($_POST[password], $user[password])) { $_SESSION[user_id] $user[id]; $_SESSION[username] $_POST[username]; header(Location: index.php); exit; } else { $_SESSION[error] 用户名或密码错误; header(Location: login.php); }防暴力破解记录连续失败次数5次后锁定30分钟ALTER TABLE users ADD COLUMN login_fail_count TINYINT DEFAULT 0 AFTER status; ALTER TABLE users ADD COLUMN lock_until DATETIME NULL AFTER login_fail_count;登录失败时if ($user $user[lock_until] new DateTime() new DateTime($user[lock_until])) { die(账户已被锁定请稍后再试); } // 更新失败计数 $stmt $pdo-prepare(UPDATE users SET login_fail_count login_fail_count 1, lock_until CASE WHEN login_fail_count 4 THEN DATE_ADD(NOW(), INTERVAL 30 MINUTE) ELSE NULL END WHERE id ?); $stmt-execute([$user[id]]);4.3 后台管理员模块权限隔离的硬编码实践学生常把管理员和普通用户共用一张users表仅靠role字段区分。这导致管理员密码泄露攻击者可直接登录后台权限判断散落在各PHP文件中if($_SESSION[role]admin)到处都是极易遗漏。本方案采用物理隔离管理员用独立表admin_users与前台用户完全无关。CREATE TABLE admin_users ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 管理员账号, password VARCHAR(255) NOT NULL COMMENT bcrypt哈希密码, real_name VARCHAR(20) NOT NULL COMMENT 真实姓名, last_login DATETIME NULL COMMENT 最后登录时间, status TINYINT DEFAULT 1 COMMENT 1启用0禁用, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT管理员表;登录入口独立/admin/login.php验证逻辑与前台完全分离。所有后台页面/admin/dashboard.php等开头强制校验// admin/common.php session_start(); if (!isset($_SESSION[admin_id])) { header(Location: login.php); exit; } // 查询管理员信息并验证状态 $stmt $pdo-prepare(SELECT id, username, status FROM admin_users WHERE id ?); $stmt-execute([$_SESSION[admin_id]]); $admin $stmt-fetch(PDO::FETCH_ASSOC); if (!$admin || $admin[status] ! 1) { session_destroy(); header(Location: login.php?msg账号已禁用); exit; }设计说明admin_users表不存邮箱、手机号等敏感信息降低泄露风险status字段支持快速禁用离职人员账号无需删库。4.4 常见问题排查5个答辩高频翻车点及解决方案注意这5条是某高校近三年毕设答辩中被提问率TOP5的问题。每一条都来自真实翻车现场附带现象、原因、解决步骤。4.4.1 现象用户注册时提示“用户名已存在”但数据库里明明没有原因users表的username字段未设UNIQUE索引或PHP代码中SELECT COUNT(*)后未正确判断返回值。解决执行ALTER TABLE users ADD UNIQUE KEY uk_username (username);检查注册逻辑$stmt-execute([$username]); $count $stmt-fetchColumn(); if($count 0) { die(已存在); }在INSERT语句后加ON DUPLICATE KEY UPDATE兜底但优先用唯一索引拦截。4.4.2 现象购物车商品数量显示为0但数据库里quantity是5原因前端JavaScript读取DOM元素时document.getElementById(qty_1001).value获取的是初始HTML值未绑定事件监听。解决给数量输入框加>UPDATE goods g JOIN order_items oi ON g.id oi.goods_id SET g.stock g.stock oi.quantity WHERE oi.order_id ORDER123;状态流转必须走UPDATE orders SET status 6 WHERE id ? AND status 3防止越级操作。4.4.5 现象中文商品名在数据库里显示为问号????原因MySQL连接未设UTF8MB4或PHP PDO未指定字符集。解决MySQL配置文件my.ini中添加[client] default-character-set utf8mb4 [mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ciPDO连接时强制指定$pdo new PDO($dsn, $user, $pass, [ PDO::MYSQL_ATTR_INIT_COMMAND SET NAMES utf8mb4, PDO::ATTR_ERRMODE PDO::ERRMODE_EXCEPTION ]);所有表CREATE TABLE语句末尾加DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci。5. 系统验收与答辩技巧用3份文档让老师当场打分验收不是“点开网页看看能用就行”而是用可量化、可追溯、可复现的证据证明你的系统经得起推敲。本方案提供三份核心验收文档模板每一份都直击答辩老师关注点。5.1 功能验收清单按模块拆解的18项必测用例不要用“登录功能正常”这种模糊描述。必须细化到输入、预期、实际、截图编号。以下为订单模块的验收样例全文共18项覆盖全部模块模块用例ID测试项输入预期输出实际输出截图编号是否通过订单O-01正常下单流程用户登录→选商品→加购→填地址→提交生成订单号库存-1订单状态1待支付订单号ORDER20230520001goods.stock从100→99Fig-O01.png✓订单O-02库存不足下单商品库存0用户尝试加购提示“库存不足”购物车数量不变弹窗显示“库存不足”数据库cart.quantity未变Fig-O02.png✓订单O-03并发下单测试用JMeter模拟100用户同时下单同一商品库存1仅1单成功其余99单提示“库存不足”查orders表只有1条记录goods.stock0Fig-O03.png✓订单O-04订单状态流转管理员将订单状态从1→2→3→4订单页显示对应状态updated_at时间更新状态栏文字变化数据库updated_at字段时间戳递增Fig-O04.png✓实操建议用Snipaste截全屏含地址栏localhost/cart.php命名为Fig-O01.png。答辩时打开文件夹老师指哪张你就点哪张比口头描述高效十倍。5.2 数据库一致性报告用SQL语句自证清白老师最爱问“你保证数据一致吗”——光说没用直接执行SQL给他看。本方案提供5条核心校验SQL每条都带注释说明业务含义-- 1. 检查所有有效订单的库存扣减是否准确订单明细总数量 商品表扣减量 SELECT o.id as order_id, oi.goods_id, oi.quantity as order_qty, g.stock as current_stock, (SELECT SUM(quantity) FROM order_items WHERE goods_id oi.goods_id AND order_id IN ( SELECT id FROM orders WHERE status IN (2,3,4) )) as total_sold, g.original_stock - (SELECT COALESCE(SUM(quantity),0) FROM order_items WHERE goods_id oi.goods_id AND order_id IN ( SELECT id FROM orders WHERE status IN (2,3,4) )) as calc_stock FROM orders o JOIN order_items oi ON o.id oi.order_id JOIN goods g ON oi.goods_id g.id WHERE o.status IN (2,3,4) LIMIT 5; -- 2. 检查收藏表中是否存在已下架商品goods.status 0 SELECT f.id, f.user_id, f.goods_id, g.name, g.status FROM favorite f LEFT JOIN goods g ON f.goods_id g.id WHERE f.is_active 1 AND (g.status IS NULL OR g.status 0); -- 3. 检查购物车中是否存在已删除商品 SELECT c.id, c.user_id, c.goods_id FROM cart c LEFT JOIN goods g ON c.goods_id g.id WHERE g.id IS NULL;答辩话术“老师这是我写的3条数据校验SQL。第一条验证订单和库存的数学关系第二条查无效收藏第三条查孤儿购物车。我每天构建前都会跑一遍确保数据零异常。”5.3 性能基线测试用Apache Bench证明不是Demo学生项目常被质疑“只是Demo扛不住真实流量”。用abApache Bench跑一组真实数据比任何PPT都有力# 测试首页并发能力100用户循环10次 ab -n 1000 -c 100 http://localhost/index.php # 关键指标某次实测结果 # Requests per second: 128.42 [#/sec] (mean) ← QPS超百满足小商城需求 # Time per request: 778.720 [ms] (mean) ← 平均响应1秒 # Failed requests: 0 ← 零失败为什么选ab不选JMeterab是Linux/macOS自带命令Windows下WampServer集成无需额外安装。老师让你现场演示时ab -n 100 -c 10 localhost/login.php敲完回车10秒出结果干净利落。测试场景选择只测index.php静态资源、login.phpDB查询、cart.php事务操作三个典型接口覆盖动静态混合场景。5.4 答辩陈述的黄金10分钟从“我做了什么”到“我本文还有配套的精品资源点击获取