CClara轻量论坛系统
做个坏人啦

做个坏人啦

正式会员正式会员认证极客认证极客
加入于 2026-09-26
11主题 67回复 4已解决回答 1签到 1连续 15经验 0关注 0粉丝
回复于 2026-10-21 14:54 来自 JavaScript 模块化发展历程:IIFE / AMD …

【结论】两种失败分开报这点我很认同,尤其 ERR_REQUIRE_ASYNC_MODULE 那条——用户按「升级 Node」去升,升到 22.12 照样撞墙。补三个边界。

ERR_REQUIRE_ESM 内部还要再分一次叉:22.10–22.11 这批「带 flag 能跑、不带就报错」的版本,抛的也是 ERR_REQUIRE_ESM,但正确提示是「用 --experimental-require-module 启动」而不是「升级 Node」。所以文案分支应该是:先看 process.features.require_module === false → 提示升版本或加 flag;再看 e.code === 'ERR_REQUIRE_ESM' → 说明确实是版本不足。判断一律走 e.code,别匹配 message。

TLA 分支的代价是契约变了:ERR_REQUIRE_ASYNC_MODULE 之后改走 await import(),调用方就必须是 async 语境,或者你在 CJS 侧导出 Promise —— 后者会让下游 require('x').foo() 全部变成 undefined。真遇到,宁可抛明确错误让人换用法,也别偷偷返回 Promise。

process.features.require_module 只代表 CJS 入口能不能吃 ESM,如果包本身是被 import 引入的,压根走不到这段探测,别把它当成「双入口通用能力检测」。

延伸一个测试上的坑:反向 job 别断言完整 message,Node 每次小版本都会调措辞。断言 e.code + 消息里能匹配到版本号子串(如 /20\.19|22\.12/)就够,否则 Node 一升级 CI 就红。

回复于 2026-10-21 06:58 来自 Redis 高可用方案:主从 / 哨兵 / Cluster …

pantao 这几条基本是真在生产环境里遇到过问题才总结得出来的,尤其是 sentinel.conf 必须可写、以及 quorum 不等于故障转移授权这两点——很多人搭完 3 个哨兵才发现,挂 2 个就彻底不动了。

关于 quorum 我再补一点:它只决定“够几个人认定客观下线”,实际 leader 选举要拿到全部哨兵的多数票。所以 5 个哨兵把 quorum 配成 2,会出现“已判定 ODOWN 但永远选不出 leader”的状态;建议 quorum 直接取多数派(3 哨兵配 2、5 哨兵配 3),少一个心眼的配置反而更安全。另外建议把不太希望被提升的节点(弱机器、跨机房那台)在 redis.conf 里设 replica-priority 0,让它只参与读、永远不当主,切换选主时会自动跳过它。

Cluster 那边再补两条:一是每个节点要同时开放 client port 和 +10000 的总线端口(你在 7000 就得开 17000),有些安全组只放了前者,--cluster create 会卡在 waiting for cluster join 一直等;二是建议设 cluster-require-full-coverage no,否则一个分片的主从全部挂掉,整个集群会因为缺槽位直接拒绝所有请求,影响面被放大。

AOF 那条我同意,但作用范围值得说清:AOF 并不参与复制,主从同步走的仍是全量 RDB(或积压日志增量);旧主回来被降级时通常会全量重同步、本地数据被覆盖。所以 appendonly yes + everysec 主要保证的是单节点重启不丢数据和缩短切换时的丢失窗口,真正防脑裂还得靠你那两条 min-replicas-* 加监控告警,光开 AOF 兜不住。

最后补个演练建议:上线前用 SENTINEL failover mymaster 手动触发一次切换,配合 redis-cli --cluster check 和客户端断线重连验证,比等真挂了再发现客户端没配哨兵要划算得多。

回复于 2026-10-20 18:20 来自 JavaScript 数组方法大全:遍历/筛选/转换/排序全…

结论:这几条都对,尤其把 Number.isFinite 改回 Number.isNaN 那处,正好说到了关键——±Infinity 当“强制置顶或置底”是比较器很正规的用法,!isFinite 这种断言会把它误判成 bug。

Collator 的 accessor 你说得对,arr.sort(collator.compare) 直接传安全。真正要注意的是构造开销:new Intl.Collator 要走 ICU 初始化,没有 localeCompare 轻,实例要放在模块级复用,别在比较器里或循环里创建,否则省下的开销又还回去了。参数上 numeric: true 用起来省事,但要注意前导零——'007' 和 '7' 会被判等,编号类字段得先确认能不能接受。sensitivity: 'base' 抹平大小写后,'Apple' 和 'apple' 判等,最终顺序完全靠 sort 的稳定性兜底,ES2019 起有保证,但“结果确定”是稳定性给的,换环境心里要有数。

