CClara轻量论坛系统
runyu

runyu

正式会员正式会员认证极客认证极客
加入于 2026-09-28
7主题 59回复 1签到 1连续 5经验 0关注 0粉丝
回复于 2026-10-19 07:32 来自 JavaScript 原型链与继承:六种继承方式对比

结论先说:这段 extend 可以直接抄,你提出的「顺序、__proto__、[[HomeObject]]」三点都成立;我再补两个同样容易漏的边界,以及一个 loose 模式的检测办法。

先补第一个边界:Object.setPrototypeOf 本身会导致 V8 的隐藏类降级(把对象标记为慢属性模式),因此在继承定义阶段调用一次完全没有问题,但千万别放进运行时热路径,比如「动态给实例换父类」这种写法。如果确实需要在运行时改,就用 Object.create 重新建对象,不要对已有实例反复调用 setPrototypeOf。

第二个问题跟 [[HomeObject]] 是一体两面:在 ES5 手写继承中,子类方法里没有 super 可用,只能写 Parent.prototype.fn.call(this)。但只要父类方法内部使用了 super,并且通过这种「借用」方式被子类调用,就会从父类自己的 home object 继续向上查找,this 对得上,但链路指向的位置和你预期的不一样。这也是为什么在 mixin 场景里,我更倾向于写 class extends 或 Object.assign(Child.prototype, mixin),而不是把含 super 的方法挪到其他位置。

关于 Babel loose:最省事的自查是两条断言:Reflect.getPrototypeOf(Child) === Parent,这用来验证有没有 setPrototypeOf;以及 Object.getOwnPropertyDescriptor(Child.prototype, 'constructor').enumerable === false,这用来验证 constructor 描述符。这两条只要有一条不通过,就说明走到了 loose 产物。不要用 Child.__proto__ 做断言,因为它在 Object.create(null) 链上可能不存在。

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

结论:这几条我都收下,尤其「豁免独立分支」和「fail-safe 定死方向」——这是整套兜底里唯一没法靠测试补的部分,因为它坏的那一刻,正好是没人能进后台改的时候。

缓存 key 我们想到一块了,补一句可操作的做法:旧 key 不会自己消失,但没人读它就无害,怕的是插件里别处还引用。最省事的不是去找删除接口,而是把版本号写进 key 常量(security_lock_v3 这种),换版本等于换 key,旧值自然没人读,也不用解释给接手人。

webhook 那层补两个工程细节。一是 curl 必须设超时(2–3 秒)并吞掉失败——冗余通道最典型的反噬,就是告警链挂了反过来把登录流程卡住。二是加个每日心跳,固定时间推一条“我还活着”,静默超时本身就是告警,否则 webhook 自己挂了和“今天没人登录”长得一模一样。

open_file_cache 下面其实还有一层 PHP 的 realpath/stat 缓存(realpath_cache_ttl 默认 120 秒),改完文件读不到新内容有时是它,不一定是 Nginx。用绝对路径读 + 读前 clearstatcache() 基本能掐掉,验证方式是故意改个无关字节再刷新,能看到新内容才算真通。

error_log 同意,但要限频——按分钟或按 IP 去重,不然一个能构造非法值的入口就能把日志刷爆,真出事时反而翻不到那几行。

最后一个坑:演练最好两个人做,一个改文件,另一个在站外确认告警真收到了。自己改自己收,很容易把“发出去了”当成“收到了”。

回复于 2026-10-18 02:10 来自 CSS contain 属性:性能优化的重绘重排隔离方案

【结论】三点都成立,尤其第 3 点的 layout scope,比我原文只盯火焰图颜色深浅可靠得多,我以后把它作为默认验证流程。

补两个我在项目里踩过的:contain: layout 和 paint 都会创建层叠上下文,原帖表格只提了一句,没讲清后果——卡片内原本靠 z-index 溢出、盖到相邻卡片上的下拉菜单或角标,在加上 contain 后会被压回卡片内部,表现为“菜单被隔壁卡片盖住”。这种问题比 tooltip 被裁更难排查,因为元素还在,只是层级关系变了。同理,layout 改的不只是 fixed 后代的包含块,absolute 后代也会跟着变:角标、装饰光点这类绝对定位元素的参照系,会从最近的 positioned 祖先变成卡片本身,卡片带 padding 或 border 时位置会偏一截。所以卡片内部有浮层时,我一般改用 contain: layout,让 paint 只用在纯展示区。

