ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

老式PHP源码部署排错指南:从本地环境到线上运维全流程

老式PHP源码部署排错指南:从本地环境到线上运维全流程

简介:phpWeb3158网站源码是一套基于PHP开发的企业级网站管理系统,适合中小企业快速搭建官方网站或在线服务平台,也适合有一定PHP基础的开发者学习模块化开发思路。系统内置新闻管理、产品展示、自定义页面、招聘发布、基础设置、菜单配置、搜索和用户反馈等常用模块,基本覆盖企业官网日常运营所需,可显著降低站点搭建门槛。下载压缩包为RAR格式,大小约3.38MB,文件总数与类型明细暂未标注,便于下载后直接结合源码结构进行部署和二次开发。已有182人浏览学习,虽然源码包体积不大,但功能模块划分清晰,适合作为PHPWeb框架的应用范例。通过学习这份源码,可以掌握企业站后台的常见功能组织方式,理解新闻与产品模块的数据处理流程,并可根据业务需求灵活扩展自定义页面、招聘和反馈等模块,是快速上手整站开发、完成课程设计或毕业设计的实用参考资料。

1. 一份PHP网站源码到手:先别急着上传,先看懂它的软肋

phpweb3158 这套 PHP 网站源码,解压后是一整套老式信息门户站:后台能管栏目、发文章、传图片,前台按分类展示内容。它的核心价值不是代码写得有多干净,而是业务闭环完整——数据库、后台、模板、伪静态规则全是配套好的,你只需要改配置和样式就能交付一个能跑的站点。

但这类源码的软肋也很典型:路由全部集中在入口文件,伪静态配置和服务器环境强绑定,数据库脚本的字符集经常和运行时不一致。所以拿到手第一件事不是上传服务器,而是先本地把这三个坑填平。它适合需要快速搭一套带后台的内容展示站点、愿意做二次开发的人;不适合想把代码推倒重写的人——那样不如直接用框架重写。

这套源码包在资源列表里能找到下载,但先别急着下,往下把目录和配置章节看完再动手,能省下不少试错时间。

2. 拆开源码包:目录结构决定你会不会在第一天就翻车

先别把文件往服务器上丢。解压后花十分钟把目录结构捋清楚,能省下后面一晚上的排错时间。这套源码的核心目录就六个,功能与风险点如下表:

目录/文件作用风险点
index.php唯一入口所有模块 include 都从这层走,先读它
config.php数据库连接与站点URL配置本地和线上配置不同,别原样上传
install/安装向导装完必须处理掉,否则配置可被覆盖
admin/后台程序默认入口名太常见,容易被扫到
template/前台模板数据和样式混在一起,改前要先备份
upload/附件上传目录必须禁止PHP执行,否则等于把后门放进来

2.1 入口文件与配置文件:先看这三个文件再做任何操作

大多数类似命名的老式 PHP 站点,index.php 负责两件事:定义常量、根据参数 include 对应模块文件。所以你改任何功能之前,都要先把 config.php 打开看一眼。常见的形式是全大写 define:

<?php // config.php 典型结构(老式 PHP 建站程序) define('DB_HOST', '127.0.0.1'); // 数据库地址,本地和线上一般不同 define('DB_PORT', '3306'); // 端口,云数据库常是自定义端口 define('DB_NAME', 'web3158'); // 数据库名 define('DB_USER', 'root'); // 数据库账号 define('DB_PASS', 'your_password'); // 数据库密码 define('DB_PREFIX', 'pw_'); // 表前缀,一套库跑多套程序时靠它区分 define('DEBUG', false); // 调试开关:true 会打印SQL错误,线上必须 false ?>

这里有几个容易翻车的点。第一,DB_HOST 在本地写 127.0.0.1,上云后要换成云数据库的内网地址,用 localhost 反而可能因为 socket 路径不一致连不上。第二,DB_PREFIX 不是摆设,程序里所有 SQL 拼接都依赖这个前缀,改前缀意味着你要同步改数据库里所有表名,不建议随便动。第三,DEBUG 开关本地调试时可以临时改成 true,能看到 SQL 报错,但上线前必须改回 false——老程序开启 DEBUG 时会把表结构、字段名、物理路径直接打到页面上,等于把底裤亮给扫描器。

我一般还会顺手在本地建一个只读账号来跑测试:给 DB_USER 换成有 SELECT、INSERT、UPDATE 权限但没有 DROP 权限的账号。做模板调整时误操作也不会把整库清掉,这个习惯在二开阶段特别有用。

