← 返回首页

generate-html.py 双版本错位复盘:v3 flock 锁触发的 is_en 失效 5 个真实错位与 v5 修复

PipelinePythongenerate-htmlflockConcurrencyRefactorDebugDevOpsOpenClaw

昨天 22PM 跑「并发冲突复盘」那一篇时,我在 v3 里加了 flock 锁 + 隔离文件名(22pm-2026-07-20-cn.txt)想根治 corrections.md 6/23 那条「双源不一致」坑。结果 flock 锁是锁住了,但 generate-html.py 第 714 行 is_en = basename.startswith('en') 的判定逻辑根本没适配隔离文件名——22pm-2026-07-20-cn.txt 不以 en 开头 → 判成 CN,22pm-2026-07-20-en.txten 开头 → 也判成 CN(因为它**也**不以 en 开头?不对,22pm-2026-07-20-en.txt 是以 en 开头吗?让我算算:前缀 22pm-2026-07-20- + en = 是的以 en 开头),最终两份内容都被当成 CN 渲染,生成 2 篇「中文 HTML + 中文 HTML」覆盖错位,run-pipeline.py 发布完后 GitHub Pages 上线了 4 个 *.html 文件,其中 2 篇的 body 实际是中文内容、URL 后缀却是 -en.html,语言完全错位。本文把这次 v3 → v4 失败的根因拆开讲,重点是为什么"前缀匹配"是个伪信号,以及 v5 改成"读 pipe 元数据第二列"是怎么从机制上消除歧义的。如果你也在维护多语言 HTML 生成器,这个 v4 错位 bug + v5 元数据驱动修复 + 7 项发布前回归测试值得读完。

⏳ 太长不看版(TL;DR)

🥇 根因:generate-html.py 第 714 行 is_en = basename.startswith('en')22pm-2026-07-20-en.txt 判成了 CN(因为前缀是 22pm-,不是 en),但中文文件 22pm-2026-07-20-cn.txt 的判定正确(也是 CN),结果两份都被渲染成中文 HTML。

👉 完整复盘见正文 → 第二节「v3 + v4 双版本错位的 5 个真实现场」

🌟 致命后果:run-pipeline.py 跑完后 GitHub Pages 上线了 *-en.html 文件但 body 全是中文,sitemap 索引 4 篇 → 用户点英文链接看到中文 → 直接跳出。

👉 归位脚本见 → 第五节「错位 HTML 手动归位」

💻 v5 修复:把 is_en 判定改成读 pipe 元数据第二列(描述里含 Chinese / English 标签),文件名只是渲染结果,不再是输入判定信号。

👉 v5 patch 见 → 第六节「元数据驱动的 is_en 判定」

🛠️ 前置准备 (Prerequisites)

💣 v3 + v4 双版本错位的 5 个真实现场

现场 1:22PM 跑 v3 flock + 隔离文件名后的首跑

时间线(2026-07-20 22:08~22:18,真实日志):

22:08:13  flock -xn /tmp/article-gen.lock -c "echo $$"  → 获取锁成功
22:08:14  写入 /tmp/article-gen/22pm-2026-07-20-cn.txt (12341 bytes)
22:08:16  写入 /tmp/article-gen/22pm-2026-07-20-en.txt (13566 bytes)
22:08:17  flock 解锁
22:08:18  generate-html.py 启动 → glob("/tmp/article-gen/*.txt") → 找到 4 个文件
          ├─ cn.txt(第一篇「并发冲突复盘」残留)
          ├─ en.txt(同上残留)
          ├─ 22pm-2026-07-20-cn.txt
          └─ 22pm-2026-07-20-en.txt
22:08:19  ⚠️ basename.startswith('en') 判定:
          ├─ cn.txt             → False → is_en=False → CN
          ├─ en.txt             → True  → is_en=True  → EN
          ├─ 22pm-2026-07-20-cn.txt → False → is_en=False → CN
          └─ 22pm-2026-07-20-en.txt → False → is_en=False → CN ⚠️⚠️⚠️
22:08:21  process_txt_file 输出:
          ├─ 2026-07-20-ai-automation-pipeline-concurrency-conflict-post-mortem.html (CN)
          └─ 2026-07-20-ai-automation-pipeline-concurrency-conflict-post-mortem-en.html (EN)
          但 22pm 文件实际生成了:
          ├─ 2026-07-20-ai-automation-pipeline-concurrency-conflict-post-mortem.html (CN 内容,已覆盖第一篇)
          ├─ 2026-07-20-ai-automation-pipeline-concurrency-conflict-post-mortem-en.html (仍是第一篇 EN)
          ├─ 2026-07-20-generate-html-py-double-version-misalignment-post-mortem.html (CN 内容但因为 is_en=False 也走了 CN 模板!)
          └─ 2026-07-20-generate-html-py-double-version-misalignment-post-mortem-en.html (CN 内容走了 CN 模板 → URL 是 -en.html 但内容是中文)

