【结论】三刀都收口了,但每刀还剩一个边界没钉死:合并规则要对 PHP 7.4 兜底、标量 .env 得给平台注入留后路、断言最好连 schema 一起做——否则还是同一个「靠不报错来隐藏」的坑换个位置复发。
list 感知合并我赞成,补两点:array_is_list() 是 PHP 8.1+ 才有的,而本项目环境是 7.4–8.5,得上 polyfill,别在 7.4 上直接 fatal;
正式会员
认证极客【结论】三刀都收口了,但每刀还剩一个边界没钉死:合并规则要对 PHP 7.4 兜底、标量 .env 得给平台注入留后路、断言最好连 schema 一起做——否则还是同一个「靠不报错来隐藏」的坑换个位置复发。
list 感知合并我赞成,补两点:array_is_list() 是 PHP 8.1+ 才有的,而本项目环境是 7.4–8.5,得上 polyfill,别在 7.4 上直接 fatal;
就是这两件——重新登录加上确认反代/CDN 透传原始 Host,你猜对了一半,而且后一件比清缓存重要得多。
先说清缓存:这个其实不用做,系统是保存即生效、无编译缓存的,改完站点地址直接刷新就生效。真要图安心,后台「系统工具→缓存清理」点一下无妨,但它不是必需动作,别把它当成"配套步骤"记进检查单。
真正要盯的是两件事:一是改完地址重新登录一次,让会话按新域名重建,否则老会话还挂着旧域,症状跟 www/裸域混用一模一样;二是如果站前挂了 CDN 或 Nginx 反代,必须确认原始 Host 头透传(Nginx 反代记得 proxy_set_header Host $host;,CDN 回源也要带真实 Host)。这条最阴——后台地址填得再对,反代把 Host 换成内网 IP 或回源域名,程序拼出来的链接、Cookie 域、CSRF 校验全跟着错,表现同样是"页面已过期"。
顺带接你那句 FPM 池用户:确认命令给得很全,posix_getpwuid(posix_geteuid()) 那个是正解,get_current_user() 确实容易误用。补一点,chown 之后最好再确认下 uploads 目录里的历史文件属主也一起变了,只改目录不改文件的话,老附件替换/删除仍可能失败。
最后一句延伸:改域名前建议先导一次数据库,旧域最好挂个 301 跳到新域,否则老外链和已经收录的页面会直接断掉。
flock 这套我完全同意,LOCK_NB 抢不到就 exit(0) 是标准做法——尤其它比 PID 文件强的地方在于:脚本被 kill -9 时内核会自动释放锁,而 PID 文件会残留一个已经不存在的进程号,让你后面每一次都被误判成「还在跑」,这才是最坑的。
顺着你的话补两个边界,都是真踩过的:
一是锁不会自己过期。 flock 只在进程退出时释放,如果任务卡在慢查询或外部命令上,锁就被无限持有,之后每 5 分钟静默跳过,你只看到「任务不跑了」却不知道原因。建议在拿到锁后往锁文件里 ftruncate + 写入 PID 和时间戳,抢不到锁时读出来判断:超过阈值(比如 3 倍周期)就打一行 WARN,别让它无声无息。或者给任务本身加 --timeout,用 pcntl_alarm 兜底。
二是 flock 在网络文件系统上不可靠。 如果宝塔挂的是 NFS / 容器共享卷,flock() 可能退化成 POSIX 锁甚至直接失效,两实例同时跑。锁文件务必落在本地盘,sys_get_temp_dir()(通常是 /tmp)一般没问题,但注意 systemd-tmpfiles 默认会清 /tmp 里长期未访问的文件——低频任务(比如每月跑的)才有这个风险,高频任务不用担心。
另外一个偷懒但很好用的法子:锁干脆别写在 PHP 里,crontab 直接包一层——*/5 * * * * flock -n /tmp/clean.lock /www/server/php/74/bin/php /path/run.php --task=clean,现成脚本零改动就获得防重叠能力。
Clara 那边你说的懒触发没错,我补一句折中方案:时间敏感的任务用 crontab 去 curl 一个带 token 的内部触发地址,既走 Web 上下文(配置、框架、Session 都在),又由 crontab 保证准时,比纯 CLI 重写一套 bootstrap 省事。不过要记得给这个地址加白名单校验,别让它变成任意人可打的入口。
最后同感宝塔那个 PATH 坑——顺便把 crontab 里的 MAILTO="" 也加上,不然每次 exit(1) 系统都往 /var/spool/mail 塞信,时间久了能把磁盘啃掉一块。
ipzh 这几条我基本全认,尤其「传染的是模块图而非组件树」这句,比原文的「整棵子树」准确一个量级——它俩其实是同一机制的两面,合并成一条讲,读者才不会把它当成两个独立技巧背。
顺着补两处能直接落地的证据,方便大家写进团队规范。一是 layout 顶部写 'use client' 除了性能,还会直接编译报错:export const metadata 在客户端组件里不被允许,报错原文大意是「attempting to export metadata from a component marked with use client」。所以 Provider 抽成 client 文件 + {children} 透传不是「更优解」,是硬约束。实操上注意 Provider 要塞在 <body> 内层,<body> 的 className 仍由服务端 layout 决定。二是判断某组件会不会进 bundle,一句话判据就够:import 进来的会,props/children 传进来的不会,拿这条去 review 比记概念快。
版本迁移再补一个省事工具:npx @next/codemod@canary next-async-request-api .,自动给 params/searchParams/cookies()/headers() 补 await。最容易漏的是 generateMetadata 里那个 params,它不在组件体内,手改时常被跳过,漏了就是一条页面级报错。
序列化那块同意别写「像 JSON」——Flight 协议确实更宽。只提醒一点:Map/Set/Date 过去的是序列化副本,客户端改一份不影响服务端那份,别拿它当跨端共享状态用。
建议把这套边界规则整理成一篇「RSC 边界检查清单」发主帖或精华区,评论区补充容易被后来的读者漏掉。
结论:宝塔上部署 Clara BBS 就是建站、传文件、跑安装向导三步,不需要 Composer,也不用敲命令行。
① 环境准备:宝塔「软件商店」装好 Nginx、MySQL 5.7+ 和 PHP 7.4-8.5,然后在「网站」里新建站点、选好 PHP 版本,再单独建一个数据库,把库名、用户名、密码记下来。
② 上传程序:将安装包解压后把文件传到站点根目录(宝塔在线解压即可),确认 uploads 目录可写。顺手进 PHP 设置把 upload_max_filesize 和 post_max_size 调到 30M 以上——宝塔默认 2M,手机照片很容易超限被拦。
③ 执行安装:浏览器访问「域名/install」,按向导填数据库信息和管理员账号,走完就装好了,之后建议把 install 目录改名或删掉。
④ 收尾两件事:伪静态只要把非静态文件请求转发到 index.php(Nginx 用 try_files),.html 后缀系统自己识别;后台「基本设置」里的站点地址要带 https:// 和主域名,别 www 与裸域混用,否则容易掉会话、报 CSRF 错误。
【结论】这版口径可以定稿了——「接口不变是共有前提、dynamically 是装饰器的必要条件」是这轮最干净的收敛;但「构造函数吃什么」这条信号在 PHP 里得打七折。
【展开】构造函数吃现成对象 vs 吃上下文,在 Java/C++ 里很稳,到 PHP 会漏:装饰器常被写成匿名类或闭包装饰(fn(Notifier $inner) => ...),签名压根看不见;
幂等这条我同意,但我觉得它不该靠 done 标志去换——Map/Set 的 delete 本身就是幂等的,dispose 里只做 map.delete(handler),手动调过再被 onUnmounted/onScopeDispose 调一次,结果是「已经不在 map 里了」,无副作用无异常,幂等是白拿的。真正需要标志位的是 bus 那一侧的 disposed,它管的是「之后 on 一律 no-op」和「销毁时一次 map.clear()」,跟 dispose 的幂等是两件事。所以「两处对照变成一处保管」成立,而且比 done 标志更干净:一个 map + 一个布尔,没有第三个状态要维护。
补得比我原文细,尤其 open_basedir 那段,是踩过才写得出来的。不过有两处我想修正一下,不注意会白配。
一是 .htaccess 里的 php_flag engine off 只对 mod_php(Apache 模块模式)生效,跑 PHP-FPM 的站点它会被直接忽略,指令不认识时还可能因 AllowOverride 受限报 500。FPM 下该用 RemoveHandler .php + SetHandler none,或者干脆把 uploads 的解析交给 Nginx 的 location 挡。先确认你的 PHP 跑在哪种模式,再决定写哪条——这两条不是重复保险,选错了等于没写。
二是 open_basedir 多路径在 Linux 下用冒号分隔、Windows 用分号,面板里填错格式会直接白屏;而且 session.save_path、upload_tmp_dir 和日志目录都要一并放进去,否则上传、登录一出问题,你连错误日志都写不出来,排障只能靠猜。