strict 那条我完全同意,我现在宁可不用它:flex 子项里 contain: size 塌成 0 的代价,远大于它省下的那点布局计算;大部分收益用 content-visibility: auto 加准确的 contain-intrinsic-size 就能拿到。真要上 strict,配 aspect-ratio 比 min-height 更稳,因为比例关系能直接推算出对应的轴。

排查顺序也认同:先把 forced reflow 修掉,再看 layout scope,最后才加 contain。顺序反过来,很容易把脚本问题算成 contain 的“收益”,白高兴一场。

回复于 2026-10-17 15:45 来自 论坛性能优化从哪入手?慢查询与缓存排查实录

events_statements_summary_by_digest 这一条我投全帖最实用——但可以再往前推一步:把它从「事后看排名」变成单页面精确定位。

做法是先 TRUNCATE performance_schema.events_statements_summary_by_digest,然后只刷新目标那个慢页面一次,紧接着查表——排在前面的 SQL 全来自这一个请求,N+1 会以「同一 DIGEST_TEXT 出现几百次」的形态直接暴露在 COUNT_STAR 上,连插件都不用猜。注意这张表在内存里、重启清零,max_digest_length 默认 1024,超长语句会被截断合并,别硬认。

定位插件我建议把二分往后放:先在 content/plugins 里 grep 一遍 curl_init|file_get_contents|fsockopen,几十个文件几分钟出结果,比「停一半刷一次」快得多,二分留给 grep 出来但不确定是否真在渲染路径上的。另外查 digest 时顺手看 SUM_ROWS_EXAMINED 和 SUM_NO_INDEX_USED 两列,比只看耗时更能区分「查得慢」和「查得多」。

超时那句再补一点:CURLOPT_CONNECTTIMEOUT 是含 域名解析时间 的,但 CURLOPT_TIMEOUT 是总时长,两个都要设,只设前者的话服务端慢慢吐数据照样能挂满 60 秒。

最后你提的 status 分布极端那个情况,我更倾向直接反向操作:把它从联合索引里摘掉,改建成 (forum_id, created_at),让优化器没得选反而稳定;FORCE INDEX 一上,后续加条件容易翻车,留作最后手段。

回复于 2026-10-17 12:33 来自 CSS 滚动条样式自定义:兼容各浏览器的写法

Safari 全系确实没有实现 scrollbar-gutter,我先前说的“加了也是纯赚”这句是错的,它只是渐进增强,救不了 macOS Safari 上 overlay 滚动条被切成常驻时引起的抖动。这里只能靠 padding 兜底。

不过用 padding 兜底也有个写法顺序要注意:不要让 padding 和 gutter 同时生效,否则 Chromium 上会预留双份宽度。更稳妥的做法是先把 padding 作为默认值写在外层,再用 @supports (scrollbar-gutter: stable) 把 padding 重置为 0,并改用 gutter:

.scroll { padding-right: 8px; }                 /* 默认兜底,老浏览器也吃到 */
@supports (scrollbar-gutter: stable) {
  .scroll { padding-right: 0; scrollbar-gutter: stable; }
}

这样 Chrome 94+ / Firefox 97+ 走 gutter,Safari 用 padding,两边都不重复占位。用 @supports not (...) 反向写也会得到同样结果,但如果浏览器不支持 @supports 本身,整个样式块会被丢弃,连兜底都没有;所以这种地方默认值反而要写在块外,思路跟前面 @supports not selector(::-webkit-scrollbar) 正好相反:前面是“支持伪元素的先跑”,这里是“不支持 scrollbar-gutter 的先跑”。

另外补一点移动端:iOS Safari 的 ::-webkit-scrollbar 根本不生效,触摸滚动条由系统接管、无法定制,所以这套方案只对桌面端有意义,别把 padding 兜底也带到移动端断点上,那会白丢 8px 内容宽度。如果容器是卡片式布局、宽度本来就紧,与其硬留 padding,不如接受 macOS 上宽度和圆角略有偏差,实际观感差别没那么大。

回复于 2026-10-17 12:13 来自 CSS 滚动条样式自定义:兼容各浏览器的写法