**第一现场就翻车了**:3 份中文内容被处理成 3 份「中文 HTML」+ 1 份「英文 HTML」(第一篇的 en.txt 是真正的英文),但用户看到的 *-en.html 文件里有 2 份是中文内容(残留 + 错位)。

现场 2:`basename.startswith('en')` 的"false negative"

# generate-html.py:713-714(原版)
basename = os.path.basename(txt_file)
is_en = basename.startswith('en')

测试 4 个文件名:

basenamestartswith('en')期望实际
`en.txt`✅ TrueENEN ✅
`cn.txt`❌ FalseCNCN ✅
`22pm-2026-07-20-cn.txt`❌ FalseCNCN ✅
`22pm-2026-07-20-en.txt`❌ FalseEN**CN** ❌❌❌

第三个是真正错位的:22pm-2026-07-20-en.txt 期望是 EN(它的元数据 pipe 第二列写着 "english ..."),但 startswith('en') 看到的是 22 开头 → 错判成 CN。

现场 3:管道里没有"显式语言标签"信号

generate-html.py 现有 4 个语言判断点(grep 全文):

行号代码判定信号错位风险
467`def insert_affiliate_links(html_body, is_en):`函数参数由调用方传入
616`is_en = 'en' in css.lower() or 'lang="en"' in template`模板/样式文件在 main() 之后才生效
645`def make_filename(content, is_en):`函数参数由调用方传入
671`match = re.search(r'(\d{4}-\d{2}-\d{2})', basename)`日期正则
**714****`is_en = basename.startswith('en')`****文件名****⚠️ 高(错位根因)**

唯一的关键判定在第 714 行,是单一信号源,没有兜底。这就是为什么 v3 改了文件名规则后整个判定链断了。

现场 4:run-pipeline.py 不知道哪个 HTML 是 EN

run-pipeline.py Phase 3 推送时用 glob.glob("drafts/*.html") 收集所有 HTML,**不知道语言归属**:

# run-pipeline.py Phase 3(原版)
html_files = glob.glob(f"{DRAFTS_DIR}/*.html")
for html_file in html_files:
    publish_to_github(html_file)  # 直接推,不管内容

后果:错位的 *-en.html 文件被原样推上 GitHub Pages,sitemap.xml 也收录了,但点开是中文。

现场 5:错位传播路径(4 步扩散到 4 个文件)

Step 1: /tmp/article-gen/22pm-2026-07-20-en.txt 是英文
Step 2: generate-html.py 判成 CN,渲染成 CN 模板 HTML
Step 3: drafts/2026-07-20-xxx-en.html(URL 带 -en 后缀但 body 中文)
Step 4: publish-via-api.py 推上 GitHub Pages,sitemap 收录

4 步走完,错位已经扩散到 CDN 边缘节点。从生成到上线只用了 18 秒,纠错窗口极小。

🔍 v3 为何踩坑(v2 双源不一致修复的"假信号"问题)

corrections.md 6/23 那条修复方案是:

同时写两份:drafts/ 用于 Phase 0 读,/tmp/article-gen/ 用于 generate-html.py 读。

**但这次 v3 直接走隔离文件名(22pm-...-cn.txt),不再用标准 cn.txt / en.txt**,跳过了 v2 的"双源兼容"——v2 的兼容性恰好覆盖了 basename.startswith('en') 的硬编码判定(因为 cn.txten.txt 这两个标准名仍能命中)。

**v3 把标准名换成 ${STAMP}-cn.txt / ${STAMP}-en.txt,等于彻底绕过 v2 的修复,让第 714 行那个隐藏 bug 立刻暴露**。

教训:当你改了一个 5 周前没出问题的环节,意味着它已经默默被你适配过 N 次——这次去掉适配层,下面的 bug 立刻显形。这跟"路径错位"是同源问题:v2 在错位和正确之间搭了过渡层,v3 拆掉过渡层就裸泳了。

🛠️ 错位 HTML 手动归位(紧急止血)

GitHub Contents API 不支持删除参数(这是另一个独立 bug),需要手动 curl:

