ThinkPHP6多微信管理系统源码架构与部署实战

发布时间:2026/9/17 0:06:27
ThinkPHP6多微信管理系统源码架构与部署实战 简介一套基于ThinkPHP 6、X-admin 2.2、layui 2.5.x构建的多微信管理系统后台源码面向需要同时管理多个公众号并将微信支付对应到企业商户的开发团队。核心亮点是不依赖微信开放平台即可完成多账号接入内置权限认证、附件管理、一键CURD等通用模块后端分层清晰、前端组件化便于功能扩展与二次开发。压缩包共含2001个文件以1223个PHP业务代码为主另有JS、HTML、CSS等前端资源及SQL脚本、JSON配置、Markdown文档整体约9.52MB目录规划明确。已有263人学习下载适合PHP中高级开发者作为企业级微信管理后台的快速起步骨架也可从中学习多公众号配置、支付路由、RBAC权限等实践实现。1. 多微信管理系统源码为什么绑在 thinkphp6 CMS 上多微信管理系统源码在国内源码交易平台和 GitHub 上常年是热门分类标题里的“多微信”和“CMS”容易让人误以为是群控脚本或内容发布系统实际落到 thinkphp6 上它的本质是一套多租户后台一个管理员维护多个公众号、企业微信或小程序账号分别配置 AppID、Secret、回调 Token再在同一个面板里完成粉丝、素材、自动回复和定时群发管理。用 thinkphp6 而不是 Laravel 或 Spring Boot主要看在三点多应用模式天然切出 admin、api、wechat 三端且 THINKPHP 的路由和缓存组件对微信回调这种低并发但高实时性的场景足够轻国内云主机默认 PHP 版本对 TP6 兼容性稳定面试和外包交付语境里 TP 源码的改造成本最低。这篇文章按“骨架、数据模型、接口封装、上线自检”四条线拆开讲读者可以照着抄一套能跑通的多微信后台。2. 用 thinkphp6 多应用模式搭出 CMS 三端骨架2.1 多应用模式和模块的差别以及 config/app.php 里的开关thinkphp6 之前TP 的“模块”指的是 application 目录下的一个子目录控制器、模型、视图混在同一个应用命名空间里模块之间防不住互相调用路由也不干净。thinkphp6 引入多应用模式后app目录下每个一级目录就是一个独立应用拥有自己的 controller、model、middleware、route命名空间也独立。对多微信管理系统这种需要分离“后台管理端、给小程序/网页调用的 API 端、接收微信回调的 wechat 端”的场景多应用是唯一让代码不腐化的选择。开启多应用模式要改config/app.php核心是下面三个参数。很多源码拿到手后前端页面 404、接口访问路径异常都是这个文件没配置对。// config/app.php auto_multi_app true, // 开启后自动根据域名或入口路径识别应用名 default_app index, // 默认应用访问 / 时进入 index 应用 app_express false, // 是否使用应用映射false 时用 app 下的目录名auto_multi_app设为true后URL 的第一段会被当作应用名来解析例如访问https://你的域名/admin/login/index时TP 会先到app/admin目录找Login控制器。default_app配合false的app_express使用保证裸域名访问不会报“找不到应用”错误。如果部署到子目录比如https://域名/wx/admin/login还需要再检查pathinfo配置这个放到 2.3 里说。2.2 composer 创建 TP6 项目并生成 admin、api、wechat 应用拿到任何一个多微信管理系统源码都应该先确认它的应用目录是不是“多应用模式”布局。自己从零搭的话标准套路是先创建项目再加多应用扩展包最后手动建立三个应用目录。# 创建 TP6 项目项目名取名 tp6-multi-wx 便于识别 composer create-project topthink/think tp6-multi-wx cd tp6-multi-wx # 安装多应用模式扩展 composer require topthink/think-multi-app # 手动创建三端应用目录 mkdir -p app/admin/controller mkdir -p app/admin/middleware mkdir -p app/api/controller mkdir -p app/wechat/controller创建完的目录结构里app/admin是运营后台app/api对外提供 JSON 接口app/wechat单独放回调接收逻辑。wechat应用不设后台界面只暴露一个回调 controller这样即使后台被攻击攻击者也碰不到回调验签代码。项目根目录下的config、route、vendor保持 TP6 默认不需要手改。composer require后如果访问首页报类不存在先执行composer dump-autoload刷新自动加载这是换环境后最常见的低级错误。2.3 域名入口、Nginx rewrite 与多应用 URL 解析顺序thinkphp6 多应用模式下的 URL 规则是多微信管理系统部署时最绕的一关热搜词里“thinkphp6多应用模式下的url”高频出现说明大量开发者在这里卡住。TP6 默认入口文件是public/index.php开发环境用php think run启动时路径一切正常换到 Nginx 后变成了?s/admin/login/index这种难看的格式甚至直接 404。原因是 Nginx 没有把请求交给 index.php 做 pathinfo 解析。生产环境推荐用下面这份 Nginx 配置把/下的请求全部 fallback 到index.phpserver { listen 80; server_name wx.example.com; root /data/www/tp6-multi-wx/public; index index.php; location / { # 如果请求的不是真实文件或目录统一交给 index.php 解析 if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s$1 last; } } location ~ \.php$ { include fastcgi_params; fastcgi_pass 127.0.0.1:9000; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } # 禁止访问应用源码目录 location ~ ^/(app|config|runtime|vendor)/ { deny all; } }这份配置里有三个注意点rewrite ^(.*)$ /index.php?s$1是 TP6 兼容写法它把admin/login/index作为参数s传给框架框架再按照“应用/控制器/操作”的顺序解析location ~ \.php$必须放在 rewrite 之后否则 PHP 文件会被 rewrite 规则拦截deny all段不能漏否则别人直接在浏览器访问域名/app/admin/controller/Login.php就能下载源码文件。配置完成后重启 Nginx访问https://wx.example.com/admin/login/index应该能正常走到 admin 应用的 Login 控制器。2.4 登录接口与 AuthCheck 中间件的最小实现CMS 后台的登录鉴权不推荐在控制器里反复写if (!session(...))TP6 的中间件机制可以把这套逻辑抽成一层。先看登录控制器它只做一件事校验账号密码写入 session。?php declare(strict_types1); namespace app\admin\controller; use think\facade\Db; use think\Response; class Login { public function index(): Response { $username request()-param(username, ); $password request()-param(password, ); if ($username || $password ) { return json([code 1, msg 参数缺失]); } $admin Db::name(admin) -where(username, $username) -find(); // 统一使用 password_hash/password_verify禁止 md5 加盐 if (!$admin || !password_verify($password, $admin[password])) { return json([code 1, msg 账号或密码错误]); } session(admin_id, $admin[id]); return json([code 0, msg 登录成功]); } }password_verify是 PHP 内置函数配合password_hash生成散列比md5(md5($password).$salt)这种老写法安全得多。接着在app/admin/middleware/AuthCheck.php写一个中间件所有需要登录的请求都经过它?php declare(strict_types1); namespace app\admin\middleware; use Closure; use think\Request; use think\Response; class AuthCheck { public function handle(Request $request, Closure $next): Response { if (!session(admin_id)) { return redirect((string) url(/admin/login/index)); } return $next($request); } }中间件注册到app/admin/middleware.php文件但登录接口本身要跳过。做法是在app/admin/route/app.php里用路由分组挂中间件登录路由放组外?php use think\facade\Route; Route::post(login, Login/index); Route::post(logout, Login/logout); Route::group(function () { Route::get(dashboard, Dashboard/index); Route::get(fan/list, Fan/list); // 其他业务路由 })-middleware([\app\admin\middleware\AuthCheck::class]);这样admin/login不经过鉴权而dashboard、fan/list等所有业务接口都会先执行AuthCheck。要注意的是多应用模式下路由文件位置是app/admin/route/app.php不是根目录的route/app.php后者只对默认应用生效很多人把路由写错地方导致中间件不生效。3. 多微信账号数据模型、绑定回调与切库设计3.1 账号维度为主键一张表管公众号、企业微信和小程序多微信管理系统的核心不是“多个粉丝”而是“多个账号”。唯一正确的主数据模型是以微信账号为第一维度粉丝、素材、标签、消息都挂在账号 ID 下。下面是账号基础表的标准建表语句兼容公众号、企业微信和小程序三种类型。CREATE TABLE wc_account ( id int unsigned NOT NULL AUTO_INCREMENT COMMENT 账号ID, admin_id int unsigned NOT NULL DEFAULT 0 COMMENT 归属管理员ID, title varchar(64) NOT NULL DEFAULT COMMENT 显示名称, type tinyint NOT NULL DEFAULT 1 COMMENT 账号类型:1公众号 2企业微信 3小程序, appid varchar(64) NOT NULL DEFAULT COMMENT AppID, secret varchar(128) NOT NULL DEFAULT COMMENT AppSecret, token varchar(64) NOT NULL DEFAULT COMMENT 回调验证Token, aes_key varchar(64) NOT NULL DEFAULT COMMENT EncodingAESKey, status tinyint NOT NULL DEFAULT 1 COMMENT 1启用 0停用 -1删除, created_at int unsigned NOT NULL DEFAULT 0, updated_at int unsigned NOT NULL DEFAULT 0, PRIMARY KEY (id), KEY idx_admin_id (admin_id), KEY idx_appid (appid), UNIQUE KEY uk_appid_type (appid, type) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT微信账号主体表;这张表有几个字段需要特别说明admin_id决定这个账号归哪个后台管理员管做多租户隔离时所有查询都强制拼接这个条件uk_appid_type唯一键防止同一个 AppID 被重复绑定到不同账号aes_key单独存而不是写死在代码配置里因为每个公众号在微信公众平台设置的 EncodingAESKey 都不一样必须支持后台可视化修改。如果还想记录企业微信的通讯录权限或小程序的页面路径可以再加一个extJSON 字段存扩展信息但不要在一张表里堆几十个业务字段。3.2 Account 模型、缓存查询和账号切换的边界TP6 里模型类建议统一放在app/common/model下三个应用共享。Account 模型除了基本的表映射还需要封装两个高频能力按 ID 查可用账号、按 AppID 查账号类型。?php declare(strict_types1); namespace app\common\model; use think\Model; class Account extends Model { protected $name wc_account; protected $type [status integer]; // 查单账号带状态过滤 60 秒缓存 public static function activeById(int $id): ?Account { return self::where(id, $id) -where(status, 1) -cache(60) -find(); } // 根据管理员ID关联查询账号列表用于后台下拉框 public static function listByAdmin(int $adminId): array { return self::where(admin_id, $adminId) -where(status, , -1) -field(id,title,type,appid) -select() -toArray(); } }使用缓存时需要注意的边界后台修改了secret或token后60 秒内旧配置仍会被读到。所以在修改账号的控制器里更新数据库后要手动清理缓存$account Account::find($id); $account-secret $newSecret; $account-save(); // 清理模型缓存避免旧secret继续生效 Account::where(id, $id)-cache(60)-delete();缓存清除没有clearCache这种魔法方法正确姿势是用Cache::delete(缓存键名)但如果键名是框架自动生成的不确定时可以直接cache()-clear()全量清理。多账号切换还有一个隐藏坑回调进来时只能拿到appid不能直接信任客户端传的account_id正确做法是用appid反查Account表拿到账号 ID再决定后续消息路由。3.3 回调 URL 编排、Token 验签与 EncodingAESKey 入库微信服务器推送消息时需要你在公众号后台配置一个回调 URL。多微信管理系统的回调 URL 要设计成带账号维度否则一台服务器收到几十个公众号的消息后根本分不清是谁的。常见做法是固定接口路径在 query string 里带appid参数https://wx.example.com/wechat/callback/index?appidwx1234567890这样做的好处是不需要在配置后台为每个公众号维护独立 URL缺点是appid是半公开信息所以回调处理器一定要验签。微信服务器首次绑定会发 GET 请求携带signature、timestamp、nonce、echostr四个参数处理器接收后按微信官方算法做 sha1 签名比较相等才原样返回echostr。?php declare(strict_types1); namespace app\wechat\controller; use app\common\model\Account; use think\Response; class Callback { public function index(): Response { $appid request()-param(appid, ); $account Account::where(appid, $appid)-find(); if (!$account || $account-status ! 1) { return Response::create(invalid appid, html, 403); } // 从微信请求里读取验证参数 $signature request()-param(signature, ); $timestamp request()-param(timestamp, ); $nonce request()-param(nonce, ); $echostr request()-param(echostr, ); // 官方签名算法按字典序拼接后 sha1 $tmp [$account-token, $timestamp, $nonce]; sort($tmp, SORT_STRING); $tmpStr sha1(implode(, $tmp)); if ($tmpStr $signature) { // 绑定成功原样返回 echostr return Response::create($echostr, html, 200); } return Response::create(signature error, html, 403); } }排序用SORT_STRING很重要PHP 默认的sort()是字典序数字特殊处理当 Token 是纯数字字符串时两种排序结果一致一旦 Token 混合字母数字就可能出错。验签通过后如果需要接收加密消息还要用EncodingAESKey对消息体做 AES 解密这套逻辑建议直接用成熟 SDK手工实现容易在 PKCS7 填充上踩坑。绑定完成这个接口就可以放到微信公众平台后台的服务器配置里了。4. 微信接口调用封装、access_token 缓存与队列参数4.1 引 SDK 还是自封 HTTP 客户端按 PHP 版本取舍多微信管理系统源码质量参差最突出的差异就在微信 API 调用方式上。老源码里常见file_get_contents直连微信接口新一些的用 Guzzle 或 cURL还有的直接套 EasyWeChat 全家桶。三者在生产表现差别很大选型时先看服务器 PHP 版本PHP 8.0 及以上直接composer require w7corp/easywechatSDK 把 access_token、消息加解密、菜单、素材的封装都做全了PHP 7.4 且不方便升版本时建议用官方 SDK 5.x 或自己封装 cURL因为 EasyWeChat 6.x 强制依赖 PHP 8.0强装会出现类方法签名不兼容的致命错误。下表是三种做法的对比选型时要结合团队维护能力而不是只看写起来快不快。方案适用条件优点主要风险EasyWeChat 6.xPHP 8.0覆盖全、社区资料多包体大、升级不向后兼容自封装 cURL任意版本链路透明、可控性强需要自己处理 token 刷新官方示例代码改造任意版本代码量最少缺少异常处理生产不可用从源码阅读和维护角度我更推荐自封装 cURL 加统一 Service 类。多微信系统本质是调不同appid的接口SDK 实例化成本高频繁 new Application 反而容易踩缓存失效的坑自封装可以把 token 缓存逻辑牢牢攥在自己手里。4.2 access_token 提前 200 秒续期并加锁防止缓存击穿微信接口调用几乎都要带access_token公众号的 token 有效期 7200 秒且每天有获取次数限制。多账号系统里最忌讳的做法是每个请求都重新获取 token正确姿势是全局缓存并设置一个缓冲时间避免 token 在边界时间失效。下面是带 Redis 锁的自封装获取方法?php declare(strict_types1); namespace app\common\library; use think\facade\Cache; use think\facade\Log; use app\common\model\Account; class WechatApi { public static function getAccessToken(int $accountId): string { $cacheKey wx:access_token:{$accountId}; // 读缓存多数请求直接命中 $token Cache::get($cacheKey); if (!empty($token)) { return $token; } // 加锁防止高并发下缓存击穿导致 token 被重复刷新 $lockKey wx:token_lock:{$accountId}; if (!Cache::set($lockKey, 1, 10)) { // 拿不到锁说明另一个进程正在刷新等 200ms 后再读 usleep(200000); return Cache::get($cacheKey); } try { $account Account::find($accountId); $url sprintf( https://api.weixin.qq.com/cgi-bin/token?grant_typeclient_credentialappid%ssecret%s, urlencode($account-appid), urlencode($account-secret) ); $ch curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER true, CURLOPT_TIMEOUT 5, CURLOPT_SSL_VERIFYPEER false, ]); $body curl_exec($ch); curl_close($ch); $ret json_decode((string) $body, true); if (!isset($ret[access_token])) { Log::error(wechat token error, [account_id $accountId, res $ret]); throw new \RuntimeException(获取 access_token 失败); } // 有效期 7200 秒提到 200 秒过期保证业务请求不会用到即将失效的 token Cache::set($cacheKey, $ret[access_token], (int) $ret[expires_in] - 200); return $ret[access_token]; } finally { Cache::delete($lockKey); } } }这段代码的关键点有三个cURL超时设置 5 秒避免微信接口挂起时整个 PHP-FPM 进程被占住expires_in - 200的余量设计是大量排查 token 过期问题的经验值余量太小会偶发 40001 错误锁的存活时间 10 秒远大于一次接口请求耗时不会出现锁提前释放的问题。如果系统 Redis 不可用可以把Cache::set($lockKey, 1, 10)换成文件缓存驱动但多服务器部署时文件缓存锁不跨机必须上 Redis。4.3 素材同步和群发任务的 think-queue 参数公众号素材同步接口、批量打标签、定时群发都属于耗时任务用户点一次按钮最多要等 5 到 15 秒放控制器里同步执行必然超时。TP6 自带 think-queue 扩展多微信系统标准做法是把这些任务丢进队列前端先返回“处理中”任务结束后写日志或回调通知。安装和基础调用如下# 安装队列扩展 composer require topthink/think-queue # 配置 redis 驱动文件驱动只适合开发环境 # config/queue.php 里 default redis投递任务代码use think\facade\Queue; public function syncMaterial(int $accountId): void { Queue::push(\app\job\MaterialSyncJob::class, [ account_id $accountId, type image, ], wx_task); }任务处理类app/job/MaterialSyncJob.php需要严格实现fire方法并处理失败重试?php declare(strict_types1); namespace app\job; use think\queue\Job; class MaterialSyncJob { public function fire(Job $job, array $data): void { try { $accountId (int) ($data[account_id] ?? 0); // 调用微信素材列表接口遍历写入本地表 $job-delete(); // 成功后删除任务 } catch (\Throwable $e) { // 已经重试3次则丢弃否则10秒后重试 if ($job-attempts() 3) { $job-delete(); } else { $job-release(10); } } } }队列消费进程的参数是这个环节最容易被忽略的。queue:work命令默认只处理一次任务就退出必须指定--once或--daemon并配合队列名。生产环境建议在 crontab 里每分钟跑一次单任务模式而不是用--daemon常驻内存因为 daemon 模式代码热更新不生效且需要额外的进程守护工具* * * * * cd /data/www/tp6-multi-wx /usr/bin/php think queue:work --once --queuewx_task --tries3 --sleep2 /data/logs/wx_queue.log 21这个 crontab 的--sleep2参数是消费完一条任务后等待 2 秒再取下一条防止空轮询把 Redis 连接数打满。日志重定向到独立文件排查时直接看wx_queue.log就能定位任务失败原因。4.4 消息回调幂等第一个唯一键就要建对微信对回调消息的推送不保证只推一次断网重试、超时重发都会让同一条消息到达服务器多次。多微信系统最容易出的数据错乱就是粉丝重复入库、回复消息重复发送。解决思路是建一张消息去重表利用数据库唯一键做自然幂等。CREATE TABLE wc_msg_log ( id bigint unsigned NOT NULL AUTO_INCREMENT, account_id int unsigned NOT NULL DEFAULT 0, msg_id bigint unsigned NOT NULL DEFAULT 0 COMMENT 微信消息ID, openid varchar(64) NOT NULL DEFAULT , event_key varchar(64) NOT NULL DEFAULT COMMENT 事件key, created_at int unsigned NOT NULL DEFAULT 0, PRIMARY KEY (id), UNIQUE KEY uk_account_msg (account_id, msg_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;处理回调时先尝试插入去重表插入报“重复键”则说明消息已处理过直接跳过。TP6 里用Db::name(wc_msg_log)-insertOrIgnore()一行代码完成$inserted Db::name(wc_msg_log)-insertOrIgnore([ account_id $accountId, msg_id $msgId, openid $openid, event_key $eventKey, created_at time(), ]); if ($inserted 0) { // 已处理过无需再处理 return Response::create(success); }insertOrIgnore在 MySQL 里生成INSERT IGNORE语句受影响行数为 0 表示主键或唯一键已存在。这里不要用ON DUPLICATE KEY UPDATE原因是更新操作会产生额外 IO而消息去重只需要“首次成功之后忽略”。注意msg_id对事件类推送可能为空此时可以把msg_id改成(account_id, openid, update_time 时间戳)做复合唯一键时间戳取自微信推送消息里的CreateTime字段同样能起到幂等效果。5. 上线前把 token、回调、队列三个链路单独验证一遍5.1 自定义 think 命令快速验证某账号 access_token 是否可用TP6 支持自定义控制台命令把它当成运维探测工具比写 PHP 脚本再访问更顺手。在app/common/command/CheckToken.php里定义一个命令调用第四章的WechatApi::getAccessToken()输出 token 的前 10 位和到期剩余时间。?php declare(strict_types1); namespace app\common\command; use think\console\Command; use think\console\Input; use think\console\Output; use app\common\library\WechatApi; class CheckToken extends Command { protected function configure(): void { $this-setName(wx:check-token) -setDescription(检查指定微信账号的access_token可用性) -addArgument(account_id); } protected function execute(Input $input, Output $output): int { $accountId (int) $input-getArgument(account_id); $token WechatApi::getAccessToken($accountId); $output-writeln(token前缀: . substr($token, 0, 10) . 获取成功); return 0; } }命令注册到config/console.php的commands数组里然后命令行执行php think wx:check-token 1如果输出 token 前缀说明接口凭据和网络链路正常如果报错则先查runtime/log下的日志错误码40013表示 AppID 错误40125表示 Secret 错误42001表示 token 过期是正常的说明缓存没生效。5.2 用 curl 模拟微信服务器验签 GET 请求回调绑定阶段最容易出现的现象是公众平台提示“Token 验证失败”。可以先用本地 curl 模拟一次微信的 GET 验证请求把验签逻辑的输入输出固定下来。先手动计算 signature# 假设账号表里 tokentesttoken123timestamp1700000000nonceabc123 # PHP 一行命令按微信规则生成签名 SIGN$(php -r $tmp[testtoken123,1700000000,abc123]; sort($tmp, SORT_STRING); echo sha1(implode(, $tmp));) curl https://wx.example.com/wechat/callback/index?appidwx1234567890signature${SIGN}timestamp1700000000nonceabc123echostrhello_wxcurl 返回hello_wx则 URL 和验签逻辑正确。如果返回signature error优先检查接口收到的$account-token值和公众号后台配置的是否完全一致注意不要复制多出空格或换行。关键点在于 curl 里三个参数和 PHP 数组里的三个值必须逐字节一致连顺序都会被sort重新排列所以手工测试时最容易错的是旧的timestamp和nonce已经超过微信服务器允许的 5 分钟时间窗口而非算法本身有误。5.3 队列消费者和定时任务进程级检查队列调通了不代表生产环境没问题交付源码前最后一项检查是确认队列消费者确实在跑。手动执行一次queue:work --once看任务是否消费再用系统命令确认 crontab 已经加载# 检查crontab是否已安装 crontab -l | grep wx_task # 手动消费一条队列任务观察wx_queue.log输出 php think queue:work --once --queuewx_task --tries3 # 查看最近10条队列日志确认无 fatal error tail -n 10 /data/logs/wx_queue.log # 如果使用Redis驱动查看list长度大于0说明还有积压任务 redis-cli llen queues:wx_taskqueues:wx_task是 think-queue 默认生成的 Redis key 前缀如果测试账号下素材不多llen 应该在任务执行后回到 0。积压任务数量持续增长时不要急着调大并发先看wx_queue.log里是否有SQLSTATE或RuntimeException多数是因为某个账号 Secret 变更后没有清理 token 缓存导致任务进程一直在重试拿不到 token。最后顺手检查 Nginx 错误日志中是否有worker_connections不足的警告优化建议是加一层/wechat/callback路径的访问限流让非微信 IP 段直接返回 403把无效回调节流在 PHP 之前。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询