简介:这是一套基于ThinkPHP 5.1开发的多用户B2B2C商城系统源码,面向PHP中级开发者、电商系统学习者及二次定制需求的技术人员,旨在提供可落地的分布式电商架构实践样本。资源共6887个文件,涵盖1848个PHP核心逻辑文件、210个SQL数据库脚本(含mobile.sql移动端结构)、452个HTML模板页、370个JS交互脚本、2571张JPG/PNG商品与界面素材,以及配置、样式、字体等配套文件,压缩包达178.85MB,结构完整、模块解耦清晰。已有1438人学习下载,适合深入理解B2B2C多角色权限体系、商家入驻流程、微信/支付宝双支付集成及可视化店铺装修实现机制。读者可直接部署运行,获取含移动端适配、商家后台、平台管理中心、消费者H5页面在内的全链路代码,同时结合系统说明.txt与安装说明.txt快速掌握部署要点与功能边界。
1. ThinkPHP 5.1 多用户商城系统:不是“又一个后台模板”,而是能跑通 B2B2C 三端闭环的最小可行源码包
你有没有试过在 GitHub 或某资源站搜“ThinkPHP 商城”,结果刷出二十多个“带后台”的项目,点开一看——首页是静态 HTML,用户中心连登录态都维持不住,更别说供应商入驻、平台抽佣、分销返利这些 B2B2C 的核心链路?这个 ThinkPHP 5.1 多用户商城系统不是那种“前端炫酷、后端空转”的演示工程。它是一套真实走通了「平台方(管理端)→ 商家端(PC 后台 + 移动端 H5)→ 消费者端(微信公众号 + H5)」三角色隔离、权限收敛、订单流闭环的源码包。所有角色共用同一套 ThinkPHP 5.1 底层,但数据库表结构明确区分user(消费者)、store(商家)、admin(平台),路由和中间件层做了硬隔离。它不承诺高并发或微服务架构,但能让你在本地 PHP 7.2+ 环境下,15 分钟内拉起一个可注册、可上架、可下单、可分账的最小 B2B2C 骨架。适合正在做毕业设计、接中小型企业定制开发、或想吃透 ThinkPHP 权限体系的开发者——不是学框架语法,而是学怎么用框架把业务规则落地成代码。
2. 从零部署:环境准备、目录解压到数据库初始化的完整链路
这套源码不是“下载即用”,但它的部署路径非常清晰,没有隐藏依赖或私有扩展。我把它拆成四个不可跳过的阶段:环境校验 → 目录结构理解 → 数据库导入 → 配置文件改写。跳过任意一步,后面都会卡在“白屏”或“404 路由未定义”上。尤其注意 PHP 版本必须是 7.2~7.4 区间,ThinkPHP 5.1 对 PHP 8.x 兼容性极差,这是血泪经验。
2.1 环境检查:三个必须确认的 PHP 扩展与配置项
ThinkPHP 5.1 的运行依赖几个底层扩展,光装好 PHP 还不够。你需要在命令行执行以下检查,并确保全部返回true:
# 检查是否启用 php -m | grep -E "(pdo|curl|mbstring|openssl|fileinfo)" # 检查 upload_max_filesize 是否 ≥ 8M(商家上传商品图必备) php -i | grep "upload_max_filesize" # 检查 max_execution_time 是否 ≥ 60(避免导入大 SQL 时超时) php -i | grep "max_execution_time"提示:如果
fileinfo扩展未启用,Linux 下常见于php.ini中extension=fileinfo.so被注释;Windows 下则是extension=php_fileinfo.dll。重启 Web 服务前务必确认该扩展已加载,否则商家后台上传商品图会直接报Class 'finfo' not found。
2.2 目录结构还原:别直接扔进 wwwroot,先理清入口与模块边界
源码包解压后,你会看到类似这样的根目录结构:
thinkphp51-b2b2c/ ├── application/ # ThinkPHP 应用目录(核心:common、index、store、admin 四个模块) ├── public/ # Web 入口目录(关键!所有请求必须指向此目录下的 index.php) ├── extend/ # 自定义类库(含支付 SDK、短信封装等) ├── runtime/ # 运行时目录(首次访问自动生成,需写入权限) ├── install/ # 安装向导(含 SQL 文件和检测脚本) └── ...重点操作:
- 不要把整个
thinkphp51-b2b2c/目录直接放到 Apache/Nginx 的DocumentRoot下; - 必须将 Web 服务器的根目录(
DocumentRoot/root)指向thinkphp51-b2b2c/public/; public/index.php是唯一入口,它会自动加载../application/下的逻辑;application/store/是商家 PC 后台模块,application/index/是消费者 H5 前端,application/admin/是平台管理后台——三者共享common模块的模型和基础服务,但控制器、视图、路由完全独立。
2.3 数据库初始化:SQL 导入顺序与字符集强制声明
install/目录下通常包含b2b2c.sql(主数据表)和b2b2c_data.sql(初始管理员、测试商家、分类数据)。必须按顺序执行,且不能依赖 phpMyAdmin 的“自动识别字符集”:
# 1. 创建数据库(显式指定 utf8mb4,避免 emoji 和长标题乱码) mysql -u root -p -e "CREATE DATABASE b2b2c DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" # 2. 导入主结构(含建表语句) mysql -u root -p b2b2c < install/b2b2c.sql # 3. 导入初始数据(含 admin 密码为 '123456' 的默认账号) mysql -u root -p b2b2c < install/b2b2c_data.sql参数说明:
utf8mb4是关键。ThinkPHP 5.1 默认使用utf8字符集,但 MySQL 的utf8实际只支持 3 字节 UTF-8 字符(不支持 emoji 和部分生僻汉字)。b2b2c.sql中的CREATE TABLE语句若未显式写CHARSET=utf8mb4,导入后需手动执行ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4;,否则商家发布含 emoji 的商品标题会截断。
2.4 配置文件改写:.env与database.php的双保险机制
ThinkPHP 5.1 支持.env文件覆盖配置,但此项目同时保留了application/database.php的硬编码方式。推荐优先修改.env,因为它是 Git 忽略项,更安全:
# .env 文件内容(放在 thinkphp51-b2b2c/ 根目录下) APP_DEBUG = true APP_TRACE = false DATABASE_TYPE = mysql DATABASE_HOST = 127.0.0.1 DATABASE_NAME = b2b2c DATABASE_USER = root DATABASE_PWD = your_password DATABASE_PORT = 3306 DATABASE_PREFIX = tp5_逻辑说明:
.env中的DATABASE_PREFIX = tp5_必须与b2b2c.sql中所有表名前缀一致(如tp5_user,tp5_store)。如果 SQL 文件里表名是b2b2c_user,则此处必须改为DATABASE_PREFIX = b2b2c_,否则所有模型查询都会Table 'b2b2c.tp5_user' doesn't exist。这是新手最常翻车的点——以为改了数据库名就万事大吉,忘了前缀才是实际拼接的字符串。
3. 三端权限体系解析:如何让平台、商家、消费者各走各的路由,互不越界
B2B2C 的本质不是功能堆砌,而是角色隔离。这套源码没用第三方 RBAC 扩展,而是用 ThinkPHP 原生的middleware+route+model scope三层控制实现权限收敛。理解这三层,你才能改出自己的分销模块或供应商审核流。
3.1 路由层隔离:route/route.php中的域名与前缀分流
打开route/route.php,你会看到三组明确的路由分组:
// 平台管理后台:强制走 admin 域名或 /admin/ 前缀 Route::domain('admin.example.com', function () { Route::group('admin', function () { Route::get('login', 'admin/Login/index'); Route::get('dashboard', 'admin/Index/index')->middleware('AuthAdmin'); }); }); // 商家后台:走 store.example.com 或 /store/ 前缀 Route::domain('store.example.com', function () { Route::group('store', function () { Route::get('login', 'store/Login/index'); Route::get('goods/list', 'store/Goods/index')->middleware('AuthStore'); }); }); // 消费者 H5:走默认域名,所有路由归入 index 组 Route::group('index', function () { Route::get('login', 'index/Login/index'); Route::get('goods/detail', 'index/Goods/detail')->middleware('AuthUser'); });参数说明:
->middleware('AuthAdmin')这类中间件不是装饰器,而是真实存在的类文件:app/middleware/AuthAdmin.php。它会读取 session 中的admin_id,并查询tp5_admin表验证状态和权限等级(如 1=超级管理员,2=财务专员)。关键点在于:同一个 URL/admin/dashboard,如果用户 session 里没有admin_id,中间件会直接重定向到/admin/login,不会进入控制器。
3.2 模型层隔离:application/common/model/User.php中的scope方法
消费者、商家、平台用户虽然都叫“用户”,但数据库表不同(tp5_user,tp5_store,tp5_admin),模型也必须严格分离。以User模型为例,它不直接继承think\Model,而是:
<?php namespace app\common\model; use think\Model; class User extends Model { // 强制指定数据表,不依赖自动推导 protected $table = 'tp5_user'; // 作用域:只查 status=1 的有效用户 public function scopeActive($query) { $query->where('status', 1); } // 作用域:只查未被删除的用户(软删除字段 delete_time) public function scopeNotDeleted($query) { $query->whereNull('delete_time'); } }逻辑说明:在控制器中调用
User::active()->notDeleted()->select(),ThinkPHP 会自动拼接WHERE status = 1 AND delete_time IS NULL。这种写法比在每个where里手写条件更安全,避免漏掉软删除判断导致“已删用户”仍能登录。同理,Store模型有自己的scopeVerified()(只查已认证商家),Admin模型有scopeEnabled()(只查启用状态的管理员)。
3.3 视图层隔离:template/目录下的三套独立前端资源
很多人以为“多用户”只是后台逻辑不同,其实前端也是物理隔离的:
template/ ├── admin/ # 平台后台:基于 LayUI 的 PC 端,含商品审核、订单分账、佣金设置 ├── store/ # 商家后台:基于 Bootstrap 4 的响应式页面,含商品上架、订单发货、店铺装修 └── index/ # 消费者 H5:基于 Vue CLI 3 构建(build 后静态文件),含购物车、拼团、优惠券参数说明:
template/index/下的index.html是 Vue 项目的index.html,而template/index/下的 PHP 模板(如goods/detail.php)是 ThinkPHP 的原生模板引擎渲染页。二者共存是因为:Vue 用于交互密集页(如购物车结算),PHP 模板用于 SEO 敏感页(如商品详情页),通过fetchAPI 从api/接口获取数据。这种混合模式降低了首屏加载时间,也规避了 Vue SSR 的复杂度。
4. 避坑指南:部署与调试中高频出现的 5 类问题及根因解决
这套源码在本地开发环境跑通容易,但一上生产服务器就容易卡在一些“看似简单、实则隐蔽”的环节。以下是我在三套不同客户环境(Nginx + PHP-FPM、Apache + mod_php、宝塔面板)中踩过的真坑,按现象→原因→解决三步写清楚,拒绝模糊描述。
4.1 现象:访问/admin/login显示 404,但/index/login正常
原因:Nginx 配置未正确传递 PATH_INFO,导致 ThinkPHP 路由解析失败。ThinkPHP 5.1 依赖PATH_INFO获取路由参数,而 Nginx 默认不设置。
解决:在 Nginx server 块中添加以下 location 配置:
location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s=$1 last; } } # 关键:显式设置 fastcgi_param location ~ \.php(.*)$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_split_path_info ^((?U).+\.php)(/?.+)$; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; # ← 必须加这一行! include fastcgi_params; }4.2 现象:商家上传商品图后,H5 页面显示 “Image not found”
原因:extend/image/Upload.php中的图片保存路径写死了./uploads/store/,但 Web 服务器的open_basedir限制了该目录写入权限。
解决:修改extend/image/Upload.php第 42 行:
// 原代码(危险!) $savePath = './uploads/store/'; // 改为(使用 ThinkPHP 内置的 RUNTIME_PATH 常量) $savePath = RUNTIME_PATH . 'uploads/store/';然后在public/index.php顶部添加:
// 确保 RUNTIME_PATH 可写 define('RUNTIME_PATH', __DIR__ . '/../runtime/');4.3 现象:微信公众号支付回调notify_url返回 “签名错误”
原因:extend/wxpay/WxPay.Api.php中的curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true)强制校验 SSL 证书,但测试环境用的是自签名证书或 IP 直连,导致 cURL 中断。
解决:临时关闭证书校验(仅限开发/测试环境):
// 在 WxPay.Api.php 的 curl 初始化处,找到 CURLOPT_SSL_VERIFYPEER 行 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // ← 改为 false curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // ← 同时加这一行注意:上线前必须恢复为
true,并配置正确的cacert.pem路径。
4.4 现象:store模块登录后,点击“我的订单”跳转到/admin/order/list
原因:application/store/controller/Base.php中的initialize()方法里,$this->redirect()的 URL 写成了绝对路径/admin/...,而非相对路径order/list。
解决:打开application/store/controller/Base.php,找到protected function initialize(),将其中所有形如$this->redirect('/admin/xxx')的语句,改为$this->redirect('order/list')或$this->redirect(url('order/list'))。
4.5 现象:php think migrate:run报错 “Class 'think\migration\Command' not found”
原因:此项目未集成 ThinkPHP 官方的think-migration扩展,php think命令只支持内置命令(如make:controller),不支持迁移。所有数据库变更必须手动执行 SQL。
解决:删除command.php中所有关于migration的引用;后续加表或改字段,直接写 SQL 到install/update_v1.2.0.sql,并在application/command/Update.php中新增执行逻辑——这才是此项目的真实演进方式。
5. 订单分账与佣金计算:从tp5_order到tp5_commission_log的完整资金流追踪
B2B2C 的核心价值不在“多用户”,而在“钱怎么分”。这套源码把平台抽佣、商家返利、分销奖励三条线全写进了application/common/service/OrderService.php,逻辑清晰,改起来不伤筋动骨。我带你顺一遍从用户下单到佣金落袋的全过程,并指出三个可安全定制的关键钩子。
5.1 订单创建时的资金流向预计算
当消费者在 H5 下单成功,application/index/controller/Order.php的create()方法会调用:
$result = OrderService::instance()->createOrder($uid, $cartItems, $addressId);OrderService::createOrder()内部会做三件事:
- 插入
tp5_order主单(含total_price,pay_price,store_id); - 插入
tp5_order_goods子表(记录每个商品的goods_id,price,num); - 关键:调用
calculateCommission($order)计算并写入tp5_commission_log。
calculateCommission()的核心逻辑如下:
private function calculateCommission($order) { $store = Store::find($order['store_id']); $platformRate = $store['commission_rate'] ?: config('platform.default_rate'); // 商家自设费率,无则取平台默认 5% $commission = round($order['pay_price'] * $platformRate / 100, 2); // 平台佣金 Db::name('commission_log')->insert([ 'order_id' => $order['id'], 'store_id' => $order['store_id'], 'amount' => $commission, 'type' => 'platform_cut', // 类型:platform_cut / distributor_reward / store_refund 'status' => 'pending', // pending / success / failed 'create_time' => time(), ]); }参数说明:
$platformRate来自商家后台设置的commission_rate字段(单位 %),不是写死的。这意味着你可以让 A 商家抽 3%,B 商家抽 8%,平台无需改代码。type字段是后续对账的关键,pending状态表示“待结算”,只有当订单完成(order.status = 3)且超过售后期(如 7 天),才触发settleCommission()将状态改为success并更新tp5_store.balance。
5.2 分销返利的触发时机与数据一致性保障
分销功能藏在application/index/model/User.php的getDistributor()方法里。当用户 A 分享链接给用户 B,B 下单时,OrderService::createOrder()会检查input('invite_code')参数:
if ($inviteCode = input('invite_code')) { $distributor = User::where('invite_code', $inviteCode)->find(); if ($distributor && $distributor['is_distributor']) { $reward = round($order['pay_price'] * config('distributor.reward_rate'), 2); Db::name('commission_log')->insert([ 'order_id' => $order['id'], 'user_id' => $distributor['id'], 'amount' => $reward, 'type' => 'distributor_reward', 'status' => 'pending', ]); } }逻辑说明:分销返利和平台抽佣是两条独立日志,但共享
pending→success的结算流程。一致性保障靠数据库事务:createOrder()方法开头有Db::startTrans(),结尾有Db::commit(),中间任何一步失败(如插入commission_log失败)都会回滚整个订单创建。这是避免“订单生成了,但佣金没记”的唯一可靠手段。
5.3 对账报表生成:一个可直接复用的CommissionReport命令行工具
项目自带application/command/CommissionReport.php,它不是摆设,而是每天凌晨自动跑的对账脚本:
// 执行命令:php think commission:report --date=2023-10-01 protected function configure() { $this->setName('commission:report') ->setDescription('生成指定日期的佣金对账报表'); } protected function execute(Input $input, Output $output) { $date = $input->getOption('date') ?: date('Y-m-d', strtotime('-1 day')); $logs = Db::name('commission_log') ->where('create_time', '>=', strtotime($date)) ->where('create_time', '<', strtotime($date . ' +1 day')) ->where('status', 'success') ->select(); $total = array_sum(array_column($logs, 'amount')); // 输出 CSV 到 runtime/report/ $fp = fopen(RUNTIME_PATH . "report/commission_{$date}.csv", 'w'); fputcsv($fp, ['订单ID', '类型', '金额', '时间']); foreach ($logs as $log) { fputcsv($fp, [ $log['order_id'], $log['type'], $log['amount'], date('Y-m-d H:i:s', $log['create_time']) ]); } fclose($fp); $output->writeln("✅ {$date} 佣金报表生成完毕,总额:{$total} 元"); }参数说明:
--date=2023-10-01是必填参数,不填则默认跑昨天。CSV 文件存放在runtime/report/,该目录需有写入权限。这个脚本的价值在于:它不依赖任何前端页面,运维人员 ssh 登录后一行命令就能拿到银行打款依据,避免了“财务要数据,程序员改接口”的扯皮。从那以后我每次交付 B2B2C 项目,都强制把这个命令加到 crontab 里:0 2 * * * cd /var/www/b2b2c && php think commission:report --date=$(date -d "yesterday" +\%Y-\%m-\%d) >> /dev/null 2>&1。希望帮到你。
本文还有配套的精品资源,点击获取