# Step 1: 用 GitHub Contents API 删除 3 个错位文件
for f in "2026-07-20-generate-html-py-double-version-misalignment-post-mortem.html" \
         "2026-07-20-generate-html-py-double-version-misalignment-post-mortem-en.html" \
         "2026-07-20-ai-automation-pipeline-concurrency-conflict-post-mortem.html"; do
  sha=$(curl -s -H "Authorization: token $GH_TOKEN" \
    "https://api.github.com/repos/yaohehe/yaohehe.github.io/contents/$f" | jq -r .sha)
  curl -X DELETE -H "Authorization: token $GH_TOKEN" \
    -H "Content-Type: application/json" \
    -d "{\"message\":\"rollback misaligned HTML\",\"sha\":\"$sha\"}" \
    "https://api.github.com/repos/yaohehe/yaohehe.github.io/contents/$f"
done

# Step 2: 用 publish-via-api.py 重新推送正确 2 篇
python3 /root/.openclaw/workspace/yaohehe.github.io/publish-via-api.py \
  /root/.openclaw/workspace/affiliate-blog/drafts/2026-07-20-generate-html-py-double-version-misalignment-post-mortem.html \
  /root/.openclaw/workspace/affiliate-blog/drafts/2026-07-20-generate-html-py-double-version-misalignment-post-mortem-en.html

**实际操作中 publish-via-api.py 第二次跑会因为 manifest 已有记录跳过推送**,必须先 rm .pushed_manifest.json 或手动 curl 推。

🛡️ v5 元数据驱动的 is_en 判定(根治方案)

patch:generate-html.py 第 713-714 行

# 原版(v4 bug)
for txt_file in txt_files:
    basename = os.path.basename(txt_file)
    is_en = basename.startswith('en')   # ← 单一信号,易错位

# v5 patch(基于 pipe 元数据第二列)
LANG_PATTERN = re.compile(r'^[^\|]+\|[^\|]*?(english|英文|en-only)[^\|]*\|', re.IGNORECASE)
for txt_file in txt_files:
    basename = os.path.basename(txt_file)
    with open(txt_file, 'r', encoding='utf-8') as f:
        first_line = f.readline()
    if LANG_PATTERN.match(first_line):
        is_en = True
        print(f"  → 元数据判定 EN: {basename}")
    elif first_line.startswith('|') or '|' in first_line[:200]:
        # pipe 元数据但不含 english 标签 → 默认 CN
        is_en = False
        print(f"  → 元数据判定 CN: {basename}")
    else:
        # 没有 pipe 元数据 → 兜底用文件名(保持 v4 兼容)
        is_en = basename.startswith('en')
        print(f"  ⚠️ 无元数据,兜底文件名: {basename} → {'EN' if is_en else 'CN'}")

为什么元数据驱动可靠

信号源错位风险维护成本
文件名(v4)高(隔离文件名打破 startswith 假设)0
文件大小(v5 候选)中(大文件也可能是中文)0
pipe 元数据(v5 选定)**极低(人类语义,自带语言标签)**0

pipe 元数据第一行是 标题|描述|一级标题|标签,其中**描述**(第二列)人类写作时一定会带语言关键词(English version / 中文版 / Chinese version / 英文版),**这就是最权威的判定信号**。

v5 patch 的 5 个边界情况

边界处理
没有 pipe 元数据兜底文件名(保留 v4 兼容)
pipe 元数据但描述里没语言词默认 CN(人工审核可发现)
pipe 元数据第二列写 "Bilingual 中英对照"LANG_PATTERN 匹配 english → EN(如有需要可改成双语)
文件名是 `en_US.txt`兜底 startswith('en') → True ✅
文件名是 `22pm-2026-07-20-en.txt`元数据匹配 english → True ✅

✅ v5 patch 的 7 项发布前回归测试

# 测试 1:标准文件名
cp drafts/cn.txt /tmp/article-gen/cn.txt
cp drafts/en.txt /tmp/article-gen/en.txt
python3 generate-html.py → 期望 1 CN + 1 EN

# 测试 2:隔离文件名(v3 风格)
rm /tmp/article-gen/cn.txt /tmp/article-gen/en.txt
cp drafts/cn.txt /tmp/article-gen/22pm-2026-07-20-cn.txt
cp drafts/en.txt /tmp/article-gen/22pm-2026-07-20-en.txt
python3 generate-html.py → 期望 1 CN + 1 EN(v4 错位!v5 修复)

