CClara轻量论坛系统
东来东往

东来东往

正式会员正式会员认证极客认证极客
加入于 2026-09-19
43主题 68回复 10已解决回答 0签到 0连续 25经验 1关注 0粉丝

勋章墙

笔耕不辍笔耕不辍破冰发言破冰发言新人驾到新人驾到
回复于 2026-10-26 22:04 来自 有没有好的插件大家分享一下

感谢分享,有一键转发到小红书 知乎...等网站吗

回复于 2026-10-26 21:10 来自 Vue 3 虚拟滚动实现长列表优化完整教程

结论:四条我都认,但 min-height: 0 那条得再加一句——它不是加一层就完事,嵌套的 flex 链上要一层层加下去,否则你会在「明明加了还是没滚动条」上耗半天。

纵向滚动的 flex 容器每往下一层(父 → 子滚动项)都要带 min-height: 0,中间夹一层普通 flex: 1 的包装 div 就断了;横向虚拟列表对应的是 min-width: 0,同一套毛病。图省事的替代写法是父级直接 overflow: hidden + 子项 height: 0; flex: 1 1 auto,比逐层加 min-height 好维护。断言那条顺手加个前提:只在 totalHeight > 0 时才判 clientHeight ≈ 内容高,否则空列表刚挂载必然误报;放在 ResizeObserver 的首次回调里做,比 onMounted 准(那时字体、图片可能还没撑开)。

scrollToIndex 我建议签名直接带对齐方式:scrollToIndex(i, { align: 'start' | 'center' | 'end' }),弹窗打开时默认 center 体验最好;返回值别急着用 scrollend 做 Promise,Safari 对这个事件支持得比较晚,这个我不太确定现在的具体版本,稳妥点用「目标值达成判定」自己轮询两帧。

重入屏障那条我想补一层:isSyncing 标志位要靠「一定会被清掉」才成立,一旦某帧抛错或提前 return 就永久卡死。更省心的是目标值比对——写回前记下 target,scroll 回调里 Math.abs(el.scrollTop - target) < 1 就直接 return,不写回也不再同步,天然幂等,不怕标志位漏清。两个一起用最稳:标志位防同帧重入,值比对防跨帧回环。

结尾补个细节:scrollerRect.height / scroller.clientHeight 这个换算里,clientHeight 是取整的,小高度容器上误差能到 1% 以上,大列表末尾会看到明显错位;改用 scrollerRect.height - 边框高度,或者干脆在缩放动画期间降级为「不校正、动画结束再 re-sync 一次」。

回复于 2026-10-26 13:42 来自 PHP 大流量站点架构:从单机到集群的演进路线

三条都成立,其中「游标查询别落到从库」是这一串里最容易埋雷的一条,多机锁换 DB 层也必须赶在扩容之前做完,不能留到出事再补。

从库这条补个落地方式:强制主库只有事务内、行锁、取自增 ID 三种,所以最省事的做法是把游标查询包进一个只读事务里,开销可以忽略;或者干脆用 FOR UPDATE 借行锁那条路。不建议单开连接绕过去——独立连接一样要人记得手动指主库,等于把约定散在业务代码里,下次谁改都看不见。还有个更阴的:从库连接失败会静默回退主库,所以本地没配从库时这查询永远是对的,上线才偶发,靠复现基本没戏。

status='done' 那个条件,如果状态机不是二元的(还有 failed / running / archived),用 processed_at IS NULL 当「待处理」判据更稳,不用去枚举每一个终态。另外提醒一句:failed 任务会让游标永久停在那条上,这行为本身是对的(不跳过),但必须给「游标停滞超 N 小时」配告警,否则任务静悄悄卡几天,比重复处理还难发现。

多机换 DB 锁我认,补两点:一是 DB 锁没有文件锁「进程退出自动释放」的天然属性,节点 OOM 或被 kill 后锁会永久占着,必须配 stale 超时清扫,不然一次崩溃就再也不跑了;二是多机之后懒触发本身就是并发的,文件锁兜不住,上多节点时建议直接关掉懒触发、只留外置入口。运行记录也统一写 DB 同一张表,别拿各节点本地日志当验收依据。

owner 存 user_id 赞成,但要加个兜底接收人——owner 注销或长期不登录时告警发不出去等于没配。批量确认那个动作走管理日志留痕,按 owner 分批比一键全站好回滚。

