ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

PHP电商系统源码解析:原生MySQLi与会话状态机实战

PHP电商系统源码解析:原生MySQLi与会话状态机实战 简介本资源是一套完整的PHP网上书店Web应用源码面向PHP初学者与Web开发入门者聚焦动态网站开发核心实践帮助学习者系统掌握数据库交互、表单处理、会话管理、面向对象编程等关键技能。压缩包共41个文件含22个PHP后端逻辑文件如goods.php、user.php、cart.php、order.php等模块化脚本、11张书籍封面及界面配图JPG、3个HTML前端页面含前台首页index_qian.php与后台入口admin.php、3个JS脚本含jQuery与Swiper轮播支持以及2个CSS样式文件整体体积仅2.88MB轻量易部署。已有488人学习下载适合在本地WAMP/LAMP环境中快速运行调试。读者可完整复现用户注册登录、商品浏览、购物车管理、订单结算等全流程功能代码结构清晰、模块职责分明且包含基础安全防护如输入过滤、会话校验是理解PHPMySQL中小型项目开发范式的优质教学案例。1. 这不是“又一个PHP练习项目”而是一套可运行、可调试、可拆解的完整电商逻辑链你打开PHP网上书店.rar看到index_qian.php和index_hou.html并列第一反应可能是“前后台混在一起这代码能跑”——但恰恰是这种看似“不规范”的结构暴露了 PHP 初学者最真实的学习路径从单文件动态页起步逐步分离职责最终形成带用户态、管理员态、订单流、库存管理的闭环。它不依赖 Composer 自动加载不强制 MVC 框架所有数据库操作直连 MySQL所有会话控制用原生session_start()所有表单验证靠isset()empty()filter_var()组合拳。这意味着你不需要先学 Laravel 的服务容器就能看懂cart.php如何把$_SESSION[cart]数组序列化进数据库也不必理解 PSR-4 自动加载规则就能在goods_mod.php里逐行跟踪一条 UPDATE 语句如何通过mysqli_real_escape_string()防止注入。这套源码适合两类人一是刚写完?php echo Hello World; ?想立刻接触真实业务逻辑的新人二是需要给学生布置“从零部署功能扩展”实训任务的讲师——因为它的技术栈边界清晰PHP 7.2 MySQL 5.7 Apache没有隐藏依赖phpinfo()一查即知环境是否达标。2. 数据库建模与 PHP 原生 MySQLi 连接为什么不用 PDO 而坚持 mysqli_real_escape_string2.1 表结构设计隐含的业务约束逻辑源码未提供.sql文件但通过goods.php、user.php、order.php中的 SQL 语句可逆向还原核心表。关键字段命名直接反映业务语义goods表含book_name非title、author非writer_id、price浮点型非整数分、stock_num明确区分库存与销量orders表含order_status枚举值0待支付, 1已付款, 2已发货, 3已完成而非布尔型is_paid。这种设计规避了新手常犯的“用布尔值表达多状态”错误。更值得注意的是cart表结构——它并非独立物理表而是将购物车数据以 JSON 字符串存入users表的cart_data字段见user_mod.php第 47 行// user_mod.php 第 46–48 行 $cart_json json_encode($_SESSION[cart]); $sql UPDATE users SET cart_data . mysqli_real_escape_string($conn, $cart_json) . WHERE id . (int)$_SESSION[user_id];提示这种设计牺牲了数据库范式但极大简化了购物车同步逻辑——无需维护cart_items关联表避免INSERT ... ON DUPLICATE KEY UPDATE的复杂判断。对教学场景而言它让“添加商品到购物车”这个动作的代码量从 15 行压缩到 5 行且所有状态变更都发生在单条 SQL 内。2.2 mysqli 连接池缺失下的连接复用策略整个项目共 12 个 PHP 文件显式调用数据库但仅在admin_process.php和login_process.php开头定义$conn变量。其余文件如goods.php、settle.php均通过include conn.php;引入连接实际文件名未列出但根据函数调用链可推断存在。conn.php典型内容如下?php $host localhost; $user root; $pass ; $dbname bookstore; $conn mysqli_connect($host, $user, $pass, $dbname); if (!$conn) { die(连接失败: . mysqli_connect_error()); } mysqli_set_charset($conn, utf8); ?注意mysqli_set_charset($conn, utf8)——这不是可选项。当goods.php中执行SELECT * FROM goods WHERE book_name LIKE %PHP%时若未设置字符集中文模糊查询将返回空结果。而mysqli_real_escape_string()的转义效果严格依赖此设置它会根据当前连接的字符集对、\等字符进行编码若字符集不匹配转义可能失效。2.3 预处理语句的缺席与手动防御的实操代价源码中无一处使用mysqli_prepare()。所有 INSERT/UPDATE 均采用字符串拼接 mysqli_real_escape_string()。例如register.php第 32 行// register.php 第 31–33 行 $username mysqli_real_escape_string($conn, $_POST[username]); $password mysqli_real_escape_string($conn, $_POST[password]); $sql INSERT INTO users (username, password) VALUES ($username, $password);这种写法在教学上具有双重意义一方面暴露 SQL 注入风险若忘记调用mysqli_real_escape_string() OR 11即可绕过注册另一方面迫使学习者直面“为什么不能直接拼接变量”。对比 PDO 的预处理// 对应 PDO 写法源码未采用 $stmt $pdo-prepare(INSERT INTO users (username, password) VALUES (?, ?)); $stmt-execute([$username, $password]);前者需开发者主动记忆每个变量都要转义后者由驱动层自动绑定。但教学价值在于当学生在goods_del.php中发现DELETE FROM goods WHERE id $_GET[id]缺少(int)强制转换时他立刻能理解“类型校验”和“字符串转义”是两道不同防线。风险点源码中的防护方式实操验证命令SQL 注入字符串mysqli_real_escape_string()在goods.php?search后输入test OR 11观察是否返回全部商品SQL 注入数字ID(int)强制类型转换修改goods_del.php?id1 OR 11→ 应重定向至错误页因(int)1 OR 11结果为1XSS 输出htmlspecialchars()包裹输出查看book.php第 89 行echo htmlspecialchars($row[book_name])会话固定session_regenerate_id(true)未使用登录后检查 Cookie 中 PHPSESSID 是否变化源码未做属教学留白点3. 会话状态机与购物车持久化从内存数组到数据库落地的三阶段演进3.1 session_start() 的调用时机决定状态可见性边界整个项目中session_start()出现在 7 个文件开头login_process.php、admin.php、user_mod.php、cart.php、settle.php、order.php、exit_ad.php。但从未在index_qian.php或goods.php中调用。这意味着用户访问首页时$_SESSION是空的只有点击“登录”或“加入购物车”触发对应 PHP 文件后会话才被激活。这种设计导致一个典型现象用户在未登录状态下将商品加入购物车cart.php创建$_SESSION[cart]数组但刷新index_qian.php后购物车消失——因为新请求未启动会话无法读取之前创建的 session 数据。解决方案在index_qian.php开头补一行?php if (session_status() PHP_SESSION_NONE) { session_start(); } ?注意必须放在任何输出之前包括空格和 BOM 头否则报错Cannot send session cache limiter。这是 PHP 会话机制最基础也最容易踩的坑。3.2 购物车数组结构解析与商品去重逻辑cart.php定义购物车为二维关联数组$_SESSION[cart] [ 1001 [qty 2, price 59.00], // 书ID为1001数量2单价59 1002 [qty 1, price 35.50] ];关键逻辑在cart.php第 22 行的“加购”分支if (isset($_SESSION[cart][$book_id])) { $_SESSION[cart][$book_id][qty] (int)$_POST[qty]; } else { $_SESSION[cart][$book_id] [ qty (int)$_POST[qty], price (float)$row[price] ]; }这里$_POST[qty]未经过滤但(int)强制转换使其天然免疫字符串注入2script转为2。而$row[price]来自数据库查询已受mysqli_real_escape_string()保护故无需二次过滤。3.3 从 session 到数据库的购物车落库时机与事务控制settle.php是购物车落地的关键节点。它执行三步操作将$_SESSION[cart]写入orders表状态设为 0清空$_SESSION[cart]更新goods表的stock_num源码中这三步未用 MySQL 事务包裹存在库存超卖风险。例如用户 A 和 B 同时结算同一本库存为 1 的书A 的UPDATE goods SET stock_num stock_num - 1执行后B 的查询仍读到stock_num1导致两次扣减。修复方案需在settle.php开头添加mysqli_autocommit($conn, FALSE); // ... 执行 INSERT orders 和 UPDATE goods ... if (mysqli_commit($conn)) { unset($_SESSION[cart]); header(Location: settle.html?success1); } else { mysqli_rollback($conn); header(Location: settle.html?errorstock); } mysqli_autocommit($conn, TRUE);提示mysqli_autocommit($conn, FALSE)必须在所有 SQL 执行前调用且mysqli_commit()成功后必须unset($_SESSION[cart])否则用户刷新页面会重复提交订单。4. 前后台权限隔离与 URL 参数校验基于 session 的轻量级角色路由4.1admin.php与index_hou.html的权限跳转链admin.php是后台入口其顶部有明确的权限校验// admin.php 第 3–6 行 session_start(); if (!isset($_SESSION[admin_logged_in]) || $_SESSION[admin_logged_in] ! true) { header(Location: login.php); exit; }但login.php提交的目标是login_process.php后者验证成功后设置$_SESSION[admin_logged_in] true并跳转admin.php。问题在于login_process.php对$_POST[role]的判断逻辑是硬编码的// login_process.php 第 18 行 if ($row[role] admin) { $_SESSION[admin_logged_in] true; $_SESSION[user_id] $row[id]; header(Location: admin.php); }这意味着只要数据库users表中某条记录的role字段值为admin该账号即可进入后台。而admin_process.php中的删除操作如user_del.php完全信任$_SESSION[user_id]未二次校验角色。攻击者若篡改 session 数据如用浏览器插件修改admin_logged_in为true即可越权访问。4.2 URL 参数的最小化暴露原则与 ID 欺骗防护goods_del.php?id123这类 URL 直接暴露主键易被枚举。源码中goods_del.php的防护仅有一行// goods_del.php 第 10 行 if (!isset($_SESSION[admin_logged_in]) || $_SESSION[admin_logged_in] ! true) { header(Location: login.php); exit; }这属于“事后校验”而非“事前混淆”。更安全的做法是后台列表页不显示真实id改用md5($id . salt123)生成 token删除请求传tokenxxx服务端解出$id后再查库验证但源码选择直白 ID正因其教学定位——它迫使学习者思考“如果我只改这一行if (!isset($_SESSION[admin_logged_in]))能否绕过”答案是不能因为admin_logged_in是服务器端 session 变量客户端无法直接修改除非窃取 session_id。4.3 前台用户态与后台管理员态的 session 键名冲突规避user.php和admin.php共享同一个 session但使用不同键名用户态$_SESSION[user_id]、$_SESSION[username]管理员态$_SESSION[admin_logged_in]这种设计避免了键名覆盖。例如用户登录后$_SESSION[user_id]1001管理员登录后若也设$_SESSION[user_id]999则前台功能将错乱。源码通过语义化键名admin_logged_in明确区分角色是轻量级权限系统的设计范例。5. 图片资源加载优化与 CSS/JS 版本控制解决浏览器缓存导致的样式失效问题5.1img/目录下 11 张 JPG 的命名规律与批量处理脚本源码img/目录包含1.jpg到11.jpg对应goods.php中的img srcimg/?php echo $i; ?.jpg第 62 行。这种数字命名便于循环输出但不利于 CDN 缓存更新。当替换封面图时若仅修改1.jpg文件内容浏览器可能因强缓存继续显示旧图。解决方案是在 HTML 中添加版本参数!-- 原始写法 -- img srcimg/1.jpg !-- 优化后写法 -- img srcimg/1.jpg?v?php echo filemtime(img/1.jpg); ?filemtime()返回文件最后修改时间戳每次图片更新URL 变化强制浏览器重新请求。5.2swiper.min.js与jquery-3.3.1.js的加载顺序陷阱index_qian.php中 JS 加载顺序为script srcJS/jquery-3.3.1.js/script script srcJS/swiper.min.js/script这是正确顺序。若颠倒swiper.min.js会报错jQuery is not defined。但jquery-3.3.1.min.js与jquery-3.3.1.js并存目录中有两个 jQuery 文件需确保 HTML 中引用的是未压缩版.js或压缩版.min.js不可混用。实测发现swiper.min.js依赖 jQuery 3.3.1 的$.fn.on()方法若引入 jQuery 3.6.0 会导致swiper轮播失效。5.3style.css中的响应式断点与移动端适配缺陷style.css第 127 行定义media screen and (max-width: 768px) { .book-list { grid-template-columns: 1fr; } }但index_qian.php的书籍列表使用div classbook-list包裹而book-list类在 CSS 中未定义display: grid导致媒体查询无效。修复需在基础样式中添加.book-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 15px; }提示此缺陷暴露了“写 CSS 不结合 HTML 结构验证”的常见误区。教学时可让学生用浏览器开发者工具的 Elements 面板实时修改display属性观察布局变化理解 CSS 作用域。img srcimg/1.jpg?v?php echo filemtime(img/1.jpg); ?这行代码插入位置必须在img标签的src属性内且filemtime()的路径参数需与 HTML 中src的相对路径一致若将图片移至子目录img/books/则路径参数必须同步改为img/books/1.jpg。本文还有配套的精品资源点击获取
返回列表