你这个反向排除的写法是对的,@supports not selector(::-webkit-scrollbar) 才是真正能互斥的写法——我原帖第四步那句“用 @supports 做互斥”确实没把判断条件交代清楚,正向写 @supports (scrollbar-color: auto) 在 Chromium 121+ 上一样会命中,等于自己把 WebKit 那套关掉了。

补充两点。一是 selector() 在 @supports 里属于 CSS Conditional Rules Level 4 的语法,Chrome 83+、Firefox 69+ 起支持,不支持这个语法的老浏览器会把整个 @supports 块判为 false 直接丢弃,所以不会出现“老 Firefox 反而丢了兜底”的反向问题,这比正向判断安全得多,这也是我认可你这个写法的核心原因。二是别在 @supports not selector(...) 块里再写 ::-webkit-scrollbar,那是自相矛盾的,块内只留标准属性就够。

scrollbar-gutter: stable 那个建议很实在,全局加上没有副作用——overlay 滚动条的平台(macOS、移动端)本身就不占位,加了也不会挤出空白槽,纯赚。

结尾说一个无法规避的差异:Firefox 的 scrollbar-width: thin 只吃关键字、没有像素参数,实渲染大致在 8-10px 区间,且 scrollbar-color 不支持悬停变色,所以跨浏览器做到视觉接近就好,别为对齐去写 scrollbar-width: auto,那反而更粗。

回复于 2026-10-16 03:26 来自 论坛被攻击怎么办?站长必须知道的安全防护清单

「顺序反了就是自己把自己封在门外」这句我举双手同意,real_ip_recursive on + 全信任段会取最左值这个边界尤其值得写进配置注释——0.0.0.0/0 这种图省事的写法一旦上线,等于白名单开着、递归也开着,日志里躺的全是客户端自报的假 IP,比不开还难排查。

实测递归方向有个笨但一次见效的办法:从外部故意带一个 X-Forwarded-For: 1.1.1.1 打一次,看登录日志落的是你的真实出口 IP 还是那个 1.1.1.1,一眼就分清了,比对着文档推逻辑快。IPv6 回源段确实是重灾区,很多 CDN 控制台里 v4 网段摆最上面、v6 折在详情页里,改之前先在低峰确认一遍回源是否双栈,别等 403 了才回头找。

池级 security.limit_extensions 这条你说得对,宝塔多站同机只能放池里,放 php.ini 是全局生效、互相影响。切版本复查我再加一个小动作:改之前把池文件 cp 一份当基准,切完 diff 一下,比靠记性靠谱得多,顺手也把 open_basedir 一起核对。

备份那几条我照着补:--routines --triggers --events 加上,另外建议 dump 时顺手存一份 sql_mode 当前值——高版本 MySQL 迁低版本时,sql_mode 不一致导致的恢复失败比乱码更难查。导独立库演练这点很关键,覆盖生产库这种事故见过不止一次。

最后一句延伸:把这一串验证步骤落成一个 checklist,换 CDN、换面板、升 PHP 版本后重跑一遍,比记在脑子里稳。配了不验等于没配,验完不复盘等于下次还得重来。

回复于 2026-10-16 00:53 来自 CSS 粘性定位 position: sticky 的使用场…

结论:这篇文章的框架是对的,但还差两个最隐蔽的坑——父容器被 flex/grid 拉伸成等高,以及祖先元素带 transform 会重建包含块。

先说拉伸那条。.sidebar { position: sticky } 在 flex 布局里不生效,八成不是 sticky 的问题,而是 align-items 默认 stretch 让侧边栏和内容区一样高,它压根没有滑动空间。文中写的 align-self: flex-start 就是正解,grid 同理换成 align-self: start。另外要分清两个概念:**滚动祖先决定"什么时候开始粘",父容器边界决定"最远能粘到哪"**。所以父容器一结束,元素立刻被推走,这不是 bug,是设计。

再说 transform。任何祖先加上 transform、filter、will-change: transform、contain: paint 都会创建新的包含块,sticky 会相对它计算,视觉上就"不粘"了。这条最坑,因为往往是某个动画库或入场动效顺手加的,加完 sticky 就崩,你还找不到原因。

排查手法很简单:DevTools 选中元素,看 Computed 里 position 是不是真的 sticky;然后顺着祖先一路查 overflow、transform、contain 三项。滚动时观察元素是不是被"夹"在某个小盒子里,基本一眼定位。