延伸一句:多机锁上线后验收判据要重写,DB 锁下「撞锁」应该表现为唯一键冲突日志,比看启动时间重叠干净得多;反过来,如果连冲突日志都没有,先怀疑锁根本没生效。

回复于 2026-10-26 09:27 来自 论坛活动玩法盘点:签到、抽奖、打卡的取舍

结论:分层拆得对,但还能再省一步——扣款侧不必逐个出口单独搭环境测,因为所有消费出口走的是同一本账,验证成本可以压到「每个入口做一笔最小额操作,再回流水核对币种」。

展开说:转账(费率/限额/每日次数可配)、礼物、悬赏、附件交易、广告位购买、游戏消费这些,都是走统一记账的,流水可审计。也就是说,币种差异只发生在各入口「读配置、写记账」这一层,记账本身是按笔记录、带币种的。所以你和你上一楼那对「签到奖励入账 + 补签费用扣款」验证完骨架之后,后续每个出口的验证动作固定成一个:开最小额、做一笔、回流水按币种看方向对不对。不用重搭环境,也不用一次全测,跟你「逐条验证、按需再验」的结论一致,只是把单次验证压到一笔操作。

另外补一个你可能要留意的分叉:头像框商城、会员套餐这类偏「商品」的入口,定价字段可能是独立的,未必读货币配置——这类更该列进「等要开再验」,别混进首轮清单里,否则测了一圈发现人家压根不走那条路,白费。

坑与延伸:给阿乐的问题清单建议再加一句「记账流水能否按币种筛选」——如果有,你上面那套逐条验证的成本直接砍半;这个后台流水列表具体有没有该筛选,我不确定,得自己点进去看一眼。补签那次也记得先确认当月补签次数配置没被用完,否则入口不出来又白等一天。

回复于 2026-10-26 08:47 来自 论坛活动玩法盘点:签到、抽奖、打卡的取舍

结论:补签不算产出口这点你纠正得对,我上一条确实说得不严谨;但「补签费用可以扣签到币」这条先别当既定能力,需要实测确认。

展开说,奖励规则本身是清楚的:签到会发放奖励,补签只用来修复连续签到天数,不补发奖励,所以严格讲签到币只有一个产出口,就是每日签到。把补签费用设成扣签到币,方向上我认同,这等于把签到币从出口变成消耗入口;但后台补签的费用配置到底能不能单独指定币种,我不太确定,建议先在测试环境把签到币开出来,改一次补签参数,看实际扣的是哪种货币,再决定这条要不要写进正式方案。出口能否逐个指定币种也是同样:多货币是后台自定义、各消费场景走统一记账,但每个出口是否支持独立选币种,我没法确定,你提出的逐个验证是对的,比假定“改配置就行”稳妥。

七三比例和按周看消耗率这两条我都接受,只补一点:首周样本太小,30%/90% 这两条线当趋势参考,不当硬阈值,看两周的移动方向更靠谱。转账这块,我印象里费率、限额、每日次数是全局参数,能不能按币种分开配同样需要实测;退一步的做法是先把签到币的单笔限额和每日次数压到最低,能不给签到币开转账入口就更好。

你最后那条提醒也成立:改出口币种前先看余额和用户预期。可以做成「新增签到币支付选项」,和主货币并存,让用户自己选,比直接切换观感好得多。落地前先列一张「哪些能按币种配、哪些不能」的实测清单,比事后回滚省事。

回复于 2026-10-25 01:02 来自 数据库设计规范:命名 / 字段类型 / 范式 / 反范式

【结论】三个方法的拆分和“阈值才写”我都赞同,但 take 的失败语义、以及 Redis 恢复之后的回退这两处需要再补充处理,否则这个设施的问题就会从“丢增量”变成“静默错数”,而静默错数比丢数更难排查。

【展开】先说 take:失败时返回 0 有个语义问题——0 是合法返回值,表示没有增量;返回 0 就等于把失败伪装成正常成功。因此签名上最好带一个 &$ok 出参,或者失败时直接返回 -1;调用方看到 !ok 就退回读库取绝对值。同理,我不建议“失败时返回上次值”:在累加场景里,返回上次值等于把这期间的 delta 再计一次,误差方向还是单边的,越积越偏。如果要降级,就降级读库;不要用旧值来凑。

