简介:一套基于ThinkPHP6框架的多微信管理系统源码,前端采用X-admin2.2与layui2.5.x,面向需要同时运营多个微信公众号、并将微信支付对接到对应企业商户的PHP开发者。无需接入微信开放平台即可完成多公众号管理与支付路由,框架结构清晰,方便二次开发与功能扩展。压缩包共2001个文件,以1223个PHP核心代码为主,辅以JS、CSS、HTML前端资源,以及GIF、PNG图片素材和MD说明文档,整体体积约9.52MB,目录归类明确。系统内置权限认证、附件管理、微信公众号管理、一键CURD等常用模块,可显著简化后台开发流程;配合完整的源码与配置,开发者可快速搭建稳定的多微信管理后台,降低业务深度开发的落地成本。目前该资源已有263人学习下载,适合具备PHP基础、希望高效开展微信生态项目的中高级开发者参考。
1. 多微信管理系统源码 thinkphp6 与 cms 的关系
多微信管理系统源码 thinkphp6–cms.zip 这个标题拆开之后,先要搞清楚一件事:真正决定这套系统能不能跑起来的,往往不是那几个微信接口调用怎么写的,而是 ThinkPHP6 的项目结构本身。通常情况下,这种源码包是一个把多个公众号接入和内容管理揉在同一套代码里的后台工程,每个账号的绑定、素材、群发任务、粉丝标签都会落在 TP6 的模型和队列上,CMS 则负责把内容组织和发布规则可视化。反直觉的地方在于,接手这类源码的第一件事不是读微信 API 文档,而是确认多应用模式开没开、数据库前缀是什么、队列进程有没有在跑。适合三类人:做二次开发的后端、要接手存量项目的维护者,以及想用一套后台同时管理多个已认证服务号的产品技术负责人。
2. ThinkPHP6 多应用模式:先把地基打正
2.1 多应用模式下的 url:一套代码拆成两个后台
ThinkPHP6 默认识别的是单应用结构,控制器全放在 app/controller 下。多微信管理系统如果要在一个工程里同时容纳账号接入、CMS 内容管理、回调接口三块职能,常见做法是安装 topthink/think-multi-app 扩展,把代码按应用目录平铺开。目录结构一般长这样:
project/ ├── app/ │ ├── wxmanage/ # 微信接入、账号列表、群发任务 │ │ ├── controller/ │ │ └── service/ │ ├── cms/ # 图文编辑、素材库、发布规则 │ │ ├── controller/ │ │ └── model/ │ ├── api/ # 对外接口,比如小程序端调用 │ └── common/ # 公共模型与工具类 ├── config/ │ └── app.php ├── route/ │ └── app.php └── public/ └── index.php把应用按目录拆开的前提下,对应配置集中在 config/app.php 里:
<?php // config/app.php return [ // 默认应用,访问域名根路径时进入 CMS 后台 'default_app' => 'cms', // 开启多应用自动解析 'auto_multi_app' => true, ];注意 auto_multi_app 开启后,URL 的第一段会被解析成应用名。因此 http://域名/wxmanage/account/list 会命中的是 app/wxmanage/controller/Account.php 里的 list 方法,而 http://域名/cms/article/index 会走到内容后台。这套 URL 语义理顺之后,后面接菜单、配权限、做路由别名都顺了。实际排查中碰到的 thinkphp6 多应用模式下的 url 404,十个里有八个是这两个配置没有同时满足,或者伪静态规则没有把 PATHINFO 交给 index.php。
2.2 微信接入层不能写死在控制器里
「多微信」的落脚点是账号可增删,意味着 app_id、app_secret 这类敏感凭证不能出现在配置文件里写死,而应该作为数据行存在表中。控制器要拿 token 时,通过一个统一的 AccountService 来取。下面这段代码是多账号场景下的常见抽取方式:
<?php declare(strict_types=1); namespace app\wxmanage\service; use think\facade\Cache; class AccountService { /** * 获取指定账号的 access_token,带缓存和预过期 */ public function getAccessToken(int $accountId): string { $cacheKey = 'wx:access_token:' . $accountId; $token = Cache::get($cacheKey); if ($token) { return $token; } $account = WxAccount::find($accountId); // 调用微信 gettoken 接口,细节由 WechatClient 封装 $token = (new WechatClient($account))->getAccessToken(); // 微信返回的 token 有效期 7200 秒,提前 300 秒过期 Cache::set($cacheKey, $token, 6900); return $token; } }关键在于缓存时间的设定。微信官方接口有每日调用量限制,如果每个请求都直接回源拿 token,多账号并发场景很容易把额度耗尽。缓存 6900 秒意味着系统在 token 真正失效前 300 秒就会自动刷新,留出的窗口期刚好覆盖接口网络延迟。这个缓存的 key 必须带上 accountId,否则 A 账号刷新 token 会把 B 账号的缓存覆盖掉,这是多微信系统里最容易埋雷的一个点。
2.3 这套 CMS 不是给访客看的
标题里出现 cms,容易让人误以为系统附带一套官网内容发布前台。在多数 php 源码包里,这里的设计定位是「内容资产管理后台」,核心是素材库和图文编辑,而不是前台展示页。CMS 和 wxmanage 两个应用共用一套后台登录态,通过菜单表把「素材管理」「文章编辑」「群发任务」编排在同一个界面里。理解这一点后,再去看路由会和普通 CMS 很不一样:cms 应用下的控制器大多渲染后台模板,wxmanage 下既有后台页面又有接收微信服务器回调的接口路由。这两者职责边界划清楚,后续做权限、日志、数据表设计才不会拧巴。
3. 源码包落地:从解压到队列跑起来
3.1 先按环境清单核对,再谈安装
拿到 thinkphp6 源码 zip,直接丢到服务器上往往第一关就过不去。常见做法是先把运行环境按这张表核对一遍:
| 检查项 | 最低要求 | 推荐配置 |
|---|---|---|
| PHP 版本 | 7.4 | 8.1,关闭短标签 |
| PHP 扩展 | pdo_mysql、curl、fileinfo | 再加 redis、gd、zip |
| 数据库 | MySQL 5.7 | MySQL 8.0,utf8mb4 |
| 缓存 | 文件缓存 | Redis 6.x,用于队列 |
| Web 服务 | Nginx / Apache | Nginx,开 pathinfo |
fileinfo 扩展影响文件上传类型校验,缺少时常表现为图片素材传不上来;zip 扩展影响后台一键安装、插件解压;队列消费依赖 Redis,这是因为 think-queue 的驱动默认配置在 redis 上。这一层尽量在部署前解决,后面排错会省很多事。
3.2 解压、装依赖、写 .env 的顺序
把压缩包里的内容放到站点目录后,第一件事是看 vendor 目录在不在。源码若已带 vendor,直接进入配置环节;若没有,先执行:
cd /data/wwwroot/multi-wx composer install --no-dev --optimize-autoloader--no-dev 跳过开发依赖,避免把 phpunit 这类工具装进生产环境;--optimize-autoloader 会生成优化后的类映射表,减少每次请求的文件扫描开销。执行完后检查 vendor 目录生成情况,再配置 .env:
APP_DEBUG = false [DATABASE] TYPE = mysql HOSTNAME = 127.0.0.1 DATABASE = multi_wx USERNAME = root PASSWORD = "你的数据库密码" HOSTPORT = 3306 CHARSET = utf8mb4 PREFIX = wx_ [REDIS] HOST = 127.0.0.1 PORT = 6379 PASSWORD = SELECT = 0DATABASE 段里的 PREFIX 是全局表前缀,这份文件里定义的值会被 ThinkPHP6 的数据库连接解析到每个模型上。如果源码的 SQL 建表语句里已经带 wx_ 前缀,这里就不要随意改动,否则模型查询时自动拼接的表名和导入库里的真实表名对不上,会直接报 1146 Table doesn't exist。改任何一项配置后记得清理 runtime 缓存,否则 .env 的变更不会立即生效。
3.3 Nginx 伪静态:多应用 url 不 404 的关键
ThinkPHP6 的多应用 URL 依赖 PATHINFO。Nginx 默认配置下,/wxmanage/account/list 会被当成目录请求直接返回 404。部署时常用下面这段 server 配置:
server { listen 80; server_name wx.example.com; root /data/wwwroot/multi-wx/public; index index.php index.html; location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s=$1 last; } } location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }root 指向 public 目录是为了避免入口文件被直接下载,这是安全底线。rewrite 规则把不存在的路径统一转成 index.php?s= 参数,多应用模式下的 url 才能顺利被 TP6 的路由解析器接收。fastcgi_pass 的地址要和你机器上的 PHP-FPM 监听一致,9000 是常见默认值,具体以 php-fpm 配置为准。配完重启 nginx,再搭配php think run做本地验证,能少走不少弯路。
3.4 队列和定时任务必须撑起来
群发任务、素材异步同步都是走消息队列的,队列不启动,后台任务会一直停留在待处理状态。ThinkPHP6 生态里多用 think-queue,启动命令是:
php think queue:work --queue wx_send --daemon--queue 参数指定消费哪个队列名,与代码里 Queue::push 时传入的队列名保持一致;--daemon 让进程常驻内存,避免每处理完一个任务就退出。生产环境更常见的做法是用 supervisor 托管这条命令:
[program:wx_send] command=php think queue:work --queue wx_send --daemon directory=/data/wwwroot/multi-wx autostart=true autorestart=true redirect_stderr=true进程数量要根据群发频率调。多微信管理场景下,一个账号的群发动作本身不频繁,1 到 2 个 worker 足够;素材上传较频繁时,适当增加 worker 数量,但要留意 Redis 连接数和微信接口限频之间的平衡。
4. 素材、群发任务与队列消费的完整链路
4.1 素材表字段怎么设计,按哪个维度落库
CMS 里编辑好的图文最终要发到多个公众号,但微信的 media_id 是按公众号维度生成的,同一个本地文件同步到不同账号后拿到的 media_id 完全不同。因此素材的落库策略通常是本地上传一份文件,数据库里记录文件路径,并缓存它在每个账号下对应的 media_id。表结构可以参考这份设计:
CREATE TABLE `wx_material` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `account_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '公众号账号ID', `type` varchar(10) NOT NULL DEFAULT 'image' COMMENT '图片/视频/语音/图文', `file_url` varchar(500) NOT NULL DEFAULT '' COMMENT '本地文件地址', `wechat_media_id` varchar(300) NOT NULL DEFAULT '' COMMENT '微信侧media_id', `material_hash` varchar(64) NOT NULL DEFAULT '' COMMENT '文件哈希,便于多账号复用', `create_time` int(11) unsigned NOT NULL DEFAULT 0, PRIMARY KEY (`id`), KEY `idx_account_type` (`account_id`, `type`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='微信素材本地表';material_hash 是容易被忽略的字段。运营上传同一个图片到多个公众号时,通过文件哈希可以先判断本地已有该文件,省去重复上传和存储成本;发送前再按目标账号调用新增永久素材接口,把本地地址换成对应账号的 media_id。把这两步拆开,素材库的数据就永远不会冗余。
4.2 群发任务的队列消费怎么写
群发不具备事务性,接口调用动辄几秒,所以业务上一定先落任务表,状态机控制进度,再把任务推进到队列。关键代码段如下:
<?php declare(strict_types=1); namespace app\wxmanage\job; use app\common\model\WxSendTask; use app\wxmanage\service\MaterialService; use think\queue\Job; class WxSendJob { public function fire(Job $job, array $data): void { $taskId = $data['task_id']; $task = WxSendTask::find($taskId); if (!$task) { $job->delete(); return; } $result = (new MaterialService())->syncAndSend($task->account_id, $task->material_id); if ($result['errcode'] === 0) { $task->send_status = 2; // 发送成功 } else { $task->send_status = 3; // 发送失败 $task->fail_reason = $result['errmsg'] ?? ''; } $task->send_time = time(); $task->save(); $job->delete(); } }syncAndSend 内部先调用素材同步逻辑,把文件地址换成账号专属的 media_id,再提交群发接口。这里有个顺序问题必须守住:先同步素材再发起群发,直接拿 A 账号的 media_id 去 B 账号群发,微信会返回 40007 invalid media_id。同时任务执行结果要写回任务表,失败原因留档,前台才能按状态筛选出失败任务做重试。
4.3 频控和定时发布里的三个实际参数
第一,微信服务号群发接口每天只能群发一次,所以任务表要有 last_send_date 这类按自然日去重的字段,提交前先查当天是否已发过,避免队列重试导致重复群发。第二,think-queue 的延迟任务可以用 Queue::later($delay, ...) 实现定时发布,延迟秒数由发布时间减去当前时间计算,注意 cron 定时的最小粒度一般是分钟级。第三,群发接口报错 45009 表示接口调用超过限额,这时不要再无脑重试队列,而是把任务标记为失败或延后到次日,由定时任务再次扫描入队。把这三个参数写进设计里,群发模块才算能交给运营用。
5. 多账号数据隔离和回调路由的正确姿势
5.1 三种隔离方案,多数源码包只实现一种
多微信管理系统在攒代码时,账号之间的数据隔离方案直接影响开发量:
| 方案 | 隔离强度 | 实现成本 | 典型适用场景 |
|---|---|---|---|
| 独立数据库 | 最强 | 高 | SaaS 多租户 |
| 独立表前缀 | 中 | 中 | 微服务拆分 |
| 共享表 + account_id | 够用 | 低 | 单项目多账号后台 |
这类 cms.zip 最常见的默认实现是共享表加 account_id。查询素材、任务、粉丝数据时,模型层统一拼接 account_id 条件,控制器从后台登录态里取出当前正在操作的账号。代价是会多写几行查询条件,好处是统计、迁移、备份都简单。有些系统会把 token 校验、菜单鉴权都放在中间件里,顺手就把 account_id 注入到请求对象上,控制器无需再关心来源。
5.2 access_token 缓存并发刷新要加锁
多账号缓存 key 带上 account_id 只是第一步。当两个队列任务同时处理同一个账号的群发,且缓存刚好过期时,requestToken 会被并发调用两次,微信侧会返回 45009 或挤掉旧 token。解决的常见做法是加一个短时锁:
<?php $lockKey = 'wx:token_lock:' . $accountId; if (!Cache::set($lockKey, 1, 10)) { // 已有进程在刷新,等待后读缓存 sleep(2); return Cache::get('wx:access_token:' . $accountId); } try { $token = (new WechatClient($account))->getAccessToken(); Cache::set('wx:access_token:' . $accountId, $token, 6900); return $token; } finally { Cache::delete($lockKey); }Cache::set 在 Redis 驱动下如果 key 已存在会返回 false,这就天然充当了互斥锁。10 秒的锁过期时间覆盖单次 token 请求的耗时上限,避免死等;拿锁失败的进程等 2 秒后读缓存,那时正常流程已经把新 token 写进去了。这套模式在无 Redis 时会退化成文件锁,效果差一些,所以部署清单里把 Redis 列为了推荐项。
5.3 回调入口、CMS 文章与账号的关系
微信服务器回调需要公网可达的 URL,多账号时代要把账号维度带在 URL 参数里,设计成 /wxmanage/callback/handler?appid=wx123456,后端根据 appid 查出对应账号再做签名校验。注意这个入口不要挂在 cms 应用下,回调的安全策略和后台不同,拆到 wxmanage 下可以单独控制日志和 IP 白名单。与此对应,CMS 端的文章表只存内容本身,发布到某个账号时生成一条发布记录,记录里带着 account_id、素材 ID、发布时间、发送状态。这样一篇文章可以被多个账号引用,每个账号的发布状态又互不干扰,数据模型里不要出现把「文章所属账号」焊死成单个字段的设计,否则扩充第二个公众号就得改表结构。
6. 上线前值得做对的三个验证动作
6.1 环境自检命令
源码包一般会带一些自定义 think 命令,如果没有,最省事的做法是写一个 console 命令,把数据库连接、Redis、目录权限一次查清楚:
php think check:env这个命令内部本质是尝试 PDO 连库、Redis ping、检查 runtime 目录可写。能输出三行明确结果,比手动开浏览器点后台更快暴露基础环境问题。
6.2 两类高频报错快速定位
日志是部署期最直接的线索,runtime/log 下按日期生成的日志里,优先搜两个错误号:1146 表示表不存在,多半是表前缀和 .env 里 PREFIX 不一致;404 则要去查 nginx rewrite 和 route/app.php 里有没有路由规则。这两个问题定位路径固定,值得先记下来,比逐行猜快得多。
6.3 access_token 预刷新任务
最后值得补的一个后台任务是 token 预刷新:每天凌晨跑一个 console 命令,遍历所有账号,提前重新拉取 access_token 并写回缓存。这样白天高峰期的群发任务极少碰到 token 过期的情况,也让 getAccessToken 里的缓存命中率逼近 100%。入口代码用 schedule 或 crontab 都行,固定每小时一次即可。
本文还有配套的精品资源,点击获取