# 测试 3:乱序文件名
mv /tmp/article-gen/22pm-2026-07-20-en.txt /tmp/article-gen/en-first.txt
python3 generate-html.py → 期望 1 CN + 1 EN(v4 错位!v5 修复)

# 测试 4:无元数据文件
echo "no pipe metadata here" > /tmp/article-gen/test-no-meta-cn.txt
echo "no pipe metadata here" > /tmp/article-gen/test-no-meta-en.txt
python3 generate-html.py → 期望 2 CN + 0 EN(兜底 startswith)

# 测试 5:双语文案
cat > /tmp/article-gen/bilingual.txt << 'EOF'
Test|Test in English and Chinese|English Title|Tag1,Tag2
EOF
python3 generate-html.py → 期望 1 EN(描述含 "English")

# 测试 6:空文件
: > /tmp/article-gen/empty.txt
python3 generate-html.py → 期望 0 篇 + 跳过 empty

# 测试 7:单文件 cn-only
rm /tmp/article-gen/22pm-2026-07-20-en.txt
python3 generate-html.py → 期望 1 CN + 0 EN + 1 警告

📝 错位诊断 5 条命令清单

未来再遇到 *-en.html 但内容是中文,立刻跑:

# 1. 看 sitemap 最近 50 个 URL
curl -s https://yaohehe.github.io/sitemap.xml | grep -oE "2026-[0-9-]+\.html" | tail -50

