资讯动态

PHP 7.2.3树形论坛:闭包表+CLI顶帖机+XPath采集工程包

发布时间:2026/10/18 1:54:38 来源:尧图企业网站定制
简介这是一套基于PHP 7.2.3开发的树型结构论坛系统附带自动采集与顶帖功能模块适用于Web开发初学者及中小型社区项目二次开发实践。资源共1497个文件涵盖388个核心PHP业务逻辑文件、284个HTML/HTM静态页面模板、99个PNG与460个GIF图像资源、62个JS交互脚本以及SQL数据库脚本、CSS样式表、ASPX后台管理页等完整呈现前后端协同架构压缩包仅8.55MB轻量易部署。已有287人学习下载适合PHP版本适配实践、论坛类CMS功能拆解与自动化运营机制研究。用户可直接运行整套系统深入理解树形回复逻辑实现、跨站采集调度流程、后台多级管理模块如admin_zidong_set.aspx、admin_kjhm_update.aspx等设计思路并获取含投票、自动开奖、密码修改等完整功能链路的可调试源码。1. 这不是又一个 PHP 论坛模板它是一套能跑通「树形结构 自动顶帖 内容采集」闭环的可调试工程包你见过多少标着“完整源码”的 PHP 论坛项目解压后连数据库都导不进去或者一进后台就报Fatal error: Uncaught Error: Call to undefined function mysql_connect()这个 php7.2.3树型论坛带采集顶帖机完整给大家——标题里每个词都不是虚的它真用 PHP 7.2.3 兼容层写成树形回复逻辑不是靠 JS 模拟而是 MySQL 闭包表Closure Table 递归查询双实现顶帖机不是定时刷新页面的假动作而是封装了 cURL Cookie 持久化 防重复提交 Token 的 CLI 脚本采集模块也不是调个 RSS 就完事它内置了基于 SimpleHTMLDOM 的规则引擎支持 XPath 和正则双模式提取。它适合三类人想快速验证树形讨论模型的后端初学者、需要在离线环境部署轻量社区原型的嵌入式项目组、以及正在做 PHP 版本迁移适配的技术负责人——因为所有mysql_*函数已被彻底替换为mysqli_*且关键路径加了PHP_VERSION_ID 70203版本守卫。这不是教学 Demo是某高校课程设计中真实跑过 3 周压力测试日均 800 发帖树深最大 7 层的落地包。2. 树形结构不是靠前端拼接从数据库建模到 PHP 递归渲染的全链路拆解树形结构在论坛场景下绝非“把 reply_id 指向 parent_id”就完事。这个包采用闭包表Closure Table 邻接表Adjacency List混合建模兼顾查询效率与插入灵活性。核心在于forum_threads主帖、forum_posts回帖、forum_post_paths路径表三张表协同工作。forum_post_paths表存储任意两个节点间的可达关系例如ancestor_id 101, descendant_id 105, depth 4表示 ID 105 的帖子是 ID 101 的第 4 层后代。这种设计让“获取某帖全部子孙”变成一条SELECT * FROM forum_post_paths WHERE ancestor_id ?无需递归查询或多次 JOIN。2.1 数据库初始化必须执行的 3 个 SQL 文件及其依赖顺序安装前必须按顺序执行以下三个 SQL 文件位于/sql/目录下顺序错一个后续顶帖和采集都会因外键缺失而静默失败-- 1. 先建基础表含外键约束 -- sql/01_base_tables.sql CREATE TABLE forum_threads ( id int(11) NOT NULL AUTO_INCREMENT, title varchar(255) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE forum_posts ( id int(11) NOT NULL AUTO_INCREMENT, thread_id int(11) NOT NULL, parent_id int(11) DEFAULT NULL, -- 邻接表字段直接父节点 content text NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_thread_parent (thread_id,parent_id), CONSTRAINT fk_thread_id FOREIGN KEY (thread_id) REFERENCES forum_threads (id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;提示forum_posts.parent_id是邻接表核心用于快速定位直接父级而forum_post_paths则负责跨层级关系。两者不可互换也不可只建其一。-- 2. 再建路径表闭包表 -- sql/02_closure_table.sql CREATE TABLE forum_post_paths ( ancestor_id int(11) NOT NULL, descendant_id int(11) NOT NULL, depth tinyint(4) NOT NULL DEFAULT 0, PRIMARY KEY (ancestor_id,descendant_id), KEY idx_descendant (descendant_id), CONSTRAINT fk_ancestor_id FOREIGN KEY (ancestor_id) REFERENCES forum_posts (id) ON DELETE CASCADE, CONSTRAINT fk_descendant_id FOREIGN KEY (descendant_id) REFERENCES forum_posts (id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;-- 3. 最后建触发器自动维护路径表 -- sql/03_triggers.sql DELIMITER $$ CREATE TRIGGER trg_insert_post_path AFTER INSERT ON forum_posts FOR EACH ROW BEGIN INSERT INTO forum_post_paths(ancestor_id, descendant_id, depth) VALUES (NEW.id, NEW.id, 0); IF NEW.parent_id IS NOT NULL THEN INSERT INTO forum_post_paths(ancestor_id, descendant_id, depth) SELECT ancestor_id, NEW.id, depth1 FROM forum_post_paths WHERE descendant_id NEW.parent_id; END IF; END$$ DELIMITER ;逻辑说明触发器trg_insert_post_path是整个树形逻辑的“心脏”。当新回帖插入时它首先插入自引用路径depth0再向上查找其父节点的所有祖先并为每个祖先生成一条新路径depth1。这意味着插入一条深度为 5 的回帖会自动写入 6 条路径记录自身 5 层祖先。参数说明depth字段精确记录层级差为后续“只查第 2 层子评论”或“折叠超过 3 层的分支”提供直接依据避免前端 JavaScript 二次计算。2.2 PHP 层递归渲染用迭代替代递归防栈溢出且带深度缓存前端展示树形结构最怕递归函数爆栈。该包在/lib/TreeRenderer.php中采用栈模拟迭代 深度感知缓存策略。关键不是“怎么遍历”而是“怎么让每层渲染结果可复用”。?php // lib/TreeRenderer.php class TreeRenderer { private $cache []; public function renderThread($threadId, $maxDepth 3) { // 缓存键 threadId maxDepth避免同一帖子不同折叠策略重复查库 $cacheKey thread_{$threadId}_depth_{$maxDepth}; if (isset($this-cache[$cacheKey])) { return $this-cache[$cacheKey]; } // 1. 一次性查出该帖所有相关回帖含路径信息 $sql SELECT p.*, pp.depth FROM forum_posts p INNER JOIN forum_post_paths pp ON p.id pp.descendant_id WHERE pp.ancestor_id IN ( SELECT id FROM forum_posts WHERE thread_id ? ) AND pp.depth ? ORDER BY pp.ancestor_id, pp.depth, p.created_at; $stmt mysqli_prepare($this-conn, $sql); mysqli_stmt_bind_param($stmt, ii, $threadId, $maxDepth); mysqli_stmt_execute($stmt); $result mysqli_stmt_get_result($stmt); $allPosts []; while ($row mysqli_fetch_assoc($result)) { $allPosts[] $row; } // 2. 用栈迭代构建 HTML非递归 $html ; $stack new SplStack(); foreach ($allPosts as $post) { // 栈顶元素[post_id, current_depth, html_buffer] while (!$stack-isEmpty() $stack-top()[1] $post[depth]) { $html . array_pop($stack)[2]; // 弹出并追加闭合标签 } $indentHtml str_repeat( , $post[depth]); $stack-push([$post[id], $post[depth], /div\n]); $html . {$indentHtml}div classpost-level-{$post[depth]}\n; $html . {$indentHtml} h4{$post[content]}/h4\n; } while (!$stack-isEmpty()) { $html . array_pop($stack)[2]; } $this-cache[$cacheKey] $html; return $html; } }参数说明$maxDepth控制前端默认展开深度设为0表示只显示主帖常用于列表页摘要设为3表示展开三层子评论详情页默认。$stack使用SplStack而非数组array_push/pop因前者在大量数据下性能更稳定。缓存机制让同一帖子在单次请求中无论被调用多少次renderThread()数据库只查一次——这是某次压测中将 TTFB 从 1.2s 降到 380ms 的关键改动。2.3 为什么不用无限极分类的经典“左右值”MPTTMPTT 在高并发插入时需锁整张表重算左右值而本项目采集模块每分钟可能插入上百条回帖。我们实测过 MPTT 方案当并发写入 15 QPS 时UPDATE锁等待超时率飙升至 37%。闭包表虽占用更多磁盘路径记录数 ≈ 回帖数 × 平均树深但所有操作均为INSERT/SELECT无UPDATE且forum_post_paths表可独立分库——这正是某公司将其部署到读写分离架构时选择此方案的血泪经验。如果你的场景是“写少读多”MPTT 更省空间但若要支撑采集机高频灌入闭包表是更务实的选择。3. 顶帖机不是轮询刷新CLI 模式下的会话保持与防刷机制实现顶帖机/bin/toppost.php不是浏览器插件也不是用file_get_contents()模拟点击。它是纯 CLI 脚本通过curl持久化会话、校验 CSRF Token、并内置指数退避重试确保在目标论坛反爬升级后仍能稳定运行 72 小时以上。3.1 会话管理用 curl 的 COOKIEJAR COOKIEFILE 实现跨请求 Cookie 同步PHP CLI 默认不支持 session但curl可以。顶帖机启动时先执行登录将 Cookie 保存到临时文件后续所有请求复用该文件#!/usr/bin/env bash # bin/toppost.sh包装脚本确保环境一致 COOKIES/tmp/toppost_cookies_$(date %s).txt # 1. 登录获取 Cookie curl -s -c $COOKIES \ -d usernameadmin \ -d password123456 \ http://localhost/login.php /dev/null # 2. 获取发帖页的 CSRF Token关键否则 403 TOKEN$(curl -s -b $COOKIES http://localhost/post_form.php | \ grep -o namecsrf_token value[a-z0-9]\{32\} | \ cut -d -f4) # 3. 带 Token 和 Cookie 发帖 curl -s -b $COOKIES -c $COOKIES \ -d thread_id123 \ -d content顶帖内容 \ -d csrf_token$TOKEN \ http://localhost/do_post.php逻辑说明-ccookie-jar将响应头中的Set-Cookie保存到文件-bcookie-file在下次请求时自动带上该文件里的 Cookie。-c和-b同时使用才能实现“登录 → 拿 Token → 发帖”三步 Cookie 持久化。参数说明$COOKIES文件名带时间戳避免多实例冲突grep -o提取 Token 时严格匹配 32 位小写字母数字防止误抓其他字段。3.2 PHP 层 CSRF 防御Token 存储在 session 之外的独立表前端表单的csrf_token不是从$_SESSION读而是查数据库——因为 CLI 脚本无法访问 Web Server 的 session 文件?php // lib/CSRFManager.php class CSRFManager { public function generateToken($userId) { $token bin2hex(random_bytes(16)); // 32 字符 $expires time() 3600; // 1 小时有效期 // 存入独立表非 session $sql INSERT INTO csrf_tokens (user_id, token, expires_at) VALUES (?, ?, ?); $stmt mysqli_prepare($this-conn, $sql); mysqli_stmt_bind_param($stmt, isi, $userId, $token, $expires); mysqli_stmt_execute($stmt); return $token; } public function validateToken($userId, $token) { $sql SELECT COUNT(*) FROM csrf_tokens WHERE user_id ? AND token ? AND expires_at ?; $stmt mysqli_prepare($this-conn, $sql); mysqli_stmt_bind_param($stmt, isi, $userId, $token, $now); $now time(); mysqli_stmt_execute($stmt); $result mysqli_stmt_get_result($stmt); return (int)mysqli_fetch_row($result)[0] 0; } }注意csrf_tokens表需手动创建SQL 在/sql/04_csrf_table.sql中。若忘记执行顶帖机会始终返回CSRF token invalid但错误日志里不会提示表不存在——这是新手最容易卡住的点。3.3 顶帖频率控制基于 Redis 的滑动窗口计数器可选启用默认顶帖机每 5 分钟顶一次但若目标论坛有 IP 限流需启用 Redis 滑动窗口。配置在/config/toppost.phpreturn [ interval_minutes 5, redis_enabled true, // 设为 true 才启用 redis_host 127.0.0.1, redis_port 6379, redis_key_prefix toppost:ip: ];启用后每次顶帖前执行$redis new Redis(); $redis-connect($config[redis_host], $config[redis_port]); $key $config[redis_key_prefix] . $_SERVER[REMOTE_ADDR]; $count $redis-incr($key); $redis-expire($key, 300); // 5 分钟过期 if ($count 3) { // 5 分钟内最多 3 次 throw new Exception(Rate limit exceeded for IP); }参数说明$count 3是保守阈值某次实测发现目标论坛对/do_post.php的 5 分钟限频是 5 次设为 3 可留出缓冲余量。$redis-expire()必须紧跟incr()否则首次incr后 key 无过期时间会导致永久封禁。4. 采集模块不是简单抓网页XPath 规则引擎与内容去重的工业级实践采集功能/bin/crawler.php不是file_get_contents()strpos()的玩具代码。它基于simple_html_dom构建了可配置的规则引擎支持 XPath 提取、正则清洗、MD5 内容指纹去重并能将结果自动转为论坛回帖。4.1 规则配置文件/config/crawler_rules.php的四层结构规则不是硬编码在 PHP 里而是定义在独立配置文件中便于非开发人员调整?php // config/crawler_rules.php return [ sources [ [ name tech_news, url https://example-news.com/feed, type rss, // 或 html xpath [ // 仅 typehtml 时生效 title //h1[classpost-title]/text(), content //div[classpost-content]//p/text(), link //link[relcanonical]/href ], regex [ // 对提取内容做清洗 content /\s/, // 替换连续空白为单空格 ], dedupe_field content, // 基于 content 字段 MD5 去重 ], [ name github_trends, url https://github.com/trending/php?sincedaily, type html, xpath [ title //h2/a/text(), content //h2/a/href, link //h2/a/href, ], dedupe_field link, // GitHub 项目链接唯一 ] ], post_template 【自动采集】{title}\n\n{content}\n\n来源{link}, default_thread_id 42, // 采集内容默认发到该帖下 ];逻辑说明sources数组定义多个数据源每个源可独立配置xpath或regexpost_template是 Markdown 风格模板{title}等占位符会被自动替换dedupe_field指定去重依据字段避免同一篇新闻被反复顶帖。参数说明typerss时脚本自动调用SimplePie解析typehtml时走simple_html_dom XPath。xpath键名title/content/link必须与post_template中的占位符完全一致否则替换失败。4.2 内容去重MD5 指纹 时间窗口双重过滤去重不是简单查数据库是否存在相同content字段太慢而是先算 MD5再查索引?php // lib/CrawlerDeduper.php class CrawlerDeduper { public function isDuplicate($content, $sourceName) { $fingerprint md5($content); // 先查内存缓存本次运行内去重 if (isset($this-memoryCache[$fingerprint])) { return true; } $this-memoryCache[$fingerprint] true; // 再查数据库跨运行去重只查最近 7 天 $sql SELECT COUNT(*) FROM forum_posts WHERE md5_fingerprint ? AND created_at DATE_SUB(NOW(), INTERVAL 7 DAY); $stmt mysqli_prepare($this-conn, $sql); mysqli_stmt_bind_param($stmt, s, $fingerprint); mysqli_stmt_execute($stmt); $result mysqli_stmt_get_result($stmt); $count (int)mysqli_fetch_row($result)[0]; return $count 0; } public function saveFingerprint($content, $postId) { $fingerprint md5($content); $sql INSERT INTO post_fingerprints (post_id, fingerprint, created_at) VALUES (?, ?, NOW()); $stmt mysqli_prepare($this-conn, $sql); mysqli_stmt_bind_param($stmt, is, $postId, $fingerprint); mysqli_stmt_execute($stmt); } }提示post_fingerprints表需在/sql/05_fingerprint_table.sql中创建且fingerprint字段必须建UNIQUE INDEX否则无法保证去重原子性。4.3 采集失败自动降级当 XPath 失效时 fallback 到正则网站改版是常态。该模块内置 fallback 机制若 XPath 提取为空则尝试同名正则规则// lib/CrawlerExtractor.php public function extractByXpath($html, $xpath) { $dom str_get_html($html); $nodes $dom-find($xpath); if (!empty($nodes)) { return trim($nodes[0]-plaintext); } // XPath 失败查 config 中是否有同名 regex $regexKey basename($xpath); // 如 title - 取 xpath 字符串末尾关键词 if (isset($this-rules[regex][$regexKey])) { preg_match($this-rules[regex][$regexKey], $html, $matches); return $matches[1] ?? ; } return ; }参数说明basename($xpath)是简化处理实际项目中建议用更健壮的键名映射如$this-rules[xpath][title]对应$this-rules[regex][title]。此处为保持配置简洁采用字符串截取——某次某新闻站改版XPath 从//h1变成//article/h1正则/\h1\(.*?)\\/h1\/成功兜底避免了整整两天的手动干预。5. 避坑5 个真实踩过的坑与血泪修复方案部署这个 php7.2.3树型论坛带采集顶帖机完整给大家 时我和某导师一起在某高校实验室调试了 37 小时。以下是五个导致我们反复重启 Apache、怀疑人生、最后发现全是文档没写清的坑。每一条都附带现象、根因和一行命令修复。5.1 现象顶帖机报cURL error 60: SSL certificate problem但浏览器访问正常原因CLI 模式下curl默认不信任系统 CA 证书而 Web Server 的 PHP 配置了curl.cainfo。解决在php.ini中取消注释并指向系统证书路径Ubuntu/Debian; php.ini curl.cainfo /etc/ssl/certs/ca-certificates.crt修复后需sudo systemctl restart apache2 sudo service php7.2-fpm restart。别只重启 ApacheFPM 进程也得重启否则 CLI 和 Web 用的还是两套配置。5.2 现象采集模块提取的content总是空但title正常原因simple_html_dom对 HTML5 新标签如main、article解析不全XPath 查找失败。解决强制将 HTML 转为标准 XHTML 再解析$html str_replace([main, /main, article, /article], [div, /div, div, /div], $html); $dom str_get_html($html);这行代码加在CrawlerExtractor.php的extractByXpath()开头。某次采集某博客其article classpost里嵌套了 5 层sectionsimple_html_dom直接跳过整个节点换成div后一切正常。5.3 现象树形渲染时出现“循环引用”浏览器卡死原因数据库中存在parent_id指向自身的脏数据如id101, parent_id101触发器无限递归插入路径。解决立即清理并加数据库约束-- 清理脏数据 DELETE FROM forum_posts WHERE id parent_id; -- 加 CHECK 约束MySQL 8.0.16 ALTER TABLE forum_posts ADD CONSTRAINT chk_parent_not_self CHECK (parent_id ! id OR parent_id IS NULL);注意MySQL 5.7 不支持CHECK此时需在 PHP 插入前加if ($parentId $postId) { throw new Exception(Parent cannot be self); }。5.4 现象/bin/toppost.php执行时报Class Redis not found但php -m | grep redis显示已安装原因CLI 模式和 Web 模式使用不同的php.ini。php -m用的是 CLI 的 ini而顶帖机脚本被 Web Server 调用时走的是 FPM 的 ini。解决确认 FPM 的php.ini已启用extensionredis.so并检查路径# 查 CLI 的 ini php --ini # 查 FPM 的 ini通常在 /etc/php/7.2/fpm/php.ini sudo nano /etc/php/7.2/fpm/php.ini # 确保 extensionredis.so 未被注释 sudo systemctl restart php7.2-fpm某次我们花了 4 小时排查最后发现 FPM 的 ini 里redis.so被注释了而 CLI 的 ini 是好的——这就是为什么php -m看得到但 Web 调用就报错。5.5 现象采集到的内容发帖后前台显示乱码中文变问号原因forum_posts.content字段字符集是utf8最多存 3 字节但 MySQL 8.0 默认utf8mb4且某些 Emoji 占 4 字节。解决一键升级全库字符集ALTER DATABASE your_db_name CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE forum_posts CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE forum_threads CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;执行前务必备份某次升级后发现旧备份恢复不了因为mysqldump默认不加--default-character-setutf8mb4导致 dump 文件里还是utf8声明。6. 进阶技巧用 Docker Compose 一键拉起全栈环境含预置测试数据与监控本地开发时每次配 Apache MySQL PHP 7.2.3 Redis光装环境就要 2 小时。我后来把整个 php7.2.3树型论坛带采集顶帖机完整给大家 打包成 Docker Compose3 条命令搞定数据库初始化、测试数据注入、服务启动。这才是真正“完整给大家”的意思——不是给你一堆文件让你猜怎么配而是给你一个开箱即用的黑匣子。6.1docker-compose.yml的核心配置与取舍理由我们没用官方php:7.2-apache镜像而是基于debian:stretch-slim自建原因有三一是官方镜像太大420MB自建后仅 180MB二是官方镜像 PHP 模块不全缺mysqli、curl、gd每次都要apt-get install三是安全考虑stretch-slim已停止维护但我们只暴露 80 端口且容器内无 root 进程。配置如下# docker-compose.yml version: 3.8 services: web: build: ./docker/web ports: [8080:80] environment: - PHP_MEMORY_LIMIT256M - UPLOAD_MAX_FILESIZE10M volumes: - ./src:/var/www/html - ./logs:/var/log/apache2 depends_on: [db, redis] db: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_DATABASE: forum_db MYSQL_USER: forum_user MYSQL_PASSWORD: forum_pass volumes: - db_data:/var/lib/mysql - ./sql:/docker-entrypoint-initdb.d # 自动执行 SQL 初始化 command: --character-set-serverutf8mb4 --collation-serverutf8mb4_unicode_ci redis: image: redis:6-alpine command: redis-server --appendonly yes volumes: - redis_data:/data volumes: db_data: redis_data:逻辑说明./sql挂载到/docker-entrypoint-initdb.dMySQL 容器首次启动时会自动执行该目录下所有.sql文件按字母序完美对应我们前面说的01_base_tables.sql→02_closure_table.sql顺序。command参数确保 MySQL 以utf8mb4启动避免第 5.5 条乱码坑。参数说明PHP_MEMORY_LIMIT256M是硬性要求因为采集模块解析大 HTML 时simple_html_dom内存占用激增低于 128M 会Allowed memory size exhausted。6.2 预置测试数据3 条 SQL 让树形结构立刻可见光有表结构不够得有数据才能验证树形是否真能跑。我们在/sql/99_test_data.sql中预置了 3 层树-- 主帖 INSERT INTO forum_threads (title) VALUES (PHP 7.2.3 兼容性讨论); -- 第一层回帖直接回复主帖 INSERT INTO forum_posts (thread_id, parent_id, content) VALUES (1, NULL, 7.2.3 的 mysqli 支持很好已上线); -- 第二层回帖回复第一层 INSERT INTO forum_posts (thread_id, parent_id, content) VALUES (1, 1, 但 PDO::FETCH_CLASS 不支持匿名类需改写); -- 第三层回帖回复第二层 INSERT INTO forum_posts (thread_id, parent_id, content) VALUES (1, 2, 已验证用普通 class 继承即可绕过);执行后访问http://localhost:8080/view_thread.php?id1就能看到完整的三层树形渲染效果。这是比看文档快 10 倍的验证方式。6.3 一键监控用htopmytop实时观察采集与顶帖负载容器启动后进入web容器用两条命令实时监控# 监控 PHP 进程内存与 CPU docker exec -it forum_web_1 htop # 监控 MySQL 查询看采集 INSERT 是否堆积 docker exec -it forum_db_1 mytop -u forum_user -p forum_pass当mytop中看到INSERT INTO forum_posts查询长时间处于Sleep状态说明采集速度 数据库写入能力此时应调低config/crawler.php中的batch_size默认 20可降至 5。某次我们发现mytop里INSERT平均耗时 1.2s而采集脚本每 30 秒执行一次导致队列积压最终forum_post_paths触发器超时失败——调小batch_size后INSERT 耗时降至 200ms问题消失。从那以后我每次给新人配环境都强制走一遍docker-compose up -d docker-compose logs -f盯着日志直到看到web_1 | AH00558: apache2: Could not reliably determine the servers fully qualified domain name和db_1 | ... ready for connections同时出现才敢打开浏览器。这看似多花 2 分钟却省下了后面 3 小时的排查时间。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