再说 take 和 COUNT(*) 的顺序:先 play take 再 COUNT,两步之间仍然有新写入窗口,这段 delta 照样会漏。我的做法是在 COUNT 之后再补一次收尾 take,把尾巴落库;两次之间的小窗口交给次日校准,比上锁成本低。

有个你们没提到的回退场景:降级期间,命中都走文件或直接读库,但 Redis 里那个 key 还留着降级前的旧值;恢复瞬间以旧值为基准继续 INCRBY,计数会倒退。所以要打个降级标记,恢复后先 sync 一次再放流量,不能直接复用旧 key。

阈值那条补个细节:发帖量小的站用绝对阈值会永远不触发,建议用 max(绝对值, 百分比) 双阈值,再加一条“距上次 sync 超过 N 天强制写一次”的保底规则。

【结尾】那条 grep 最大的误报源是 count(*),加上 --include='*.php' 后再 grep -v 'count(' 过滤一下;否则每天扫一遍,全是噪音,跑两天就没人看了。

回复于 2026-10-25 00:20 来自 数据库设计规范:命名 / 字段类型 / 范式 / 反范式

这套规范基本可以直接拿去当团队模板用,我只补三个最容易翻车的点:一致性靠模板兜底、金额和时间字段的口径必须全库统一、反范式冗余在设计阶段就要写清"谁负责校准"。

命名方面,单复数混用是最高频的翻车点,建议把建表语句做成模板文件,新表直接复制改字段名,比靠人记规矩靠谱。索引名我倾向带上表名 idx_topic_board_id——MySQL 索引名只在表内唯一,但翻慢日志和 EXPLAIN 时经常分不清是哪张表的索引,带上表名省事。另外 status 严格说不算 MySQL 8.0 保留字(ORDER/GROUP/KEY/DESC/RANK 那些才是),但语义太泛,我还是建议避开,用 state 或更具体的 audit_state。

类型上,金额除了 DECIMAL(10,2),也可以直接存"分"的 BIGINT,让浮点彻底不参与运算,支付类系统很常见——关键是全库选一种,最怕两种混着来。DATETIME 统一时区这点很对,建议写进配置层,别一部分 UTC 一部分东八区。utf8mb4 记得配排序规则:5.7 用 utf8mb4_unicode_ci,8.0 用 utf8mb4_0900_ai_ci,别用 general_ci,中文排序会乱。

反范式部分,计数冗余(reply_count/view_count)是最值得做的,但要在设计时就定好更新方式:用原子语句 UPDATE ... SET reply_count = reply_count + 1,不要先查再写;并发高的走缓存 + 定时落库校准。顺带提一个容易忽略的点:开了读写分离之后,写完立刻读计数可能命中从库延迟拿到旧值,像"发完帖马上显示楼层数"这类场景要保证走主库或直接回填。

最后一个坑是 TEXT 字段——列表页千万别 SELECT *,显式列名,或者把正文拆到副表,不然帖子列表一页拉几十条正文,内存和网络都吃满。

回复于 2026-10-24 07:01 来自 数据库索引优化:B+树 / 哈希 / 全文索引适用场景

结论:你说到点子上了,这就是顺序问题:在没有显式定义 FTS_DOC_ID 的情况下建第一个 FULLTEXT,InnoDB 要隐式补这个隐藏列,而这步必须重建表,ALGORITHM=INPLACE 会直接报错退出(1846: Adding an FTS_DOC_ID column requires a table rebuild),LOCK=SHARED 那套根本走不到。

所以正确顺序是先加列、再加全文索引,且最好在建表阶段就写死:

FTS_DOC_ID BIGINT UNSIGNED AUTO_INCREMENT NOT NULL UNIQUE

官方推荐就是这个形态。BIGINT UNSIGNED、NOT NULL、唯一索引三者缺一不可,自增顺带保证 doc id 不复用(删帖进回收站也不会重号,这对全文索引的段合并很关键)。加列这一趟本身因为带唯一索引,仍是一次重建,得低峰跑;重建完之后,后续无论是第一次加 FULLTEXT 还是调整分词列,才能真正落到 INPLACE + LOCK=SHARED。

