
简介这是一份基于 PHPCMS V9.6.6 深度优化的精简增强版源码包重点解决老版本在 PHP8、MySQL8 及 HTTPS 环境下的兼容问题并移除 PHPSSO认证、Flash依赖、视频库与在线升级等过时功能适合需要快速搭建安全、现代后台的企业站或二次开发人员。资源共180个文件以138个php核心逻辑、35个html模板页面及htaccess配置为主压缩包仅436KB结构精简为/CMS目录便于部署维护。后台与前台界面全面重写登录、锁屏、内容管理均采用响应式设计所有上传场景统一升级为HTML5并集成UEditor 4.16.1支持水印、微信图文导入、Word粘贴、远程图片自动下载压缩等实用能力。新增二维码生成、IP地理定位、面包屑导航、JSON统一返回、安全路径处理等工具函数字段系统拓展至站点、栏目、单页自定义字段及组图、关联字段等模式关键词提取已对接讯飞、百度及本地API。目前已有53人学习下载适合希望摆脱旧版技术栈、快速获得高兼容性CMS内核的开发者直接使用。 做了这么多年 PHP 老项目维护我一直觉得“老”和“烂”是两回事。就拿 PHPCMS V9.6.6 来说这套系统在当年确实风光过但放到现在直接装到 PHP 8 环境里基本就是一堆白屏和 Fatal error。可问题是很多企业站、地方门户、行业信息站至今还在用这套老系统跑数据量大、历史页面多、客户又不愿意掏重构的钱那就只能走“精简增强”这条路。我自己手里就接过好几个这类的单子需求高度一致PHP8 下能跑、移动端能传图、编辑器别太古董、把用不上的 SSO 和视频模块拆干净。这篇文章就是想把这套改造思路完整复盘一遍给同样被困在 PHPCMS V9.6.6 维护坑里的朋友一个可落地、不劝退的参考方案。1. 为什么还有人啃 V9.6.6 这块老骨头先说难听的话但凡能重构没人愿意在新项目里选 PHPCMS V9.6.6。但现实情况是市面上有大量存量站点后台几百篇乃至上万篇内容模板是当年美工一笔一笔调出来的搜索收录权重也都在客户预算可能只够“续命”不够“投胎”。这种时候我们做的不是重写而是在保留原有业务和数据的前提下把系统的运行底座换成现代环境。我经手的一个客户站点是本地行业协会的信息门户文章、政策、行业动态加起来接近 8 万条后台管理员还有十几个。原来的服务器还是 PHP 5.2 时代的环境连 PHP 7 都升不上去安全补丁根本没法打后台三天两头被扫描。客户的要求很明确“能用就行别丢数据别让我重新教编辑怎么发文章。” 这就是典型的不该重构、只该改造的场景。那为什么选 V9.6.6 而不是其他版本因为 V9 系列是 PHPCMS 生命周期里撑得最久、模板资源最多的一代.NET 版和早期版本都没法比。而 9.6.6 作为最后的补丁版修复了之前不少已知问题虽然网上也能下到各种精简版但自己动手改一套最稳妥至少你知道每一行改了什么。这套改造最终要达成四个目标一是能在 PHP 8.1 下稳定跑不报死错误二是后台上传图片和附件不再依赖 Flash手机浏览器也能传三是默认的编辑器换成 UEditor至少编辑体验跟得上时代四是把单点登录SSO模块和视频模块彻底摘掉减少依赖、缩小攻击面。2. PHP8 适配老代码与新时代的兼容方案2.1 V9.6.6 在 PHP8 下跑不起来的真实原因很多人以为老系统升级 PHP 版本无非就是改几个报错。真动起手来你会发现V9.6.6 在 PHP 8 下的问题不是一个一个蹦的而是成片成片地爆。它当初是给 PHP 5.2 / 5.3 写的代码PHP 8 等于把地基都给换了。我自己踩下来最典型的四类问题mysql_函数族被移除*。V9.6.6 的底层数据库类大量使用mysql_connect、mysql_fetch_array这些函数在 PHP 7.0 就没了到 PHP 8 更是直接白屏罪魁祸首。each() 函数被移除。老代码里到处是while (list($key, $value) each($array))PHP 8 里each已经不存在直接报 Fatal error。动态属性弃用。PHP 8.2 开始给没有显式声明属性的对象动态赋值会抛 Deprecated到 8.x 后期甚至成了致命问题。PHPCMS 里不少模型类图省事$this-xxx 直接动态挂属性。preg_replace() 的 /e 修饰符被移除。模板解析、搜索关键字高亮这些地方如果还在用 /ePHP 7 废掉后到 PHP 8 就是直接错误。如果只是把 php.ini 里的display_errors关掉确实能挡住一部分报错画面但 Fatal error 关不住而且代码里逻辑坏了页面上展示的也是残缺内容。所以正确路线是先把代码修到能在 PHP8 下跑通再考虑关错误显示。2.2 我的迁移步骤先建独立环境再动手我改造时没有直接在现网服务器上动刀而是用 Docker 建了一套 PHP 8.1 MySQL 5.7 的独立环境从原服务器导了一份数据库和完整文件下来在本地反复试。这个环节千万别省原因很直接老系统没有一个固定的“标准配置”每个站点的模板、插件、自定义标签都不一样只有实际跑起来才知道哪个文件有问题。具体步骤大致是这样完整备份原站所有文件和数据备份文件不要只放同一台服务器我习惯同时下载到本地。用 PHP 8.1 容器跑原版代码打开error_reporting(E_ALL)把首页、列表页、内容页、后台登录页逐个打开收集所有报错。跟着报错顺序一类一类修。先修数据库连接层再修每类函数替换最后修模板层和扩展包。每修完一批就重新跑一遍前台页面的回归清单确保没有改坏。全部修完并在本地跑满 48 小时后再把改造版本同步到测试服务器最后才部署到生产环境。这套流程听着麻烦但实际是最省时间的路径。如果在生产环境上边测边改碰到一个 Fatal error 页面就挂一次编辑部的同事会直接跑到你工位来。2.3 几类典型报错的处理示例数据库层V9.6.6 的/phpcms/libs/classes/db_factory.class.php和mysql.class.php里老代码全是mysql_*风格。最省事的改法不是逐行改函数名而是写一个兼容函数层把mysql_connect等映射到mysqli_版本。比如if (!function_exists(mysql_connect)) { function mysql_connect($host, $user, $password) { global $mysqli_link; $mysqli_link mysqli_connect($host, $user, $password); return $mysqli_link; } function mysql_select_db($database, $link null) { global $mysqli_link; $link $link ?: $mysqli_link; return mysqli_select_db($link, $database); } function mysql_query($sql, $link null) { global $mysqli_link; $link $link ?: $mysqli_link; return mysqli_query($link, $sql); } function mysql_fetch_array($result) { return mysqli_fetch_array($result); } function mysql_fetch_assoc($result) { return mysqli_fetch_assoc($result); } function mysql_num_rows($result) { return mysqli_num_rows($result); } function mysql_insert_id($link null) { global $mysqli_link; $link $link ?: $mysqli_link; return mysqli_insert_id($link); } function mysql_error($link null) { global $mysqli_link; $link $link ?: $mysqli_link; return mysqli_error($link); } function mysql_real_escape_string($string, $link null) { global $mysqli_link; $link $link ?: $mysqli_link; return mysqli_real_escape_string($link, $string); } }这段兼容层可以放在入口文件里比如index.php或phpcms/base.php中通过require引入。需要注意老代码里mysql_query调用时有时候会不传连接参数所以在兼容函数里通过global $mysqli_link兜底能避免改业务代码的时候漏掉某个函数调用。each() 替换老代码里长得像这样的while (list($key, $value) each($list)) { // ... }改成 foreach 最直接foreach ($list as $key $value) { // ... }如果循环内还有each的指针用法比如配合current、next那就要小心一些按数组指针逻辑逐段改造不能粗暴替换。但绝大多数场景就是普通遍历foreach 无缝替换。动态属性问题最简单的是在类定义里补上属性声明。比如/phpcms/model/content_model.class.php这类模型类把常用字段先public $xxx;声明出来。另一个方案是给对应类加#[AllowDynamicProperties]属性但这是偷懒做法我一般只在第三方插件里用核心模型能补声明就补声明毕竟后面维护的人也是咱们自己人。preg_replace /e 问题模板引擎里处理标签替换时老代码会这么写$content preg_replace(/\{(\$[\w\d\[\]\\])\}/e, \$this-template_parse(\\1), $content);PHP 7 之后 /e 被废必须改成preg_replace_callback$content preg_replace_callback(/\{(\$[\w\d\[\]\\])\}/, function($matches) { return $this-template_parse($matches[1]); }, $content);这套适配做完V9.6.6 在 PHP 8.1 下能正常跑起来但注意不要盲目上 PHP 8.2 或 8.3。越新的版本对动态属性和隐式转换管得越严V9.6.6 这种老骨架没必要追新稳定优先。3. 去SSO、卸掉视频模块减法改造的完整路径3.1 为什么选择去掉SSOPHPCMS V9 的 SSO 单点登录模块本来是为了多站点同步登录设计的例如总站和子站共享用户登录态。但在绝大多数单站点业务里这玩意儿不但没用还会带来一堆额外开销用户登录时要请求同步接口接口地址失效或者域名变了登录逻辑就异常更烦的是它拉长了登录链路排查问题都得多看好几个文件。我改造的那个客户站点就是纯单站后台管理员只有十几个前台用户注册量也很少SSO 纯属负担。而且 SSO 相关的同步脚本是历史遗留漏洞的重灾区删掉之后安全面会显著缩小。3.2 去掉SSO的实操路径我先确认了核心入口PHPCMS V9.6.6 的会员相关逻辑主要在/phpcms/modules/member/目录其中 login 和 register 控制器在初始化时会载入 SSO 客户端类。实际操作时我把这些位置逐个处理在member/index.php构造方法里删除或注释掉对sync_client的初始化调用保留正常的session_start和用户数据读取逻辑。删除/phpcms/modules/member/classes/下的 SSO 客户端类引用。打开后台模板中会员中心的头部和登录页模板去掉加载同步脚本的script标签这些标签通常指向index.php?mmembercindexa...的同步请求。检查api/目录下是否有老版本的 SSO 接口文件确认没有其他模块调用后一并移除。清理数据库里和 SSO 相关的配置项比如v9_sso开头的表以及v9_setting里跟同步相关的配置记录。改完之后登录流程变成纯粹的本地校验用户提交用户名密码PHP 查member表校验密码写入 session。登录退出都不会再发外部请求简单直接出问题也好查。3.3 卸掉视频模块不只是删个目录视频模块在 V9.6.6 里是一个独立功能包路径一般是/phpcms/modules/video/。如果站点没用它留着不光是代码冗余后台菜单多一块、数据库多几张表而且老视频模块涉及上传播放逻辑很容易被扫描工具盯上。删除时我按以下顺序操作在后台“模块管理”里先卸载视频模块让它从系统模块列表中移除。删除/phpcms/modules/video/目录以及模板目录里对应的video模板文件夹。删除数据库里视频模块的数据表我通常先查SHOW TABLES LIKE %video%确认是视频模块专用表再 drop。清理后台菜单表menu里的视频模块菜单记录避免后台出现“无模块”的空白菜单项。检查内容模型里是否绑定了视频字段如果有要把相关字段从模型里解除。这一步做完了整个系统的路由、菜单、数据表都会清爽很多后台登录之后的加载速度也明显快。3.4 减法后的收益不夸张地说去掉 SSO 和视频模块之后站点后台登录从原来可能触发 3 到 4 个外部请求变成了 1 个本地请求前台请求链路也短了。安全上更明显少了两个历史代码包扫描器能打的点少了很多。几个改造单子里客户最直接的感受就是“后台不卡了、登录不转圈了”。4. H5上传与UEditor整合把上传入口搬到移动端时代4.1 原版上传组件为什么在移动端废了V9.6.6 自带的附件上传组件是基于 Flash 的PC 浏览器里还能忍但到了手机浏览器Flash 直接不工作点上传按钮毫无反应。现在很多编辑直接用手机登录后台发图文这个环节不解决整个后台在移动端就是半残废状态。同时UEditor 原装自带的上传也是 Flash 附件方式需要一并替换为 H5 方案。UEditor 本身是百度开源的老编辑器但它的 PHP 上传接口设计得还算独立和 PHPCMS 的附件表对接并不复杂。很多 PHPCMS 二开项目早就这么干了核心思路是前端用 UEditor 的 H5 上传能力后端把上传请求转到 PHPCMS 的附件处理逻辑。4.2 H5上传改造前端代码思路与后端兼容后端接口我保留了 PHPCMS 原有的upload_json.php逻辑接收$_FILES后调用附件处理函数生成缩略图、水印、返回附件 ID 和 URL这样改造后原有内容模型的图片字段、缩略图字段都不需要动。前端我用原生 XMLHttpRequest 2 写一个统一的上传入口支持多图选择和进度显示。核心代码思路如下function uploadFile(file, url, callback) { var formData new FormData(); formData.append(file, file); formData.append(module, content); formData.append(catid, catid); var xhr new XMLHttpRequest(); xhr.open(POST, url, true); xhr.onload function () { if (xhr.status 200) { var res JSON.parse(xhr.responseText); callback(res); } else { alert(上传失败服务器返回 xhr.status); } }; xhr.upload.onprogress function (e) { if (e.lengthComputable) { var percent Math.round(e.loaded / e.total * 100); // 更新进度条 } }; xhr.send(formData); }这里有个小坑PHPCMS 的附件上传接口对module、catid参数有依赖直接传空字符串可能导致附件表归类错误所以前端一定要把这些参数带上。另外老接口对上传文件大小默认限制在 2MB 左右如果编辑要传高清图、PDF就要同步修改php.ini的upload_max_filesize、post_max_size并修改 PHPCMS 后台“附件设置”里的允许大小和扩展名白名单。别只看百度编辑器配置PHP 侧的upload_max_filesize是最容易漏的。4.3 UEditor整合版本选择与目录配置UEditor 我选的是经典的 1.4.3 PHP 版虽然它也不再更新但胜在稳定、文档多、踩坑记录全。整合时主要按这几步下载ueditor/目录放到站点根目录比如/statics/ueditor/。在后台内容模型里把默认编辑器字段替换为 UEditor通常就是改内容模型的字段类型和后台模板里的加载脚本。配置ueditor/php/config.json把图片上传接口指向 PHPCMS 的上传入口或者直接使用 UEditor 自带的controller.php再在controller.php里引入 PHPCMS 初始化文件复用附件表和权限校验。在编辑器初始化时设置serverUrl指向正确的后端入口同时打开图片在线管理、涂鸦、截图上传这些能力这些正好都是 H5 友好的功能。我习惯直接改 UEditor 的controller.php在顶部引入 PHPCMS 的phpcms/base.php然后上传成功后把附件 ID 写入attachment表这样后台上传管理里能看到编辑器上传的文件不产生“上传了但管理后台查不到”的孤儿附件。4.4 改造后的上传链路与常见问题改造完之后的完整链路是编辑器或列表页上传按钮触发 H5 文件选择 → FormData 提交到上传接口 → PHPCMS 校验权限和格式 → 保存附件记录、生成缩略图和水印 → 返回 JSON 给前端 → 前端把 URL 填入编辑器或表单。实际运行中我遇过最多的三类问题都在这里提前说上传成功但编辑器不显示图片。通常是返回 JSON 格式不对UEditor 要求固定格式如{state:SUCCESS,url:...,title:...,original:...}PHPCMS 原接口的返回字段名可能不一致需要做一层字段映射。手机传图方向不对。手机拍的照片带着 EXIF 信息PC 浏览器显示会歪。可以在上传接口里用 PHP 的exif_read_data读取方向并调用imagejpeg重新校正或者在前端用 canvas 压缩时顺带纠正方向。上传 .mp4 被拦。默认扩展名白名单里没有视频格式如果确实要传视频在后台上传设置里把mp4、webm加进去。但要注意老系统的播放器对 mp4 支持也不算好如果只是临时需求建议让客户直接传外链视频。5. 上线前的验证清单与几个容易翻车的边角料5.1 从后台到前台的核心回归用例改造完成不代表就能上线我一般会列一个回归清单前后台都过一遍。后台重点关注管理员登录、退出、栏目管理、内容发布、图片上传、附件管理、模板更新、缓存更新、会员管理。前台重点关注首页、栏目列表页、内容详情页、搜索页、伪静态规则、会员登录注册、评论/留言如果站点有。这里给你一个我在项目里反复用到的最小回归确认表照着点一遍基本能覆盖 80% 的问题后台登录是否走通退出后 session 是否彻底清掉。后台发布一篇文章插入一张图片保存后到前台看是否正确输出。用手机浏览器登录后台上传一张大图确认是否不依赖 Flash。用 UEditor 编辑器发布一段带格式、带图片的正文确认前端样式正常。把站点默认页、栏目页、详情页各打开几个确认没有 PHP 报错和样式丢失。测试搜索功能搜索一个包含特殊字符的关键词确认不再报错。测试后台“更新缓存”和“生成静态页”功能确认模板缓存目录可写、生成逻辑正常。5.2 那些不报错但会出问题的配置平时最容易翻车的反而是“不报错但行为怪异”的配置。PHP 8 环境下有几个点一定要提前检查session.save_path是否可写。老系统部署经常忽略这个一旦目录不可写前台匿名用户可能正常后台登录直接失败而且日志里不一定有明显错误。date.timezone是否设置。不设置时 PHP 8 会抛警告更关键的是老代码发布时间戳会错 8 小时导致新发布的文章显示时间不对。模板缓存目录权限。PHPCMS 的模板缓存通常在/caches/template/权限不对时后台刚保存内容前台还是老页面特别容易让人误以为修改没生效。伪静态规则。V9.6.6 自带.htaccess或者 nginx rewrite 规则很多环境切到 PHP 8 后URL 重写规则没同步导致栏目页、详情页 404。这个问题和 PHP 版本无关但升级过程中最容易忽略。数据库连接字符集。老配置里如果没强制SET NAMES utf8PHP 8 下 mysqli 默认字符集可能不对中文字段出现乱码但页面不报错非常隐蔽。建议启动时强制mysqli_set_charset($link, utf8)。5.3 部署时的几个安全习惯不是纯粹的 PHPCMS 改造但既然碰上了老系统顺手把安全底子补一补值得做后台路径从/index.php?madmin这种默认入口改掉或者至少把admin目录改名。关掉后台的目录列表避免/caches/等目录被扫描。删除/install/目录避免重装风险。把数据库备份文件放在 web 根目录外或者至少设置访问权限禁止下载。后台强制使用 HTTPS老系统的表单提交用的是相对路径一般不影响套一层证书后登录密码就不再裸奔。6. 我个人做这套改造时的一些操作习惯回到开头那句话老项目改造最重要的不是技术多炫而是别把客户正在用的东西弄坏。我踩过几次坑之后慢慢养成了一些固定习惯分享给准备动手的人。第一永远先做减法再做加法最后升环境。先把 SSO、视频模块这些多余的东西摘掉减少代码量再换编辑器、改上传最后升 PHP 版本时涉及的文件数量已经少了很多排查面也小。反过来先升级再做减法报错会混在一起很难定位。第二本地环境要模拟生产环境不只是 PHP 版本一致MySQL 版本、nginx/apache 规则、上传目录权限都要尽量一致。PHPCMS 在 Apache 和 nginx 下的伪静态规则不一样容易在本地明明好好的、部署到线上就 404。有条件的话用 Docker Compose 把一套和线上差不多的环境固定下来改起来心里有底。第三每一次改动都留一个“可回滚点”。我习惯每完成一个功能点就打个 git 标签或者做一份增量备份这样到时候出了问题能快速回到上一个稳定状态而不是从头开始查。老系统不比新框架没有自动迁移、没有完善的测试人工回滚能力就是保命符。第四控制改造范围别顺手“优化”业务逻辑。很多人一打开老代码就手痒这个 SQL 可以优化、那个函数写得烂想顺手全改了。我的建议是别。改造目标是让 V9.6.6 在 PHP8 下稳定运行、补齐 H5 上传、换好编辑器、去掉多余模块而不是把它改成一个新系统。范围一旦扩大测试量跟着翻倍交付日期基本就别想了。最后说一句实在的PHPCMS V9.6.6 这套东西虽然老旧但只要你摸清了它的脾气整一次“精简增强”也没那么可怕。这套改造方案完整走下来站点的运行环境整体前进了好几代后台编辑体验也有了质的提升客户那边基本不会再有“后台卡、登录飘、传图失败”的抱怨。对维护老项目的朋友来说这就是实实在在的价值。本文还有配套的精品资源点击获取