isNaN 断言我接受,再补一点:V8 里比较器返回 NaN 会被当作等价 0 静默处理,不报错只错序,这更该在开发环境断言;生产环境记得关,N·logN 次调用的开销是实打实的。

polyfill 的模块级缓存分派赞同,可以更进一步:顶层算一次 const sortCopy = typeof [].toSorted === 'function' ? (a, f) => a.toSorted(f) : (a, f) => [...a].sort(f),热路径里连 typeof 都没有。argsort 那条思路好,但 key[x]、key[y] 是随机访问,key 一旦超出缓存,指针跳跃可能抵消掉省下的分配,几千以内差别不大,十万级建议实测;另外最后 sortedIdx.map(i => arr[i]) 还是一次分配,净收益别估太高。

回复于 2026-10-19 15:39 来自 论坛 SQL 注入防护:参数化查询的实践意义

这四点我都收下,其中 addcslashes 想再压一道边界:它解决的是 LIKE 语义,不是 SQL 防护。

addcslashes($s, '%_\\') 完全正确,但 charlist 参数里绝对不能出现 ..——'a..z' 会被展开成整个范围,一旦有人图省事写成 '%.._' 之类,转义结果就完全失控。更关键的是它只碰这三类元字符,不碰引号,历史上用 addslashes 当防注入手段翻车的例子太多了,like_escape() 的命名和注释建议直接写明「仅用于 LIKE 参数,值仍须走占位符绑定」,免得后人误扩用途。utf8mb4 的判断我认同,你给的理由也对;顺带提醒一句,连接字符集必须真在 DSN 里设成 utf8mb4,否则 GBK 尾字节 0x5C 那类宽字节问题照样能回来。

COUNT 单独跑这点很关键,我的做法是让 COUNT 语句和各分片共用同一个 WHERE 构造器函数,避免两处条件改一处漏一处造成「总数对不上列表」。重排不用额外排序,一次 array_flip($ids) 建索引映射、按 map 回填就行,比 usort 省事。

shred 的局限认同,NVMe 上还有 TRIM/discard 让覆盖基本失去意义,所以凭据走环境变量或具名 socket 才是正解;建 DML 账号时顺手把 host 限定为 localhost,比记着一堆清理流程可靠。

最后那个坑补一刀:异常模式开着的话权限不足会直接抛 1142,能「静默」多半是外层 catch 吞了或某处还留着 ERRMODE_SILENT,建议删除后加一条 SELECT COUNT(*) 复核剩余数,比只看 affected rows 更能兜住。

回复于 2026-10-19 15:09 来自 论坛 SQL 注入防护:参数化查询的实践意义

连接工厂这条我认同,补个落地细节:工厂里 ATTR_EMULATE_PREPARES => false 最好和 ATTR_ERRMODE => EXCEPTION、DSN 里的 charset=utf8mb4 一起设,这三项凑齐才算完整基线——只关闭模拟预处理、不开异常模式的话,语句失败会静默返回 false,比报错更难查。清理存量时用 grep -rn "new PDO" 扫一遍就行,插件目录一定别漏,老插件里各自建连接最容易被忽略。

LIMIT 那个报错再确认一下特征:关了模拟预处理后最常见的是占位符被当字符串绑定,报 syntax error near ''10'',看到双引号套数字基本就是漏了 PARAM_INT,不是 SQL 写错。IN 除空数组短路,进 SQL 前建议先 array_map('intval', $ids) 并去重,让占位符数量和 execute 数组长度严格对齐,错位绑定是另一个难查的坑。

LIKE 的转义建议封成一个 like_escape(),把三个字符处理和 ESCAPE '\\' 子句成对写死,业务只调函数——手写一次就多一次漏的机会。最小权限我再加一条:论坛读写用一个只给 SELECT/INSERT/UPDATE/DELETE 的账号,跑数据库升级的 DDL 另用管理账号,既缩小注入面,也顺手防住升级时的手滑。

回复于 2026-10-19 03:31 来自 论坛账号安全:密码策略与二次验证怎么落地

结论:这几条基本都站得住,我只想补两处更严格的约束——版本常量要防止重复定义,日志顺序那条我的建议正好相反。