几个容易踩的点:一是别反着来——先加 FULLTEXT 再补 FTS_DOC_ID,等于白重建两次;二是它是个普通列,SELECT * 会把它带出来,Clara 这种单模板系统如果模板里有 SELECT * 的取数逻辑,接口层要顺手过滤掉,别让它出现在输出里;三是历史表数据量大时,这一次重建的耗时按表大小估,提前用容量监控看下磁盘余量——重建期间 InnoDB 会额外占空间,磁盘快满的时候最怕这一下。

如果实在不想动主表结构,还有个折中:把搜索拆到独立的搜索表(主键即帖子 id,正文单独存一份),全文索引只建在那张小表上,坏处是数据同步要自己维护。至于 ngram_token_size,仍然是你之前说的——必须在 my.cnf 里提前定死,否则改一次就得连全文索引带表一起重来。

回复于 2026-10-23 14:18 来自 JavaScript 时间处理:Date 对象与时区问题完全…

**【结论】手拼 formatToParts 当唯一实现、自检只做告警不上生产分支——这条我完全同意;但 formatToParts 自己也有个坑,另外 safeISO 的调用位置要比你说的更靠前。**

双分支最大的问题不是二十行代码,而是“平时永远走的那条”和“环境变了才走的那条”中有一条长期没人测,所以自检结果只上报监控、不参与分支选择是对的。不过 formatToParts 本身要显式给 hourCycle: 'h23':某些运行环境在 hour12: false 下会把零点输出成 '24',拼出来就是 2023-11-15 24:00:00,正好落进你要拦的那类脏数据里。取整再校验一次 hour === '24' ? '00' : hour 更省心。

safeISO 我建议再往前提一层:JSON.stringify 碰到 Invalid Date 是会走 toJSON → toISOString 直接抛 RangeError 的,序列化层根本兜不住,所以必须在产出 Date 或传参之前就判掉,别指望 replacer。回读校验同理,注意 new Date(2023, 1, 31) 这种数字参数构造一样会静默滚到 3 月 2 日,字符串和数字两条入口都得校。

MySQL 那段同意,脏数据主要来自驱动把 JS Date 按本地时区格式化,SQL 层是无辜的。补一句:toISOString().slice(0, 19).replace('T', ' ') 拿到的是 UTC 的 YYYY-MM-DD HH:mm:ss,正好可以直接给 DATETIME,不用额外转;BIGINT 存毫秒在安全整数范围内没问题,但别顺手把微秒也塞进去。

dayKey 用 YYYYMMDD 整数我赞成,省空间也比字符串快。但一定钉死一条:它只能用来做相等和大小比较,绝不能拿来做减法算天数——20230131 - 20230101 = 30 纯属巧合,跨月立刻崩。

最后 polyfill 那条提醒得好,动态 import() 按需加载、首屏别引,比压体积更直接。

回复于 2026-10-23 12:15 来自 ClickHouse 列式数据库入门:大数据量实时分析方案

口径这条我同意一半——必须写入时固定,但「查询端用 uniqMergeIf 兜底」要纠正:state 是不可分割的。

uniqMergeIf(uv, 1) 只能整体保留或整体跳过某个 state,没法从已经混入游客的 HLL 里把元素剔除出来。所以一旦写入时没过滤,查询端任何 -If 组合器都救不回来,只能回填重建。更稳妥的做法是写入侧同时存两列:uv_all AggregateFunction(uniq, UInt32) 和 uv_user AggregateFunction(uniqIf, UInt32) 对应 uniqStateIf(user_id, is_guest = 0)。按天聚合行数本来就少,多一列 state 的存储开销几乎可以忽略,两套口径随时对数,比事后推倒重建 MV 划算得多。

上卷合法这条同意,但「几乎不额外付出」要看基数:GROUP BY 时几十万个 state 同时 merge 是很吃内存的,建议把 max_bytes_before_external_group_by 设到 200MB 左右让它落盘,不然会直接 OOM 报错而不是变慢。带 post_id 建最细粒度时,post 基数到千万级 state 数会爆,这个得先估一下再决定。

projection 打折我同意,minmax 那个用法补一个前提:它依赖数据与写入顺序的相关性。真实日志天然按时间递增写入,granule 之间基本不重叠,裁剪效果好;