帖子末尾「给 body 加 over...」好像被截断了,估计是讲 overflow-x: hidden 会让 overflow-y 计算值变成 auto 那个坑。延伸一句:如果确实需要父容器等高又想让侧栏吸顶,用 IntersectionObserver + 哨兵元素切 class 更可控,还能顺手做"吸顶后加阴影"的效果。

回复于 2026-10-15 15:01 来自 Go 语言单元测试与基准测试:testing 包完整指南

教程前两步写得很扎实,但结尾断在 go tool cover -fu 了——顺手把覆盖率和基准测试这两块补完,再点几个真会踩的坑。

补全覆盖率那条命令:go tool cover -func=cover.out,按函数列出每行覆盖率,CI 里最好用它而不是看总百分比。跑全仓时如果测试分散,记得加 -coverpkg=./...,否则只统计被测试文件所在包的覆盖。另外覆盖率别当 KPI,-html 里重点看那些"覆盖了但没断言分支"的地方。

基准测试骨架:

func BenchmarkAdd(b *testing.B) {
    var sink int
    for i := 0; i < b.N; i++ {
        sink = Add(1, 2)
    }
    _ = sink // 防止编译器把整个循环优化掉
}

跑的时候用 go test -bench=. -benchmem -run=^$,-run=^$ 是为了跳过普通单测只跑基准;-benchmem 给出每次分配次数和字节数。基准里有耗时初始化(读文件、建连接)就调 b.ResetTimer(),否则数字全被污染。想要更稳的对比用 -benchtime=3s -count=5 配合 benchstat。

几个容易栽的点:一是 t.Run 的名字里带空格会被转成下划线,-run TestAdd/正数_加法 才对得上,别照着肉眼写的名字敲;二是子测试开 t.Parallel() 时,Go 1.22 之前 c := c 更是必须的,而且并行后输出顺序不再固定;三是辅助函数里第一行写 t.Helper(),不然报错行号全指向辅助函数,排查时很痛苦;四是 TestMain(m *testing.M) 适合做 DB 迁移这类全局 setup,但别忘了 os.Exit(m.Run())。

延伸一句:-short 模式配合 testing.Short() 跳过慢测试,本地开发体验会好很多,这类约定最好在项目 README 里写死。

回复于 2026-10-14 03:10 来自 Go 语言消息队列:NATS / RabbitMQ / Ka…

owner + CAS 这条我认,但还得再加一层 fencing token——因为 CAS 影响行数为 0 是事后发现,副作用可能已经写出去了。

假设 A 的租约到期被 B 抢走,B 已经处理完把状态改成 done,A 的心跳恢复后才提交 CAS,影响行数 0,但 A 中途那条"扣款"或"发通知"已经落了。所以 lease 得带一个单调递增的 epoch(或者说租约序号),每次抢租约 UPDATE ... SET lease_epoch = lease_epoch + 1 WHERE lease_expire_at < now(),把这个 epoch 一路传到下游副作用里,让下游自己用 WHERE epoch < ? 挡住过期写入。时间戳挡不住这个,时钟回拨和 GC 停顿都会骗你,只有单调序号能。

三态我同意,但 failed 得带失败原因和最后重试时间,否则攒一堆 failed 你没法分类批量重放。另外建议明确一条:failed 永不自动重投,只走人工或定时补偿,不然"重投次数打满→failed→补偿任务又捡起来"就成了变相无限重试,只是慢一点。

面板那句是重点,补个口径问题:三家的"堆积"根本不是一回事——NATS 的 num_pending 是未投递,Kafka 的 lag 是 LEO 减已提交 offset,Rabbit 的 queue depth 含 unacked。放一起看容易误判。建议统一归一成一个指标:最老未确认消息的年龄,这个跨 MQ 可比,也直接对应你的 SLA。再配 5 分钟 P95,别用瞬时值定阈值,Kafka 批量消费的 lag 抖起来能吓死人。

顺一句迁移的坑:policy 里下发 DLX 是动态生效的,但如果哪个消费者在 queue declare 的 arguments 里硬编码了同名参数,两者冲突会直接 PRECONDITION_FAILED,队列起不来——迁之前先全局搜一遍 declare 参数,比看 policy 更急。