注意:老程序靠 include 组织代码,改一个公共文件全站生效。调试时先在入口文件顶部打开错误报告,比对着白屏猜原因快得多。

2.2 后台入口与默认账号:修改三处而不是一处

这类源码的后台入口一般就在 admin 目录或是一个 admin.php 文件。安装完成后,第一个要做的是改默认账号,但改密码这个操作远不止数据库里改一个字段那么简单。老式程序的登录态校验,常见写法长这样:

<?php // 后台登录态校验的常见写法(示意) $login_key = 'admin_uid'; // 认证 cookie 名,整个系统里写死了 if (isset($_COOKIE[$login_key]) && intval($_COOKIE[$login_key]) > 0) { // 当前请求被认为是"已登录状态" } ?>

这种校验在安全上约等于没有:校验的是 cookie 值是否大于 0,而不是 cookie 值是否和服务端 session 匹配。换句话说,只要把 admin_uid 这个 cookie 手动改成 1,某些版本就直接进后台了。所以拿到源码后,我建议把三个地方一起改:

第一,数据库里管理员表的密码字段重置为强密码,用 password_hash 或者至少加盐 MD5,别用老程序默认的 admin123。第二,把后台入口文件名改掉,比如 admin 改成 admin_2024,这一步能拦住绝大多数自动化扫描。第三,把登录态校验从只看 cookie 改成 cookie 加 session 双重校验,如果不想动太多代码,至少要把 cookie 名改掉,再在后台所有操作前加一次 session 校验。这三件事做完,后台才算能见人。

2.3 upload 与 template 目录:看似随便放,出事都在这两个目录

upload 目录是用来存附件图片的地方,但很多老程序为了让上传目录足够兼容,会把上传脚本的临时文件也一起放进来。这里有一个绝对不能妥协的底线:upload 目录不能执行 PHP。Nginx 环境下一定要加一段显式禁止的配置:

location ~* ^/upload/.*\.(php|php5|phtml)$ { deny all; }

这条配置直接拒绝 upload 目录下所有 PHP 请求,不管它们叫什么后缀。只限定 /upload/ 前缀路径,不影响其它目录的 PHP 请求。如果你用的是 Apache,对应的写法是在 upload 目录下放一个 .htaccess,内容只有一行php_admin_flag engine off。这样即使有人传上去一个带 PHP 的伪装文件,整站也不会裸奔。

template 目录则是另一个重灾区。老式模板文件里经常直接写include('./template/' . $tpl . '.php')这种拼接,如果 $tpl 的值是从 URL 参数里取出来的,就存在文件包含漏洞。改模板之前,先搜一下代码里加载模板的位置,确认加载的是固定拼接还是经过白名单校验。改之前先把整个 template 目录复制一份,后面想还原时,这是你的后悔药。

3. 本地跑通:从Apache切到Nginx时最容易踩的三个坑

在本地把源码跑起来,听起来简单,实际上大多数人第一次都栽在三个地方:PHP 版本不对、伪静态规则没配、扩展没装全。把这三个解决掉,本地环境就稳定了。

3.1 PHP 版本兼容:老代码在 PHP 7.4 以上最常见的三类报错

这套源码是 PHP 5.x 时代的老写法,如果你本地的集成环境默认是 PHP 7.4 或 8.0,打开首页大概率直接白屏。常见的报错不外乎三类:mysql_* 系列函数被移除、each() 这类函数没了、构造函数和类同名导致解析错误。最典型的是第一种:

<?php // mysql 扩展在 PHP 7 已经被移除,直接用是致命错误 $conn = mysql_connect($host, $user, $pass); // PHP 5 时代的写法 // 在 PHP 7 下改成 mysqli,注意参数顺序变了 $conn = mysqli_connect($host, $user, $pass, $dbname); // 库名是第4个参数 mysqli_set_charset($conn, 'utf8'); // 设置连接字符集 ?>

这里的关键差异不在于函数名,而在于参数顺序和连接后的字符集设置。PHP 5 时代连完库经常直接执行一句SET NAMES utf8,在 mysqli 里请改成 mysqli_set_charset,虽然效果类似,但后者不会产生一次额外的查询,也不容易踩到字符集不一致的坑。

