简介:phpWeb3158网站源码是一套基于PHP开发的企业级建站系统,面向需要快速搭建官方网站的中小企业与PHP开发者,解决从内容发布到用户互动的整站功能需求。源码按模块组织,包含新闻管理、产品展示、自定义页面、招聘信息、基础设置、菜单配置、站内搜索与用户反馈等常用组件,覆盖企业站点日常运营的主要场景,既有可直接部署使用的完整结构,也便于在此基础上按业务做二次开发与功能扩展。资源以rar压缩包形式提供,体积约3.38MB,压缩包内文件清单与类型明细暂未提供,解压后可按模块目录逐一查阅。已有185人学习下载,说明其在PHP建站学习中具备一定参考价值。对于希望了解整站系统如何拆分模块、如何组织后台配置与前台展示的读者,这套源码可作为对照案例,帮助梳理建站思路并复用到自己的项目中。
1. 拿到 phpweb 3158 网站源码先别急着上传:三分钟看清它是什么
十个下载完 phpweb 3158 网站源码的人里,有八个的第一动作是把压缩包解压到虚拟主机根目录,然后打开浏览器,看到一个白屏或者一行 Fatal error。别笑,我自己第一次也是这么干的。这类包本质上是「一套 PHP 企业建站系统内核 + 一套前台模板 + 一份演示数据」的组合,包名里的 3158 更像是模板编号或演示站编号,它决定的是首页长什么样,而不是系统能力本身。
它能解决的问题很具体:客户要一个能自己改栏目、发新闻、换轮播的企业官网,预算和工期都不允许从零写框架。核心卖点是后台可视化、模板标签化、扩展成本低。适合谁:接中小站点交付的独立开发者、要拿现成后台练二次开发的学生、需要一套可改的成品站做行业垂直改造的人。不适合谁:想拿它学 Laravel、ThinkPHP 8 那套工程化分层的人,这套代码的分层逻辑会让你失望。后面几章我会按「先跑通、再拆结构、最后加固」的顺序往下走,能抄的代码和要避的坑都放进去。
2. phpweb 目录结构解剖:这套网站源码的运行链路是怎么串起来的
解压之后先别改代码,先花十分钟把目录认全。这一步的价值在于:后面任何一个报错,你都能在三秒内判断它是「配置问题」还是「代码问题」,这个判断能力比背一堆函数名有用得多。
2.1 解压后先认这五个目录
不同版本的 phpweb 包内部命名会有出入,但骨架大同小异。下面这张表是我拆包时必看的几个位置,命名按你包里实际的对上,对不上也没关系,职责不会变。
| 位置(常见命名) | 职责 | 能不能动 |
|---|---|---|
index.php | 前台唯一入口,几乎所有请求最后都落到它 | 可读,谨慎改 |
admin/或manage/ | 后台入口目录,安装后通常会被建议改名 | 建议改名 |
include/或core/ | 公共库:数据库连接、路由解析、模板引擎 | 只读为主 |
template/或themes/ | 前台模板,一套模板一个子目录 | 二开主要战场 |
upload/data/runtime/ | 上传附件、运行数据、编译缓存 | 必须可写 |
install/ | 安装器,装完必须删 | 必须删 |
第一件事是用find把入口文件和配置文件的真实位置摸出来。血泪经验是:很多包会在include/下藏一个config.inc.php,和你以为的根目录config.php长得一模一样,改错了那个「看起来对」的文件,后台永远连不上库。
# 进到站点根目录,先列两层结构,不用一上来就 tree 全量刷屏 cd /data/www/phpweb3158 find . -maxdepth 2 -type d | sort | head -40 # 定位真正的配置文件:名字里带 config 的 php 文件,通常就那几个 find . -maxdepth 3 -type f -name "*config*.php" -not -path "./runtime/*" # 定位安装器残留,这个后面上线前要重点处理 find . -maxdepth 2 -type d \( -name "install" -o -name "setup" \)这三条命令的逻辑很直白:第一条只展开两层,避免大包里几万个文件把终端冲爆;第二条限制在三级目录内且排除运行时缓存,因为缓存目录里经常有文件名带 config 的编译产物,容易误导你;第三条是安全前置动作,安装器存在与否直接决定这个站能不能被别人重装。
2.2 一次请求从入口到浏览器,中间过了几道手
理解这条链路,比背下任何一个函数都重要。典型的老式企业站走的是「单入口 + 参数路由」:所有请求都进index.php,入口文件先加载公共初始化文件(配库、开会话、定义常量),然后根据?m=xxx&c=xxx&a=xxx这类参数拼出控制器和方法名,再查数据库拿数据,塞进模板变量,模板引擎把标签编译成原生 PHP 落到缓存目录,最后输出。
这套设计的好处是部署极简,改模板不用碰逻辑;坏处是所有请求都压在一个入口上,缓存不清就会出现「我明明改了代码,页面纹丝不动」的玄学现场。我一般会在读源码前先做一件小事——把入口文件的前 40 行打出来,配置定义、目录常量、错误级别基本都在这几十行里。
# 只看入口文件头部,重点找 define 和 require,这两类行决定后面所有路径 sed -n '1,40p' index.php # 把里面所有 require/include 抽出来,看清初始化链条 grep -nE "require|include" index.php | head -20逻辑说明:sed -n '1,40p'只取前面一小段,避免入口文件里混着大段 HTML;grep -nE带行号输出所有引入语句,require后面跟的路径就是初始化顺序,出错时按这个顺序逐个ls过去,能快速定位是哪个文件缺失。参数上,head -20是个保险,正常入口文件不会引入超过二十个文件,超了说明这个包被二次封装过,更值得多看两眼。
2.3 PHP 版本怎么选:为什么我默认锁 7.4 而不是最新版
这是选型里最容易翻车的一环。这类企业站源码的成文年代普遍偏早,代码里高频出现三类写法:mysql_*系列函数、each()遍历数组、以及用花括号取字符串下标$str{0}。这三样在 PHP 7 里还能喘气(mysql_*在 7.0 已被移除,只能靠mysqli兼容层或者改代码),到 PHP 8 就是直接 Fatal error。
所以我的默认策略是:本地开发用 PHP 7.4,生产环境如果主机商只给 8.x,那就得做好改代码的心理准备,而不是幻想它能跑。判断代码年代有个快招,扫一遍关键函数出现次数。
# 扫描高危老写法,数字越大,说明这个包离 PHP 8 越远 grep -rIl --include="*.php" -E "\bmysql_(query|connect|fetch_array)\b" . | wc -l grep -rIl --include="*.php" -E "\beach\s*\(" . | wc -l grep -rIl --include="*.php" -E "\\\$[a-zA-Z_]+\{[0-9]+\}" . | wc -l逻辑说明:三条命令都是先统计「有多少个文件命中」,而不是直接看命中内容,因为老包里这类写法可能上千处,一屏屏翻没有意义,先要一个量级判断。-I忽略二进制文件,.sql、.jpg里出现each(纯属噪音;--include="*.php"把范围锁死在 PHP 文件上。参数说明:第一条如果结果大于 0,说明这包在 PHP 7 下也得改;第三条的正则里{需要转义,shell 双引号里还要再转一层,这是我自己踩过的坑——正则写错的时候,它匹配的是空集,你会误以为代码很干净。
扩展侧不能漏的还有几个:pdo_mysql或mysqli(二选一,看代码用哪套)、gd(缩略图和水印,企业站基本都有)、mbstring(中文截断,缺了标题会截出乱码)、fileinfo(上传类型校验)、curl(采集和接口)。这些在php.ini里是编译期或扩展行控制的,缺了不会报「扩展缺失」,只会报某个函数 undefined,这点特别坑新人。
3. 本地跑通 3158 源码:建库、改配置、配伪静态三步走
这一章给的是不依赖任何面板的最小可跑方案。用面板当然快,但面板会把你和真实报错隔开,第一次跑通建议走命令行,知道每一步在做什么,后面换环境才不会懵。
3.1 环境参数表:先定死这几个值
| 组件 | 推荐值 | 边界与说明 |
|---|---|---|
| PHP | 7.4 | 5.6 也能跑但性能差;8.x 需改代码 |
| MySQL | 5.7 / MariaDB 10.4 | 字符集统一 utf8mb4 |
| Web 服务器 | Nginx 1.20+ | Apache 也行,重写规则不同 |
upload_max_filesize | 32M | 要和 Nginx 的client_max_body_size对齐 |
max_execution_time | 60 | 后台批量导入数据时容易超时 |
| 目录属主 | 与 php-fpm 运行用户一致 | 不一致就是上传失败的头号原因 |
upload_max_filesize和client_max_body_size这对参数一定要同时改。只改 PHP 的,Nginx 会在 413 就把请求拦掉,浏览器给你的提示是「上传失败」,日志里连个 PHP 错误都没有,这种黑匣子最耗时间。同理max_execution_time和fastcgi_read_timeout也要对齐,后台导几百条数据时,PHP 还在跑,Nginx 已经 504 了。
3.2 建库、导数据、改配置
顺序不能反:先建库定字符集,再导 SQL,最后改配置里的库名和前缀。反过来的话,你会先看到一个连不上库的报错,然后再回头排查字符集,来回折腾。
# 1. 建库时就把字符集定死,别等导完再改,改表字符集比建库麻烦十倍 mysql -uroot -p -e "CREATE DATABASE phpweb3158 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" # 2. 建一个专用账号,只给这一个库的权限,别用 root 跑站点 mysql -uroot -p -e "CREATE USER 'web3158'@'127.0.0.1' IDENTIFIED BY '换成你自己的强密码';" mysql -uroot -p -e "GRANT ALL PRIVILEGES ON phpweb3158.* TO 'web3158'@'127.0.0.1'; FLUSH PRIVILEGES;" # 3. 导入演示数据,--default-character-set 必须显式带上 mysql -uroot -p --default-character-set=utf8mb4 phpweb3158 < ./phpweb3158.sql逻辑说明:第一步的COLLATE显式写成utf8mb4_unicode_ci,是为了和后面 SQL 文件里的建表语句保持一致,两边不匹配时会出现「表建出来了但排序规则混乱」,中文排序结果诡异。第二步把账号的主机限定成127.0.0.1,比%安全得多,本地调试也够用。第三步的--default-character-set是老司机必备,缺了它,SQL 文件里的中文栏目名会被按 latin1 解释,导完打开后台全是问号,而且这种乱码是不可逆的,只能删库重导。
配置文件的改法,键名按你包里实际的来,形式大同小异:
// 典型老式企业站的配置写法,键名请对齐你包里真实的 config 文件 define('DB_HOST', '127.0.0.1'); define('DB_PORT', '3306'); define('DB_NAME', 'phpweb3158'); define('DB_USER', 'web3158'); define('DB_PASS', '换成你自己的强密码'); define('DB_CHARSET', 'utf8mb4'); // 老库若是 utf8,这里跟着改,两边必须一致 define('TABLE_PREFIX', 'pw_'); // 表前缀必须和导入的 SQL 完全一致 define('SITE_URL', 'http://local.phpweb3158.test'); // 换域名时这里最容易漏逻辑说明:前六行是连接参数,TABLE_PREFIX是最容易被忽略的一行——很多包在安装时会让你填前缀,安装器把前缀同时写进配置和所有表名,你手工导库时如果 SQL 里是pw_而配置里是空的,报错是Table 'xxx_article' doesn't exist,看起来像表丢了,其实只是前缀对不上。SITE_URL这类常量在老代码里会被写进数据库的图片路径和跳转链接里,换环境不改它,就会出现「本地能看,上线全是裂图」。
3.3 伪静态与入口重写:把 .htaccess 翻译过来
Nginx 不认.htaccess,但包里一般会附带一份 Apache 规则,照着逐条翻译是最稳的做法,别自己臆想规则。下面这份是我常用的基础版,按需增删。
server { listen 80; server_name local.phpweb3158.test; root /data/www/phpweb3158; index index.php index.html; # 与 php.ini 的 upload_max_filesize 对齐,否则大文件上传直接被 413 拦掉 client_max_body_size 32m; location / { # 先找真实文件,再找目录,最后统一交给入口文件做参数路由 try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; fastcgi_read_timeout 60s; } # 上传目录和缓存目录里的 php 一律不执行,这条规则能挡掉绝大多数上传型后门 location ~* ^/(upload|data|runtime|cache)/.*\.(php|php5|phtml)$ { deny all; } # 隐藏文件不对外,.git、.env、.htaccess 都在这个范围内 location ~ /\.(git|env|htaccess|svn) { deny all; } }逻辑说明:try_files那一行是整个配置的核心,它的执行顺序是「真文件优先 → 目录其次 → 兜底给 index.php」,缺了最后一跳,后台所有带参数的页面都会 404。第二条location ~ \.php$里的SCRIPT_FILENAME必须用$document_root拼,如果照抄了别人的绝对路径,换目录就崩。第三条 deny 规则看着不起眼,但它是我在交付现场一定会检查的一项——很多老包的上传目录是可执行的,一个伪装成图片的 php 文件传上去就能直接访问。
参数怎么调:client_max_body_size建议按你客户最常传的素材大小定,32M 够用;fastcgi_read_timeout调到 60s 是为了后台批量操作,生产环境如果不需要批量导入,压回 30s 更安全。
3.4 白屏与 500 的标准定位顺序
遇到白屏别慌,按这个顺序走,九成问题在三步之内能定位:
第一步,打开错误显示(只在本地开,生产绝对不能开)。第二步,看 PHP 错误日志的最后 20 行,注意是 php-fpm 的日志不是 Nginx 的 access log。第三步,检查runtime/、data/cache/是否可写,老代码写缓存失败时经常只是静默返回空字符串,页面就白了。
# 临时开错误显示,仅限本地调试,改完记得还原 php -i | grep -E "^(display_errors|error_reporting|error_log)" # 盯住错误日志尾部和运行时目录属主,两条信息基本能覆盖大半白屏 tail -n 20 /var/log/php-fpm/error.log ls -ld runtime data/cache upload逻辑说明:第一条不是改配置,而是先确认生效值,因为很多人改了php.ini却忘了 php-fpm 有自己的池配置,优先级更高,改完不生效纯属白忙。第二条的tail -n 20足够,报错通常集中在最新的几行;ls -ld看的是目录的属主和权限位,php-fpm 运行用户对这三个目录没有写权限,表现就是「首页能开,一传图就失败」或者「后台一保存就白屏」。
4. 二次开发前的准备:模板编译、缓存机制与数据表结构
改这套网站源码的样式和功能,八成时间不是在写代码,而是在跟缓存和表结构较劲。这一章把我拆包时先做的三件事摊开讲,做完这三件,后面改什么都顺手。
4.1 模板标签是怎么被编译成 PHP 的
老式企业站的模板引擎大多不是实时解析,而是「首次访问编译一次,结果落到缓存目录,后续直接 include 编译产物」。这就解释了一个经典现象:你在template/里改了一行 HTML,刷新页面毫无变化,因为浏览器加载的是缓存目录里那份旧 PHP。
编译产物一般落在runtime/、data/cache/或template_c/这类目录下,文件名通常是模板路径的哈希。判断方法很简单:改模板前先记下缓存目录的文件数量和总大小,改完刷新,看有没有新增文件。
# 改模板前先摸清缓存现状,改完对比一下就知道有没有重新编译 du -sh runtime data/cache template_c 2>/dev/null find runtime data/cache -type f -name "*.php" 2>/dev/null | wc -l # 清理编译产物:只删缓存,绝对不要顺手带上 upload find runtime data/cache -type f -name "*.php" -delete 2>/dev/null逻辑说明:第一条命令给的是「基线」,du -sh看体积,find | wc -l看文件数,两个数字都要记。第二条只删*.php编译产物,这是有意的保守策略——缓存目录里除了编译后的模板,可能还有配置快照和小型索引文件,全删虽然也能重建,但会触发一轮全量重编译,站点短暂变慢。如果你确定目录是纯缓存,直接rm -rf runtime/*更快,前提是你先ls看过里面有什么。
模板标签的写法各家不同,常见是{$变量}、{loop ...}、{php}...{/php}这套。看标签定义直接去公共库里找正则替换的那段代码,关键词搜preg_replace基本一击命中。
# 模板标签的解析规则就藏在正则里,找到它就知道哪些标签能用 grep -rn "preg_replace" include/ core/ 2>/dev/null | grep -iE "template|tpl|tag" | head逻辑说明:preg_replace是这类模板引擎的核心函数,把模板里的花括号语法替换成<?php ?>。加-r递归搜目录,head限制条数避免刷屏。看到那行正则之后,你就能反推支持的语法范围,比如它可能只支持{$var}不支持{$var|escape},这时候你在模板里写了过滤器,页面直接把它原样输出,看着像模板没生效,其实是语法不支持。
4.2 核心数据表怎么认
不看表结构就改功能,等于蒙着眼睛改电路。老站表数量通常在三十到八十张之间,真正高频用到的就下面这几类,表名前缀按你配置里的TABLE_PREFIX替换。
| 表用途 | 常见表名形态 | 关键字段要看什么 |
|---|---|---|
| 栏目/分类 | {pf}_category | pid父级、path层级路径、sort排序 |
| 内容主表 | {pf}_article或{pf}_news | catid归属栏目、status审核态、hits浏览量 |
| 内容详情 | {pf}_article_data | 大字段正文,主表拆出来是为了查询快 |
| 单页 | {pf}_page | 关于我们、联系方式这类固定页 |
| 附件 | {pf}_attachment | filepath存相对路径,换域名时靠它 |
| 后台账号 | {pf}_admin | 密码字段是 md5 还是 password_hash,决定能不能直接改 |
| 配置项 | {pf}_config | 站点名、SEO 模板、水印开关都在这里 |
三张表最重要的关系是:栏目表主导航,内容主表管列表,详情表管正文。改栏目层级最容易出错的是path字段——很多老系统用0,1,5,这种逗号串存祖先链,你手工插一条记录只改pid不改path,前台导航就乱了,而且乱得很有迷惑性:新栏目在主表里明明存在,就是不显示。
-- 先看清栏目树,pid 是父级,path 是祖先链,两者必须自洽 SELECT id, pid, name, path, sort, status FROM pw_category ORDER BY path, sort; -- 找出 path 和 pid 对不上的脏数据,这类记录就是导航错乱的元凶 SELECT c.id, c.name, c.pid, c.path FROM pw_category c LEFT JOIN pw_category p ON p.id = c.pid WHERE c.pid > 0 AND (p.id IS NULL OR c.path NOT LIKE CONCAT('%', c.pid, '%'));逻辑说明:第一条按path排序能把树状结构直观地摊平,sort是同层排序,status是上下架状态,看这几列就知道导航该长什么样。第二条是脏数据体检,条件的意思是「有父级但父级不存在,或者祖先链里不包含父级 id」,两种都是典型的手工插数据后遗症。参数上把表前缀pw_换成你配置里的实际值,别忘了 SQL 里也要换。
4.3 改样式不生效的三层排查法
这是被问得最多的问题,答案是固定的三层:模板层、缓存层、浏览器层。顺序不能乱,从外往里查。
# 第一层:确认你改的文件就是被加载的那个,路径大小写敏感 grep -rn "该模板文件名" include/ core/ 2>/dev/null | head -5 # 第二层:确认缓存目录里有没有对应的编译产物,有就说明缓存没刷 find runtime data/cache -name "*.php" -newermt "-10 minutes" 2>/dev/null | head # 第三层:确认静态资源有没有被 Nginx 的 expires 缓存住 grep -nE "expires|cache-control" /etc/nginx/conf.d/*.conf 2>/dev/null逻辑说明:第一层查的是「模板文件到底被谁引用」,有时候你改的是template/default/,但配置里指定的模板目录是template/blue/,改一天也不生效。第二层用-newermt "-10 minutes"找最近十分钟内生成的文件,如果有,说明系统确实重编译了,问题就在你改错文件;如果没有,说明缓存没清或者模板没触发重编译。第三层是很多人忽略的——CSS 改了但浏览器拿的是 304,看着像没改,其实服务器上早改了,加个时间戳参数或者强刷就能验证。
5. 上线前避坑清单:这套源码最容易翻车的五个地方
这一章全部是我和同行踩过的真实类型的坑,现象、原因、解决三件套,照着比对能省掉至少两个通宵。
坑一:首页正常,后台 404 或 403
现象很迷惑:前台所有页面都能开,唯独/admin打不开,浏览器返回 404 或者 403。
原因通常是两个。一是后台目录被改名了,很多包在安装完成后会把默认后台目录重命名并在配置里记录新路径,你直接访问旧路径当然找不到。二是 Nginx 的try_files里没有兜底到 index.php,后台带参数的 URL 被当成静态路径找文件,找不到就 404。
解决:先去配置或include/下搜后台目录名常量,确认实际路径;再检查try_files $uri $uri/ /index.php?$query_string;这行是否完整。权限类 403 则看目录属主和location的 deny 规则,有时候是上一任管理员加了 IP 白名单,换机器就进不去了。
# 定位后台入口的真实路径,注意排除缓存目录里的干扰项 find . -maxdepth 3 -type f -name "index.php" -not -path "./runtime/*" -not -path "./upload/*"坑二:图片上传成功,前台全是破图
现象:后台上传显示成功,文件也确实落到了upload/,但前台展开全是裂图。
原因是路径写死。老站在安装时把SITE_URL连同域名一起写进数据库的附件表,你换了域名,数据库里还是http://旧域名/upload/...。另一种是代码里直接拼了http://加当前域名,但站点部署在子目录下,路径少了一层。
解决:先查附件表里的filepath字段值,确认存的是相对路径还是绝对 URL;绝对 URL 就写条 SQL 批量替换域名。子目录部署的情况,要改的是配置里的站点根地址,别去逐个改模板。
-- 换域名后批量修正附件路径,先 SELECT 看清楚再 UPDATE SELECT id, filepath FROM pw_attachment WHERE filepath LIKE 'http://旧域名%' LIMIT 20; UPDATE pw_attachment SET filepath = REPLACE(filepath, 'http://旧域名', 'http://新域名') WHERE filepath LIKE 'http://旧域名%';注意:先把LIMIT 20的查询结果核对一遍再执行更新,REPLACE是不可逆的,跑错了只能靠备份回滚,而这类老项目的备份往往就是「上次导出的那个 SQL」,很可能是三天前的。
坑三:换服务器后中文全变问号
现象:数据库导过去了,表也在,但栏目名、文章标题全是???或者乱码方块。
原因是导出或导入时字符集不匹配。最常见的是mysqldump没带--default-character-set,或者导入时客户端默认按 latin1 连接。
解决:重新导出一次,两端都显式指定utf8mb4。如果原库本身就是 latin1 存的 utf8 内容(历史遗留),直接改字符集会二次损坏,那种情况得走导出 → 转码 → 导入的专门流程,别在一个已经乱掉的库上反复试。
# 导出和导入两端都锁字符集,中间不要用管道拼,文件落盘便于核对 mysqldump -uroot -p --default-character-set=utf8mb4 --single-transaction phpweb3158 > dump.sql mysql -uroot -p --default-character-set=utf8mb4 phpweb3158 < dump.sql坑四:后台改了内容,前台纹丝不动
现象:后台编辑文章、保存成功、提示成功,前台刷新还是旧内容。
原因:缓存。三层缓存里至少中了两层——模板编译缓存和站点数据缓存。老系统为了扛住访问量,会把栏目数据和首页区块缓存成文件或存进{pf}_cache表,后台保存时正常应该触发清理,但如果你手工改过数据库,或者清理函数因为目录权限失败被静默吞掉,缓存就一直在。
解决:先清runtime/、data/cache/下的编译产物;如果还不行,去配置表找缓存开关,临时关掉验证一下。低峰期全清一次缓存是常规操作,别在工作时间做。
坑五:安装目录没删,站点随时能被重装
现象:站点运行正常,某天首页突然跳转到安装向导,或者数据被清空。
原因:install/目录还躺在网站根目录下,任何人访问你的域名/install/都能走一遍安装流程,直接覆盖数据库配置。
解决:装完立刻删或改名,这是上线检查清单的第一条。已在运行的站点被重装过,别急着重新安装,先去数据库看数据还在不在,很多时候只是配置被覆盖,数据表还在,改回配置就能救回来。
# 上线前必做:安装器必须不存在;如果业务要求保留,就整目录改名并加访问限制 ls -d install setup 2>/dev/null && echo "存在安装入口,必须处理" mv install install_done_$(date +%Y%m%d) 2>/dev/null6. 交付前的加固与自检:把源码包变成能交出去的站
到这一步站点已经能跑了,但「能跑」和「能交付」是两回事。我一般会留出半小时做三件事:关入口、跑自检、人工验收。
6.1 先关掉三个不该存在的入口
第一个是安装器目录,第二个是备份文件(*.sql、*.zip、*.bak直接放在根目录的情况太常见了),第三个是调试开关。这三个东西留一个,前面几章的心血就可能一夜清零。
# 根目录下的备份包和 SQL 文件,一律移出网站根目录,别放在能被下载的地方 find . -maxdepth 2 \( -name "*.sql" -o -name "*.zip" -o -name "*.bak" -o -name "*.tar.gz" \) -print # 确认没有残留的安装器 ls -d install setup 2>/dev/null # 生产环境错误显示必须是关的 php -i | grep "^display_errors"逻辑说明:第一条只查两层是因为深层目录里的备份一般是缓存产物,风险集中在根目录;-print而不是-delete,是因为这一步我只想看清风险面,删除动作交给人工确认。第三条在实际环境里查的是 php-fpm 池的配置,php -i在 CLI 下读的可能是另一份 ini,两边都要对一遍。
6.2 一段只读自检脚本
上线前跑一遍,只读不改,失败了按提示处理。
#!/usr/bin/env python3 # 上线前自检:只做只读检查,不修改任何文件 import os, sys, subprocess ROOT = "/data/www/phpweb3158" results = [] def check(name, ok, hint=""): results.append((name, ok, hint)) # 1. 安装入口必须不存在 check("安装入口已移除", not any(os.path.exists(os.path.join(ROOT, d)) for d in ("install", "setup")), "删除或改名 install/setup 目录") # 2. 上传与缓存目录必须可写,且其中的 php 不应被执行(靠 Nginx 的 deny 规则兜底) for d in ("upload", "runtime", "data/cache"): p = os.path.join(ROOT, d) check(f"{d} 存在且可写", os.path.isdir(p) and os.access(p, os.W_OK), f"chown -R www:www {p}") # 3. 生产环境错误显示必须是关闭状态 ini = subprocess.run(["php", "-i"], capture_output=True, text=True).stdout check("display_errors 已关闭", "display_errors => Off" in ini, "php.ini 中改为 Off,错误写入日志") for name, ok, hint in results: print(f"[{'OK ' if ok else 'FAIL'}] {name}" + ("" if ok else f" -> {hint}")) sys.exit(0 if all(ok for _, ok, _ in results) else 1)逻辑说明:os.access(p, os.W_OK)判断的是当前执行用户对这个目录的写权限,用sudo跑会给出假阳性,所以脚本要用和 php-fpm 相同的用户执行。subprocess.run(["php", "-i"])拿的是 CLI 的配置,和生产 fpm 可能有差异,这一步只作参考,最终以 fpm 池配置为准。退出码非零表示有检查项没通过,可以直接接进发布流程,卡在最后一道关口。
6.3 交付前的三项人工验收
自动脚本查不出体验问题,这三项我每次都会亲自走一遍:后台发一篇带图文章,前台确认图片、标题、正文、SEO 标题四处都对;换个浏览器无痕模式打开首页,确认没有依赖本地缓存的假象;用手机流量(不是公司 WiFi)打开一次,确认静态资源不是只在内网可达。这三项走完,才算真正交付。
说个我自己的教训。有次赶交付,自检脚本跑了绿灯,安装目录也确实删了,但我漏了根目录里一个三天前打包的.tar.gz,里面完整包含了安装器和数据库配置。站点上线第二周被人从那个包里把配置掏走,虽然数据没丢,但整个迁移重配花了整整一天。从那以后我每次交付前,都会把「根目录下还有什么能被直接下载」当成固定动作走一遍,不管自检脚本报什么。希望帮到你。
本文还有配套的精品资源,点击获取