☰
二手交易平台源码解析:PHP+MySQL独立后台与二维码收款实现
2026/10/21 4:33:31 网站建设 项目流程

简介:这套源码构建了转转与闲鱼风格的二手交易展示页面及独立管理后台,集成了个人二维码收款入口,面向Web前端开发者、PHP初学者和移动端仿站练习者,方便快速部署一套可交互的H5交易演示环境。压缩包共355个文件,约11.85MB,以PHP后端接口、JavaScript交互逻辑、CSS页面样式以及PNG/JPG图片素材为主,其中PHP文件负责后台管理与收款请求处理,JS/CSS用于还原页面动态效果和视觉风格,图片素材覆盖商品图、图标及界面点缀。目前已有8027人学习下载,具有一定的参考热度。解压后可直接运行前端页面并进入独立后台,体验从商品展示、订单提交到二维码收款模拟的完整流程;同时源码内包含多个CSS细节文件和前端脚本,适合作为仿站改版、支付流程理解及界面布局优化的实战范例。

1. 从源码包里拆出的二手交易平台:带独立后台和二维码收款的完整实现

做开发这些年,我在不少源码站下过资源,但像这个「转转+闲鱼钓鱼源码」一样能直接跑起来的完整项目并不多。它是个什么定位?一句话说清楚:这是一个模仿主流二手交易平台界面与业务流程的成套源码,前端有商品浏览、发布、搜索、下单,后端有一套独立的运营管理后台,还集成了个人二维码收款能力。它不是那种只有一个静态页面的半成品,而是前后端齐全、带数据库脚本、可独立部署的整套工程。

这个项目最适合两类人:一类是做二手交易类产品原型验证的开发者,想快速搭一个界面接近真实平台的可交互 Demo;另一类是研究 Web 支付流程、订单状态机、后台权限管理的学习者——它能让你在一套真实工程里看到订单从创建到支付到核销的完整链路。需要注意一点:项目定位是教学与合规演示用途,源码本身不含任何真实支付通道对接,收款部分是基于个人二维码的模拟方案。下面我按「先看清整体设计,再逐模块拆代码,最后跑起来改造」的顺序把它解剖开。

2. 整体架构与目录结构:前后端分离但部署一体

2.1 技术栈选型:为什么是 PHP + MySQL + 原生前端

拿到压缩包解压后,第一感觉是旧但完整。整套代码基于经典的 PHP 原生 + MySQL 组合,前端没有用到重型框架,主要是 HTML + CSS + 原生 JavaScript 配合少量 jQuery。选择这套技术栈在今天看来反而有个好处:部署门槛极低,任何一个支持 PHP 5.6+ 的虚拟主机或 Docker 容器都能跑,不依赖 Node 构建链、不依赖 Composer 自动加载,适合快速验证和二次开发。

目录结构在解压后大致是这样的:

├── admin/ # 独立后台管理端 │ ├── login.php # 后台登录 │ ├── dashboard.php # 数据概览 │ ├── goods_list.php # 商品管理 │ ├── order_list.php # 订单管理 │ ├── user_list.php # 用户管理 │ └── config.php # 后台配置 ├── api/ # 前端接口层 │ ├── goods.php # 商品相关接口 │ ├── order.php # 订单相关接口 │ └── user.php # 用户相关接口 ├── includes/ # 公共函数与数据库连接 │ ├── db.php # PDO 数据库连接 │ ├── functions.php # 公共函数库 │ └── auth.php # 登录鉴权 ├── uploads/ # 上传文件目录 ├── index.php # 前台首页 ├── goods.php # 商品详情页 ├── publish.php # 发布商品页 ├── user.php # 个人中心 └── install.sql # 数据库初始化脚本

这个结构最大的优点是「功能边界清晰」。admin与前台完全独立,通过includes/auth.php做角色区分;api层承担了所有数据交互,即使后续要把前端换成 Vue 或小程序,接口层可以直接复用。

2.2 数据库设计要点:订单表与商品表的核心字段