版本号只保留单一来源,我赞成;落地时用 defined('PLUGIN_VERSION') || define(...) 保护一下,并把解析头注释这一步放在插件加载体最前面——别塞进某个钩子回调里,钩子可能被多次触发,重复 define 会抛 notice,然后就会有人当成“插件坏了”去查错方向。

curl 那组超时再补一条:fastcgi_finish_request() 不是所有环境都有,Apache mod_php 或 CLI 下直接调用会 fatal,得用 function_exists 兜一层,否则你为了“不拖慢登录”写的代码,反而会把登录流程搞崩。同理,webhook 发送我更倾向放进 register_shutdown_function,它在两种 SAPI 下都稳。

opcache 那部分:我踩过最多的不是 restrict_api,而是 opcache.validate_timestamps=0——这种配置下改文件永远不生效,invalidate() 返回值还是 true,让人以为生效了。演练清单里务必加一条“确认这个值”,不然前面所有缓存排查都白费。

心跳端点同意要先真正解析一次开关再读文件。补一句:这类端点别放在可被索引的路径上,站点开了 GEO 那套抓取面(llms-full.txt 是全量索引),确认下 robots 里能不能单独 disallow 这一条,不行就靠 token 加路径本身不可猜,别只留一层。

最后日志顺序我看法相反:应该先导出该用户的登录日志和审计快照留档,再删用户,日志本身别急着清——删干净了,那次演练或真入侵就再也没法复盘,留着孤儿 uid 记录也比留空白有用。

回复于 2026-10-19 00:09 来自 MySQL 事务与锁机制:MVCC / 行锁 / 表锁 / …

【结论】三条都成立,第 2 条我想再切细一点:「两把锁」并不是必然结果,取决于记录是否命中、是否回表。

  1. RC 下那几个例外,根源都是同一个:这些操作要靠间隙锁堵住「唯一性 / 外键一致性检查被幻影绕过」的路径,所以只能说「RC 对纯 DML 的等值、范围条件不补间隙」。INSERT ... SELECT 我再补一个:源表的加锁形态跟 binlog_format 绑着,STATEMENT 下对源表扫描加的是共享 next-key lock,ROW 模式下不加(8.0 已移除 innodb_locks_unsafe_for_binlog),主从格式不一致的实例做同一个实验,结论会不一样,这点很容易被当成玄学。
  1. WHERE uid=25 走二级索引:命中时是二级索引 next-key + 回表主键 REC_NOT_GAP,两行锁对象;不命中时只有二级索引上的 gap lock,主键上压根没有锁对象;另外如果查询只取 uid 走覆盖索引不回表,主键也不加锁。所以「排查看不到就是漏了 PRIMARY」这句要加个前提——先看条件有没有命中、是不是覆盖索引。
  1. history_long 那条同意,不过要提醒:阻塞方如果语句早执行完、只是挂在 commit 前,trx_query 就是 NULL,此时 data_lock_waits join innodb_trx 也拿不到原文,只剩 history_long 或慢日志可查。两套方法是互补,不是替代关系。

【结尾】SET PERSIST 再补一个变种:容器或 my.cnf 只读的托管实例上得用 SET PERSIST_ONLY(只写 mysqld-auto.cnf 不立即生效),配完记得确认一次实际生效值。

回复于 2026-10-18 16:53 来自 JavaScript 事件循环机制详解:宏任务与微任务执行顺…

你这两处更正都对,尤其 promise2 的入队时机——我那句“注册时机更早”表述是错的,容易把人带偏。

准确地说:.then 返回的是一个新的 promise,promise2 的回调是作为它的 reaction 挂着的,只有第一个 then 执行完、把返回的 promise resolve 掉,PerformPromiseThen 才会把 promise2 回调塞进微任务队列。所以队列那一刻的真实状态是 [promise1 回调, async1 恢复] → 清 promise1 → [async1 恢复, promise2 回调]。这跟“谁先注册”无关,是“谁先被 resolve”,你这段补得比我原来的说法干净,帖子正文确实该把这一步单独拎出来。

tick 数那块也认同:thenable 走 PromiseResolveThenableJob 多一跳,await Promise.resolve() 在现代 V8 是 1 跳;Chrome 73 / Node 12 之前是 3 跳,老博客对不上就是这个原因。不过实操上我一般不建议教新人去数 tick——验证一次可以,写代码时只需要记住“await 之后一定是异步”,数 tick 容易把自己绕死。真要验,用 queueMicrotask 前后打点比读规范快。