我的建议很务实:不要为了炫技非要把老代码跑到 PHP 8 上。先确认本地环境能切到 PHP 5.6 或 7.0,把站点跑通、数据导出备份好,再考虑逐步迁移兼容。如果你非要用 PHP 7.4,就全局搜 mysql_ 开头的调用,逐个改成 mysqli 版本,改完再全站跑一遍列表页、详情页和后台的每个操作页面。函数能改完,但代码里那些依赖旧版行为的隐式类型转换,才是真正耗时间的地方。

3.2 伪静态规则手动补:.htaccess 转成 Nginx location 段

源码目录里通常会带一个 Apache 用的 .htaccess,规则写得还算清楚,比如把index.php?m=content&c=index&a=list&catid=5伪装成/list-5.html。本地或服务器用 Nginx 的话,.htaccess 不生效,表现为首页能开、栏目页全部 404。你需要在 Nginx 的 server 块里手动补规则:

location / { # 如果请求的文件或目录真实存在,直接交还给静态文件处理,不进PHP if (!-e $request_filename) { rewrite ^/list-(\d+)\.html$ /index.php?m=content&c=index&a=list&catid=$1 last; rewrite ^/show-(\d+)-(\d+)\.html$ /index.php?m=content&c=index&a=show&id=$2&catid=$1 last; rewrite ^(.*)$ /index.php?last; } }

这段配置里,if (!-e $request_filename)的意思是当请求的路径不是真实存在的文件或目录时才走伪静态。这一句很重要,否则你站点里的图片、CSS、JS 全会被转发到 PHP 去处理,网站会慢得离谱。三条 rewrite 的顺序也别动:先匹配精确的栏目页规则,再匹配详情页规则,最后才是兜底让所有路由都进 index.php。正则里的$1、$2对应前面括号捕获的数字,比如/show-5-123.html会拆出 catid=5、id=123。

如果你没有原版的 URL 规则,想知道每个参数的含义,最笨也最有效的办法是先把伪静态关掉,用原始入口文件访问一遍,把地址栏里生成的参数抄下来,再反推正则怎么写。这个方法比猜规则靠谱得多。Apache 环境下则去确认两件事:httpd.conf 里有没有开启 mod_rewrite,站点目录的 AllowOverride 是否设成了 All。这两项缺一个,.htaccess 就是废文件,栏目页同样会 404。

3.3 本地环境检查清单:先确认这三个扩展再开工

本地集成环境切好 PHP 版本之后,还要确认三个扩展是不是开着:gd 扩展、curl 扩展、mysqli 扩展。gd 不开,后台登录页的验证码会直接显示成红叉,不是不显示,是连图片都生成不了。curl 不开,老程序里依赖远程请求的功能会静默失败——表现在后台可能是某个按钮点了没反应,前台可能是某个数据块一直是空的。这两个模块缺失时的报错往往不在页面上,而在 PHP 错误日志里,所以排查方向很容易跑偏。

检查扩展最直接的方式是写一个临时 phpinfo 页面,或者用命令行确认:

php -m | grep -E "gd|curl|mysqli"

一行搞定。如果缺了哪个,就去集成环境的扩展管理里打开,改完重启 PHP 进程。这三个扩展齐了,本地环境的地基才算打好。

4. 线上部署:数据库迁移、写权限与四个常见参数调整

本地跑通只是第一步,真正折磨人的是上传到线上之后。线上环境和本地至少有四处不一样:数据库版本、PHP 进程用户、php.ini 参数、域名。每一样都能让本地好好的站点上线即翻车。

4.1 数据库导入三连:字符集、表前缀、sql_mode

先从数据库开始。老程序自带的 .sql 文件,建表语句里基本都不写DEFAULT CHARSET,这意味着表字符集完全继承库的字符集。所以建库的时候就把字符集明确设成 utf8mb4,后面反而省事:

mysql -uroot -p -e "CREATE DATABASE IF NOT EXISTS web3158 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" mysql -uroot -p web3158 < ./web3158.sql

第一条命令创建数据库并指定默认字符集,第二条命令把整个 SQL 文件导入进去。如果你用 phpMyAdmin 导入,界面里同样能找到字符集下拉框,选 utf8mb4 而不是默认的 latin1_swedish_ci,这个坑踩过的人都知道有多烦。

导入之后还有一个隐蔽问题:MySQL 5.7 以上默认的 sql_mode 带ONLY_FULL_GROUP_BY,老代码里的分组查询基本都只写了主表字段,上线后某些列表页会直接报 SQL 错误。临时验证可以先在当前会话里关掉:

SET SESSION sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';

把ONLY_FULL_GROUP_BY从这个列表里去掉,查询就能跑。但这属于让老程序先活下来的妥协处理,新项目千万不要学,该补 GROUP BY 字段就补全字段,否则后续做统计查询时结果可能和你预期不一致。

4.2 目录写权限:找到对的属主,而不是无脑 777

老程序跑起来之后最常见的两类报错,是上传目录不可写和缓存目录不可写。处理这种问题,第一步不是 chmod 777,而是先搞清楚 PHP 进程到底以哪个用户运行。常见做法是先看进程:

ps aux | grep php-fpm

假设输出显示 php-fpm 以 www 用户运行,那么站点目录的属主也应该是 www,而不是你的 SSH 登录用户。然后给文件和目录分配权限:

sudo chown -R www:www /var/www/web3158 sudo chmod -R 755 /var/www/web3158 sudo chmod 750 /var/www/web3158/upload

755 的意思是属主可读写执行,其他人可读可执行;750 则连其他人可读都去掉。upload 目录只要让 PHP 进程用户可写即可,没必要让全世界可写。如果你在配置了 SELinux 的服务器上操作,chown 和 chmod 都做对了还是不能写,可以先用setenforce 0验证是不是 SELinux 在拦截,确认之后再决定是放行该目录的写策略,还是保持现状。

另一个值得提前做的事:检查站点的日志目录。老程序一般会把错误日志写到 data/log 这类子目录,如果这个目录不可写,错误日志写不进去,发生问题时你连排错线索都没有。宁可先把它建好并赋予正确属主,也别等出事了再临时加权限。

4.3 上传大小、内存限制、超时时间:三个 php.ini 参数联动调

很多老站点的后台有上传图片或导入数据这类功能,线上传不上去基本都是 php.ini 里的限制顶住了。你需要同时关注四个参数:

参数作用常见默认值建议值
upload_max_filesize单文件上传上限2M20M
post_max_size整个 POST 请求上限8M24M
memory_limit脚本最大内存128M256M
max_execution_time单脚本最大执行秒数30120

这里有一个很容易被忽略的原则:post_max_size必须大于upload_max_filesize,否则大文件上传时服务器会直接拒绝请求,而且很多程序不会给出明确错误信息。另外,如果你用的是 Nginx,client_max_body_size默认只有 1M,PHP 这边改到 20M 也没用,Nginx 会在到达 PHP 之前就把请求拦下来,返回 413 错误。两边的参数要一起调,不然永远卡在同一个位置。

4.4 站点 URL 配置:换域名后图片全 404 的根治方法

老站换域名是最容易出幺蛾子的事。表现为首页正常,图片全部裂开,点开图片地址一看,还带着旧域名。原因是老程序把图片地址以绝对 URL 形式写进了数据库的记录里,模板直接输出这个绝对地址,换域名就全部失效。批量替换 SQL 是标准做法:

UPDATE pw_content SET thumb = REPLACE(thumb, 'http://old.example.com', 'https://new.example.com'); UPDATE pw_article SET content = REPLACE(content, 'http://old.example.com', 'https://new.example.com');

执行替换前一定要先导出原表作为备份,因为 REPLACE 是不可逆操作,一旦把不该替换的内容也替换了,数据库里没有后悔药。哪些表需要替换,取决于程序把内容存了哪几张表。如果你不确定,可以用一条查询把所有包含旧域名的表和字段先列出来:

SELECT table_name, column_name FROM information_schema.columns WHERE table_schema='web3158' AND column_name IN ('content','thumb','url');

先摸清楚再动手,比拍脑袋替换要安全得多。

5. 常见问题排查:五个现象对应的原因与修复路径

这一章整理的是这套源码上线后最高频的五个故障。每条按现象、原因、解决的顺序来写,基本可以当排查手册用。排查时不看先后顺序的后果是:明明问题在伪静态,你花两小时去查数据库权限,最后只能骂自己。所以遇到故障先按下面的概率从高到低过一遍。

5.1 首页能开、栏目页 404

现象:首页正常显示,点任何栏目或详情链接都是 404。原因:伪静态规则没有生效,或者规则只覆盖了首页没有覆盖栏目页。解决:先确认站点跑在 Nginx 还是 Apache。Nginx 下查看 server 块是否加载了第 3 章写的 rewrite 规则,规则里栏目页的$1捕获是否正确;Apache 下检查 .htaccess 是否被AllowOverride None禁用,或 httpd.conf 里有没有开启 mod_rewrite。最快的验证方式是手动访问原始入口文件index.php?m=content&c=index&a=list&catid=5,如果原始地址能打开、伪静态地址 404,问题一定在 rewrite 规则本身。

5.2 后台登录成功又跳回登录页

现象:账号密码输对了,页面跳转一下又回到登录页。原因有两类,一类是 session 目录不可写导致登录态根本存不下来,另一类是 cookie 的域名作用域和当前访问域名不一致,比如配置里写的是 127.0.0.1,你通过 localhost 访问。解决:先检查 php.ini 里的session.save_path指向的目录是否存在、属主是否是 PHP 进程用户;再把程序里的站点域名配置改成当前实际使用的一级域名,清掉浏览器 cookie 重试。一个容易被忽略的细节是,老程序登录成功后还会写一条跳转来源,如果后台入口地址配了带端口的地址,也会造成登录态丢失。

5.3 上传图片提示失败或上传后是 0 字节

现象:前端提示上传失败,或上传成功但页面上的文件损坏。原因:upload_tmp_dir指向的目录不可写,或上传文件大小超过了 php.ini 限制。上层程序通常报的是通用错误,不会直接告诉你临时目录不可写。解决:先执行php -r "echo ini_get('upload_tmp_dir'), PHP_EOL;"确认临时目录路径,再检查该目录的写权限和磁盘空间。如果磁盘满了,上传也会失败,而且日志里不会留下明显线索。检查完临时目录再看 error_log,很多程序在上传失败时会往这里写真实原因,比猜快得多。

5.4 页面中文乱码

现象:数据库里中文正常,网页上却是一堆???或乱码符号。原因:连接字符集和表字符集不一致,程序连接数据库时执行了SET NAMES gbk,但库里存的是 utf8mb4,两边的编码对不上。解决:找到程序里连接数据库后设置字符集的代码,统一改成SET NAMES utf8mb4。如果数据已经乱了,越往后拖越难修。还没乱的就先改配置,已经乱的数据需要用按旧字符集导出、按新字符集导入的方式处理,修复过程不是单纯改一个配置能解决的。

5.5 安装完成后没有锁定安装目录

现象:部署完成后再次访问安装向导,还能重新跑一遍安装流程,把数据库配置覆盖掉。原因:install 目录没有删除,或安装脚本判断锁定文件时的逻辑不可靠。解决:装完立即把整个 install 目录从服务器上移除,这不是可选操作。如果你担心以后要重新安装,可以先把 install 目录压缩一份留在本机,服务器上不要保留可执行入口。另一层防护是在 config.php 末尾加一个常量判断,如果配置文件已经存在就不再执行安装逻辑,这样即使别人访问 install 目录,也没有实际效果。

6. 模板二次修改:先建立数据调用概念,再动手改样式

拿到这套源码,大多数人第一个想改的就是前台模板。但是直接改 HTML 是最容易翻车的路径:样式改了,数据不显示;数据显示了,URL 又不对。模板不是一个独立文件,它是和控制器里传递的变量强绑定的。

模板文件里的列表输出,常见写法是这样:

<?php foreach ($list as $row): ?> <a href="/show-<?php echo $row['catid']; ?>-<?php echo $row['id']; ?>.html"> <?php echo $row['title']; ?> </a> <?php endforeach; ?>

你把$list换成自己想要的数组前,得先清楚控制器里到底给模板传了哪些变量。不确定时,直接在模板文件最上面临时写一行<?php var_dump($list); ?>,把数组打出来看字段名,比对着代码猜要快得多。看完删掉这行再改样式,这是最省时间的排查习惯。如果你想在列表页多展示一个字段,就去数据库看这张表有哪些字段,而不是在模板里瞎猜字段名。

改模板之前,还有一个更重要的习惯要养成:备份。我现在的固定动作是先在服务器上复制一份 template 目录再动手:

cp -r template template.bak.$(date +%Y%m%d)

这样改到一半发现样式与后台数据对不上,随时能回到修改前的版本。从那以后,我每次改这套源码都强制走一遍这个流程:先备份模板和数据库,再动页面,改完在电脑浏览器和手机浏览器各看一遍,确认列表页、详情页、后台三个入口都正常,才继续改下一处。这份源码的价值在于它能快速改出一套可交付的站点,但前提是你始终给自己留着后悔药。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进