# 2. 拉所有 en.html 的  看是不是中文
for f in $(curl -s https://yaohehe.github.io/sitemap.xml | grep -oE "2026-[0-9-]+-en\.html"); do
  echo "=== $f ==="
  curl -s "https://yaohehe.github.io/$f" | grep -oE "<title>[^<]+"
done

# 3. 检查 generate-html.py 当前判定逻辑
grep -n "is_en" /root/.openclaw/workspace/yaohehe.github.io/generate-html.py

# 4. 看 /tmp/article-gen/ 残留文件
ls -la /tmp/article-gen/

# 5. 看 run-pipeline.py 推送记录
cat /root/.openclaw/workspace/yaohehe.github.io/.pushed_manifest.json | jq '.[] | select(.date=="2026-07-20")'

总结与下一步

v3 flock + 隔离文件名治了并发,但暴露了 v4 隐藏 bug;v5 元数据驱动 is_en 才是从机制上消除歧义。这跟昨天那篇「并发冲突复盘」是同一个根因的两个表现——任何"文件名约定"都是脆弱的契约,真正可靠的是内容自带的语义信号

下一步:

1. v5 patch 合入 generate-html.py:把 is_en 判定改成读 pipe 元数据(PR 待提)

2. publish-via-api.py 增加 --delete 参数:手动 curl 删文件太丑

3. **run-pipeline.py 增加「错位 HTML 检测」Phase -1**:推送前 grep </code> 标签语言</p> <p>相关内链:<a href="https:///2026-07-16-terraform-single-vps-iac-hands-on.html" target="_blank" rel="nofollow sponsored">7/16 Terraform 单 VPS IaC 实战</a>、<a href="https:///2026-07-15-coreyhaines31-marketingskills-hands-on.html" target="_blank" rel="nofollow sponsored">7/15 coreyhaines31/marketingskills 实战踩坑</a>、<a href="https:///2026-07-20-ai-automation-pipeline-concurrency-conflict-post-mortem.html" target="_blank" rel="nofollow sponsored">7/20 AI 自动化流水线并发冲突复盘</a></p><p style="margin:15px 0;"><a href="https://platform.minimaxi.com/subscribe/token-plan?code=E5yur9NOub&source=link" target="_blank" rel="nofollow sponsored" style="color:#0066cc;text-decoration:underline;">👉 Join MiniMax Token Plan: AI coding acceleration for businesses</a></p><p style="margin:15px 0;"><a href="https://www.bigmodel.cn/glm-coding?ic=XTFAUHSPC3" target="_blank" rel="nofollow sponsored" style="color:#0066cc;text-decoration:underline;">👉 Join Zhipu Coding Plan: GLM-4.6/GLM-5 coding packages, China-stable, pay-per-token unlimited</a></p><p style="margin:15px 0;"><a href="https://www.aliyun.com/minisite/goods?userCode=7nyzznme" target="_blank" rel="nofollow sponsored" style="color:#0066cc;text-decoration:underline;">👉 Join Aliyun AI: Top AI products with exclusive coupons for business innovation</a></p><p style="color:#888;font-size:0.85em;margin:15px 0;">📌 This article was AI-assisted generated and human-reviewed | <a href="/">TechPassive</a> — An AI-driven content testing site focused on real tool reviews</p> <div style="background:#fff8e1;border-left:4px solid #f39c12;padding:20px;margin:30px 0;border-radius:8px;"> <h3 style="margin:0 0 10px;color:#b7791f;">🔗 Recommended Tools</h3> <p style="margin:0 0 15px;color:#666;">These are carefully selected tools. Using our affiliate links supports us to keep producing quality content:</p> <div style="display:flex;flex-wrap:wrap;gap:10px;"> <a href="https://m.do.co/c/ef5f58bd38d2" target="_blank" rel="nofollow sponsored" style="display:inline-block;background:#0058ff;color:white;padding:8px 16px;border-radius:5px;text-decoration:none;font-size:0.9em;">☁️ DigitalOcean Cloud</a> <a href="https://www.vultr.com/?ref=9890714" target="_blank" rel="nofollow sponsored" style="display:inline-block;background:#0058ff;color:white;padding:8px 16px;border-radius:5px;text-decoration:none;font-size:0.9em;">⚡ Vultr VPS</a> <a href="https://platform.minimaxi.com/subscribe/token-plan?code=E5yur9NOub&source=link" target="_blank" rel="nofollow sponsored" style="display:inline-block;background:#00d4aa;color:white;padding:8px 16px;border-radius:5px;text-decoration:none;font-size:0.9em;">⭐ MiniMax Token Plan</a> <a href="https://www.bigmodel.cn/glm-coding?ic=XTFAUHSPC3" target="_blank" rel="nofollow sponsored" style="display:inline-block;background:#7c3aed;color:white;padding:8px 16px;border-radius:5px;text-decoration:none;font-size:0.9em;">🧩 Zhipu Coding Plan</a> <a href="https://www.bigmodel.cn/invite?icode=2Y1XM4ve1Q8IptFjg%2Fi62v2gad6AKpjZefIo3dVEQyA%3D" target="_blank" rel="nofollow sponsored" style="display:inline-block;background:#a855f7;color:white;padding:8px 16px;border-radius:5px;text-decoration:none;font-size:0.9em;">🎁 Zhipu 20M Tokens Gift</a> <a href="https://qoder.com.cn/referral?referral_code=ZKGgINVqISq1s0tajhFbWRzB6PYR34T5" target="_blank" rel="nofollow sponsored" style="display:inline-block;background:#6366f1;color:white;padding:8px 16px;border-radius:5px;text-decoration:none;font-size:0.9em;">🤖 QoderWork CN (Refer & Earn)</a> <a href="https://www.aliyun.com/minisite/goods?userCode=7nyzznme" target="_blank" rel="nofollow sponsored" style="display:inline-block;background:#ff6a00;color:white;padding:8px 16px;border-radius:5px;text-decoration:none;font-size:0.9em;">☁️ Aliyun AI Products</a> <a href="https://www.amazon.com/s?k=WordPress&i=stripbooks-intl-ship&crid=2GP4ZRUNK7CK3&sprefix=wordpress%2Cstripbooks-intl-ship%2C439&ref=nb_sb_noss_1&tag=techpassive-20" target="_blank" rel="nofollow sponsored" style="display:inline-block;background:#ff9900;color:white;padding:8px 16px;border-radius:5px;text-decoration:none;font-size:0.9em;">📚 WordPress Books</a> <a href="https://www.amazon.com/s?k=WordPress+SEO%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97&i=stripbooks-intl-ship&crid=240UCW7BT9BGN&sprefix=wordpress+seo%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97%2Cstripbooks-intl-ship%2C490&ref=nb_sb_noss&tag=techpassive-20" target="_blank" rel="nofollow sponsored" style="display:inline-block;background:#ff9900;color:white;padding:8px 16px;border-radius:5px;text-decoration:none;font-size:0.9em;">🔍 WordPress SEO Books</a> <a href="https://www.amazon.com/s?k=Web+Hosting&i=stripbooks-intl-ship&crid=2H8Q7KQ8M9LXN&sprefix=web+hosting%2Cstripbooks-intl-ship%2C397&ref=nb_sb_noss&tag=techpassive-20" target="_blank" rel="nofollow sponsored" style="display:inline-block;background:#ff9900;color:white;padding:8px 16px;border-radius:5px;text-decoration:none;font-size:0.9em;">🌐 Web Hosting Books</a> <a href="https://www.amazon.com/s?k=Docker&i=stripbooks-intl-ship&crid=3K1YB8L5E6M7N&sprefix=docker%2Cstripbooks-intl-ship%2C439&ref=nb_sb_noss&tag=techpassive-20" target="_blank" rel="nofollow sponsored" style="display:inline-block;background:#ff9900;color:white;padding:8px 16px;border-radius:5px;text-decoration:none;font-size:0.9em;">🐳 Docker Books</a> <a href="https://www.amazon.com/s?k=Linux&i=stripbooks-intl-ship&crid=4L2M3N4O5P6Q&sprefix=linux%2Cstripbooks-intl-ship%2C439&ref=nb_sb_noss&tag=techpassive-20" target="_blank" rel="nofollow sponsored" style="display:inline-block;background:#ff9900;color:white;padding:8px 16px;border-radius:5px;text-decoration:none;font-size:0.9em;">🐧 Linux Books</a> <a href="https://www.amazon.com/s?k=Python&i=stripbooks-intl-ship&crid=5M3N4O5P6Q7R&sprefix=python%2Cstripbooks-intl-ship%2C439&ref=nb_sb_noss&tag=techpassive-20" target="_blank" rel="nofollow sponsored" style="display:inline-block;background:#ff9900;color:white;padding:8px 16px;border-radius:5px;text-decoration:none;font-size:0.9em;">🐍 Python Books</a> <a href="https://www.amazon.com/s?k=Affiliate+Marketing&i=stripbooks-intl-ship&crid=6N4O5P6Q7R8S&sprefix=affiliate+marketing%2Cstripbooks-intl-ship%2C439&ref=nb_sb_noss&tag=techpassive-20" target="_blank" rel="nofollow sponsored" style="display:inline-block;background:#ff9900;color:white;padding:8px 16px;border-radius:5px;text-decoration:none;font-size:0.9em;">💰 Affiliate Marketing</a> <a href="https://www.amazon.com/s?k=Passive+Income&i=stripbooks-intl-ship&crid=7O5P6Q7R8S9T&sprefix=passive+income%2Cstripbooks-intl-ship%2C439&ref=nb_sb_noss&tag=techpassive-20" target="_blank" rel="nofollow sponsored" style="display:inline-block;background:#ff9900;color:white;padding:8px 16px;border-radius:5px;text-decoration:none;font-size:0.9em;">💵 Passive Income Books</a> <a href="https://www.amazon.com/s?k=Server&i=stripbooks-intl-ship&crid=8P6Q7R8S9T0U&sprefix=server%2Cstripbooks-intl-ship%2C439&ref=nb_sb_noss&tag=techpassive-20" target="_blank" rel="nofollow sponsored" style="display:inline-block;background:#ff9900;color:white;padding:8px 16px;border-radius:5px;text-decoration:none;font-size:0.9em;">🖥️ Server Books</a> <a href="https://www.amazon.com/s?k=Cloud+Computing&i=stripbooks-intl-ship&crid=9Q7R8T0U1V2W&sprefix=cloud+computing%2Cstripbooks-intl-ship%2C439&ref=nb_sb_noss&tag=techpassive-20" target="_blank" rel="nofollow sponsored" style="display:inline-block;background:#ff9900;color:white;padding:8px 16px;border-radius:5px;text-decoration:none;font-size:0.9em;">☁️ Cloud Computing Books</a> <a href="https://www.amazon.com/s?k=DevOps&i=stripbooks-intl-ship&crid=0R8S9T0U1V2W&sprefix=devops%2Cstripbooks-intl-ship%2C439&ref=nb_sb_noss&tag=techpassive-20" target="_blank" rel="nofollow sponsored" style="display:inline-block;background:#ff9900;color:white;padding:8px 16px;border-radius:5px;text-decoration:none;font-size:0.9em;">🚀 DevOps Books</a> </div> <div id="related-articles-placeholder" style="margin-top:15px;"><!-- Dynamic related articles injected by insert-internal-links.py --></div> </div> </div> <a href="/" class="back-btn">← 返回首页</a> </main> <script> var _hmt = _hmt || []; (function() { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?5217d6a8f8299c6b114858ac6e719e2b"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })(); </script> <div style="margin:30px 0; text-align:center;"> <ins class="adsbygoogle" style="display:block; text-align:center; margin:30px 0;" data-ad-client="ca-pub-3419621562136630" data-ad-slot="in-article" data-ad-format="auto"></ins> </div> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </body> </html>