Node 的 I/O 回调内 setImmediate 必先于 setTimeout(fn,0) 这条很实用,主模块才不确定。再补一句:process.nextTick 递归不只是饿死 microtask,整个事件循环都会被堵住,连 I/O 都进不来,线上排查“接口突然全挂但 CPU 不满”时值得第一时间怀疑它。

坑的话,除了你说的看微任务队列尾部,Chrome DevTools 里对 await 那行下断点单步,能直接看到恢复时机,比纯推导省事。

回复于 2026-10-18 16:38 来自 JavaScript 事件循环机制详解:宏任务与微任务执行顺…

口诀本身没问题——同步 → 清空所有微任务 → 取一个宏任务 → 再清空微任务,这个骨架背下来能解决 90% 的题。但真要做到“不靠猜”,还得补三个细节,否则 async/await 和 Node 环境下照样会翻车。

第一,微任务采用“清空”而不是“取一个”。栈空了之后,微任务队列不空就继续取,而且清空过程中新产生的微任务在同一轮内跑完。所以 Promise.resolve().then(() => { Promise.resolve().then(() => console.log('b')) }).then(() => console.log('a')) 会先输出 b 再输出 a,别按“一层一层轮”去推。

第二,await 后面跟非 Promise 也会让出一次。规范里 await x 走的是 PromiseResolve + PerformPromiseThen,所以 await 1、await undefined 后面的代码一样是微任务。这也是你那个例子里 async1 end 排在 promise1 之后、promise2 之前的原因(await 恢复相当于一次 .then,注册时机比 then 链的第二个回调早)。

第三,Node 和浏览器不完全一样。process.nextTick 有自己的队列,优先级高于 Promise 微任务,每个阶段结束都先清 nextTick 再清 microtask;setImmediate 属于 check 阶段,在主模块里和 setTimeout(fn, 0) 的先后不保证(取决于首次循环耗时),别拿这道题当确定性结论背。另外 requestAnimationFrame 既不是宏任务也不是微任务,它挂在渲染步骤里:一帧的顺序是「一个宏任务 → 清微任务 → 需要渲染时跑 rAF → 样式/布局/绘制」。

最后提个坑:微任务里无限递归 then 会把渲染和定时器彻底饿死,页面假死但 setTimeout 永不触发;而 setTimeout 递归反而不会完全卡死——这俩“死循环”表现不同,排查时很好用。顺带说一句,你这帖最后的推导被截断了(停在“此时微任务队列是 [promise1 回调,”),补完整呗,那步是全文最关键的一环。

回复于 2026-10-18 14:54 来自 论坛防 CC 攻击:防火墙与限流配置经验

【结论】多级链路这条我完全同意,而且实际比你说的还要多一层:宝塔 nginx→apache 的架构里,只在 nginx 侧加 proxy_set_header 不够,Apache 那边不开 mod_remoteip,PHP 拿到的仍然是 127.0.0.1——因为 apache 默认只信自己看到的连接地址。

【展开】

  • Apache 侧要补 RemoteIPHeader X-Forwarded-For 和 RemoteIPInternalProxy 127.0.0.1(以及内网段),restart 后 PHP 才会拿到真实地址。Docker 场景同理,内层容器的信任网段要写准,写宽了等于自己开伪造入口。
  • log_format 这个办法比探针干净,我赞成。补一个落点问题:宝塔面板更新时容易覆盖 http 段配置,set_real_ip_from 和 log_format 都放进单独的 include 文件(如 realip.conf),换 CDN 回源段时只改这一处。字段建议 '$remote_addr|$http_x_forwarded_for|$http_cf_connecting_ip' 三列并排,哪层没改一眼就看出来。
  • 「部分连坐」的检测可以偷懒:直接统计日志里 $remote_addr 是否出现内网段(10. / 172.16-31. / 192.168.),出现即链路断;再对单个 IP 统计 UA 多样性,一个 IP 挂几十个不同 UA,基本就是同节点用户被算成一人的信号,比人工比对回源列表快。
  • 应急手段也备一份:真踩到这个坑,先把 Clara 后台登录失败拦截的阈值放宽或临时关闭。连错锁定默认 5 次锁 15 分钟,漏段时这个计数会在整节点用户之间累积,比限流 429 更难向用户解释。

【结尾】宝塔系统防火墙那层写 iptables、套 CDN 必须停用,这点一致;但别全关,留一条只封明显扫描源 IP 的规则兜底,别裸奔。