install.sql里一共建了 6 张表,核心的几张表字段设计值得参考。商品表goods包含id、user_id、title、description、price、original_price、images、status、created_at,其中images字段用逗号分隔存储多张图片路径,是这类小型项目的常见做法——简单直接,但后续如果要按图片维度做统计就会吃力,属于「先用起来再优化」的典型取舍。

订单表orders的设计是这套源码里最有学习价值的:

CREATE TABLE `orders` ( `id` int(11) NOT NULL AUTO_INCREMENT, `order_sn` varchar(32) NOT NULL COMMENT '订单编号', `goods_id` int(11) NOT NULL COMMENT '商品ID', `buyer_id` int(11) NOT NULL COMMENT '买家ID', `seller_id` int(11) NOT NULL COMMENT '卖家ID', `amount` decimal(10,2) NOT NULL COMMENT '订单金额', `pay_status` tinyint(1) DEFAULT 0 COMMENT '0待支付 1已支付 2已取消', `trade_status` tinyint(1) DEFAULT 0 COMMENT '0进行中 1已完成 2退款中', `pay_time` datetime DEFAULT NULL COMMENT '支付时间', `create_time` datetime NOT NULL COMMENT '下单时间', PRIMARY KEY (`id`), KEY `order_sn` (`order_sn`), KEY `goods_id` (`goods_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里把「支付状态」和「交易状态」拆成两个独立字段,是二手交易场景下的合理设计——支付成功不代表交易完成,还有发货、确认收货、退款等后续流程。order_sn用独立的 32 位字符串而非自增 ID,是为了避免订单号被遍历枚举,也算安全意识。

2.3 后台管理的权限模型:单管理员 + 会话控制

独立后台没有做复杂的 RBAC 权限树,而是采用「单管理员 + Session 鉴权」的轻量方案。管理员账号密码写在admin/config.php中,登录成功后写入 Session,所有后台页面共用includes/auth.php里的一个检查函数:

function check_admin_login() { if (!isset($_SESSION['admin_id'])) { header('Location: login.php'); exit; } }

这种设计对单人或小团队维护足够用,但如果你要做成多运营角色(比如客服、财务、超管),就得自己扩展一个admin_users表和对应的角色字段。后面我会在改造章节具体讲怎么升级。

3. 核心流程拆解:商品发布、下单支付与二维码收款实现

3.1 商品发布:图片上传与表单处理的完整链路

先看用户发布商品的 PHP 处理逻辑,这是前台最高频的操作。publish.php的表单提交到api/goods.php的create动作,关键代码:

// api/goods.php - create 动作 public function create() { // 1. 基础校验 $title = trim($_POST['title']); $price = floatval($_POST['price']); if (mb_strlen($title) < 2 || $price <= 0) { $this->response(400, '商品标题至少2个字符,价格必须大于0'); } // 2. 图片上传处理 $image_paths = []; if (!empty($_FILES['images'])) { $count = count($_FILES['images']['name']); for ($i = 0; $i < $count; $i++) { $tmp_name = $_FILES['images']['tmp_name'][$i]; $ext = pathinfo($_FILES['images']['name'][$i], PATHINFO_EXTENSION); $allow = ['jpg', 'jpeg', 'png', 'gif', 'webp']; if (!in_array(strtolower($ext), $allow)) { continue; } $new_name = date('YmdHis') . '_' . uniqid() . '.' . $ext; move_uploaded_file($tmp_name, '../uploads/' . $new_name); $image_paths[] = '/uploads/' . $new_name; } } // 3. 写入数据库 $sql = "INSERT INTO goods (user_id, title, description, price, original_price, images, status) VALUES (?, ?, ?, ?, ?, ?, 1)"; $stmt = $this->db->prepare($sql); $stmt->execute([ $_SESSION['user_id'], $title, $_POST['description'], $price, $_POST['original_price'] ?? 0, implode(',', $image_paths) ]); $this->response(200, '发布成功', ['goods_id' => $this->db->lastInsertId()]); }

这里有几个值得注意的细节。图片校验用了pathinfo取扩展名白名单过滤,能挡住常见的不安全文件上传;文件名加了date + uniqid双重随机,避免重名覆盖;images字段存储的是逗号分隔的相对路径,前端展示时用explode拆成数组即可。这套逻辑对于日百级上传量的场景完全够用。

常见的安全隐患点是move_uploaded_file之前没有校验文件大小,如果服务器upload_max_filesize设置得大,会存在磁盘被撑满的风险。我一般在生产环境会加一层「文件大小 + 图片真实类型(用getimagesize验证 MIME)」的双重检查。

3.2 下单逻辑:事务处理与库存扣减

用户点击「立即购买」后,前台的order.php接口执行下单。这块代码用了事务来保证数据一致性:

// api/order.php - create 动作 public function create() { $goods_id = intval($_POST['goods_id']); $buyer_id = $_SESSION['user_id']; // 开启事务 $this->db->beginTransaction(); try { // 1. 锁定商品行,防止超卖 $sql = "SELECT * FROM goods WHERE id = ? AND status = 1 FOR UPDATE"; $stmt = $this->db->prepare($sql); $stmt->execute([$goods_id]); $goods = $stmt->fetch(); if (!$goods) { throw new Exception('商品不存在或已下架'); } // 2. 不允许购买自己的商品 if ($goods['user_id'] == $buyer_id) { throw new Exception('不能购买自己发布的商品'); } // 3. 生成订单号并写入订单表 $order_sn = date('YmdHis') . str_pad(mt_rand(1, 9999), 4, '0', STR_PAD_LEFT); $sql = "INSERT INTO orders (order_sn, goods_id, buyer_id, seller_id, amount, pay_status, trade_status) VALUES (?, ?, ?, ?, ?, 0, 0)"; $stmt = $this->db->prepare($sql); $stmt->execute([ $order_sn, $goods_id, $buyer_id, $goods['user_id'], $goods['price'] ]); // 4. 商品标记为已售 $sql = "UPDATE goods SET status = 2 WHERE id = ?"; $stmt = $this->db->prepare($sql); $stmt->execute([$goods_id]); $this->db->commit(); $this->response(200, '下单成功', ['order_sn' => $order_sn]); } catch (Exception $e) { $this->db->rollBack(); $this->response(500, $e->getMessage()); } }

SELECT ... FOR UPDATE是这里的关键,它把商品行锁住,防止两个用户同时下单同一件商品时出现超卖。lock之后再做「是否自己商品」「是否已下架」的判断,最后统一提交。这套逻辑是标准的电商下单模型,迁移到别的语言或框架也是同样的套路。

下单成功后,goods.status变为 2(已售),商品从前台列表消失,避免被重复下单。这里的取舍是「先锁单、后支付」,如果用户不支付,订单会一直占用商品——项目里没做超时释放逻辑,这是一个明显的待优化点,我会在避坑章节细说。

3.3 个人二维码收款:无第三方支付通道的模拟方案

这个源码没有接入支付宝或微信支付官方 API,而是采用「个人收款二维码 + 用户确认」的模拟流程。在支付页面,系统读取卖家在个人中心上传的收款二维码图片,展示给买家;买家扫码转账后,点击「我已支付」按钮,把支付状态改为已支付。

关键代码在api/order.php的pay动作:

// api/order.php - pay 动作 public function pay() { $order_sn = $_POST['order_sn']; $user_id = $_SESSION['user_id']; // 校验订单归属 $sql = "SELECT * FROM orders WHERE order_sn = ? AND buyer_id = ?"; $stmt = $this->db->prepare($sql); $stmt->execute([$order_sn, $user_id]); $order = $stmt->fetch(); if (!$order) { $this->response(403, '订单不存在或无权操作'); } if ($order['pay_status'] == 1) { $this->response(400, '订单已支付,请勿重复操作'); } // 更新支付状态 $sql = "UPDATE orders SET pay_status = 1, pay_time = NOW() WHERE id = ?"; $stmt = $this->db->prepare($sql); $stmt->execute([$order['id']]); // 更新交易状态为进行中 $sql = "UPDATE orders SET trade_status = 1 WHERE id = ?"; $stmt = $this->db->execute($sql); $this->response(200, '支付成功'); }

这套模拟支付的思路很直白:没有回调验证、没有金额核对,全凭买家自觉点击确认。放在真实场景里当然有资损风险,但这正是教学项目的价值所在——你可以在不碰任何支付资质的前提下把订单流程完整跑通。要做成真实可用,后面接微信支付 Native 或支付宝当面付时,只需要把pay动作替换成「调用支付API -> 回调验签 -> 更新订单」,前台的交互流程几乎不用动。

4. 独立后台实战:登录鉴权、数据看板与订单管理

4.1 后台登录与会话保持的实现细节

后台入口admin/login.php的逻辑很典型。它先检查当前是否已有登录 Session,有就直接跳转看板;没有则处理登录表单。密码校验用password_verify配合存储在config.php里的password_hash值,避免明文密码:

// admin/login.php - 核心片段 session_start(); require_once '../includes/db.php'; require_once '../includes/functions.php'; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $username = trim($_POST['username']); $password = $_POST['password']; // 从配置文件读取管理员账号(生产环境应迁到数据库) $admin_user = ADMIN_USER; // 例如 'admin' $admin_hash = ADMIN_HASH; // password_hash('your_password', PASSWORD_DEFAULT) if ($username === $admin_user && password_verify($password, $admin_hash)) { $_SESSION['admin_id'] = session_id(); $_SESSION['admin_name'] = $username; header('Location: dashboard.php'); exit; } else { $error = '用户名或密码错误'; } }

这个做法的优点是ADMIN_USER和ADMIN_HASH定义在admin/config.php里,改密码只需要重新生成一次 hash 写入配置文件。缺点也明显:多管理员场景没支持、密码变更需要改文件。在二次开发时可以先把admin_users表建起来,把校验逻辑改为查表 +password_verify,改动量不大。

4.2 数据看板:用 SQL 聚合统计核心业务指标

dashboard.php是后台首页,用几个简单的聚合查询展示核心指标。这里体现了「SQL 即报表」的思路,没有引入任何图表库,用数字卡片和简单的表格呈现:

// admin/dashboard.php - 统计逻辑 // 今日新增用户 $today_users = $db->query("SELECT COUNT(*) FROM users WHERE DATE(create_time) = CURDATE()")->fetchColumn(); // 今日订单数 $today_orders = $db->query("SELECT COUNT(*) FROM orders WHERE DATE(create_time) = CURDATE()")->fetchColumn(); // 待处理订单(已支付但未完成) $pending_orders = $db->query("SELECT COUNT(*) FROM orders WHERE pay_status = 1 AND trade_status = 1")->fetchColumn(); // 商品总数与在售商品 $goods_total = $db->query("SELECT COUNT(*) FROM goods")->fetchColumn(); $goods_on_sale = $db->query("SELECT COUNT(*) FROM goods WHERE status = 1")->fetchColumn();

这些聚合查询都走的是「全表扫描 + 日期函数」,在数据量低于十万级时性能完全够用。如果后续用户量上来了,需要给create_time加索引,或者把统计改成定时任务落汇总表,这是后话。对于这个项目当下的定位,dashboard 的「数字卡片 + 最近订单表格」的呈现方式很直白,运营一眼能看清当天的交易活跃度。

4.3 订单管理页:条件筛选与状态流转操作

order_list.php是运营日常使用最频繁的页面,它支持按订单号模糊搜索、按支付状态筛选、按时间倒序排列。每条订单后面根据pay_status和trade_status的组合显示不同的操作按钮:

pay_statustrade_status可执行操作
0 待支付0 进行中关闭订单
1 已支付1 进行中标记完成
1 已支付2 退款中同意退款 / 拒绝退款
2 已取消0 进行中无操作

状态流转的核心是一个 update 语句,搭配前端确认弹窗:

// admin/order_list.php - 状态更新 if (isset($_POST['action']) && isset($_POST['order_id'])) { $order_id = intval($_POST['order_id']); $action = $_POST['action']; switch ($action) { case 'close': // 关闭订单:恢复商品状态为在售 $db->beginTransaction(); $db->exec("UPDATE orders SET pay_status = 2, trade_status = 0 WHERE id = $order_id"); $db->exec("UPDATE goods SET status = 1 WHERE id = (SELECT goods_id FROM orders WHERE id = $order_id)"); $db->commit(); break; case 'complete': $db->exec("UPDATE orders SET trade_status = 2 WHERE id = $order_id"); break; case 'refund': $db->exec("UPDATE orders SET trade_status = 3, pay_status = 2 WHERE id = $order_id"); break; } header('Location: order_list.php'); exit; }

「关闭订单时恢复商品状态」这个联动是个很容易漏掉的细节。如果只把订单关了不把goods.status改回 1,商品就永远躺在已售列表里卖不出去。事务在这里再次发挥作用,确保订单关闭和商品恢复要么都成功、要么都失败。

5. 部署与运行:从压缩包到能访问的完整流程

5.1 环境准备:PHP 版本与伪静态配置

这个项目对运行环境的要求很低,用 PHP 5.6 或 7.x 都可以跑,MySQL 5.7 + 即可。推荐用 phpStudy 或 Laragon 这类集成环境,Windows 和 macOS 都能装。唯一需要留意的是伪静态——如果你希望前台 URL 形如/goods.php?id=12而不是/goods.php?id=12&from=list,可以在 Apache 的.htaccess里加上:

RewriteEngine On RewriteRule ^goods/([0-9]+)$ goods.php?id=$1 [L]

如果是 Nginx,对应的配置是:

location /goods/ { rewrite ^/goods/([0-9]+)$ /goods.php?id=$1 last; }

这一步不是必须的,但对于想让 URL 更整洁的改造场景,提前配好伪静态能给后面省事。

5.2 数据库初始化:导入 install.sql 的步骤与参数调整

把压缩包解压到 Web 根目录后,首先要做的不是改代码,而是建库。打开 phpMyAdmin 或命令行执行:

mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS second_hand DEFAULT CHARACTER SET utf8mb4;" mysql -u root -p second_hand < install.sql

接着修改includes/db.php里的连接参数:

// includes/db.php define('DB_HOST', '127.0.0.1'); define('DB_NAME', 'second_hand'); define('DB_USER', 'root'); define('DB_PASS', '你的数据库密码'); define('DB_CHARSET', 'utf8mb4');

这里有个容易踩的坑:install.sql里如果有CREATE TABLE的字符集不是 utf8mb4,数据表后续插入中文可能报「Incorrect string value」错误。解决办法是导入前先确认表定义里的DEFAULT CHARSET是 utf8mb4,或者导入后用ALTER TABLE goods CONVERT TO CHARACTER SET utf8mb4;统一转一下。

5.3 前台与后台的首次访问:检查清单

启动环境后,浏览器访问:

  • 前台首页:http://localhost/index.php
  • 后台入口:http://localhost/admin/login.php
  • 默认管理员账号:见admin/config.php里的ADMIN_USER,初始密码需要你自己生成 hash 后写入

前台注册一个新用户后,可以完整走一遍「发布商品 → 浏览商品 → 下单 → 个人中心上传收款码 → 模拟支付 → 后台关单/完成」的闭环。如果某个环节报 500,先看 PHP 错误日志(通常是php_errors.log或集成环境的日志目录),90% 的问题是数据库连接失败或uploads目录没有写权限。

uploads目录的权限在这里是个高频问题。在 Linux/macOS 下,要给 Web 用户写权限:

chmod -R 775 uploads/ chown -R www-data:www-data uploads/ # 具体用户取决于你的 Web 服务

否则前端上传商品图片时会报「无法移动文件到上传目录」之类的错误,而日志里未必会打出来,排查思路容易绕远。

6. 避坑与常见问题排查:部署运行中那些翻车现场

6.1 后台登录后无限跳回登录页

现象:输入正确的管理员账号密码,浏览器跳到dashboard.php,又被拉回login.php。

原因:Session 没有正常保持。大部分情况是session_start()之前有输出(比如 BOM 头或空格),导致 Session 写入失败;还有一种情况是cookie域配置不对。

解决:检查admin/login.php和includes/db.php最顶部,确保<?php前面没有任何空格或换行;检查 php.ini 里的session.cookie_path是否与项目路径匹配。我一般会在db.php里显式声明:

if (session_status() === PHP_SESSION_NONE) { session_set_cookie_params(['path' => '/']); session_start(); }

6.2 商品图片上传成功但前端不显示

现象:发布商品时没有报错,列表页图片位置却是空白或裂图。

原因:图片路径拼接错误。api/goods.php里存的是/uploads/xxx.jpg,如果项目的实际访问路径不在域名根目录(比如http://localhost/second_hand/),这个相对路径就会指向根目录下的 uploads,自然 404。

解决:把存储路径改成相对当前脚本位置的形式,或者做一个统一的基础路径处理:

// includes/functions.php function asset_url($path) { $base = dirname($_SERVER['SCRIPT_NAME']); return rtrim($base, '/') . '/' . ltrim($path, '/'); }

前端展示时调用asset_url($goods['images'])进行归一化。这也是很多从压缩包直接部署的项目最容易踩的「路径环境不一致」问题,根源在于写代码的人默认部署在根目录。

6.3 订单关闭后商品状态不一致

现象:买家取消订单后,后台管理员关闭了订单,但前台商品列表里看不到该商品重新上架。

原因:前面提到过,关闭订单时如果没有联动物流把goods.status改回 1,商品会一直停留在 status=2(已售)状态。这个 bug 在源码的order_list.php里其实已经处理了,但有些分支(比如买家在前台主动取消)没有做联动。

解决:在api/order.php的cancel动作里,同样加入事务:

// api/order.php - cancel 动作 public function cancel() { $order_id = intval($_POST['order_id']); $user_id = $_SESSION['user_id']; $this->db->beginTransaction(); try { // 校验订单归属和状态 $stmt = $this->db->prepare("SELECT * FROM orders WHERE id = ? AND buyer_id = ? AND pay_status = 0"); $stmt->execute([$order_id, $user_id]); if (!$stmt->fetch()) { throw new Exception('订单不存在或无法取消'); } $this->db->exec("UPDATE orders SET pay_status = 2, trade_status = 0 WHERE id = $order_id"); // 关键是这一步:恢复商品在售状态 $this->db->exec("UPDATE goods SET status = 1 WHERE id = (SELECT goods_id FROM orders WHERE id = $order_id)"); $this->db->commit(); } catch (Exception $e) { $this->db->rollBack(); $this->response(500, $e->getMessage()); } }

从那以后我每次改这种带状态流转的项目,都会强制把所有修改订单状态的地方摊开来看一遍,确认每个状态变更都合得上商品表的状态变更,「下单锁商品、关单放商品」这个闭环必须一致。

6.4 数据库中文乱码

现象:后台商品列表里的中文标题显示成 ?? 或乱码。

原因:数据库连接没有指定 utf8mb4 字符集,或者数据表本身建表时用的不是 utf8mb4。

解决:在includes/db.php的 PDO 连接后强制设置字符集:

$this->db->exec("SET NAMES utf8mb4");

同时在执行install.sql之前,把 SQL 文件另存为 UTF-8 无 BOM 格式(用 VS Code 或 Sublime 转换),再把CREATE TABLE语句里的DEFAULT CHARSET统一改为utf8mb4。这两个动作同时做,基本能根治乱码问题。

7. 二次开发指南:从演示项目到可商用项目的三个改造方向

7.1 把模拟支付替换为真实支付通道

这个项目最大的改造空间就在这里。当前的「个人二维码 + 手动确认」流程适合演示,但要做成真实可运营的二手交易平台,必须接支付 API。以微信支付 Native 为例,改造思路是:

  1. 在api/order.php的pay动作里,调用微信支付下单接口,拿到code_url;
  2. 前端用轮询或 WebSocket 检测支付结果;
  3. 在支付回调 URL 里做验签和订单更新,把pay_status置为 1。
// 改造示意:微信支付回调处理 $wechat_sign = $_POST['sign']; $post_data = file_get_contents('php://input'); // 1. 校验签名 $calc_sign = md5($post_data . MD5_KEY); if ($calc_sign !== $wechat_sign) { exit('签名错误'); } // 2. 更新订单 $out_trade_no = $_POST['out_trade_no']; $stmt = $db->prepare("UPDATE orders SET pay_status = 1, pay_time = NOW() WHERE order_sn = ?"); $stmt->execute([$out_trade_no]);

这块改动别看代码不多,但对支付安全性的理解要求很高:回调验签、幂等处理(防止重复通知重复改状态)、金额核对(防止篡改参数),每一项都是生产环境必需的。我第一版做支付对接时就因为漏了金额核对,被测试环境里改订单金额的请求直接打穿了——从那以后验签和金额核对成了我接支付通道的固定动作。

7.2 增加订单超时自动释放机制

原始代码里,下单后如果不支付,商品会一直被锁定。真实场景下这会严重影响转化率。改造方案是在订单表加一个expire_time字段,用一个每分钟执行的定时任务扫描超时订单:

# crontab - 每分钟检查超时订单 * * * * * php /path/to/project/cron/release_expired_orders.php

对应的脚本逻辑:

// cron/release_expired_orders.php require_once '../includes/db.php'; $sql = "SELECT id, goods_id FROM orders WHERE pay_status = 0 AND create_time < (NOW() - INTERVAL 30 MINUTE)"; $stmt = $db->query($sql); $expired_orders = $stmt->fetchAll(); foreach ($expired_orders as $order) { $db->beginTransaction(); try { $db->exec("UPDATE orders SET pay_status = 2 WHERE id = {$order['id']}"); $db->exec("UPDATE goods SET status = 1 WHERE id = {$order['goods_id']}"); $db->commit(); } catch (Exception $e) { $db->rollBack(); } }

30 分钟是常见的选择,具体时长可以根据你的平台属性调整。这个脚本和后台「关闭订单」操作是同一套逻辑,建议把「释放订单 + 恢复商品」封装成一个公共函数,避免多处维护。

7.3 给后台升级多管理员权限

当前单管理员设计在小规模运营时没问题,但如果你要分运营、财务、客服角色,就需要引入admin_users表和角色字段。我的做法是:

ALTER TABLE admin_users ADD COLUMN role ENUM('super', 'ops', 'finance') DEFAULT 'ops';

然后在includes/auth.php里增加一个权限检查函数:

function require_role($role) { if ($_SESSION['admin_role'] !== 'super' && $_SESSION['admin_role'] !== $role) { die('无权访问此页面'); } }

在订单管理页对财务角色只开放查看和退款操作,对运营角色开放商品上下架,超管才有用户管理权限。这个改造做完,后台的使用边界就清晰了,也方便后续接审计日志。

整个项目跑下来,我的体会是:它的代码风格偏向「能跑就行」的原生派,没有炫技的框架,没有过度抽象,但核心电商链路是完整且合理的。买家和卖家两条身份线的区分、商品状态与订单状态的联动、前台与后台的权限隔离,这几个骨架性的设计都立得住。对于想学 PHP 原生开发或者想快速搭一个可演示的二手交易产品的人来说,把它跑通、改明白,比看一百篇框架教程都有用。希望这些踩坑记录能帮你少走几步弯路。

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

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

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

立即咨询