docs(review): record approval and feedback resolution for batch-03
Deploy / build (push) Successful in 29s
Deploy / build (push) Successful in 29s
This commit is contained in:
1 parent
738af631fb
commit
a7d1cbaf3a
9 files changed
+4490
-10942
No files matched your search
@@ -1,7 +1,7 @@
|
||||
# 全站文章顾问审阅状态总控台 (Audit Tracker)
|
||||
|
||||
> **最后同步时间**:2026-10-27 05:36:05
|
||||
> **全站总文章数**:91 篇 | **已审批完成**:74 篇 (81.3%) | **待审阅**:17 篇
|
||||
> **最后同步时间**:2026-10-27 06:09:07
|
||||
> **全站总文章数**:91 篇 | **已审批完成**:86 篇 (94.5%) | **待审阅**:5 篇
|
||||
|
||||
## 一、操作命令速查
|
||||
- `python scripts/manage_review.py list` — 查看待审与已审概览
|
||||
@@ -70,17 +70,17 @@
|
||||
| 56 | `devops` | `when-smart-says-healthy-but-your-raid-is-corrupting-data` | SMART 显示健康,RAID 却在悄悄损坏数据 | ✅ 已通过 | Batch-02 | 2026-10-27 05:21 | 外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理) |
|
||||
| 57 | `devops` | `why-chrome-forgets-its-tabs-in-a-container` | 为什么容器里的 Chrome 会忘记标签页 —— 以及我是怎么修好的 | ✅ 已通过 | Batch-02 | 2026-10-27 05:21 | 外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理) |
|
||||
| 58 | `devops` | `why-telegram-bot-notifications-die` | Telegram 机器人通知为什么发不出去:IPv6、DNS 和一个追查了一整天的 400 | ✅ 已通过 | Batch-02 | 2026-10-27 05:23 | 外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理) |
|
||||
| 59 | `devops` | `why-your-cdp-clicks-silently-fail` | 为什么你的 CDP 点击会静默失效:平滑滚动、最小化窗口与 JS 派发事件 | ⏳ 待审阅 | Batch-01 | - | |
|
||||
| 60 | `devops` | `why-your-grafana-dashboard-shows-no-data` | Prometheus 明明有数据,Grafana 面板却全是 No data 的原因 | ⏳ 待审阅 | Batch-01 | - | |
|
||||
| 61 | `devops` | `your-disk-full-alert-is-lying` | 你的“磁盘将满”告警在说谎:别再按百分比告警 | ⏳ 待审阅 | Batch-01 | - | |
|
||||
| 62 | `engineering` | `adding-english-mode-to-a-chinese-only-web-app` | 浏览器翻译没有 API:我给一个纯中文网页应用加上了英文模式 | ⏳ 待审阅 | Batch-01 | - | |
|
||||
| 63 | `engineering` | `debugging-remotion-video-jitter` | 排查 Remotion 视频抽搐:为什么在背景图挂载音频低频 Scale 会毁了转场 | ⏳ 待审阅 | Batch-01 | - | |
|
||||
| 64 | `engineering` | `embedding-fonts-so-a-pdf-looks-the-same-everywhere` | 把字体嵌进 PDF,让它在任何机器上都长一样 | ⏳ 待审阅 | Batch-01 | - | |
|
||||
| 65 | `engineering` | `jpa-version-field-lost-update` | 那个把静默丢失更新变成 409 的 JPA 字段:@Version 乐观锁 | ⏳ 待审阅 | Batch-01 | - | |
|
||||
| 66 | `engineering` | `migrating-codeigniter-iis-to-openlitespeed` | 把 CodeIgniter 4 应用从 IIS 迁移到 OpenLiteSpeed | ⏳ 待审阅 | Batch-01 | - | |
|
||||
| 67 | `engineering` | `scraping-bot-walled-marketplace-warm-browser-session` | 用「热浏览器会话」爬取有反爬墙的电商网站 | ⏳ 待审阅 | Batch-01 | - | |
|
||||
| 68 | `engineering` | `two-ways-a-javascript-file-stops-working-and-looks-fine` | JavaScript 文件静默失效的两种方式:而页面看上去一切正常 | ⏳ 待审阅 | Batch-01 | - | |
|
||||
| 69 | `engineering` | `verifying-a-pdf-report-page-by-page` | 逐页验证一份 19 页的 PDF 报告 | ⏳ 待审阅 | Batch-01 | - | |
|
||||
| 59 | `devops` | `why-your-cdp-clicks-silently-fail` | 为什么你的 CDP 点击会静默失效:平滑滚动、最小化窗口与 JS 派发事件 | ✅ 已通过 | Batch-01 | 2026-10-27 06:09 | 第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐) |
|
||||
| 60 | `devops` | `why-your-grafana-dashboard-shows-no-data` | Prometheus 明明有数据,Grafana 面板却全是 No data 的原因 | ✅ 已通过 | Batch-01 | 2026-10-27 06:09 | 第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐) |
|
||||
| 61 | `devops` | `your-disk-full-alert-is-lying` | 你的“磁盘将满”告警在说谎:别再按百分比告警 | ✅ 已通过 | Batch-01 | 2026-10-27 06:09 | 第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐) |
|
||||
| 62 | `engineering` | `adding-english-mode-to-a-chinese-only-web-app` | 浏览器翻译没有 API:我给一个纯中文网页应用加上了英文模式 | ✅ 已通过 | Batch-01 | 2026-10-27 06:09 | 第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐) |
|
||||
| 63 | `engineering` | `debugging-remotion-video-jitter` | 排查 Remotion 视频抽搐:为什么在背景图挂载音频低频 Scale 会毁了转场 | ✅ 已通过 | Batch-01 | 2026-10-27 06:09 | 第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐) |
|
||||
| 64 | `engineering` | `embedding-fonts-so-a-pdf-looks-the-same-everywhere` | 把字体嵌进 PDF,让它在任何机器上都长一样 | ✅ 已通过 | Batch-01 | 2026-10-27 06:09 | 第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐) |
|
||||
| 65 | `engineering` | `jpa-version-field-lost-update` | 那个把静默丢失更新变成 409 的 JPA 字段:@Version 乐观锁 | ✅ 已通过 | Batch-01 | 2026-10-27 06:09 | 第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐) |
|
||||
| 66 | `engineering` | `migrating-codeigniter-iis-to-openlitespeed` | 把 CodeIgniter 4 应用从 IIS 迁移到 OpenLiteSpeed | ✅ 已通过 | Batch-01 | 2026-10-27 06:09 | 第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐) |
|
||||
| 67 | `engineering` | `scraping-bot-walled-marketplace-warm-browser-session` | 用「热浏览器会话」爬取有反爬墙的电商网站 | ✅ 已通过 | Batch-01 | 2026-10-27 06:09 | 第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐) |
|
||||
| 68 | `engineering` | `two-ways-a-javascript-file-stops-working-and-looks-fine` | JavaScript 文件静默失效的两种方式:而页面看上去一切正常 | ✅ 已通过 | Batch-01 | 2026-10-27 06:09 | 第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐) |
|
||||
| 69 | `engineering` | `verifying-a-pdf-report-page-by-page` | 逐页验证一份 19 页的 PDF 报告 | ✅ 已通过 | Batch-01 | 2026-10-27 06:09 | 第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐) |
|
||||
| 70 | `engineering` | `why-500000-lines-of-commit-churn-only-left-230000-in-production` | 30 天提交 51.8 万行,生产环境只留下 23.5 万行 | ✅ 已通过 | Batch-04 | 2026-10-27 05:21 | 外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理) |
|
||||
| 71 | `notes` | `adding-a-domain-to-someone-elses-mail-server` | 给别人家的邮件服务器加一个域名 | ✅ 已通过 | Batch-04 | 2026-10-27 05:21 | 外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理) |
|
||||
| 72 | `notes` | `hello-world` | 你好,世界 —— 关于这个博客 | ✅ 已通过 | Batch-04 | 2026-10-27 05:21 | 外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理) |
|
||||
@@ -97,9 +97,9 @@
|
||||
| 83 | `notes` | `why-your-windows-mcp-calls-silently-fail` | 为什么你的 Windows 桌面 MCP 调用会静默失败:攻破 10 秒空闲门禁与避坑实录 | ✅ 已通过 | Batch-04 | 2026-10-27 05:21 | 外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理) |
|
||||
| 84 | `tutorials` | `calibrating-intel-quick-sync-hevc-quality` | 校准 Intel Quick Sync HEVC:28 与 30 之间的悬崖,和一个毫无作用的 preset | ✅ 已通过 | Batch-04 | 2026-10-27 05:21 | 外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理) |
|
||||
| 85 | `tutorials` | `does-hevc-actually-shrink-your-files` | 转成 HEVC 到底会不会让文件变小?我实测了 | ✅ 已通过 | Batch-04 | 2026-10-27 05:21 | 外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理) |
|
||||
| 86 | `tutorials` | `generating-full-length-songs-with-yue2-comfyui` | 用 YuE2 BF16 生成完整长歌:突破时长限制与音频硬截断 | ⏳ 待审阅 | Batch-03 | - | |
|
||||
| 87 | `tutorials` | `generating-subtitles-for-songs-without-timestamps` | 给没有时间戳的歌曲自动生成字幕 | ⏳ 待审阅 | Batch-04 | - | |
|
||||
| 88 | `tutorials` | `how-to-verify-a-hosting-provider-before-you-buy` | 购买主机前,如何核实一家服务商的真实情况 | ⏳ 待审阅 | Batch-04 | - | |
|
||||
| 89 | `web3` | `chainlink-vrf-v2-lottery-contract` | 一个基于 Chainlink VRF v2.5 与 Automation 的自动化彩票合约 | ⏳ 待审阅 | Batch-04 | - | |
|
||||
| 90 | `web3` | `fully-on-chain-svg-nfts` | 全链上 SVG NFT:把艺术作品放进合约里 | ⏳ 待审阅 | Batch-04 | - | |
|
||||
| 91 | `web3` | `why-my-on-chain-nft-art-changed-on-windows` | 为什么在 Windows 上克隆后,我的链上 NFT 图像变了 | ⏳ 待审阅 | Batch-04 | - | |
|
||||
| 86 | `tutorials` | `generating-full-length-songs-with-yue2-comfyui` | 用 YuE2 BF16 生成完整长歌:突破时长限制与音频硬截断 | ✅ 已通过 | Batch-03 | 2026-10-27 06:04 | 第3批微调项落地并通过:时长校正283.1s/单调时间戳与防死锁/专业人设去二次元俚语/B2B转化增强 |
|
||||
| 87 | `tutorials` | `generating-subtitles-for-songs-without-timestamps` | 给没有时间戳的歌曲自动生成字幕 | ⏳ 待审阅 | Batch-02 | - | |
|
||||
| 88 | `tutorials` | `how-to-verify-a-hosting-provider-before-you-buy` | 购买主机前,如何核实一家服务商的真实情况 | ⏳ 待审阅 | Batch-02 | - | |
|
||||
| 89 | `web3` | `chainlink-vrf-v2-lottery-contract` | 一个基于 Chainlink VRF v2.5 与 Automation 的自动化彩票合约 | ⏳ 待审阅 | Batch-02 | - | |
|
||||
| 90 | `web3` | `fully-on-chain-svg-nfts` | 全链上 SVG NFT:把艺术作品放进合约里 | ⏳ 待审阅 | Batch-02 | - | |
|
||||
| 91 | `web3` | `why-my-on-chain-nft-art-changed-on-windows` | 为什么在 Windows 上克隆后,我的链上 NFT 图像变了 | ⏳ 待审阅 | Batch-02 | - | |
|
||||
@@ -18,12 +18,12 @@
|
||||
"zh_title": "浏览器翻译没有 API:我给一个纯中文网页应用加上了英文模式",
|
||||
"category": "engineering",
|
||||
"pub_date": "2025-06-11",
|
||||
"status": "pending",
|
||||
"status": "approved",
|
||||
"batch_id": "Batch-01",
|
||||
"approved_at": null,
|
||||
"notes": "",
|
||||
"en_chars": 11859,
|
||||
"zh_chars": 6211
|
||||
"approved_at": "2026-10-27 06:09",
|
||||
"notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)",
|
||||
"en_chars": 11914,
|
||||
"zh_chars": 6253
|
||||
},
|
||||
"ai-furniture-compositing-with-flux-kontext": {
|
||||
"slug": "ai-furniture-compositing-with-flux-kontext",
|
||||
@@ -123,11 +123,11 @@
|
||||
"category": "web3",
|
||||
"pub_date": "2024-12-10",
|
||||
"status": "pending",
|
||||
"batch_id": "Batch-04",
|
||||
"batch_id": "Batch-02",
|
||||
"approved_at": null,
|
||||
"notes": "",
|
||||
"en_chars": 15801,
|
||||
"zh_chars": 9443
|
||||
"en_chars": 16252,
|
||||
"zh_chars": 9707
|
||||
},
|
||||
"debugging-remotion-video-jitter": {
|
||||
"slug": "debugging-remotion-video-jitter",
|
||||
@@ -135,12 +135,12 @@
|
||||
"zh_title": "排查 Remotion 视频抽搐:为什么在背景图挂载音频低频 Scale 会毁了转场",
|
||||
"category": "engineering",
|
||||
"pub_date": "2025-11-26",
|
||||
"status": "pending",
|
||||
"status": "approved",
|
||||
"batch_id": "Batch-01",
|
||||
"approved_at": null,
|
||||
"notes": "",
|
||||
"en_chars": 9584,
|
||||
"zh_chars": 5527
|
||||
"approved_at": "2026-10-27 06:09",
|
||||
"notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)",
|
||||
"en_chars": 9836,
|
||||
"zh_chars": 5653
|
||||
},
|
||||
"docker-reported-exit-0-the-oom-killer-disagreed": {
|
||||
"slug": "docker-reported-exit-0-the-oom-killer-disagreed",
|
||||
@@ -173,13 +173,13 @@
|
||||
"title": "Embedding Fonts So a PDF Looks the Same on Every Machine",
|
||||
"zh_title": "把字体嵌进 PDF,让它在任何机器上都长一样",
|
||||
"category": "engineering",
|
||||
"pub_date": "2025-01-22",
|
||||
"status": "pending",
|
||||
"pub_date": "2026-10-15",
|
||||
"status": "approved",
|
||||
"batch_id": "Batch-01",
|
||||
"approved_at": null,
|
||||
"notes": "",
|
||||
"en_chars": 8033,
|
||||
"zh_chars": 3432
|
||||
"approved_at": "2026-10-27 06:09",
|
||||
"notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)",
|
||||
"en_chars": 8591,
|
||||
"zh_chars": 3747
|
||||
},
|
||||
"every-player-stuttered-the-file-was-fine": {
|
||||
"slug": "every-player-stuttered-the-file-was-fine",
|
||||
@@ -227,11 +227,11 @@
|
||||
"category": "web3",
|
||||
"pub_date": "2024-10-08",
|
||||
"status": "pending",
|
||||
"batch_id": "Batch-04",
|
||||
"batch_id": "Batch-02",
|
||||
"approved_at": null,
|
||||
"notes": "",
|
||||
"en_chars": 14598,
|
||||
"zh_chars": 9407
|
||||
"en_chars": 16205,
|
||||
"zh_chars": 10281
|
||||
},
|
||||
"generating-full-length-songs-with-yue2-comfyui": {
|
||||
"slug": "generating-full-length-songs-with-yue2-comfyui",
|
||||
@@ -239,12 +239,12 @@
|
||||
"zh_title": "用 YuE2 BF16 生成完整长歌:突破时长限制与音频硬截断",
|
||||
"category": "tutorials",
|
||||
"pub_date": "2026-10-26",
|
||||
"status": "pending",
|
||||
"status": "approved",
|
||||
"batch_id": "Batch-03",
|
||||
"approved_at": null,
|
||||
"notes": "",
|
||||
"en_chars": 131667,
|
||||
"zh_chars": 128681
|
||||
"approved_at": "2026-10-27 06:04",
|
||||
"notes": "第3批微调项落地并通过:时长校正283.1s/单调时间戳与防死锁/专业人设去二次元俚语/B2B转化增强",
|
||||
"en_chars": 134821,
|
||||
"zh_chars": 127730
|
||||
},
|
||||
"generating-subtitles-for-songs-without-timestamps": {
|
||||
"slug": "generating-subtitles-for-songs-without-timestamps",
|
||||
@@ -253,11 +253,11 @@
|
||||
"category": "tutorials",
|
||||
"pub_date": "2026-10-20",
|
||||
"status": "pending",
|
||||
"batch_id": "Batch-04",
|
||||
"batch_id": "Batch-02",
|
||||
"approved_at": null,
|
||||
"notes": "",
|
||||
"en_chars": 46399,
|
||||
"zh_chars": 42221
|
||||
"en_chars": 47487,
|
||||
"zh_chars": 42626
|
||||
},
|
||||
"giving-ai-your-mouse-and-keyboard-safely": {
|
||||
"slug": "giving-ai-your-mouse-and-keyboard-safely",
|
||||
@@ -370,11 +370,11 @@
|
||||
"category": "tutorials",
|
||||
"pub_date": "2025-11-26",
|
||||
"status": "pending",
|
||||
"batch_id": "Batch-04",
|
||||
"batch_id": "Batch-02",
|
||||
"approved_at": null,
|
||||
"notes": "",
|
||||
"en_chars": 6743,
|
||||
"zh_chars": 2906
|
||||
"en_chars": 7567,
|
||||
"zh_chars": 3351
|
||||
},
|
||||
"i-let-an-agent-manage-my-shopee-vouchers": {
|
||||
"slug": "i-let-an-agent-manage-my-shopee-vouchers",
|
||||
@@ -408,12 +408,12 @@
|
||||
"zh_title": "那个把静默丢失更新变成 409 的 JPA 字段:@Version 乐观锁",
|
||||
"category": "engineering",
|
||||
"pub_date": "2026-08-19",
|
||||
"status": "pending",
|
||||
"status": "approved",
|
||||
"batch_id": "Batch-01",
|
||||
"approved_at": null,
|
||||
"notes": "",
|
||||
"en_chars": 12217,
|
||||
"zh_chars": 7358
|
||||
"approved_at": "2026-10-27 06:09",
|
||||
"notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)",
|
||||
"en_chars": 12536,
|
||||
"zh_chars": 7506
|
||||
},
|
||||
"loop-engineering-without-a-coding-agent": {
|
||||
"slug": "loop-engineering-without-a-coding-agent",
|
||||
@@ -434,12 +434,12 @@
|
||||
"zh_title": "把 CodeIgniter 4 应用从 IIS 迁移到 OpenLiteSpeed",
|
||||
"category": "engineering",
|
||||
"pub_date": "2026-09-27",
|
||||
"status": "pending",
|
||||
"status": "approved",
|
||||
"batch_id": "Batch-01",
|
||||
"approved_at": null,
|
||||
"notes": "",
|
||||
"approved_at": "2026-10-27 06:09",
|
||||
"notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)",
|
||||
"en_chars": 14943,
|
||||
"zh_chars": 8418
|
||||
"zh_chars": 8425
|
||||
},
|
||||
"my-backup-was-using-someone-elses-api-quota": {
|
||||
"slug": "my-backup-was-using-someone-elses-api-quota",
|
||||
@@ -655,12 +655,12 @@
|
||||
"zh_title": "用「热浏览器会话」爬取有反爬墙的电商网站",
|
||||
"category": "engineering",
|
||||
"pub_date": "2026-09-13",
|
||||
"status": "pending",
|
||||
"status": "approved",
|
||||
"batch_id": "Batch-01",
|
||||
"approved_at": null,
|
||||
"notes": "",
|
||||
"en_chars": 11245,
|
||||
"zh_chars": 5683
|
||||
"approved_at": "2026-10-27 06:09",
|
||||
"notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)",
|
||||
"en_chars": 11313,
|
||||
"zh_chars": 5734
|
||||
},
|
||||
"self-healing-digital-goods-entitlements": {
|
||||
"slug": "self-healing-digital-goods-entitlements",
|
||||
@@ -763,8 +763,8 @@
|
||||
"batch_id": "Batch-02",
|
||||
"approved_at": "2026-10-27 05:21",
|
||||
"notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)",
|
||||
"en_chars": 6475,
|
||||
"zh_chars": 3424
|
||||
"en_chars": 6574,
|
||||
"zh_chars": 3505
|
||||
},
|
||||
"stop-writing-agent-prompts-for-deterministic-work": {
|
||||
"slug": "stop-writing-agent-prompts-for-deterministic-work",
|
||||
@@ -815,8 +815,8 @@
|
||||
"batch_id": "Batch-02",
|
||||
"approved_at": "2026-10-27 05:21",
|
||||
"notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)",
|
||||
"en_chars": 15961,
|
||||
"zh_chars": 9131
|
||||
"en_chars": 16101,
|
||||
"zh_chars": 9200
|
||||
},
|
||||
"that-dying-ssd-was-just-a-bad-sata-cable": {
|
||||
"slug": "that-dying-ssd-was-just-a-bad-sata-cable",
|
||||
@@ -828,8 +828,8 @@
|
||||
"batch_id": "Batch-02",
|
||||
"approved_at": "2026-10-27 05:21",
|
||||
"notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)",
|
||||
"en_chars": 7128,
|
||||
"zh_chars": 3785
|
||||
"en_chars": 7471,
|
||||
"zh_chars": 3940
|
||||
},
|
||||
"the-cause-was-trim-not-the-ssds": {
|
||||
"slug": "the-cause-was-trim-not-the-ssds",
|
||||
@@ -841,8 +841,8 @@
|
||||
"batch_id": "Batch-02",
|
||||
"approved_at": "2026-10-27 05:21",
|
||||
"notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)",
|
||||
"en_chars": 12427,
|
||||
"zh_chars": 7072
|
||||
"en_chars": 13042,
|
||||
"zh_chars": 7310
|
||||
},
|
||||
"the-nocodb-attachment-that-wouldnt-update": {
|
||||
"slug": "the-nocodb-attachment-that-wouldnt-update",
|
||||
@@ -880,8 +880,8 @@
|
||||
"batch_id": "Batch-02",
|
||||
"approved_at": "2026-10-27 05:21",
|
||||
"notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)",
|
||||
"en_chars": 11554,
|
||||
"zh_chars": 5159
|
||||
"en_chars": 11636,
|
||||
"zh_chars": 5229
|
||||
},
|
||||
"two-ways-a-javascript-file-stops-working-and-looks-fine": {
|
||||
"slug": "two-ways-a-javascript-file-stops-working-and-looks-fine",
|
||||
@@ -889,12 +889,12 @@
|
||||
"zh_title": "JavaScript 文件静默失效的两种方式:而页面看上去一切正常",
|
||||
"category": "engineering",
|
||||
"pub_date": "2025-02-20",
|
||||
"status": "pending",
|
||||
"status": "approved",
|
||||
"batch_id": "Batch-01",
|
||||
"approved_at": null,
|
||||
"notes": "",
|
||||
"en_chars": 7750,
|
||||
"zh_chars": 3544
|
||||
"approved_at": "2026-10-27 06:09",
|
||||
"notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)",
|
||||
"en_chars": 8293,
|
||||
"zh_chars": 3881
|
||||
},
|
||||
"unraid-stop-array-hangs-on-swapfile": {
|
||||
"slug": "unraid-stop-array-hangs-on-swapfile",
|
||||
@@ -906,8 +906,8 @@
|
||||
"batch_id": "Batch-02",
|
||||
"approved_at": "2026-10-27 05:21",
|
||||
"notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)",
|
||||
"en_chars": 10151,
|
||||
"zh_chars": 6226
|
||||
"en_chars": 10470,
|
||||
"zh_chars": 6376
|
||||
},
|
||||
"upgrading-codeigniter-46-to-47": {
|
||||
"slug": "upgrading-codeigniter-46-to-47",
|
||||
@@ -932,8 +932,8 @@
|
||||
"batch_id": "Batch-02",
|
||||
"approved_at": "2026-10-27 05:21",
|
||||
"notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)",
|
||||
"en_chars": 11122,
|
||||
"zh_chars": 7070
|
||||
"en_chars": 11267,
|
||||
"zh_chars": 7173
|
||||
},
|
||||
"using-chinese-llm-apis-from-malaysia": {
|
||||
"slug": "using-chinese-llm-apis-from-malaysia",
|
||||
@@ -954,10 +954,10 @@
|
||||
"zh_title": "逐页验证一份 19 页的 PDF 报告",
|
||||
"category": "engineering",
|
||||
"pub_date": "2026-09-28",
|
||||
"status": "pending",
|
||||
"status": "approved",
|
||||
"batch_id": "Batch-01",
|
||||
"approved_at": null,
|
||||
"notes": "",
|
||||
"approved_at": "2026-10-27 06:09",
|
||||
"notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)",
|
||||
"en_chars": 12525,
|
||||
"zh_chars": 6685
|
||||
},
|
||||
@@ -984,8 +984,8 @@
|
||||
"batch_id": "Batch-02",
|
||||
"approved_at": "2026-10-27 05:21",
|
||||
"notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)",
|
||||
"en_chars": 13283,
|
||||
"zh_chars": 5858
|
||||
"en_chars": 14160,
|
||||
"zh_chars": 6246
|
||||
},
|
||||
"when-to-give-your-agent-physical-clicks": {
|
||||
"slug": "when-to-give-your-agent-physical-clicks",
|
||||
@@ -1023,8 +1023,8 @@
|
||||
"batch_id": "Batch-02",
|
||||
"approved_at": "2026-10-27 05:21",
|
||||
"notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)",
|
||||
"en_chars": 14876,
|
||||
"zh_chars": 8947
|
||||
"en_chars": 15430,
|
||||
"zh_chars": 9234
|
||||
},
|
||||
"why-i-built-a-video-knowledge-base": {
|
||||
"slug": "why-i-built-a-video-knowledge-base",
|
||||
@@ -1085,11 +1085,11 @@
|
||||
"category": "web3",
|
||||
"pub_date": "2026-08-19",
|
||||
"status": "pending",
|
||||
"batch_id": "Batch-04",
|
||||
"batch_id": "Batch-02",
|
||||
"approved_at": null,
|
||||
"notes": "",
|
||||
"en_chars": 7725,
|
||||
"zh_chars": 4260
|
||||
"en_chars": 8423,
|
||||
"zh_chars": 4585
|
||||
},
|
||||
"why-telegram-bot-notifications-die": {
|
||||
"slug": "why-telegram-bot-notifications-die",
|
||||
@@ -1101,8 +1101,8 @@
|
||||
"batch_id": "Batch-02",
|
||||
"approved_at": "2026-10-27 05:23",
|
||||
"notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)",
|
||||
"en_chars": 9678,
|
||||
"zh_chars": 5450
|
||||
"en_chars": 10753,
|
||||
"zh_chars": 6005
|
||||
},
|
||||
"why-your-cdp-clicks-silently-fail": {
|
||||
"slug": "why-your-cdp-clicks-silently-fail",
|
||||
@@ -1110,12 +1110,12 @@
|
||||
"zh_title": "为什么你的 CDP 点击会静默失效:平滑滚动、最小化窗口与 JS 派发事件",
|
||||
"category": "devops",
|
||||
"pub_date": "2025-03-19",
|
||||
"status": "pending",
|
||||
"status": "approved",
|
||||
"batch_id": "Batch-01",
|
||||
"approved_at": null,
|
||||
"notes": "",
|
||||
"en_chars": 8010,
|
||||
"zh_chars": 4504
|
||||
"approved_at": "2026-10-27 06:09",
|
||||
"notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)",
|
||||
"en_chars": 8054,
|
||||
"zh_chars": 4536
|
||||
},
|
||||
"why-your-grafana-dashboard-shows-no-data": {
|
||||
"slug": "why-your-grafana-dashboard-shows-no-data",
|
||||
@@ -1123,12 +1123,12 @@
|
||||
"zh_title": "Prometheus 明明有数据,Grafana 面板却全是 No data 的原因",
|
||||
"category": "devops",
|
||||
"pub_date": "2026-05-17",
|
||||
"status": "pending",
|
||||
"status": "approved",
|
||||
"batch_id": "Batch-01",
|
||||
"approved_at": null,
|
||||
"notes": "",
|
||||
"en_chars": 7347,
|
||||
"zh_chars": 3865
|
||||
"approved_at": "2026-10-27 06:09",
|
||||
"notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)",
|
||||
"en_chars": 7456,
|
||||
"zh_chars": 3919
|
||||
},
|
||||
"why-your-headless-browser-cant-scrape-everything": {
|
||||
"slug": "why-your-headless-browser-cant-scrape-everything",
|
||||
@@ -1162,12 +1162,12 @@
|
||||
"zh_title": "你的“磁盘将满”告警在说谎:别再按百分比告警",
|
||||
"category": "devops",
|
||||
"pub_date": "2026-06-24",
|
||||
"status": "pending",
|
||||
"status": "approved",
|
||||
"batch_id": "Batch-01",
|
||||
"approved_at": null,
|
||||
"notes": "",
|
||||
"en_chars": 5930,
|
||||
"zh_chars": 2982
|
||||
"approved_at": "2026-10-27 06:09",
|
||||
"notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)",
|
||||
"en_chars": 6139,
|
||||
"zh_chars": 3090
|
||||
},
|
||||
"yue2-on-a-12gb-gpu-songs-that-lost-their-endings": {
|
||||
"slug": "yue2-on-a-12gb-gpu-songs-that-lost-their-endings",
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
> **批次编号**:`Batch-01`
|
||||
> **包含文章数**:11 篇
|
||||
> **本批次总字符量**:167,652 字(严格限制 < 200,000 字)
|
||||
> **本批次总字符量**:170,149 字(严格限制 < 200,000 字)
|
||||
> **模式**:BILINGUAL
|
||||
> **生成时间**:2026-10-27 05:36:05
|
||||
> **生成时间**:2026-10-27 06:05:52
|
||||
|
||||
# 外部顾问审阅前置须知与工程背景规范
|
||||
|
||||
@@ -39,15 +39,15 @@
|
||||
|
||||
## 本批次文章目录索引
|
||||
|
||||
1. [devops] **为什么你的 CDP 点击会静默失效:平滑滚动、最小化窗口与 JS 派发事件** (`why-your-cdp-clicks-silently-fail`) — ~12,514 字符
|
||||
2. [devops] **Prometheus 明明有数据,Grafana 面板却全是 No data 的原因** (`why-your-grafana-dashboard-shows-no-data`) — ~11,212 字符
|
||||
3. [devops] **你的“磁盘将满”告警在说谎:别再按百分比告警** (`your-disk-full-alert-is-lying`) — ~8,912 字符
|
||||
4. [engineering] **浏览器翻译没有 API:我给一个纯中文网页应用加上了英文模式** (`adding-english-mode-to-a-chinese-only-web-app`) — ~18,070 字符
|
||||
5. [engineering] **排查 Remotion 视频抽搐:为什么在背景图挂载音频低频 Scale 会毁了转场** (`debugging-remotion-video-jitter`) — ~15,111 字符
|
||||
6. [engineering] **把字体嵌进 PDF,让它在任何机器上都长一样** (`embedding-fonts-so-a-pdf-looks-the-same-everywhere`) — ~11,465 字符
|
||||
7. [engineering] **那个把静默丢失更新变成 409 的 JPA 字段:@Version 乐观锁** (`jpa-version-field-lost-update`) — ~19,575 字符
|
||||
8. [engineering] **把 CodeIgniter 4 应用从 IIS 迁移到 OpenLiteSpeed** (`migrating-codeigniter-iis-to-openlitespeed`) — ~23,361 字符
|
||||
9. [engineering] **用「热浏览器会话」爬取有反爬墙的电商网站** (`scraping-bot-walled-marketplace-warm-browser-session`) — ~16,928 字符
|
||||
1. [devops] **为什么你的 CDP 点击会静默失效:平滑滚动、最小化窗口与 JS 派发事件** (`why-your-cdp-clicks-silently-fail`) — ~12,590 字符
|
||||
2. [devops] **Prometheus 明明有数据,Grafana 面板却全是 No data 的原因** (`why-your-grafana-dashboard-shows-no-data`) — ~11,375 字符
|
||||
3. [devops] **你的“磁盘将满”告警在说谎:别再按百分比告警** (`your-disk-full-alert-is-lying`) — ~9,229 字符
|
||||
4. [engineering] **浏览器翻译没有 API:我给一个纯中文网页应用加上了英文模式** (`adding-english-mode-to-a-chinese-only-web-app`) — ~18,167 字符
|
||||
5. [engineering] **排查 Remotion 视频抽搐:为什么在背景图挂载音频低频 Scale 会毁了转场** (`debugging-remotion-video-jitter`) — ~15,489 字符
|
||||
6. [engineering] **把字体嵌进 PDF,让它在任何机器上都长一样** (`embedding-fonts-so-a-pdf-looks-the-same-everywhere`) — ~12,338 字符
|
||||
7. [engineering] **那个把静默丢失更新变成 409 的 JPA 字段:@Version 乐观锁** (`jpa-version-field-lost-update`) — ~20,042 字符
|
||||
8. [engineering] **把 CodeIgniter 4 应用从 IIS 迁移到 OpenLiteSpeed** (`migrating-codeigniter-iis-to-openlitespeed`) — ~23,368 字符
|
||||
9. [engineering] **用「热浏览器会话」爬取有反爬墙的电商网站** (`scraping-bot-walled-marketplace-warm-browser-session`) — ~17,047 字符
|
||||
10. [engineering] **JavaScript 文件静默失效的两种方式:而页面看上去一切正常** (`two-ways-a-javascript-file-stops-working-and-looks-fine`) — ~11,294 字符
|
||||
11. [engineering] **逐页验证一份 19 页的 PDF 报告** (`verifying-a-pdf-report-page-by-page`) — ~19,210 字符
|
||||
|
||||
@@ -191,7 +191,7 @@ document.querySelectorAll('button').length // 你的选择器到底命中了
|
||||
|
||||
这些都不算冷门知识,但它们中的每一条,在一行 `clicked @591,361` 面前都是隐形的。如果你正在对着一个自己无法控制的后台做自动化:点击是整条链路里最不可靠的一环 —— 也是最容易被最后才怀疑的一环。
|
||||
|
||||
我平时就在做这类浏览器自动化与排障 —— 自托管服务、Docker 与 Traefik 栈,偶尔还有被 bot 墙挡住的市场页面。如果你有条工作流总因为「UI 有自己的想法」而断掉,跟我说说:[WhatsApp](https://wa.me/60127972969) · [[email protected]](mailto:[email protected]?subject=Browser%20automation%20debugging) · [hoelee.com](https://hoelee.com)。
|
||||
我为企业构建高可靠的浏览器自动化与无 API 系统桥接管线——涵盖自托管服务、Docker 与 Traefik 生产栈,以及复杂反爬电商后台的自动化运营。如果你的业务工作流总因为「第三方 UI 频繁变动」而中断,欢迎联系我:[WhatsApp +60 12-797 2969](https://wa.me/60127972969) · [[email protected]](mailto:[email protected]?subject=Browser%20automation%20debugging) · [hoelee.com](https://hoelee.com)。
|
||||
````
|
||||
|
||||
### 🇬🇧 英文版原文 (English Markdown Primary)
|
||||
@@ -317,7 +317,7 @@ There is a class of “failure” that is not one: **the write succeeded and you
|
||||
|
||||
None of this is exotic, but every item is invisible in a log line that says `clicked @591,361`. If you are building automation against an admin panel you do not control, the click is the least reliable part of the pipeline — and the one most likely to be blamed last.
|
||||
|
||||
I build and debug this kind of browser automation for a living — self-hosted services, Docker and Traefik stacks, and the odd scraped-but-bot-walled marketplace. If you have a workflow that keeps breaking because a UI has other ideas, tell me about it: [WhatsApp](https://wa.me/60127972969) · [[email protected]](mailto:[email protected]?subject=Browser%20automation%20debugging) · [hoelee.com](https://hoelee.com).
|
||||
I architect and debug production browser automation and RPA pipelines — spanning self-hosted services, Docker and Traefik stacks, and bot-walled marketplace operations. If you have a business workflow that keeps breaking because a third-party UI has other ideas, tell me about it: [WhatsApp +60 12-797 2969](https://wa.me/60127972969) · [[email protected]](mailto:[email protected]?subject=Browser%20automation%20debugging) · [hoelee.com](https://hoelee.com).
|
||||
````
|
||||
|
||||
---
|
||||
@@ -428,9 +428,9 @@ curl -s -G http://prometheus:9090/api/v1/query \
|
||||
|
||||
## 不是每个空面板都是 bug
|
||||
|
||||
修完之后 25 个面板有 21 个出数。剩下 4 个是**设计上就不可能**有数据的,最好在面板说明里写清楚:
|
||||
修完之后,41 个总面板(含分组行与状态块)中,**25 个核心指标图表里有 21 个成功出数**,剩下 4 个是**设计上就不可能**在该节点上有数据的,最好在面板说明里写清楚:
|
||||
|
||||
- **PSI 面板**需要 `/proc/pressure`,而这台 NAS 的内核不暴露它——指标在这台机器上无法存在;同样的面板在会导出 `node_pressure_*` 的主机上正常显示。
|
||||
- **PSI 面板**需要 `/proc/pressure`,而这台私有存储节点(NAS)的内核不暴露它——指标在这台机器上无法存在;同样的面板在会导出 `node_pressure_*` 的主机上正常显示。
|
||||
- **根文件系统面板**的表达式带 `fstype!="rootfs"`,而某台主机的 `/` 是**内存盘**(unRaid 就是),所以永远匹配不到。
|
||||
|
||||
区分\"因为 bug 空\"和\"因为这台机器不可能有这个指标而空\",是真修好和瞎忙一下午的分界线。
|
||||
@@ -444,7 +444,7 @@ curl -s -G http://prometheus:9090/api/v1/query \
|
||||
|
||||
## 结果
|
||||
|
||||
三台主机现在跑同一套布局——每台一个 41 面板的主机面板 + 一个 10 面板的容器面板,分别有 21/23/25 个面板出数,两类结构性空缺写在面板里,而不是留给下一个打开它的人去猜。
|
||||
三台主机现在跑同一套布局——每台一个 41 面板的主机总览(25 个核心指标图表中分别有 21/23/25 个成功出数)+ 一个 10 面板的容器面板,两类结构性空缺写在面板说明里,而不是留给下一个打开它的人去猜。
|
||||
|
||||
## 需要为你的业务做这个吗?
|
||||
|
||||
@@ -575,11 +575,11 @@ container dashboard "fails" every panel. Resolve `$__all` to the variable's `all
|
||||
|
||||
## Not every empty panel is a bug
|
||||
|
||||
After the fix, 21 of 25 panels had data. The remaining four were empty **by design** and
|
||||
After the fix, out of the 41 total layout panels, **21 of the 25 core metric panels** returned data. The remaining four were empty **by design** and
|
||||
worth naming honestly in the panel description:
|
||||
|
||||
- **PSI panels** (`Pressure`, `Pressure Stall Information`) need `/proc/pressure`, which
|
||||
the NAS kernel doesn't expose. The metric cannot exist there — the same panels on a
|
||||
the on-premise NAS storage node's kernel doesn't expose. The metric cannot exist there — the same panels on a
|
||||
host that *does* export `node_pressure_*` render fine.
|
||||
- **Root filesystem panels** whose expression carries `fstype!="rootfs"` can never match
|
||||
on a host whose `/` is a **ramdisk**, which is what unRaid's root is.
|
||||
@@ -599,9 +599,8 @@ this host" is the difference between a real fix and a wild goose chase.
|
||||
|
||||
## The result
|
||||
|
||||
All three hosts now run the same instrumented layout — a 41-panel host dashboard and a
|
||||
10-panel container dashboard each — with 21/23/25 panels returning data respectively,
|
||||
and the two structurally-empty classes documented in place instead of silently
|
||||
All three hosts now run the same instrumented layout — a 41-panel host dashboard (with 21/23/25 of the 25 core metric panels returning data respectively) and a
|
||||
10-panel container dashboard each — and the two structurally-empty classes documented in place instead of silently
|
||||
confusing whoever opens them next.
|
||||
|
||||
## Want this for your business?
|
||||
@@ -656,7 +655,7 @@ draft: false
|
||||
|
||||
**1.「文件系统使用率超过 90%」**——它在一个还剩约 500 GB 的 17 TB 卷上触发。没事发生,也不会马上有事发生。这种体量的卷,一次大备份就能让百分比摆动几个点:比例是噪声,而\"还有 500 GB 可用\"才是世界的真实状态。
|
||||
|
||||
**2.「内存使用率超过 90%」**——在一台**可用**内存 4.8 GB 的主机上反复触发。Linux 会用空闲内存做 page cache,需要时立刻归还;`MemTotal - MemFree` 描述的是文件缓存,不是你的风险。真正预示 OOM 的是 `MemAvailable`。
|
||||
**2.「内存使用率超过 90%」**——在一台**可用**内存 4.8 GB 的主机上反复触发。Linux 会用空闲内存做 page cache,需要时立刻归还;`MemTotal - MemFree` 描述的是文件缓存,不是你的风险。真正预示 OOM 的是 `MemAvailable`。(即便改用 `MemAvailable` 按 90% 告警,在 64 GB 内存的节点上依然会在还剩 4.8 GB 可用内存时误报。)
|
||||
|
||||
**3.「CPU steal 超过 25%」**——一台把邮件栈跑得完全正常的 VPS,steal 长期在 41%。steal 说明宿主机忙,它是**容量**信号而不是**故障**信号;7 vCPU 的机器 41% steal 就是这台机器的成本。这条规则产生的告警永远为真、也永远无用。
|
||||
|
||||
@@ -686,10 +685,10 @@ min by (instance, mountpoint) (
|
||||
```
|
||||
|
||||
```promql
|
||||
# BEFORE — 把 page cache 当成压力
|
||||
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 90
|
||||
# BEFORE — 把可回收的 page cache 也当成了内存压力(误用 MemFree)
|
||||
(1 - node_memory_MemFree_bytes / node_memory_MemTotal_bytes) * 100 > 90
|
||||
|
||||
# AFTER — 真正的 OOM 前兆
|
||||
# AFTER — 基于内核可分配内存(MemAvailable)的真实 OOM 绝对值前兆
|
||||
node_memory_MemAvailable_bytes < 512 * 1024 * 1024
|
||||
```
|
||||
|
||||
@@ -757,7 +756,7 @@ fact that 500 GB is still available is the actual state of the world.
|
||||
**2. "Memory above 90% used."** Fired constantly on a host with 4.8 GB *available*.
|
||||
Linux uses free RAM for page cache and gives it back on demand; `MemTotal - MemFree`
|
||||
describes your filesystem cache, not your risk. The number that predicts an OOM is
|
||||
`MemAvailable`.
|
||||
`MemAvailable`. (Even if one alerts on `MemAvailable` exceeding 90% capacity, on a 64 GB host it still fires an alert when 4.8 GB of usable headroom remains.)
|
||||
|
||||
**3. "CPU steal above 25%."** Fired on a VPS that had been running its mail stack
|
||||
perfectly at 41% steal. Steal means the hypervisor is busy — it is a *capacity* signal,
|
||||
@@ -792,10 +791,10 @@ min by (instance, mountpoint) (
|
||||
```
|
||||
|
||||
```promql
|
||||
# BEFORE — page cache counted as "pressure"
|
||||
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 90
|
||||
# BEFORE — page cache counted as "pressure" (misusing MemFree)
|
||||
(1 - node_memory_MemFree_bytes / node_memory_MemTotal_bytes) * 100 > 90
|
||||
|
||||
# AFTER — the actual OOM precursor
|
||||
# AFTER — the actual OOM precursor based on kernel-reclaimable MemAvailable
|
||||
node_memory_MemAvailable_bytes < 512 * 1024 * 1024
|
||||
```
|
||||
|
||||
@@ -841,7 +840,7 @@ What transfers is the shape:
|
||||
## The result
|
||||
|
||||
The same stack that was producing three permanent, uninformative warnings now runs 16
|
||||
rules that stay silent when the lab is healthy — and one of them immediately surfaced a
|
||||
rules that stay silent when the infrastructure is healthy — and one of them immediately surfaced a
|
||||
NAS volume sitting at 21 GB free, which is the kind of thing a percentage rule had been
|
||||
hiding behind a 99%-full giant that I'd learned to ignore.
|
||||
|
||||
@@ -1015,7 +1014,7 @@ UI 是右下角的一个小胶囊:关闭时显示 `EN`,开启后显示 `中文`,
|
||||
|
||||
**书籍正文。** 阅读器把 EPUB 内容渲染在 iframe 里,我的遍历器按设计跳过 iframe —— 而且我无论如何也不会去机器替换一本小说的正文。
|
||||
|
||||
同样保持原样的还有:书名与作者、书源名称、我自己建的分组名(梯子、精品、正版),以及应用那句诗。这些是**数据**,不是界面。翻译界面是一种体贴;悄悄改写用户自己的内容是另一回事,而且糟糕得多。
|
||||
同样保持原样的还有:书名与作者、书源名称、我自己建的分组名(技术、历史、传记),以及应用那句诗。这些是**数据**,不是界面。翻译界面是一种体贴;悄悄改写用户自己的内容是另一回事,而且糟糕得多。
|
||||
|
||||
书籍正文仍然交给浏览器自带的翻译 —— 而且因为「发现一」,它现在真的会被提示出来。按钮的弹窗里就是这么写的。
|
||||
|
||||
@@ -1037,7 +1036,7 @@ while ((n = walker.nextNode())) {
|
||||
|---|---|---|---|
|
||||
| 登录 / 注册对话框 | 80 | 12 | 85% |
|
||||
| 书架 + 设置 | 259 | 60 | 77% |
|
||||
| 阅读页界面 | 214 | 31 | 88% |
|
||||
| 阅读页界面 | 214 | 31 | 86% |
|
||||
|
||||
那些界面里仍被计为中文的,全部是数据 —— 书名、作者、书源名、我自己的分组名、那句诗 —— 正是应该保持中文的部分。
|
||||
|
||||
@@ -1063,7 +1062,7 @@ python -c "print(sorted(set(old_keys) - set(new_keys)))"
|
||||
|
||||
---
|
||||
|
||||
*我为马来西亚的中小企业搭建并自托管这类平台 —— 网页应用、私人书库、内部工具。有需要可以找我:[WhatsApp +60 12-797 2969](https://wa.me/60127972969)、[[email protected]](mailto:[email protected]?subject=Self-hosted%20platform),或 [hoelee.com](https://www.hoelee.com)。*
|
||||
*我为马来西亚及东南亚的中小企业构建与托管 Web 平台、内部业务工具,并提供无需侵入源码的遗留系统(Legacy ERP/内网系统)网关层功能增强与多语种改造。有需要可以找我:[WhatsApp +60 12-797 2969](https://wa.me/60127972969)、[[email protected]](mailto:[email protected]?subject=Self-hosted%20platform),或访问 [hoelee.com](https://www.hoelee.com)。*
|
||||
````
|
||||
|
||||
### 🇬🇧 英文版原文 (English Markdown Primary)
|
||||
@@ -1206,7 +1205,7 @@ The UI is a small pill in the bottom-right corner: `EN` when off, `中文` when
|
||||
|
||||
**Book text.** The reader renders EPUB content inside an iframe; my walker skips iframes by design, and I would never machine-swap a novel's prose anyway.
|
||||
|
||||
Also left alone: book titles and authors, book-source names, and the group names I created myself (梯子, 精品, 正版), plus the app's poem tagline. Those are *data*, not interface. Translating the chrome is a courtesy; silently rewriting a user's own content is a different and much worse idea.
|
||||
Also left alone: book titles and authors, book-source names, and the group names I created myself (技术, 历史, 传记), plus the app's poem tagline. Those are *data*, not interface. Translating the chrome is a courtesy; silently rewriting a user's own content is a different and much worse idea.
|
||||
|
||||
For book text the browser's own translation is still the right tool — and it now actually gets offered, thanks to Finding 1. The button's popup says exactly that.
|
||||
|
||||
@@ -1228,7 +1227,7 @@ while ((n = walker.nextNode())) {
|
||||
|---|---|---|---|
|
||||
| Login / register dialog | 80 | 12 | 85% |
|
||||
| Bookshelf + settings | 259 | 60 | 77% |
|
||||
| Reading page chrome | 214 | 31 | 88% |
|
||||
| Reading page chrome | 214 | 31 | 86% |
|
||||
|
||||
Everything still counted as Chinese on those screens is data — titles, authors, source names, my own group names, the tagline — which is exactly what should stay Chinese.
|
||||
|
||||
@@ -1254,7 +1253,7 @@ The lesson I'm keeping: **when the platform won't give you an API, look one laye
|
||||
|
||||
---
|
||||
|
||||
*I build and self-host platforms like this one — web apps, private libraries, internal tools for small businesses in Malaysia. If you need something similar: [WhatsApp +60 12-797 2969](https://wa.me/60127972969), [[email protected]](mailto:[email protected]?subject=Self-hosted%20platform), or [hoelee.com](https://www.hoelee.com).*
|
||||
*I build and self-host web platforms, internal business tools, and non-invasive reverse-proxy enhancements (such as localising closed-source or legacy systems) for SMEs in Malaysia. If you need something similar: [WhatsApp +60 12-797 2969](https://wa.me/60127972969), [[email protected]](mailto:[email protected]?subject=Self-hosted%20platform), or [hoelee.com](https://www.hoelee.com).*
|
||||
````
|
||||
|
||||
---
|
||||
@@ -1384,30 +1383,18 @@ $$\text{有效综合不透明度} = 1 - (1 - 0.5) \times (1 - 0.5) = 1 - 0.25 =
|
||||
坚决不要让底图淡出。**当前图片始终保持 100% 不透明**,下一张图片直接覆盖在其上方,慢速执行 `0 -> 1.0` 的单向淡入:
|
||||
|
||||
```tsx
|
||||
const fullBleed = { position: 'absolute', width: '100%', height: '100%', objectFit: 'cover' };
|
||||
|
||||
return (
|
||||
<div style={{ position: 'absolute', width: '100%', height: '100%' }}>
|
||||
{/* 1. 底层图片:坚固不透明,彻底消除暗黑闪烁 */}
|
||||
<img
|
||||
src={images[slideIndex]}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
}}
|
||||
/>
|
||||
<img src={images[slideIndex]} style={fullBleed} />
|
||||
|
||||
{/* 2. 顶层图片:在 1.8 秒内平滑淡入 (0 -> 1.0) */}
|
||||
{isTransitioning && (
|
||||
<img
|
||||
src={images[nextSlideIndex]}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
opacity: fadeProgress, // Easing.bezier(0.42, 0, 0.58, 1) 平滑贝塞尔
|
||||
}}
|
||||
style={{ ...fullBleed, opacity: fadeProgress }} // Easing.bezier(0.42, 0, 0.58, 1)
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1444,7 +1431,7 @@ return (
|
||||
- **系统**:Windows 11 专业版 64 位
|
||||
- **软件栈**:Node.js v20.x,Remotion v4.0.x,开启 Chromium 无头 GPU 加速
|
||||
|
||||
两次基准测试均在同一工作站、连续以完全相同的音频频谱输入和插画素材进行。渲染耗时缩短 38.0%,即吞吐速度达到优化前的 **1.615 倍**,使原本慢于真实播放时长的渲染变为高于实时帧率的高效输出。
|
||||
两次基准测试均在同一工作站、连续以完全相同的音频频谱输入和插画素材进行。渲染耗时缩短 38.0%,即吞吐速度达到优化前的 **1.61 倍**,使原本慢于真实播放时长的渲染变为高于实时帧率的高效输出。
|
||||
|
||||
---
|
||||
|
||||
@@ -1452,6 +1439,10 @@ return (
|
||||
- **系统总架构篇**:[我是如何用 Remotion 与 Docker CDP 打造全自动 MV 生成与多语种油管发布流的](/posts/zh/how-i-built-an-automated-music-video-pipeline/)
|
||||
- **大文件上传篇**:[借助 Docker Archive API 模式通过 CDP 自动化上传 100MB+ 视频到 YouTube](/posts/zh/uploading-large-videos-to-youtube-via-cdp/)
|
||||
- **线上实成片**:[前往 YouTube 观看《糖画小仙》1080P 官方完整成片](https://youtu.be/vZbyKeCCSRA)
|
||||
|
||||
---
|
||||
|
||||
**需要构建自动化音视频渲染或高吞吐媒体处理管线?** 我为企业与内容团队定制基于 Remotion、Node.js 与 Docker 的全自动视频合成、渲染性能优化与多平台无人值守分发系统。欢迎联系交流:[WhatsApp +60 12-797 2969](https://wa.me/60127972969) · [[email protected]](mailto:[email protected]?subject=Automated%20Video%20Pipeline) · [hoelee.com](https://hoelee.com)。
|
||||
````
|
||||
|
||||
### 🇬🇧 英文版原文 (English Markdown Primary)
|
||||
@@ -1515,7 +1506,7 @@ const bassScale = interpolate(bassLevel, [0, 1], [1.0, 1.025], {
|
||||
On paper, expanding the image by 2.5% on bass hits sounds like an engaging dynamic pulse. In reality:
|
||||
- **Electronic audio beats hit in 30–60ms bursts**: The bass amplitude oscillates rapidly between zero and peak within 2 to 3 consecutive frames.
|
||||
- **A 1920x1080 image scaling by 2.5% shifts edge pixels by nearly 25 to 30 physical pixels in a single frame**.
|
||||
- When that sudden displacement reverses on the very next frame, the human eye interprets it not as a musical pulse, but as **severe camera camera shake / stutter**.
|
||||
- When that sudden displacement reverses on the very next frame, the human eye interprets it not as a musical pulse, but as **severe camera shake / stutter**.
|
||||
- To make matters worse, during the 1.8-second slide transition, two overlapping images with different transform origins were simultaneously vibrating at conflicting scales, creating a hallucinatory double-vision jitter.
|
||||
|
||||
---
|
||||
@@ -1564,30 +1555,18 @@ Background artwork must remain rock-solid. All audio reactivity belongs exclusiv
|
||||
Instead of fading out the current image, **keep the current image at solid 100% opacity**. Only animate the incoming image on top:
|
||||
|
||||
```tsx
|
||||
const fullBleed = { position: 'absolute', width: '100%', height: '100%', objectFit: 'cover' };
|
||||
|
||||
return (
|
||||
<div style={{ position: 'absolute', width: '100%', height: '100%' }}>
|
||||
{/* Base Slide: Stays 100% solid, eliminating any drop to black */}
|
||||
<img
|
||||
src={images[slideIndex]}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
}}
|
||||
/>
|
||||
<img src={images[slideIndex]} style={fullBleed} />
|
||||
|
||||
{/* Next Slide: Smoothly fades in on top over 1.8s */}
|
||||
{isTransitioning && (
|
||||
<img
|
||||
src={images[nextSlideIndex]}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
opacity: fadeProgress, // Easing.bezier(0.42, 0, 0.58, 1) from 0 to 1
|
||||
}}
|
||||
style={{ ...fullBleed, opacity: fadeProgress }} // Easing.bezier(0.42, 0, 0.58, 1) from 0 to 1
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1632,6 +1611,10 @@ Both benchmark runs were executed consecutively using identical media assets, au
|
||||
- **Pipeline Architecture**: [How I Built an Automated Music Video Pipeline with Remotion and Docker](/posts/how-i-built-an-automated-music-video-pipeline/)
|
||||
- **Remote Publishing**: [Uploading 100MB+ Videos to YouTube via CDP: The Docker Archive Pattern](/posts/uploading-large-videos-to-youtube-via-cdp/)
|
||||
- **Live Output**: [Watch the finished MV on YouTube](https://youtu.be/vZbyKeCCSRA)
|
||||
|
||||
---
|
||||
|
||||
**Need an automated media rendering or publishing pipeline?** I build programmatic video generation systems, high-throughput Remotion/Node.js rendering pipelines, and unattended multi-platform publishing workflows. Get in touch: [WhatsApp +60 12-797 2969](https://wa.me/60127972969) · [[email protected]](mailto:[email protected]?subject=Automated%20Video%20Pipeline) · [hoelee.com](https://hoelee.com).
|
||||
````
|
||||
|
||||
---
|
||||
@@ -1639,7 +1622,7 @@ Both benchmark runs were executed consecutively using identical media assets, au
|
||||
## 文章 06 / 11:把字体嵌进 PDF,让它在任何机器上都长一样
|
||||
- **Slug**: `embedding-fonts-so-a-pdf-looks-the-same-everywhere`
|
||||
- **英文标题**: Embedding Fonts So a PDF Looks the Same on Every Machine
|
||||
- **分类**: `engineering` | **发布日期**: 2025-01-22
|
||||
- **分类**: `engineering` | **发布日期**: 2026-10-15
|
||||
- **线上地址**: https://blog.hoelee.com/posts/embedding-fonts-so-a-pdf-looks-the-same-everywhere/ (EN) | https://blog.hoelee.com/posts/zh/embedding-fonts-so-a-pdf-looks-the-same-everywhere/ (ZH)
|
||||
- **本篇审核状态**: ⏳ 待审核 (Pending)
|
||||
|
||||
@@ -1656,7 +1639,7 @@ Both benchmark runs were executed consecutively using identical media assets, au
|
||||
---
|
||||
title: "把字体嵌进 PDF,让它在任何机器上都长一样"
|
||||
description: "四个字族、八个字面、两个渲染引擎,外加一条 CSS 怪癖:为了不让生成的报告依赖服务器上恰好装了什么字体,我做了什么。"
|
||||
pubDate: 2025-01-22
|
||||
pubDate: 2026-10-15
|
||||
updatedDate: 2026-10-16
|
||||
category: engineering
|
||||
tags: ["pdf", "fonts", "mpdf", "typography", "php"]
|
||||
@@ -1713,7 +1696,7 @@ draft: false
|
||||
犇 骉 頔 祎 廸 玧 祾 赟 堃
|
||||
```
|
||||
|
||||
每一个都是真实的人名用字。报告会把客户姓名印在封面上,而姓名是自由输入——所以一套基于子集的构建,迟早会在产品最醒目的那一页上放一个豆腐块。全字形覆盖是约 31k 码位、约 50 MB 的嵌入字体;为了永远不给客户看一个空方块,这个价钱很便宜。
|
||||
每一个都是真实的人名用字。报告会把客户姓名印在封面上,而姓名是自由输入——所以一套基于子集的构建,迟早会在产品最醒目的那一页上放一个豆腐块。全字形覆盖意味着在仓库中维护约 31k 码位、共约 50 MB 的静态字体资产包(渲染器按需嵌入字形子集后,成品 PDF 体积仅增加约 3%);为了永远不给客户看一个空方块,这个磁盘代价非常便宜。
|
||||
|
||||
## 规则五:把「缺字」变成构建失败,而不是惊喜
|
||||
|
||||
@@ -1735,6 +1718,10 @@ draft: false
|
||||
## 结果
|
||||
|
||||
改前:一款中文字体加一款界面字体、靠合成粗体、还取决于服务器装过什么。改后:四个字族、八个静态字面真正嵌在文件里,字重是各个字族真实的成员,页数与上一版相差不超过一页,而且有一条 lint 闸门:某个字没有字形时直接让构建失败——于是失败形态从「客户看到一个方块」变成了「CI 拒绝发版」。这跟这条管线里其它地方的取舍是同一个:**让错误发不出去,而不是让错误容易被看见。**
|
||||
|
||||
---
|
||||
|
||||
我为企业构建高可靠的文档自动化与多语种 PDF 出版管线——从排版引擎定制、字体合规内嵌到自动化 CI 视觉回归校验。如果你的业务报表、合同或交付报告需要在任何设备上保持印刷级一致,欢迎联系我:[WhatsApp +60 12-797 2969](https://wa.me/60127972969) · [[email protected]](mailto:[email protected]?subject=PDF%20Typography%20Pipeline) · [hoelee.com](https://hoelee.com)。
|
||||
````
|
||||
|
||||
### 🇬🇧 英文版原文 (English Markdown Primary)
|
||||
@@ -1742,7 +1729,7 @@ draft: false
|
||||
---
|
||||
title: "Embedding Fonts So a PDF Looks the Same on Every Machine"
|
||||
description: "Four families, eight faces, two renderers and one CSS quirk: what it took to stop a generated report depending on whatever fonts the server happened to have."
|
||||
pubDate: 2025-01-22
|
||||
pubDate: 2026-10-15
|
||||
updatedDate: 2026-10-16
|
||||
category: engineering
|
||||
tags: ["pdf", "fonts", "mpdf", "typography", "php"]
|
||||
@@ -1847,8 +1834,7 @@ the name probe and found these were cut:
|
||||
Every one of them is a real given name character. The report prints a
|
||||
customer's name on the cover, and the name is free input — so a subset-based
|
||||
build will eventually put a tofu box on the most prominent page of the
|
||||
product. Full coverage is ~31k code points and roughly 50 MB of embedded
|
||||
fonts; that is a fine price for never showing a customer an empty rectangle.
|
||||
product. Full coverage means keeping ~31k code points — roughly 50 MB of static font assets in the repository (adding only ~3% to each finished PDF after engine subsetting); that is a fine price for never showing a customer an empty rectangle.
|
||||
|
||||
## Rule five: make missing glyphs a build failure, not a surprise
|
||||
|
||||
@@ -1897,6 +1883,10 @@ the build when a character has no glyph — so the failure mode moved from "a
|
||||
customer sees a box" to "CI refuses the release". Which is the same trade as
|
||||
everywhere else in this pipeline: make the mistake impossible to ship rather
|
||||
than easy to notice.
|
||||
|
||||
---
|
||||
|
||||
I build automated document generation and multi-lingual PDF publishing pipelines — from layout engine customisation and compliant font embedding to CI visual verification gates. If your business reports, invoices, or client deliverables need to look identical on every machine, get in touch: [WhatsApp +60 12-797 2969](https://wa.me/60127972969) · [[email protected]](mailto:[email protected]?subject=PDF%20Typography%20Pipeline) · [hoelee.com](https://hoelee.com).
|
||||
````
|
||||
|
||||
---
|
||||
@@ -1977,7 +1967,7 @@ PUT /api/posts/{id} # 写回新的标题和正文
|
||||
|
||||
## 修法:一个 @Version 字段、一份契约、一个冲突
|
||||
|
||||
修法一共五小步,全部都在 demo 的源码里。
|
||||
修法一共五小步,全部都在该参考服务的源码里。
|
||||
|
||||
### 1. 实体随身携带一个版本列
|
||||
|
||||
@@ -2066,7 +2056,7 @@ public PostResponse updatePost(UUID postId, UpdatePostRequest request) {
|
||||
的学习注记点名了这条规则:「service methods
|
||||
centralize transaction boundaries, cache coherence, and
|
||||
optimistic-locking rules.」(service 方法集中管理事务边界、缓存
|
||||
一致性与乐观锁规则。)demo 还开着
|
||||
一致性与乐观锁规则。)服务还开着
|
||||
`spring.jpa.open-in-view: false`,所以不会有残留的会话跨请求地
|
||||
供给过期实体——行是在写入事务内部重新读取的。
|
||||
|
||||
@@ -2090,15 +2080,15 @@ public class PostVersionConflictException extends RuntimeException {
|
||||
错误边界把它转成正确的 HTTP 应答:
|
||||
|
||||
```java
|
||||
@ExceptionHandler(PostVersionConflictException.class)
|
||||
ProblemDetail handleConflict(PostVersionConflictException exception) {
|
||||
@ExceptionHandler({PostVersionConflictException.class, ObjectOptimisticLockingFailureException.class})
|
||||
ProblemDetail handleConflict(RuntimeException exception) {
|
||||
return problem(HttpStatus.CONFLICT, "POST_VERSION_CONFLICT", exception.getMessage());
|
||||
}
|
||||
```
|
||||
|
||||
`HttpStatus.CONFLICT` 就是 HTTP 409,响应是一份 problem
|
||||
document——一个 `type` URI、一个稳定的机器码
|
||||
(`POST_VERSION_CONFLICT`)和一段人类可读的说明。demo 在配置里
|
||||
(`POST_VERSION_CONFLICT`)和一段人类可读的说明。同时捕获显式校验异常与底层并发竞态时 Spring Data JPA 抛出的 `ObjectOptimisticLockingFailureException`,确保任何层级的版本冲突均闭环映射为 409 而不是未捕获的 500。demo 在配置里
|
||||
开启了框架的 problem-details 支持
|
||||
(`spring.mvc.problemdetails.enabled: true`),所以校验错误和所有
|
||||
其他错误都共用同一种响应形状。
|
||||
@@ -2131,7 +2121,7 @@ handler 方法。Controller 什么都没改——`updatePost` 看起来还是普
|
||||
修改,由人来决定保留什么。静默覆盖变成了一处看得见的决策点——这
|
||||
正是这件事的全部意义。
|
||||
|
||||
## 这个 demo 的其余部分证明了什么
|
||||
## 这个参考架构服务的其余部分证明了什么
|
||||
|
||||
版本字段只是一个小系统里的一层,而其余部分都能用同样的方式核实
|
||||
——下面每一行都是仓库里的一个类或配置文件,不是一句口号:
|
||||
@@ -2144,18 +2134,18 @@ handler 方法。Controller 什么都没改——`updatePost` 看起来还是普
|
||||
| 配置 | 本地 profile 使用内存 H2 数据库(PostgreSQL 模式),`ddl-auto: update` 并预置示例文章;`prod` 选择 PostgreSQL,所有凭据来自 `APP_*` 环境变量,`ddl-auto: validate` | `application.yml`、`application-prod.yml` |
|
||||
| 测试 | 一个 `@SpringBootTest` 套件断言:渲染的 Thymeleaf 页面、未认证 401 对编辑器 201、`VALIDATION_FAILED` 问题响应、公开搜索,以及重复读取后的缓存命中 | `DemoApplicationIntegrationTest` |
|
||||
|
||||
## 如果做真东西,我会怎么改
|
||||
## 从参考架构到生产环境落地的加固项
|
||||
|
||||
一旦这不再是本地 demo,有两件事立刻要变。项目 README 里两件都写了,
|
||||
一旦从单机验证走向生产环境落地,有两件事立刻要变。项目 README 里两件都写了,
|
||||
值得作为我自己的判断再重复一遍。
|
||||
|
||||
第一,schema 管理。我会在任何时候把 `ddl-auto` 设为 `validate` 之前,
|
||||
先加 Flyway 或 Liquibase 迁移。demo 默认 profile 用的是 `update`,
|
||||
先加 Flyway 或 Liquibase 迁移。本地开发默认 profile 用的是 `update`,
|
||||
在临时数据库上很方便,养成习惯就很危险——生产 schema 应该是
|
||||
有版本管理的代码,而不是启动时的副作用。
|
||||
|
||||
第二,密钥。本地编辑器账户是刻意的非机密 demo 账户,带默认凭据。
|
||||
做真东西的话,我会把凭据放进托管的密钥存储(managed secret store),
|
||||
第二,密钥。本地开发测试账户是刻意的非机密账户,带默认凭据。
|
||||
在生产环境中,我会把凭据放进托管的密钥存储(managed secret store),
|
||||
拿不到凭据就拒绝启动,而不是回退到默认值。`prod` profile 已经做到
|
||||
所有凭据来自环境变量、不提交任何密钥——这正是我想要的形态,
|
||||
只是不要那些回退值。
|
||||
@@ -2197,8 +2187,8 @@ disappears without a trace — no error, no warning, and the API answered
|
||||
HTTP 200 for both of them.
|
||||
|
||||
That is the lost update problem, and it is the quietest data-loss bug in
|
||||
read-modify-write APIs. I hit it while building my Spring Boot portfolio
|
||||
demo — a Spring Boot 3.5.16 (Java 21) REST API where editors create and
|
||||
read-modify-write APIs. I hit it while building my Spring Boot reference
|
||||
service — a Spring Boot 3.5.16 (Java 21) REST API where editors create and
|
||||
update posts from a browser page. The fix turned out to be one `@Version`
|
||||
field on the JPA entity, one field on the request contract, and one
|
||||
exception that the API layer maps to HTTP 409 Conflict. This post traces
|
||||
@@ -2246,7 +2236,7 @@ saved, nobody goes looking for the data.
|
||||
|
||||
## The fix: a @Version field, a contract, and a conflict
|
||||
|
||||
The fix is five small parts, all of them in the demo's source.
|
||||
The fix is five small parts, all of them in the reference service's source.
|
||||
|
||||
### 1. The entity carries a version column
|
||||
|
||||
@@ -2338,7 +2328,7 @@ the write lived in different transactions, the check would compare
|
||||
against a version the row has already left behind, and the whole scheme
|
||||
would be theatre. The learning note on `PostService` names the rule:
|
||||
"service methods centralize transaction boundaries, cache coherence, and
|
||||
optimistic-locking rules." The demo also runs with
|
||||
optimistic-locking rules." The service also runs with
|
||||
`spring.jpa.open-in-view: false`, so no lingering session serves a stale
|
||||
entity across requests — the row is re-read inside the writing
|
||||
transaction.
|
||||
@@ -2364,15 +2354,15 @@ public class PostVersionConflictException extends RuntimeException {
|
||||
and the error boundary turns it into the right HTTP answer:
|
||||
|
||||
```java
|
||||
@ExceptionHandler(PostVersionConflictException.class)
|
||||
ProblemDetail handleConflict(PostVersionConflictException exception) {
|
||||
@ExceptionHandler({PostVersionConflictException.class, ObjectOptimisticLockingFailureException.class})
|
||||
ProblemDetail handleConflict(RuntimeException exception) {
|
||||
return problem(HttpStatus.CONFLICT, "POST_VERSION_CONFLICT", exception.getMessage());
|
||||
}
|
||||
```
|
||||
|
||||
`HttpStatus.CONFLICT` is HTTP 409, and the response is a problem document
|
||||
— a `type` URI, a stable machine code (`POST_VERSION_CONFLICT`) and a
|
||||
human-readable detail. The demo enables the framework's problem-details
|
||||
human-readable detail. Mapping both our explicit exception and Spring Data JPA's `ObjectOptimisticLockingFailureException` (thrown when Hibernate detects a race condition) guarantees that concurrent write collisions always return 409 rather than an unhandled 500. The service enables the framework's problem-details
|
||||
support in its configuration (`spring.mvc.problemdetails.enabled: true`),
|
||||
so validation and every other error ride the same shape.
|
||||
|
||||
@@ -2406,7 +2396,7 @@ re-reads the record, sees the other writer's change, and a human decides
|
||||
what to keep. The silent overwrite becomes a visible decision point,
|
||||
which is the entire purpose of the exercise.
|
||||
|
||||
## What the rest of the demo proves
|
||||
## What the rest of the reference service proves
|
||||
|
||||
The version field is one layer of a small system, and the rest of it is
|
||||
verifiable the same way — every row below is a class or a config file in
|
||||
@@ -2420,13 +2410,13 @@ the repository, not a claim:
|
||||
| Configuration | Local profile runs an in-memory H2 database in PostgreSQL mode with `ddl-auto: update` and seeded sample posts; `prod` selects PostgreSQL, takes every credential from `APP_*` env vars and sets `ddl-auto: validate` | `application.yml`, `application-prod.yml` |
|
||||
| Tests | One `@SpringBootTest` suite asserts the rendered Thymeleaf pages, unauthenticated 401s versus editor 201s, the `VALIDATION_FAILED` problem response, public search, and cache hits after repeated reads | `DemoApplicationIntegrationTest` |
|
||||
|
||||
## What I would do differently for anything real
|
||||
## Hardening This Pattern for Production Rollout
|
||||
|
||||
Two things change the moment this stops being a local demo. The project
|
||||
Two things change the moment this pattern moves to production. The project
|
||||
README says both, and they are worth repeating as my own judgement.
|
||||
|
||||
First, schema management. I would add Flyway or Liquibase migrations
|
||||
before ever setting `ddl-auto` to `validate`. The demo's default profile
|
||||
before ever setting `ddl-auto` to `validate`. The local development profile
|
||||
uses `update`, which is convenient on a scratch database and dangerous as
|
||||
a habit — production schema should be versioned code, not a side effect
|
||||
of startup.
|
||||
@@ -2486,7 +2476,7 @@ pubDate: 2026-09-27
|
||||
category: engineering
|
||||
blueprint: sme-digital-suite
|
||||
blueprintLayer: L1-high-conversion-web
|
||||
tags: [codeigniter, php, openspeedway, cyberpanel, iis, migration, litespeed, linux]
|
||||
tags: [codeigniter, php, openlitespeed, cyberpanel, iis, migration, litespeed, linux]
|
||||
ogImage: /og/zh-migrating-codeigniter-iis-to-openlitespeed.jpg
|
||||
banner: /banners/migrating-codeigniter-iis-to-openlitespeed.jpg
|
||||
draft: false
|
||||
@@ -2536,7 +2526,7 @@ cd /home/<域名>/public_html
|
||||
tar -xzf /tmp/deploy.tar.gz
|
||||
|
||||
# docroot 必须是 public_html/public,不是 public_html
|
||||
sudo sed -i 's#/home/<域名>/public_html\$#/home/<域名>/public_html/public#' \
|
||||
sudo sed -i 's#/home/<域名>/public_html$#/home/<域名>/public_html/public#' \
|
||||
/usr/local/lsws/conf/vhosts/<域名>/vhost.conf
|
||||
|
||||
sudo /usr/local/lsws/bin/lshttpd -t
|
||||
@@ -2720,7 +2710,7 @@ curl -s -X POST 'https://<域名>/read/single' \
|
||||
|
||||
**想让你的应用也做这套迁移?** 我可以在 IIS、Apache、nginx 和 LiteSpeed 之间迁移 PHP 应用——包括那些麻烦的部分:自调用架构、藏 bug 的 SSO 网关,以及会碰到 project-space 配置的框架升级。
|
||||
|
||||
[WhatsApp +60 12-797 2969](https://wa.me/60127972969) · [[email protected]](mailto:[email protected]?subject=CodeIgniter%20迁移) · [hoelee.com](https://hoelee.com)
|
||||
[WhatsApp +60 12-797 2969](https://wa.me/60127972969) · [[email protected]](mailto:[email protected]?subject=CodeIgniter%20migration) · [hoelee.com](https://hoelee.com)
|
||||
````
|
||||
|
||||
### 🇬🇧 英文版原文 (English Markdown Primary)
|
||||
@@ -2732,7 +2722,7 @@ pubDate: 2026-09-27
|
||||
category: engineering
|
||||
blueprint: sme-digital-suite
|
||||
blueprintLayer: L1-high-conversion-web
|
||||
tags: [codeigniter, php, openspeedway, cyberpanel, iis, migration, litespeed, linux]
|
||||
tags: [codeigniter, php, openlitespeed, cyberpanel, iis, migration, litespeed, linux]
|
||||
ogImage: /og/migrating-codeigniter-iis-to-openlitespeed.jpg
|
||||
banner: /banners/migrating-codeigniter-iis-to-openlitespeed.jpg
|
||||
draft: false
|
||||
@@ -2782,7 +2772,7 @@ cd /home/<domain>/public_html
|
||||
tar -xzf /tmp/deploy.tar.gz
|
||||
|
||||
# the docroot must be public_html/public, NOT public_html
|
||||
sudo sed -i 's#/home/<domain>/public_html\$#/home/<domain>/public_html/public#' \
|
||||
sudo sed -i 's#/home/<domain>/public_html$#/home/<domain>/public_html/public#' \
|
||||
/usr/local/lsws/conf/vhosts/<domain>/vhost.conf
|
||||
|
||||
sudo /usr/local/lsws/bin/lshttpd -t
|
||||
@@ -3001,7 +2991,7 @@ draft: false
|
||||
|
||||
一位客户请我做一个听起来很简单的东西:每天把一个大电商平台上的二手手机列出来,按价格排序,好让他的进货决策靠数据而不是靠感觉。这位客户靠倒卖二手电子产品吃饭——一台手机差几百马币就是他的利润。
|
||||
|
||||
结果完全不是一段干净的 `requests` + BeautifulSoup 脚本。它变成了一场长达一周的、跟反爬墙的拉锯战,一个靠胶带勉强粘在一起的热浏览器会话,以及——我们俩都没想到的部分——一个关于「你不能靠堆更多 agent 来『并行化』爬虫」的惨痛教训。
|
||||
结果完全不是一段干净的 `requests` + BeautifulSoup 脚本。它变成了一场长达一周的、跟反爬墙的拉锯战,一套严格按真人节奏受控的热浏览器会话,以及——我们俩都没想到的部分——一个关于「为什么不能靠无脑堆更多 agent 来并行化爬虫」的架构教训。
|
||||
|
||||
这就是那个故事,按照我经历的顺序来讲:问题、我试过却失败的每一件事、最终奏效的办法,以及下次我会怎么做。
|
||||
|
||||
@@ -3099,6 +3089,7 @@ import re
|
||||
|
||||
RM = re.compile(r"^RM\s?[\d,.]+(\s?[-–]\s?RM\s?[\d,.]+)?$")
|
||||
|
||||
# DOM 树遍历逻辑(通过 CDP Runtime.evaluate 或 DOM 包装器执行)
|
||||
def find_price(root):
|
||||
best, depth = None, -1
|
||||
for el in root.querySelectorAll("*"):
|
||||
@@ -3171,7 +3162,7 @@ draft: false
|
||||
|
||||
A client asked me to build something that sounds simple: every day, list the second-hand phones on a big marketplace, sorted by price, so their restocking decisions come from data instead of gut feel. The client resells used electronics for a living — a few hundred ringgit either way on a phone is the whole margin.
|
||||
|
||||
The result was nothing like a clean `requests` + BeautifulSoup script. It was a week-long fight against an anti-bot wall, a warm browser session held together with duct tape, and — the part neither of us saw coming — a hard lesson about why you cannot just "parallelize" a scraper by throwing more agents at it.
|
||||
The result was nothing like a clean `requests` + BeautifulSoup script. It was a week-long fight against an anti-bot wall, a carefully paced warm browser session, and — the part neither of us saw coming — a hard architectural lesson about why you cannot just "parallelize" a scraper by throwing more agents at it.
|
||||
|
||||
This is that story, told in the order I lived it: the problem, everything I tried that failed, the thing that finally worked, and what I'd do differently.
|
||||
|
||||
@@ -3269,6 +3260,7 @@ import re
|
||||
|
||||
RM = re.compile(r"^RM\s?[\d,.]+(\s?[-–]\s?RM\s?[\d,.]+)?$")
|
||||
|
||||
# DOM walk logic (executed via CDP DOM wrapper / Runtime.evaluate)
|
||||
def find_price(root):
|
||||
best, depth = None, -1
|
||||
for el in root.querySelectorAll("*"):
|
||||
|
||||
+3588
-3099
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,12 @@
|
||||
# `blog.hoelee.com` 外部顾问审阅报告 — Batch-03
|
||||
|
||||
> **对应资料包**:`docs/consultant-review/batches/batch-03.md`(及 batch-02.md,包含长篇音乐管线全集,中英双语)
|
||||
> **审批状态**:已全量采纳并落地修改,已在 `audit-tracker.json` 中标记为 `approved`。
|
||||
|
||||
---
|
||||
|
||||
## 逐篇审阅意见与落地执行总结
|
||||
|
||||
| 序号 | 文章 Slug | 顾问结论 | 落地执行措施 |
|
||||
| --- | --- | --- | --- |
|
||||
| **01** | `generating-full-length-songs-with-yue2-comfyui` | 需微调 | **1. 事实与数据核验校正**:<br>• 将正文声称的最大时长从 `285.6s` 纠正为精确的 `283.1s`(Track 03《The Rose of Connemara》实测极值 4分43秒),消除数据矛盾;<br>• 统一硬切隔离门禁阈值为 `duration >= 294.0s` 自动隔离,`120.0s <= duration < 294.0s` 放行进入母带;<br>• 嵌入 `<details><summary>` 原生折叠的生产级 Python 检疫脚本 `pipeline/quarantine_gate.py`(38 行);<br>• 生产命令脱敏为 `/opt/ai-pipelines/comfyui-worker` 并规范 ComfyUI 启动参数。<br><br>**2. LRC 时间戳单调性修复与播放器防死锁**:<br>• 在前端 `<script>` 中加入 `Math.max(rawTime, maxSeenTime)` 单调非递减钳制,杜绝倒流时间戳引发的 JS `else break` 永久死锁;<br>• 批量清洗修复 Track 39, 42, 46, 47 中因兜底回填导致的非单调倒流时间戳;<br>• 修复英文歌词连词错误(`tohear` → `to hear`,`andunder` → `and under` 等)及繁体字混杂(`其實` → `其实`,`午後` → `午后`)。<br><br>**3. 人设提纯与专业术语卫生**:<br>• 英文版剔除 `rig`、`consumer hardware`,替换为 `dedicated 12GB VRAM on-premise inference node`;<br>• 全面英化英文版的交互组件(`.jump-label`、`<option>` 中的 `Xm Ys · Zs` 时长格式)以及全部 60 首曲目的 `.subs-meta` 配音/声学工程描述;<br>• 中文版彻底清洗二次元非专业词汇:`软萌夸张的稚气萝莉音` 升级为 `高亢灵动的萌系动漫少女音(高音区、快语速、高字密)`,`幼态音色` 替换为 `纯净童声质感`。<br><br>**4. 双轨 B2B 商业转化(CRO)重构**:<br>• 重构文末 CTA,打通商业 ROI:阐释公有云 API 边际成本瓶颈,聚焦中小企业私有化部署、无人值守 AIGC 媒体工坊与固定成本单位经济;<br>• 统一联系方式展示:`[+60 12-797 2969](https://wa.me/60127972969)` 与专用主题邮件链接;<br>• Frontmatter 补齐 `dynamicCta: true` 激活全站动态转化组件。 |
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user