十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

ThinkPHP6多公众号CMS源码:多应用架构与支付回调隔离实践

ThinkPHP6多公众号CMS源码:多应用架构与支付回调隔离实践 简介这是一套基于ThinkPHP6框架的多微信管理系统后台源码面向需要同时管理多个微信公众号及对应企业商户支付场景的PHP开发者或团队。系统不依赖微信开放平台可实现多公众号统一管理、微信支付自动匹配到对应企业商户权限认证、附件管理、一键CURD等开箱即用适合作为中后台项目的基础底座。资源包共约9.52MB含2001个文件以PHP业务代码、JS交互逻辑、HTML模板为主辅以CSS样式、SQL数据表及配置文件目录结构清晰便于定位与维护。已有263人学习下载代码基于成熟框架开发注释规范、易于二次扩展有助于降低开发成本、快速聚焦业务深度实现。1. 多公众号后台为什么难做ThinkPHP6 CMS 源码的多租户思路做过公众号矩阵的人都有体会账号一多后台登进登出token 失效重来支付回调和商户号对不上每次排查都像破案。这套基于 ThinkPHP6 的 CMS 源码把多公众号管理做成了后台内的独立配置不依赖微信开放平台绑定每个公众号维护自己的 AppID、Secret 和微信支付商户号适合需要给多个品牌或区域业务独立结算的团队。后端用 TP6 多应用模式前端用 X-admin2.2 layui2.5自带权限认证、附件管理和一键 CURD 命令。对写过 TP 的人来说几乎没有上手成本但如果你是第一次接触多公众号系统我建议先读透配置模型和回调分发这两块否则后面改起来容易把公众号配置和支付商户号耦合在一起越改越乱。2. ThinkPHP6 多应用架构与多公众号配置的落地方式2.1 多应用模式下如何拆分公众号业务模块ThinkPHP6 默认是单应用但官方提供了topthink/think-multi-app多应用扩展安装后在 config/app.php 里设置auto_multi_app true每个应用目录下都有自己的 controller、model、view。为什么一定要拆应用因为后台管理、微信消息接收、支付回调这三个入口的安全要求完全不同。后台要登录鉴权微信接口要走签名验签和 XML 响应如果不分离中间件规则会互相干扰。目录结构如下project-root/ ├── app/ │ ├── admin/ # 后台管理应用 │ │ ├── controller/ │ │ ├── model/ │ │ └── view/ │ ├── api/ # 微信接口应用 │ │ ├── controller/ │ │ └── middleware/ │ └── common/ # 公共模型、服务 ├── config/ ├── route/ └── public/后台管理放在admin控制器如MpConfig、Order微信服务器要访问的地址放在api比如Notify控制器负责回调Wechat控制器负责消息接入。TP6 多应用模式下的 url 规则是/应用名/控制器/操作所以回调地址会形如https://你的域名/api/notify/wxpay。如果不想在 URL 里暴露api字样可以在route/app.php中为 api 应用绑定独立域名。api应用下的控制器还需要在基类中做统一处理关闭 session设置默认返回类型为 xml因为微信服务器要求回调返回固定格式 XML 文本。这里贴一段我在 api 基类里常用的中间件配置namespace app\api\middleware; class XmlResponse { public function handle($request, \Closure $next) { $response $next($request); $response-contentType(text/xml, charsetutf-8); return $response; } }中间件逻辑说明$next($request)执行后续控制器拿到响应对象后强制改成 XML 类型。微信接口验签中间件也放在这个应用里不会被后台登录逻辑干扰。下面这张表列出了不同应用目录的职责边界二开时新增模块先想清楚该放哪一层。应用目录职责典型控制器访问方式admin后台管理、权限、公众号配置、订单管理MpConfig, Order, Upload/admin/mp_config/indexapi微信消息接收、支付回调、token 获取Notify, Wechat, AccessToken/api/notify/wxpaycommon公共模型、服务层不直接对外MpConfigService, OrderService-2.2 不借助微信开放平台的配置模型先说明背景微信开放平台的典型能力是打通 UnionID、统一授权但如果你只是要让多个公众号各自登录、各自支付到各自商户号用开放平台反而增加复杂度。这套源码的做法是把公众号自身的 AppID、AppSecret 和微信支付商户号写进一张配置表同一个后台里按「公众号标识」读取不同配置。建表语句如下CREATE TABLE wc_mp_config ( id int(11) unsigned NOT NULL AUTO_INCREMENT, appid char(32) NOT NULL DEFAULT COMMENT 公众号AppID, appsecret char(64) NOT NULL DEFAULT COMMENT 公众号AppSecret, mch_id char(32) NOT NULL DEFAULT COMMENT 微信支付商户号, pay_key char(64) NOT NULL DEFAULT COMMENT 商户平台API密钥, cert_path varchar(255) DEFAULT COMMENT apiclient_cert.pem相对路径, status tinyint(1) NOT NULL DEFAULT 1 COMMENT 1启用 0停用, create_time int(11) NOT NULL DEFAULT 0, PRIMARY KEY (id), UNIQUE KEY uk_appid (appid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT多公众号配置表;字段设计里有两个关键点appid做了唯一约束保证配置表里不会出现同一个公众号两份配置pay_key是微信支付 V2 的 API 密钥V3 模式则不需要它而是换成商户证书加 APIv3 密钥。cert_path建议只存相对路径比如cert/mp_1/apiclient_cert.pem部署到新服务器时整体移动目录不用改数据库。代码里不能每次都查数据库需要缓存整个配置表。TP6 的缓存门面可以直接做到use think\facade\Cache; function getMpConfig(string $appid): array { $mps Cache::remember(mp_config_all, function () { return \think\facade\Db::name(wc_mp_config) -where(status, 1) -column(appid, appsecret, mch_id, pay_key, cert_path, appid); }, 300); return $mps[$appid] ?? []; }Cache::remember的作用是缓存不存在时执行闭包并写入缓存第三个参数 300 表示缓存 300 秒。这样在后台新增公众号后最多 5 分钟缓存过期自动生效。column方法第一个参数是字段列表第二个参数是数组下标键直接用appid作为键后面取数据时复杂度只有 O(1)。这套思路和「开放平台绑定」最大区别是授权关系不是由微信侧维护而是由你自己在业务代码里管理。好处是自由度高新加公众号不需要等开放平台审核代价是公众号消息后台硬要配置同一个服务器地址时你需要拿到消息里的ToUserName去路由到具体公众号逻辑这个问题在第 4 章展开。3. 权限认证与一键 CURD后台可维护性的底层支撑3.1 基于 RBAC 的权限节点设计后台管理系统绕不开权限这套源码自带权限认证核心是 RBAC 模型。设计上有五张表管理员表、角色表、节点表、角色-节点关联表、管理员-角色关联表。其中管理员表简单到足够放进业务里CREATE TABLE system_admin ( id int(11) unsigned NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录名, password varchar(255) NOT NULL COMMENT 密码哈希, role_id int(11) unsigned NOT NULL DEFAULT 0, status tinyint(1) NOT NULL DEFAULT 1, last_login_time int(11) NOT NULL DEFAULT 0, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;配合system_role角色表和system_node节点表节点表一条记录对应一个控制器操作。比如mp_config/add就是一个节点管理员登录后把其允许访问的节点数组放入缓存每次请求通过中间件校验节点是否在数组中。TP6 中间件实现可以这样写namespace app\admin\middleware; use think\facade\Cache; use think\facade\Session; class AuthCheck { public function handle($request, \Closure $next) { $adminId Session::get(admin_id); if (!$adminId) { return redirect(/admin/login/index); } // 把控制器/操作拼接成权限节点如 mp_config/add $node strtolower($request-controller() . / . $request-action()); $perms Cache::get(admin_perms_ . $adminId, []); if (!in_array($node, $perms, true)) { return json([code 0, msg 无权限操作]); } return $next($request); } }参数说明$request-controller()返回当前控制器名$request-action()返回操作名。in_array第三个参数为true表示严格比较避免大小写绕过。节点数组在后台角色编辑后需要刷新缓存常见做法是角色保存成功后Cache::delete(admin_perms_ . $adminId)否则会出现「角色已经改权限用户重新登录还是旧权限」的情况。3.2 一键 CURD 的生成逻辑与二次开发一键 CURD 是本源码最提效的功能。原理是自定义 ThinkPHP6 命令读取数据表结构自动生成控制器、模型、视图文件。命令使用方式类似php think make:curd --tablewc_mp_config --controllerMpConfig --appadmin参数说明--table指定数据表名--controller指定控制器类名--app是 TP6 多应用下的应用名。命令内部查表结构生成的内容可以直接在后台使用。生成的控制器骨架大概是namespace app\admin\controller; use app\common\model\MpConfig; use think\response\Json; class MpConfig extends Base { public function index(): Json { $list MpConfig::paginate(15); return json([code 0, data $list]); } public function add() { $data request()-post(); MpConfig::create($data); return json([code 0, msg 添加成功]); } public function edit() { $data request()-post(); MpConfig::update($data, [id request()-post(id)]); return json([code 0, msg 更新成功]); } public function delete() { MpConfig::destroy(request()-post(id)); return json([code 0, msg 删除成功]); } }这段代码的业务逻辑完全依赖模型没有复杂的 SQL二开成本很低。但注意paginate(15)返回的是 TP6 分页对象layui 表格需要的是data数组所以我在 index 方法里做了json([code 0, data $list])实际代码要按前端数据格式微调。我一般会在生成后做两件事第一把add/edit里的业务校验抽到app\common\validate目录比如 appid 必须是合法微信号第二把订单号生成、支付签名这类复杂逻辑放到 service 层控制器只做参数接收和返回。这样后面接入微信支付回调时控制器不会被业务代码占满。3.3 附件管理与富文本组件集成源码包里的bootstrap.css、layui.css、ueditor.css、video-js.css等文件说明后台集成了 UEditor 富文本和 video-js 播放器。附件管理通常是全局上传接口namespace app\admin\controller; use think\facade\Filesystem; class Upload { public function image() { $file request()-file(file); if (!$file) { return json([code 0, msg 未接收到文件]); } $path Filesystem::disk(public)-putFile(image, $file); if ($path) { return json([code 1, url /storage/ . $path]); } return json([code 0, msg 上传失败]); } }putFile(‘image, $file)会按日期自动分目录返回的是image/20250315/xxxx.jpg这样的相对路径访问时拼上/storage/前坠即可。UEditor 的config.json里serverUrl要指到/admin/upload/image这样富文本插入的图片也能走附件管理不会被传到第三方图床。4. 多公众号消息与支付回调的分离处理4.1 公众号 token 的多租户缓存隔离微信公众号接口调用前需要 access_token多公众号场景下最典型的错误是全局只用一个 token公众号 A 的 token 被公众号 B 的请求覆盖导致接口报invalid credential。正确做法是缓存 key 带上 appid过期时间也要人为缩短到 7000 秒不要等到微信侧 7200 秒完全过期再刷新防止边界请求失败。namespace app\common\service; use think\facade\Cache; class MpTokenService { public function getToken(string $appid, string $appsecret): string { $cacheKey mp_access_token_ . $appid; $token Cache::get($cacheKey); if ($token is_string($token)) { return $token; } $token $this-requestToken($appid, $appsecret); if (!empty($token)) { Cache::set($cacheKey, $token, 7000); } return $token; } private function requestToken(string $appid, string $appsecret): string { $url sprintf( https://api.weixin.qq.com/cgi-bin/token?grant_typeclient_credentialappid%ssecret%s, $appid, $appsecret ); $res json_decode(file_get_contents($url), true); return $res[access_token] ?? ; } }注意Cache::set的第二个参数必须用字符串。如果多个服务器同时刷新同一个公众号 token会出现重复请求不过这部分代价很小场景复杂时可以用 Redis 加锁这里不展开。file_get_contents是演示用生产环境建议换成 Guzzle 并设置 5 秒超时。4.2 微信支付回调按商户号分发后台可以配置一个统一的回调域名但多个公众号使用不同的商户号收款时回调地址只能配一个入口后端必须知道这笔订单到底属于哪个公众号和商户。我推荐的方案是订单号生成时带公众号前缀比如M01、M02回调解析out_trade_no后通过前缀找到对应订单和商户密钥。namespace app\api\controller; use app\common\service\OrderService; class Notify { public function wxpay() { $xml file_get_contents(php://input); $data $this-fromXml($xml); $orderNo $data[out_trade_no] ?? ; // 假设订单号格式M01 时间戳 随机串 $mpPrefix substr($orderNo, 0, 3); $order OrderService::getByOrderNo($orderNo); if (!$order || $order[mp_prefix] ! $mpPrefix) { return $this-toXml([return_code FAIL, return_msg 订单不存在]); } // 验签需要此订单对应商户号的密钥不可以用全局固定密钥 $verify $this-verifySign($xml, $order[pay_key]); if (!$verify) { return $this-toXml([return_code FAIL, return_msg 验签失败]); } OrderService::paySuccess($order[id], $data[transaction_id]); return $this-toXml([return_code SUCCESS, return_msg OK]); } }代码逻辑说明fromXml负责把微信回调 XML 转数组substr($orderNo, 0, 3)取出公众号前缀结合数据库里的订单记录核对一致性验签函数需要传入$order[pay_key]这样才能保证商户号 A 的订单不会用商户号 B 的密钥去验。微信支付 V3 下验签逻辑更复杂需要平台证书序列号和签名但核心原则不变验签密钥或证书必须与订单商户号绑定。提示回调响应体必须是微信要求的 XML 格式任何多余的输出都会导致微信重试。调试时不要直接在控制器里使用dump()这会污染输出。可以用本章最后一节介绍的 var-dump-server。4.3 layui 表格展示多公众号订单后台订单列表用 layui table 搭配后端分页接口前端代码如下table.render({ elem: #orderTable, url: /admin/order/index, page: true, limit: 20, cols: [[ { field: order_no, title: 订单号 }, { field: mp_name, title: 所属公众号 }, { field: amount, title: 金额(元) }, { field: pay_status, title: 状态, templet: function (d) { return d.pay_status 1 ? 已支付 : 未支付; }} ]] });这里/admin/order/index走的是 TP6 多应用模式下的 url 格式后端控制器根据查询参数mp_id过滤订单即可。pay_status用templet回调把 0/1 转成文案比在服务端拼 HTML 更清晰。layui table 默认请求参数是page和limit后端用paginate($limit)接收返回格式要与约定一致{code:0, count: 总条数, data: 列表}。5. 从源码包到可用项目的验证与排查技巧5.1 快速起步Composer 安装与目录写权限拿到 zip 解压后先确认环境PHP 8.0 及以上需要安装topthink/think-multi-app扩展。首次启动按以下顺序执行命令。composer install --no-dev cp .env.example .env php think run --port 8000参数说明--no-dev跳过开发依赖.env文件里配置数据库连接、缓存驱动、app 调试模式。TP6 的入口文件在public目录生产环境要把站点根目录指到public否则路由和静态资源都会出问题。首次打开后台前还要确认runtime和storage目录可写否则报错信息干干净净看不到。5.2 高频踩坑Nginx 重写与回调链接公众号后台配接口地址时域名必须是公网可访问而且尽量不要在路径里再套一层index.php。TP6 多应用模式对 URL 重写很敏感Nginx 配置如下server { listen 80; server_name yourdomain.com; root /var/www/tp6-cms/public; index index.php; location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s$1 last; } } }这段配置的作用是请求的文件不存在时全部重写到入口文件TP6 通过s参数解析应用名、控制器和操作。如果重写规则漏掉会出现/api/notify/wxpay直接返回 404。修改配置后nginx -s reload再用 curl 访问一次回调接口curl https://yourdomain.com/api/notify/wxpay看到空响应或者微信错误码都算正常看到明文报错说明应用配置没生效。5.3 使用 var-dump-server 捕获接口调试输出源码包里带了var-dump-server.bat这是 Symfony VarDumper 组件的调试工具。微信支付回调场景下直接dump()会污染响应 XML导致微信认为回调失败。我的做法是启动这个服务然后在代码里dump($data)输出不会进入 HTTP 响应而是显示在 var-dump-server 控制台。# Windows 环境直接双击或命令行执行 ./var-dump-server.bat启动后本地代码中直接使用dump($xml)可以看到完整回调 XML、数组结构甚至变量类型接口响应保持纯净。这个技巧对调试微信消息推送、支付回调这类不能改变响应体的接口非常实用。5.4 验证多公众号支付到账的检查清单检查项操作方法期望结果配置读取后台分别填写公众号 A/B 的 appid、secret、商户号列表展示两条记录且状态为启用token 隔离在消息接口打印缓存 key两个 appid 对应两个不同 key 的 token互不覆盖统一下单公众号 A 下单使用商户号 A 的密钥签名下单成功返回 prepay_id无签名错误回调验签用订单号反查商户号密钥验签return_code 为 SUCCESS订单状态变为已支付资金隔离分别查看两个商户号的流水明细交易记录与订单所属公众号一一对应按这个顺序走一遍基本能确定这套多微信管理系统源码在 TP6 下的核心链路是通的。最后再补充一点上线前把每个公众号后台的 IP 白名单加上支付回调逻辑尽量改为异步队列处理避免微信超时重试导致重复回调。本文还有配套的精品资源点击获取
返回列表