<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>棒无</title><description>Aim lower</description><link>https://bangwu.top/</link><language>zh_CN</language><item><title>OpenClaw 新手完全攻略深挖：从“会聊天”到“能持续干活”的 6 层方法</title><link>https://bangwu.top/posts/openclaw-beginners-guide-deep-dive/</link><guid isPermaLink="true">https://bangwu.top/posts/openclaw-beginners-guide-deep-dive/</guid><description>我把《OpenClaw 新手完全攻略》主题页和其中多篇关键文章重新拆解，提炼出一套更适合个人开发者与小团队的落地框架。</description><pubDate>Thu, 05 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;先说结论&lt;/h2&gt;
&lt;p&gt;这组 OpenClaw 内容真正有价值的，不是“装起来就能用”，而是它把一个经常被忽视的事实讲清楚了：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Agent 从“会聊天”到“能干活”，中间至少隔着 6 层系统能力。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我现在的判断是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;新手最容易卡住的不是模型不够强，而是&lt;strong&gt;系统没闭环&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;多数失败不是“不会调用工具”，而是&lt;strong&gt;记忆、权限、触发、反馈没有串起来&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;真正关键的不是“再加一个 Agent”，而是先把&lt;strong&gt;编排层&lt;/strong&gt;做对&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这篇我不重复“安装步骤”，而是做一次深度拆解：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这份主题页 20 篇内容到底在讲什么&lt;/li&gt;
&lt;li&gt;哪些观点互相冲突，怎么取舍&lt;/li&gt;
&lt;li&gt;如果今天重来，我会按什么顺序搭&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;这份主题页，其实在回答同一个问题&lt;/h2&gt;
&lt;p&gt;表面看，它把文章分成了底层逻辑、安装配置、记忆身份、技能效率、场景实战、进阶架构 6 层。&lt;/p&gt;
&lt;p&gt;本质上都在回答同一个问题：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;如何把“模型能力”变成“持续可用的生产能力”。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我读完后的归纳是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;第 1 层（底层逻辑）&lt;/strong&gt;：你先得知道 OpenClaw 不是魔法，是网关、会话、工具、调度的组合&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第 2 层（安装与安全）&lt;/strong&gt;：你跑起来的不是玩具，而是一个有外部接口的执行系统&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第 3 层（记忆与身份）&lt;/strong&gt;：不把记忆落盘、不定义行为边界，Agent 迟早退化成“每次重启都失忆”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第 4 层（Skill 与效率）&lt;/strong&gt;：技能不是提示词，而是可复用工作流；效率瓶颈首先是上下文和缓存&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第 5 层（实战闭环）&lt;/strong&gt;：没有 execute → feedback → re-trigger，所有“自动化”都只是假动作&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第 6 层（编排与多 Agent）&lt;/strong&gt;：真正的杠杆在编排层，不在盲目堆执行 Agent&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;我认为最值得深挖的 4 个分歧&lt;/h2&gt;
&lt;h2&gt;1）“多 Agent 才高级” vs “单 Agent 圆桌先够用”&lt;/h2&gt;
&lt;p&gt;这是这组内容里最容易被误读的一点。&lt;/p&gt;
&lt;p&gt;一类文章强调双层架构（编排层 + 执行层）和多 Agent 并行；另一类文章反而强调“先别急着 multi-agent”。看起来矛盾，其实是&lt;strong&gt;问题定义不同&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我现在默认这么做：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你要的是“多视角思考” → 先单 Agent 圆桌（便宜、可调、可复盘）&lt;/li&gt;
&lt;li&gt;你要的是“并行执行 + 权限隔离 + 长时任务” → 再上多 Agent&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;真正关键的不是 X（Agent 数量），而是 Y（你到底在解“思考问题”还是“执行问题”）。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2）“更大上下文” vs “文件化记忆 + 按需检索”&lt;/h2&gt;
&lt;p&gt;多篇文章都在重复一个结论：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Context 不是 Memory。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Context 是一次性的、昂贵的、有窗口上限&lt;/li&gt;
&lt;li&gt;Memory 是持久的、低成本的、可检索的&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这不是概念游戏，而是成本结构问题。&lt;/p&gt;
&lt;p&gt;如果你把历史全塞进 prompt，每轮都在重复付费；如果你把记忆落到 Markdown + 检索，模型只读“这次任务需要的那一点”。&lt;/p&gt;
&lt;p&gt;我现在默认记忆策略是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;日志层：&lt;code&gt;memory/YYYY-MM-DD.md&lt;/code&gt;（append-only）&lt;/li&gt;
&lt;li&gt;长期层：&lt;code&gt;MEMORY.md&lt;/code&gt;（curated）&lt;/li&gt;
&lt;li&gt;查询层：语义 + 关键词混合检索&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这不是追新，是为了降低后续成本。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3）“能做事”优先 vs “安全边界前置”&lt;/h2&gt;
&lt;p&gt;很多人把安全当“后面再补”的工作，这在 Agent 系统里基本等于埋雷。&lt;/p&gt;
&lt;p&gt;主题页里有一条我很认同：&lt;strong&gt;威胁模型要先于配置细节。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因为 Agent 的风险不是单点漏洞，而是“工具能力 × 自动触发 × 持久记忆”的组合风险。&lt;/p&gt;
&lt;p&gt;我总结成一句话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;不要让危险动作进入队列后再拦截，要在入口就拒绝。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;也就是把限制前移到 proposal / policy gate，而不是在 worker 执行时才发现“这个不能做”。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4）“更聪明”优先 vs “缓存命中率优先”&lt;/h2&gt;
&lt;p&gt;这组内容里最“工程化”的洞见之一是：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;长上下文 Agent 的成本和延迟，往往由重复前缀决定。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;换句话说，先优化缓存命中率，收益常常比“换更贵模型”更直接。&lt;/p&gt;
&lt;p&gt;如果你的系统是循环式执行（工具调用 + 多轮上下文累积），那缓存策略应该是一级指标，而不是可选优化。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;从内容到落地：一套我会直接执行的 30 天路线&lt;/h2&gt;
&lt;p&gt;为了避免“读了很多、系统没变”，我把这批内容压成一条可执行路线。&lt;/p&gt;
&lt;h2&gt;第 1 周：先跑通最小闭环，不追求花活&lt;/h2&gt;
&lt;p&gt;目标：让系统具备最基本的 propose → execute → feedback。&lt;/p&gt;
&lt;p&gt;清单：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 先确定一个渠道（例如 Telegram）&lt;/li&gt;
&lt;li&gt;[ ] 跑通会话、工具调用、结果回传&lt;/li&gt;
&lt;li&gt;[ ] 明确只有一个执行入口（避免双 worker 竞态）&lt;/li&gt;
&lt;li&gt;[ ] 给每次任务写状态（pending/running/succeeded/failed）&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;第 2 周：补齐安全和策略门控&lt;/h2&gt;
&lt;p&gt;目标：把“能做”变成“可控地做”。&lt;/p&gt;
&lt;p&gt;清单：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 明确 threat model（谁会攻击、从哪里进来、能造成什么后果）&lt;/li&gt;
&lt;li&gt;[ ] 配置工具 allow/deny 策略&lt;/li&gt;
&lt;li&gt;[ ] 敏感动作前置审批（不是执行时才拦）&lt;/li&gt;
&lt;li&gt;[ ] 锁定凭据和文件权限&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;第 3 周：重构记忆层，不再依赖长上下文硬扛&lt;/h2&gt;
&lt;p&gt;目标：把系统从“会话记忆”升级为“文件记忆”。&lt;/p&gt;
&lt;p&gt;清单：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 建立 daily + long-term 两层记忆&lt;/li&gt;
&lt;li&gt;[ ] 为记忆增加基础分类（decision / preference / commitment / lesson）&lt;/li&gt;
&lt;li&gt;[ ] 压缩前先 flush 关键信息，避免压缩丢事实&lt;/li&gt;
&lt;li&gt;[ ] 定期清理低价值上下文，保留高价值索引&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;第 4 周：再谈多 Agent 与编排升级&lt;/h2&gt;
&lt;p&gt;目标：把并行能力建立在稳定系统上。&lt;/p&gt;
&lt;p&gt;清单：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 明确哪些任务必须并行（而不是“看起来并行很酷”）&lt;/li&gt;
&lt;li&gt;[ ] 编排层持有业务上下文，执行层只拿最小必要上下文&lt;/li&gt;
&lt;li&gt;[ ] 为子任务设置超时、重试、回收策略&lt;/li&gt;
&lt;li&gt;[ ] 建立失败后“重写 prompt 再执行”的学习循环&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;一个可以直接抄的“最小系统定义”&lt;/h2&gt;
&lt;p&gt;如果你是个人开发者，或者小团队想先做 MVP，我建议目标先定成下面这样：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;目标: 让 Agent 连续 7 天稳定完成固定任务，不人工盯盘

必须具备:
  - 单一执行入口（无双 worker 竞争）
  - 可追踪状态机（任务与步骤可审计）
  - 策略门控（敏感动作前置拒绝/审批）
  - 文件化记忆（daily + long-term）
  - 周期触发（cron）+ 事件反馈（event）

暂缓事项:
  - 复杂多 Agent 编排
  - 大而全的工具接入
  - 花哨 UI 和可视化面板
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这不是保守，而是降低系统复杂度的最优路径。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;我会避免的 5 个常见误区&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;误区&lt;/th&gt;
&lt;th&gt;结果&lt;/th&gt;
&lt;th&gt;更稳的做法&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;先堆很多 Agent&lt;/td&gt;
&lt;td&gt;编排复杂度爆炸&lt;/td&gt;
&lt;td&gt;先单 Agent + 圆桌，按需再拆&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;先求全工具权限&lt;/td&gt;
&lt;td&gt;风险边界模糊&lt;/td&gt;
&lt;td&gt;先最小权限，逐步放开&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;把记忆全塞 context&lt;/td&gt;
&lt;td&gt;成本高、速度慢、易失真&lt;/td&gt;
&lt;td&gt;文件落盘 + 按需检索&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;失败就机械重试&lt;/td&gt;
&lt;td&gt;重复犯错&lt;/td&gt;
&lt;td&gt;分析失败原因后改 prompt 再试&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;只追模型参数&lt;/td&gt;
&lt;td&gt;提升不稳定&lt;/td&gt;
&lt;td&gt;先做缓存、状态机、门控和可观测性&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;结语：真正的分水岭不是“会不会写 prompt”&lt;/h2&gt;
&lt;p&gt;这组内容最有价值的地方，是把 Agent 工程从“提示词技巧”拉回到了“系统设计”。&lt;/p&gt;
&lt;p&gt;结论再说一遍：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;OpenClaw 入门真正要跨过的门槛，不是安装，而是把系统做成闭环。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我现在默认的优先级是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;闭环先于炫技&lt;/li&gt;
&lt;li&gt;边界先于能力&lt;/li&gt;
&lt;li&gt;编排先于并行&lt;/li&gt;
&lt;li&gt;记忆先于上下文堆叠&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果你也在搭自己的 Agent 系统，这套顺序会比“追最新模型”更能帮你稳定跑起来。&lt;/p&gt;
&lt;p&gt;先把闭环做出来，再谈更复杂的编排。这样系统会稳很多，也更容易长期维护。&lt;/p&gt;
</content:encoded></item><item><title>Agent Skills 介绍与最佳实践：把经验变成可复用工作流</title><link>https://bangwu.top/posts/agent-skills-intro-best-practices/</link><guid isPermaLink="true">https://bangwu.top/posts/agent-skills-intro-best-practices/</guid><description>这篇文章给出通用 Skill 的核心概念、设计方法与落地清单，帮助你把一次性操作沉淀成稳定、可复用的自动化能力。</description><pubDate>Sat, 28 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;结论先说&lt;/h2&gt;
&lt;p&gt;如果你已经在反复做同一类任务（比如固定格式写作、发布流程、资料调研、社区巡检），最值得做的不是继续“临场发挥”，而是把它封装成 &lt;strong&gt;Skill&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我现在默认用这套判断：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;重复 3 次以上&lt;/strong&gt;的任务，开始考虑做 Skill&lt;/li&gt;
&lt;li&gt;涉及多步骤、多依赖、多边界（比如外部发布）的任务，优先做 Skill&lt;/li&gt;
&lt;li&gt;需要长期维护一致输出质量的任务，必须做 Skill&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;真正关键的不是“让 Agent 更聪明”，而是让流程更稳定、可检查、可迭代。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;什么是 Skill（以及它不是什么）&lt;/h2&gt;
&lt;p&gt;在各种 Agent 系统里，Skill 本质上是“面向任务的标准作业包”，通常包含：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;SKILL.md&lt;/code&gt;：入口说明、适用场景、决策树、执行步骤&lt;/li&gt;
&lt;li&gt;&lt;code&gt;references/*.md&lt;/code&gt;：风格规范、规则细节、发布手册&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scripts/*&lt;/code&gt;：需要脚本化的步骤（可选）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;它不是“写一段提示词就完事”。&lt;/p&gt;
&lt;p&gt;Skill 的价值在于：&lt;strong&gt;把人脑里的隐性经验，变成显性、可复用、可审计的流程&lt;/strong&gt;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;什么时候应该做 Skill&lt;/h2&gt;
&lt;p&gt;建议用这个快速判断表：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;场景&lt;/th&gt;
&lt;th&gt;是否值得封装 Skill&lt;/th&gt;
&lt;th&gt;原因&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;一次性小任务（10 分钟内）&lt;/td&gt;
&lt;td&gt;否&lt;/td&gt;
&lt;td&gt;封装成本可能高于收益&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;每周都会做的同类任务&lt;/td&gt;
&lt;td&gt;是&lt;/td&gt;
&lt;td&gt;可以持续节省沟通和返工成本&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;多工具串联流程（写作+封面+上传+发布）&lt;/td&gt;
&lt;td&gt;强烈建议&lt;/td&gt;
&lt;td&gt;最容易在步骤衔接处出错&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;高风险动作（外发、删除、改配置）&lt;/td&gt;
&lt;td&gt;强烈建议&lt;/td&gt;
&lt;td&gt;需要固定安全边界和确认机制&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
不要一把梭。先把最常做、最容易出错的一条链路封装好，再扩展。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;一个好 Skill 的结构模板&lt;/h2&gt;
&lt;p&gt;下面这个结构我在项目里反复验证过，够实用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;skills/
  your-skill/
    SKILL.md
    references/
      style-profile.md
      rules.md
      runbook.md
    scripts/
      pipeline.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;SKILL.md&lt;/code&gt; 建议固定包含这 6 部分：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Overview&lt;/strong&gt;：这个 Skill 解决什么问题&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Decision Tree&lt;/strong&gt;：用户不同诉求如何分支处理&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Execute&lt;/strong&gt;：每个阶段的操作步骤&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quality Gate&lt;/strong&gt;：交付前必须通过的检查项&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Safety&lt;/strong&gt;：哪些动作要确认、哪些不能自动做&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Examples&lt;/strong&gt;：至少 1 组可复制执行示例&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;最佳实践 1：把触发条件写具体&lt;/h2&gt;
&lt;p&gt;很多 Skill 用不好，不是能力不够，而是触发条件太模糊。&lt;/p&gt;
&lt;h3&gt;反例&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;“当用户提到文档时使用”&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;正例&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;“当用户提到 Feishu docx 链接、云文档写入、批量追加文档内容时使用”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;触发条件越清晰，路由越稳定，误触发越少。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;最佳实践 2：先决策再执行，不要边跑边想&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;SKILL.md&lt;/code&gt; 里给出明确决策树，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;新建文章 -&amp;gt; 创建新文件 -&amp;gt; 写作 -&amp;gt; 封面 -&amp;gt; 发布&lt;/li&gt;
&lt;li&gt;改稿 -&amp;gt; 编辑现有文件 -&amp;gt; 保留 frontmatter -&amp;gt; 可选重做封面&lt;/li&gt;
&lt;li&gt;只改封面 -&amp;gt; 不动正文 -&amp;gt; 仅更新 &lt;code&gt;image&lt;/code&gt; 字段&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样做的好处是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输出路径可预测&lt;/li&gt;
&lt;li&gt;复盘时能快速定位在哪个分支出问题&lt;/li&gt;
&lt;li&gt;新需求可以挂在现有分支上增量迭代&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;最佳实践 3：把“可执行片段”写进文档&lt;/h2&gt;
&lt;p&gt;我一般会要求每个 Skill 至少包含：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1 段可执行命令（或脚本调用）&lt;/li&gt;
&lt;li&gt;1 段配置示例（如 YAML）&lt;/li&gt;
&lt;li&gt;1 段风险提示与回滚建议&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;示例（YAML 配置片段）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;workflow:
  name: blog-pipeline
  stages:
    - write
    - cover
    - upload
    - verify
  safety:
    require_confirm_for:
      - publish
      - delete
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
涉及外部发布（推送、发帖、对外消息）时，不要默认自动执行。建议显式确认后再做最后一步。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;最佳实践 4：质量门禁要量化&lt;/h2&gt;
&lt;p&gt;不要只写“检查一下有没有问题”，要写成可验证条目。&lt;/p&gt;
&lt;p&gt;推荐最小门禁清单：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 必填字段完整（如 frontmatter）&lt;/li&gt;
&lt;li&gt;[ ] 关键链接可访问（HTTP 200）&lt;/li&gt;
&lt;li&gt;[ ] 构建通过（无报错）&lt;/li&gt;
&lt;li&gt;[ ] 变更文件清单清晰&lt;/li&gt;
&lt;li&gt;[ ] 交付信息完整（包含路径、哈希、下一步建议）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;有门禁，才有“稳定复现”。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;最佳实践 5：把坑写进 references，而不是写进脑子&lt;/h2&gt;
&lt;p&gt;很多团队会反复掉进同一个坑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;环境变量名写错&lt;/li&gt;
&lt;li&gt;文件路径约定不一致&lt;/li&gt;
&lt;li&gt;发布前漏 build&lt;/li&gt;
&lt;li&gt;图片格式和尺寸不统一&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我的做法是：&lt;strong&gt;每次踩坑后，立刻补一条 reference 规则&lt;/strong&gt;。这不是文档洁癖，是在降低未来沟通成本。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;常见反模式（建议避开）&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;大而全 Skill&lt;/strong&gt;：一个 Skill 包所有任务，最后谁都不敢改&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流程无边界&lt;/strong&gt;：读写发布都自动执行，缺少确认点&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;只有步骤没有判定&lt;/strong&gt;：看起来很完整，实际执行时频繁卡住&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;没有版本意识&lt;/strong&gt;：更新后不记录变化，回归问题难定位&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;我现在的默认落地流程&lt;/h2&gt;
&lt;p&gt;如果你想在一周内把 Skill 真正用起来，可以按这个节奏推进：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先选一个高频任务做 MVP Skill（不要超过 1 条主链路）&lt;/li&gt;
&lt;li&gt;跑 3 次真实任务，记录失败点&lt;/li&gt;
&lt;li&gt;补齐 &lt;code&gt;references&lt;/code&gt; 和 &lt;code&gt;Quality Gate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;再决定是否加脚本自动化&lt;/li&gt;
&lt;li&gt;最后才考虑扩展到第二个相邻任务&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这不是追新，是为了降低后续维护成本。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;结尾建议&lt;/h2&gt;
&lt;p&gt;如果你现在还在“每次都从头讲需求、从头试命令”，那就已经到了该做 Skill 的阶段。建议从你最常做、最容易返工的一条流程开始，把它写成可复用模板。&lt;/p&gt;
&lt;p&gt;先把一个 Skill 做稳，再谈规模化。&lt;/p&gt;
</content:encoded></item><item><title>K8s Gateway API + Traefik 生产上线 Checklist</title><link>https://bangwu.top/posts/k8s-gateway-api-traefik-checklist/</link><guid isPermaLink="true">https://bangwu.top/posts/k8s-gateway-api-traefik-checklist/</guid><description>一份可直接执行的生产清单 覆盖边界治理 TLS 灰度 观测 安全 回滚</description><pubDate>Fri, 27 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;先说目标&lt;/h2&gt;
&lt;p&gt;这篇不讲概念
只给可执行清单
目标是四件事&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;稳定&lt;/li&gt;
&lt;li&gt;可观测&lt;/li&gt;
&lt;li&gt;可灰度&lt;/li&gt;
&lt;li&gt;可回滚&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;上线前先对齐三个指标&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;可用性目标 例如月可用性 &lt;code&gt;&amp;gt;= 99.95%&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;延迟目标 例如 &lt;code&gt;P95 &amp;lt; 120ms&lt;/code&gt; &lt;code&gt;P99 &amp;lt; 300ms&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;恢复目标 例如 &lt;code&gt;RTO 10m&lt;/code&gt; &lt;code&gt;RPO 1m&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
没有指标就没有上线完成&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;角色边界清单&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 平台团队负责 &lt;code&gt;GatewayClass&lt;/code&gt; &lt;code&gt;Gateway&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;[ ] 业务团队负责 &lt;code&gt;HTTPRoute&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;[ ] 跨命名空间引用必须有 &lt;code&gt;ReferenceGrant&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;[ ] 业务命名空间不能直接改核心 Gateway&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Traefik 部署清单&lt;/h2&gt;
&lt;h3&gt;高可用&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] &lt;code&gt;replicas &amp;gt;= 2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;podAntiAffinity&lt;/code&gt; 已配置&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;PodDisruptionBudget&lt;/code&gt; 已配置&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;资源和弹性&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] &lt;code&gt;requests/limits&lt;/code&gt; 已配置&lt;/li&gt;
&lt;li&gt;[ ] HPA 已配置&lt;/li&gt;
&lt;li&gt;[ ] 高峰压测已通过&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;升级策略&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] &lt;code&gt;maxUnavailable: 0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;maxSurge: 1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;[ ] 滚动升级期间无中断&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Gateway 治理清单&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[ ] &lt;code&gt;allowedRoutes&lt;/code&gt; 使用白名单策略&lt;/li&gt;
&lt;li&gt;[ ] 生产环境避免直接 &lt;code&gt;from: All&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;[ ] Gateway 命名规范统一&lt;/li&gt;
&lt;li&gt;[ ] Route 命名规范统一&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;示例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: gw-public
  namespace: infra-gateway
spec:
  gatewayClassName: traefik
  listeners:
    - name: https
      protocol: HTTPS
      port: 443
      tls:
        mode: Terminate
        certificateRefs:
          - kind: Secret
            name: wildcard-example-com
      allowedRoutes:
        namespaces:
          from: Selector
          selector:
            matchLabels:
              gateway-access: &quot;true&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;TLS 清单&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 已启用 &lt;code&gt;80 -&amp;gt; 443&lt;/code&gt; 强制跳转&lt;/li&gt;
&lt;li&gt;[ ] 证书自动签发和续期可用&lt;/li&gt;
&lt;li&gt;[ ] 证书过期告警可触达&lt;/li&gt;
&lt;li&gt;[ ] TLS 最低版本策略已生效&lt;/li&gt;
&lt;li&gt;[ ] 证书 Secret 命名空间正确&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
证书问题通常不是功能故障
会直接变成线上事故&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;灰度清单&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 灰度路径已定义&lt;/li&gt;
&lt;li&gt;[ ] 权重切流脚本可执行&lt;/li&gt;
&lt;li&gt;[ ] 异常阈值已定义&lt;/li&gt;
&lt;li&gt;[ ] 自动回滚条件已定义&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;推荐节奏&lt;/p&gt;
&lt;p&gt;&lt;code&gt;1% -&amp;gt; 5% -&amp;gt; 20% -&amp;gt; 50% -&amp;gt; 100%&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;示例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: app-route
  namespace: app
spec:
  parentRefs:
    - name: gw-public
      namespace: infra-gateway
  hostnames:
    - api.example.com
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /v1
      backendRefs:
        - name: app-v1
          port: 8080
          weight: 90
        - name: app-v2
          port: 8080
          weight: 10
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;观测清单&lt;/h2&gt;
&lt;h3&gt;指标&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 网关入口 &lt;code&gt;QPS/RPS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;2xx/4xx/5xx&lt;/code&gt; 比例&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;P95/P99&lt;/code&gt; 延迟&lt;/li&gt;
&lt;li&gt;[ ] 上游错误率 重试率 超时率&lt;/li&gt;
&lt;li&gt;[ ] Traefik Pod CPU 内存 重启次数&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;日志和追踪&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 访问日志采样已启用&lt;/li&gt;
&lt;li&gt;[ ] Request ID Trace ID 已透传&lt;/li&gt;
&lt;li&gt;[ ] 错误日志按 Route 聚合&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;安全清单&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 网关命名空间独立 RBAC&lt;/li&gt;
&lt;li&gt;[ ] Traefik 控制面仅内网访问&lt;/li&gt;
&lt;li&gt;[ ] 高风险路径有 ACL 或 WAF&lt;/li&gt;
&lt;li&gt;[ ] 限流策略已生效&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;回滚清单&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 旧配置快照已保留&lt;/li&gt;
&lt;li&gt;[ ] 回滚脚本可执行&lt;/li&gt;
&lt;li&gt;[ ] 灰度观察窗口已约定&lt;/li&gt;
&lt;li&gt;[ ] 超阈值自动回滚可触发&lt;/li&gt;
&lt;li&gt;[ ] 回滚后验证脚本可执行&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;回滚流程&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;新入口灰度切流&lt;/li&gt;
&lt;li&gt;观察 &lt;code&gt;10 ~ 15&lt;/code&gt; 分钟&lt;/li&gt;
&lt;li&gt;指标超阈值立即回滚权重&lt;/li&gt;
&lt;li&gt;必要时切回旧入口&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;发布日 Runbook&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;阶段&lt;/th&gt;
&lt;th&gt;动作&lt;/th&gt;
&lt;th&gt;通过条件&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;T-30m&lt;/td&gt;
&lt;td&gt;配置冻结&lt;/td&gt;
&lt;td&gt;无临时高风险变更&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T-10m&lt;/td&gt;
&lt;td&gt;健康检查&lt;/td&gt;
&lt;td&gt;核心探针全绿&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T+0m&lt;/td&gt;
&lt;td&gt;开始灰度&lt;/td&gt;
&lt;td&gt;1% 流量成功&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T+15m&lt;/td&gt;
&lt;td&gt;提升流量&lt;/td&gt;
&lt;td&gt;指标稳定&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T+45m&lt;/td&gt;
&lt;td&gt;全量切流&lt;/td&gt;
&lt;td&gt;错误率和延迟在阈值内&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T+60m&lt;/td&gt;
&lt;td&gt;结束观察&lt;/td&gt;
&lt;td&gt;无异常告警&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;结尾&lt;/h2&gt;
&lt;p&gt;Gateway API 迁移里最关键的不是 YAML
是工程化流程&lt;/p&gt;
&lt;p&gt;我的实践重点一直是三件事&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;边界清晰&lt;/li&gt;
&lt;li&gt;观测完整&lt;/li&gt;
&lt;li&gt;回滚快速&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;把这三件事做实
入口层就能长期稳定&lt;/p&gt;
</content:encoded></item><item><title>K8s 集群使用 Gateway API 和 Traefik</title><link>https://bangwu.top/posts/k8s-gateway-api-traefik-overview/</link><guid isPermaLink="true">https://bangwu.top/posts/k8s-gateway-api-traefik-overview/</guid><description>先给结论 再讲原因和迁移路径 一篇讲清楚为什么我建议从 Ingress 迁到 Gateway API + Traefik</description><pubDate>Fri, 27 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;先说结论&lt;/h2&gt;
&lt;p&gt;我现在的默认策略&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;新服务直接用 Gateway API&lt;/li&gt;
&lt;li&gt;网关实现优先 Traefik&lt;/li&gt;
&lt;li&gt;老服务按窗口从 Ingress 分批迁移&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这不是追新
这是为了降低后续治理成本&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;为什么 Ingress 在规模上会吃力&lt;/h2&gt;
&lt;p&gt;单服务阶段 Ingress 很顺手
服务和团队变多以后会出现几个问题&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;入口能力和业务路由耦合太紧&lt;/li&gt;
&lt;li&gt;大量依赖控制器私有注解 可移植性一般&lt;/li&gt;
&lt;li&gt;多团队协作时权限边界不够清楚&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;结果就是入口层越来越难改
一次小改动也可能影响大范围流量&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Gateway API 解决的核心问题&lt;/h2&gt;
&lt;p&gt;Gateway API 不只是对象更多
核心是职责拆分更清楚&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;对象&lt;/th&gt;
&lt;th&gt;谁负责&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GatewayClass&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;平台团队&lt;/td&gt;
&lt;td&gt;定义网关实现&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Gateway&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;平台团队&lt;/td&gt;
&lt;td&gt;定义入口监听和 TLS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HTTPRoute&lt;/code&gt; &lt;code&gt;TCPRoute&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;业务团队&lt;/td&gt;
&lt;td&gt;定义业务路由&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ReferenceGrant&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;平台团队&lt;/td&gt;
&lt;td&gt;控制跨命名空间授权&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这种拆分在生产里很实用&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;平台和业务边界明确&lt;/li&gt;
&lt;li&gt;路由治理能力更强&lt;/li&gt;
&lt;li&gt;变更审计更清晰&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;为什么我选 Traefik&lt;/h2&gt;
&lt;p&gt;我关注的是落地效率和长期维护
Traefik 在这两个点都比较均衡&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;K8s 生态集成成熟&lt;/li&gt;
&lt;li&gt;Gateway API 支持稳定&lt;/li&gt;
&lt;li&gt;中间件能力够用 限流 重试 Header 处理都好用&lt;/li&gt;
&lt;li&gt;观测链路接入简单&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果团队没有强绑定其他网关栈
Traefik 是一个稳妥选择&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;最小可运行方案&lt;/h2&gt;
&lt;h3&gt;1 安装 Gateway API CRD&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.1.0/standard-install.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2 安装 Traefik 并开启 Gateway Provider&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;helm repo add traefik https://traefik.github.io/charts
helm repo update

helm upgrade --install traefik traefik/traefik \
  -n traefik --create-namespace \
  --set service.type=LoadBalancer \
  --set providers.kubernetesGateway.enabled=true
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3 创建 Gateway&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: gw-public
  namespace: infra-gateway
spec:
  gatewayClassName: traefik
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      allowedRoutes:
        namespaces:
          from: Selector
          selector:
            matchLabels:
              gateway-access: &quot;true&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4 业务服务接入 HTTPRoute&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: app-route
  namespace: app
spec:
  parentRefs:
    - name: gw-public
      namespace: infra-gateway
  hostnames:
    - api.example.com
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /v1
      backendRefs:
        - name: app-svc
          port: 8080
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;迁移时最常见的坑&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;allowedRoutes&lt;/code&gt; 过宽或过窄 导致失控或挂不上&lt;/li&gt;
&lt;li&gt;跨命名空间引用缺少 &lt;code&gt;ReferenceGrant&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;证书 Secret 在错误命名空间&lt;/li&gt;
&lt;li&gt;灰度阶段缺监控 误判问题位置&lt;/li&gt;
&lt;li&gt;直接硬切 没有并行窗口&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
入口层迁移不要一把梭
先并行 再灰度 最后收敛&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;我推荐的迁移路径&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;并行期
&lt;ul&gt;
&lt;li&gt;Ingress 和 Gateway API 同时在线&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;试点期
&lt;ul&gt;
&lt;li&gt;低风险服务先迁&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;灰度期
&lt;ul&gt;
&lt;li&gt;核心服务按权重切流&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;收敛期
&lt;ul&gt;
&lt;li&gt;指标稳定后下线旧规则&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;结尾&lt;/h2&gt;
&lt;p&gt;如果你现在还是小规模集群
继续用 Ingress 没问题&lt;/p&gt;
&lt;p&gt;如果你已经进入多团队和高频变更阶段
建议尽早切到 Gateway API&lt;/p&gt;
&lt;p&gt;我的建议还是这句&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;新服务直接 Gateway API 老服务分批迁移 Traefik 做统一入口承接&lt;/strong&gt;&lt;/p&gt;
</content:encoded></item><item><title>OpenClaw 架构分析：从消息入口到多智能体协作</title><link>https://bangwu.top/posts/openclaw-architecture-analysis/</link><guid isPermaLink="true">https://bangwu.top/posts/openclaw-architecture-analysis/</guid><description>结合实战视角拆解 OpenClaw 的核心架构：会话路由、工具层、子代理、定时任务与安全边界。</description><pubDate>Thu, 26 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;为什么值得分析 OpenClaw&lt;/h2&gt;
&lt;p&gt;很多 AI Agent 项目都卡在一个问题上：&lt;strong&gt;模型能回答问题，但系统很难长期稳定“做事”&lt;/strong&gt;。&lt;br /&gt;
OpenClaw 的有趣之处在于，它不是只做一个“会聊天的壳”，而是把「消息通道、会话管理、工具执行、定时任务、多代理协作」串成了一个可运维的体系。&lt;/p&gt;
&lt;p&gt;这篇文章不做功能罗列，而是从架构层面看它如何解决三个工程难题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;如何把不同渠道（Telegram/WhatsApp/Discord…）的消息统一成同一种会话语义&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;如何让 Agent 在“能做事”与“不越界”之间保持平衡&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;如何把一次性对话，演进成可持续运行的个人自动化系统&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;一、分层视角：OpenClaw 的五层结构&lt;/h2&gt;
&lt;p&gt;从实现职责看，可以把 OpenClaw 粗分为五层：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Channel / Surface 层&lt;/strong&gt;：接收和发送消息（Telegram、Signal、Discord 等）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Session 层&lt;/strong&gt;：把消息映射到会话，管理上下文、模型参数、权限范围&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agent Runtime 层&lt;/strong&gt;：提示词编排、工具调用决策、回复生成&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tooling 层&lt;/strong&gt;：文件、命令执行、浏览器自动化、定时任务、跨会话通信&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Orchestration 层&lt;/strong&gt;：子代理、cron、心跳、通知等异步机制&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果画成数据流，大致是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Channel Message
   -&amp;gt; Session Router
      -&amp;gt; Agent Runtime (reason + plan)
         -&amp;gt; Tool Calls (sync/async)
            -&amp;gt; Results back to Runtime
               -&amp;gt; Response / Scheduled Job / Sub-agent
                  -&amp;gt; Channel Delivery
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这套分层的好处是：&lt;strong&gt;模型逻辑和系统能力解耦&lt;/strong&gt;。模型决定“做什么”，工具层决定“怎么做”，会话层决定“在哪个边界里做”。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;二、消息与会话：把“聊天”变成“可追踪任务”&lt;/h2&gt;
&lt;p&gt;OpenClaw 的核心抽象不是单条消息，而是 &lt;strong&gt;Session&lt;/strong&gt;。&lt;br /&gt;
这意味着同一句“帮我发出去”，在不同会话里语义可以完全不同：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主会话：默认是和用户直接交互&lt;/li&gt;
&lt;li&gt;子会话：可以是隔离执行某个长期任务的 worker&lt;/li&gt;
&lt;li&gt;跨会话消息：通过 &lt;code&gt;sessions_send&lt;/code&gt; 做“任务交接”&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;关键价值&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;可追踪性&lt;/strong&gt;：任务发生在哪个会话、调用了哪些工具、最后怎么结束，都有路径&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可隔离性&lt;/strong&gt;：复杂任务可在子代理里跑，不污染主会话上下文&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可恢复性&lt;/strong&gt;：即便主会话中断，异步任务也可继续并在完成后回推结果&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这套机制非常像把“对话系统”升级成了“轻量任务编排系统”。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;三、工具层设计：能力很强，但入口统一&lt;/h2&gt;
&lt;p&gt;OpenClaw 的工具面很宽：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文件系统：&lt;code&gt;read / write / edit&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;系统执行：&lt;code&gt;exec / process&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Web：&lt;code&gt;web_search / web_fetch / browser&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;编排：&lt;code&gt;cron / sessions_spawn / subagents&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;通知与消息：&lt;code&gt;message / nodes.notify&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;虽然能力多，但调用接口统一，且被策略层约束（例如是否允许外发、是否需要确认）。&lt;br /&gt;
工程上这点很重要：&lt;strong&gt;强能力 ≠ 高风险，关键在于把风险收敛到统一策略入口&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;一个典型链路&lt;/h3&gt;
&lt;p&gt;“帮我每周一早上提醒复盘周目标”：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;主会话理解意图&lt;/li&gt;
&lt;li&gt;通过 &lt;code&gt;cron.add&lt;/code&gt; 建立定时任务&lt;/li&gt;
&lt;li&gt;到点触发 &lt;code&gt;systemEvent&lt;/code&gt; 或隔离 &lt;code&gt;agentTurn&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;通过 channel 自动推送&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;模型不需要“记住”时间，系统层负责长期可靠执行。这就是 Agent 系统与普通聊天机器人最大的分水岭。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;四、异步能力：从“即时响应”到“持续运行”&lt;/h2&gt;
&lt;p&gt;大多数 LLM 应用是同步式：问一句答一句。&lt;br /&gt;
OpenClaw 真正实用的部分在于异步机制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;cron&lt;/strong&gt;：精确定时与周期任务&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sub-agent&lt;/strong&gt;：复杂任务拆分与并行执行&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;heartbeat&lt;/strong&gt;：轻量巡检与低频主动服务&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这三者组合后，系统可以做到：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当下回答（即时）&lt;/li&gt;
&lt;li&gt;稍后完成（异步）&lt;/li&gt;
&lt;li&gt;定期触发（长期）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这对应了个人 AI 助手最关键的三个时间尺度。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;五、安全边界：不是“禁止能力”，而是“限制意图外扩”&lt;/h2&gt;
&lt;p&gt;OpenClaw 的安全策略不是简单把高风险工具全部禁掉，而是强调：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;能力在，但受上下文约束&lt;/strong&gt;（比如会话范围、授权发送者、是否允许外部动作）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;敏感动作优先人类确认&lt;/strong&gt;（外发消息、公开发布、配置变更等）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;记忆可控读取&lt;/strong&gt;（通过 memory 检索与按需片段读取）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这类设计比“全自动化”更现实。因为真实世界里，助手最怕的不是“不会做”，而是“做错且不可追责”。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;六、架构优点与潜在瓶颈&lt;/h2&gt;
&lt;h3&gt;优点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;会话抽象清晰&lt;/strong&gt;：天然支持多渠道和多任务并存&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工具扩展成本低&lt;/strong&gt;：统一工具协议，新增能力不破坏主流程&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;异步编排完整&lt;/strong&gt;：cron + 子代理让系统具备长期执行能力&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;潜在瓶颈&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;工具依赖环境强&lt;/strong&gt;：例如浏览器、CLI、外部服务可用性直接影响任务成功率&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提示词复杂度上升&lt;/strong&gt;：能力越多，策略提示越难维护&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可观测性需求高&lt;/strong&gt;：异步任务一多，日志、追踪、告警会成为刚需&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;换句话说，OpenClaw 已经越过“Demo Agent”，正在逼近“生产型个人自动化平台”的工程形态。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;七、给想落地 Agent 系统的团队三条建议&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;先把会话模型设计好，再堆工具&lt;/strong&gt;&lt;br /&gt;
没有会话边界，工具越多越混乱。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;把异步能力当一等公民&lt;/strong&gt;&lt;br /&gt;
真实任务很少都能在一个请求里完成。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;把“可审计”写进架构目标&lt;/strong&gt;&lt;br /&gt;
你迟早会遇到“它为什么这么做”的追责问题。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;OpenClaw 的启发不是“又一个 Agent 框架”，而是它把 &lt;strong&gt;LLM 能力、系统工程、运维思维&lt;/strong&gt;放在了同一个设计平面上。&lt;br /&gt;
如果你正在做个人助手、运营自动化或 AI 工作流平台，这套架构思路值得认真借鉴。&lt;/p&gt;
&lt;p&gt;后面如果你感兴趣，我可以再写一篇实操向的：&lt;br /&gt;
&lt;strong&gt;《OpenClaw 从 0 到 1：我的个人自动化工作流配置清单》&lt;/strong&gt;，把会话、cron、子代理和消息推送串成一套可直接复用的模板。&lt;/p&gt;
</content:encoded></item><item><title>Hello Arch + Niri</title><link>https://bangwu.top/posts/arch-niri-setup/</link><guid isPermaLink="true">https://bangwu.top/posts/arch-niri-setup/</guid><description>Archlinux + Niri 配置，用起来真是太爽啦</description><pubDate>Thu, 25 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这是一份我在 Arch Linux 上搭建 niri 桌面环境的完整记录。内容包含：系统与引导器、核心软件、常用快捷键、坑位与修复方案（例如飞书共享屏幕），并尽量做到“可复现”。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;0. 环境概览&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;系统：Arch Linux（UEFI）&lt;/li&gt;
&lt;li&gt;桌面：niri（Wayland）&lt;/li&gt;
&lt;li&gt;登录管理器：SDDM（自定义主题）&lt;/li&gt;
&lt;li&gt;引导器：systemd-boot&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 引导器：从 GRUB 迁移到 systemd-boot&lt;/h2&gt;
&lt;h3&gt;loader.conf&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# /boot/loader/loader.conf
default arch.conf
timeout 0
console-mode max
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;启动项&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# /boot/loader/entries/arch.conf
title   Arch Linux
linux   /vmlinuz-linux
initrd  /amd-ucode.img
initrd  /initramfs-linux.img
options root=UUID=ee47c9bd-01d0-425e-83f5-1627a27c7a78 rw quiet splash rd.systemd.show_status=false rd.udev.log_level=3
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Shell 与终端&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Shell：zsh&lt;/li&gt;
&lt;li&gt;Oh My Zsh + powerlevel10k&lt;/li&gt;
&lt;li&gt;即时提示设置为 quiet，避免 zsh 启动警告&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# ~/.zshrc
typeset -g POWERLEVEL9K_INSTANT_PROMPT=quiet
ZSH_THEME=&quot;powerlevel10k/powerlevel10k&quot;
plugins=(git zsh-autosuggestions zsh-syntax-highlighting)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;终端：Ghostty&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# ~/.config/ghostty/config
font-family = Maple Mono NF CN
font-size = 14
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;3. niri 核心配置&lt;/h2&gt;
&lt;p&gt;配置文件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;~/.config/niri/config.kdl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;~/.config/niri/binds.kdl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;~/.config/niri/output.kdl&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.1 显示器&lt;/h3&gt;
&lt;p&gt;只固定外接屏刷新率，其他保持自动：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ~/.config/niri/output.kdl
output &quot;HDMI-A-1&quot; {
    mode &quot;1920x1080@100.000&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2 桌面 Shell / Bar&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# ~/.config/niri/config.kdl
spawn-sh-at-startup &quot;qs -c noctalia-shell&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Noctalia 性能优化（内存占用下降）&lt;/h4&gt;
&lt;p&gt;我在 noctalia-shell 上关闭了动画与阴影后，内存占用明显下降，体验更稳定。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// ~/.config/noctalia/settings.json
&quot;general&quot;: {
  &quot;animationDisabled&quot;: true,
  &quot;enableShadows&quot;: false
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.3 锁屏&lt;/h3&gt;
&lt;p&gt;手动锁屏走 noctalia：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ~/.config/niri/binds.kdl
Mod+Alt+L { spawn &quot;qs&quot; &quot;-c&quot; &quot;noctalia-shell&quot; &quot;ipc&quot; &quot;call&quot; &quot;lockScreen&quot; &quot;lock&quot;; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;自动锁屏仍用 swayidle 触发：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ~/.config/niri/scripts/swayidle.sh
exec swayidle -w \
  timeout 300  &apos;qs -c noctalia-shell ipc call lockScreen lock&apos; \
  timeout 600  &apos;niri msg action power-off-monitors&apos; \
  resume       &apos;niri msg action power-on-monitors&apos; \
  timeout 1200 &apos;systemctl suspend&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 常用快捷键（节选）&lt;/h2&gt;
&lt;h3&gt;启动器与工具&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Super+T&lt;/code&gt;：Ghostty&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Super+Z&lt;/code&gt;：noctalia launcher&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Super+Alt+L&lt;/code&gt;：锁屏（noctalia）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Super+Alt+V&lt;/code&gt;：剪贴板&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;音量/亮度&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;XF86Audio*&lt;/code&gt;：音量/静音/媒体&lt;/li&gt;
&lt;li&gt;&lt;code&gt;XF86MonBrightness*&lt;/code&gt;：亮度&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;截图（grim + slurp + satty）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Print&lt;/code&gt;：全屏截图并编辑&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Shift+Print / Sys_Req&lt;/code&gt;：区域截图并编辑&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Ctrl+Print&lt;/code&gt;：窗口截图并编辑&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Super+Shift+A&lt;/code&gt;：区域截图并编辑&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 输入法与主题&lt;/h2&gt;
&lt;h3&gt;Fcitx5&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# ~/.config/fcitx5/conf/classicui.conf
Theme=Matugen
DarkTheme=Matugen
Vertical Candidate List=False
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;系统深色模式&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;gsettings set org.gnome.desktop.interface color-scheme &apos;prefer-dark&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 浏览器与触控板&lt;/h2&gt;
&lt;h3&gt;Chrome 触控板前进/后退&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# ~/.config/chrome-flags.conf
--enable-features=TouchpadOverscrollHistoryNavigation
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;触控板滚动灵敏度&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# ~/.config/niri/config.kdl
scroll-factor horizontal=0.3 vertical=0.3
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 飞书相关问题与解决&lt;/h2&gt;
&lt;h3&gt;7.1 Dock 出现灰色 + 问号双图标&lt;/h3&gt;
&lt;p&gt;原因：Wayland 下飞书窗口 &lt;code&gt;app_id&lt;/code&gt; 为空，dock 无法正确匹配。&lt;/p&gt;
&lt;p&gt;解决：强制 Feishu 走 XWayland：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ~/.config/feishu-flags.conf
--ozone-platform=x11
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在 noctalia 的固定项里用一致的 app_id：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// ~/.config/noctalia/settings.json
&quot;pinnedApps&quot;: [
  &quot;...&quot;,
  &quot;Feishu&quot;
]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7.2 飞书屏幕共享（niri）&lt;/h3&gt;
&lt;p&gt;GNOME portal 在 niri 下无法正确获取窗口列表，导致屏幕共享失败。&lt;/p&gt;
&lt;p&gt;最终方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;默认 portal 走 gnome/gtk&lt;/li&gt;
&lt;li&gt;屏幕共享走 hyprland portal&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# ~/.config/xdg-desktop-portal/portals.conf
[preferred]
default=gnome;gtk;
org.freedesktop.impl.portal.Access=gtk;
org.freedesktop.impl.portal.Notification=gtk;
org.freedesktop.impl.portal.Secret=gnome-keyring;
org.freedesktop.impl.portal.ScreenCast=hyprland;
org.freedesktop.impl.portal.RemoteDesktop=hyprland;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;niri 启动时拉起 hyprland portal：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ~/.config/niri/config.kdl
spawn-sh-at-startup &quot;dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP=niri &amp;amp; /usr/lib/xdg-desktop-portal-hyprland&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;屏幕共享可用&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;窗口级共享在 niri 下仍可能不可用（portal 限制）&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;8. 小结&lt;/h2&gt;
&lt;p&gt;这套配置的目标是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;保留 niri 的轻量与灵活&lt;/li&gt;
&lt;li&gt;让日常工具稳定可用（飞书/浏览器/输入法）&lt;/li&gt;
&lt;li&gt;关键操作有明确快捷键&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果将来要迁移或复现，只需要同步这些配置文件即可。&lt;/p&gt;
</content:encoded></item><item><title>Caddy 最佳实践与演进史</title><link>https://bangwu.top/posts/caddy/</link><guid isPermaLink="true">https://bangwu.top/posts/caddy/</guid><description>从发展历史切入，系统梳理 Caddy 的优势、架构、配置技巧与生产级落地经验</description><pubDate>Wed, 05 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;从历史看 Caddy 的野心&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;2015 年&lt;/strong&gt;：Matt Holt 发布 Caddy 1.0 Beta，定位“自动 HTTPS 的现代 Web 服务器”。彼时主流仍是 Nginx/Apache 人工申领证书，Caddy 首次将 Let&apos;s Encrypt ACME 流程写入默认行为，HTTP/2 默认开启。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2016 年&lt;/strong&gt;：引入 &lt;code&gt;Caddyfile&lt;/code&gt; 配置语法，被称为“Go 版 Nginx”。社区围绕插件（proxy, markdown, git 等）快速扩展。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2017 年&lt;/strong&gt;：Caddy 0.10 起将多租户、TLS On-Demand、自动 HTTP-&amp;gt;HTTPS 重定向等能力放入核心。官方尝试商业化授权，引发争议后在 2018 年改为 Apache-2.0 + 商业增值模式（Certified Caddy）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2020 年&lt;/strong&gt;：Caddy 2 GA，底层完全重写为模块化框架（多称“xcaddy 生态”），引入 Admin API、JSON 配置、无锁热加载。官方成立 Light Code Labs，提供企业支持。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2022 年以后&lt;/strong&gt;：默认支持 HTTP/3、自动 OCSP Stapling、Zero-downtime Reload；与 Kubernetes、Docker、Nomad、Consul 等集成插件生态完善。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对比 Nginx 从“高性能 Web 服务器”进化而来，Caddy 一开始便围绕“安全默认值、自动化”设计，目标是成为现代分发层的“操作系统”。&lt;/p&gt;
&lt;h2&gt;为什么在 2025 年仍然推荐 Caddy&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;自动 HTTPS &amp;amp; TLS 生命周期托管&lt;/strong&gt;：证书申请、续期、失效检测、OCSP Stapling 全自动。对多域名、Wildcard、On-Demand TLS（多租户 SaaS）极其友好。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;默认安全&lt;/strong&gt;：HTTP/2/HTTP/3、Strict-Transport-Security、自动重定向、合理的 Cipher Suite 默认值，大幅降低安全配置错误的风险。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;配置友好&lt;/strong&gt;：&lt;code&gt;Caddyfile&lt;/code&gt; 上手快，&lt;code&gt;caddy fmt&lt;/code&gt; 保持一致性；热加载无需重启。需要时可转换为 JSON，通过 Admin API 动态修改，适合“声明式 + 程序化”双模式。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模块化架构&lt;/strong&gt;：任何功能（服务器、路由、TLS、日志、存储、认证）都是插件，可用 &lt;code&gt;xcaddy&lt;/code&gt; 定制镜像。灵活程度远超 Nginx/Envoy 传统模块。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Go 实现，资源占用轻&lt;/strong&gt;：二进制单文件，无外部依赖，容器镜像可做到 &amp;lt; 50MB。相比 Java 生态（如 Spring Gateway）启动速度快几倍。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;原生 HTTP/3 &amp;amp; QUIC&lt;/strong&gt;：不依赖 OpenSSL，直接使用 Go 标准库 QUIC 实现；对移动端、弱网场景优化明显。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;强大的反向代理能力&lt;/strong&gt;：支持请求匹配（主机、路径、Header、Query）、重写、负载均衡、熔断、健康检查、缓存、CORS、速率限制。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;企业级扩展&lt;/strong&gt;：商业版提供集中式证书管理、SAML/LDAP 认证、Metrics/Tracing 集成；社区版亦支持 OpenTelemetry、Prometheus。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;核心架构解剖&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Caddy (Process)
├── Admin API (REST/Unix Socket)
├── Modules (caddyserver.com/docs/modules/)
│   ├── HTTP Server (caddyhttp)
│   │   ├── Routes, Matchers, Handlers
│   │   └── Reverse Proxy (caddyhttp/reverseproxy)
│   ├── TLS (caddytls)
│   ├── Logging (caddy.logging)
│   ├── Storage (certificate store: file system, Consul, etcd…)
│   └── Apps (HTTP、TLS、PKI、DNS 等)
└── Config Manager
    ├── Caddyfile
    ├── JSON (REST PUT/POST)
    └── Autoload (watch config changes)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;模块化设计&lt;/strong&gt;：Caddy 2 一切皆模块，HTTP Server 是 &lt;code&gt;apps.http&lt;/code&gt; 模块之一。加载配置后通过 dependency graph 构建 pipeline。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Admin API&lt;/strong&gt;：默认监听 &lt;code&gt;:2019&lt;/code&gt;（或 Unix socket），支持 &lt;code&gt;POST /load&lt;/code&gt;, &lt;code&gt;POST /config/apps/http/servers/srv0/routes&lt;/code&gt; 动态更新。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;证书存储&lt;/strong&gt;：默认 &lt;code&gt;~/.local/share/caddy&lt;/code&gt;, 可切换为 S3、Consul、Redis 等远程存储，使多副本共享证书。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;热重载&lt;/strong&gt;：变更配置时先启动新实例，通过引用计数停掉旧实例，无需中断现有连接。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;快速上手：安装与基础配置&lt;/h2&gt;
&lt;h3&gt;安装方式&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# Linux 官方脚本（含 systemd 服务）
curl -sfL https://get.caddyserver.com | bash -s personal

# Homebrew
brew install caddy

# Docker
docker run -p 80:80 -p 443:443 \
  -v $PWD/Caddyfile:/etc/caddy/Caddyfile \
  caddy:2-alpine

# 自定义模块
xcaddy build v2.8.4 \
  --with github.com/caddy-dns/cloudflare \
  --with github.com/mholt/caddy-dynamicdns
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Ubuntu 环境部署示例&lt;/h3&gt;
&lt;p&gt;官方推荐通过 Cloudsmith 仓库安装，以便跟随稳定版更新：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf &apos;https://dl.cloudsmith.io/public/caddy/stable/gpg.key&apos; | sudo tee /usr/share/keyrings/caddy-stable-archive-keyring.gpg &amp;gt; /dev/null
curl -1sLf &apos;https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt&apos; | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装完成后，Caddy 以 &lt;code&gt;caddy&lt;/code&gt; 系统用户运行并注册为 systemd 服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl status caddy
sudo systemctl enable --now caddy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;反向代理 Node.js（端口 3000）示例：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;编辑 &lt;code&gt;/etc/caddy/Caddyfile&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;example.com {
  reverse_proxy 127.0.0.1:3000
  encode zstd gzip
  header {
    Strict-Transport-Security &quot;max-age=31536000; includeSubDomains; preload&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;确认 DNS A/AAAA 记录指向服务器，放行 80/443 端口（如使用 UFW：&lt;code&gt;sudo ufw allow 80,443/tcp&lt;/code&gt;）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用内置格式化与配置校验：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo caddy fmt --overwrite /etc/caddy/Caddyfile
sudo caddy validate --config /etc/caddy/Caddyfile
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;热重载并查看日志：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl reload caddy
journalctl -u caddy -f
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;首次访问 &lt;code&gt;https://example.com&lt;/code&gt; 即可看到自动颁发的 Let&apos;s Encrypt 证书，后续续期与 OCSP 由 Caddy 自行处理。若需要自定义证书目录，可在 &lt;code&gt;/etc/caddy/Caddyfile&lt;/code&gt; 顶部使用全局段 &lt;code&gt;storage file_system { root /var/lib/caddy }&lt;/code&gt; 或挂载远程存储。&lt;/p&gt;
&lt;h3&gt;Hello Reverse Proxy&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Caddyfile&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;example.com {
  reverse_proxy 127.0.0.1:3000
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;caddy run --config Caddyfile --watch
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;打开浏览器访问 &lt;code&gt;https://example.com&lt;/code&gt; 即自动申请证书并转发请求。&lt;/p&gt;
&lt;h2&gt;进阶配置精要&lt;/h2&gt;
&lt;h3&gt;多域名站点 + 自动重定向&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;{
  email admin@example.com
  auto_https disable_redirects # 自定义重定向
}

example.com, www.example.com {
  @httpScheme protocol http
  handle @httpScheme {
    redir https://{host}{uri} permanent
  }

  encode zstd gzip

  reverse_proxy {
    to app1:8080 app2:8080
    lb_policy least_conn
    health_interval 20s
    health_timeout 5s
  }

  log {
    output file /var/log/caddy/example.log {
      roll_size 20MB
      roll_keep 7
    }
    format json
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;动态配置（Admin API）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST http://localhost:2019/load -H &quot;Content-Type: application/json&quot; -d @config.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置片段（JSON）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;apps&quot;: {
    &quot;http&quot;: {
      &quot;servers&quot;: {
        &quot;srv0&quot;: {
          &quot;listen&quot;: [&quot;:443&quot;],
          &quot;routes&quot;: [
            {
              &quot;match&quot;: [{ &quot;host&quot;: [&quot;api.example.com&quot;] }],
              &quot;handle&quot;: [
                {
                  &quot;handler&quot;: &quot;reverse_proxy&quot;,
                  &quot;upstreams&quot;: [{ &quot;dial&quot;: &quot;api:9000&quot; }]
                }
              ]
            }
          ]
        }
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;TLS 自动化选项&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;*.tenant.example.com {
  tls {
    on_demand            # 客户首次访问时动态申请证书
    issuer acme {
      email tls@example.com
      dns cloudflare {env.CLOUDFLARE_API_TOKEN}
    }
    renew_before 720h    # 提前 30 天续期
  }

  reverse_proxy {host}.backend.internal:8443
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On-Demand TLS 需配合访问控制（例如 &lt;code&gt;tls.on_demand ask https://tenant-registry/api/check&lt;/code&gt;）避免被恶意滥用。&lt;/p&gt;
&lt;h3&gt;HTTP/3 与 QUIC 调优&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;默认开启 HTTP/3；如需关闭：&lt;code&gt;auto_https disable_certs&lt;/code&gt; + &lt;code&gt;servers { protocols { enable h3 disable h1 h2 } }&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;对移动端可增加 &lt;code&gt;header Response add Alt-Svc &quot;h3=\&quot;:443\&quot;; ma=86400&quot;&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;缓存与边缘处理&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;https://assets.example.com {
  root * /var/www/assets
  file_server

  header {
    Strict-Transport-Security &quot;max-age=31536000; includeSubDomains; preload&quot;
    Cache-Control &quot;public, max-age=31536000, immutable&quot;
  }

  handle_path /api/* {
    reverse_proxy backend:8080
    header_up X-Forwarded-Proto {scheme}
    header_up X-Request-Id {http.request.id}
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;与 Nginx / Traefik / Envoy 的对比&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特性&lt;/th&gt;
&lt;th&gt;Caddy&lt;/th&gt;
&lt;th&gt;Nginx&lt;/th&gt;
&lt;th&gt;Traefik&lt;/th&gt;
&lt;th&gt;Envoy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;自动 HTTPS&lt;/td&gt;
&lt;td&gt;✅ 默认&lt;/td&gt;
&lt;td&gt;❌ 需 Certbot&lt;/td&gt;
&lt;td&gt;✅ Provider&lt;/td&gt;
&lt;td&gt;❌ 外部实现&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;配置体验&lt;/td&gt;
&lt;td&gt;Caddyfile/JSON/REST&lt;/td&gt;
&lt;td&gt;nginx.conf&lt;/td&gt;
&lt;td&gt;TOML/YAML/CRD&lt;/td&gt;
&lt;td&gt;YAML/ADS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;动态配置&lt;/td&gt;
&lt;td&gt;Admin API&lt;/td&gt;
&lt;td&gt;需 reload&lt;/td&gt;
&lt;td&gt;Provider/动态发现&lt;/td&gt;
&lt;td&gt;xDS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP/3 支持&lt;/td&gt;
&lt;td&gt;内建稳定&lt;/td&gt;
&lt;td&gt;实验 (依赖 quic patch)&lt;/td&gt;
&lt;td&gt;实验&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;模块扩展&lt;/td&gt;
&lt;td&gt;Go 模块 &lt;code&gt;xcaddy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;编译模块&lt;/td&gt;
&lt;td&gt;插件&lt;/td&gt;
&lt;td&gt;自定义 Filter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;性能&lt;/td&gt;
&lt;td&gt;优秀 (Go, 多核友好)&lt;/td&gt;
&lt;td&gt;极佳 (C 基础)&lt;/td&gt;
&lt;td&gt;良&lt;/td&gt;
&lt;td&gt;极佳 (C++)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;学习曲线&lt;/td&gt;
&lt;td&gt;低&lt;/td&gt;
&lt;td&gt;中&lt;/td&gt;
&lt;td&gt;中&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;适用场景&lt;/td&gt;
&lt;td&gt;Web 前端、SaaS、多租户&lt;/td&gt;
&lt;td&gt;老牌反向代理&lt;/td&gt;
&lt;td&gt;云原生 ingress&lt;/td&gt;
&lt;td&gt;微服务服务网格&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Caddy 不追求取代所有场景，但在希望“快速上线、安全默认、自动化证书”的团队中胜率很高。&lt;/p&gt;
&lt;h2&gt;生产实践建议&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;版本管理&lt;/strong&gt;：固定到 &lt;code&gt;caddy:2.x.y&lt;/code&gt;，关注 &lt;a href=&quot;https://github.com/caddyserver/caddy/releases&quot;&gt;Release Notes&lt;/a&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;配置托管&lt;/strong&gt;：将 &lt;code&gt;Caddyfile&lt;/code&gt; 与 JSON 配置放入 Git，使用 &lt;code&gt;caddy fmt&lt;/code&gt; + Pre-commit 保持规范。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;日志与监控&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;log { format json }&lt;/code&gt; 便于 ELK/ClickHouse 分析。&lt;/li&gt;
&lt;li&gt;启用 &lt;code&gt;metrics&lt;/code&gt; 模块，或集成 Prometheus Exporter。&lt;/li&gt;
&lt;li&gt;利用 &lt;code&gt;admin metrics&lt;/code&gt; 暴露运行指标。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;证书持久化&lt;/strong&gt;：在容器部署时挂载 &lt;code&gt;/data&lt;/code&gt;，或使用远程存储模块避免容器重建导致重新申请证书。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;安全&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;限制 Admin API 仅监听 Unix socket 或内网地址。&lt;/li&gt;
&lt;li&gt;对 on-demand TLS 设置 &lt;code&gt;ask&lt;/code&gt; 校验接口。&lt;/li&gt;
&lt;li&gt;加固 &lt;code&gt;auto_https disable_redirects&lt;/code&gt; 场景中的手动重定向链，防止开放端口被利用。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;高可用&lt;/strong&gt;：多实例部署时共享证书存储，或通过 &lt;code&gt;caddy.cluster.consul&lt;/code&gt; 模块同步状态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能调优&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;合理设置 &lt;code&gt;reverse_proxy&lt;/code&gt; 的 &lt;code&gt;max_requests&lt;/code&gt;、&lt;code&gt;max_conns&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;在高并发场景打开 &lt;code&gt;buffers&lt;/code&gt; 和响应压缩。&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;layer4&lt;/code&gt; 模块处理 TCP/UDP（数据库代理）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;典型落地场景&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;个人/团队博客&lt;/strong&gt;：一套 Caddyfile 管理多个站点，Let’s Encrypt 自动续期免维护。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SaaS 多租户域名&lt;/strong&gt;：On-Demand TLS + DNS Provider 模块 + 动态 upstream。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;微服务网关&lt;/strong&gt;：配合 Consul/Etcd 服务发现模块、JWT 验证、速率限制。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;静态资源/CDN 边缘节点&lt;/strong&gt;：结合 Rclone/S3 同步，利用 HTTP/3 提升移动端体验。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DevOps 内部工具&lt;/strong&gt;：通过 &lt;code&gt;trusted_proxies&lt;/code&gt;、Basic Auth/OAuth 插件保护内部管理端。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;学习路线与工具&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;caddy help&lt;/code&gt;、&lt;code&gt;caddy list-modules&lt;/code&gt;：快速了解已安装模块。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;caddy fmt&lt;/code&gt;：格式化 Caddyfile。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;caddy adapt --config Caddyfile --adapter caddyfile --pretty&lt;/code&gt;：将 Caddyfile 转换为 JSON，便于自动化。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://caddyserver.com/docs/build&quot;&gt;xcaddy&lt;/a&gt; 构建带自定义模块的二进制。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://caddyserver.com/docs/&quot;&gt;官方文档&lt;/a&gt; + &lt;a href=&quot;https://caddy.community/&quot;&gt;Caddy Community Forum&lt;/a&gt;。&lt;/li&gt;
&lt;li&gt;深入阅读 Matt Holt 的博客与演讲，如 “Caddy 2: The Ultimate Modern Web Server”。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;十年间，Caddy 从“自动 HTTPS 小众服务器”成长为成熟的边缘分发平台。它在默认安全、配置体验、动态化方面的设计理念，与现代应用架构的诉求高度契合。对追求“少折腾、快上线、易维护”的团队而言，Caddy 不只是 Nginx 的替代品，更是一次重新思考反向代理和 TLS 生命周期管理的机会。&lt;/p&gt;
&lt;p&gt;掌握本文提到的历史视角、优势与实战经验，你可以更有底气地在项目中引入 Caddy，并构建稳定可靠的应用交付层。&lt;/p&gt;
</content:encoded></item><item><title>勇气</title><link>https://bangwu.top/posts/courage/</link><guid isPermaLink="true">https://bangwu.top/posts/courage/</guid><description>关于勇气与表达：从等待到敢说出口</description><pubDate>Thu, 31 Jul 2025 16:07:12 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;audio controls data-title=&quot;好不容易&quot; data-artist=&quot;告五人&quot; data-cover=&quot;https://cdn.bangwu.top/img/20260111vsvkg-ot.webp&quot;&amp;gt;
&amp;lt;source src=&quot;https://cdn.bangwu.top/img/20260111x6hht-ga.mp3&quot; type=&quot;audio/mpeg&quot;&amp;gt;
&amp;lt;/audio&amp;gt;&lt;/p&gt;
&lt;p&gt;你有勇气吗？“我没有勇气，遇到一点事情就打退堂鼓”一个朋友对我这样说到。想想之前，勇气是什么我也不知道，之前想在2024年终总结中说说这件事所以2024的年终总结我命名为勇气，可是在命名好标题后就放在那里了，我想当时的我是知道勇气是什么的因为2024的我真的变勇敢了好多。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/20260111ab981732.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;最近给朋友写了一封信，里面有提到“我想我们都有勇气去面对，因为我们都太年轻”前半段是我真的想说的，后半段是之前看哪吒觉得很帅的一句台词“因为我们都太年轻，不知道天高地厚”，其实我想表达的是做什么都要勇敢一点，勇敢和胆小、腼腆、内向是不一样的，就像我之前和她说“未来会怎样？我不知道，但请勇敢一点！”我很少说教都是去建议，真心建议大家都勇敢一点。&lt;/p&gt;
&lt;p&gt;我最擅长的就是等待，等待不远不近的人到我身边，等待对方先开口邀请我，等待一个好的机会说出未说出口的话...后来大多数等待的结果都是不了了之。留下一个又一个遗憾，有的随着酒一同消散在清晨，有的不论怎么不在意都忘不掉，我在之前的文章里也说过: &lt;a href=&quot;https://bangwu.top/posts/initiative-passive&quot;&gt;主动与被动&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;亲爱的朋友，请不要缺少对一切说再见的勇气。被动与否，请果断一点，大声地说出 “再见”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;主动说出来不代表自己占了下风或者陷入了被动，他什么都没代表就只是主动而已，是啊，就是说出了自己的想法做自己想做的事情，仅此而已。越是对它加重砝码就越是难以开口，回过头来想想，其实也没什么。只觉得如果不发生那种后果的话，面子尊严什么的一切都不重要了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/202601114011ad3e.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;所以勇敢的第一步——表达自己的想法。后来我对开头的那个朋友说：“在我这里你没有什么是不能说的，这是我们两个之间的问题不光是你自己的😊”我希望我们两个都能勇敢一点，有时候对她说的话也是在对我自己说呀。有时候怀疑自己说的这些真的有用吗？有一段时间我频繁的追问是否还记得那些诺言那些说过的话经历过的事情，她说不记得了。当时觉得，哇 我说的那么多重要的话只有自己记得了吗，只有自己在意这些说过的话做的事情吗。&lt;/p&gt;
&lt;p&gt;是的，这个世界只有你自己在乎自己，勇气能改变世界。其实不需要太多的慰藉也能够迈出那一步，不需要在脑海中构想千万遍“要是我这么说他xxx怎么办”“最坏的情况也就是xxx吧，加油你可以的”我觉得相反，正是这些想法束缚了你，在世界下起倾盆大雨的时候没人会思考是否要撑开那把伞，迎上来的只有密密麻麻的雨滴砸在脸上，这时候我大概会伸出手张开手掌感受雨的力量，正如我勇敢的面对这个世界一样，一切发生的都是那么自然。&lt;/p&gt;
&lt;p&gt;我说这个世界没人在乎你，又何尝不是对自己的一个慰藉。世界上还有一些在意自己的人，我们的爸爸妈妈，亲人恋人...可勇气，只能自己来面对。我很喜欢行走江湖的侠客，把什么事都放在酒里，也没觉得有必要说出来表现出来，有种很孤独的感觉但内心丰盈，但不论是历经沧桑还是初入世事，勇气，是指引前行必不可少的动力，在明知道结局的情况下还要勇敢迈出那一步，知道前路艰险的时候依然选择继续前行。&lt;/p&gt;
&lt;p&gt;祝——世界上没有胆怯的小孩～&lt;/p&gt;
&lt;p&gt;“我的心
你放在哪里
或许你
根本就不在意”&lt;/p&gt;
</content:encoded></item><item><title>做一个留痕迹的人</title><link>https://bangwu.top/posts/trace/</link><guid isPermaLink="true">https://bangwu.top/posts/trace/</guid><description>做一个留痕迹的人：记录、创作与自我证明</description><pubDate>Sat, 05 Jul 2025 17:32:16 GMT</pubDate><content:encoded>&lt;p&gt;ps：好久没发文章了，有时候忙着忙着就没心情写了，当然，还是因为幸福让人江郎才尽呀🥰【 其实也不是，懒😪】&lt;/p&gt;
&lt;p&gt;&amp;lt;audio controls data-title=&quot;孤岛&quot; data-artist=&quot;赵二&quot; data-cover=&quot;https://cdn.bangwu.top/img/20260111w5k94-wl.webp&quot;&amp;gt;
&amp;lt;source src=&quot;https://cdn.bangwu.top/img/20260111e2b9c47e.mp3&quot; type=&quot;audio/mpeg&quot;&amp;gt;
&amp;lt;/audio&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/20260111d92a0206.jpg&quot; alt=&quot;bangwu20250706013051&quot; /&gt;&lt;/p&gt;
&lt;p&gt;最近发生的一系列事情都告诉我：做一个留痕迹的人，没什么不好。【V2EX 521天留念】
&lt;img src=&quot;https://cdn.bangwu.top/img/2026011147c3c22a.jpg&quot; alt=&quot;bangwu20250529135149&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这个世界很是奇怪，有人历经千难万险只为见你一面，有人却反反复复抛弃了你千万遍。而我们一直所追求的 其实是同一个东西，只是有人在追寻的路上慢慢把它看成了别的样子，有人在路上看到了纸醉金迷，于是它就慢慢被看成了金色，有句话是这样的“你对我的百般注解和认识，并不构成万分之一的我，却是一览无余的你自己”很多时候，我们见到的都是镜子中的自己啊。我喜欢记录，所以有了一篇又一篇日记和文章，一张张不太有美学的照片和舍不得删的视频、聊天记录...&lt;/p&gt;
&lt;p&gt;举个平常的的例子 git 记录：在一次结课项目的答辩上，我意识到了做事留痕是有多么重要。作为一个写代码的初学者，从一开始就了解到了 git 作为项目的版本管理工具，这是一个程序员的必修课因为一个项目在构建时总有不确定的因素，这时候需要回滚版本有记录也方便了解项目历史、团队协作等等，在写这段话的时候我特意去看了一下随意的提交历史，最后一次提交竟然定格在四个月前了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/20260111a368104f.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;不过这也没关系，在那天答辩我在讲台上说着“由于选择了网站类型，就以浏览器访问的形式来展示了&lt;a href=&quot;https://yrzx.bangwu.top/&quot;&gt;https://yrzx.bangwu.top/&lt;/a&gt; ”（因为是一个计算机综合实践课，项目可以是多种形式，但是老师课上讲的是 Adobe Animate 和 PS，也有声明过可以做动态网页）我点开浏览器在导航栏熟练的输入网址，解释完后就播放了最下面的音乐，我是一个《一人之下》迷，所以选择做一个相关的网页，音乐是&lt;a href=&quot;https://music.163.com/song?id=2021721966&amp;amp;uct2=U2FsdGVkX1+YmuuQfXnwdJRwLlnDAbFeQ+92czKCwJY=&quot;&gt;再见，陈朵&lt;/a&gt;很好听！当然我也没太抱希望台下的同学们和老师有多大反应，因为我在做自己喜欢的事情，自己喜欢就好。好吧，真实的心理活动是“小爷我这么精心准备的，那么好看的网页展示出来你们不惊掉下巴啊😎”事实并非如此，台下并不会有任何反馈，后面迎来的却是老师的质疑？？？这个项目当然是我自己写的，问我动画实现“motion”估计他也没听过...嗯 在大三下学期遇到了网上经常开玩笑的那种“老古董老师”课后，我找到老师给他看我的 git 记录这时候不得不感慨一下，多亏了当时的自己知道用 git 啊。所以是，这个世界上并不会有人理解你相信你，just do yourself. 清楚的知道自己在做什么，并且自豪的展示给别人的时候，真的很快乐自信。&lt;/p&gt;
&lt;p&gt;后来我也没有再联系到那个老师，课程也在我最后的解释后离开教室而终止，走在路上我和朋友吐槽着刚刚遇到的奇葩事情然后就过去了，至于那个结课项目，谁知道呢。它早就从我本地删除躺在 github 的私人仓库里面，永远也不会再被看到，永远.....&lt;/p&gt;
&lt;p&gt;那就改变自身观念——《沉默的大多数》中花剌子模信使与《当下的力量》中的ABC理论都说，事情已经发生无论怎么样都会是这个结局了，我们能做的就是改变对这件事情的看法，当然有的人蒙蔽自己的双眼不愿接受，把自己关进暗无天日的沼泽之地，有的人欣然接受这个结果，继续踏上自己的大道。&lt;/p&gt;
&lt;p&gt;我们总觉得别人幸福很容易，自己快乐却很困难，那如果把自己的快乐建立在别人的幸福之上，是不是，很容易快乐啊🥰&lt;/p&gt;
&lt;p&gt;对了，发现最近用 AI 越来越频繁了，之前刷到那个外国教授在课堂上因为学生都用 AI 回答问题而发火，然后评论区有个评论彻底把我逗笑了“ChatGPT 帮我安慰一下老师”其实笑归笑，我有时候甚至连怎么回复邮件、怎么回复别人、怎么开玩笑、怎么安慰别人....
这些最基本的都想过让 AI 来帮我完成，脑子秀逗这方面还是有的，但我更害怕的是我不再是我了，我不再有自己的思考方式，就如同傀儡一般。
刚好看到这样一篇论文&lt;a href=&quot;https://arxiv.org/abs/2506.08872&quot;&gt;Your Brain on ChatGPT: Accumulation of Cognitive Debt when Using an AI Assistant for Essay Writing Task&lt;/a&gt;我开玩笑说：“ChatGPT，帮我总结一下这篇论文”
“散”
“残梦终于消散”
“像飞走的船”
“让孤岛难堪”&lt;/p&gt;
</content:encoded></item><item><title>Linux服务器初始化指南</title><link>https://bangwu.top/posts/linuxstart/</link><guid isPermaLink="true">https://bangwu.top/posts/linuxstart/</guid><description>新到手的服务器的一些基本配置</description><pubDate>Tue, 10 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;&lt;a href=&quot;https://wiki.archlinuxcn.org/wiki/SSH_%E5%AF%86%E9%92%A5&quot;&gt;Autherized Keys&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这个对于需要频繁输入密码却又不想复制粘贴的特别有帮助,例如vscode远程开发&lt;/p&gt;
&lt;p&gt;需要先生成自己的&lt;code&gt;ssh-key&lt;/code&gt;如果没有的话, 然后只将公钥放到服务器上&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh-keygen -t rsa -C &quot;your_email@example.com&quot;
cd ~/.ssh
cat id_rsa.pub &amp;gt;&amp;gt; authorized_keys
sudo nano /etc/ssh/sshd_config
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在最后面输入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RSAAuthentication yes
PubkeyAuthentication yes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后执行重启ssh服务&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl restart sshd
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href=&quot;https://www.haoyep.com/posts/zsh-config-oh-my-zsh/&quot;&gt;Oh-my-zsh&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;或者可选其他终端,已经被&lt;code&gt;oh my zsh&lt;/code&gt;下毒了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update &amp;amp;&amp;amp; sudo apt upgrade -y
sudo apt install zsh git curl -y
chsh -s /bin/zsh # 切换默认终端为zsh

sh -c &quot;$(curl -fsSL https://install.ohmyz.sh/)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意如果之前使用&lt;code&gt;bash&lt;/code&gt;一段时间,记得迁移环境变量&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 查看bash配置文件，并手动复制自定义配置
cat ~/.bashrc
# 编辑zsh配置文件，并粘贴自定义配置
nano ~/.zshrc
source ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置主题我只用&lt;code&gt;powerlevel10k&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在 &lt;code&gt;~/.zshrc&lt;/code&gt; 设置 &lt;code&gt;ZSH_THEME=&quot;powerlevel10k/powerlevel10k&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;插件的话我只启用三个&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions

git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting

sudo nano ~/.zshrc
# plugins=(git zsh-autosuggestions zsh-syntax-highlighting)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href=&quot;https://docs.brew.sh/Homebrew-on-Linux&quot;&gt;brew&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;export HOMEBREW_BREW_GIT_REMOTE=&quot;https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git&quot;

git clone --depth=1 https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/install.git brew-install
/bin/bash brew-install/install.sh
rm -rf brew-install
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>PostgREST 入门与实践</title><link>https://bangwu.top/posts/postgrest/</link><guid isPermaLink="true">https://bangwu.top/posts/postgrest/</guid><description>用 PostgREST 为 PostgreSQL 自动生成 REST API 的部署、认证与查询技巧</description><pubDate>Thu, 29 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;PostgREST 是什么&lt;/h2&gt;
&lt;p&gt;PostgREST 能把 PostgreSQL 数据库直接封装成符合 RESTful 规范的 API 服务器。你无需再写一层 Node/Go 服务，只要约定好角色权限、视图和函数，它会自动完成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;按照表/视图结构生成标准的 CRUD 接口；&lt;/li&gt;
&lt;li&gt;基于 SQL 权限模型实现细粒度的访问控制；&lt;/li&gt;
&lt;li&gt;支持过滤、排序、分页、多表关联等查询；&lt;/li&gt;
&lt;li&gt;集成 JWT 鉴权用于多租户或行级安全（RLS）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这篇笔记介绍如何快速搭建、配置 JWT、梳理常用查询操作符，以及一些上线经验。&lt;/p&gt;
&lt;h2&gt;Docker Compose 快速启动&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;version: &apos;3&apos;
services:
  db:
    image: postgres:16
    environment:
      POSTGRES_DB: appdb
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: change_me
    volumes:
      - ./data:/var/lib/postgresql/data
    ports:
      - &quot;5432:5432&quot;

  postgrest:
    image: postgrest/postgrest:v12.0.2
    depends_on:
      - db
    ports:
      - &quot;3000:3000&quot;
    environment:
      PGRST_DB_URI: postgres://admin:change_me@db:5432/appdb
      PGRST_DB_SCHEMAS: api
      PGRST_DB_ANON_ROLE: anon
      PGRST_OPENAPI_SERVER_PROXY_URI: http://127.0.0.1:3000
      PGRST_JWT_SECRET: my_super_secret_32_bytes_key
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关键环境变量说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PGRST_DB_URI&lt;/code&gt;：连接字符串，至少使用具备 &lt;code&gt;USAGE&lt;/code&gt; 权限的用户。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PGRST_DB_SCHEMAS&lt;/code&gt;：暴露给 API 的 schema，建议单独建 &lt;code&gt;api&lt;/code&gt; schema 放视图和函数。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PGRST_DB_ANON_ROLE&lt;/code&gt;：匿名访问使用的 PostgreSQL role。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PGRST_JWT_SECRET&lt;/code&gt;：JWT 共享密钥，生产环境请通过 &lt;code&gt;openssl rand -base64 32&lt;/code&gt; 生成并写入 &lt;code&gt;.env&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
如果部署在反向代理之后，需要把 &lt;code&gt;PGRST_SERVER_PROXY_URI&lt;/code&gt; 或 &lt;code&gt;PGRST_OPENAPI_SERVER_PROXY_URI&lt;/code&gt; 指向外部访问域名，确保 OpenAPI 文档和重定向正确。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;角色与权限模型&lt;/h2&gt;
&lt;p&gt;PostgREST 完全依赖 PostgreSQL 的角色/权限体系。推荐的最小结构如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- 匿名角色（仅能 GET）
create role anon nologin;
grant usage on schema api to anon;
grant select on all tables in schema api to anon;

-- 受信任角色（可以写）
create role webuser nologin;
grant usage on schema api to webuser;
grant select, insert, update, delete on all tables in schema api to webuser;

-- 将数据库登录用户映射到这些角色
create role admin login password &apos;change_me&apos;;
grant anon, webuser to admin;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;匿名请求默认使用 &lt;code&gt;PGRST_DB_ANON_ROLE&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;带 JWT 的请求如果包含 &lt;code&gt;role&lt;/code&gt; 字段，PostgREST 会尝试切换到目标角色（需确保 token 里的角色是登录角色的成员）。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
PostgreSQL 默认不会自动把权限应用到新表/视图。可使用 &lt;code&gt;ALTER DEFAULT PRIVILEGES&lt;/code&gt; 或数据库迁移工具（如 Sqitch、Flyway）统一维护。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;准备 API Schema&lt;/h2&gt;
&lt;p&gt;在业务 schema（例如 &lt;code&gt;public&lt;/code&gt;）里定义真实表，再在 &lt;code&gt;api&lt;/code&gt; schema 中创建视图/函数对外暴露。这样可以隐藏内部字段，并内置联表逻辑。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;create schema if not exists api;

create table public.todos (
  id serial primary key,
  user_id int not null,
  title text not null,
  done boolean default false,
  inserted_at timestamptz default now()
);

create view api.todos as
  select id, title, done, inserted_at
  from public.todos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重载 &lt;code&gt;/todos&lt;/code&gt; 接口即可拿到 &lt;code&gt;api.todos&lt;/code&gt; 视图结果，&lt;code&gt;user_id&lt;/code&gt; 字段不会被暴露。&lt;/p&gt;
&lt;h2&gt;启用行级安全（RLS）&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;alter table public.todos enable row level security;

create policy select_own_todos on public.todos
  for select
  using (current_setting(&apos;request.jwt.claim.user_id&apos;, true)::int = user_id);

create policy write_own_todos on public.todos
  for all
  using (current_setting(&apos;request.jwt.claim.user_id&apos;, true)::int = user_id)
  with check (current_setting(&apos;request.jwt.claim.user_id&apos;, true)::int = user_id);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当请求携带的 JWT 包含 &lt;code&gt;user_id&lt;/code&gt; claim 时，PostgREST 会写入 &lt;code&gt;request.jwt.claim.user_id&lt;/code&gt; 这个 GUC 变量，策略自然就生效。&lt;/p&gt;
&lt;h2&gt;生成 JWT&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;openssl rand -base64 32
# 复制结果写入 PGRST_JWT_SECRET
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例 payload：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;role&quot;: &quot;webuser&quot;,
  &quot;user_id&quot;: 42
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将 payload 使用 &lt;code&gt;HS256&lt;/code&gt; 与密钥签名后即可调用受保护接口。可以用 &lt;a href=&quot;https://jwt.io/#debugger-io&quot;&gt;jwt.io&lt;/a&gt; 生成并调试。&lt;/p&gt;
&lt;p&gt;请求示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl https://api.example.com/todos \
  -H &quot;Authorization: Bearer $TOKEN&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;查询语法速查&lt;/h2&gt;
&lt;p&gt;PostgREST 通过 URL 查询参数映射 SQL 过滤器。核心操作符示例：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;操作&lt;/th&gt;
&lt;th&gt;语法&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;等于&lt;/td&gt;
&lt;td&gt;&lt;code&gt;?done=eq.true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;eq&lt;/code&gt; 表示 &lt;code&gt;=&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;模糊&lt;/td&gt;
&lt;td&gt;&lt;code&gt;?title=ilike.*bug*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ilike&lt;/code&gt; 支持大小写不敏感&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;范围&lt;/td&gt;
&lt;td&gt;&lt;code&gt;?inserted_at=gte.2025-01-01&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gte/gt/lte/lt&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;IN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;?id=in.(1,2,3)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;逗号分隔&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;嵌套 &lt;code&gt;OR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;?or=(done.eq.true,title.ilike.*urgent*)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;记得 URL encode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSONB&lt;/td&gt;
&lt;td&gt;&lt;code&gt;?meta-&amp;gt;&amp;gt;status=eq.active&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;支持 &lt;code&gt;-&amp;gt;&lt;/code&gt;、&lt;code&gt;-&amp;gt;&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;分页与排序&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;排序：&lt;code&gt;?order=inserted_at.desc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;限制：&lt;code&gt;?limit=20&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;游标翻页：使用 &lt;code&gt;Range&lt;/code&gt; 头或 &lt;code&gt;?offset=20&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;服务端会在响应头给出 &lt;code&gt;Content-Range&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;多表嵌套&lt;/h3&gt;
&lt;p&gt;PostgREST 支持通过外键自动嵌套资源：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;alter table public.todos
  add constraint todos_user_fk foreign key (user_id) references public.users(id);

create view api.users as
  select id, email from public.users;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;请求 &lt;code&gt;GET /users?select=id,email,todos(id,title)&lt;/code&gt; 会自动返回嵌套 JSON。&lt;/p&gt;
&lt;h3&gt;调用存储过程&lt;/h3&gt;
&lt;p&gt;PostgREST 将 SQL 函数映射为 RPC 接口，例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;create function api.complete_todo(_id int)
returns public.todos as $$
  update public.todos
     set done = true
   where id = _id
   returning *;
$$ language sql security definer;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;调用：&lt;code&gt;POST /rpc/complete_todo&lt;/code&gt;，请求体为 &lt;code&gt;{&quot;_id&quot;: 1}&lt;/code&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
函数默认运行在调用者角色下。若需提升权限，请使用 &lt;code&gt;security definer&lt;/code&gt; 并仔细设定 &lt;code&gt;search_path&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;常见排错&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;接口 401/403&lt;/strong&gt;：确认 JWT 是否包含 &lt;code&gt;role&lt;/code&gt;，且该角色被登录用户授予；检查 RLS 是否放行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;新建表无法访问&lt;/strong&gt;：忘记给 &lt;code&gt;api&lt;/code&gt; schema 或新表授权，或视图没同步。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenAPI 缺字段&lt;/strong&gt;：必须在 &lt;code&gt;api&lt;/code&gt; schema 下暴露视图，或在函数设置 &lt;code&gt;stable&lt;/code&gt; 属性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CORS 问题&lt;/strong&gt;：配置反向代理添加 CORS 头，或使用 &lt;code&gt;PGRST_SERVER_PROXY_URI&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;生产实践建议&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;使用迁移脚本统一管理角色、策略和视图，避免手工操作。&lt;/li&gt;
&lt;li&gt;为 &lt;code&gt;postgrest&lt;/code&gt; 服务加上 &lt;code&gt;healthcheck&lt;/code&gt;，配合 Nginx/Traefik 自动探活。&lt;/li&gt;
&lt;li&gt;结合 &lt;code&gt;pg_stat_statements&lt;/code&gt; 分析慢查询，必要时创建索引或重写视图。&lt;/li&gt;
&lt;li&gt;对外暴露前务必在 staging 环境跑一轮权限测试，防止数据泄露。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://postgrest.org/en/stable/&quot;&gt;官方文档&lt;/a&gt;：涵盖全部配置项与示例。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://postgrest.org/en/stable/references/api/reading.html&quot;&gt;Query Cheatsheet&lt;/a&gt;：查询参数详解。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.postgresql.org/docs/current/ddl-rowsecurity.html&quot;&gt;Row Level Security 指南&lt;/a&gt;：RLS 策略写法。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Cloudflare Workers 开发者指南</title><link>https://bangwu.top/posts/cloudflare-worker/</link><guid isPermaLink="true">https://bangwu.top/posts/cloudflare-worker/</guid><description>从零开始构建、调试、部署 Cloudflare Workers，并结合官方最佳实践整理常见场景与工具链</description><pubDate>Sat, 24 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Workers 是什么&lt;/h2&gt;
&lt;p&gt;Cloudflare Workers 是运行在 Cloudflare 全球 310+ 边缘节点上的无服务器计算平台。它兼容 Web 标准（Fetch API、Service Worker 语法），支持亚毫秒级冷启动，非常适合：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;前端 BFF、API 网关、A/B 测试；&lt;/li&gt;
&lt;li&gt;请求重写、缓存、鉴权；&lt;/li&gt;
&lt;li&gt;静态/动态渲染（SSR）、后台任务（Cron Triggers、Queues）；&lt;/li&gt;
&lt;li&gt;结合 KV、D1、R2 等产品构建完整的全栈应用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Workers 采用“事件驱动 + Module Worker”模式，开发者只需导出 &lt;code&gt;fetch&lt;/code&gt;/&lt;code&gt;scheduled&lt;/code&gt;/&lt;code&gt;queue&lt;/code&gt; 等 handler 即可。&lt;/p&gt;
&lt;h2&gt;基础架构概览&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Worker&lt;/strong&gt;：核心运行单元，处理 HTTP、计划任务、队列消息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;KV&lt;/strong&gt;：全球分发的键值存储，读多写少场景。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Durable Objects&lt;/strong&gt;：具备状态的一致性 Actor，用于实时协作、计数、聊天室。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;R2&lt;/strong&gt;：兼容 S3 的对象存储，免出口费。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;D1&lt;/strong&gt;：托管 SQLite 数据库。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Queues / Cron Triggers&lt;/strong&gt;：后台任务、消息队列。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zero Trust / Turnstile&lt;/strong&gt;：安全防护、验证码。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Workers 原生支持 TypeScript、ES Modules、npm 包（要求 ESM/边缘兼容），生态工具以 &lt;a href=&quot;https://developers.cloudflare.com/workers/wrangler/&quot;&gt;Wrangler&lt;/a&gt; CLI 为中心。&lt;/p&gt;
&lt;h2&gt;准备开发环境&lt;/h2&gt;
&lt;h3&gt;安装 Wrangler&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;npm install -g wrangler
# 或者使用 pnpm/yarn：pnpm add -g wrangler
wrangler login # 打开浏览器授权
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;登陆后 Wrangler 会保存 API Token 到 &lt;code&gt;~/.wrangler/config/default.toml&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;初始化项目&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;npm create cloudflare@latest my-worker
cd my-worker
npm install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CLI 会询问运行时（JavaScript/TypeScript）、模板（Hello World、Hono、Queues 等）、是否开启测试框架 Vitest。生成的目录结构通常包含：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;src/index.ts&lt;/code&gt;：Worker 入口（Module Worker）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wrangler.toml&lt;/code&gt;：项目配置。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;package.json&lt;/code&gt;：依赖与脚本。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tsconfig.json&lt;/code&gt;：TS 编译配置。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;编写你的第一个 Worker&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;src/index.ts&lt;/code&gt; 示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise&amp;lt;Response&amp;gt; {
    const { pathname } = new URL(request.url);

    if (pathname === &apos;/hello&apos;) {
      return new Response(JSON.stringify({ message: &apos;Hello from Workers&apos; }), {
        headers: { &apos;Content-Type&apos;: &apos;application/json&apos; },
      });
    }

    return new Response(&apos;Not Found&apos;, { status: 404 });
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Env&lt;/code&gt; 接口代表 &lt;code&gt;wrangler.toml&lt;/code&gt; 中 &lt;code&gt;bindings&lt;/code&gt;（KV、R2、Secrets 等），可通过 &lt;code&gt;types&lt;/code&gt; 字段指定。&lt;/p&gt;
&lt;h2&gt;wrangler.toml 配置&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;name = &quot;my-worker&quot;
main = &quot;src/index.ts&quot;
compatibility_date = &quot;2024-11-01&quot;
compatibility_flags = [&quot;web_socket_compression&quot;]
logpush = true

[vars]
API_BASE = &quot;https://api.example.com&quot;

[[kv_namespaces]]
binding = &quot;CACHE&quot;
id = &quot;xxxxxx...&quot;

[[d1_databases]]
binding = &quot;DB&quot;
database_name = &quot;my-db&quot;
database_id = &quot;yyyyyy...&quot;

[triggers]
crons = [&quot;0 */1 * * *&quot;]  # 每小时运行
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意事项：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;compatibility_date&lt;/code&gt; 决定运行时 API 行为，建议定期升级。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vars&lt;/code&gt; 会作为环境变量注入 &lt;code&gt;env&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;绑定（binding）名称是代码里访问的名称。&lt;/li&gt;
&lt;li&gt;对于 &lt;code&gt;development&lt;/code&gt; 环境可使用 &lt;code&gt;[env.dev]&lt;/code&gt; 覆盖配置，实现多环境部署。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;本地开发与调试&lt;/h2&gt;
&lt;h3&gt;&lt;code&gt;wrangler dev&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;wrangler dev
# 或指定端口 / 远程模式
wrangler dev --port 8787 --remote
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;默认使用 Miniflare 在本地模拟，支持热重载。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--remote&lt;/code&gt; 与 &lt;code&gt;--live-reload&lt;/code&gt; 会将请求发送到最近的 Cloudflare 边缘节点，适合测试与第三方服务交互。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--inspect&lt;/code&gt; 可开启 Chrome DevTools 调试。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;请求测试&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;curl http://127.0.0.1:8787/hello
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;本地环境无法访问生产 KV/R2，Wrangler 会提供模拟器。若需要真实数据，可在 &lt;code&gt;wrangler dev --remote&lt;/code&gt; 时指定 &lt;code&gt;--persist&lt;/code&gt; 或在 &lt;code&gt;wrangler.toml&lt;/code&gt; 中设置 &lt;code&gt;kv_namespaces.preview_id&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;日志查看&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;wrangler tail
# 或者过滤
wrangler tail --filters status=500
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;wrangler tail&lt;/code&gt; streaming 实时日志（默认 5 分钟），亦可导出到 Logpush（存储到 R2、Splunk、Datadog 等）。&lt;/p&gt;
&lt;h2&gt;部署&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;wrangler deploy
# 指定环境
wrangler deploy --env production
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功后 CLI 会返回访问 URL，以及 &lt;code&gt;Worker KV&lt;/code&gt; 等绑定结果。生产日志可通过 Cloudflare Dashboard &amp;gt; Workers &amp;gt; Your Worker &amp;gt; Logs 查看。&lt;/p&gt;
&lt;h2&gt;绑定 Cloudflare 产品&lt;/h2&gt;
&lt;h3&gt;KV（键值存储）&lt;/h3&gt;
&lt;p&gt;在 Dashboard 创建 KV namespace，复制 &lt;code&gt;Namespace ID&lt;/code&gt; 填入 &lt;code&gt;wrangler.toml&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[[kv_namespaces]]
binding = &quot;CACHE&quot;
id = &quot;xxxxxxxx&quot;
preview_id = &quot;yyyyyyyy&quot; # 可选，用于 dev 环境
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;await env.CACHE.put(&apos;key&apos;, &apos;value&apos;, { expirationTtl: 3600 });
const result = await env.CACHE.get(&apos;key&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;KV 写入最终一致、读多写少；写操作可一次性 &lt;code&gt;put&lt;/code&gt;/&lt;code&gt;delete&lt;/code&gt;，不要当事务数据库使用。&lt;/p&gt;
&lt;h3&gt;Durable Objects&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;[[durable_objects.bindings]]
name = &quot;ROOM&quot;
class_name = &quot;RoomDO&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;export class RoomDO {
  state: DurableObjectState;

  constructor(state: DurableObjectState, env: Env) {
    this.state = state;
  }

  async fetch(request: Request) {
    const messages = (await this.state.storage.get&amp;lt;string[]&amp;gt;(&apos;messages&apos;)) ?? [];
    return new Response(JSON.stringify({ messages }), {
      headers: { &apos;Content-Type&apos;: &apos;application/json&apos; },
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 Worker 中使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const id = env.ROOM.idFromName(&apos;general&apos;);
const stub = env.ROOM.get(id);
const res = await stub.fetch(request);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;D1（数据库）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;[[d1_databases]]
binding = &quot;DB&quot;
database_name = &quot;app-db&quot;
database_id = &quot;abc123&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;const { results } = await env.DB.prepare(&apos;SELECT * FROM todos WHERE done = ?&apos;).bind(false).all();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配合 &lt;code&gt;drizzle-orm&lt;/code&gt;, &lt;code&gt;kysely&lt;/code&gt; 等 ORM 使用时，需确保运行时兼容（一般使用 drizzle-orm/d1 driver）。&lt;/p&gt;
&lt;h3&gt;R2（对象存储）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;[[r2_buckets]]
binding = &quot;ASSETS&quot;
bucket_name = &quot;my-assets&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;await env.ASSETS.put(&apos;images/logo.png&apos;, fileReadableStream);
const object = await env.ASSETS.get(&apos;images/logo.png&apos;);
return new Response(object?.body, { headers: object?.httpMetadata });
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Queues&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;[[queues.producers]]
queue = &quot;task-queue&quot;
binding = &quot;TASK_QUEUE&quot;

[[queues.consumers]]
queue = &quot;task-queue&quot;
max_batch_size = 1
max_batch_timeout = 30
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Producer：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;await env.TASK_QUEUE.send({ type: &apos;email&apos;, payload: { to: &apos;hi@example.com&apos; } });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Consumer：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export default {
  async queue(batch: MessageBatch&amp;lt;any&amp;gt;, env: Env, ctx: ExecutionContext) {
    for (const msg of batch.messages) {
      // 处理任务
      msg.ack();
    }
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Secrets 与环境变量&lt;/h2&gt;
&lt;h3&gt;普通变量&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;wrangler.toml&lt;/code&gt; 中 &lt;code&gt;[vars]&lt;/code&gt; 会以字符串注入。适合非敏感配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[vars]
API_BASE = &quot;https://api.example.com&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;代码中：&lt;code&gt;env.API_BASE&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;Secrets&lt;/h3&gt;
&lt;p&gt;敏感信息使用 &lt;code&gt;wrangler secret put&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wrangler secret put GITHUB_TOKEN
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Secrets 默认仅存在于特定环境，请分别为 preview/production 设置。删除：&lt;code&gt;wrangler secret delete&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;多环境管理&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;[env.preview]
vars = { API_BASE = &quot;https://staging.example.com&quot; }

[env.production]
vars = { API_BASE = &quot;https://api.example.com&quot; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;部署时：&lt;code&gt;wrangler deploy --env production&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;常见事件与示例&lt;/h2&gt;
&lt;h3&gt;HTTP Fetch&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    ctx.waitUntil(env.ANALYTICS.write({ path: new URL(request.url).pathname }));
    return new Response(&apos;OK&apos;);
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Cron Trigger&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;export default {
  async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) {
    await env.REPORT_QUEUE.send({ at: event.scheduledTime });
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 Dashboard 或 &lt;code&gt;wrangler.toml&lt;/code&gt; 的 &lt;code&gt;[triggers]&lt;/code&gt; 中配置 Cron 表达式。&lt;/p&gt;
&lt;h3&gt;WebSocket / SSE&lt;/h3&gt;
&lt;p&gt;Workers 支持 &lt;code&gt;WebSocketPair&lt;/code&gt; 与 &lt;code&gt;ReadableStream&lt;/code&gt;，使用 &lt;code&gt;c.fwd&lt;/code&gt; 或 Hono/itty-router 处理更方便。注意 &lt;code&gt;compatibility_flags&lt;/code&gt; 需要开启 &lt;code&gt;web_socket_compression&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;与框架结合&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hono&lt;/strong&gt;：轻量 TypeScript 框架，&lt;code&gt;npm create hono@latest -- --cloudflare-workers&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Astro SSR&lt;/strong&gt;：通过 &lt;code&gt;@astrojs/cloudflare&lt;/code&gt; adapter。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Next.js Middleware / Pages&lt;/strong&gt;：Workers 可作为自定义服务器或中间层。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Remix&lt;/strong&gt;：官方支持 &lt;code&gt;remix-cloudflare&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;示例（Hono）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Hono } from &apos;hono&apos;;
import { zValidator } from &apos;@hono/zod-validator&apos;;
import { z } from &apos;zod&apos;;

type Bindings = {
  DB: D1Database;
};

const app = new Hono&amp;lt;{ Bindings: Bindings }&amp;gt;();

app.get(&apos;/todos&apos;, async (c) =&amp;gt; {
  const { results } = await c.env.DB.prepare(&apos;SELECT * FROM todos&apos;).all();
  return c.json(results);
});

app.post(
  &apos;/todos&apos;,
  zValidator(&apos;json&apos;, z.object({ title: z.string().min(1) })),
  async (c) =&amp;gt; {
    const { title } = c.req.valid(&apos;json&apos;);
    await c.env.DB.prepare(&apos;INSERT INTO todos (title) VALUES (?)&apos;).bind(title).run();
    return c.json({ message: &apos;created&apos; }, 201);
  }
);

export default app;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;调试与 Observability&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;wrangler tail&lt;/code&gt;：实时日志。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wrangler dev --inspect&lt;/code&gt;：Chrome DevTools。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workers Metrics&lt;/strong&gt;：Dashboard 提供请求量、CPU 时间、错误率。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workers Trace Events&lt;/strong&gt;：可启用 HTTP 请求追踪（Beta）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logpush&lt;/strong&gt;：将日志推送到 R2、S3、Datadog。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workers Analytics Engine&lt;/strong&gt;：结构化日志，可通过 SQL 查询。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;推荐在代码中引入请求 ID：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const requestId = crypto.randomUUID();
console.log(&apos;request start&apos;, requestId);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;并统一使用 &lt;code&gt;ctx.waitUntil&lt;/code&gt; 上报异步日志。&lt;/p&gt;
&lt;h2&gt;性能优化建议&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;保持函数纯粹，避免阻塞操作；长任务使用 Queues。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;复用 &lt;code&gt;fetch&lt;/code&gt; 请求：避免多次调用同一 API，利用 &lt;code&gt;Promise.all&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;合理使用 &lt;code&gt;Cache API&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const cache = caches.default;
const cacheKey = new Request(request.url, request);
let response = await cache.match(cacheKey);
if (!response) {
  response = await fetch(request);
  ctx.waitUntil(cache.put(cacheKey, response.clone()));
}
return response;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;静态资源放 R2 + CDN，Workers 做鉴权与签名。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果依赖第三方 npm 包，确认其兼容 Web 平台（无 Node-specific API）。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;安全与合规&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;使用 &lt;code&gt;wrangler secret&lt;/code&gt; 管理敏感信息。&lt;/li&gt;
&lt;li&gt;与 Cloudflare Zero Trust 集成，限制访问来源。&lt;/li&gt;
&lt;li&gt;使用 &lt;a href=&quot;https://developers.cloudflare.com/turnstile/&quot;&gt;Turnstile&lt;/a&gt; 抵御机器人。&lt;/li&gt;
&lt;li&gt;需要 CORS 时手动设置 &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt;，并关注 &lt;code&gt;OPTIONS&lt;/code&gt; 预检。&lt;/li&gt;
&lt;li&gt;对于用户数据，结合 DLP/日志脱敏策略。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;常见问题排查&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;问题&lt;/th&gt;
&lt;th&gt;排查思路&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;522/525 错误&lt;/td&gt;
&lt;td&gt;检查 Worker 超时（CPU 时间超过限制）、上游服务不可达&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;本地 dev 行为与生产不一致&lt;/td&gt;
&lt;td&gt;使用 &lt;code&gt;wrangler dev --remote&lt;/code&gt;，或确认 &lt;code&gt;compatibility_date&lt;/code&gt; 一致&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;未找到绑定&lt;/td&gt;
&lt;td&gt;确认 &lt;code&gt;wrangler.toml&lt;/code&gt; 中 binding 名称与代码一致，部署后刷新 Dashboard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;npm 包不兼容&lt;/td&gt;
&lt;td&gt;优先选用 ESM、无 Node API 的库；或通过 &lt;code&gt;workerd&lt;/code&gt; polyfill&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request body 重复读取失败&lt;/td&gt;
&lt;td&gt;使用 &lt;code&gt;request.clone()&lt;/code&gt; 复用流&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cron 不触发&lt;/td&gt;
&lt;td&gt;确认 &lt;code&gt;wrangler.toml&lt;/code&gt; 中 &lt;code&gt;triggers.crons&lt;/code&gt; 已配置，且 Worker 部署在支持 Cron 的计划（Free 计划每天 3 次）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;发布流程建议&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;使用 GitHub Actions / Cloudflare Pages 集成自动部署。&lt;/li&gt;
&lt;li&gt;PR 阶段运行 &lt;code&gt;wrangler deploy --dry-run&lt;/code&gt; 快速验证编译。&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;wrangler deploy --env staging&lt;/code&gt; 发布到预发，结合 Zero Trust 限制访问。&lt;/li&gt;
&lt;li&gt;通过 &lt;code&gt;wrangler tail&lt;/code&gt; 或 Logpush 观察 200/500 比例。&lt;/li&gt;
&lt;li&gt;升级 &lt;code&gt;compatibility_date&lt;/code&gt; 前在 staging 测试，确保向后兼容。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;进一步阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.cloudflare.com/workers/&quot;&gt;Cloudflare Workers 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.cloudflare.com/workers/wrangler/&quot;&gt;wrangler CLI 指南&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/cloudflare/workers-sdk/tree/main/templates&quot;&gt;Workers Templates&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.cloudflare.com/durable-objects/&quot;&gt;Durable Objects Cookbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.cloudflare.com/queues/&quot;&gt;Cloudflare Queues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.cloudflare.com/workers/platform/observability/&quot;&gt;Workers Observability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/cloudflare/workerd&quot;&gt;Workerd Runtime 源码&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;掌握以上内容后，可以快速利用 Cloudflare Workers 构建全球加速、成本可控的边缘应用，从个人项目到企业级服务都能覆盖。基于模块化、标准化的 API，开发体验与浏览器端极为相似，入门门槛远低于传统 Serverless 平台。&lt;/p&gt;
</content:encoded></item><item><title>Hello, Hono</title><link>https://bangwu.top/posts/honojs/</link><guid isPermaLink="true">https://bangwu.top/posts/honojs/</guid><description>Hono 教程，非常适用于微服务api的js框架</description><pubDate>Sat, 24 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;为什么选择 Hono.js&lt;/h2&gt;
&lt;p&gt;Hono 起初是为 Cloudflare Workers 设计的超轻量 Web 框架，现在已支持 Deno、Bun、Node.js、Vercel Edge、Lagon 等多种运行时。它兼具以下特性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;快速&lt;/strong&gt;：核心仅 ~15KB，响应链路短，天然适合边缘侧（Edge）与无服务器函数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;兼容 Fetch 标准&lt;/strong&gt;：上下文就是 &lt;code&gt;Request&lt;/code&gt;/&lt;code&gt;Response&lt;/code&gt;，易于在不同运行时迁移。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TypeScript 友好&lt;/strong&gt;：严格的类型推导，加上 &lt;code&gt;hc&lt;/code&gt;/&lt;code&gt;zod&lt;/code&gt; 等生态可自动生成客户端与 OpenAPI。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;中间件生态&lt;/strong&gt;：内置 CORS、Basic Auth、JWT、Validator 等工具，也能继承 Express 风格中间件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本文整理 Hono 在实际项目中的最佳实践，帮助你搭建稳定、可维护的微服务 API。&lt;/p&gt;
&lt;h2&gt;项目脚手架&lt;/h2&gt;
&lt;p&gt;推荐使用官方 CLI：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm create hono@latest my-hono-app
cd my-hono-app
npm install
npm run dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成的项目默认使用 TypeScript 与 ESLint，可根据运行时切换 &lt;code&gt;adapter&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cloudflare-workers&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bun&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;deno&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;node-server&lt;/code&gt;（Node.js 运行，配合 Fastly Compute、Lambda 等）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;配置建议：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在 &lt;code&gt;tsconfig.json&lt;/code&gt; 开启 &lt;code&gt;strict&lt;/code&gt;, &lt;code&gt;noUncheckedIndexedAccess&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;配置 &lt;code&gt;eslint-config-hono&lt;/code&gt; 与 &lt;code&gt;typescript-eslint&lt;/code&gt; 保证一致性。&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;@hono/zod-validator&lt;/code&gt; 做输入校验。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;路由与模块化&lt;/h2&gt;
&lt;p&gt;Hono 按照路径注册 handler，建议按业务拆分：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Hono } from &apos;hono&apos;;
import { userRouter } from &apos;./routes/users&apos;;
import { authRouter } from &apos;./routes/auth&apos;;

const app = new Hono();

app.route(&apos;/auth&apos;, authRouter);
app.route(&apos;/users&apos;, userRouter);

export default app;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;routes/users.ts&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Hono } from &apos;hono&apos;;
import { zValidator } from &apos;@hono/zod-validator&apos;;
import { z } from &apos;zod&apos;;

export const userRouter = new Hono()
  .get(
    &apos;/&apos;,
    zValidator(&apos;query&apos;, z.object({ page: z.coerce.number().int().min(1).default(1) })),
    async (c) =&amp;gt; {
      const { page } = c.req.valid(&apos;query&apos;);
      const result = await c.get(&apos;services&apos;).user.list({ page });
      return c.json(result);
    }
  )
  .post(
    &apos;/&apos;,
    zValidator(&apos;json&apos;, z.object({ email: z.string().email(), password: z.string().min(8) })),
    async (c) =&amp;gt; {
      const payload = c.req.valid(&apos;json&apos;);
      const user = await c.get(&apos;services&apos;).user.create(payload);
      return c.json(user, 201);
    }
  );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;尽量在路由文件中保持“瘦控制器”，把业务逻辑下沉到 service/domain 层。&lt;/li&gt;
&lt;li&gt;通过 &lt;code&gt;c.get()&lt;/code&gt;/&lt;code&gt;c.set()&lt;/code&gt; 将服务或依赖注入 &lt;code&gt;Context&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;利用 &lt;code&gt;zod&lt;/code&gt; 校验 + 类型推导避免重复声明类型。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;中间件与上下文&lt;/h2&gt;
&lt;p&gt;常用中间件示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { logger } from &apos;hono/logger&apos;;
import { cors } from &apos;hono/cors&apos;;
import { poweredBy } from &apos;hono/powered-by&apos;;
import { secureHeaders } from &apos;hono/secure-headers&apos;;

app.use(&apos;*&apos;, poweredBy());
app.use(&apos;*&apos;, logger());
app.use(&apos;*&apos;, secureHeaders());
app.use(
  &apos;/api/*&apos;,
  cors({
    origin: [&apos;https://app.example.com&apos;],
    allowMethods: [&apos;GET&apos;, &apos;POST&apos;, &apos;PATCH&apos;, &apos;DELETE&apos;],
    allowHeaders: [&apos;Authorization&apos;, &apos;Content-Type&apos;],
  })
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;依赖注入：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import type { Services } from &apos;./services&apos;;

type Bindings = {
  SERVICES: Services;
};

const app = new Hono&amp;lt;{ Bindings: Bindings }&amp;gt;();

app.use(&apos;*&apos;, async (c, next) =&amp;gt; {
  c.set(&apos;services&apos;, c.env.SERVICES);
  await next();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行顺序遵循注册顺序，注意在 Edge 环境中避免阻塞型操作（例如 &lt;code&gt;crypto.randomUUID&lt;/code&gt; 可替代 &lt;code&gt;uuid&lt;/code&gt; 包）。&lt;/p&gt;
&lt;h2&gt;错误处理&lt;/h2&gt;
&lt;p&gt;统一错误处理能提升 DX：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app.onError((err, c) =&amp;gt; {
  c.get(&apos;logger&apos;)?.error(err);

  if (err instanceof HTTPException) {
    return err.getResponse();
  }

  return c.json(
    {
      code: &apos;INTERNAL_ERROR&apos;,
      message: &apos;服务器开小差了&apos;,
    },
    500
  );
});

app.notFound((c) =&amp;gt;
  c.json(
    {
      code: &apos;NOT_FOUND&apos;,
      message: `Route ${c.req.method} ${c.req.path} 不存在`,
    },
    404
  )
);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;使用 &lt;code&gt;HTTPException&lt;/code&gt; 抛出领域错误，例如 &lt;code&gt;throw new HTTPException(403, { message: &apos;Forbidden&apos; })&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;在日志中记录 &lt;code&gt;requestId&lt;/code&gt;（可从 header 或 &lt;code&gt;crypto.randomUUID()&lt;/code&gt; 生成）。&lt;/li&gt;
&lt;li&gt;Edge 平台通常有 &lt;code&gt;ExecutionContext.waitUntil&lt;/code&gt;，可以结合 &lt;code&gt;app.use&lt;/code&gt; 注入日志/埋点。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;数据访问与性能&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Cloudflare D1、PlanetScale、Supabase 可通过 &lt;code&gt;Context.env&lt;/code&gt; 注入客户端。&lt;/li&gt;
&lt;li&gt;在 Edge 环境使用 &lt;code&gt;fetch&lt;/code&gt; 调用内部服务时，注意复用 &lt;code&gt;Request&lt;/code&gt; 对象或使用 &lt;code&gt;Cache API&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;结合 &lt;code&gt;itty-router-openapi&lt;/code&gt; 或 &lt;code&gt;hono-openapi&lt;/code&gt; 自动生成文档。&lt;/li&gt;
&lt;li&gt;当需要 WebSocket/SSE，可引用 &lt;code&gt;hono/ws&lt;/code&gt; 或 &lt;code&gt;hono/sse&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;缓存策略：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app.get(&apos;/health&apos;, async (c) =&amp;gt; {
  return c.text(&apos;ok&apos;, 200, {
    &apos;Cache-Control&apos;: &apos;max-age=30&apos;,
  });
});

app.get(&apos;/articles&apos;, cacheMiddleware(), async (c) =&amp;gt; {
  // ...
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 Cloudflare Workers 中可用 &lt;code&gt;c.executionCtx.waitUntil(cache.put(...))&lt;/code&gt; 延迟写缓存。&lt;/p&gt;
&lt;h2&gt;安全最佳实践&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;code&gt;secureHeaders()&lt;/code&gt; 设置 &lt;code&gt;Content-Security-Policy&lt;/code&gt;, &lt;code&gt;Strict-Transport-Security&lt;/code&gt;, &lt;code&gt;X-Frame-Options&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JWT 验证使用 &lt;code&gt;hono/jwt&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app.use(
  &apos;/api/*&apos;,
  jwt({
    secret: c.env.JWT_SECRET,
    cookie: &apos;access_token&apos;,
    algorithm: &apos;HS256&apos;,
  })
);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对外部输入全部走 &lt;code&gt;zod&lt;/code&gt;/&lt;code&gt;valibot&lt;/code&gt; 校验，防止类型穿透。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对于文件上传，结合 R2/S3 签名上传 + 临时 URL。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;记录审计日志：可将请求信息写入 Workers Analytics Engine 或第三方服务。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;测试策略&lt;/h2&gt;
&lt;h3&gt;单元测试&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import { describe, it, expect } from &apos;vitest&apos;;
import app from &apos;../src/app&apos;;

describe(&apos;GET /health&apos;, () =&amp;gt; {
  it(&apos;should return ok&apos;, async () =&amp;gt; {
    const res = await app.request(&apos;/health&apos;);
    expect(res.status).toBe(200);
    expect(await res.text()).toBe(&apos;ok&apos;);
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hono 提供 &lt;code&gt;app.request&lt;/code&gt; 直接在 Node 里模拟请求，无需启动服务器。&lt;/p&gt;
&lt;h3&gt;集成测试&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Cloudflare Workers：使用 &lt;code&gt;@cloudflare/workers-types&lt;/code&gt; 与 &lt;code&gt;miniflare&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;Node 环境：搭配 &lt;code&gt;supertest&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;Edge Function：在部署前使用平台提供的本地模拟器（如 &lt;code&gt;wrangler dev&lt;/code&gt;、&lt;code&gt;vercel dev&lt;/code&gt;）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;部署与观察&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cloudflare Workers&lt;/strong&gt;：&lt;code&gt;wrangler deploy&lt;/code&gt;；配置 Durable Objects、KV、R2 于 &lt;code&gt;wrangler.toml&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vercel Edge&lt;/strong&gt;：导出 &lt;code&gt;export default app&lt;/code&gt;，在 &lt;code&gt;vercel.json&lt;/code&gt; 指定 &lt;code&gt;runtime: &quot;edge&quot;&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Node Server&lt;/strong&gt;：使用 &lt;code&gt;@hono/node-server&lt;/code&gt; 或 &lt;code&gt;fastly-hono-adapter&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docker&lt;/strong&gt;：将 &lt;code&gt;app.fetch&lt;/code&gt; 包装在 &lt;code&gt;server.listen&lt;/code&gt; 中，注意 Node 端口。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;监控与日志：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cloudflare Logs / Workers Analytics Engine。&lt;/li&gt;
&lt;li&gt;OpenTelemetry：可结合 &lt;code&gt;hono/opentelemetry&lt;/code&gt; 输出 trace。&lt;/li&gt;
&lt;li&gt;使用平台原生指标（Vercel Speed Insights、Deno Deploy Metrics）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;常见陷阱&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Edge 环境没有 Node polyfills，避免依赖 &lt;code&gt;fs&lt;/code&gt;, &lt;code&gt;crypto.randomBytes&lt;/code&gt; 等 Node 特有 API。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;await&lt;/code&gt; 中不要阻塞事件循环：使用 &lt;code&gt;Promise.all&lt;/code&gt; 并行执行。&lt;/li&gt;
&lt;li&gt;注意 &lt;code&gt;Request&lt;/code&gt;/&lt;code&gt;Response&lt;/code&gt; 流只能读取一次；需要复用时调用 &lt;code&gt;req.clone()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;app.fire()&lt;/code&gt; 必须在 &lt;code&gt;fetch&lt;/code&gt; handler 中调用，在 Cloudflare 中通常是 &lt;code&gt;export default app&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;如果在中间件中抛异常，却返回 200，多半是 &lt;code&gt;await next()&lt;/code&gt; 忘写。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;推荐资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://hono.dev/&quot;&gt;Hono 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/honojs/awesome-hono&quot;&gt;Awesome Hono&lt;/a&gt;：社区收集的中间件、模板。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.cloudflare.com/workers/&quot;&gt;Cloudflare Workers Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/honojs/examples&quot;&gt;Hono Examples 仓库&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://edge-runtime.vercel.app/&quot;&gt;Edge Runtime Compared&lt;/a&gt;：不同 Edge 运行时对比。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;合理运用以上实践，可以让 Hono 成为构建高性能、易维护 API 的利器，无论是 SaaS 后端、BFF 还是边缘函数都能胜任。&lt;/p&gt;
</content:encoded></item><item><title>数据可视化</title><link>https://bangwu.top/posts/data-visual/</link><guid isPermaLink="true">https://bangwu.top/posts/data-visual/</guid><description>数据可视化的一些技巧、规范和实践经验</description><pubDate>Fri, 18 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;数据可视化&lt;/h2&gt;
&lt;p&gt;数据可视化的目标不是把表格换一层皮，而是帮助读者迅速理解趋势、比较和异常点。一次好的呈现应该在三秒内告诉读者“发生了什么”，在三十秒内解释“为什么会这样”。&lt;/p&gt;
&lt;p&gt;我在&lt;a href=&quot;https://cdn.bangwu.top/assets/visualization-end.pdf&quot;&gt;最后一次作业&lt;/a&gt;里踩过不少坑，也常被同事问起怎么做“有信息量又不难看的图”。这篇文章整理了常用的流程、设计规范和工具清单，方便以后查阅。&lt;/p&gt;
&lt;h3&gt;为什么要做可视化&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;快速建模：可视化能让我们迅速验证假设，发现数据里的结构或异常值。&lt;/li&gt;
&lt;li&gt;聚焦重点：通过合理的视觉编码，把最重要的指标放在第一视觉层级。&lt;/li&gt;
&lt;li&gt;建立共识：图表比 PPT 文本更容易在评审会上达成共识，也是讲述产品/业务故事的骨架。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;从原始数据到图表&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;明确目标受众&lt;/strong&gt;：他们最关心的指标是什么？背景知识到什么程度？输出的交互复杂度取决于这个答案。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;梳理数据结构&lt;/strong&gt;：先在表格里做数据探查（EDA），标记可用维度、需要清洗的字段和时间跨度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;挑选合适的图表类型&lt;/strong&gt;：定量比较优先考虑柱状/折线，结构占比用堆叠柱或矩形树图，关联关系可尝试散点和网络图。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;草图迭代&lt;/strong&gt;：先画草图或使用 Figma/Excalidraw 快速拼布局，再决定配色、标注和交互节奏。&lt;/li&gt;
&lt;/ol&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;目标&lt;/th&gt;
&lt;th&gt;推荐图表&lt;/th&gt;
&lt;th&gt;适用说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;趋势变化&lt;/td&gt;
&lt;td&gt;折线图、面积图&lt;/td&gt;
&lt;td&gt;时间序列数据，注意纵轴是否从零开始&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;分类比较&lt;/td&gt;
&lt;td&gt;分组柱状图、哑铃图&lt;/td&gt;
&lt;td&gt;同类指标在不同群体之间的差异&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;占比结构&lt;/td&gt;
&lt;td&gt;堆叠柱、矩形树图&lt;/td&gt;
&lt;td&gt;强调局部与整体关系&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;地理分布&lt;/td&gt;
&lt;td&gt;Choropleth、符号地图&lt;/td&gt;
&lt;td&gt;需要地理上下文时优先&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;设计与可读性&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;颜色层级&lt;/strong&gt;：主色用于核心数据，辅色承载比较组，灰度用作背景参考线。遵守品牌色的同时记得通过色盲模拟检查对比度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;标注策略&lt;/strong&gt;：关键数字直接贴在图上，少用图例让读者来回对照。超出阈值的点用强调箭头或注释解释原因。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;最少装饰&lt;/strong&gt;：去掉无意义的 3D 效果和阴影，保留紧凑的网格线和刻度；文字切换为 Sentence case 或全中文陈述句，提高可读性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;响应式布局&lt;/strong&gt;：移动端优先保留核心图表，细节可以折叠至“展开更多”里。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;交互与讲故事&lt;/h3&gt;
&lt;p&gt;当图表要讲述一个“故事”时，可以采用 Step-by-step 的结构：先出现整体趋势，再逐步揭示关键节点和原因。常见做法是分章节滚动触发（scrollytelling）或提供标签切换（tab/segmented control）。一定要配合文字说明，为读者提供上下文。&lt;/p&gt;
&lt;h3&gt;常见坑位清单&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;数据颗粒度不对导致“锯齿”或“噪声”：先做平滑处理或改变汇总粒度。&lt;/li&gt;
&lt;li&gt;多尺度指标混在同一个坐标轴：尝试正则化或使用双轴，但要清楚提示量纲不同。&lt;/li&gt;
&lt;li&gt;图表过载：一次只传达一个主结论，其余信息可以放到 Tooltip 或说明文字中。&lt;/li&gt;
&lt;li&gt;忽略可访问性：注意颜色对比、字号、键盘操作路径，让读屏读者也能理解。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;常用工具组合&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;探索性&lt;/strong&gt;：Jupyter Notebook + pandas + seaborn/Plotly，用于快速验证数据特征。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生产级&lt;/strong&gt;：D3.js 适合高级定制，&lt;a href=&quot;https://d3js.org/getting-started&quot;&gt;D3 官方入门指南&lt;/a&gt;仍然是最佳入口；业务图表可以考虑 ECharts、Vega-Lite 或 AntV G2，能更快落地。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;低代码&lt;/strong&gt;：Superset、Metabase 和 Power BI 适合内部运营面板，强调权限和多数据源接入。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下方演示片段摘自 D3，展示如何用线性比例尺映射数值到可视化位置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import * as d3 from &apos;d3&apos;;

const scale = d3.scaleLinear()
  .domain([0, 100])
  .range([0, 600]);

d3.select(&apos;svg&apos;)
  .append(&apos;rect&apos;)
  .attr(&apos;x&apos;, 0)
  .attr(&apos;y&apos;, 20)
  .attr(&apos;width&apos;, scale(72))
  .attr(&apos;height&apos;, 24);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;评估与迭代&lt;/h3&gt;
&lt;p&gt;发布前做一次 checklist：文字描述是否与图表一致、不同设备显示是否正常、交互是否有备选操作路径。上线后可以通过埋点或用户访谈收集反馈，根据具体结论再开新的迭代。&lt;/p&gt;
&lt;h3&gt;进一步阅读&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.bilibili.com/video/BV19C4y1F7na&quot;&gt;影视飓风《可视化少林功夫》&lt;/a&gt;：故事性的可视化参考。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.stephen-few.com/idd.php&quot;&gt;Information Dashboard Design&lt;/a&gt;：信息仪表盘设计经典资料。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://datavizcatalogue.com&quot;&gt;The Data Visualization Catalogue&lt;/a&gt;：图表类型速查。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>情绪需要稳定还是释放</title><link>https://bangwu.top/posts/sentiment/</link><guid isPermaLink="true">https://bangwu.top/posts/sentiment/</guid><description>情绪稳定还是释放？在理性与宣泄间平衡</description><pubDate>Wed, 16 Apr 2025 15:45:22 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;audio controls data-title=&quot;美梦&quot; data-artist=&quot;周公&quot; data-cover=&quot;https://cdn.bangwu.top/img/20260111w9h9x-9m.webp&quot;&amp;gt;
&amp;lt;source src=&quot;https://cdn.bangwu.top/img/20260111f51d1e8e.mp3&quot; type=&quot;audio/mpeg&quot;&amp;gt;
&amp;lt;/audio&amp;gt;&lt;/p&gt;
&lt;p&gt;刷视频看到刘晓燕老师讲affection（n. 感情，影响）这个单词，她说“一定不要相信你要做个情绪稳定的人，你要做个没有情绪的人，而是要离开那个让你情绪不稳定的人和地方”刚好，最近心情不太好（实际这篇文章已经拖很长时间了，最近没心思写🥲），来聊一聊这件事&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/202601110c2b02bb.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在生活中，我认为自己的情绪是属于非常稳定的那一种，稳定到什么程度？高考的时候没有任何感觉、就算是喜欢的人离开我，我也只会冷静面对。可能这也不是情绪稳定，大多数事情造成的情感都被我足够多的理性压制了，这就造成了我偶尔做一些感性的事情的时候，都会想很多考虑各种情况，往往能接受最坏的那一个情况我才会去做（接受不了离别，所以我不会选择表白）OK，人人都会产生各种各样的情绪，记得《一人之下》里面纳森岛有个异人，会将人们的各种情绪释放出来，从七窍流出情绪越多越重，重重的压着你让你无法自拔，情绪足够稳定的张楚岚却流出了很多很多情绪……&lt;/p&gt;
&lt;p&gt;兴奋，憎恶，暴躁，快乐这些总是会或多或少地充斥着我们的心思，涤荡我们的灵魂又留下痕迹。遇到一些离谱的事情，我偶尔会和朋友吐槽，有些不好的事情让我心情不好，我会选择睡大觉，情绪稳定让我有足够好的状态迎接接下来的生活，不至于因为一件事就把生活弄得一团糟，让我有冷静面对问题的能力&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/2026011113785858.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;之前许愿，能做个“个性鲜明”的人，像动漫里的人物一样热血的就一直热血中二，吃货走到哪里就都是想着吃，这样好像就可以一直将自己想要表达的说出来也不会觉得不合适，因为“我”就是这样，但事实是生活中有许许多多的枷锁出于考虑，大多数选择不说或说些别的，我也不想以xx型人格来说不同的人们对待事情的方式，大家都是个性鲜明的呀，做出什么或说哪种话都是个概率问题啊，是啊...&lt;/p&gt;
&lt;p&gt;情绪这个东西，其实就像大海，有时风平浪静，有时波涛汹涌。过去我一直认为，情绪要有一份难得的稳定——正如高考时那种无波无澜的心境，也如面对喜欢的人离开时的冷静处事。理性似乎总能替情感埋下太多的石子，让人站得笔直，免去一时的情绪波动。然而，越是压抑那些即将迸发的情感，我越发觉得，有时释放一下也许是必要的，就像《一人之下》里那个能将情绪化作实体的异人，情绪释放后的破裂或许才是真正的存在。&lt;/p&gt;
&lt;p&gt;我发现，情绪释放并不意味着完全失控，也不全是情绪失范的代名词。它更像是在面对生活中那些压抑和沉淀的烦闷时，一种自我调适的方法。当一件事真得让你无法承担，或是那积压已久的心里负担已经到了极限的时候，偶尔放下理性的枷锁，把那些长期按捺的情绪爆发出来，或许能让内心获得一种久违的解脱。试想一下，当你把所有的不快、大大小小的负面情绪都“释放”出来，伴随着哭泣、呐喊甚至是一阵无声的叹息后，或许会迎来一种轻松的空白期，就像大雨过后的清新空气，让人深吸一口气，再次整理好自己的思绪。&lt;/p&gt;
&lt;p&gt;对我来说，绝大多数时候，我更喜欢保持稳定，因为情绪的稳定让生活多了一份掌控感，可以让我在各种考验面前冷静应对。稳定的情绪为我构建了一道坚实的防线，它让我在遇到不期而至的挫折时，不至于瞬间崩溃，一步步找到问题的症结。然而，内心深处，我也明白，完全压抑情绪并非长久之计。人毕竟不是没有感情的机器，偶尔的情绪迸发，反而会给我带来关于自我的更深认识。这种释放，无论是通过与朋友的深夜长谈，还是偶尔的一场独处的散步，都让我明白，情绪既需要保护的稳定，也需要适当的发泄。&lt;/p&gt;
&lt;p&gt;因此，我开始试着调整自己的策略：在面对大部分琐碎和紧张事务时，我依然选择保持冷静，利用理性的力量稳定情绪；而当那些深藏心底的情感积压到一定程度，我会允许自己去体验那种短暂的释放——那既不是自毁也不是放纵，而是一种勇敢面对内在真实声音的方式。就像有时夜深人静时，一场偶然的哭泣，可能会让我看清心底那些被理性忽略的温情、恐惧和对未来的渴望。或许正是在这一过程中，我学会了如何在稳定与释放之间寻找一种微妙的平衡，既不让情感泛滥失控，也不让内心的火焰永远熄灭在理性的冰层之下。&lt;/p&gt;
&lt;p&gt;这样的探索，让我渐渐明白了：情绪不该只是一味地被压抑或任意的宣泄，而应是一种内心的艺术。它需要在适当的时候得到稳定，以便我们能以清晰的头脑面对生活；同时，在适当的时刻，它又需要被释放，让我们的精神获得治愈与重生。这既是一种自我疗愈的方式，也是对生活多彩情感的真诚回馈。&lt;/p&gt;
&lt;p&gt;“我看不破
我放不过
只能默默忍受被情绪操纵...”&lt;/p&gt;
</content:encoded></item><item><title>Supervisor 从入门到精通</title><link>https://bangwu.top/posts/supervisor/</link><guid isPermaLink="true">https://bangwu.top/posts/supervisor/</guid><description>Supervisor 从入门到精通：不止是进程守护，更是应用管理的瑞士军刀</description><pubDate>Wed, 16 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Supervisor 从入门到精通：不止是进程守护，更是应用管理的瑞士军刀&lt;/h2&gt;
&lt;p&gt;大家好，我是bangwu。在后端开发和运维的世界里，确保我们的应用程序（无论是 Web 服务、后台任务队列还是其他关键进程）能够稳定、持续地运行至关重要。进程意外退出、服务器重启后需要手动恢复，这些都是令人头疼的问题。今天，我们就来深入探讨一个优雅解决这些问题的利器——&lt;strong&gt;Supervisor&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Supervisor 不仅仅是一个简单的进程监控和重启工具，它更是一个功能强大的客户端/服务器系统，允许用户在类 UNIX 操作系统上控制和监控多个进程。这篇文章将带你从 Supervisor 的基础概念出发，一步步掌握其安装、配置、实战应用，并深入了解其高级特性和最佳实践，助你成为 Supervisor 的使用专家。&lt;/p&gt;
&lt;h2&gt;1. Supervisor 是什么？为什么选择它？&lt;/h2&gt;
&lt;p&gt;想象一下，你部署了一个重要的 Python Web 应用，它通过 Gunicorn 运行。如果 Gunicorn 进程因为某些错误意外崩溃了，或者服务器重启了，会发生什么？服务中断，用户无法访问，你需要手动登录服务器去重启它。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Supervisor&lt;/strong&gt; 就是来解决这个问题的。它是一个用 Python 编写的进程控制系统，可以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;启动进程&lt;/strong&gt;：在后台启动你的应用程序。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;监控进程&lt;/strong&gt;：持续关注你的进程状态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动重启&lt;/strong&gt;：如果进程意外退出，Supervisor 会自动尝试重启它。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;管理多个进程&lt;/strong&gt;：轻松管理多个不同的应用程序或同一应用的不同实例。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;日志管理&lt;/strong&gt;：捕获进程的标准输出 (stdout) 和标准错误 (stderr) 到指定文件。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提供控制接口&lt;/strong&gt;：通过 &lt;code&gt;supervisorctl&lt;/code&gt; 命令行工具或 Web UI 来管理进程（启动、停止、重启、查看状态等）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;为什么选择 Supervisor？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;简单易用&lt;/strong&gt;：配置相对直观，上手快。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;稳定可靠&lt;/strong&gt;：经过广泛使用和测试，非常稳定。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;资源占用低&lt;/strong&gt;：相比一些重量级解决方案，Supervisor 本身消耗的资源很少。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;功能适中&lt;/strong&gt;：提供了足够强大的功能，但又不像 systemd 那样与操作系统深度绑定，更侧重于应用层进程管理。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跨平台性好&lt;/strong&gt;：虽然主要用于类 UNIX 系统，但因为它基于 Python，理论上可在任何支持 Python 的平台上运行（尽管 Windows 支持可能有限）。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 核心概念解析&lt;/h2&gt;
&lt;p&gt;理解 Supervisor 的工作方式，需要了解几个核心组件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;supervisord&lt;/code&gt;&lt;/strong&gt;: 这是 Supervisor 的服务器端进程。它负责启动、管理和监控所有在配置文件中定义的子进程。它本身通常也需要被某种方式（如 systemd、init.d 脚本）守护，以确保 Supervisor 自身的稳定运行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;supervisorctl&lt;/code&gt;&lt;/strong&gt;: 这是 Supervisor 的客户端命令行工具。通过它，用户可以连接到 &lt;code&gt;supervisord&lt;/code&gt;，并对其管理的进程进行各种操作（如 &lt;code&gt;start&lt;/code&gt;, &lt;code&gt;stop&lt;/code&gt;, &lt;code&gt;restart&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt; 等）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;配置文件&lt;/strong&gt;: Supervisor 的行为由一个或多个配置文件定义。通常有一个主配置文件（如 &lt;code&gt;/etc/supervisord.conf&lt;/code&gt;），它可以包含（include）其他目录下的配置文件（如 &lt;code&gt;/etc/supervisor/conf.d/*.conf&lt;/code&gt;），每个文件通常定义一个或多个要管理的程序（program）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;程序 (Program)&lt;/strong&gt;: 指你希望 Supervisor 管理的实际应用程序进程。在配置文件中以 &lt;code&gt;[program:your_program_name]&lt;/code&gt; 部分定义。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;进程组 (Group)&lt;/strong&gt;: 可以将多个相关的程序定义在一个组内，方便统一管理。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 安装 Supervisor&lt;/h2&gt;
&lt;p&gt;安装 Supervisor 最常见的方式是通过包管理器或 &lt;code&gt;pip&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用 pip (推荐，通常版本较新)&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 确保你有 pip
# python -m ensurepip --upgrade  # 或者 python3 -m ensurepip --upgrade
# pip install --upgrade pip

# 安装 supervisor
pip install supervisor

# 安装后，可能需要手动创建配置文件和目录
# 通常配置文件位于 /etc/supervisord.conf 或 /etc/supervisor/supervisord.conf
# 程序配置文件目录通常是 /etc/supervisor/conf.d/
# 可以运行 echo_supervisord_conf 将默认配置输出到文件
echo_supervisord_conf &amp;gt; /etc/supervisord.conf # 可能需要 sudo
mkdir -p /etc/supervisor/conf.d/ # 可能需要 sudo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用系统包管理器 (以 Ubuntu/Debian 和 CentOS/RHEL 为例)&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ubuntu/Debian&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update
sudo apt install supervisor
# 配置文件通常在 /etc/supervisor/supervisord.conf
# 程序配置文件在 /etc/supervisor/conf.d/
# 安装后通常会自动启动 supervisord 服务
sudo systemctl status supervisor
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CentOS/RHEL (可能需要 EPEL 源)&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo yum install epel-release # 如果没有安装 EPEL
sudo yum install supervisor
# 配置文件通常在 /etc/supervisord.conf
# 程序配置文件在 /etc/supervisor/conf.d/
# 安装后需要手动启动并设置开机自启
sudo systemctl start supervisord
sudo systemctl enable supervisord
sudo systemctl status supervisord
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;安装完成后，&lt;code&gt;supervisord&lt;/code&gt; (服务) 和 &lt;code&gt;supervisorctl&lt;/code&gt; (客户端) 命令就可用了。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 初识配置：管理第一个进程&lt;/h2&gt;
&lt;p&gt;让我们来管理一个简单的长时间运行的脚本。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;创建示例脚本&lt;/strong&gt; (&lt;code&gt;/opt/myapp/loop.sh&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash
echo &quot;My simple loop script started at $(date)&quot;
while true; do
  echo &quot;Looping... $(date)&quot;
  sleep 5
done
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;给它执行权限: &lt;code&gt;chmod +x /opt/myapp/loop.sh&lt;/code&gt; (确保 &lt;code&gt;/opt/myapp&lt;/code&gt; 目录存在)。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;创建 Supervisor 程序配置文件&lt;/strong&gt; (&lt;code&gt;/etc/supervisor/conf.d/loop_script.conf&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[program:loop_script]
command=/opt/myapp/loop.sh             ; 要执行的命令
autostart=true                         ; supervisord 启动时自动启动该程序
autorestart=true                       ; 程序退出时自动重启 (意外退出时)
user=nobody                            ; 以哪个用户身份运行 (建议非 root)
stdout_logfile=/var/log/supervisor/loop_script_stdout.log ; 标准输出日志文件路径
stderr_logfile=/var/log/supervisor/loop_script_stderr.log ; 标准错误日志文件路径
directory=/tmp                         ; 命令执行前切换到的目录 (可选)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;注意&lt;/em&gt;: 确保 &lt;code&gt;/var/log/supervisor/&lt;/code&gt; 目录存在并且 Supervisor 运行的用户（通常是 root 或 supervisor）有权写入。&lt;code&gt;mkdir -p /var/log/supervisor &amp;amp;&amp;amp; chown nobody:nogroup /var/log/supervisor&lt;/code&gt; (用户 &lt;code&gt;nobody&lt;/code&gt; 可能因系统而异，根据实际情况调整)。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;让 Supervisor 加载新配置&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo supervisorctl reread  # 读取配置文件 (包括 conf.d 下的新文件)
# 输出类似: loop_script: available

sudo supervisorctl update  # 应用新的配置，启动新增的程序
# 输出类似: loop_script: added process group

# 或者，如果你想明确启动它
# sudo supervisorctl start loop_script
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;检查状态&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo supervisorctl status
# 输出类似:
# loop_script                      RUNNING   pid 12345, uptime 0:00:15
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;查看日志&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo tail -f /var/log/supervisor/loop_script_stdout.log
# 你会看到 &quot;Looping...&quot; 的输出
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;停止进程&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo supervisorctl stop loop_script
# 再次 status 查看，状态应变为 STOPPED 或 EXITED
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;恭喜！你已经成功使用 Supervisor 管理了你的第一个进程。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 配置文件深度解析 (&lt;code&gt;supervisord.conf&lt;/code&gt;)&lt;/h2&gt;
&lt;p&gt;Supervisor 的强大之处在于其灵活的配置。主配置文件（通常是 &lt;code&gt;/etc/supervisord.conf&lt;/code&gt;）定义了 &lt;code&gt;supervisord&lt;/code&gt; 自身行为、&lt;code&gt;supervisorctl&lt;/code&gt; 连接方式以及包含其他配置文件的路径。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;; /etc/supervisord.conf (示例片段)

[unix_http_server]
file=/var/run/supervisor.sock   ; socket 文件路径，supervisorctl 默认连接这个
;chmod=0700                 ; socket 文件的权限
;chown=nobody:nogroup       ; socket 文件的所有者

[inet_http_server]         ; HTTP 服务器，用于 Web UI 和远程 XML-RPC
;port=127.0.0.1:9001        ; 监听地址和端口 (默认不启用)
;username=user              ; (可选) 访问 Web UI/RPC 的用户名
;password=123               ; (可选) 密码

[supervisord]
logfile=/var/log/supervisor/supervisord.log ; supervisord 自身日志
pidfile=/var/run/supervisord.pid ; supervisord 进程 ID 文件
childlogdir=/var/log/supervisor            ; 子进程日志目录 (如果 program 中没指定绝对路径)
;nodaemon=false             ; false 表示后台运行 (守护进程模式)，true 表示前台运行 (调试用)
;minfds=1024                ; 最小可用文件描述符
;minprocs=200               ; 最小可用进程数
;user=root                  ; supervisord 进程的运行用户 (通常保持 root，以便管理不同用户的子进程)

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///var/run/supervisor.sock ; supervisorctl 连接的服务器 URL
;serverurl=http://127.0.0.1:9001 ; 如果使用 inet_http_server
;username=user              ; 如果服务器设置了认证
;password=123               ;
;prompt=mysupervisor        ; supervisorctl 的提示符

; 包含 conf.d 目录下的所有 .conf 文件
[include]
files = /etc/supervisor/conf.d/*.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;[program:x]&lt;/code&gt; 部分详解 (重点中的重点)&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;这是定义你要管理的应用程序的地方。&lt;code&gt;x&lt;/code&gt; 是你为程序起的名字，在 &lt;code&gt;supervisorctl&lt;/code&gt; 中会用到。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[program:my_web_app]
command=/usr/bin/python /opt/myapp/app.py --port=8080 ; ★ 启动进程的完整命令
process_name=%(program_name)s_%(process_num)02d ; 进程名模板 (用于多实例)
numprocs=1                 ; 启动多少个进程实例 (默认为 1)
directory=/opt/myapp       ; ★ 执行命令前切换到的工作目录
umask=022                  ; 进程的文件模式创建屏蔽码
priority=999               ; 进程启动/关闭优先级 (值越小越高，默认 999)
autostart=true             ; ★ supervisord 启动时自动启动
autorestart=true           ; ★ 进程退出时是否自动重启 (true, false, unexpected)
                           ; unexpected: 仅在退出码不是 exitcodes 定义的值时重启
startsecs=10               ; ★ 进程启动后持续运行多少秒才认为启动成功 (默认 1)
startretries=3             ; ★ 启动失败时的重试次数 (默认 3)
stopsignal=TERM            ; ★ 停止进程时发送的信号 (默认 TERM)
stopwaitsecs=10            ; ★ 发送停止信号后等待多少秒，超时则发送 KILL (默认 10)
stopasgroup=false          ; 是否向整个进程组发送停止信号 (默认 false)
killasgroup=false          ; stop 超时后，是否向整个进程组发送 KILL 信号 (默认 false)
user=www-data              ; ★ 以哪个用户身份运行进程 (重要！)
redirect_stderr=false      ; 是否将 stderr 重定向到 stdout (默认 false)
stdout_logfile=/var/log/supervisor/my_web_app_stdout.log ; ★ 标准输出日志
stdout_logfile_maxbytes=50MB ; ★ 日志文件最大大小 (默认 50MB, 到达后轮转)
stdout_logfile_backups=10  ; ★ 保留多少个轮转后的日志文件 (默认 10)
stdout_capture_maxbytes=1MB ; 捕获到内存用于事件通知的最大字节数
stdout_events_enabled=false ; 是否为 stdout 产生事件 (用于事件监听)
stderr_logfile=/var/log/supervisor/my_web_app_stderr.log ; ★ 标准错误日志
stderr_logfile_maxbytes=50MB ; 同上
stderr_logfile_backups=10  ; 同上
stderr_capture_maxbytes=1MB ; 同上
stderr_events_enabled=false ; 同上
environment=APP_ENV=&quot;production&quot;,SECRET_KEY=&quot;mysecret&quot; ; ★ 设置环境变量 (键值对，逗号分隔)
serverurl=AUTO             ; 通常不需要设置，除非有特殊 RPC 需求
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;[group:x]&lt;/code&gt; 部分&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;用于将多个 &lt;code&gt;[program:y]&lt;/code&gt; 归为一个逻辑组，方便统一管理。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[group:my_app_cluster]
programs=my_web_app,my_worker ; program 名称列表，逗号分隔
priority=998                ; 组的优先级
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;之后可以用 &lt;code&gt;supervisorctl start my_app_cluster:*&lt;/code&gt; 来启动组内所有程序。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;[include]&lt;/code&gt; 部分&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;允许你将配置分散到多个文件中，保持主配置文件的整洁。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[include]
files = /etc/supervisor/conf.d/*.conf /opt/myapp/supervisor_extra.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;6. &lt;code&gt;supervisorctl&lt;/code&gt; 命令行详解&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;supervisorctl&lt;/code&gt; 是与 &lt;code&gt;supervisord&lt;/code&gt; 交互的主要工具。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;进入交互模式&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo supervisorctl
# prompt&amp;gt; status
# prompt&amp;gt; help
# prompt&amp;gt; quit
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;常用命令&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;status [&amp;lt;name&amp;gt;]&lt;/code&gt;: 查看所有或指定程序的运行状态。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;start &amp;lt;name|group:*&amp;gt;|all&lt;/code&gt;: 启动指定程序、组内所有程序或所有程序。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stop &amp;lt;name|group:*&amp;gt;|all&lt;/code&gt;: 停止指定程序、组内所有程序或所有程序。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;restart &amp;lt;name|group:*&amp;gt;|all&lt;/code&gt;: 重启指定程序、组内所有程序或所有程序。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reread&lt;/code&gt;: 重新读取配置文件，检测是否有新增或删除的程序/组。不会影响正在运行的程序。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;update [&amp;lt;gname&amp;gt; ...]&lt;/code&gt;: 应用 &lt;code&gt;reread&lt;/code&gt; 后检测到的配置变更（启动新增的，停止删除的）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reload&lt;/code&gt;: 重启 &lt;code&gt;supervisord&lt;/code&gt; 进程本身（相当于 &lt;code&gt;stop all&lt;/code&gt; + 重启 &lt;code&gt;supervisord&lt;/code&gt; 服务 + &lt;code&gt;start all&lt;/code&gt;）。通常用 &lt;code&gt;reread&lt;/code&gt; + &lt;code&gt;update&lt;/code&gt; 更好。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shutdown&lt;/code&gt;: 关闭 &lt;code&gt;supervisord&lt;/code&gt; 服务（所有管理的进程也会被停止）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pid [&amp;lt;name&amp;gt;]&lt;/code&gt;: 查看指定程序（或所有程序）的进程 ID。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tail [-f] &amp;lt;name&amp;gt; [stdout|stderr]&lt;/code&gt;: 查看指定程序的日志尾部 (&lt;code&gt;-f&lt;/code&gt; 持续跟随)。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fg &amp;lt;name&amp;gt;&lt;/code&gt;: 将程序附加到前台运行 (主要用于调试，按 Ctrl+C 会停止进程)。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;signal &amp;lt;signal_name&amp;gt; &amp;lt;name|group:*&amp;gt;|all&lt;/code&gt;: 向指定程序发送信号 (如 &lt;code&gt;signal HUP my_web_app&lt;/code&gt;)。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;maintail -f&lt;/code&gt;: 同时查看所有程序的 stdout 日志。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 实战教程：使用 Supervisor 管理 Web 应用 (以 Flask/Gunicorn 为例)&lt;/h2&gt;
&lt;p&gt;假设我们有一个简单的 Flask 应用 (&lt;code&gt;/opt/myflaskapp/app.py&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# /opt/myflaskapp/app.py
from flask import Flask
import os
import logging

app = Flask(__name__)

# 配置日志记录到标准输出，以便 Supervisor 捕获
logging.basicConfig(level=logging.INFO, format=&apos;%(asctime)s %(levelname)s: %(message)s&apos;)

@app.route(&apos;/&apos;)
def hello():
    app.logger.info(&apos;Root endpoint was hit!&apos;)
    env = os.environ.get(&apos;APP_ENV&apos;, &apos;development&apos;)
    return f&quot;Hello from Flask in {env} environment!&quot;

if __name__ == &apos;__main__&apos;:
    # 注意：直接运行 &apos;python app.py&apos; 不适用于生产环境
    # 我们将使用 Gunicorn 来运行它
    app.run(host=&apos;0.0.0.0&apos;, port=5000, debug=True)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;目标&lt;/strong&gt;: 使用 Gunicorn 作为 WSGI 服务器运行此应用，并由 Supervisor 管理。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;步骤&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;创建项目目录和虚拟环境&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo mkdir -p /opt/myflaskapp
sudo chown $USER:$USER /opt/myflaskapp # 临时更改所有权以便操作
cd /opt/myflaskapp
python3 -m venv venv
source venv/bin/activate
pip install Flask gunicorn
deactivate
sudo chown -R www-data:www-data /opt/myflaskapp # 将所有权给运行应用的用户 (假设是 www-data)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;确定 Gunicorn 启动命令&lt;/strong&gt;:
我们需要找到虚拟环境中的 &lt;code&gt;gunicorn&lt;/code&gt; 可执行文件路径。通常是 &lt;code&gt;/opt/myflaskapp/venv/bin/gunicorn&lt;/code&gt;。
启动命令类似：
&lt;code&gt;/opt/myflaskapp/venv/bin/gunicorn --workers 3 --bind 0.0.0.0:8000 app:app&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--workers 3&lt;/code&gt;: 启动 3 个工作进程。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--bind 0.0.0.0:8000&lt;/code&gt;: 监听在 8000 端口。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;app:app&lt;/code&gt;: 指定模块 &lt;code&gt;app.py&lt;/code&gt; 中的 &lt;code&gt;app&lt;/code&gt; 对象。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;创建 Supervisor 配置文件&lt;/strong&gt; (&lt;code&gt;/etc/supervisor/conf.d/myflaskapp.conf&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[program:myflaskapp]
command=/opt/myflaskapp/venv/bin/gunicorn --workers 3 --bind 0.0.0.0:8000 app:app
directory=/opt/myflaskapp             ; ★ 必须设置，Gunicorn 需要找到 app.py
user=www-data                         ; ★ 以 www-data 用户运行
autostart=true
autorestart=true
stopsignal=QUIT                       ; Gunicorn 推荐使用 QUIT 实现优雅关闭
stopwaitsecs=60                       ; 等待 Gunicorn worker 优雅退出的时间
killasgroup=true                      ; ★ 重要：确保 Gunicorn 的 master 和 worker 都被正确关闭
stdout_logfile=/var/log/supervisor/myflaskapp_stdout.log
stderr_logfile=/var/log/supervisor/myflaskapp_stderr.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=5
stderr_logfile_maxbytes=100MB
stderr_logfile_backups=5
environment=APP_ENV=&quot;production&quot;      ; ★ 设置环境变量
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;关键点&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;command&lt;/code&gt; 使用虚拟环境中的 &lt;code&gt;gunicorn&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;directory&lt;/code&gt; 设置为项目根目录。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;user&lt;/code&gt; 设置为安全的非 root 用户 (&lt;code&gt;www-data&lt;/code&gt; 或其他)。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stopsignal=QUIT&lt;/code&gt; 和 &lt;code&gt;killasgroup=true&lt;/code&gt; 配合 Gunicorn 实现优雅重启/停止。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;environment&lt;/code&gt; 用于传递配置。&lt;/li&gt;
&lt;li&gt;确保 &lt;code&gt;/var/log/supervisor/&lt;/code&gt; 目录存在且 &lt;code&gt;www-data&lt;/code&gt; 用户有权写入日志文件，或者让 &lt;code&gt;supervisord&lt;/code&gt; (通常以 root 运行) 创建它们。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;加载配置并启动&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo supervisorctl reread
sudo supervisorctl update myflaskapp # 或者直接 update
sudo supervisorctl status myflaskapp
# 应显示 RUNNING
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;测试访问&lt;/strong&gt;:
用 &lt;code&gt;curl http://localhost:8000&lt;/code&gt; 或浏览器访问，应看到 &quot;Hello from Flask in production environment!&quot;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;查看日志&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo tail -f /var/log/supervisor/myflaskapp_stdout.log
sudo tail -f /var/log/supervisor/myflaskapp_stderr.log
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;现在，你的 Flask 应用就由 Supervisor 可靠地管理了！如果 Gunicorn 进程崩溃，Supervisor 会自动重启它。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;8. 高级特性与技巧&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;进程组 (Process Groups)&lt;/strong&gt;: 如前所述，用 &lt;code&gt;[group:x]&lt;/code&gt; 将多个 &lt;code&gt;[program:y]&lt;/code&gt; 묶在一起管理。例如，一个 Web 应用可能需要一个 Web 服务器进程和一个后台任务 Worker 进程，可以将它们放在同一组中。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[group:webapp]
programs=webapp-server,webapp-worker
priority=100

[program:webapp-server]
command=... (gunicorn command)
...

[program:webapp-worker]
command=... (celery worker command)
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;管理：&lt;code&gt;supervisorctl start webapp:*&lt;/code&gt;, &lt;code&gt;supervisorctl stop webapp:*&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;事件监听 (Event Listeners)&lt;/strong&gt;: Supervisor 可以监控进程状态变化（如 &lt;code&gt;STARTING&lt;/code&gt;, &lt;code&gt;RUNNING&lt;/code&gt;, &lt;code&gt;STOPPING&lt;/code&gt;, &lt;code&gt;EXITED&lt;/code&gt;）、心跳（&lt;code&gt;TICK_5&lt;/code&gt;, &lt;code&gt;TICK_60&lt;/code&gt;, &lt;code&gt;TICK_3600&lt;/code&gt;）以及进程的 stdout/stderr 输出，并在事件发生时执行一个特定的程序（监听器）。
这对于实现自定义监控、告警或自动化任务非常有用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[eventlistener:mylogger]
command=/usr/bin/python /opt/scripts/log_event.py ; 监听器脚本
events=PROCESS_STATE,TICK_60                   ; 监听的事件类型
autostart=true
autorestart=true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;监听器脚本 (&lt;code&gt;log_event.py&lt;/code&gt; 示例) 会从 stdin 读取事件信息，并在 stdout 写 &lt;code&gt;READY&lt;/code&gt; 表示准备就绪，&lt;code&gt;OK&lt;/code&gt; 或 &lt;code&gt;FAIL&lt;/code&gt; 表示处理结果。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# /opt/scripts/log_event.py (简化示例)
import sys
import os

def write_stdout(s):
    sys.stdout.write(s)
    sys.stdout.flush()

def write_stderr(s):
    sys.stderr.write(s)
    sys.stderr.flush()

def main():
    while True:
        write_stdout(&apos;READY\n&apos;) # 告诉 supervisord 我准备好了
        line = sys.stdin.readline() # 读取事件头信息
        headers = dict([ x.split(&apos;:&apos;) for x in line.split() ])
        data = sys.stdin.read(int(headers[&apos;len&apos;])) # 读取事件体

        # 在这里处理事件 (例如记录到日志或发送通知)
        log_path = &quot;/var/log/supervisor/events.log&quot;
        with open(log_path, &quot;a&quot;) as f:
            f.write(f&quot;Header: {line}\n&quot;)
            f.write(f&quot;Data: {data}\n---\n&quot;)

        write_stdout(&apos;RESULT 2\nOK&apos;) # 告诉 supervisord 处理成功

if __name__ == &apos;__main__&apos;:
    # 确保日志文件可写 (简单处理，生产环境应更健壮)
    log_dir = os.path.dirname(&quot;/var/log/supervisor/events.log&quot;)
    if not os.path.exists(log_dir):
        os.makedirs(log_dir) # 可能需要权限

    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;XML-RPC 接口与 Web UI&lt;/strong&gt;:
通过在主配置文件中启用 &lt;code&gt;[inet_http_server]&lt;/code&gt;，可以开启 Supervisor 的 HTTP 服务。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Web UI&lt;/strong&gt;: 浏览器访问 &lt;code&gt;http://&amp;lt;server_ip&amp;gt;:9001&lt;/code&gt; (或配置的地址端口)，可以看到一个简单的 Web 界面，用于查看和管理进程。建议设置用户名密码 (&lt;code&gt;username&lt;/code&gt;, &lt;code&gt;password&lt;/code&gt;) 并考虑防火墙或反向代理保护。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;XML-RPC&lt;/strong&gt;: 提供了编程接口，可以用 Python (或其他语言的库) 远程控制 Supervisor。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import xmlrpc.client

# 如果没有认证
# server = xmlrpc.client.ServerProxy(&apos;http://localhost:9001/RPC2&apos;)

# 如果有认证
server = xmlrpc.client.ServerProxy(&apos;http://user:123@localhost:9001/RPC2&apos;)

try:
    print(&quot;Supervisor version:&quot;, server.supervisor.getSupervisorVersion())
    print(&quot;All process info:&quot;, server.supervisor.getAllProcessInfo())
    # 启动进程
    # server.supervisor.startProcess(&apos;myflaskapp&apos;)
except xmlrpc.client.Fault as err:
    print(&quot;XML-RPC Fault:&quot;, err)
except Exception as e:
    print(&quot;Error:&quot;, e)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;日志管理与轮转&lt;/strong&gt;:
Supervisor 内建了简单的日志轮转功能 (&lt;code&gt;stdout_logfile_maxbytes&lt;/code&gt;, &lt;code&gt;stdout_logfile_backups&lt;/code&gt;)。对于更复杂的日志管理需求（如按日期轮转、压缩、发送到中心化日志系统），建议：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;让应用程序自己处理日志轮转（如 Python 的 &lt;code&gt;logging.handlers.RotatingFileHandler&lt;/code&gt; 或 &lt;code&gt;TimedRotatingFileHandler&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;或者将 Supervisor 日志输出到 stdout/stderr (&lt;code&gt;stdout_logfile=/dev/stdout&lt;/code&gt;, &lt;code&gt;stderr_logfile=/dev/stderr&lt;/code&gt;)，然后由外部工具（如 &lt;code&gt;logrotate&lt;/code&gt;, Fluentd, Docker logging drivers）来管理。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;环境变量配置&lt;/strong&gt;:
&lt;code&gt;environment=KEY1=&quot;value1&quot;,KEY2=&quot;value2&quot;&lt;/code&gt; 是设置环境变量的标准方式。值可以包含空格，用引号括起来即可。对于大量或敏感变量，可以考虑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从文件加载：&lt;code&gt;environment=PYTHONPATH=&quot;/opt/myapp/lib&quot;,%(ENV_VAR_FROM_SUPERVISORD)s&lt;/code&gt; (从 supervisord 进程继承)。&lt;/li&gt;
&lt;li&gt;或者在启动脚本中 &lt;code&gt;source&lt;/code&gt; 一个 &lt;code&gt;.env&lt;/code&gt; 文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;优雅停止与信号处理&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;stopsignal&lt;/code&gt;: 定义了 &lt;code&gt;supervisorctl stop&lt;/code&gt; 时发送给进程的信号。常见信号：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TERM&lt;/code&gt;: 通用终止信号，程序应捕获并优雅退出。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;INT&lt;/code&gt;: 中断信号，类似 Ctrl+C。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QUIT&lt;/code&gt;: 类似 &lt;code&gt;TERM&lt;/code&gt;，有时用于请求更优雅的退出（如 Gunicorn）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HUP&lt;/code&gt;: 通常用于通知进程重新加载配置。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;USR1&lt;/code&gt;, &lt;code&gt;USR2&lt;/code&gt;: 用户自定义信号。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stopwaitsecs&lt;/code&gt;: 发送 &lt;code&gt;stopsignal&lt;/code&gt; 后等待进程退出的时间。如果超时，Supervisor 会发送 &lt;code&gt;KILL&lt;/code&gt; 信号强制终止。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stopasgroup=true&lt;/code&gt;: 如果你的主进程会创建子进程，并且希望停止时能同时停止所有子进程，可以设为 &lt;code&gt;true&lt;/code&gt;（前提是主进程是进程组的领导者）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;killasgroup=true&lt;/code&gt;: 如果 &lt;code&gt;stopwaitsecs&lt;/code&gt; 超时后发送 &lt;code&gt;KILL&lt;/code&gt;，&lt;code&gt;killasgroup=true&lt;/code&gt; 会将 &lt;code&gt;KILL&lt;/code&gt; 信号发送给整个进程组。&lt;strong&gt;对于像 Gunicorn/uWSGI 这样的多进程模型，这通常是必要的&lt;/strong&gt;，以确保所有 worker 都被清理。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;9. 常见问题与故障排查 (Troubleshooting)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;supervisorctl&lt;/code&gt; 无法连接 (&lt;code&gt;unix:///var/run/supervisor.sock no such file&lt;/code&gt;)&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;supervisord&lt;/code&gt; 服务是否正在运行？(&lt;code&gt;ps aux | grep supervisord&lt;/code&gt;, &lt;code&gt;systemctl status supervisor(d)&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;配置文件中的 &lt;code&gt;[unix_http_server]&lt;/code&gt; 或 &lt;code&gt;[supervisorctl]&lt;/code&gt; 的 &lt;code&gt;file&lt;/code&gt;/&lt;code&gt;serverurl&lt;/code&gt; 路径是否正确且一致？&lt;/li&gt;
&lt;li&gt;socket 文件 (&lt;code&gt;/var/run/supervisor.sock&lt;/code&gt;) 是否存在？权限是否正确 (运行 &lt;code&gt;supervisorctl&lt;/code&gt; 的用户需要有读写权限)？&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;程序状态为 &lt;code&gt;FATAL&lt;/code&gt; 或 &lt;code&gt;EXITED&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;检查程序的 &lt;code&gt;stderr_logfile&lt;/code&gt; (&lt;code&gt;sudo tail /var/log/supervisor/program_stderr.log&lt;/code&gt;)，通常会包含错误信息。&lt;/li&gt;
&lt;li&gt;检查程序的 &lt;code&gt;stdout_logfile&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;尝试手动在终端中以配置文件中的 &lt;code&gt;user&lt;/code&gt; 身份和 &lt;code&gt;directory&lt;/code&gt; 运行 &lt;code&gt;command&lt;/code&gt;，看是否能成功启动并保持运行。&lt;/li&gt;
&lt;li&gt;检查 &lt;code&gt;startsecs&lt;/code&gt; 设置是否过短，程序可能需要更长时间才能稳定。&lt;/li&gt;
&lt;li&gt;检查系统资源是否耗尽（内存、文件描述符）。&lt;/li&gt;
&lt;li&gt;检查程序依赖是否正确安装（特别是虚拟环境路径）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;配置文件修改后未生效&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;是否运行了 &lt;code&gt;sudo supervisorctl reread&lt;/code&gt;？&lt;/li&gt;
&lt;li&gt;是否运行了 &lt;code&gt;sudo supervisorctl update&lt;/code&gt; 来应用变更？&lt;/li&gt;
&lt;li&gt;检查配置文件语法是否有误 (&lt;code&gt;supervisord -n -c /etc/supervisord.conf&lt;/code&gt; 可以检查配置但不启动)。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;日志文件过大&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;检查 &lt;code&gt;stdout_logfile_maxbytes&lt;/code&gt; 和 &lt;code&gt;stdout_logfile_backups&lt;/code&gt; (以及 stderr 的对应项) 配置是否正确。&lt;/li&gt;
&lt;li&gt;考虑使用外部日志轮转工具或让应用自己管理日志。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;权限问题&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;确保配置文件中指定的 &lt;code&gt;user&lt;/code&gt; 有权限执行 &lt;code&gt;command&lt;/code&gt;、读写 &lt;code&gt;directory&lt;/code&gt; 以及写入日志文件。&lt;/li&gt;
&lt;li&gt;如果 Supervisor 以 root 运行，它通常能创建日志文件，但需要确保后续进程（以 &lt;code&gt;user&lt;/code&gt; 身份运行）有权写入。可以预先创建日志文件并设置正确的所有权和权限。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;10. Supervisor vs. Systemd vs. PM2 等&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Supervisor&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;优点&lt;/strong&gt;: 简单易用，专注于应用进程管理，跨平台性较好（类 UNIX），配置直观，资源占用低。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点&lt;/strong&gt;: 不像 systemd 那样与 OS 深度集成（如 cgroups 资源限制、依赖管理、socket 激活等功能较弱），主要面向单个主机。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;适用场景&lt;/strong&gt;: 管理单个服务器上的多个用户态应用程序进程，快速部署和管理后台任务、Web 服务。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Systemd&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;优点&lt;/strong&gt;: Linux 系统的标准 init 系统，功能强大，与 OS 深度集成（日志管理 journald, cgroups 资源控制, socket 激活, 定时任务, 服务依赖等），非常稳定。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点&lt;/strong&gt;: 配置相对复杂，学习曲线陡峭，强绑定于使用 systemd 的 Linux 发行版。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;适用场景&lt;/strong&gt;: 管理系统级服务和需要精细 OS 资源控制的应用，已成为现代 Linux 发行版的标准。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PM2&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;优点&lt;/strong&gt;: 专门为 Node.js 应用设计，功能丰富（集群模式、负载均衡、零停机重启、监控面板、模块系统），开箱即用体验好。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点&lt;/strong&gt;: 主要面向 Node.js 生态，虽然也能管理其他类型进程，但不如 Supervisor 或 Systemd 通用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;适用场景&lt;/strong&gt;: Node.js 应用的生产环境部署和管理。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Docker/Kubernetes&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;优点&lt;/strong&gt;: 提供容器化环境，隔离性强，易于打包和分发，支持跨主机集群管理、自动伸缩、服务发现等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点&lt;/strong&gt;: 学习曲线更陡峭，资源消耗相对较高，引入了新的抽象层。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;适用场景&lt;/strong&gt;: 微服务架构，需要标准化部署、弹性和跨云环境的复杂应用。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;选择建议&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果只需要在单台 Linux 服务器上管理几个应用进程，且追求简单快捷，&lt;strong&gt;Supervisor&lt;/strong&gt; 是个绝佳选择。&lt;/li&gt;
&lt;li&gt;如果你已经在使用现代 Linux 发行版，并且需要更强大的 OS 集成功能或管理系统级服务，学习并使用 &lt;strong&gt;Systemd&lt;/strong&gt; 是值得的。&lt;/li&gt;
&lt;li&gt;如果你主要开发和部署 &lt;strong&gt;Node.js&lt;/strong&gt; 应用，&lt;strong&gt;PM2&lt;/strong&gt; 提供了量身定做的优秀体验。&lt;/li&gt;
&lt;li&gt;如果你的应用需要部署在 &lt;strong&gt;容器&lt;/strong&gt; 环境或 &lt;strong&gt;集群&lt;/strong&gt; 中，&lt;strong&gt;Docker&lt;/strong&gt; 和 &lt;strong&gt;Kubernetes&lt;/strong&gt; 是更现代化的解决方案（注意：Supervisor 也可以在 Docker 容器 &lt;em&gt;内部&lt;/em&gt; 使用，用于管理容器内的多个进程，但这通常被认为是一种反模式，推荐一个容器只运行一个主进程）。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;11. 总结与最佳实践&lt;/h2&gt;
&lt;p&gt;Supervisor 是一个成熟、可靠且易于使用的进程控制系统，极大地简化了类 UNIX 系统上应用程序进程的管理。它通过自动启动、监控和重启，确保了服务的可用性。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;最佳实践回顾&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;使用非 Root 用户&lt;/strong&gt;: 在 &lt;code&gt;[program:x]&lt;/code&gt; 中始终指定 &lt;code&gt;user&lt;/code&gt; 为一个权限受限的用户。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;明确工作目录&lt;/strong&gt;: 使用 &lt;code&gt;directory&lt;/code&gt; 指定命令执行的上下文。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;管理日志&lt;/strong&gt;: 合理配置日志路径、大小和轮转 (&lt;code&gt;stdout_logfile&lt;/code&gt;, &lt;code&gt;stderr_logfile&lt;/code&gt;, &lt;code&gt;...maxbytes&lt;/code&gt;, &lt;code&gt;...backups&lt;/code&gt;)，或结合外部日志工具。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优雅关闭&lt;/strong&gt;: 理解并配置好 &lt;code&gt;stopsignal&lt;/code&gt;, &lt;code&gt;stopwaitsecs&lt;/code&gt;, &lt;code&gt;stopasgroup&lt;/code&gt;, &lt;code&gt;killasgroup&lt;/code&gt;，特别是对于多进程应用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模块化配置&lt;/strong&gt;: 使用 &lt;code&gt;[include]&lt;/code&gt; 和 &lt;code&gt;conf.d&lt;/code&gt; 目录组织配置文件。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;环境变量&lt;/strong&gt;: 使用 &lt;code&gt;environment&lt;/code&gt; 传递配置，避免硬编码。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;监控与告警&lt;/strong&gt;: 利用 &lt;code&gt;supervisorctl status&lt;/code&gt;、日志、Web UI 或事件监听器进行监控。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;安全&lt;/strong&gt;: 如果启用 &lt;code&gt;inet_http_server&lt;/code&gt;，务必设置认证并使用防火墙或反向代理保护。&lt;code&gt;unix_http_server&lt;/code&gt; 的 socket 文件权限也要注意。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;理解 &lt;code&gt;autorestart&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;unexpected&lt;/code&gt; 选项通常比 &lt;code&gt;true&lt;/code&gt; 更安全，避免程序在正常退出（如完成任务）后被无限重启。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;reread&lt;/code&gt; 与 &lt;code&gt;update&lt;/code&gt;&lt;/strong&gt;: 掌握这两个命令，安全地更新配置。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;掌握 Supervisor，无疑会让你在应用部署和运维管理上更加得心应手。希望这篇从入门到精通的指南能帮助你充分利用 Supervisor 的强大功能！&lt;/p&gt;
</content:encoded></item><item><title>MkDocs Material 实践</title><link>https://bangwu.top/posts/mkdocs/</link><guid isPermaLink="true">https://bangwu.top/posts/mkdocs/</guid><description>MkDocs Material 的安装、配置与协作流程</description><pubDate>Tue, 25 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;为什么选择 MkDocs&lt;/h2&gt;
&lt;p&gt;写技术文档时，我们通常会在 VitePress、Docusaurus、MkDocs、Sphinx 之间挑选。MkDocs Material 兼顾了“写 Markdown 的丝滑体验”和“默认主题即是业界标杆”两件事：不用懂 React、Vue，也能写出交互友好的文档。更重要的是，它的配置文件精简易读，适合小团队快速搭建知识库和项目手册。&lt;/p&gt;
&lt;h2&gt;环境准备&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;安装 Python 3.9+，建议顺手安装 &lt;code&gt;pipx&lt;/code&gt; 方便管理命令行工具。&lt;/li&gt;
&lt;li&gt;创建独立虚拟环境，避免和项目依赖混在一起：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;python -m venv .venv
source .venv/bin/activate          # Windows 使用 .venv\Scripts\activate
pip install --upgrade pip
pip install mkdocs-material
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;初始化骨架：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;mkdocs new docs-site
cd docs-site
mkdocs serve                      # 默认 http://127.0.0.1:8000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MkDocs 会在 &lt;code&gt;docs/&lt;/code&gt; 目录下生成 Markdown 文件，并通过 &lt;code&gt;mkdocs.yml&lt;/code&gt; 管理导航、主题和插件。&lt;/p&gt;
&lt;h2&gt;配置文件要点&lt;/h2&gt;
&lt;p&gt;以下是一个常用的 &lt;code&gt;mkdocs.yml&lt;/code&gt; 示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;site_name: Awesome Docs
theme:
  name: material
  language: zh
  palette:
    - scheme: default
      primary: blue
      accent: light blue
  features:
    - navigation.tabs
    - navigation.sections
    - content.code.annotate
markdown_extensions:
  - admonition
  - pymdownx.superfences
  - pymdownx.tabbed:
      alternate_style: true
plugins:
  - search
  - blog
  - tags
nav:
  - 🏠 概览: index.md
  - 快速开始:
      - 安装: getting-started/install.md
      - 配置: getting-started/config.md
  - 进阶:
      - 主题定制: advanced/theme.md
      - 部署: advanced/deploy.md
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;features&lt;/code&gt; 中的选项可以开启目录分组、代码注释、粘性导航等 Material 主题特性。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;markdown_extensions&lt;/code&gt; 建议启用 &lt;code&gt;pymdownx&lt;/code&gt; 系列，支持代码标签、折叠块、键盘符号等增强语法。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;plugins&lt;/code&gt; 里常见的还有 &lt;code&gt;minify&lt;/code&gt;, &lt;code&gt;git-revision-date-localized&lt;/code&gt;, &lt;code&gt;awesome-pages&lt;/code&gt; 等，可根据项目体量再行添加。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Markdown 写作增强&lt;/h2&gt;
&lt;p&gt;Material 对 Markdown 语法进行了大量扩展，可以降低文档维护成本：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;提示块&lt;/strong&gt;：使用标准的 admonition 语法即可生成漂亮的提醒框。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;!!! note &quot;接口说明&quot;
    这里会展示接口返回的数据结构示例。
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;标签页&lt;/strong&gt;：通过 &lt;code&gt;pymdownx.tabbed&lt;/code&gt; 将多语言代码段组合在一起。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;=== &quot;Python&quot;
    ```py
    print(&quot;hello&quot;)
    ```
=== &quot;Go&quot;
    ```go
    fmt.Println(&quot;hello&quot;)
    ```
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;数据表格&lt;/strong&gt;：搭配 &lt;code&gt;pymdownx.tabular&lt;/code&gt; 或直接使用 Markdown 表格，Material 会自动添加滚动和排序能力。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;插入 Mermaid&lt;/strong&gt;：启用 &lt;code&gt;pymdownx.superfences&lt;/code&gt; 后，可直接书写流程图与时序图，文档即刻渲染。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;自定义主题与品牌化&lt;/h2&gt;
&lt;p&gt;当需要加入品牌色、字体或组件时，最简方案是改写 &lt;code&gt;docs/stylesheets/extra.css&lt;/code&gt; 并在配置中引入：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;extra_css:
  - stylesheets/extra.css
extra_javascript:
  - javascripts/analytics.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配合 &lt;code&gt;mkdocs-material&lt;/code&gt; 的 &lt;code&gt;theme.custom_dir&lt;/code&gt; 参数可以覆写部分模板，实现首页英雄横幅、页脚版权信息等定制化。记得把公共变量抽进 &lt;code&gt;partials&lt;/code&gt;，避免每次升级主题时难以合并。&lt;/p&gt;
&lt;h2&gt;部署与持续集成&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;GitHub Pages&lt;/strong&gt;：执行 &lt;code&gt;mkdocs gh-deploy --force&lt;/code&gt; 即可发布到 &lt;code&gt;&amp;lt;username&amp;gt;.github.io&lt;/code&gt;。在 CI/CD 中可以使用官方提供的 &lt;a href=&quot;https://github.com/marketplace/actions/deploy-mkdocs&quot;&gt;GitHub Actions workflow&lt;/a&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;内网发布&lt;/strong&gt;：将 &lt;code&gt;mkdocs build&lt;/code&gt; 生成的 &lt;code&gt;site/&lt;/code&gt; 目录交给 Nginx/OSS/CDN 即可。推荐在流水线中添加 &lt;code&gt;pip install mkdocs-material[imaging]&lt;/code&gt; 提前生成图标、暗色主题资源。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;预览环境&lt;/strong&gt;：使用 &lt;code&gt;mkdocs serve -a 0.0.0.0:8000&lt;/code&gt;，让团队成员在局域网访问实时预览；配合 Docker 可以做到一行命令启动：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run --rm -it -p 8000:8000 -v ${PWD}:/docs squidfunk/mkdocs-material
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;协同编辑流程&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;将 &lt;code&gt;docs/&lt;/code&gt; 配合 Conventional Commit 与 PR 检查流程管理，避免文档与代码脱节。&lt;/li&gt;
&lt;li&gt;利用 &lt;code&gt;mkdocs serve --dirtyreload&lt;/code&gt; 获得实时刷新体验，写作效率显著提升。&lt;/li&gt;
&lt;li&gt;为大型团队增加 &lt;code&gt;markdownlint&lt;/code&gt;、&lt;code&gt;typos&lt;/code&gt; 等校验工具，保证行文风格一致性。&lt;/li&gt;
&lt;li&gt;若文档与版本关联，使用 &lt;code&gt;mike&lt;/code&gt; 插件维护多版本站点，保留历史版本入口。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;常用资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://squidfunk.github.io/mkdocs-material/&quot;&gt;官方文档&lt;/a&gt;：每个 feature 都有 demo 代码，直接复用。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/squidfunk/mkdocs-material/tree/master&quot;&gt;mkdocs-boilerplate&lt;/a&gt; 示例仓库：学习如何组织目录和导航。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://diataxis.fr/&quot;&gt;Diátaxis 文档写作方法论&lt;/a&gt;：帮助我们区分教程、解释、操作指南与参考资料，合理拆分文档结构。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docsearch.algolia.com/&quot;&gt;DocSearch&lt;/a&gt;：为公开站点申请免费全文检索服务，搜索体验对大型文档尤为重要。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Python 自动化浏览器实践</title><link>https://bangwu.top/posts/python-auto/</link><guid isPermaLink="true">https://bangwu.top/posts/python-auto/</guid><description>使用 Python 驾驭浏览器完成测试、采集与脚本化操作的经验总结</description><pubDate>Thu, 20 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;为什么要让浏览器自动跑&lt;/h2&gt;
&lt;p&gt;日常测试、数据采集、批量操作后台系统时，手点页面既低效又容易出错。Python 生态提供了多种浏览器自动化方案，可以在保留真实渲染环境的情况下完成登录、数据抓取、导出报告等任务。&lt;/p&gt;
&lt;p&gt;常用框架对比：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;工具&lt;/th&gt;
&lt;th&gt;特点&lt;/th&gt;
&lt;th&gt;适合场景&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Selenium&lt;/td&gt;
&lt;td&gt;历史悠久，生态丰富，与各大浏览器兼容&lt;/td&gt;
&lt;td&gt;UI 自动化测试、旧系统兼容&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Playwright&lt;/td&gt;
&lt;td&gt;现代、API 简洁，自带浏览器管理&lt;/td&gt;
&lt;td&gt;多语言支持、高并发采集&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requests + browsercookie&lt;/td&gt;
&lt;td&gt;无头访问，绕过浏览器成本&lt;/td&gt;
&lt;td&gt;已有 Cookie/Token、接口稳定&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;以下内容以 Selenium 与 Playwright 为主线，补充一些工程化实践。&lt;/p&gt;
&lt;h2&gt;环境准备&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;python -m venv .venv
source .venv/bin/activate  # Windows 使用 .venv\Scripts\activate
pip install --upgrade pip
pip install selenium~=4.21 webdriver-manager~=4.0 playwright~=1.44
playwright install chromium
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;webdriver-manager&lt;/code&gt; 可自动下载匹配版本的浏览器驱动；若内网环境，请预下载并配置 &lt;code&gt;PATH&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;Playwright 自带浏览器二进制，执行 &lt;code&gt;playwright install&lt;/code&gt; 即可。&lt;/li&gt;
&lt;li&gt;建议把这些安装写进 &lt;code&gt;requirements.txt&lt;/code&gt; 或 &lt;code&gt;pyproject.toml&lt;/code&gt;，方便 CI/CD 复现。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Selenium 抓取接口 Token&lt;/h2&gt;
&lt;p&gt;部分前端只在浏览器里发请求，需要我们在网络日志里捕获 Token。下方示例基于 Chrome DevTools Protocol：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import json
import time
import urllib.parse

from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By


def get_token(timeout: int = 15) -&amp;gt; str | None:
    caps = DesiredCapabilities.CHROME
    caps[&quot;goog:loggingPrefs&quot;] = {&quot;performance&quot;: &quot;ALL&quot;}

    options = webdriver.ChromeOptions()
    options.add_argument(&quot;--headless=new&quot;)
    options.add_argument(&quot;--disable-gpu&quot;)
    options.add_argument(&quot;--window-size=1280,720&quot;)

    driver = webdriver.Chrome(options=options, desired_capabilities=caps)
    target_url = &quot;https://vp.fact.qq.com/home&quot;
    api_pattern = &quot;/api/config/initial&quot;

    try:
        driver.get(target_url)
        WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.CSS_SELECTOR, &quot;[data-page-ready]&quot;))
        )

        start_time = time.time()
        token = None

        while time.time() - start_time &amp;lt; timeout and not token:
            for log_item in driver.get_log(&quot;performance&quot;):
                message = json.loads(log_item[&quot;message&quot;])[&quot;message&quot;]
                if message.get(&quot;method&quot;) != &quot;Network.requestWillBeSent&quot;:
                    continue

                request = message[&quot;params&quot;][&quot;request&quot;]
                url = request.get(&quot;url&quot;, &quot;&quot;)
                if api_pattern in url:
                    parsed = urllib.parse.urlparse(url)
                    qs = urllib.parse.parse_qs(parsed.query)
                    token = qs.get(&quot;token&quot;, [None])[0]
                    break

        if token:
            print(&quot;捕获 Token:&quot;, token)
        else:
            print(&quot;未在超时时间内发现 Token&quot;)
        return token
    finally:
        driver.quit()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要点归纳：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;新版 Chrome 建议启用 &lt;code&gt;--headless=new&lt;/code&gt;，兼容性更好。&lt;/li&gt;
&lt;li&gt;加 &lt;code&gt;WebDriverWait&lt;/code&gt; 确保页面加载完再监听日志。&lt;/li&gt;
&lt;li&gt;尝试控制循环超时时间，避免长时间阻塞或 CPU 飙升。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Playwright 的替代写法&lt;/h2&gt;
&lt;p&gt;Playwright 同样能抓取网络请求，并且 API 更直接：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from playwright.sync_api import sync_playwright


def fetch_token(target: str) -&amp;gt; str | None:
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        page = browser.new_page()
        token_holder = {&quot;value&quot;: None}

        def handle_request(route):
            url = route.request.url
            if &quot;/api/config/initial&quot; in url:
                token_holder[&quot;value&quot;] = route.request.url.split(&quot;token=&quot;)[-1]
            route.continue_()

        page.route(&quot;**/api/config/initial*&quot;, handle_request)
        page.goto(target, wait_until=&quot;networkidle&quot;)
        browser.close()
        return token_holder[&quot;value&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Playwright 能直接对特定请求做 &lt;code&gt;route&lt;/code&gt; 拦截，减少日志解析。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wait_until=&quot;networkidle&quot;&lt;/code&gt; 避免页面仍在加载时就关闭浏览器。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;自动化常见任务清单&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;UI 回归测试&lt;/strong&gt;：搭配 &lt;code&gt;pytest&lt;/code&gt;，为关键流程编写断言，用 &lt;code&gt;pytest-xdist&lt;/code&gt; 并发执行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据采集&lt;/strong&gt;：与 &lt;code&gt;pandas&lt;/code&gt;、&lt;code&gt;openpyxl&lt;/code&gt; 结合，把页面数据写出 Excel。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;批量操作&lt;/strong&gt;：后台管理系统重复操作（如批量审批、导入），可以沿用录制脚本。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;截图/录屏&lt;/strong&gt;：Selenium 可通过 &lt;code&gt;driver.save_screenshot&lt;/code&gt; 捕获截图；Playwright 原生支持录像 &lt;code&gt;page.video&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;下载管理&lt;/strong&gt;：Playwright 提供 &lt;code&gt;page.expect_download()&lt;/code&gt;；Selenium 需通过 Chrome 配置 &lt;code&gt;download.default_directory&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;工程化细节&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;结构化项目&lt;/strong&gt;：把页面元素封装为 Page Object，可读性和复用度更高。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;依赖管理&lt;/strong&gt;：CI 中将浏览器二进制缓存到镜像或制品仓库，缩短冷启动时间。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;日志与报告&lt;/strong&gt;：结合 &lt;code&gt;pytest-html&lt;/code&gt; 或 &lt;code&gt;allure&lt;/code&gt; 生成测试报告，失败时附带截图。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;定时调度&lt;/strong&gt;：在 Airflow/Prefect 下运行脚本，统一重试策略与告警。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docker 化&lt;/strong&gt;：使用 &lt;code&gt;selenium/standalone-chrome&lt;/code&gt;、&lt;code&gt;mcr.microsoft.com/playwright/python&lt;/code&gt; 等镜像可避免宿主机缺依赖。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;合规与反爬注意事项&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;确认你有权访问目标数据，遵守网站 Robots 协议与法律法规。&lt;/li&gt;
&lt;li&gt;控制请求频率，通过 &lt;code&gt;time.sleep&lt;/code&gt; 或队列限流，避免触发风控。&lt;/li&gt;
&lt;li&gt;对于登录态相关脚本，及时处理 Cookie 过期和验证码；可结合第三方识别服务，但需评估安全风险。&lt;/li&gt;
&lt;li&gt;生产环境保管好账号、Token 等敏感信息，建议接入 Vault、Secrets Manager。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;进一步阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.selenium.dev/documentation/webdriver/&quot;&gt;Selenium Docs&lt;/a&gt;：官方 WebDriver API 文档。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://playwright.dev/python/docs/intro&quot;&gt;Playwright Documentation&lt;/a&gt;：详尽示例和调试技巧。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chromedevtools.github.io/devtools-protocol/&quot;&gt;Chrome DevTools Protocol&lt;/a&gt;：了解网络日志结构的最佳资料。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>这个世界怎么了</title><link>https://bangwu.top/posts/whatiswrong/</link><guid isPermaLink="true">https://bangwu.top/posts/whatiswrong/</guid><description>找实习的焦虑与自省：大三下的现实冲击</description><pubDate>Tue, 11 Mar 2025 15:36:02 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;audio controls data-title=&quot;虚拟&quot; data-artist=&quot;陈粒&quot; data-cover=&quot;https://cdn.bangwu.top/img/20260111w2e48-oz.webp&quot;&amp;gt;
&amp;lt;source src=&quot;https://cdn.bangwu.top/img/2026011193e34430.mp3&quot; type=&quot;audio/mpeg&quot;&amp;gt;
&amp;lt;/audio&amp;gt;&lt;/p&gt;
&lt;p&gt;最近在找实习，大三下学期了，加上自己本身也没有考研的打算（觉得考研就两个原因逃避工作和热爱科研，可惜两者我都不想要）笨拙地打开了社会的大门——实习。
我没做好准备...是的，我还无法面对工作带来的压力，无法从安逸的校园生活中快速切换到通勤、同事、汇报各种职场中，我也并不是在逃避 ，只是暂时还无法适应😔2.27补考成绩出了，过了，悬着的心终于放下然后就奋力投入到投简历、找实习中。2.28在 &lt;a href=&quot;https://rxresu.me&quot;&gt;https://rxresu.me&lt;/a&gt; 中创建了第一个简历开启了我的大学回顾生涯，简历上总要写点什么亮眼的东西吧？回想大学期间学到了什么，好像没一个拿得出手的项目，唯一能提得上的就是自己的爬虫技能还有Markdown（当然，这个肯定不算）剩下的还有什么，弄不清的人际关系还是时常情绪低落的自己。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/202601110d418d34.jpg&quot; alt=&quot;bangwu20250311233546&quot; /&gt;&lt;/p&gt;
&lt;p&gt;那天是周五，下午有节课是我大学期间最喜欢的老师之一上的，记得她第一次给我们上课是大二下学期《创新思维与学术写作》一开始就被她的讲课风格所吸引，总是喜欢轻松愉快的上课氛围，还有能与学生开玩笑、互动，不免想起那时的我，一年前的自己...那时的自己总有一种自信，觉得这个世界上虽然有很难很难的事情，但是我棒无一定可以做得到参透其中的奥秘，于是结课论文就写了《基于 LangChain 的问答机器人设计——以保险定价策略建议问答机器人为例》经过这个论文也慢慢认定了论文格式会限制学术的发展，那学期，有许多奇妙的想法也有求知的热爱得以实现......&lt;/p&gt;
&lt;p&gt;3.5我完成了简历的初版，我的第一份简历，上面写着手机号，微信号，邮箱还有一个帅气的大头照。我在简历中说“热爱技术，有代码编写规范，有较强的自学能力，对新技术有较强的兴趣且持续关注行业动态，在相关论坛活跃，积极拥抱AI技术”事实上我只有这些了，剩下的就是一些Python，git，Linux之类的，项目经验拿的是大二时参加可视化竞赛做的一个项目，没有实习经历。可能简历中唯一显眼的就是学校吧，这也是我唯一能拿得出手的闪光点了。于是就到处海投，投了很多大厂的暑期实习，只要是base上海的满足基本要求的我都投了，事实只有大厂愿意给面试机会，小厂要的是直接能上手项目，有一点不满足要求就直接pass了，于是迎来了第一个笔试，第一个面试（字节-飞书测开）&lt;a href=&quot;https://base.bangwu.top/env/%E9%9D%A2%E7%BB%8F/&quot;&gt;面经&lt;/a&gt;
这里有我的笔试和面经，首先我清楚的一点是我没有算法基础，只会简单的排序算法当然进不了大厂的，面试只是为了多锻炼一下自己，希望给自己提个醒该努力了，感谢面试官的宝贵时间🙏&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/202601116198d3d4.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;最近几天都是比较焦虑的状态，直到中午面完试，所有的投递也都进入了待处理的状态，我也从忙碌中抽出身来第一件事就是睡觉休息，然后心里暗暗下定决心，不可否认的是我确实摆烂了很长很长时间，正如之前一个朋友说的，你选择了自学但是还是没坚持下去
我说这些不是博取同情，不要同情我，我是个可恨的人，也不是为了讲现在的我被工作殴打的千疮百孔，事实上我仍然能找到实习只是预期降低了，同样我现在仍然有着探索新事物的好奇心，在写这篇文章前我还浅浅了解了一下字节刚开源的跨端框架Lynx&lt;/p&gt;
&lt;p&gt;从前总觉得一味地背东西很low啊，上高中的时候看不起只知道套公式做题，死板的背语文答题模板的同学，上大学时看不起为了卷绩点多与老师互动，期末疯狂背知识点的学生，越来越觉得人生是一场持久的学习，现在觉得看不起自己，正如我曾七次鄙视自己的灵魂，当它本可进取时却故作谦卑。最近想到了好多以前的事情，不过只是叹息一下又迅速投入到找实习中&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/2026011171da27da.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;晚上洗澡的时候，和舍友聊天说找实习好难啊，聊着聊着就说大环境不好，我想起了那句“大环境不好大家都开始怀疑努力的意义了”这个世界怎么了，是啊，这个世界怎么了
焦虑中前进，大家共勉！&lt;/p&gt;
&lt;p&gt;“你是我未曾拥有无法捕捉的亲昵
我却有你的吻你的魂你的心
载着我飞呀飞呀飞 越过了意义”&lt;/p&gt;
</content:encoded></item><item><title>幸福一定是个陷阱吧</title><link>https://bangwu.top/posts/fear-happiness/</link><guid isPermaLink="true">https://bangwu.top/posts/fear-happiness/</guid><description>对幸福的恐惧与接纳：别把快乐当陷阱</description><pubDate>Fri, 28 Feb 2025 01:50:10 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;audio controls data-title=&quot;幸福了，然后呢&quot; data-artist=&quot;A-Lin&quot; data-cover=&quot;https://cdn.bangwu.top/img/20260111vwzdg-hw.webp&quot;&amp;gt;
&amp;lt;source src=&quot;https://cdn.bangwu.top/img/20260111bb8d7ced.mp3&quot; type=&quot;audio/mpeg&quot;&amp;gt;
&amp;lt;/audio&amp;gt;&lt;/p&gt;
&lt;p&gt;我有个不爱吃甜食的朋友，每次让他吃甜食，糖果之类的他就拒绝，说不爱吃甜的，问其原因也只是敷衍的说小时候糖吃多了，长大了就不爱吃了。是啊，他小时候特别爱吃糖，有记忆的时候嘴里就一直有着糖果，后来也不出所料得了蛀牙，牙疼。长大了一些，就记住了蛀牙带来的疼痛，空壳的牙齿一直在提醒他不要吃甜的，然后就习惯了，只是觉得甜的东西是多余了，这就好像我们生活中的幸福，记得瑞克和莫蒂里面有一个经典的桥段，只有少数强大的存在才惧怕幸福，幸福不可能持久。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/202601114d0e04f0.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;当你经历过幸福的到来又失去，你就会不断怀疑幸福存在的真实性，当幸福再次到来的时候你只会觉得像是虚幻的泡沫，这就是为什么接受幸福比接受痛苦更需要勇气。
后来觉得难以接受的未来也慢慢接受了，我就在想，是不是生活中这些普通的日子慢慢把我们的理想，热情，斗志通通磨灭了，最后连同对未来的期待也一同丢尽了垃圾桶，进了焚烧炉。
每次写作的时候，我大部分都是情绪憋不住了，要写出来，好像这样，就可以把我未说出口的话讲给想要讲的人，那些看不到的人啊，你们能知道这些吗
现在我想问问一年后的自己，是否在工作了？每天重复的工作会觉得烦么？想问问三年后的自己，你现在回到老家了吗？买了自己的车了没有？还在玩光遇么...&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/2026011133715432.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我其实一点也不脆弱，那天晚上我告诉你我哭了，哭的一点也不撕心裂肺...后来你告诉我，觉得我哭的好莫名其妙 好离谱，从那一刻起，我就知道你在我心里从必不可少的人变成了可有可无的人了。只是，还会怀念以前的日子，但不会想回去了“人生若只如初见”&lt;/p&gt;
&lt;p&gt;当然，后来畏惧幸福，然后就失去了好多，比如对时间的感知，能自给自足的快乐，还有那份曾经轻而易举就能感受到的满足。幸福像是一把双刃剑，越是渴望，越是害怕失去，于是索性将自己封闭起来，假装不需要它。时间在指尖悄然流逝，却不再有那种“此刻即是永恒”的踏实感。日子变得机械，像是被设定好的程序，重复着同样的动作，却少了灵魂的参与。&lt;/p&gt;
&lt;p&gt;曾经，一片落叶、一缕阳光、一句不经意的话，都能让我感到世界的美好。可如今，这些微小的快乐似乎被一层无形的屏障隔绝在外。我开始怀疑，是不是自己太过贪婪，想要得太多，才让幸福变得如此遥远。可后来才明白，幸福从来不是遥不可及的东西，而是我们是否愿意敞开心扉去接纳它。&lt;/p&gt;
&lt;p&gt;或许，畏惧幸福是因为害怕失去，害怕那种从高处跌落的痛楚。可生活本就是起伏不定的，没有人能永远站在顶峰，也没有人会永远沉在谷底。与其因为害怕失去而拒绝幸福，不如学会在拥有时珍惜，在失去时释然。毕竟，幸福不是一种状态，而是一种选择。&lt;/p&gt;
&lt;p&gt;我开始尝试重新找回那些被遗忘的快乐，比如在清晨醒来时，感受第一缕阳光洒在脸上的温暖；比如在忙碌之余，给自己一杯热茶，静静地品味片刻的宁静；比如在孤独时，学会与自己对话，倾听内心的声音。这些微小的瞬间，或许不足以改变生活的全部，但它们让我重新感受到，幸福其实一直都在，只是我们是否愿意看见它。&lt;/p&gt;
&lt;p&gt;于是，我告诉自己，不要再畏惧幸福。即使它会带来短暂的脆弱，即使它可能会在某一天消失，但至少，我曾真实地拥有过它。而这份拥有，足以让我的生命变得更加丰盈。&lt;/p&gt;
&lt;p&gt;幸福既非甜蜜的牢笼，也不是虚幻的泡影。当我们停止用社会标尺丈量幸福，转而向内建立价值坐标系；当能欣赏阴晴圆缺的完整性，在耕耘中体会生命力的舒展——这种带着清醒觉知的幸福，或许就是破解陷阱魔咒的终极密码。就像黑塞在《悉达多》中写道的：「智慧无法言传。智者试图传授智慧，总像痴人说梦。」真正的答案，永远在每个人的躬身实践中。&lt;/p&gt;
&lt;p&gt;“幸福了，
然后呢，
爱情用什么再确认。”&lt;/p&gt;
</content:encoded></item><item><title>Python标准库——re</title><link>https://bangwu.top/posts/python-standard-re/</link><guid isPermaLink="true">https://bangwu.top/posts/python-standard-re/</guid><description>Python标准库系列</description><pubDate>Tue, 25 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;正则&lt;/h2&gt;
&lt;p&gt;::github{repo=&quot;any86/any-rule&quot;}&lt;/p&gt;
&lt;p&gt;Python 的 &lt;code&gt;re&lt;/code&gt; 库是用于处理正则表达式的核心库，提供字符串匹配、替换、分割等功能。以下是其常用用法及示例：&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;1. 基本匹配&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;re.match(pattern, string)&lt;/code&gt;&lt;/strong&gt;
从字符串&lt;strong&gt;开头&lt;/strong&gt;匹配，返回第一个匹配对象；失败返回 &lt;code&gt;None&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import re
result = re.match(r&apos;hello&apos;, &apos;hello world&apos;)
if result:
    print(result.group())  # 输出: hello
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;re.search(pattern, string)&lt;/code&gt;&lt;/strong&gt;
在字符串中&lt;strong&gt;全局搜索&lt;/strong&gt;第一个匹配项。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;result = re.search(r&apos;world&apos;, &apos;hello world&apos;)
print(result.group())  # 输出: world
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;2. 查找所有匹配&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;re.findall(pattern, string)&lt;/code&gt;&lt;/strong&gt;
返回所有匹配的字符串列表。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;matches = re.findall(r&apos;\d+&apos;, &apos;2021年7月7日，2002年02月03日&apos;)
print(matches)  # 输出: [&apos;2021&apos;, &apos;7&apos;, &apos;7&apos;, &apos;2002&apos;, &apos;02&apos;, &apos;03&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;re.finditer(pattern, string)&lt;/code&gt;&lt;/strong&gt;
返回迭代器，逐个生成匹配对象（适合处理大文本）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for match in re.finditer(r&apos;\d+&apos;, &apos;2021年7月7日&apos;):
    print(match.group())  # 依次输出: 2021, 7, 7
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;3. 字符串替换&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;re.sub(pattern, repl, string)&lt;/code&gt;&lt;/strong&gt;
替换匹配的字符串。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;text = &quot;dawdada&quot;
text_new = re.sub(r&apos;a&apos;, &apos;%&apos;, text)  # 替换所有 &apos;a&apos; 为 %
print(text_new)  # 输出: d%w%d%d%

# 使用分组反向引用（如 \1 代表第一个分组）
para = re.sub(r&apos;([！。])([%@])&apos;, r&apos;\1分隔\2&apos;, &quot;hell。！@%o&quot;)
print(para)  # 输出: hell。分隔！@%o
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;4. 预编译正则表达式&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;re.compile(pattern)&lt;/code&gt;&lt;/strong&gt;
预编译正则表达式提升复用效率。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pattern = re.compile(r&apos;\d+&apos;)
matches = pattern.findall(&apos;2021年7月7日&apos;)
print(matches)  # 输出: [&apos;2021&apos;, &apos;7&apos;, &apos;7&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;5. 分割字符串&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;re.split(pattern, string)&lt;/code&gt;&lt;/strong&gt;
根据正则表达式分割字符串。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;parts = re.split(r&apos;[,\s]+&apos;, &apos;apple, banana  cherry&apos;)
print(parts)  # 输出: [&apos;apple&apos;, &apos;banana&apos;, &apos;cherry&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;6. 分组与捕获&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;()&lt;/code&gt; 分组&lt;/strong&gt;
提取子模式内容。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;text = &quot;2021年7月7日&quot;
match = re.search(r&apos;(\d+)年(\d+)月(\d+)日&apos;, text)
if match:
    print(match.groups())  # 输出: (&apos;2021&apos;, &apos;7&apos;, &apos;7&apos;)
    print(match.group(1))  # 输出: 2021
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;7. 常用正则符号&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;\d&lt;/code&gt;：匹配数字（等价于 &lt;code&gt;[0-9]&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\w&lt;/code&gt;：匹配字母、数字、下划线。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\s&lt;/code&gt;：匹配空白字符（空格、换行等）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.&lt;/code&gt;：匹配任意字符（除换行外）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;*&lt;/code&gt;：匹配前一个字符 0 次或多次。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;+&lt;/code&gt;：匹配前一个字符 1 次或多次。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;?&lt;/code&gt;：匹配前一个字符 0 次或 1 次。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{n}&lt;/code&gt;：匹配前一个字符恰好 n 次。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;8. 标志参数&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;re.IGNORECASE&lt;/code&gt;（&lt;code&gt;re.I&lt;/code&gt;）：忽略大小写。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;re.MULTILINE&lt;/code&gt;（&lt;code&gt;re.M&lt;/code&gt;）：多行模式。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;re.DOTALL&lt;/code&gt;（&lt;code&gt;re.S&lt;/code&gt;）：允许 &lt;code&gt;.&lt;/code&gt; 匹配换行符。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;text = &quot;Hello\nWorld&quot;
matches = re.findall(r&apos;^[a-z]+&apos;, text, flags=re.I | re.M)
print(matches)  # 输出: [&apos;Hello&apos;, &apos;World&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;&lt;strong&gt;示例整合&lt;/strong&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;import re

# 示例1：查找日期
text = &quot;日期：2023-12-25，2024-01-01&quot;
dates = re.findall(r&apos;\d{4}-\d{2}-\d{2}&apos;, text)
print(dates)  # 输出: [&apos;2023-12-25&apos;, &apos;2024-01-01&apos;]

# 示例2：替换电话号码
text = &quot;电话：123-4567-8900&quot;
new_text = re.sub(r&apos;(\d{3})-(\d{4})-(\d{4})&apos;, r&apos;(\1) \2-\3&apos;, text)
print(new_text)  # 输出: 电话：(123) 4567-8900
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;&lt;strong&gt;注意事项&lt;/strong&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;正则表达式需注意&lt;strong&gt;贪婪匹配&lt;/strong&gt;（如 &lt;code&gt;.*&lt;/code&gt; 默认匹配到最长）。&lt;/li&gt;
&lt;li&gt;复杂正则可添加注释使用 &lt;code&gt;re.VERBOSE&lt;/code&gt; 标志提高可读性。&lt;/li&gt;
&lt;li&gt;处理中文时建议使用 &lt;code&gt;re.UNICODE&lt;/code&gt; 标志。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;使用原始字符串&lt;/strong&gt;：正则表达式中常用原始字符串（如 r&apos;\n&apos;），避免反斜杠转义问题。例如，r&quot;\n&quot; 表示 \n，而 &quot;\n&quot; 表示换行符。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能考虑&lt;/strong&gt;：对于频繁使用的模式，优先使用 re.compile() 预编译。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;复杂任务的替代&lt;/strong&gt;：对于如 HTML 解析或深度邮箱验证，re 库可能不够 robust，建议使用专门库（如 Beautiful Soup 或 email-validator）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;通过灵活组合这些方法，可以高效处理字符串匹配、清洗和提取任务。&lt;/p&gt;
</content:encoded></item><item><title>陪安东尼度过漫长岁月——橙、黄</title><link>https://bangwu.top/posts/antony-orange/</link><guid isPermaLink="true">https://bangwu.top/posts/antony-orange/</guid><description>安东尼橙黄篇书摘，关于爱与成长的句子</description><pubDate>Fri, 14 Feb 2025 09:39:48 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;橙——陪安东尼度过漫长岁月Ⅱ&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ 总是莫名地觉得幸福 尽管我还不知道幸福是个什么东西&lt;/p&gt;
&lt;p&gt;◆ 用拒人千里之外的冷酷来掩饰自己试图与人亲近的热血沸腾 这算完美人格 还是闷骚？&lt;/p&gt;
&lt;p&gt;◆ 是贪婪的人 渴望被爱 被拥抱 被理解 被接受又是自私的人 拒绝去爱 去尝试 去解释 去接纳&lt;/p&gt;
&lt;p&gt;◆ 有的时候会隐隐约约地觉得 想要一直待在这城市 结婚生子 慢慢地老去 平静而安稳的一生&lt;/p&gt;
&lt;p&gt;◆ 我一直一直在想你 思念带来前所未有的甜蜜&lt;/p&gt;
&lt;p&gt;◆ 生活是一场又一场美好事物的 追逐&lt;/p&gt;
&lt;p&gt;◆ 我们都很 仔细地思考 定义过 所谓幸福的生活 不过 我们都没有认真地 活但是 我又会觉得 没有认真生活没有什么 也会觉得 偶尔难过失落也没有什么嗯 如果有你在的话&lt;/p&gt;
&lt;p&gt;◆ 之后的几年 或许 出现别的事 别的人 感觉就是 也可以啊&lt;/p&gt;
&lt;p&gt;◆ 好吧 我们就这样走下去 看看还有什么……&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;橙——陪安东尼度过漫长岁月Ⅱ&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ 我想 将来如果 有天 尝试写小说 会写一个男生 他总是和 开满花的树 合照 他不笑 也不悲伤 他只是笔挺地站在那里&lt;/p&gt;
&lt;p&gt;◆ 有一天 我会忘记你 我没有很期待 也没有觉得失落我只是 知道 有那么一天&lt;/p&gt;
&lt;p&gt;◆ 好了现在 我没有什么 秘密 可以告诉你了&lt;/p&gt;
&lt;p&gt;◆ 你不知道我如此爱你 和 我不知道我如此爱你 你觉得哪个 更言情&lt;/p&gt;
&lt;p&gt;◆ 如果你有1000万，你怎么用？省着用&lt;/p&gt;
&lt;p&gt;◆ 二月去日本 有些话如果现在不说 以后就再也不会说了&lt;/p&gt;
&lt;p&gt;◆ 还是应该像现在这样 默默地看着你还要说些什么&lt;/p&gt;
&lt;p&gt;◆ 我在想 这次与你一别 还要什么时候能见面&lt;/p&gt;
&lt;p&gt;◆ 嗯 我想说 其实 我很喜欢 每一次 去找你的时候 的 自己&lt;/p&gt;
&lt;p&gt;◆ 好像你离开家了以后 才知道自己多想家 多爱父母&lt;/p&gt;
&lt;p&gt;◆ tnd 没有 文学小青年 那么高产 却沾染了 一身文学小青年的坏毛病&lt;/p&gt;
&lt;p&gt;◆ if you go away far enough,then you are on the way home.&lt;/p&gt;
&lt;p&gt;◆ 有的 时候 我真的很 鄙视 工作效率极低的自己&lt;/p&gt;
&lt;p&gt;◆ 你拥有你的, 我拥有我的, 盛开。你适合你的, 我适合我的, 垂败。&lt;/p&gt;
&lt;p&gt;◆ 有一些爱 像 pola相机 或者已经停产的700相纸 用一点儿少一点儿 我似乎能听到它下降的声音&lt;/p&gt;
&lt;p&gt;◆ 你说 以后要一直在一起哦 以后 不能因为一些小事就不理我哦 有那么几秒 我百分之百地认真 相信了&lt;/p&gt;
&lt;p&gt;◆ 亲爱的不二 会不会 你也不是很了解我？&lt;/p&gt;
&lt;p&gt;◆ 当初 我让你 吃了我的时候 你就应该吃了我&lt;/p&gt;
&lt;p&gt;◆ 你眼里 我的缺点太多了 多到看不到 我这么喜欢你&lt;/p&gt;
&lt;p&gt;◆ 我不喜欢一个人的 时候 就对他 很客气&lt;/p&gt;
&lt;p&gt;◆ 你毫不掩饰地 自私着 再 这样下去 我如何讨你欢心&lt;/p&gt;
&lt;p&gt;◆ 不知道 如何爱你 看着你 是我唯一 的方式&lt;/p&gt;
&lt;p&gt;◆ 如何向喜欢的人表白？……盯着&lt;/p&gt;
&lt;p&gt;◆ 如果是100%的人的话，不管多么晚才发现自己的心意，兜兜转转绕了一个圈回来，你也刚好站在那里。&lt;/p&gt;
&lt;p&gt;◆ 现在 执著追求的事 将来必定有一天变成不 重要的&lt;/p&gt;
&lt;p&gt;◆ 亲爱的不二啊 你说怎么 想要一生一世的人 最后 都变成了 一期一会呢&lt;/p&gt;
&lt;p&gt;◆ 那一次之后 你就从 不可或缺的人 变成 可有可无的人了&lt;/p&gt;
&lt;p&gt;◆ 那么多人 都等着和你做朋友 你就 move on好了 不过这也好 如果你问我 我一定会选错&lt;/p&gt;
&lt;p&gt;◆ 嗯 丢东西 很可怕 可是 丢东西 不知道怎么丢的 更让人郁闷&lt;/p&gt;
&lt;p&gt;◆ 后来 后来 就像 很多人说的 时间 让我们淡忘了 那个人&lt;/p&gt;
&lt;p&gt;◆ 总之 会一直写下去 一定会 一直写下去&lt;/p&gt;
&lt;p&gt;◆ 我是 很不喜欢圣诞—— 世界上 最不喜欢圣诞的 除了 火鸡 一定 还有我&lt;/p&gt;
&lt;p&gt;◆ 亲爱的 总有一些时候 是 寂寞的 寂寞的&lt;/p&gt;
&lt;p&gt;◆ 亲爱的 最近 总有些时候 我在想 我所害怕的是 即使我使尽全力 用最快的速度一直跑 一直跑 也到不了你那里&lt;/p&gt;
&lt;p&gt;◆ 恋爱中的 兔崽子 很不像白羊 变得 患得患失&lt;/p&gt;
&lt;p&gt;◆ 可是 我担心 我不喜欢 不再喜欢你的自己&lt;/p&gt;
&lt;p&gt;◆ 很难因为别人 喜欢 而喜欢 也不会 因为别人 恨 而恨&lt;/p&gt;
&lt;p&gt;◆ 只是每次遇到喜欢的人 才会 患得患失 把自己 贬低进尘埃 伸手 向别人借 人品&lt;/p&gt;
&lt;p&gt;◆ 恍惚间 隐约想着 应该 趁着年轻 和喜欢的人一起 制造些 比夏天还要温暖的事&lt;/p&gt;
&lt;p&gt;◆ 恋爱中 应有的嘴脸 再厉害的人也逃不过&lt;/p&gt;
&lt;p&gt;◆ 要找一个以后会一起的人 虽然我不是缺点多多 但是也不完美 真的找到对的人谈何容易 但是 总是觉得一定会找到那个人 一定&lt;/p&gt;
&lt;p&gt;◆ 那些真的在你生命里 发光 让你反复回味的 它们都是 好的&lt;/p&gt;
&lt;p&gt;◆ 我很好 我很好 只是说不出加油啊 因为觉得自己没有立场 也做不出那样的动作了 尽管我一直想回到过去 将你紧紧抱住 紧紧抱住&lt;/p&gt;
&lt;p&gt;◆ 常常觉得山穷水尽了 也会知道不久便会变得风生水起 可是 不停揣摩 纠结萦绕的爱 还是不知道何去何从&lt;/p&gt;
&lt;p&gt;◆ 我觉得 我们俩之间就像喝酒 我干杯 你随意&lt;/p&gt;
&lt;p&gt;◆ 而且你看 即使 身体不记得 那心也会 记得吧 心 也是一块肌肉吧 我想&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;黄——陪安东尼度过漫长岁月Ⅲ&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ 亲爱的不二 抱着你睡觉的时候也不觉得彻底地拥有你&lt;/p&gt;
&lt;p&gt;◆ 所以啊 我就在想 人生 之所以珍贵 说不定 不是我们去过的 一个又一个 精彩华丽的 旅游胜地 而是 旅途之中 寂寞 无聊 不可打发的时间里 有你陪伴吧&lt;/p&gt;
&lt;p&gt;◆ 其实吧 他骂我 我也没啥 后来他可能也想通了 就过来和我说&lt;/p&gt;
&lt;p&gt;◆ 当你看到这篇日志的时候 不用 怀疑 我说的就是你&lt;/p&gt;
&lt;p&gt;◆ 有些东西 你知道 就算扔掉了 它的痕迹扔不掉 所以就一直带着&lt;/p&gt;
&lt;p&gt;◆ 亲爱的 不二 我们又要开始新的生活咯意气风发 意气风发～&lt;/p&gt;
&lt;p&gt;◆ 如果我的手臂有 这么长&amp;gt;---------------------------------O--------------------------------------&amp;lt;一定紧紧抱住你们 地球很难逃离 但是可以尝试一起 Jump&lt;/p&gt;
&lt;p&gt;◆ 那些 陪我一起傻过的人啊 你们在我身上 留下了什么我该死心塌地地找回你 还是要微笑后坚定转身 从此变得聪明&lt;/p&gt;
&lt;p&gt;◆ 之前那个我 和现在的我之间的距离 被许多细小琐碎的事填充着这些都是 你给我的 爱吧&lt;/p&gt;
&lt;p&gt;◆ 希望我们 心脏很强 胆子很大 好好恋爱 荒唐放纵 最后找到真爱&lt;/p&gt;
&lt;p&gt;◆ 2006年3月11日 来澳洲 来的时候怕超重 箱子里放的被子床单 电饭煲 字典 父母朋友的祝福……2010年3月22日 回中国 走的时候怕箱子超重 箱子里放的照片 相机 书 菜单 刀 和 梦一般的记忆&lt;/p&gt;
&lt;p&gt;◆ 你好 我就要飞了 我一点儿也不酷 也不特别 所以我不会说很酷的话 只想说请不要忘记我&lt;/p&gt;
&lt;p&gt;◆ 2010／4／23　［看不出来 我的无奈］&lt;/p&gt;
&lt;p&gt;◆ 过去已经不在了 将来也不想考虑 只想 此时此刻 为了你任性的勇敢&lt;/p&gt;
&lt;p&gt;◆ 看我的书 你的书 遇到感动的地方就记录下来 散播出去&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;黄——陪安东尼度过漫长岁月Ⅲ&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ 你会追问 你怎么了 我就习惯性笑着说没什么 其实真的没什么 我习惯把事情装在心里 有的时候装着装着就没了&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;【序】&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ 困惑到底怎样才能把工作做得更好，到底怎样才能实现梦想，到底怎样才能获得周围人的喜欢，到底怎样才能成功，到底怎样才能活得更出色……&lt;/p&gt;
&lt;p&gt;◆ 有时候要忍不住咬牙切齿地想，世界是打定主意要对自己不好了，要捏许多优秀得多的精英出现，来衬托我们平凡人活得有多么事倍功半&lt;/p&gt;
&lt;p&gt;◆ 当我们在一天一天变平凡，一天一天在与困惑的角逐中失利，一天一天老去，世界开始灰败下来，上面覆盖着厚厚的挣脱不成后的苦果，而就算到了这个时候，我想安东尼仍然是带着彩虹颜色的。&lt;/p&gt;
&lt;p&gt;◆ 不要愁老之将至，你老了一定很可爱。而且，假如你老了十岁，我当然也同样老了十岁，世界也老了十岁，上帝也老了十岁，一切都是一样。&lt;/p&gt;
&lt;p&gt;◆ 希望以后有一天 有人说 你变了的时候 我坦然地知道 我没变 或者 变成更好的人了&lt;/p&gt;
&lt;p&gt;◆ 亲爱的不二 人的一生有很多个感觉好极了 也会经历很多感觉糟透了不过其实 这些都没什么 不用看的很重很多年 以后当你回忆过去的时候 我敢保证 那些好极了和糟透了的时刻你都会记不清了&lt;/p&gt;
&lt;p&gt;◆ 唯一真实 和让你骄傲的 是你挺胸抬头走过的人生&lt;/p&gt;
&lt;p&gt;◆ 她唱 “天上人间 如果真值得歌颂 也是因为有你才变得闹哄哄” 这让我想起来我和 我的那群朋友们 一路走来 有你真好&lt;/p&gt;
&lt;p&gt;◆ 不过又一想 写陪也不是为了这些的 写陪只是因为想写而已 不是为了陪别人 不是为了找人陪 就是单单纯纯的想写而已 想到这里就觉得 以后说不定还会写下去&lt;/p&gt;
&lt;p&gt;◆ 不过最让我感觉难过的 不是一年后你还不确定你对我的感情 而是 我不再确定你就是那个对的人了&lt;/p&gt;
&lt;p&gt;◆ 在遇到能像喜欢你这么喜欢的人之前 还是好好喜欢自己吧 因为和任何人一起 只要没有像喜欢你这么喜欢 我都没法再见到你 所以这段时间就好好喜欢自己吧&lt;/p&gt;
&lt;p&gt;◆ 人们经常说 it will get better 我觉得也不一定 maybe it will get worse 不过我相信 it gets easier&lt;/p&gt;
&lt;p&gt;◆ 一个人 是在相信的时候比较勇敢还是不信的时候呢？&lt;/p&gt;
&lt;p&gt;◆ 有时候我 明明很想你也不说 当你发 how are you 的时候我也不回 在心里纠结了一天 睡觉前 才回复一条 I am OK&lt;/p&gt;
&lt;p&gt;◆ 不过感情这 东西要看得多透呢 也许看不透比较有意思吧&lt;/p&gt;
&lt;p&gt;◆ 我要爱 眼神 表情 每一个仔细推敲的短信 改了又改的邮件 适时的殷勤 为了你一次次的迁徙 当然也会遇到之上各种各样的人 遭遇各种各样的对待 不过那样要来的爱总是不长久 捧在手里却暖不了心&lt;/p&gt;
&lt;p&gt;◆ 可能我就是喜欢 喜欢一个人很久 或者 喜欢被一个人喜欢很久 但没有他喜欢我那么喜欢他 但还是喜欢&lt;/p&gt;
&lt;p&gt;◆ 受伤的心 假以时日也不会复原的 但是你会意识到 受伤的那块只是一小部分 很小很小的一部分&lt;/p&gt;
&lt;p&gt;◆ 说不好怎样形容上海 我觉得他很有包容性 任何国家 任何背景的人都可以按照自己的方式在这里活着 互不干扰&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;【后记】&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ 二十岁开始时候的自己 充满元气横冲直闯快要三十岁的时候的自己 经常抽离肉身 站得远远的审视自己 想这是我么 这是我想要的人生么 爱情是这个样子么 生活只是这个样子么&lt;/p&gt;
</content:encoded></item><item><title>Nextjs初探</title><link>https://bangwu.top/posts/nextjs-first/</link><guid isPermaLink="true">https://bangwu.top/posts/nextjs-first/</guid><description>Nextjs了解以及基本使用</description><pubDate>Fri, 14 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Nextjs&lt;/h2&gt;
&lt;p&gt;Nextjs现在成为react官方推荐的第一框架，虽然偏向的是全栈框架，但是其性价比非常高，一个框架可以直接做出一个完整的项目，是个人开发者的不二之选&lt;/p&gt;
&lt;h2&gt;Learn&lt;/h2&gt;
&lt;p&gt;Next.js 的 &lt;strong&gt;App Router&lt;/strong&gt;（从 v13 开始引入）是一种基于文件系统的新型路由方案，结合 TypeScript (&lt;code&gt;.tsx&lt;/code&gt;) 使用能提供更好的类型安全，同时我比较推荐从&lt;a href=&quot;https://nextjs.org/learn&quot;&gt;Nextjs Learn&lt;/a&gt;开始入门&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;1. 项目初始化&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;使用 TypeScript 模板创建项目：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx create-next-app@latest --ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;选择启用 App Router（默认选项）。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;2. App Router 核心结构&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;项目目录结构：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app/
├── layout.tsx       # 全局布局
├── page.tsx         # 首页（对应路由 `/`）
├── dashboard/
│   ├── layout.tsx   # 嵌套布局
│   └── page.tsx     # `/dashboard`
└── blog/
    ├── [slug]/
    │   └── page.tsx # 动态路由 `/blog/:slug`
    └── page.tsx     # `/blog`
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;3. 基础页面与布局&lt;/strong&gt;&lt;/h3&gt;
&lt;h4&gt;&lt;strong&gt;全局布局 (&lt;code&gt;app/layout.tsx&lt;/code&gt;)&lt;/strong&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;import type { ReactNode } from &apos;react&apos;;

export default function RootLayout({
  children,
}: {
  children: ReactNode;
}) {
  return (
    &amp;lt;html lang=&quot;en&quot;&amp;gt;
      &amp;lt;body&amp;gt;{children}&amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;strong&gt;首页 (&lt;code&gt;app/page.tsx&lt;/code&gt;)&lt;/strong&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;export default function Home() {
  return &amp;lt;h1&amp;gt;Hello, Next.js!&amp;lt;/h1&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;4. 动态路由与 TypeScript&lt;/strong&gt;&lt;/h3&gt;
&lt;h4&gt;&lt;strong&gt;动态路由示例 (&lt;code&gt;app/blog/[slug]/page.tsx&lt;/code&gt;)&lt;/strong&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;interface PageProps {
  params: { slug: string };
  searchParams?: { [key: string]: string | string[] | undefined };
}

export default function BlogPage({ params }: PageProps) {
  return &amp;lt;div&amp;gt;Blog Slug: {params.slug}&amp;lt;/div&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;strong&gt;生成动态路由参数 (&lt;code&gt;generateStaticParams&lt;/code&gt;)&lt;/strong&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;export async function generateStaticParams() {
  const posts = await fetch(&apos;https://api.example.com/posts&apos;).then((res) =&amp;gt; res.json());

  return posts.map((post: { id: string }) =&amp;gt; ({
    slug: post.id,
  }));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;5. 数据获取与类型安全&lt;/strong&gt;&lt;/h3&gt;
&lt;h4&gt;&lt;strong&gt;服务端数据获取 (&lt;code&gt;fetch&lt;/code&gt; + TypeScript)&lt;/strong&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;interface Post {
  id: string;
  title: string;
  content: string;
}

async function getPost(slug: string): Promise&amp;lt;Post&amp;gt; {
  const res = await fetch(`https://api.example.com/posts/${slug}`);
  if (!res.ok) throw new Error(&apos;Failed to fetch&apos;);
  return res.json();
}

export default async function BlogPage({ params }: PageProps) {
  const post = await getPost(params.slug);
  return (
    &amp;lt;article&amp;gt;
      &amp;lt;h1&amp;gt;{post.title}&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;{post.content}&amp;lt;/p&amp;gt;
    &amp;lt;/article&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;6. 嵌套布局与类型&lt;/strong&gt;&lt;/h3&gt;
&lt;h4&gt;&lt;strong&gt;嵌套布局 (&lt;code&gt;app/dashboard/layout.tsx&lt;/code&gt;)&lt;/strong&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;import type { ReactNode } from &apos;react&apos;;

export default function DashboardLayout({
  children,
}: {
  children: ReactNode;
}) {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;nav&amp;gt;Dashboard Navbar&amp;lt;/nav&amp;gt;
      {children}
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;7. 客户端组件与交互&lt;/strong&gt;&lt;/h3&gt;
&lt;h4&gt;&lt;strong&gt;客户端组件 (&lt;code&gt;app/components/Counter.tsx&lt;/code&gt;)&lt;/strong&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&apos;use client&apos;; // 必须标记为客户端组件

import { useState } from &apos;react&apos;;

export default function Counter() {
  const [count, setCount] = useState(0);
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;+&amp;lt;/button&amp;gt;
      &amp;lt;span&amp;gt;{count}&amp;lt;/span&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;8. API 路由 (可选)&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;创建 API 路由 (&lt;code&gt;app/api/hello/route.ts&lt;/code&gt;)：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { NextResponse } from &apos;next/server&apos;;

export async function GET() {
  return NextResponse.json({ name: &apos;John Doe&apos; });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;访问 &lt;code&gt;/api/hello&lt;/code&gt; 即可调用。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;9. 类型增强配置&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;tsconfig.json&lt;/code&gt; 中添加 Next.js 类型：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;compilerOptions&quot;: {
    &quot;types&quot;: [&quot;next&quot;, &quot;next/types&quot;]
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;10. 实用技巧&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;路由跳转&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import Link from &apos;next/link&apos;;

&amp;lt;Link href=&quot;/blog/123&quot;&amp;gt;Read Blog&amp;lt;/Link&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;流式传输&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Suspense } from &apos;react&apos;;

&amp;lt;Suspense fallback={&amp;lt;div&amp;gt;Loading...&amp;lt;/div&amp;gt;}&amp;gt;
  &amp;lt;AsyncComponent /&amp;gt;
&amp;lt;/Suspense&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;环境变量&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const apiUrl = process.env.NEXT_PUBLIC_API_URL;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;常见问题&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;类型错误&lt;/strong&gt;：确保为 &lt;code&gt;params&lt;/code&gt; 和 &lt;code&gt;searchParams&lt;/code&gt; 定义正确的接口。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;客户端组件限制&lt;/strong&gt;：在客户端组件中不能直接使用服务端数据获取方法（如 &lt;code&gt;getStaticProps&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动态导入&lt;/strong&gt;：使用 &lt;code&gt;next/dynamic&lt;/code&gt; 导入客户端组件。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;官方资源&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Next.js App Router 文档：&lt;a href=&quot;https://nextjs.org/docs/app&quot;&gt;App Router Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;TypeScript 配置指南：&lt;a href=&quot;https://nextjs.org/docs/pages/building-your-application/configuring/typescript&quot;&gt;TypeScript Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;next-auth&lt;/h2&gt;
&lt;p&gt;在 Next.js 中使用 &lt;code&gt;next-auth&lt;/code&gt; 实现鉴权的完整指南（支持 App Router 和 TypeScript）：&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;步骤 1：安装依赖&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;pnpm install next-auth @next-auth/prisma-adapter @prisma/client bcrypt
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;步骤 2：配置环境变量&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# .env
NEXTAUTH_SECRET=&quot;your-secure-secret&quot; # openssl rand -base64 32 生成
NEXTAUTH_URL=&quot;http://localhost:3000&quot;

# GitHub OAuth 示例
GITHUB_CLIENT_ID=&quot;your-client-id&quot;
GITHUB_CLIENT_SECRET=&quot;your-client-secret&quot;

# 数据库配置（以 PostgreSQL 为例）
DATABASE_URL=&quot;postgresql://user:password@localhost:5432/mydb&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;步骤 3：创建 Prisma Schema（数据库集成）&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// prisma/schema.prisma
generator client {
  provider = &quot;prisma-client-js&quot;
}

datasource db {
  provider = &quot;postgresql&quot;
  url      = env(&quot;DATABASE_URL&quot;)
}

model User {
  id            String    @id @default(cuid())
  name          String?
  email         String?   @unique
  emailVerified DateTime?
  image         String?
  accounts      Account[]
  sessions      Session[]
}

model Account {
  id                 String  @id @default(cuid())
  userId             String
  type               String
  provider           String
  providerAccountId  String
  refresh_token      String?  @db.Text
  access_token       String?  @db.Text
  expires_at         Int?
  token_type         String?
  scope              String?
  id_token           String?  @db.Text
  session_state      String?
  user               User    @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
}

model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行 &lt;code&gt;npx prisma generate&lt;/code&gt; 生成客户端。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;步骤 4：配置 NextAuth (App Router)&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// app/api/auth/[...nextauth]/route.ts
import NextAuth from &quot;next-auth&quot;;
import GitHubProvider from &quot;next-auth/providers/github&quot;;
import { PrismaAdapter } from &quot;@next-auth/prisma-adapter&quot;;
import { PrismaClient } from &quot;@prisma/client&quot;;

const prisma = new PrismaClient();

export const {
  handlers: { GET, POST },
  auth, // 用于中间件获取会话
  signIn,
  signOut,
} = NextAuth({
  adapter: PrismaAdapter(prisma),
  providers: [
    GitHubProvider({
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    }),
  ],
  secret: process.env.NEXTAUTH_SECRET,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;步骤 5：全局 SessionProvider&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// app/layout.tsx
import type { ReactNode } from &quot;react&quot;;
import { SessionProvider } from &quot;next-auth/react&quot;;
import { auth } from &quot;@/auth&quot;;

export default async function RootLayout({
  children,
}: {
  children: ReactNode;
}) {
  const session = await auth();

  return (
    &amp;lt;html lang=&quot;en&quot;&amp;gt;
      &amp;lt;body&amp;gt;
        &amp;lt;SessionProvider session={session}&amp;gt;{children}&amp;lt;/SessionProvider&amp;gt;
      &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;步骤 6：创建登录/登出组件&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// components/AuthButton.tsx
&quot;use client&quot;;

import { signIn, signOut } from &quot;@/auth&quot;;
import { useSession } from &quot;next-auth/react&quot;;

export function AuthButton() {
  const { data: session } = useSession();

  return (
    &amp;lt;div&amp;gt;
      {session ? (
        &amp;lt;button onClick={() =&amp;gt; signOut()}&amp;gt;Sign Out&amp;lt;/button&amp;gt;
      ) : (
        &amp;lt;button onClick={() =&amp;gt; signIn(&quot;github&quot;)}&amp;gt;Sign In with GitHub&amp;lt;/button&amp;gt;
      )}
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;步骤 7：保护路由（中间件）&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// middleware.ts
import { auth } from &quot;@/auth&quot;;

export default auth((req) =&amp;gt; {
  if (!req.auth) {
    return Response.redirect(new URL(&quot;/login&quot;, req.nextUrl));
  }
});

// 配置需要保护的路径
export const config = {
  matcher: [&quot;/dashboard/:path*&quot;, &quot;/profile&quot;],
};
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;步骤 8：服务端获取会话&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// app/dashboard/page.tsx
import { auth } from &quot;@/auth&quot;;

export default async function DashboardPage() {
  const session = await auth();

  if (!session) {
    return &amp;lt;div&amp;gt;Unauthorized&amp;lt;/div&amp;gt;;
  }

  return &amp;lt;div&amp;gt;Welcome {session.user?.name}&amp;lt;/div&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;步骤 9：自定义登录页面&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// app/login/page.tsx
&quot;use client&quot;;

import { signIn } from &quot;@/auth&quot;;

export default function LoginPage() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; signIn(&quot;github&quot;)}&amp;gt;GitHub 登录&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;关键配置说明&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;适配器选择&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 &lt;code&gt;PrismaAdapter&lt;/code&gt; 将用户数据存入数据库&lt;/li&gt;
&lt;li&gt;支持其他适配器：Firebase、MongoDB 等&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;OAuth 回调 URL&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在 GitHub 开发者设置中添加回调 URL：
&lt;code&gt;http://localhost:3000/api/auth/callback/github&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Session 策略&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;session: {
  strategy: &quot;jwt&quot;, // 或 &quot;database&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;常见问题解决&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;类型错误&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// next-auth.d.ts
import &quot;next-auth&quot;;

declare module &quot;next-auth&quot; {
  interface Session {
    user: {
      id: string;
    } &amp;amp; DefaultSession[&quot;user&quot;];
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;中间件不生效&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;确保 &lt;code&gt;matcher&lt;/code&gt; 路径正确&lt;/li&gt;
&lt;li&gt;检查中间件文件是否在项目根目录&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;生产环境配置&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设置 &lt;code&gt;NEXTAUTH_URL&lt;/code&gt; 为生产域名&lt;/li&gt;
&lt;li&gt;使用 HTTPS&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;扩展功能&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;邮箱登录&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import EmailProvider from &quot;next-auth/providers/email&quot;;

providers: [
  EmailProvider({
    server: process.env.EMAIL_SERVER,
    from: process.env.EMAIL_FROM,
  }),
]
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;自定义认证页面&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;theme: {
  logo: &quot;/logo.png&quot;,
  brandColor: &quot;#000&quot;,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;角色鉴权&lt;/strong&gt;：
在 &lt;code&gt;User&lt;/code&gt; 模型中添加 &lt;code&gt;role&lt;/code&gt; 字段，通过中间件校验。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;文档参考：&lt;a href=&quot;https://next-auth.js.org&quot;&gt;NextAuth.js Documentation&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;解决国内网络登录显示不成功的问题&lt;/h3&gt;
&lt;p&gt;&amp;lt;script src=&quot;https://gist.github.com/markbang/f411fcf140e130d9a4aff79e0a891706.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/p&gt;
</content:encoded></item><item><title>我时常觉得你们是大人</title><link>https://bangwu.top/posts/skyfriends/</link><guid isPermaLink="true">https://bangwu.top/posts/skyfriends/</guid><description>写给光遇朋友们的信：陪伴与青春</description><pubDate>Wed, 29 Jan 2025 12:08:05 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;audio controls data-title=&quot;若青春可以重来&quot; data-artist=&quot;张叶蕾&quot; data-cover=&quot;https://cdn.bangwu.top/img/20260111w809d-g7.webp&quot;&amp;gt;
&amp;lt;source src=&quot;https://cdn.bangwu.top/img/202601116cbb180f.mp3&quot; type=&quot;audio/mpeg&quot;&amp;gt;
&amp;lt;/audio&amp;gt;&lt;/p&gt;
&lt;p&gt;此篇献给光遇中的朋友们&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/202601112f6ac86a.webp&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;起初是一个突然想玩光遇，这个我听过但没玩过的游戏，有点随性的我就立即下载了。在暑假，我想“这游戏一定有好多好玩的小孩子吧”🤔第一天中午，在云巢和一个小黑互扔玩偶扔了好长时间。后面就不出所料，和所有萌新一样，开图的时候我选择了点击问号请求帮助，好像这个游戏里的人都喜欢帮助萌新，不用我自己跑了好像还不错&lt;/p&gt;
&lt;p&gt;于是，就是你。你喜欢抓萌新玩，最初我加的是你的小号，只是第一次被抓到时聊了一会天，记得当时你指着小王子星球那边说这是朕为你打下的江山，后来遇到的你会弹好多歌，总会孜孜不倦的过试炼、跑图。记得有一次不小心叫了你人机🤕，然后这就成了我的称号。情劫，后来后来有许多事情不在我脑海里了，但我还记得你问我选什么科目的时候，你问我名字的时候，在我的小屋你和你崽告诉我高中的事情的时候，还有玩真心话大冒险的时候，周末放假回来玩的时候......你是很仔细的人啊&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/20260111f9fc595f.webp&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;你是她的崽，一开始我不知道你的名字，只有备注情劫的崽，后来问你的名字，你说“不知道你不会问吗”“司雩”遇到你时经常和你监护待在一起，你总是仗着她欺负我🤕后来开学了，我希望你在学校要好好的，fighting！偶尔重合的假期时间，你和你监护一定在见不到的日子里想念着彼此 觉得每次你们在一块的时候，都会很快乐，运动会去食堂吃饭的时间也可以上线一起玩会，真不错😌 你是很坚强的人啊&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/2026011118469cf8.webp&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;你说再过一个多月你就 17 岁了，不知道日期但希望你岁岁平安。遇见你时是国庆节，我承认一开始是玩抽象加的你，那天你说“好开心”于是就每人送一颗心，后来就熬了许多夜，口头禅是“累了累了”你说抽象是你的保护色，🤔我觉得没错。阿淮，后来觉得你对遇到的每个人都很好，希望你是幸运的。后来发生了好多好多事情，音乐桌上听的歌，遇境的留影，小号开的挂......你是很伶俐的人啊&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/20260111b64d6521.webp&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们都会讨论高中的困苦，朋友的不理解，有些一成不变的事比如几年后的高中毕业，会过完的2025，高中的学习等等，但我希望这些一成不变的事情里有你们还是监护和崽崽，我们还是朋友，考试慢慢取得好的成绩，良好的同学关系还有自己内心的丰腴&lt;/p&gt;
&lt;p&gt;“坟墓里寂静无比，埋葬你的是所有你未说出口的话”我无法预知后来会变成什么样子，不过刻舟求剑，变好、变坏、还是再见，我都接受，但青春不可以重来
他们说青春是场盛大的逃亡，逃出苦难向春山。我想了想，想告诉你们向左向右还是随意散步都可以的，青春弥足珍贵，不知道我们未来会遇到某某但现在已经遇到了彼此......新年不是一瞬间，是度过了去年的种种，希望我们都能找到自己的选择，开心快乐！&lt;/p&gt;
&lt;p&gt;“为什么青春会偷偷溜走”
“为什么回忆变得不够用”&lt;/p&gt;
</content:encoded></item><item><title>早交卷？什么时候交卷</title><link>https://bangwu.top/posts/when/</link><guid isPermaLink="true">https://bangwu.top/posts/when/</guid><description>关于爱情与人生考试：不急着交卷</description><pubDate>Sun, 12 Jan 2025 12:15:23 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;audio controls data-title=&quot;晚风&quot; data-artist=&quot;7opy / BT07&quot; data-cover=&quot;https://cdn.bangwu.top/img/20260111w0y3k-z4.webp&quot;&amp;gt;
&amp;lt;source src=&quot;https://cdn.bangwu.top/img/202601116d2b4214.mp3&quot; type=&quot;audio/mpeg&quot;&amp;gt;
&amp;lt;/audio&amp;gt;&lt;/p&gt;
&lt;p&gt;我有个舍友，大二上学期一天夜里，我们在公园散步，他对我说他有个女朋友。震惊之余，思绪难免落到自己身上，每天大大咧咧的我偶尔抽象，嗯，在大一的时候也是一个经常患得患失的人呢&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/20260111e99e76d0.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;后来，我和他说一些心里的秘密。
我时常觉得，男女之间没有纯友谊，后来我看到了他，好像有女朋友的人也没被管的太宽泛，也可以和其他女生一起打游戏，那么，在玩的时候你在想些什么呢，这是爱么
我似乎想要遇到的每个人都喜欢我，让我干什么只要不太过分，都行。emm这样会不会觉得太卑微了，后来我不这样想了，做自己想做的吧，想要的东西，想做的事情，想追的某某，就去做吧，不管别人怎么样了也没那么多精力来管别人了，先管好自己吧！&lt;/p&gt;
&lt;p&gt;亲爱的X，我现在不敢掏出真心对人了，很少用“最”来形容自己遇到的人或事了，没有什么必须要做的，也没有什么放不下的。但我遇到你时，也会是这样吗，我向你说非你不可的时候，你也会说非我不可吗，我对你付出的时候你也会想着对我付出吗，你也会很害羞的问我的想法吗？&lt;/p&gt;
&lt;p&gt;对于爱情这门考试，我已经不急着交卷了，后来，也慢慢变成，不想交卷了。身边人的爱情，喜剧也好，悲剧也罢，轰轰烈烈我不会内心燥热，平平静静我也不会觉得无比幸福，就这样吧。目前，对学业上的考试我也有这种想法了，无论考的好坏了，都不着急，爱谁谁交卷吧&lt;/p&gt;
&lt;p&gt;一些“厉害的人”都有自己的“道”，无论是前端大牛的一些性能为王，用户多的项目才是好项目之类的，又或是有钱人的唯利益之道，任何事物都用价值来衡量 ，这些都没错。人是要有自己的道的，无论说出来与否，都要给自己定下一个道，例如一切影响我喝牛奶的人或事都要抛弃，又或是坚持自己的本心，永远不被世俗侵袭。有的很荒唐，对吧，但有的也太过理想，不过亲爱的，没关系的，有道就行，有道，我们就站在生活的摇摆船上了，不是吗？我们已经在生活了呀，坚持自己的道！&lt;/p&gt;
&lt;p&gt;“将近的晚风快吹干整条小巷，燃尽的灯光无法再将我们点亮”&lt;/p&gt;
</content:encoded></item><item><title>燃尽</title><link>https://bangwu.top/posts/todie/</link><guid isPermaLink="true">https://bangwu.top/posts/todie/</guid><description>2024最后一天的小记：期末、书信与烤肉</description><pubDate>Tue, 31 Dec 2024 15:01:30 GMT</pubDate><content:encoded>&lt;p&gt;今天是2024年12月31日，为什么要特别强调一下日期呢，对，不光是因为是今年的最后一天了，以后也不会再有2024年了，这不是一篇年终总结，当然，只是分享一下我今天的小事。&lt;/p&gt;
&lt;p&gt;周二，当然要上课，不同的是今天要考试，期末考试。嗯，越来越对考试没有感觉了，没想到是以这种方式拜托了考试综合征，我甚至都不想看一下那些知识点即使知道要考这些，不知道为什么，越来越提不起兴趣了，像是“蝉要在地下埋18年然后出来叫两个月，死去”燃尽自己最后的热情。考前在和舍友一起对答一下知识点，我们四个都没怎么复习，连背都不想背了，然后就随随便便过了好几遍。&lt;/p&gt;
&lt;p&gt;在考试的过程中，emmm还算可以，总之能写的都写上去了，临时背的也都还算用上了。有个知识点是UML关系中的聚合和组合关系，这两个在考试的时候突然弄混淆了，分不清那个是属于关系了，然后选择题就随便选了一个，选的聚合。不过没想到选错了，可能那三分本就不属于我吧。&lt;/p&gt;
&lt;p&gt;年终的第一封信是Follow给的，也准备寄出去一封信，送给我远方的朋友。一直都觉得一封信是有必要的，来讲述我这一年的点点滴滴，也正式的长篇大论一下，不是在聊天框里面输入，而是一封信，它可以是一个沾满了泪渍的白纸，也可以是一个精美的页面，或是礼物下压着的那封“你开心吗”。对的，要对你说的话想来想去是你我的记忆。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/20260111d25704f5.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;晚上想去外面吃，就和室友约好。嗯，最后就我和另一个室友去的，吃烤肉，好吃😋
&lt;img src=&quot;https://cdn.bangwu.top/img/2026011158500ab1.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;69两个的玩偶
&lt;img src=&quot;https://cdn.bangwu.top/img/20260111b35994e3.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;嗯，没什么了，一切安好，燃尽......&lt;/p&gt;
&lt;p&gt;新年快乐，大家&lt;/p&gt;
</content:encoded></item><item><title>陪安东尼度过漫长岁月——红</title><link>https://bangwu.top/posts/antony-red/</link><guid isPermaLink="true">https://bangwu.top/posts/antony-red/</guid><description>安东尼红篇书摘，关于孤独与喜欢的句子</description><pubDate>Sun, 22 Dec 2024 06:22:29 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;红——陪安东尼度过漫长岁月Ⅰ&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ Every now and then sunshine is warm making life extremely long.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;序&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ 他偶尔会发一些短信给大家，虽然是中文，虽然我们都认识那些字，但是那些字组合起来的意思，并没有人可以了解。比如“我要带你去飞行”……你说这算什么？一起从楼顶跳下去么……&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;红——陪安东尼度过漫长岁月 I&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ 有的时候会隐隐约约地觉得 想要一直待在这城市 结婚生子 慢慢地老去 平静而安稳的一生 我喜欢的女歌手 又一次结婚 好像怀孕了 还要退出歌坛 她说 如果有一天我不再唱歌了 希望你们忘记我 怎么忘记？ 夜半做梦 梦到喜欢的人 早上醒来 忽然想到那个女歌手唱的歌 她唱 思念是一种很玄的东西 如影随形 我喜欢的一句话 ——有时候阳光很好 有时候阳光很暗 这就是生活
睡了
晚安&lt;/p&gt;
&lt;p&gt;◆ 只是那些喜欢过的人还会再遇见么 那些感动过的事还会再怀念么&lt;/p&gt;
&lt;p&gt;◆ 我不怕浪费钱 我没有那么多钱浪费 我只怕我的那么多的感情你都不在乎&lt;/p&gt;
&lt;p&gt;◆ 那天喝剩的啤酒瓶 留在海边 我们都没收&lt;/p&gt;
&lt;p&gt;◆ 一开始你就特别 从眼神就很体贴 我们都 不穿鞋 光着脚穿越耳语流言 在这之前 我 到底是谁 你 出现我 眼前 一瞬间 一切都改变&lt;/p&gt;
&lt;p&gt;◆ 比起深邃的东西我更喜欢肤浅的 不过偶尔深邃一下也许并不坏&lt;/p&gt;
&lt;p&gt;◆ 我在想 就算我知道了这些 一定还有一些你默默地为我做了我却不知道的吧&lt;/p&gt;
&lt;p&gt;◆ 我们总是一起出现在校园里 食堂 图书馆 教室 车站我偶尔会想 现在你一个人做这些事情的时候是什么样子呢&lt;/p&gt;
&lt;p&gt;◆ 为什么快乐也会流下眼泪 灌溉了我的荒野 开满了玫瑰 我不累 我不睡 我不休息 我不合眼 我不想浪费 每一秒 在这 有你 的世界&lt;/p&gt;
&lt;p&gt;◆ 是不是一个人一生只能说10000次我爱你 所以有些人怎么也不肯说了&lt;/p&gt;
&lt;p&gt;◆ 我们常听到的话 不表示就值得相信很可能是那些 懒惰的人 随口说说而已&lt;/p&gt;
&lt;p&gt;◆ 等车的时候 我吃了剩下的那个橘子 我到澳大利亚以后吃的第一个橘子&lt;/p&gt;
&lt;p&gt;◆ 亲爱的不二 我会觉得 人生是没有什么非要做不可以的 也没有什么不可以放弃的&lt;/p&gt;
&lt;p&gt;◆ 那天在 MSN上 我对你说 我爱你过了 六秒 你回消息说 谢谢你那一刻 我忽然觉得很糟糕&lt;/p&gt;
&lt;p&gt;◆ 好吧 我原谅你了 你走吧 这样可以了么？还是 连我对你的原谅你都觉得 来得太晚了 而不那么有效呢？&lt;/p&gt;
&lt;p&gt;◆ 冬天的时候 我没有特别想家 偶尔想你&lt;/p&gt;
&lt;p&gt;◆ 会很认真地觉得 如果是春天来了的话 那么我 安东尼 也要开始欣欣向荣了&lt;/p&gt;
&lt;p&gt;◆ 我深知 快乐 没有寂寞长久坚强可是再怎么也没料到 还没到保质期 它就开始不安 腐烂&lt;/p&gt;
&lt;p&gt;◆ 原来我是看到虫了 原来我的世界永远都没有真正的黑暗呀嘿嘿 嘿嘿嘿&lt;/p&gt;
&lt;p&gt;◆ 就这样 十一月的时候 我有了一只猫或者 应该说 猫 有了我又或许不是 拥有的关系 而是 安东尼 开始 和一只猫 一起生活&lt;/p&gt;
&lt;p&gt;◆ 不二啊 那些我们觉得理所应当的事情 不一定是对的&lt;/p&gt;
&lt;p&gt;◆ 这样 他就又可以按时 看日落 思考他和玫瑰之间的事情了&lt;/p&gt;
&lt;p&gt;◆ 如果我说 我现在的生活 很辛苦 是前所未有的艰难他们会不会 觉得我身在福中不 知福呢&lt;/p&gt;
&lt;p&gt;◆ 不过 老子这么年轻 吃点苦没什么最困难的已经过去了 最快乐的还没来 哦耶&lt;/p&gt;
&lt;p&gt;◆ 我问她 你在陌生的国家 陌生街道 看陌生风景的时候 脑袋里都在想什么呢？&lt;/p&gt;
&lt;p&gt;◆ 意识比身体坚强语言比心绪理智&lt;/p&gt;
&lt;p&gt;◆ 本来以为 快乐不会长久 怎知 一日比一日快乐 ——这话 真是又贱 又幸福&lt;/p&gt;
&lt;p&gt;◆ 不二说 它喜欢可以在第一句就介绍清楚 时间 地点 人物的作者&lt;/p&gt;
&lt;p&gt;◆ 我一直这么觉得 不过寂寞的时候做什么都显得不自然 所以 随它去吧&lt;/p&gt;
&lt;p&gt;◆ 不二：鳄鱼无法伸舌头安东尼：怪不得 它总是哭&lt;/p&gt;
&lt;p&gt;◆ 我们的意识存在于我们的肉体之内 我们的肉体之外 有另一个世界 这一关系性 常常给我们带来痛苦 迷惘 悲伤和分裂&lt;/p&gt;
&lt;p&gt;◆ 我在想你觉得很可笑 我在每一个地方 想你 人很多的时候想你 自己一个人的时候 你更是成了思想的主角 这样的你 让我羡慕&lt;/p&gt;
&lt;p&gt;◆ 你看 有些人 不明白 就是怎么也不明白有些人 明白了 他们不会问我 不二到底是什么&lt;/p&gt;
&lt;p&gt;◆ 而且 说给你听的 那些话 那些 不明白的人 估计看了也没有任何意义&lt;/p&gt;
&lt;p&gt;◆ 亲爱的不二 我们又要 搬家了 这次 要搬到山上想说 还好 有你&lt;/p&gt;
&lt;p&gt;◆ 脑袋在放空 于是 即使是我这样 记性不好的人 也很莫名地想起来很早之前的事&lt;/p&gt;
&lt;p&gt;◆ 所谓人生 便是取决于 遇见谁&lt;/p&gt;
&lt;p&gt;◆ 给你写东西的时候 不能加亲爱的 因为 我们没到那个程度 你知道&lt;/p&gt;
&lt;p&gt;◆ 我想 可能是 因为 有些人 有些事 有些地方 一旦离开 就回不去了 或者应该说 总觉得 自己回不去了&lt;/p&gt;
&lt;p&gt;◆ 我的旅游方式是 到一个地方 选一个好宾馆 然后开始睡觉 醒来就出门找个好饭店 吃饱以后开始乱逛 完全没有计划 随意且慵懒 可以放空&lt;/p&gt;
&lt;p&gt;◆ 其实 我没有很想去 那么多地方 我只想和你&lt;/p&gt;
&lt;p&gt;◆ 我觉得 我是特意想把自己弄醉的 然后 后来的事情都不记得了&lt;/p&gt;
&lt;p&gt;◆ 那些 我们一直好奇 而又有一些 惴惴不安的未来有的时候 在我心里 隐隐约约地 感觉到它们是 明亮的&lt;/p&gt;
&lt;p&gt;◆ 没有糗事的人 真没劲呢&lt;/p&gt;
&lt;p&gt;◆ 他只是 出现在我生活里的 一个新的说谎者&lt;/p&gt;
&lt;p&gt;◆ 不二 有的时候 我会觉得我 有点色 &amp;gt; &amp;lt;&lt;/p&gt;
&lt;p&gt;◆ 我们总是很容易觉得别人幸福 觉得自己可怜&lt;/p&gt;
&lt;p&gt;◆ 看到 灯塔的时候 我会很自然地想起你 只是 现在 我觉得 就算有天 你不再指引我了 我也能找到回去的路&lt;/p&gt;
&lt;p&gt;◆ 我觉得呢 漫长就是 如果一辈子的时间 都用来做后记&lt;/p&gt;
&lt;p&gt;◆ 这样 如此寂寞地 我也长大了&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注：我想，我慢慢喜欢上这个温柔的少年了&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>CET与我的故事</title><link>https://bangwu.top/posts/cet/</link><guid isPermaLink="true">https://bangwu.top/posts/cet/</guid><description>英语四六级经历与拖延反思，谈态度与行动</description><pubDate>Mon, 16 Dec 2024 16:56:59 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;audio controls data-title=&quot;阴天快乐&quot; data-artist=&quot;陈奕迅&quot; data-cover=&quot;https://cdn.bangwu.top/img/20260111vqxgz-xy.webp&quot;&amp;gt;
&amp;lt;source src=&quot;https://cdn.bangwu.top/img/2026011164c019b0.mp3&quot; type=&quot;audio/mpeg&quot;&amp;gt;
&amp;lt;/audio&amp;gt;&lt;/p&gt;
&lt;p&gt;前几天在英语六级的考场，写完了试卷，坐在那里我就想，嗯，我应该写一篇文章来说说此事——态度，何时出发。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/202601110ad86ed1.jpg&quot; alt=&quot;IMG_20241214_141002&quot; /&gt;&lt;/p&gt;
&lt;p&gt;整个CET考试生涯是从大二上学期开始的，虽然学校从大一下学期就可以报名了，我没报，当时想的是反正时间还长，而且我这学期课还多，不想在期末周还去考个四级(不过也可能最大的原因是害怕自己过不了)没错，我是一个外地的考生，来到上海上大学时，第一个感受就是这里的英语太难学了，强度好高:记得大一的英语读写和听说课我都是被老师捞上来的，感谢老师🙏🙏学习热情熄灭的时间段，而且还是最讨厌的英语，我怎么可能去背单词，去学英语。我时常觉得英语是一个很神奇的科目，甚至整个人类语言系统都是很神奇的，因为你就算是学习了很长时间，遇到不会的单词，不会就是不会也只能猜出来个大概意思。于是就导致了英语整门课就变得很“随机”而且对于一个需要长年累月积累的学科来说，我更加会选择“运气”这条路，英语对我来说就是能过就行，能听懂就行，实在不行有翻译。&lt;/p&gt;
&lt;p&gt;之前就会刷到一些大学生自律视频，都是离不开早起背单词，嗯，早起第一件事都是背单词，可我呢，根本没有早起这件事，早在睡梦中度过了。不清楚英语背单词为什么会和自律挂上钩，这是需要长久的坚持没错，也要吃很多难背的苦，但总觉得，背了就是背了，不能背了直接听懂老外讲话了吧，不能背了直接可以入职高薪吧......没错，当你准备做任何一件让你痛苦但是又没那么重要的事情的时候，就是会有那么一个声音劝你不要去做。
然后大一就在自娱自乐中过去了，背单词数：小于100，到了大二，再不报CET就说不过去了吧，已经逃避一次了。我记得点击付款按钮的那一刻，我下定决心要坚持背单词，却又在付款完成后的3分钟把这件事忘得一干二净，就好像我刚刚只是点了一个外卖🤕记得那天去考四级的路上，我还是很有信心的，毕竟还是有点底子，我就在想，我与英语纠缠久，不知道从什么时候染上的中二病，学英语时也总说“wtf”“come on，ples”“What&apos;s wrong with you？”“what are you looking for？”之类的话语，我承认，学习这些语句是很简单的，也能随口说出来，但我确是一遍又一遍地重复着，可能觉得这样很帅吧。&lt;/p&gt;
&lt;p&gt;英语四级就这样考过了，我不知道文章中的大部分意思，听力一点也没听懂，但我还是及格了，迎来了CET6，有意思的是，在大学有一个说法是“六级刷分”因为要刷到高分，接下来的大学生涯不出意外每年都会考两次六级，所以根本不担心第一次考不过这件事，无论如何，下次还是要去考的。又一次抱着侥幸心理，这次，幸运女神没站在我这边，差十几分及格。我不能坦然接受这个事实，对的，我没努力也没期待，但是我还是不能接受这个事实，不能接受这个失败的标志，像是我人生的污点。&lt;/p&gt;
&lt;p&gt;这一切不都是我自找的吗？是，没错。于是暑假的时候，我给自己定了个背单词的目标，主要还是过考试，其次是要看很多英文文档，多会点单词没坏处。我对自己说这次你能行的，我当时都能想象到自己在家背单词的情景，还有看英文文档时无阻的喜悦，我是个爱幻想的小丑。到了开学的那天，我一个单词也没背，一点自控力没有。&lt;/p&gt;
&lt;p&gt;下次在做吧，太累了这次；明天再弄吧，今天先休息；从下周开始，我一定要好好学习...有时候态度可能会决定一点东西，正如我对写作的态度是——余生要坚持的事情，那当我想些一篇文章的时候，我可以连续一个小时都在写，构思好长时间，每天都在想写点什么，有什么新的观点。对于行动，请记住，想要做的事情现在就做，因为“不知道明天和意外哪个先来”，不知道明天的我，还有没有这个心情，这个条件。现在能做的话，请开始吧！像我现在一样，现在是凌晨一点，我刚完成我的写作，睡前想到完成这篇文章，所以我告诉自己不写完就不睡觉了。&lt;/p&gt;
&lt;p&gt;现在，我知道了对一些事情的态度问题，也知道不行动永远没有好的改变，但我还是打算就这样一直下去，带着我的缺憾，生活下去......&lt;/p&gt;
</content:encoded></item><item><title>这些都是你给我的爱，云治</title><link>https://bangwu.top/posts/yunzhi/</link><guid isPermaLink="true">https://bangwu.top/posts/yunzhi/</guid><description>云治书摘与句子收藏：爱与离别的呢喃</description><pubDate>Mon, 16 Dec 2024 03:41:53 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Safe &amp;amp; Sound&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ 我们一直在爱着同时也难过寂寞 愤怒失望觉得累了伤心心灰意冷但是又会出现一个人让你觉得一切都好了犹如季节变化冷热交替&lt;/p&gt;
&lt;p&gt;◆ 但是我很确定这个故事一定有一个好的结局小兔子会幸福因为世界是真的付出的都值得 小兔子一点都不可怜&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hometown&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ But that doesn&apos;t stop me from believing in andloving this world from the bottom of my heart.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;WELLINTON&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ My world seemed fine before you came along.&lt;/p&gt;
&lt;p&gt;◆ 我肯定会有人喜欢它我就喜欢它在这个世界上人们追求不一样的东西也通过不同的方式来认识这个世界&lt;/p&gt;
&lt;p&gt;◆ 我第一次遇到她的时候我忽然意识到接下来几天我都要好好整理脑子里的思绪空间 好让她搬进来&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;GARDEN ORNAMENTS&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ 里德很认真地想了想说我觉得爱就是“在乎”你做的这个东西对于在乎的人来说就有了意义&lt;/p&gt;
&lt;p&gt;◆ 人们愿意和刚认识几天的人结婚也不愿意和一个看起来很好的人说我们做朋友吧&lt;/p&gt;
&lt;p&gt;◆ 我觉得我有一点喜欢这个城市了这是我们的城市&lt;/p&gt;
&lt;p&gt;◆ 我不是魔术师你是在我漆黑的世界里你不动声色轻而易举地成为最亮的明星&lt;/p&gt;
&lt;p&gt;◆ 的而是要去感受的&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;安东尼在诗人的工作室里买了一个红皮的小笔记本本子的扉页上写着&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ The thing we love about love stories is that two people teacheach other things that they needed to know but they didn&apos;tknow that they needed to know.&lt;/p&gt;
&lt;p&gt;◆ 我想成为你最好的邂逅最难的再见&lt;/p&gt;
&lt;p&gt;◆ 我当然想成为她最好的邂逅但是再怎么难的再见也只是再见而已 我不想为难她也不想让她难过&lt;/p&gt;
&lt;p&gt;◆ 至于我尽管我曾经一心一意地对她付出了真心但是我的心还在等遇到对的人它又会活起来的&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;他想&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ 至于你 也许你在天边也许我遇到你的那天还很远这些我都不怕&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Pillow latk&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ 有的时候我想离开你去很远的地方人烟荒芜的地方可以在海边好好思考可是 我又害怕我离开了你会难过会哭我也觉得我不能像之前那么幼稚 了每次遇到这样尴尬的局面就要逃跑 于是我就这样跟着你走&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This or Mha wih.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ 觉得每次都爱得那么用力很伤元气也许爱情也不是找来的它要自然而然地发生&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Just tove me, dammil!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ when you break-up with someone, the worst you can say isnot &apos;I hate you&apos;,It&apos;s &apos;I changed who I was for you&quot;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;that a heat&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ 抱着你睡觉的时候也不觉得彻底地拥有你总觉得我醒来的时候你就会消失不见&lt;/p&gt;
&lt;p&gt;◆ 每一处的海都是我的海我坚信它们在某一处汇入贯通储存了我的很多情绪和过往又默不做声&lt;/p&gt;
&lt;p&gt;◆ 所以啊 我就在想 人生 之所以 珍贵 说不定不是我们去过的一个又一个精彩华丽的 旅游胜地 而是旅途之中寂寞 无聊不可打发的时间里有你陪伴吧&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;BeFore realily sehs in&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ 所以 你愿意成为我为数不多的秘密么&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/202601117dbe5091.jpg&quot; alt=&quot;weread_image_846666758169847&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/2026011154cc90da.jpg&quot; alt=&quot;weread_image_938328666348080&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/202601113e52da1f.jpg&quot; alt=&quot;weread_image_71591773021266&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/202601110b64b8a6.jpg&quot; alt=&quot;weread_image_71961521610211&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>这些都是你给我的爱，里德</title><link>https://bangwu.top/posts/lide/</link><guid isPermaLink="true">https://bangwu.top/posts/lide/</guid><description>《这些都是你给我的爱》书摘，关于爱与相遇</description><pubDate>Thu, 05 Dec 2024 12:37:31 GMT</pubDate><content:encoded>&lt;p&gt;《这些都是你给我的爱》书摘&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;送给之前陪我一起傻的你&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ 我都会坚定地走向你不迷惑不慌张不犹豫总有一阵子会出现这样的生活状态舒适得让人不知所措&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Dear Anthony&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ If walking with you, every street seems like no end forme.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“当歌手好辛苦”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ 如果你喜欢的人不喜欢你那么就算全世界的人都喜欢你还是会觉得很孤独吧?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;半条尾巴的 黄条纹猫」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ 如果找不到也不要太难过……嗯……想得到的东西不一定都会得到……嗯……人生也不过如此&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;安东尼点头&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ 嗯我是说如果你口渴了应该喝水咖啡是用来享受的&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「在巫婆的店里」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ 还是算了吧我一点也不喜欢新的面包篮子它根本没有面包的味道不算是一个面包篮子也许就算我带着这个味道有一天也会开心起来吧&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;又过了很久&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ 我们都会一天天变老然后死掉多好&lt;/p&gt;
&lt;p&gt;◆ 不要走得太慢花会凋谢的也不要走得太快那样花还没有开&lt;/p&gt;
&lt;p&gt;◆ “我喜欢你看我的方式它让我感觉到我自己的存在”“那是因为我想喜欢你”&lt;/p&gt;
&lt;p&gt;◆ 第二天早上起来的时候狐狸就消失了连他留在我身上那些一直清理不净的绒毛也不见了 我一个人走在路上脑子里空空的然后……然后忽然放声大哭好像我从来没有那样哭过心里想着我当初如果和他做朋友就好了&lt;/p&gt;
&lt;p&gt;◆ 城市里的人都在找东西找工作找住处找恋人找一段记忆找一个梦有一些在找另外一个人还有一些在找自己&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;读着这封信我慢慢有了睡意&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ 想找个 保鲜盒把你给我的那些感动都装起来 当我不那么喜欢你的时候就拿出来回味一下&lt;/p&gt;
&lt;p&gt;◆ 所谓人生便是取决于遇见谁The so-called &quot;life&quot;, all depends on who you meet.&lt;/p&gt;
&lt;p&gt;◆ 我们为什么要旅行呢我想可能是因为有些人有些事有些地方一旦离开就回不去了或者应该说总觉得自己回不去了&lt;/p&gt;
&lt;p&gt;◆ 那些我们一直好奇而又有一些惴惴不安的未来有时候在我心里隐隐约约地感觉到它们是明亮的&lt;/p&gt;
&lt;p&gt;◆ 之前那个我和现在的我之间的距离被许多细小琐碎的事填充着这些都是你给我的 爱吧&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;From echo:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ 很多年以后，在一本书、一段音乐、一片微醺的光线里，倏忽记起的，那段时光。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;From Anthony:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ 你说我们现在成了这个样子是不是都是别人给的?比如握手时候的力度走路的姿势我们的逻辑身上的疤痕爱怎样的人说话的语气和语速我们旅行的目的地喜欢哪一个歌手什么时候会哭手段与造诣去的地方微笑时嘴角的弧度到底有多努力地生活有没有很爱喝水是不是很喜欢说我爱你接吻的技巧拥抱的姿势说英文时的口音有多天真或者多老练喜欢牛仔还是运动裤可乐还是泡面&lt;/p&gt;
&lt;p&gt;◆ 眉毛的粗细头发的浓密走路的时候手指有多弯&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;后记&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;◆ 那些准备好不离不弃的那些没有说再见就从此不见的那些不论伤害我多少次可是只要你微笑对我时我就拼命想将你原谅的当你们一个个还是或者已经不是那些日子里的你的时候我已经成了现在的我了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/2026011178ecf326.webp&quot; alt=&quot;bangwu20241205201246&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/20260111768f46f2.webp&quot; alt=&quot;bangwu20241205201738&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/20260111c7da5dd1.webp&quot; alt=&quot;bangwu20241205201941&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/20260111b4a9806e.webp&quot; alt=&quot;bangwu20241205202015&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/2026011183462b72.webp&quot; alt=&quot;bangwu20241205202052&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>深度学习项目思考</title><link>https://bangwu.top/posts/deeplearning-thinking/</link><guid isPermaLink="true">https://bangwu.top/posts/deeplearning-thinking/</guid><description>深度学习小组项目为识别声音是否是克隆出来的，此为项目复盘和感想</description><pubDate>Sun, 01 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;&lt;strong&gt;个人报告&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;在此次深度学习大作业中，我深切感受到了网络环境、训练数据以及计算资源的多重“摧残”。先不说网络环境的种种问题，单是在寻找训练数据和资源时，就已经感到极其繁琐。深度学习的训练需要大量标注数据，而数据标注本身就是一项费时费力的工作。因此，我们只能选择公开的数据集。按深度学习常见的输入格式划分，数据通常包括图像、文本和音频三种类型。实际上，它们在向量空间中都可以统一表达，但在处理方式上仍有显著差异。&lt;/p&gt;
&lt;p&gt;我们决定跳过图像和文本领域，因为这两个方向的应用相对成熟，而音频方向的实际案例较少且挑战性更大。于是，我们在 GitHub 上搜索与 voice deep-learning 相关的项目，找到了几个热门的声音克隆项目，如 fish、GPT-SoVITS 和 MockingBird。由于我们希望实现一个判断声音是否为克隆生成的模型，因此需要先使用这些项目克隆出音频数据。&lt;/p&gt;
&lt;p&gt;首先，这些声音克隆项目在 Linux 系统上的部署普遍较为复杂。不过值得一提的是，大多数国人开发者都会准备 Windows 的一键整合包（大概率是考虑到网络环境或方便小白用户）。但对于我们来说，这种整合包不适用，因为我们需要能够提供 API 的项目，以便通过批量调用 API 来生成克隆音频。然而，在仔细阅读多个项目的 README 文件后，我们惊讶地发现，几乎所有项目都没有提到如何通过 API 使用这些工具。&lt;/p&gt;
&lt;p&gt;这一点让人十分头疼。尽管项目的整体复杂性较高，但提供一个简单的 API 接口本应该是常见需求。然而，我们在文档中一无所获，最后不得不通过查阅 GPT-SoVITS 的 issue 页面才找到相关说明。坦白说，我完全不理解为什么这些关键信息不直接写进 README 中。&lt;/p&gt;
&lt;p&gt;这一步解决后，新的问题接踵而至：程序调用时不支持异步和多线程。按照程序现状测算，完整克隆所有音频数据大约需要 60 小时。起初我们也无计可施，但后来想到可以通过在同一台机器上多开几个 API 调用实例，并在文件生成时增加“是否已存在”的判断逻辑，从而加速数据生成。这一调整让我们节省了不少时间。&lt;/p&gt;
&lt;p&gt;可是运行程序所需的训练资源太大，显存需要18G，内存30多G，将我们训练数据上传到服务器，耗费了大量的时间，在服务器上安装环境又有网络因素从本地下载后再上传上去，总时长四个多小时。不等上传完，什么也不能干，但是这个是没办法改变的，带宽就这么大。与此同时，在服务器上安装训练环境又因网络原因问题频出，无法直接从官方源获取依赖，不得不选择从本地打包后再上传。整个过程耗时冗长且无法并行进行，导致项目的时间大部分浪费在数据传输和环境配置上，而代码实现和参数调优的时间相对较少。（提一句，linux nohup命令跟踪log挺好用的，可以实时看训练日志极大地方便了模型的调试和监控）&lt;/p&gt;
&lt;p&gt;克隆出来音频后又发现了问题，有大量音频是0s的，也就是没有音频数据，对于这些情况，只能归咎于模型api问题，可能是流数据传输问题。我们用的是迁移学习，需要从huggingface上拉取模型，不知道为什么我们本地下载完后传到服务器上根据官方文档引用不能使用，没办法，从服务器上也不能直接连huggingface。好在，国内有个镜像&lt;a href=&quot;https://hf-mirror.com/&quot;&gt;https://hf-mirror.com/&lt;/a&gt; 使用了这个后成功拉取下来了模型，又是一个网络环境问题。后面开始训练后就还一切顺利了，只需等待训练完成看看准确率就好了，一开始我们想的是搭建前后端分离带数据库的应用项目的，时间有限，就用Gradio几十行命令搭建出来界面了。对于应用，当然是部署到公网上才算酷，只能内网本地访问也太不行了。我们就利用本地服务器然后使用frp进行穿透，再配置域名成功部署到公网上。&lt;/p&gt;
&lt;p&gt;我们从一开始便没对模型的最终效果抱有太高的期望，因为在项目初期，当我们用克隆出来的音频进行试听时，发现这些音频的质量实在是太好了——它们和原声几乎一模一样，甚至让人完全听不出来是克隆出来的。这种初步的直观感受让我们对任务的难度有了更清晰的认知：如果人耳都无法分辨，那么依靠机器学习来判断，会有胜算吗？&lt;/p&gt;
&lt;p&gt;不过，正如一句玩笑所说的那样，“深度学习虽然不可理解，但总是能带来惊喜。”这句话在我们项目中得到了充分体现。我们用深度学习训练模型来判断是否是深度学习生成的数据，这种“自我较劲”的做法乍一听似乎有点矛盾，但恰恰展示了深度学习技术的强大之处。最终，尽管模型在一些方面的表现并不完美（例如仍存在一定误判），但它的整体效果已经远超我们的初衷。通过对克隆音频的训练和测试，模型能够捕捉到一些人耳无法察觉的细微特征，准确区分克隆音频与真实音频。这一点正是深度学习的魅力所在：它可以从海量数据中学习到人类难以显式描述的规律，并将其转化为具体的判断能力。&lt;/p&gt;
&lt;p&gt;这个项目算什么？如果从学术意义上来说，这个项目可以被归类为 生成对抗与检测 的研究领域。近年来，生成模型（如 GAN 和 VAE）以及音频克隆技术的快速发展，让合成数据的质量越来越高。然而，与此同时，检测这些数据的真实性也变得越来越重要，特别是在一些需要高可信度的应用场景中（如语音识别、法律取证等）。因此，我们的项目其实是站在了这类研究的边缘：试图用深度学习来对抗深度学习，用 AI 来识别 AI。&lt;/p&gt;
&lt;p&gt;更广泛地看，这也是当下 AI 技术发展的一个趋势——生成与检测的对抗赛。生成模型在不断提升合成数据的质量，而检测模型也在不断进化，以便更好地区分真伪。我们的项目虽然规模不大，但也算是这一趋势的一个微小实践。&lt;/p&gt;
&lt;h2&gt;以下是本次项目的总结与反思&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;1、时间分配上的反思&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;从整个项目来看，数据准备和模型部署耗费了我们大部分时间，而代码实现和模型调参的时间相对较少。这与我们最初的时间规划有所偏差。如果能在项目开始时更合理地安排任务（如提前下载模型和数据集，或利用网速更快的服务器），可能会提升整体效率。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2、网络环境的限制&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;项目过程中，网络问题成为一个反复出现的瓶颈。不论是数据上传、环境安装还是模型下载，都受到带宽限制。建议以后可以提前缓存所需的依赖和数据，或者选择国内的开源镜像（如 TUNA 和上文提到的 Hugging Face 国内镜像）。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;3、对深度学习工具链的依赖&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;现代深度学习的工具链非常丰富，但很多工具的文档仍然不够友好。特别是对于音频领域的项目，API 的使用方式和代码示例非常有限，这导致了我们在初期花费大量时间摸索。未来，希望更多项目可以注重文档的完善性，减少学习和使用成本。&lt;/p&gt;
&lt;p&gt;总体来说，这次深度学习大作业让我深刻感受到了深度学习项目的复杂性。从数据准备、模型部署到训练优化，每一步都充满挑战，但也收获了许多宝贵的经验。&lt;/p&gt;
</content:encoded></item><item><title>生日快乐！棒无</title><link>https://bangwu.top/posts/birthday20/</link><guid isPermaLink="true">https://bangwu.top/posts/birthday20/</guid><description>20岁生日的一天：平淡忙碌却有小确幸</description><pubDate>Sat, 23 Nov 2024 15:32:06 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;audio controls data-title=&quot;Payphone&quot; data-artist=&quot;Maroon 5/Wiz Khalifa&quot; data-cover=&quot;https://cdn.bangwu.top/img/20260111vdv9n-vh.webp&quot;&amp;gt;
&amp;lt;source src=&quot;https://cdn.bangwu.top/img/2026011190132cff.mp3&quot; type=&quot;audio/mpeg&quot;&amp;gt;
&amp;lt;/audio&amp;gt;
这是我第一次写生日文章，希望以后每年也都能写一篇。今天是我的生日，和大多数人一样，这将是没什么特别的一天，我之前在文章 &lt;a href=&quot;https://bangwu.top/posts/butaire&quot;&gt;人太多大部分是漫无目的地走&lt;/a&gt;
里也有说过，不是很重视生日也好久没吃过属于自己的蛋糕了。不过一切都无所谓，我不是很在乎，挺好的。
早上十点钟醒了，打开手机看到一个朋友给我发的生日祝福，祝我老了一岁还给我转了杯奶茶，幸福往往就是那么普通的一瞬间，一切都是刚刚好的样子。下了床坐在椅子上，我回复到：我再也不是个小孩子了😭，略感伤心，不知不觉间自己已经20岁了，想起10岁的棒无，他也会好奇10年后的自己是什么样子吗？现在我或许能知道，还不错，目前来说还是挺优秀的，哈哈哈。
内心的期待：这几天偶尔打开菜鸟驿站看看我的快递，虽然我没买，但我还是希望能看到多出一个快递派送单，来自远方。不把自己的生日告诉别人，却希望得到走心的祝福。
我打算把这一样记录下来，就从早上醒来开始吧。醒来后就去简单洗漱了一下，微笑，内心想着今天是生日，怎么说也要比平常开心一点吧。坐在椅子上，打开电脑，想着下周二要交的小组作业还没有头绪，这次并不是我拖延症之类的，是近几周作业任务实在有点忙不过来，好几篇论文还有作业。坐在椅子上对作业还是没有什么头绪，只能等着小组成员一起来讨论，于是我就想着趁着这个时间干点啥，想起来昨天晚上要买东西，宿舍缺垃圾袋了。忽然觉得，过生日只买垃圾袋好像有点不妥，思来想去，果然，人在想买东西的时候不要乱逛电商平台，会像我这样的买到固态和内存条🤕&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/202601114090022b.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/20260111741e04ba.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;就是这样，我没有丝毫犹豫的拿下了垃圾袋、方便面、固态和内存条。算是我买给自己的生日礼物吧，一边是生活，一边是爱好。然后就是小组讨论，当然，我不会因为是我的生日而影响了小组的进度。
讨论到中午，朋友叮嘱我要吃长寿面。遂一个人至河东食堂一楼点了一碗面，周六的食堂是有点冷清的，中午饭点也没多少人在面条窗口就我和另一个女生，打饭的阿姨却不在。我总是沉默的那个，不愿意开口喊阿姨在不在，好在那个女生趴在窗口上喊阿姨“阿姨还有面可以煮么？”由于我在那个女生前面，她还让我先点，我推脱了，让她先来。不知道为什么我现在还记着这件事（写的时候已经晚上十一点了），可能是无意间的善意吧。虽然之前吃了很多次这个窗口的面条，但是还是觉得这次的面条好吃，长寿面果然会暗暗在心里加分！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/202601113a06fbab.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;吃完面回到宿舍，又开始鼓捣我的小组作业，然后我崽下午休息就喊我跑图，11月23号，光遇绊爱来了，有饺子可以吃，像我的生日一样安静美好。跑完图打了几把王者，到四点了。小组作业也在这时候有了一点头绪，我把代码一些，由于是深度学习项目，剩下的就是等待了（深深地感受到了深度学习对网速和硬件的要求😔）。后面我又干了啥捏，有点忘了，大概是发呆吧。然后到了六点多，室友向我推荐点黏糊糊麻辣烫，于是我们俩就一人点了一份。不得不说，还挺好吃的&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/20260111d03b5720.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;吃完我就在逛一逛帖子论坛之类的，刷抖音。到了晚上八点半就开始打王者了，打到十点半累了，就不打了。
把手机放在桌子上，我准备写点啥。忽然有个电话打进来了，我以为又是推销电话，一看是我的发小打来的。我和两个发小有一个群，只有我们三个，群聊名称也没有。这成了我们联系的主要渠道，打过来电话，先问我今天吃点好的没，“随便吃点，吃啥好的”，你过生不吃点好的吗，“随便吃点”。后面就是一顿闲聊，真好。
就这样，我的一天快结束了，普通的一天。现在是23.23，记录我的生日23号。作业、生活和理想、朋友。人生 如此 20年，棒无，生日快乐！&lt;/p&gt;
</content:encoded></item><item><title>独立开发者网络营销指南</title><link>https://bangwu.top/posts/internet-marketing/</link><guid isPermaLink="true">https://bangwu.top/posts/internet-marketing/</guid><description>软件产品网络营销策略——以独立开发者为例</description><pubDate>Fri, 22 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;&lt;strong&gt;软件产品网络营销策略——以独立开发者为例&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;本文为我的网络营销课程中的一个作业，在这里分享一下，欢迎改正和讨论。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;前言&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;随着计算机技术的发展，独立做出一个软件产品、项目的门槛不断降低。越来越多人选择成为独立开发者。谁是独立开发者？其实独立开发者归属于自由职业者，是自由职业者中从事和软件开发相关工作的一支人群，更严格来说一般是“从产品立项、设计、开发、推广、到盈利闭环全部独立完成的人”。&lt;/p&gt;
&lt;p&gt;独立开发者往往将全部精力投入应用开发，而忽视了制定优秀的营销方案。这通常是由于缺乏资金、资源和专业团队。好消息是，有效的营销并不需要庞大的广告预算。&lt;/p&gt;
&lt;p&gt;在这之前要认识到一个方法论：好的想法、营销比好的技术有用。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;营销手段探究&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;对于独立开发者没有充足的广告预算，进行网络营销所花费的成本有限，不能进行像搜索引擎营销中的关键字营销——付费赞助与竞标的方式通常会花费大量资金和精力，不适合独立开发者来做，以下所有的策略都是建立在成本低且个人可维护的前提下的。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;一、搜索引擎营销&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;正如上述所说的，关键字营销不可取，PPC 的费用不足以支撑独立开发者盈利，但是可以利用搜索引擎优化(SEO)来将自己的网站列入搜索结果的前面，吸引消费者注意与点击。SEO 一般需要的手段不深入的话是简单的，例如使用现有的前端框架都有专门的 SEO 优化，仅需简单的设置即可。所以可以着手优化的是页面优化(On-Page SEO),由于谷歌算法始终在变化，但谷歌继续优先考虑用户体验。谷歌建议关注“以人为本的内容”，页面优化需要的是高质量的内容、网站架构(优化 URL、标题和副标题等)、外观设计与用户体验。通过以上手段，对于独立开发者来说就基本可以将网站排名提高很多。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;二、社会网络营销&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;社会网络营销需要的成本低，是独立开发者营销的主要手段。与目标顾客群创造长期沟通，社会网络营销需要长期运营维护，产生的收益也是逐渐递增的，需要注意的是产品的使用截图、海报等需要专门的设计，好的外观才能先吸引到用户。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;社群网络服务利用微信、微博、Bilibili 等本地的社交媒体平台以及 Facebook、Twitter、YouTube、Twitch 等海外平台建立社群。目标是国内用户的独立开发者可以通过微信公众号和小程序来吸引流量。目标是国内外用户群体的可以通过 X 发帖来宣传，这种方法一般是高效的，可以引来转帖和反馈。&lt;/li&gt;
&lt;li&gt;博客推广——通过与用户互动、发布有趣内容有利于 SEO 将自己的网站搜索排名提高。这里有个特别的点就是建立友情链接和找知名博主推荐两种方式都是高效的，虽然这两种方法的原有流量不如社交媒体、社群，但是转换率很高，人们通常认为文章或者友情链接推荐出来的产品都是高质量的，更愿意为其付费。&lt;/li&gt;
&lt;li&gt;论坛营销。许多现有的热门独立开发者软件都有在论坛第一次发布产品来推广的做法，这些论坛主要是 v2ex，w2solo，reddit 等。能够有效地找到第一批用户&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;&lt;strong&gt;三、应用商店优化(ASO)&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;软件分发到各大应用商店来提供更便捷的安装方式和流量增长，ASO 大致有两种方法：Keyword Optimization(包含：选词、词排名、转化率)和 Asset Optimization 即 app 其它属性的优化(图标、截图、描述等，通过 a/b 测试)。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;商店搜索优化——App Store 分发量分布：搜索、榜单、推荐位、外部导量。65%以上来源于搜索(苹果官方数据)，剩下的分发量中至少一半来源于榜单，未上榜且无外部导流的 app，搜索是前期唯一的曝光入口。其中关键词方面，转化率：精准品牌词 &amp;gt; 特征词 &amp;gt; 相关热门其它品牌词 &amp;gt; 不相关词。&lt;/li&gt;
&lt;li&gt;APP 商店主页内容优化——主要有 APP 名称、图标、描述、配图、用户评价等这些属性。例如描述应用要简洁有目的性，配图要突出特色抓住用户心理。用户评价方面要单独处理，适当给好评用户一些奖励及反馈。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;ASO 并不像 SEO 那样有许多规则，成本低的话能做得很少，尽可能的把产品发布到每个应用商店就行。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;四、不采用的策略&lt;/strong&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;开源推广——付费版本 完善项目、增加流量。但由于开源的原因，流量增加的同时也会带来付费用户的减少，需要在这两者之间找到一个平衡点，通过 Github 上开源的项目来说，作为独立开发者盈利项目的很少，所以不建议选择开源来推广项目的思路。&lt;/li&gt;
&lt;li&gt;显示广告营销：不采用、收益较低&lt;/li&gt;
&lt;li&gt;电子邮件营销：由于国内偏好因素不采用&lt;/li&gt;
&lt;li&gt;提升口碑：对待用户反馈及时处理、Bug 及时修复并补偿等等。会有好的影响但不是主要策略，例如有的产品不采用社会网络营销，而是用口碑、开源等其他营销手段提升用户好感度，使用户自发的在社交媒体和其他途径进行分享，良性循环。例如：开源程序 Alist 会因为解决用户网盘管理需求以及官网的详细文档而受到用户大规模的推广，在社交媒体上分享自己的使用体验。也是一种好的行为，但不是好的商业行为。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;&lt;strong&gt;五、付费策略&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;目前市场上主流的付费策略是订阅制和买断制选择，一般来说这两种策略如何选择主要是看产品属性：后续维护、提供服务成本高选择订阅制，而产品成熟、更新频率低选择买断制。&lt;/p&gt;
&lt;p&gt;但是国内用户偏好买断制，有一个新的付费策略是这样的：“按年 + 付费更新”的思路，即： 用户支付一次性费用，获得软件的永久使用权和一年内的更新；后续更新则需要按年付费 我觉得这种定价模式的好处在于：1. 降低了那些抵触订阅制的用户的心理负担，因为他们仍可以一次付费，永久使用 2. 将“软件订阅费用”巧妙转化为“更新服务费”，无形之中提升了用户获得的价值感，消解了“我不知道我在为什么东西付费”的疑虑。&lt;/p&gt;
</content:encoded></item><item><title>在安逸的日子里</title><link>https://bangwu.top/posts/anyi/</link><guid isPermaLink="true">https://bangwu.top/posts/anyi/</guid><description>班会投票与内耗中，反思大学的安逸与迷茫</description><pubDate>Thu, 21 Nov 2024 16:31:53 GMT</pubDate><content:encoded>&lt;p&gt;今天（11月13日，拖延症的我现在才写完文章）晚上下课我们班召开了一个简短的择优大会，具体就是投票选出优秀学生和优秀班干。说实话，我对这种会议一点感觉也没有，可能是我不优秀无法去充当分母吧，但由于是班级投票的原因，每个人又都需要参加。&lt;/p&gt;
&lt;p&gt;下课了，正收拾书包的我被室友叫住：还不能走，等下要开会。一瞬间放学的快乐全无，装作不情愿的样子把双手搭在桌子上，我也不知道自己为什么会这么做，大概是情绪外显吧。说说我的大学班级吧，在师范学校里总体男女比例三比七我是不奇怪的，我一个理科生竟然被分到了经管学部的工商管理学院，虽说专业内容还是有些与计算机啊、数学有关的，但是不能忍受学会计学、管理学、经济学，这些课程慢慢磨灭了我的憧憬，我的幻想。大学的课程是很恶心的，就是为了学分和绩点，我敢打赌就算是去学文学、民俗学照样会有人费尽各种心思拿高分，并不是说这种行为太功利了啊之类的，只是总觉得好像与初衷背道而驰了。让我想起了《肖申克的救赎》里对肖申克监狱的描述，一开始你厌恶它，后来习惯它，最后离不开它。在这堵高高的围墙里，我总以君子不器、不被规矩限制、不屑于争逐为借口来麻痹自己，我讨厌掉入这种循环一年又一年，和周围的人们一同腐烂。
投票选优秀学生，竞选的同学站在前面掏出手机念着准备好的竞选词。什么才算是当代优秀大学生呢？同学们念着，参加xxx竞赛获得国家级一等奖，我的绩点是xxx，积极参加社会实践和公益活动......每次听到这些条条框框的话语我都会想到，那你是怎么定义你自己的呢？你该如何描述自己的理想或是人生之类的呢？每次看着这些很卷，表面数值很高的人，我总是会有这些疑问，他们都好像是从一个模子刻出来的，机械化生产，或者说，更像是一个被设定好的NPC。&lt;/p&gt;
&lt;p&gt;:::div{style=&quot;max=width: 300px&quot;}&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/20260111df669fd0.jpg&quot; alt=&quot;Screenshot_2024-11-22-00-29-54-454_com.mi.health-edit&quot; /&gt;&lt;/p&gt;
&lt;p&gt;:::
可事实是什么呢，我还是会把自己带入他们的履历：我也有这些奖项吗，这些奖项要花费很大的精力吧，为什么我的绩点那么低啊...而优秀的同学会有一个不为物质发愁的未来，我却还在纠结是否该迈出步伐。最近小米su7ultra的纽北破纪录视频结尾一段文字火了：永远相信美好的事情即将发生，只要开始追赶，就已经走在胜利的路上。&lt;/p&gt;
&lt;p&gt;最近发现我的生活已经很美好了，差不多的人际关系，差不多的消费，差不多的成绩还有差不多的人生目标。偶尔会不合时宜地想起一些不会再见的朋友，不过也没关系，我很清楚目前来说很长一段时间将会是我人生中难以忘却的美好时光，就这样，我必须好好珍惜这段时间。如果说未来是迷茫的话，那就好好享受当下吧，没必要为了一些事情而难以忘怀，更不必因为某些人而生气，只会让自己难受，错过了一些事情。
我们并不需要有美好的未来。这个世界是很奇妙的，有人星夜赶科场，有人辞官归故里，而又有人只是为了想着明天要怎么活下去。我想，这个世界我活着就够了。&lt;/p&gt;
&lt;p&gt;我总结以上内容为“在安逸的日子里”，因为这段时间是我人生中为数不多的安逸时刻，像是高考后的那个夏天，不过还是多了一点焦虑少了一点热情。最近，我总想着大学的这段日子，大一的我好像有些年少无知，却也是因为缺乏素质教育吧，说来惭愧，选择目前这个专业的我，大一的时候还不怎么会用电脑，打字也特别慢不知道什么是Windows，Word，PPT....不知道是什么支撑我走过那段时光，可能是身边同样盲目的朋友吧，谢谢你！
在安逸的日子里，我们要不经意地在脑海里想着此刻的珍贵，认定此刻的美好，这或许是未来某天午后，那个经历了很糟糕的一件事后怀念起来的时刻。“天地玄宗，万炁本根。广修浩劫，正吾神通。”对此，我有的不是特别快乐，是很强烈很浓郁的满足，对于当下充实的满足。&lt;/p&gt;
</content:encoded></item><item><title>fuwari(Astro) 添加评论功能实践记录</title><link>https://bangwu.top/posts/fuwari-comment/</link><guid isPermaLink="true">https://bangwu.top/posts/fuwari-comment/</guid><description>为 Fuwari 主题引入 Twikoo 评论服务的实现思路、踩坑与优化建议</description><pubDate>Wed, 20 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;Fuwari 是基于 Astro + Svelte 的博客主题，默认没有评论模块。为了保留静态站点的性能与部署体验，我选择集成自建的 &lt;a href=&quot;https://twikoo.js.org/&quot;&gt;Twikoo&lt;/a&gt; 服务。本篇记录最终方案、遇到的问题和改善建议。&lt;/p&gt;
&lt;h2&gt;目标与约束&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;运行环境&lt;/strong&gt;：Astro + Fuwari（Svelte 渲染层），部署到 Cloudflare Pages。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;评论服务&lt;/strong&gt;：Twikoo（腾讯云云函数部署，兼容 LeanCloud）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;要求&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;首次进入文章页即可渲染评论，无需刷新；&lt;/li&gt;
&lt;li&gt;不影响静态渲染，尽量避免打破 Astro 的 islands 架构；&lt;/li&gt;
&lt;li&gt;支持暗色模式、移动端适配；&lt;/li&gt;
&lt;li&gt;保留无评论时的占位文案，避免布局突兀。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Twikoo 部署概要&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;准备数据库&lt;/strong&gt;：我使用腾讯云云函数 + 云开发环境 &lt;code&gt;env-xxx&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;部署云函数&lt;/strong&gt;：按照官方指引上传 &lt;code&gt;twikoo&lt;/code&gt;，记录部署后的 &lt;code&gt;envId&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;配置域名&lt;/strong&gt;：自定义域名 + HTTPS，方便前端调用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;后台初始化&lt;/strong&gt;：在云开发控制台中创建管理员用户、配置默认主题色。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果不想维护服务，可选择第三方托管，但需关注隐私与速率限制。&lt;/p&gt;
&lt;h2&gt;在 Fuwari 中引入评论组件&lt;/h2&gt;
&lt;p&gt;Fuwari 的文章详情使用 Svelte 组件 &lt;code&gt;Post.astro&lt;/code&gt;。为了保持 SSR 与事件绑定，我采用“前端懒加载”的做法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;新增 Twikoo 入口组件 &lt;code&gt;src/components/post/TwikooComments.svelte&lt;/code&gt;&lt;/strong&gt;：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script lang=&quot;ts&quot;&amp;gt;
  import { onMount } from &apos;svelte&apos;;

  export let envId: string;
  export let path: string;
  export let darkSelector = &apos;html[data-theme=&quot;dark&quot;]&apos;;

  let container: HTMLDivElement | null = null;
  let twikooLoaded = false;

  const loadTwikoo = async () =&amp;gt; {
    if (twikooLoaded || !container) return;
    twikooLoaded = true;

    const { default: twikoo } = await import(&apos;twikoo&apos;);
    await twikoo.init({
      envId,
      el: container,
      path,
      lang: &apos;zh-CN&apos;,
      theme: matchMedia(`(prefers-color-scheme: dark)`).matches ? &apos;dark&apos; : &apos;light&apos;,
    });
  };

  onMount(() =&amp;gt; {
    loadTwikoo();

    const observer = new MutationObserver(() =&amp;gt; {
      const isDark = document.querySelector(darkSelector);
      container?.setAttribute(&apos;data-twikoo-theme&apos;, isDark ? &apos;dark&apos; : &apos;light&apos;);
    });

    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: [&apos;data-theme&apos;],
    });

    return () =&amp;gt; observer.disconnect();
  });
&amp;lt;/script&amp;gt;

&amp;lt;div bind:this={container} class=&quot;tk-container&quot;&amp;gt;
  &amp;lt;p class=&quot;tk-loading&quot;&amp;gt;评论加载中...&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;style&amp;gt;
  .tk-container {
    margin-top: 3rem;
  }

  .tk-loading {
    text-align: center;
    color: var(--muted-foreground, #888);
    font-size: 14px;
  }
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;在 &lt;code&gt;Post.astro&lt;/code&gt; 中注入组件&lt;/strong&gt;：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;---
import TwikooComments from &apos;@/components/post/TwikooComments.svelte&apos;;

const { frontmatter } = Astro.props;
const twikooEnvId = Astro.site ? &apos;your-env-id&apos; : import.meta.env.PUBLIC_TWIKOO_ENV_ID;
---

&amp;lt;article&amp;gt;
  &amp;lt;!-- 文章内容 --&amp;gt;
&amp;lt;/article&amp;gt;

&amp;lt;TwikooComments client:idle envId={twikooEnvId} path={Astro.url.pathname} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;client:idle&lt;/code&gt; 确保组件在空闲时加载，避免阻塞首屏。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;path&lt;/code&gt; 传入文章路径用于评论定位。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;envId&lt;/code&gt; 可以通过环境变量注入。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;解决“首次进入不渲染”的问题&lt;/h2&gt;
&lt;p&gt;一开始我把 Twikoo 的初始脚本直接写在 &lt;code&gt;client:load&lt;/code&gt; 的组件里，结果在首次导航到文章页时不会执行，必须刷新才出现。这与 Astro 的 SPA 切换有关：页面通过 &lt;code&gt;@astrojs/view-transitions&lt;/code&gt; 保留 DOM，导致 Twikoo 初始化被错过。&lt;/p&gt;
&lt;p&gt;最终改进方案：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;监听路由切换&lt;/strong&gt;：Fuwari 使用客户端导航，需在 &lt;code&gt;onMount&lt;/code&gt; 中关心 &lt;code&gt;Astro.url.pathname&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;使用 &lt;code&gt;Astro.props&lt;/code&gt; 动态变更 &lt;code&gt;path&lt;/code&gt;&lt;/strong&gt;：当文章切换时，&lt;code&gt;path&lt;/code&gt; 随 props 更新，组件重新初始化。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自定义事件重载&lt;/strong&gt;：如果妙用 &lt;code&gt;&amp;lt;ViewTransitions /&amp;gt;&lt;/code&gt;，可以在切换时派发事件：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;document.addEventListener(&apos;astro:page-load&apos;, () =&amp;gt; {
  twikooLoaded = false;
  loadTwikoo();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fuwari 目前（2024.11）没有暴露事件，我通过在 &lt;code&gt;src/layouts/Base.astro&lt;/code&gt; 添加：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script&amp;gt;
  document.addEventListener(&apos;astro:page-load&apos;, () =&amp;gt; {
    window.dispatchEvent(new CustomEvent(&apos;fuwari:page-loaded&apos;));
  });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在组件里监听：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;onMount(() =&amp;gt; {
  const rerender = () =&amp;gt; {
    twikooLoaded = false;
    loadTwikoo();
  };
  window.addEventListener(&apos;fuwari:page-loaded&apos;, rerender);
  loadTwikoo();
  return () =&amp;gt; window.removeEventListener(&apos;fuwari:page-loaded&apos;, rerender);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样任何一次页面跳转都会重新初始化 Twikoo，解决首屏不显示的问题。&lt;/p&gt;
&lt;h2&gt;样式与暗色模式&lt;/h2&gt;
&lt;p&gt;Twikoo 提供 &lt;code&gt;data-theme&lt;/code&gt; 属性控制主题。Fuwari 使用 &lt;code&gt;data-theme=&quot;dark&quot;&lt;/code&gt; 切换，我在 MutationObserver 里同步这个状态：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;切换时更新容器属性；&lt;/li&gt;
&lt;li&gt;通过自定义 CSS 重写输入框、按钮的颜色；&lt;/li&gt;
&lt;li&gt;在暗色模式下调低边框与阴影。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.tk-container[data-twikoo-theme=&apos;dark&apos;] {
  --twikoo-font-color: #d4d4d8;
  --twikoo-card-bg: rgba(24, 24, 27, 0.6);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;性能与懒加载&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;client:idle&lt;/code&gt; + &lt;code&gt;await import(&apos;twikoo&apos;)&lt;/code&gt; 组合确保在主线程空闲时加载脚本。&lt;/li&gt;
&lt;li&gt;可以进一步使用 &lt;code&gt;IntersectionObserver&lt;/code&gt;，只有当评论区域进入视口时才加载。&lt;/li&gt;
&lt;li&gt;Twikoo 默认加载表情包等资源，必要时使用 &lt;code&gt;twikoo.init({ reaction: false })&lt;/code&gt; 关闭。&lt;/li&gt;
&lt;li&gt;若担心 bundle 体积，考虑通过 CDN 注入 &lt;code&gt;&amp;lt;script src=&quot;.../twikoo.min.js&quot; defer&amp;gt;&lt;/code&gt;，再调用 &lt;code&gt;window.twikoo.init&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://twikoo.js.org/&quot;&gt;Twikoo 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/saicaca/fuwari&quot;&gt;Fuwari 主题仓库&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.astro.build/en/concepts/islands/&quot;&gt;Astro Islands 指引&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.astro.build/en/guides/view-transitions/&quot;&gt;Astro View Transitions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cloud.tencent.com/document/product/876/44547&quot;&gt;CloudBase CLI&lt;/a&gt;：云函数部署与备份&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>恋爱观</title><link>https://bangwu.top/posts/aboutlike/</link><guid isPermaLink="true">https://bangwu.top/posts/aboutlike/</guid><description>看恋爱观视频后的迷茫与反思，想听你的想法</description><pubDate>Sat, 16 Nov 2024 16:21:25 GMT</pubDate><content:encoded>&lt;p&gt;视频来自博主：&lt;a href=&quot;https://v.douyin.com/iAGMpCL8/&quot;&gt;@久树青&lt;/a&gt;
&amp;lt;video&amp;gt;
&amp;lt;source src=&quot;https://static.bangwu.top/video/aboutlike.mp4&quot; type=&quot;video/mp4&quot; /&amp;gt;
&amp;lt;/video&amp;gt;&lt;/p&gt;
&lt;p&gt;说实话，由于今天早上起来心情很不好，又做了一个很奇怪的梦，希望斩断一切又害怕自己内耗，为什么会有不清不楚的男女关系。之前我想佛系，想缘分不物质，我的恋爱观果然有问题，想听听大家的想法。&lt;/p&gt;
</content:encoded></item><item><title>致那三年</title><link>https://bangwu.top/posts/sannian/</link><guid isPermaLink="true">https://bangwu.top/posts/sannian/</guid><description>课堂偶遇同学文档，回望高中三年的成长</description><pubDate>Tue, 05 Nov 2024 02:12:14 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;audio controls data-title=&quot;三年&quot; data-artist=&quot;吴垚滔&quot; data-cover=&quot;https://cdn.bangwu.top/img/20260111wb81c-er.webp&quot;&amp;gt;
&amp;lt;source src=&quot;https://cdn.bangwu.top/img/2026011109dff1dc.mp3&quot; type=&quot;audio/mpeg&quot;&amp;gt;
&amp;lt;/audio&amp;gt;&lt;/p&gt;
&lt;p&gt;今天下午上博弈论，嗯.....一门很无聊的课。这种听不听都无所谓的课，最大的痛点就是要去待着，从上课直到下课，台上老师讲得眉飞色舞我坐在位置上低着头摆弄平板，心却早已飞到了九霄云外。我时常觉得上课有时候还挺幸福的，不像是初高中那样紧绷着神经，需要去理解老师说出来的知识点，我可以只做我想做的事情，对于经常发呆的我来说（有时候觉得自己是ADHD患者，不过这目前来说也不重要啦），真是幸福呢。
不经意间撇到了旁边舍友的平板，说实话，我是不太有意愿去窥探别人正在做的事的，即使是在我旁边睁眼就能看到。我看着好像是某个文档的页面，上面表头赫然写着“致一中的同学们”对于他的高中，我还是有一点了解的，但不知道是一中，也不知道有哪些有趣的事情。这次我没有怯懦，而是凑了上去和他一起看。就这样，在老师的讲课声中，我们一起看着文档往下划呀划，我看到了好多人把高中时拍的照片贴上去，也有高中时情侣的合照，青春、热烈、盎然。一瞬间思绪把我拉到了高中，目光中看到的是他们高中的建筑，我也想起了我们高中的那个湖，那座桥，印象最深的是下午放学到晚上上课期间有一大段时间，那时就和高中的挚友们一起去在桥上待着，那桥现在看起来很小很小，记忆中上面却有很多人——春夏秋冬都有着我们的脚印，而我还会看到那个少年靠在桥边和朋友诉说着学习的痛苦或是玩游戏的喜悦。&lt;/p&gt;
&lt;p&gt;现在回头看高中，我好像除了没谈恋爱以为没什么遗憾，高考毕业时还有很好的同学关系以及意料之外的成绩，对于17岁的我来说，足矣。曾经总以为毕业遥遥无期，如今距我踏入高中班级算下来已经是5年前了，高中给我最深的印象不是做不完的练习题，堆积成山的资料，更不是倒背如流的知识点（高考之后就差不多忘干净啦）反而是我从来都认为无关紧要的同学关系。说说我的事情吧，高一的时候到班级里，刚开学的那天是我记忆最深的一天，可能当时也没想到吧，在那天发生的种种，现在仍然清晰，和大多数人对于陌生环境的焦虑感不一样，我想着的竟不是周围，我认为无论怎样，我以后都会交到朋友的，也没想过主动去认识同学之类的，我总是害怕复杂的人际关系，如果不能给我带来健康的反馈，我会深陷其中不能自拔，就像是一个悉心照料了很久的玫瑰，刺伤了你，还是紧紧握着。于是乎我总是秉承着我所谓的态度，想到之前一个同学说的：无所为，无所为，无所谓。事实也证明只要你不把一件事情看重要，那么无论这件事情怎么变差、变好，都不会带来很大的影响，我当时觉得这样蛮好的，可以很轻松地对即将分出班级的同学说后会有期。有个词叫没心没肺，说的就是当时的我啦，总以为别人不懂我的做法，我的思考，现在想想当时还真是蠢、自以为是捏，最不懂的那个人其实是我，但不知的我还以占了一点点自以为的上风就沾沾自喜。&lt;/p&gt;
&lt;p&gt;高中印象深的有三次人际关系的崩坏，现在想想他们三个如今也和我没了联系，但这段关系还是淹没在了我的17岁，那个意气风发自大的少年。少年时的失望大概是QQ签名里的话、空间里的照片。高中时的人际关系崩坏是致命的，大部分清醒的时间都是在教室里度过的，总是抬头不见低头见，大家也都有相同的同学、朋友，没有特别幼稚的“我不跟他玩了，你也别和他玩”上放学的路上会特意走慢点怕迎了上去会尴尬，体育课也会躲着，总之无论什么时候，只要是在学校，脑海里就要时刻想着不要和他有任何交流。这三次中，有一次很短的，只有短短几天的不说话，相处了三年的他拉下面子来找我道歉，我却还以为占了理，现在想，那叫幼稚。这是台上的老师说到博弈，你以为我在第一层，其实我在第三层，而你以为你在第二层，其实你在负一层。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/2026011165bd7beb.jpg&quot; alt=&quot;IMG_20241105_001144&quot; /&gt;&lt;/p&gt;
&lt;p&gt;现在呢，我想人生路漫漫，高中三年的故事早已翻篇。留下的只有被改变的那部分我，彻底的陪在了我的身边，如此，甚好。&lt;/p&gt;
&lt;p&gt;“教室窗外，弥漫着落日云彩，期待着铃声到来，起身，匆匆离开。”
“倚靠在玻璃窗台，遥望那白云沧海，流过了光阴十载，转身，不再回来。”&lt;/p&gt;
</content:encoded></item><item><title>Auth api设计详解</title><link>https://bangwu.top/posts/web-auth/</link><guid isPermaLink="true">https://bangwu.top/posts/web-auth/</guid><description>介绍主流认证流程、数据库及api设计</description><pubDate>Mon, 28 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;&lt;a href=&quot;https://thecopenhagenbook.com/&quot;&gt;The Copenhagen Book&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这本书是 Lucia 作者大佬 &lt;a href=&quot;https://github.com/pilcrowonpaper&quot;&gt;pilcrow&lt;/a&gt; 写的开源指南，围绕“如何把身份认证做对”展开。我按照书中章节脉络，结合在实际项目里踩过的坑，总结一份适合日常后端开发与产品协作的备查表。&lt;/p&gt;
&lt;p&gt;以下内容覆盖：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;身份模型（用户、身份、会话）如何拆分；&lt;/li&gt;
&lt;li&gt;凭证存储与秘密管理；&lt;/li&gt;
&lt;li&gt;注册/登录/忘记密码等基础流程；&lt;/li&gt;
&lt;li&gt;多因素认证（MFA）、WebAuthn、第三方登录；&lt;/li&gt;
&lt;li&gt;会话管理、切换设备、注销策略；&lt;/li&gt;
&lt;li&gt;管理后台与自建 OAuth Provider；&lt;/li&gt;
&lt;li&gt;审计日志、风控、隐私合规。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;核心实体模型&lt;/h2&gt;
&lt;p&gt;The Copenhagen Book 把身份系统拆成三层，可避免“把所有逻辑塞进 user 表”：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;实体&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;常见字段&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;用户 (&lt;code&gt;users&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;代表真实的个人或组织，提供业务上下文&lt;/td&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;, &lt;code&gt;display_name&lt;/code&gt;, &lt;code&gt;created_at&lt;/code&gt;, &lt;code&gt;banned_at&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;身份 (&lt;code&gt;identities&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;每种登录方式的唯一标识&lt;/td&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;, &lt;code&gt;user_id&lt;/code&gt;, &lt;code&gt;provider_id&lt;/code&gt;, &lt;code&gt;provider_user_id&lt;/code&gt;, &lt;code&gt;created_at&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;会话 (&lt;code&gt;sessions&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;某次登录状态，可跨设备&lt;/td&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;, &lt;code&gt;user_id&lt;/code&gt;, &lt;code&gt;active_expires&lt;/code&gt;, &lt;code&gt;idle_expires&lt;/code&gt;, &lt;code&gt;user_agent&lt;/code&gt;, &lt;code&gt;ip&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;设计关键点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户可以拥有多个身份：邮箱密码、GitHub OAuth、手机号等。&lt;/li&gt;
&lt;li&gt;身份与用户是 1:N；会话与用户也是 1:N。&lt;/li&gt;
&lt;li&gt;会话应该存储 IP、 User-Agent、最近活动时间，便于风控与用户自助管理。&lt;/li&gt;
&lt;li&gt;不要把密码、第三方 token、WebAuthn credential 混入用户表，分开存储才能实现更细的权限与轮换策略。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;凭证存储与秘密管理&lt;/h2&gt;
&lt;h3&gt;密码&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;使用 &lt;code&gt;bcrypt&lt;/code&gt;、&lt;code&gt;argon2id&lt;/code&gt; 等现代算法，设置合理代价（如 argon2id &lt;code&gt;time_cost=3&lt;/code&gt;, &lt;code&gt;memory=64MB&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;永远不要明文存储，也不要“自创盐”，库已经处理。&lt;/li&gt;
&lt;li&gt;存储额外字段：&lt;code&gt;password_version&lt;/code&gt;（调整参数或算法时逐步迁移）、&lt;code&gt;last_rotated_at&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;登录成功后如果检测到 hash 版本过旧，后台静默升级（rehash-on-login）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;OTP 与重置码&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;一次性验证码/重置码使用 &lt;code&gt;uuid&lt;/code&gt; 或 &lt;code&gt;crypto.randomBytes&lt;/code&gt; 生成，存储哈希值（&lt;code&gt;sha256&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;记录 &lt;code&gt;expires_at&lt;/code&gt;、&lt;code&gt;consumed_at&lt;/code&gt;，用数据库唯一约束防止并发使用。&lt;/li&gt;
&lt;li&gt;邮件/短信里不要包含用户 ID，用 token 换取身份。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;JWT/Session Secret&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;密钥统一放在 Secret Manager（Vault、AWS Secrets Manager）；禁用硬编码。&lt;/li&gt;
&lt;li&gt;轮换策略：生成新密钥、让旧密钥在合理宽限期内仍可验证（kid 机制）。&lt;/li&gt;
&lt;li&gt;Server-side session 存数据库或 Redis 时，同步记录 IP/UA，异常时可以踢出。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;注册流程最佳实践&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;前端校验&lt;/strong&gt;：限制密码强度、邮箱格式，但不要过度暴露错误信息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;速率限制&lt;/strong&gt;：邮箱注册/验证码接口需做 Rate Limit + Captcha。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;邮箱验证&lt;/strong&gt;：注册时生成 email verification token，允许账号未验证时有限操作（如仅能登录一次提醒验证）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;邀请制&lt;/strong&gt;：提前创建 invitation 记录，关联 &lt;code&gt;inviter_id&lt;/code&gt;、&lt;code&gt;accepted_at&lt;/code&gt;，控制注册入口。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;事务处理&lt;/strong&gt;：创建用户、身份、发送验证邮件在事务中完成，失败时回滚。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;登录流程&lt;/h2&gt;
&lt;h3&gt;基础邮箱+密码&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;返回错误时统一提示“凭证错误”，避免区分“用户不存在”或“密码错误”。&lt;/li&gt;
&lt;li&gt;支持 remember me：&lt;code&gt;idle_expires&lt;/code&gt; 设置为 7-30 天，并通过 httpOnly + Secure Cookie 存储会话 ID。&lt;/li&gt;
&lt;li&gt;登录成功写入审计日志（&lt;code&gt;login_success&lt;/code&gt;），包括设备指纹。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;多因素认证（MFA）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;支持 TOTP（Authenticator App）和备份代码（Backup Codes）。&lt;/li&gt;
&lt;li&gt;TOTP 种子加密存储（可用 KMS 包裹）。&lt;/li&gt;
&lt;li&gt;绑定流程：显示二维码 → 输入 6 位验证码确认 → &lt;code&gt;is_primary_mfa&lt;/code&gt; 标记。&lt;/li&gt;
&lt;li&gt;登录流程：先验证主凭证，再要求 2FA；允许“信任设备”7-30 天，通过 &lt;code&gt;mfa_remember_token&lt;/code&gt; 标记。&lt;/li&gt;
&lt;li&gt;恢复：备份代码一次性使用；客服通道需严格验证身份。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;WebAuthn / Passkeys&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;存储 WebAuthn credential 时记录 &lt;code&gt;credential_id&lt;/code&gt;, &lt;code&gt;public_key&lt;/code&gt;, &lt;code&gt;transports&lt;/code&gt;, &lt;code&gt;sign_count&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;对于 passkey，允许跨设备同步，不要绑定特定浏览器。&lt;/li&gt;
&lt;li&gt;WebAuthn 可作为无密码登录方式，也可作为 2FA。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;第三方登录（OAuth/OIDC）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;创建 &lt;code&gt;oauth_providers&lt;/code&gt; 表记录配置信息（client_id、scopes、redirect URI）。&lt;/li&gt;
&lt;li&gt;将 provider 返回的 &lt;code&gt;sub&lt;/code&gt;/&lt;code&gt;id&lt;/code&gt; 等唯一标识与 &lt;code&gt;identities&lt;/code&gt; 表关联。&lt;/li&gt;
&lt;li&gt;若用户首次使用第三方登录，需要流程：
&lt;ul&gt;
&lt;li&gt;找到与邮箱匹配的用户 → 提示合并或拒绝；&lt;/li&gt;
&lt;li&gt;无匹配时创建新用户（可直接标记邮箱已验证）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;定期刷新 access_token/refresh_token；失效时告知用户重新授权。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;会话管理&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;单点登录/多设备&lt;/strong&gt;：允许多个活跃 session；提供“查看并注销其他设备”功能。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;活跃检测&lt;/strong&gt;：&lt;code&gt;active_expires&lt;/code&gt; 表示 token 有效期，&lt;code&gt;idle_expires&lt;/code&gt; 表示最大不活跃时间。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;注销&lt;/strong&gt;：服务端删除 session 记录，删除 cookie/localStorage；对于 JWT 需维护 denylist 或缩短过期时间。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Remembered Device&lt;/strong&gt;：存储在 &lt;code&gt;trusted_devices&lt;/code&gt; 表，包含指纹、首次信任时间、最后使用时间。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Session Fixation&lt;/strong&gt;：登录成功后一定要 rotate session ID。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;API 设计示例&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;POST /auth/register
POST /auth/login
POST /auth/logout
POST /auth/refresh
POST /auth/mfa/setup
POST /auth/mfa/verify
POST /auth/password/request-reset
POST /auth/password/reset
GET  /auth/sessions
DELETE /auth/sessions/{sessionId}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回格式建议统一：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;session&quot;: {
    &quot;id&quot;: &quot;sess_123&quot;,
    &quot;expiresAt&quot;: &quot;2025-05-01T00:00:00Z&quot;
  },
  &quot;user&quot;: {
    &quot;id&quot;: &quot;user_456&quot;,
    &quot;displayName&quot;: &quot;Alice&quot;,
    &quot;hasMfa&quot;: true
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;错误响应包含 &lt;code&gt;code&lt;/code&gt;（内部追踪）与 &lt;code&gt;message&lt;/code&gt;（给用户），必要时加 &lt;code&gt;metadata&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;code&quot;: &quot;AUTH_INVALID_TOTP&quot;,
  &quot;message&quot;: &quot;验证码无效或已过期，请重试&quot;,
  &quot;metadata&quot;: {
    &quot;remainingAttempts&quot;: 3
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;管理后台与支持流程&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;管理后台应具备：搜索用户、查看身份信息、重置密码、禁用账号、强制注销等能力。&lt;/li&gt;
&lt;li&gt;所有敏感操作（禁用、冻结资金）必须记录审计日志，含操作者 ID 与理由。&lt;/li&gt;
&lt;li&gt;支持团队需要明确定义升级流程：例如社交账号被盗、设备遗失、申诉流程。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;风险控制与检测&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;登录异常检测：同一账号在短时间内不同国家登录、IP 风险评分。&lt;/li&gt;
&lt;li&gt;密码喷射（password spraying）：统计不同邮箱的失败次数，整体限流。&lt;/li&gt;
&lt;li&gt;设备指纹：可以使用指纹库或自定义 UA + 分辨率 + Canvas 指纹（注意隐私合规）。&lt;/li&gt;
&lt;li&gt;风控触发时增加验证因素（Step-up Auth），例如要求验证码或 2FA。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;审计与隐私&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;审计日志表存储：&lt;code&gt;event&lt;/code&gt;, &lt;code&gt;user_id&lt;/code&gt;, &lt;code&gt;session_id&lt;/code&gt;, &lt;code&gt;actor_id&lt;/code&gt;, &lt;code&gt;ip&lt;/code&gt;, &lt;code&gt;user_agent&lt;/code&gt;, &lt;code&gt;metadata&lt;/code&gt;, &lt;code&gt;created_at&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;遵守最小化原则：只收集必要数据，设定数据保留期限（GDPR、CCPA）。&lt;/li&gt;
&lt;li&gt;提供数据导出与删除接口（Data Subject Request）。&lt;/li&gt;
&lt;li&gt;避免在日志里记录敏感信息，如密码或完整 token。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;常见坑位与应对&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;跨域 Cookie 失效&lt;/strong&gt;：确保设置 &lt;code&gt;SameSite=None&lt;/code&gt; 且 &lt;code&gt;Secure&lt;/code&gt;，以及后台与前端域名一致。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CSRF&lt;/strong&gt;：推荐使用 SameSite Cookie + 双提交 Cookie 或 CSRF Token。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;验证码滥用&lt;/strong&gt;：引入滑块、人机验证服务，或对高危 IP 拦截。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缓存问题&lt;/strong&gt;：包含授权信息的响应 header 应禁用 CDN 缓存。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;时钟漂移&lt;/strong&gt;：TOTP 校验容忍 ±30s；服务部署多地区需同步 NTP。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;推荐工具与库&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lucia&lt;/strong&gt;：TypeScript 生态下的身份框架，书中示例基于它。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NextAuth.js / Auth.js&lt;/strong&gt;：前端友好的 Next.js 解决方案。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ory / Supabase Auth&lt;/strong&gt;：托管身份平台，适合 MVP。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FGA / CASL&lt;/strong&gt;：做细粒度授权（Access Control）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Temporal / Q Stash&lt;/strong&gt;：处理邮件发送、密码重置等异步任务。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;进一步阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://thecopenhagenbook.com/&quot;&gt;The Copenhagen Book 原文&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://owasp.org/www-project-application-security-verification-standard/&quot;&gt;OWASP ASVS&lt;/a&gt;：认证与会话安全条目。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pages.nist.gov/800-63-3/sp800-63b.html&quot;&gt;NIST 800-63B&lt;/a&gt;：数字身份指南（密码、MFA 建议）。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webauthn.guide/&quot;&gt;WebAuthn Guide&lt;/a&gt;：无密码认证详解。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://auth0.com/blog/&quot;&gt;Auth0 Blog&lt;/a&gt;：实战文章与攻防视角。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>主动与被动</title><link>https://bangwu.top/posts/initiative-passive/</link><guid isPermaLink="true">https://bangwu.top/posts/initiative-passive/</guid><description>在被动与主动之间挣扎，渴望被坚定选择</description><pubDate>Fri, 25 Oct 2024 05:31:56 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;audio controls data-title=&quot;再见（good bye）&quot; data-artist=&quot;G.E.M.邓紫棋&quot; data-cover=&quot;https://cdn.bangwu.top/img/20260111vyqpt-tg.webp&quot;&amp;gt;
&amp;lt;source src=&quot;https://cdn.bangwu.top/img/20260111548a9fcf.mp3&quot; type=&quot;audio/mpeg&quot;&amp;gt;
&amp;lt;/audio&amp;gt;&lt;/p&gt;
&lt;p&gt;算是最近一两年吧，越来越纠结主动与被动的事情，我是一个有点社恐的人，所以大多数情况下关乎与人有关的事，都是秉承着被动的态度：聊天时等着对方先开口，组队时等着被邀请才进队，自己娱乐时也不会叫朋友一起......但是想想这样也有点不好，如果等来的不是主动那就只有沉默。最近在读王小波的《沉默的大多数》，里面提到话语权，说到话语是有力量的，但不幸的是大多数人选择了沉默。&lt;strong&gt;人人有权争胜负，无人有权论是非&lt;/strong&gt;。我在想大多数沉默的人可能和我一样，是在一遍遍确认自己是否被坚定的选择吧。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/20260111a1ba9188.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;网上有句很流行的话“不要试图去改变一个人”，我想这可能也是一个原因，如果本该是选择我的话，那我无需说什么也会被选择，但不是我的话，做什么也是无济于事的。于是，就慢慢的学会了“不说”，慢慢觉得这样其实也不错，不主动不靠近就不会受伤啊。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;克洛伊，我不要你爱我，爱是束缚， 爱要我同样去爱。而我要的是自由。 期待是情感的一笔债务。——《我的心迟到了》&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;说说被动，我被动的接受周围的一切包括世界，无关勇气，只是觉得主动既浪费了我的热情也败了事务的芳华。去年到现在，很长一段时间大家喜欢讨论自己的人格MBTI，最多的就是i人与e人之分，i人就是大多数被动的那个，渴望被坚定的选择。我身边关系特别好的朋友有好几个，但我从来没跟他们说过要坚定的选择我，我不想成为选项，也幸运人与人之间相处都是有一些未说得出口但都懂的话语。却跟过一个网友说过，固定的玩伴需要被坚定的选择，我说如果下次犹豫在两个或多个之间选择的话，那请把我排除，我从来都不是任何选项中的一个，这并不是所有人都需要遵循的，那样在这个时代也有点离谱了。拧巴的人需要被坚定的选择，只要是你，我都愿意。没有你的话，我做不下去。
&lt;img src=&quot;https://cdn.bangwu.top/img/2026011193c2887d.jpg&quot; alt=&quot;Clip_2024-10-25_13-22-24&quot; /&gt;&lt;/p&gt;
&lt;p&gt;不得不说，我很羡慕那些主动的人，虽然我内心还是觉得“主动就是占下风”“主动被拒绝会很伤心”，主动需要的是勇气。有勇气去追寻自己所喜欢的，自己所想的、感到不舒服也会说出来。写到这里我发现我写不出来了，我沉寂了好久，自己不是一个主动的的人，也不会有太多的感受啦，但请敢爱敢恨！&lt;/p&gt;
&lt;p&gt;亲爱的朋友，请不要缺少对一切说再见的勇气。被动与否，请果断一点，大声地说出“再见”&lt;/p&gt;
&lt;p&gt;“那些慢吞吞悲情的音乐 早说过爱过之后就是离别”“不要对我说再见 一句再见 就结束这一切”&lt;/p&gt;
</content:encoded></item><item><title>一文解决python爬虫</title><link>https://bangwu.top/posts/python-crawler-all/</link><guid isPermaLink="true">https://bangwu.top/posts/python-crawler-all/</guid><description>总结python爬虫各种方法收益和支出，提供一些优化思路</description><pubDate>Fri, 25 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;爬虫&lt;/h2&gt;
&lt;p&gt;无论是做科研还是做开发者都需要一个数据基础，有时是图片、音频，又有时是一张表格、一句评论。但凡是所有能在互联网上触及到的东西，爬虫都能够把它们采集下来，整理成你需要的数据。这里就不过多介绍了，更多介绍请看&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E7%B6%B2%E8%B7%AF%E7%88%AC%E8%9F%B2&quot;&gt;维基&lt;/a&gt;。&lt;/p&gt;
&lt;h2&gt;抓包工具&lt;/h2&gt;
&lt;p&gt;抓包是写爬虫过程中必不可少的部分，目的是获取请求过程的&lt;code&gt;请求URL&lt;/code&gt;、&lt;code&gt;Headers&lt;/code&gt;、&lt;code&gt;请求体&lt;/code&gt;等信息，然后编写程序模拟这个过程，好的爬虫总会是模拟一个真实的人类行为，来进行请求数据。抓包工具我只推荐&lt;a href=&quot;https://reqable.com/&quot;&gt;Reqable&lt;/a&gt;：全平台，特别推荐，我也在用。其他的可以去查查，工具只要顺手就行，达到目的就可以。&lt;/p&gt;
&lt;h2&gt;Python网络请求库&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;requests&lt;/th&gt;
&lt;th&gt;aiohttp&lt;/th&gt;
&lt;th&gt;httpx&lt;/th&gt;
&lt;th&gt;pycurl&lt;/th&gt;
&lt;th&gt;curl_cffi&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;http2&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sync&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;async&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;websocket&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fingerprints&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;speed&lt;/td&gt;
&lt;td&gt;🐇&lt;/td&gt;
&lt;td&gt;🐇🐇&lt;/td&gt;
&lt;td&gt;🐇&lt;/td&gt;
&lt;td&gt;🐇🐇&lt;/td&gt;
&lt;td&gt;🐇🐇&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Python常用的网络请求库如上所示(来源于&lt;a href=&quot;https://github.com/lexiforest/curl_cffi&quot;&gt;curl_cffi&lt;/a&gt;)，其中最常用的是requests，可能是api比较方便吧。&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;psf/requests&quot;}&lt;/p&gt;
&lt;p&gt;从上表可以看出，支持异步的有httpx和curl_cffi，pycurl不推荐。httpx被称作为是下一代python网络请求库，我也非常推荐日常爬虫使用这个库，因为用它写异步特别方便。&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;encode/httpx&quot;}&lt;/p&gt;
&lt;p&gt;一个httpx异步程序可以是这样的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import asyncio
import httpx
import time


start_time = time.time()


async def get_pokemon(client, url):
    resp = await client.get(url)
    pokemon = resp.json()

    return pokemon[&apos;name&apos;]


async def main():

    async with httpx.AsyncClient() as client:

        tasks = []
        for number in range(1, 151):
            url = f&apos;https://pokeapi.co/api/v2/pokemon/{number}&apos;
            tasks.append(asyncio.ensure_future(get_pokemon(client, url)))

        original_pokemon = await asyncio.gather(*tasks)
        for pokemon in original_pokemon:
            print(pokemon)

asyncio.run(main())
print(&quot;--- %s seconds ---&quot; % (time.time() - start_time))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note&lt;/p&gt;
&lt;p&gt;需要注意的是，异步请求之所以快是因为网络阻塞，如果网络阻塞很小的话（例如进行本地请求）使用异步反而会减速。这样的话使用多线程会更快&lt;/p&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;curl_cffi是一个可以模拟浏览器指纹的请求库，官方宣称速度比httpx还快，我还没有尝试，毕竟还没遇到与浏览器指纹拦截相关的问题，速度的话httpx已经非常快了并且curl_cffi的普及度好像很小，感兴趣的可以去了解了解&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;lexiforest/curl_cffi&quot;}&lt;/p&gt;
&lt;p&gt;还有一种最万能的爬虫方案：使用selenium或者playwright这两个web自动化框架，如果都没接触过推荐学习playwright。下面是一个大神的国内社交媒体的爬虫，使用playwright实现，非常强大！&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;NanmiCoder/MediaCrawler&quot;}&lt;/p&gt;
&lt;h2&gt;Python网络解析库&lt;/h2&gt;
&lt;p&gt;网络解析有几种方法，主要是解析HTML，因为json格式很好读取，解析也很方便。&lt;/p&gt;
&lt;p&gt;:::note&lt;/p&gt;
&lt;p&gt;json解析的话，除了python内置的json库，还可以用性能更高的ujson库，之前读取一个2GB多的json文件用内置库很卡，ujson很快&lt;/p&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;而解析HTML格式的话，需要进行标签和属性匹配，得到具体想要的数据，例如图片链接、表格数据等。常用的解析方式有正则(re)、BeautifulSoup(bs4)、Xpath(lxml) 三种方式，其中BeautifulSoup和Xpath适合简单地解析，正则用来提取出特定规则的数据。以下提供这三个库的简单的api调用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# re https://docs.python.org/zh-cn/3.12/library/re.html
import re
 
line = &quot;Cats are smarter than dogs&quot;
 
matchObj = re.match( r&apos;(.*) are (.*?) .*&apos;, line, re.M|re.I)
 
if matchObj:
   print &quot;matchObj.group() : &quot;, matchObj.group()
   print &quot;matchObj.group(1) : &quot;, matchObj.group(1)
   print &quot;matchObj.group(2) : &quot;, matchObj.group(2)
else:
   print &quot;No match!!&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# BeautifulSoup   pip install bs4
from bs4 import BeautifulSoup
# 读取示例 HTML 文件
html = &quot;&quot;&quot;
&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;title&amp;gt;Example Page&amp;lt;/title&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;h1&amp;gt;Hello, World!&amp;lt;/h1&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&quot;&quot;&quot;
 
# 创建 BeautifulSoup 对象，并指定解析器为 lxml
soup = BeautifulSoup(html, &apos;lxml&apos;)
 
# 找到第一个 h1 标签，并输出其文本内容
h1 = soup.find(&apos;h1&apos;)
print(h1.text)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# Xpath   pip install lxml
from lxml import etree
text = &apos;&apos;&apos;
&amp;lt;div&amp;gt;
    &amp;lt;ul&amp;gt;
         &amp;lt;li class=&quot;item-0&quot; id=&quot;id1&quot;&amp;gt;&amp;lt;a href=&quot;link1.html&quot;&amp;gt;first item&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
         &amp;lt;li class=&quot;item-1&quot;&amp;gt;&amp;lt;a href=&quot;link2.html&quot;&amp;gt;second item&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
         &amp;lt;li class=&quot;item-inactive&quot;&amp;gt;&amp;lt;a href=&quot;link3.html&quot;&amp;gt;third item&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
         &amp;lt;li class=&quot;item-1&quot;&amp;gt;&amp;lt;a href=&quot;link4.html&quot;&amp;gt;fourth item&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
         &amp;lt;li class=&quot;item-0&quot; id=&quot;id2&quot;&amp;gt;&amp;lt;a href=&quot;link5.html&quot;&amp;gt;fifth item&amp;lt;/a&amp;gt;
     &amp;lt;/ul&amp;gt;
 &amp;lt;/div&amp;gt;
&apos;&apos;&apos;
html = etree.HTML(text)
print(etree.tostring(html))
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;数据保存&lt;/h2&gt;
&lt;p&gt;简单的数据保存推荐使用&lt;code&gt;pandas.DataFrame&lt;/code&gt;将需要的字段以二维表的方式存储起来，然后选择输出文件类型(csv, xlsx, pickle)&lt;/p&gt;
&lt;p&gt;因为爬取过程中会出现反爬等原因导致请求不到数据，然后就会需要耗费时间将之前的请求再请求一遍或者调整代码之类的，如果是单文件的话就无法将之前的数据保存为文件，无法持久化数据，使用jupyter notebook可以解决此问题但我觉得还不是最佳方案，我认为可以使用sqlite数据库然后用ORM库 SqlModel来映射，这样就可以持久化数据而且使用强大的SQL来查询、筛选数据。&lt;/p&gt;
&lt;h2&gt;框架&lt;/h2&gt;
&lt;p&gt;python爬虫框架最常用的是&lt;code&gt;Scrapy&lt;/code&gt;，使用框架的好处是只需要考虑数据解析的问题，其他网络请求、数据保存、错误处理等问题框架都提供了比较完善的方案，再次不多做说明，毕竟用到框架是处理很复杂的爬虫方案的，我目前还没接触到，学习框架也会浪费一定精力。&lt;/p&gt;
</content:encoded></item><item><title>第一篇声明</title><link>https://bangwu.top/posts/firstpost/</link><guid isPermaLink="true">https://bangwu.top/posts/firstpost/</guid><description>棒无的第一篇博客声明</description><pubDate>Fri, 18 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;棒无的声明&lt;/h2&gt;
&lt;p&gt;Hello，这里是棒无.经过大约一年半的挣扎，最终我选择了使用Astro搭建了博客这是我发布的第一篇真正意义上的文章，在此做个声明😐&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;saicaca/fuwari&quot;}&lt;/p&gt;
&lt;h2&gt;我的建站历史&lt;/h2&gt;
&lt;p&gt;记得第一次接触到云服务器、域名、博客等概念还是大一下学期的某一天。那时候刷到一个视频，想着有个云服务器真的好酷啊，于是我打开了阿里云的网站然后看到有个大学生活动，可以免费领到云服务器，虽然配置很低，却还是激动不已。记得第一次使用ssh连接到服务器后，输入&lt;code&gt;sudo apt update &amp;amp;&amp;amp; sudo apt upgrade -y&lt;/code&gt;后终端不断跑出来一行一行进度，虽然是我看不懂的英文，但就像之前使用&lt;code&gt;pip install&lt;/code&gt;那样，真的很爽。后面就去搜搭建博客的教程，现在想想还真是一入深似海啊，按照普及度来说，我不出意外的安装了宝塔面板，使用了WordPress搭建博客，起初配置起来还算简单，但是到后面提供的主题不足以满足我之后，我就去查找WordPress主题，然后安装、定制，又折腾了好长时间，那时像一个无头苍蝇一样到处碰壁。就这样，折腾了小半个月的我最终还算是把主题什么的都配置好了，但是我突然发现，我不知道写什么。在后台文章编辑界面写了又写，只写出个&lt;code&gt;print(&apos;hello world!&apos;)&lt;/code&gt;，那时我也没有域名，服务器也要过期了，又快接近暑假，后面建站的事就不了了之了。&lt;/p&gt;
&lt;p&gt;2023年9月23日，我经过折腾了几天后，正式用上了&lt;a href=&quot;https://vitepress.dev/&quot;&gt;Vitepress&lt;/a&gt;搭建了&lt;a href=&quot;https://github.com/markbang/knowledge/commit/f4a41c46a78b2ee0e35a7eff0121024a5c9b22a4&quot;&gt;知识库&lt;/a&gt;后面就有一搭没一搭地写着学过的知识，但是我发现个问题，我折腾这些静态框架，自定义配置，然后浪费了很多时间，配置完成后也没有去持续输出文章知识，大部分时间都花在了折腾上面。然后就到了后来，接触到了&lt;code&gt;vercel&lt;/code&gt;、&lt;code&gt;vue&lt;/code&gt;、&lt;code&gt;react&lt;/code&gt;、&lt;code&gt;astro&lt;/code&gt;、&lt;code&gt;tailwindcss&lt;/code&gt;、各种UI库等等，但全是浅尝辄止，没有坚持深入下去。&lt;/p&gt;
&lt;p&gt;gap一年修复自己后，2024年9月开学，我准备真正做点什么，想来想去准备写一些文章，虽然之前也有写过一些，但是都不算真正意义上的技术博客。起初因为学过一点前端，想自己手搓一个博客的，后来还是放弃了，我写不出精美的排版🥲然后，就没然后了，我开始真正写博客了，这让我想起了之前看过的Anthon Fu大佬的文章，&lt;a href=&quot;https://antfu.me/posts/about-yak-shaving-zh&quot;&gt;Yak Shaving&lt;/a&gt;，不要想那么多，先聚焦一件事干好！&lt;/p&gt;
&lt;h2&gt;我的信息流&lt;/h2&gt;
&lt;p&gt;说到信息流就不得不提到RSS了，我自从接触到RSS就一直在感叹其强大，可以通过&lt;a href=&quot;https://now.bangwu.top/rss.xml&quot;&gt;now&lt;/a&gt;订阅关于我的互联网活动&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/my-media-network.webp&quot; alt=&quot;my-media-network&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;内容输出&lt;/h2&gt;
&lt;p&gt;我学习的主要是全栈偏向后端，Python、Go、React以及一些运维的知识，很杂但我保证质量很高，不会敷衍任何一篇文章，就这样了。&lt;/p&gt;
</content:encoded></item><item><title>人太多大部分是漫无目的地走</title><link>https://bangwu.top/posts/butaire/</link><guid isPermaLink="true">https://bangwu.top/posts/butaire/</guid><description>从乡村到城市的成长独白：现实、自卑与漂泊</description><pubDate>Tue, 01 Oct 2024 16:17:50 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;audio controls data-title=&quot;东京不太热&quot; data-artist=&quot;一北玖&quot; data-cover=&quot;https://cdn.bangwu.top/img/20260111vl8yk-rw.webp&quot;&amp;gt;
&amp;lt;source src=&quot;https://cdn.bangwu.top/img/2026011123a157f9.mp3&quot; type=&quot;audio/mpeg&quot;&amp;gt;
&amp;lt;/audio&amp;gt;
“没人给你过生日吗？”朋友在一次闲聊中这样问我。我心头一颤，回过神来才想着自己已经19岁了，到底多久没有过上一个正式的生日了。“身边大家都不互相庆生，就不是很重视喽”我应付地回到。记得那时我还在上小学，破天荒的过上了生日，姥姥和舅舅竟然到我家来还给我带了蛋糕，那时我没想过以后过不上这样的生日，那时只有开心。小时候的快乐很简单，大概是五毛钱的辣条，开心就大声笑，难过就表现在脸上，悲伤就哭出来，从不畏惧表达。大概是当时有父母的撑腰或是儿时的单纯吧，小时候的父母还有哥哥在我心里都很强大，强大到可以满足我能想到的任何事情。不知从哪段时间起，我慢慢了解到了“现实”，它告诉我这个世界的一切都是有价格的，没有足够的经济就无法去做到某件事情，起初我不相信，我把心爱的玩具赛车拿去学校和同学们一起比试速度，看着他随意一跑的距离就以是我的极限，“这是我爸爸从外地买的”这句话从我耳边飘过，慢慢的，我意识到童年也是有价格的。大多数同学选择了沉默不语，我也是其中的一员。后来心里就慢慢种下了自卑的种子，和别人聊天表达自己的观点时，我会感觉到很不自在。“我觉得粒上皇的板栗比较好吃，你觉得呢”，“还行吧”我不敢说我没吃过，可能那样我认为比别人落后了。我总是这样，一边认清了现实却又不敢去接受，就这样把自己封闭起来，等着哪天我吃上了各种店的板栗再去说我的观点。&lt;/p&gt;
&lt;p&gt;就这样，我是村里长大的孩子，小学和初中都算是在农村上的吧，条件并不是很好，好在当时要争一口气，考上了镇上普高的小班，当时可高兴了，我觉得终于可以有机会改变自己的命运了。全家很重视我的学业，在上高中的三年基本是我说东就是东，妈妈可以为了我很早起来去买早饭。高中的同学家境基本都很好，都算是小康吧（因为小班可以托关系进）当时和他们相处，我就慢慢忘却了我是乡下走出来的。在这期间，和初中同学交谈，我多了一份虚无缥缈的自信。三年过的很快，又因为疫情的原因，在学校的日子大概也就两年，现在想想当时是真好啊，家人就在身边，我也不需要考虑现在要考虑的各种问题，只知道把每一场考试都做好就行了。初中的时候成了班里的活宝，没想到高中时也是，他们给我起了个“6班人气王”这个称号，我觉得能给别人带来快乐很有意义，所以基本能接受各种无下限的玩笑。高考完之后，我想我终于能去别的地方见见世面了，于是我把志愿填在了繁华的上海，想着这下我就算是“有见识”了吧。随着迈入大学的大门，哥哥在门外目送我，我回了一下头看着他，好像在说“我可以的！”。&lt;/p&gt;
&lt;p&gt;再回头，我已经大三了。所以成长带给我的是什么呢，我感觉自己好像还是从前的那个小孩。喜欢把自己封闭起来，懦弱，不敢交流，以他人为中心。上了大学后，我经常在深夜emo，多是因为现实和自己的理想不一样吧。过了那么长的时间，我并没有学会好好学习，也没有谈一场轰轰烈烈的恋爱，就只是经常发呆有一搭没一搭的想着事情。又会因为某一瞬间想起以前生活里的某一时刻、一个地方或是一个人而纠结到半夜睡不着，或是白天因为别人的一句话而悲伤不已，但这些似乎都已经成为了我生活的一部分，我像疯狂嗑药一样贪婪地令这一切发生。我觉得我应该只能活在偶然间的回忆里吧。小时候想去大城市看看，长大后来到上海站在外滩的围栏边，我抬头望着对岸的CBD心中却没有一丝波澜，这些像是我的一个老朋友，经常在我的幻想里出现，长大就是可以活在大城市吗？&lt;/p&gt;
&lt;p&gt;直到某一天过年走亲戚，不再跟着我爸一起去，而是我和我哥。那天我、我哥和表哥，我们一起喝了个烂醉，哥哥还在喝酒时一直提醒我别喝那么多。那天喝了好久，久到忘却了时间，只知道要起身离开的时候天色已有点昏暗，我还是没能表达出我沉溺已久的想法，我想是喝的不够多吧。回去的时候坐在哥的车里，我坚持说我喝的不多没事，眼睛望着窗外，看着外面的车流，我知道自己再也变不回那个单纯的小孩了。
我总是和身边朋友打趣道“这也太搞了吧”“抽象完了，孩子”.......却只敢在喝酒后与朋友说说心里话，讲讲我的经历，谈谈我的感受，但还是吞吞吐吐说了半天才说出“加油”。不知道从什么时候起，我开始渴望喝醉，好像只有那时我是真真正正的我，却还是有只无形的手拉着克制我去表达。别人总说我情绪稳定，但生活中我总是阴晴不定，欠缺表达就总是平淡。朋友无聊的问我“要去干啥”我总是回“我也不知道”，但其实我想说就这样陪着我就好，只要和你一起干啥都行。&lt;/p&gt;
&lt;p&gt;我也有自己的理想，也有不切实际的期待，想去更远的远方，也有自己生活的节奏，晚上吃完饭没事的时候会偶尔去公园散步，戴上耳机听着歌发呆，或是跑步就好。喜欢安静和规划，但又因为随性做一些不太正常的事情。我知道自己不是什么有天分的人，也不是很努力的人，每次上课都害怕轮到我发言，我总是害怕在很多人面前说话。非常羡慕那些能在人群里侃侃而谈的人，但自己又很难成为他们。我警惕所有令我迷恋的事物，以防止失去后的痛楚。抑郁就像你玩一个游戏太长时间，已经厌倦了，然后就退游了，但是人生这场游戏你不能退游，就只能抑郁了。我想：只要我用最真挚的感情去演奏接下来的人生就好了吧。
“人太多大部分是漫无目的地走，听着歌想念一个人心痛也不说...”&lt;/p&gt;
</content:encoded></item><item><title>ShareX-截图webp格式</title><link>https://bangwu.top/posts/sharexmd/</link><guid isPermaLink="true">https://bangwu.top/posts/sharexmd/</guid><description>ShareX截图自动转webp的配置与压缩步骤</description><pubDate>Wed, 03 Apr 2024 13:44:01 GMT</pubDate><content:encoded>&lt;p&gt;ShareX是一个非常强大的开源截图并分享软件，提供了非常多的自定义功能，我就是看上的它的可以自动压缩截图图片为webp格式，大大降低的图片大小。软件结构：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/20260111afcd0562.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;下载地址：&lt;a href=&quot;https://github.com/ShareX/ShareX/releases/tag/v16.0.1&quot;&gt;https://github.com/ShareX/ShareX/releases/tag/v16.0.1&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;建议下载portable版，自带ffmpeg，可以直接用来压缩&lt;/p&gt;
&lt;p&gt;然后在 sharex 的动作设置中，添加一个动作：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/20260111cba8efe3.webp&quot; alt=&quot;chrome_1712151713&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;名称: 随意&lt;/li&gt;
&lt;li&gt;文件路径: ffmpeg 的安装路径，要指定到 ffmpeg 可执行文件&lt;/li&gt;
&lt;li&gt;参数: &lt;code&gt;-i &quot;$input&quot; -q 75 &quot;$output&quot;&lt;/code&gt; 其中 -q 75 是以75%质量压缩&lt;/li&gt;
&lt;li&gt;输出文件扩展名: webp&lt;/li&gt;
&lt;li&gt;扩展名筛选: png&lt;/li&gt;
&lt;li&gt;勾上下面的 隐藏窗口 和 删除输入文件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;然后在截图后的任务里勾选上&lt;code&gt;执行操作&lt;/code&gt;就OK了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.bangwu.top/img/2026011191c681ec.webp&quot; alt=&quot;chrome_1712151800&quot; /&gt;&lt;/p&gt;
</content:encoded></item></channel></rss>