标签归档:开发经验

从“代码补全”到“全托管 Agent”:我的 2025 AI Coding 进化论

本文有一个 Online 的 Sample 版本。如果你对于「我具体是怎么做的」感兴趣,可以直接看这个 Twitter Threads

2025 年,我的工作习惯彻底被 Claude Code / Cursor / Codex 改变了;这一年给我带来的改变,不亚于当年我用易语言写出第一个应用程序的时候。这句话不仅适用于我的产品经理身份,同样适用于我的工程师身份。这不仅是软件开发效率的提升,更是软件开发方式的重构。

AI Coding?到底是什么?

talking past each other

在社交媒体上讨论 AI 编程的时候,很多时候大家其实没有对齐在讨论的 AI 编程的范畴和适用领域,使得大部分时候,大家在鸡同鸭讲,公说公有理,婆说婆有理。所以,在我们真正讨论 AI 编程之前,我们需要先澄清一下,我们到底在讨论什么「AI Coding」?

different levels

目前来说, AI Coding 有几种不同的产品形态和用户交互形态,他们包括:

  1. L1:古典的直接问 ChatGPT:有问题直接问 ChatGPT、Gemini、Claude 等 AI 助手,借助 AI 助手给出的信息,自行 Debug 和修改代码。
  2. L2:使用 IDE / 插件中提供的补全功能:直接写函数名,然后 AI 会帮你补全函数的细节,你再自己微调或者不微调。这种能力其实过去也有,只是没有这么强悍。大家使用 IDE 来开发,很大程度上就是 IDE 提供了极好的补全能力。
  3. L3:使用本地的 AI Coding 工具的 Agent 模式,全托管或半托管式的编程:你只描述需要做什么,具体做动作由 AI 来完成;这里还有几个细分的方式,包括完全托管(比如 claude code 开 –dangerously-skip-permissions ; codex 开 –dangerously-bypass-approvals-and-sandbox)和半托管(用户手动确认行为,只是由 AI 来完成具体修改的动作)。L3 操作的是你的本地环境,有破坏的风险,但也有无限的可能。
  4. L4:使用网页端的 AI Coding 工具,直接出 Demo:你不需要在本地有任何开发环境的配置,直接在网页端使用一个应用,来完成 Coding,你只需要描述你想要的东西,剩下的完全交给 AI(虽然这往往意味着难以与复杂的本地业务流集成)。

大家在社交媒体上和别人讨论 AI Coding 的时候,需要注意,很有可能你在聊的是 L3 ,但别人在聊的是 L4 ;你们看似聊的都是一个东西,但实际上是完全不同的东西。我们这篇文章讨论的,则主要是 L3 —— 即使用本地的 AI Coding 工具的 Agent 模式,全托管或半托管式的编程。

到底谁在用 AI Coding 工具的 Agent 模式?为什么是 Agent 模式?

说完了大家眼中不同的 AI Coding 工具,我也必须再强调一下,即使大家聊的都是【AI Coding 工具的 Agent 模式】,因为身份的不同,每个人的用法也会有很大差异,大家对他的预期也完全不同。因此,不同的人对其的评价也千差地别:其中大体也可以拆分为三个大类:

different groups of people
  1. 传统的软件工程师:使用 AI Coding 工具完成自己工作过程中的一些辅助性工作,对于自身的能力和工作的要求有较高的要求。
  2. 有一定研发概念的产品工程师(称之为产品工程师是因为他们有一些基础的软件工程概念):使用 AI Coding 工具拓展自己的能力圈,去做一些之前必须依赖软件工程师才能做完的事情(比如做个小 Demo,或是上线一个软件产品)。
  3. 被媒体上的文章忽悠进来的小白(他们基本上没有太多的软件工程经验和基础):跟着网络上的信息了解到了 Coding Agent,然后尝试在不同的软件上使用(不局限于 Claude Code,Cursor 的 Agent 模式也算),经常会卡在一些软件工程的基础问题上。(我特别推荐这类人去看《计算机教育中缺失的一课》,看完以后,会让你很快理解你所遇到的问题,并能够快速处理 AI Agent 所给你的信息,进行下一步操作)。

这三类人因为工作的要求、对于软件工程的理解的不同,会导致他们使用 AI Coding 工具的 Agent 产出完全不同的体验,从而有不同的评价。

而之所以是 Agent 模式,是因为相比于补全模式需要你现有一段代码;Chat 模式往往不指引你完成所有的工作或需要你现有互联网软件的基础才能完成工作;Agent 模式自带的环境操作能力,使得你即使完全不懂 AI 和 Coding,也可以做一个像模像样的小应用出来。这打破了过去需要软件工程师才能做出一个 Demo 的限制,同时也极大的鼓励了新手小白,试着去做一些有意思的事情。

Agent 模式也是 AI Coding 最出圈的一个 Feature;

我和 AI Coding 工具的 Agent 模式的缘分

在这条上,我提到,我其实经历过三种不同的状态,这源自于我过去一年的体验。

在过去这一年里,我从一个「以补全为主,抗拒 AI Coding Agent」的工程师,转变为了一个「好好利用 AI Coding Agent」的工程师。这其中,离不开我身边的朋友们和小伙伴们,和他们的协作,让我真的意识到了 AI Coding Agent 的价值。

这里要感谢刘超、杨鼎睿、岳增五,和你们的协作让我极大的改变了自己对于 AI Coding 的认知和看法。

作为一个写了十数年代码的工程师,我拥有一些自己平时写代码的脚手架,可以帮助我快速完成一个项目,而同时,作为工程师的自我要求,我希望我自己写的代码能够拥有不错的代码质量和性能,从而可以提供一个稳定的软件质量。所以在一开始,我对于 AI Coding Agent 的能力是持怀疑态度的,虽然依然会使用,但整体信任度没有那么高,往往只让它处理细枝末节,基本上是让 AI 去写一个完整的功能的细节,或者是让 AI Coding Agent 去进行 Code Review,而不是让他去动业务代码;直到 Claude Sonnet 3.7 的时候,我才发现 Claude Code 已经能完成不少的工作,甚至很多小的 Feature,不再需要我先行规划,再让他自己去做细节,我可以非常坦然的交给他一些功能,让他自己去完成我只做关键验收。

而从这个时刻开始,我对于 AI Coding Agent 的使用开始与日俱增,我开始使用 AI Coding 去完成越来越多的功能和效果,然后,我开始对 AI Coding 放权;开启了 --dangerously-skip-permissions ,让 Claude Code 自己去写代码,去实现效果;

也和所有的使用 AI Coding Agent 的人一样,我也经历过 AI Coding 工具将代码整的一团糟,然后放弃的时刻。不得不说,Claude 有些时候的过度设计,真的让人觉得无语。或许这是最佳实践,但最佳实践不仅仅要配合着实践用,也要配合着项目的时间节点和周期,以更好的完成项目的目标 —— 你要记得,Coding 只是完成目标的手段,而不是目的。

不过,随着对于 AI Coding Agent 的逐步使用的深入,我对于 AI Coding Agent 的使用越来越了解也越来越多的在不同的场景下去使用 AI Coding Agent ,去完成自己的工作。

如今的我对于 AI Coding Agent 模式的观点

首先,我旗帜鲜明的说所有的软件工程师都应该试着使用 AI Coding Agent,放弃、逃避、蒙头装鸵鸟都是没有意义的,熟练掌握 AI Coding Agent 已不再是加分项,而是生存技能。 AI Coding Agent 正在切实的改变着我们的行业。诚然,AI Coding Agent 不会影响我们这些「老」工程师的工作,但这个问题如同被熊追着我们一样 —— 淘汰你的从来不是熊,而是比你跑得更快的人。 AI 不会淘汰工程师,但会淘汰那些拒绝进化的“手动操作员”。优秀的工程师不会消失,我们的职责正从“手写逻辑”转变为“选择方案、设计架构、验收质量”。

其次,我依然相信大家的软件工程经验是有价值的,就像上面我提到,最佳实践本身没有问题,但每个人所面对的项目的节奏是完全不同的。软件工程师的价值从手写代码,变为了选择 AI 提供的解决方案,Review AI 提供的解决方案,可以帮助我们更好的完成自己的工作。优秀的软件工程师只是工作职责变了,但他们不会消失,甚至会获得更多的加成

我现在怎么用这些 Coding Agent?

我目前自己同时在使用的包括 Claude Code、OpenAI Codex、和 Gemini Cli;其中前两者是我自己花钱买的,后者是 GDG 赠送的。

我强烈建议大家自己花钱购买。如果公司买了最好,如果公司没买,自己买也是划算的。 Claude Code 5X 已经满足日常使用了,如果你用量大,再升级 Claude Code 20X 即可;$100 其实就是大家聚餐吃一顿价格,并不贵,但给你带来的提升是远超这个价格的。

我对于 Coding Agent 的使用会包含两块:

  1. 本地使用
  2. 云端使用

以及,一些质量守卫。

本地使用

本地使用时,我会使用 Claude Code 的 Plan Mode 来设计一些需求,通过和 Coding Agent 的几轮交互,约束需求的范围,并让 Claude Code 去完成相关的功能;

同时,我会使用 Codex 来为我的项目补全测试 —— 过去我自己很多时候懒得写测试,现在有了 AI 来帮助我们完成测试的基础覆盖,大大提升了我的效率。或许 AI 无法保证测试的如一个专业的测试同学,但却可以让我先完成从 0 到 1 的建设,让我自己可以变的更好。

Gemini Cli 则会在一些时候,我会让其作为项目的补充,主要也是 GDG 赠送的 Plan 额度有限,所以用的不多。

之所以会同时使用三个不同的 Coding Agent,主要还是考虑到模型本身的能力是有差异的;不同的模型可以给我提供不同的视角,从而可以确保对于一个问题有更全面的思考,规避可能的思维漏洞。

云端使用

除了本地的使用,我还会在云端使用 AI Coding Agent,但并不是使用网页端的 Coding 工具,而是在 CI 工具链中集成 AI Coding Agent,使用 AI Coding Agent 对于我的每一次提交、每一次 PR 进行 Code Review ,通过这样的方式,帮助我更好的发现代码中的问题,既可以确保自己对于问题的思考没有漏洞,同时可以进一步的发现自己的经验不足,补全对于问题的思考维度。

特别是,集成多个 Coding Agent 可以明显看到不同 Coding Agent 对于问题的思考角度和深度不同,对于自我提升非常有帮助。

质量守卫

由于AI Coding Agent 本身存在的问题,往往会在大规模编辑代码的时候,存在一些编辑错误、无用代码、不符合规范的问题,因此我在深度使用 AI Coding Agent 的项目中,都会引入大量的质量守卫,来确保不符合规范的代码无法提交到云上。我们应当保持对 AI 的信任,但必须坚守 「信任,但要核实 (Trust, but Verify)」 的原则,确保 AI 提交的代码每一行都可信、可控。

flowchart

具体包括:

  1. 引入静态检查的准入机制,扼杀低级错误:借助 git hook,在提交前进行 code format
  2. 引入质量红线,控制确保逻辑不出错,没有改错之前的代码:借助 git hook 和单元测试围栏,要求提交到代码库中的代码必须测试覆盖率满足要求(强 AI Coding Agent 介入的项目会要求测试覆盖率到 100%;并包含集成测试和端到端测试)。
  3. 引入 AI Agent 的自愈能力,闭环问题的修复:借助 git hook 和一些 linter 工具,发现代码中的问题,并让 AI Coding Agent 自己去修复他。
  4. 引入代码复杂度分析工具,让 AI 写出人和 AI 都易于维护的代码:常见的编程语言基本上都提供了圈复杂度检查工具,你可以在你的项目中引入圈复杂度检查工具,让 AI 提交之前确保所有代码的复杂度不会太高,从而既可以借助 AI 的能力快速迭代,同时又保留自己随时介入的可能性。

当你有充足的围栏检查的时候,你就可以放心让你的 AI 去完成工作,并要求他自行提交 commit,这样可以让其自动执行 git hook ,并修复 git hook 中所配置的工具检查出来的问题,从而确保提交到代码库中的代码不会有一些基础问题。

如果你还没有开始使用 AI Coding Agent ,你要怎么开始(工程师篇)

如果你是一个工程师,想要试试 AI Coding Agent ,但不知道怎么做?

  • 我能理解你对于代码质量的要求和你的工作对于你的要求;
  • 我也知道你担心引入 AI 导致你的项目复杂度快速提升,无法维护,最后导致项目彻底崩盘,你反而被干掉;

所以,我推荐你这么干:

  1. 在现有的 CI 工具链中加入 AI Coding Agent 进行 Review,从而让 AI 先从 Review 代码开始,给你提供更多的建议,帮助你先变成一个更好的工程师;
  2. 在习惯让 AI Coding Agent 参与到你的工作流里后,可以先让 AI 帮助你补全测试,建立起单元测试围栏,从而让你放心的使用 AI Coding Agent 参与到你的项目里;
  3. 使用 AI Coding Agent 完成你的工作(记得加入各种围栏),让你自己获得进一步的解放。

当你完成了上述三步后,你已经熟悉了 AI Coding Agent,就可以除了做一些你熟悉的工作,还可以让他带着你,去做一些你所不熟悉的事情,比如你是前端,让他带着你搞后端;或者你是后端,让他带着你搞 iOS。

总结

到了 2026 年,你依然可以 Happy Coding,不过你要学会意识到,我们除了做体力劳动的 Coding,我们更应该用好 AI Coding 工具,将体力劳动托管给 AI Coding Agent,让自己的工作越来越具有「创造性」,做一个「创造者」。

如何在 Google Analytics 4(GA4) 查看 Referer URL ,获取来源地址

作为统计站的第一,这个 Blog 也挂了 GA 4 作为统计。如果你希望知道是谁在推荐你的 Blog,一个很好的办法是查看 HTTP 的 Referer 的 URL,来判断哪些人在哪些地方推荐了你。

不过 Google Analytics 在升级到 GA4 之后,查看 Referer  变得麻烦了不少,没办法直接通过预置的看板来查看。这篇文章就是帮你找回丢失 Referer URL。

具体操作步骤

一、登录 GA 4 ,找到你的站点;点击左侧的「探索」,进入到探索页面。在探索页面点击「空白」,来创建一个新的探索看板

    image

    二、新的探索页面,选择维度这里,新增两个维度和一个指标

    维度:网页引荐来源网址网页位置

    指标:新用户数

    image

    三、将网页位置和网页引荐来源网址配置到设置中的行,且顺序为网页位置在先,网页引荐来源网址在后;显示行数设置为 500;新用户数配置到设置中的值当中;设置过滤器为网页引荐来源网址包含 //

    image

    四、配置完成后,你就可以看到类似我这样的界面了,在这个页面里,你就可以看到不同的来源给你带来了多少流量;从而进一步的去和对方沟通~

    image

    django-storages 配置使用 S3 Provider 支持 Aliyun OSS 使用

    阿里云 OSS 提供了 S3 的兼容,所以如果你在 Django 应用当中,希望使用 OSS 作为文件存储的话,可以参考下方的说明,来使用。

    安装

    首先,你需要执行如下命令安装 django-storages 的 S3 兼容

    # uv
    uv add django-storages[s3]
    # pip
    pip install django-storages[s3]
    Code language: CSS (css)

    配置

    接下来,就是在你的项目文件夹中的 settings.py 中添加如下配置

    AWS_S3_ACCESS_KEY_ID = env("AWS_S3_ACCESS_KEY_ID") # 你在阿里云拿到的 ACCESS_KEY
    AWS_S3_SECRET_ACCESS_KEY = env("AWS_S3_SECRET_ACCESS_KEY") # 你在阿里云拿到的 Secret Key
    AWS_STORAGE_BUCKET_NAME = env("AWS_STORAGE_BUCKET_NAME")# 你在阿里云拿到的 Bucket Name
    AWS_LOCATION = env("AWS_LOCATION") # 你的文件上传路径,比如  uploads/,你的所有文件都会上传到这个路径下
    AWS_S3_REGION_NAME = env("AWS_S3_REGION_NAME") # 你的 OSS 的可用区,比如 oss-cn-beijing
    AWS_S3_CUSTOM_DOMAIN = env("AWS_S3_CUSTOM_DOMAIN") # 你自己的自定义域名,以便于后续访问的时候使用。如果不知道的话,可以填 bucket 的默认域名。
    AWS_S3_ENDPOINT_URL = env("AWS_S3_ENDPOINT_URL") #  你的阿里云 Endpoint URL,比如 https://oss-cn-beijing.aliyuncs.com
    
    AWS_S3_ADDRESSING_STYLE = "virtual" # 阿里云只支持二级域名的形式
    AWS_S3_SIGNATURE_VERSION = "s3" # 阿里云只支持 v2 版的签名逻辑
    
    # 配置默认使用 S3 Storage,即使用 OSS 的 URL
    STORAGES = {
        "default": {
            "BACKEND": "storages.backends.s3.S3Storage", # 使用 S3  Storage
        },
    }
    
    Code language: PHP (php)

    参考上方的配置,添加配置项后,保存,并重启服务器,即可在代码中进行测试。

    测试代码

    你可以执行 python manage.py shell 并执行如下代码,如果无报错,且可以在 OSS 控制台看到文件,则说明你的配置成功了。

    
    from django.core.files.storage import default_storage
    from django.core.files.base import ContentFile
    content = ContentFile(b"Hello World!")
    path = default_storage.save('test_file2.txt', content)
    print(f"文件保存路径: {path}")
    # 测试文件读取
    if default_storage.exists(path):
        with default_storage.open(path, 'r') as f:
            content = f.read()
            print(f"文件内容: {content}")
    # 测试文件URL生成
    url = default_storage.url(path)
    print(f"文件URL: {url}")
    # 测试文件删除
    default_storage.delete(path)
    print(f"文件是否存在: {default_storage.exists(path)}")
    
    Code language: PHP (php)

    一些我自己用的还不错的 Chrome 插件

    英语/日语翻译:沉浸式翻译

    我的日常要看到很多英文文章和网站,因此,可以借助沉浸式翻译,帮助我快速翻译多种语言为中文,从而降低我在阅读不同语言内容的障碍。

    1743588697 image

    快速搜索:超级搜索

    超级搜索支持我快速的选中词汇并进行搜索,对于一些特定的场景下,会非常有帮助。比如我在浏览网页的时候,发现了一部电影,想快速在豆瓣电影中找到并标记,就可以借助超级搜索,配置一个搜索关键词来实现。

    1743588684 image

    SEO 检查器:AITDK

    AI TDK 是我用来检查自己的网页是否完成了一些基本的 SEO 设置的工具。当我上线了一个新的网站后,就会打开 AITDK,然后查看哪里的信息还不完整,需要补充的。就可以继续去补充相应的内容。

    1743588868 image

    JSON 查看:JSON Viewer

    在开发的时候,经常会有要查看服务端返回的 JSON 的情况, 借助 JSON Viewer 可以将不容易看明白的 JSON 给格式化了,方便你快速定位要看的 JSON。

    1743589100 image

    广告拦截:AdGuard

    广告拦截我选择了 AdGuard,有了它,我看 Youtube 再也没有广告了。。。

    1743589301 image

    复制为 Markdown:Copy as Markdown

    因为经常要将部分内容复制为 Markdown,方便在我的其他工具中使用,所以我安装了这个 Copy as Markdown 插件,方便自己随意复制。

    1743589434 image

    页面增强:篡改猴

    当我需要对一些网页做一些快速的改造,但同时又不想写成 Chrome 插件的时候,就会选择写成油猴脚本,然后放在篡改猴里来用,非常方便。

    1743589508 image

    灵感记录:Memos

    我自己部署了一个 Memos ,用于记录自己的灵感和想法。因此,我使用了一个 Chrome 插件,来方便我记录。

    1743589800 image

    记一次发现小宇宙 iOS 版的跳转注入漏洞

    漏洞风险:可以在其他应用借助小宇宙端内跳转任何网页。

    此文章发布前,小宇宙已经修复了这个问题,所以你们可能不能复现这个问题了。

    先看漏洞效果,这个漏洞的问题是你可以在小宇宙里跳转到任何网站,甚至是 PornHub。不过这个 Bug 不重要,重要的是发现 Bug 的过程

    漏洞效果

    0. 背景

    在一天晚上,我在和朋友聊小宇宙的 URL Scheme,想要做个功能,可以实现一键打开小宇宙的节目页面。但卡在我面前的是,我不知道小宇宙的 URL Scheme 到底是什么。于是便开始了我的 Hack 之旅,也就找到了小宇宙的这个安全漏洞。

    1. 找到小宇宙的 URL Scheme

    由于 Scheme 是在 App 中定义的,所以当我想到要找 Scheme 之后,第一反应的是去拿 iOS App 的 Info.plist(因为 iOS 是将 Scheme 定义在 info.plist 当中)。

    过去需要通过 IPA 备份、越狱等方式来获取到这个文件,不过得益于 M 系列支持在 macOS 上运行的原因,现在 IPA 的获得变得非常的简单。 先在 App Store 安装小宇宙,并在「应用程序」中找到小宇宙。

    image

    然后右键点击小宇宙,点击「显示包内容」

    image

    然后看到这样的内容,WrappedBundle 是一个假的应用程序,所以继续点击 Wrapper 往里跳转。

    image

    然后会发现里面还有一个 Podcast 应用,这个才是真正的小宇宙的 IPA 包。然后继续点击「显示包内容」

    image

    在包内容当中可以看到 info.plist 文件,使用 Xcode 或者 VSCode 打开 info.plist 文件。

    image

    info.plist 文件中,搜索 CFBundleURLSchemes ,找到了小宇宙的 URL Scheme(里面有很多个,但很多都是其他应用的,试一下就可以发现):cosmos://

    image

    2. 找到 URL Scheme 能够打开的页面

    找到了 URL Scheme,只能通过 cosmos:// 打开应用首页,无法满足我的需求,于是开始继续寻找可能的 URL Scheme 。一般来说,这个时候就只能继续反编译 IPA 包或者 APK 包了。不过对于我来说,这些不是一个好的选项(成本太高)。

    然后想到,小宇宙的网页似乎是提供了打开客户端的能力,所以可以从网页版找到突破口。通过简单的搜索,果然让我在网页前端找到了突破口。找到了 7 个 Scheme 。

    image

    当然,中间存在一些重复的 Scheme。所以最终梳理出来的 Scheme URL:

    • cosmos://page.cos/discover:打开发现页
    • cosmos://page.cos/shownotes/EPISODE_ID:打开节目的 Shownote 页面
    • cosmos://page.cos/episode/EPISODE_ID:打开节目的详情页
    • cosmos://page.cos/webView?url=:意义不明,看起来像是打开一个特定的 URL
    • cosmos://page.cos/web?url=:意义不明,看起来像是打开一个特定的 URL

    3. 发现问题 URL Scheme

    前面的三个很正常,但后面的两个带 URL 的引起我的注意 —— as a Hacker,你知道的,任何一个可能的输入框都可能成为我们的注入点,于是乎,我就构建了一个链接,来打开我的 Blog

    cosmos://page.cos/web?url=https%3A%2F%2Fwww.ixiqin.com
    Code language: JavaScript (javascript)

    将这个链接使用 Safari 打开,就会自动唤起小宇宙,并打开我的播客。

    至此,我发现了小宇宙这个跳转注入漏洞,并快速将其反馈给小宇宙官方同学。

    复盘:如何规避这样的问题

    在这个 Case 当中,小宇宙因为没有设置 URL 跳转的白名单,导致实际上出现了跳转恶意网站的风险。理论上,作为应用提供商,出于安全合规的视角,最好是控制 URL 跳转的域名和空间,避免被恶意滥用。

    或者也可以参考我们现在见到的很多网站,在外部跳转时加一个风险提醒

    如果在这个 Case 当中,小宇宙在一开始就限制了可以打开有限域名,那也不会出现如今我这次的漏洞问题。

    这个问题风险大么?

    取决于如何定义和如何使用。如果只是跳转一些常规网站,自然是风险不大的。但如果不受限制的,比如跳转到一些诈骗网站,可能风险就是大的。

    如何修改 Advanced Media Offloader 使其可以批量将历史文件上传至对象存储?

    Advanced Media Offloader 提供了 Bulk Offload 功能,可以实现将历史的文件上传到对象存储中,从而降低本地的存储压力,使得站点自身变得无状态。

    但其默认的 Bulk Offload 功能每次只能加载 50 个图片,如果附件太多,则需要点击 N 次,十分麻烦。

    image 6


    不过,可以通过简单的修改,来实现一次上传,将多个图片进行 Offload。

    在 WordPress 后台的插件管理器中,找到 Advanced Media Offloader 插件,并将includes/BulkOffloadHandler.php文件打开,找到其中的 get_unoffloaded_attachments 函数,修改函数定义中的 $batch_size = 50 为你想要的大小即可使其一次批量加载多个文件了。

    image 8
    修改位置

    修改后效果:

    image 7

    WordPress 使用 Caddy 完成静态化缓存

    使用 Caddy 处理 WordPress 当中,我提到在用 Caddy 处理 WordPress,且为了性能做了很多优化。

    我的博客经历了三重优化:最基础的 PHP OpCache + Redis 数据查询缓存 + 静态化缓存。

    其中一个比较有效的,便是在整个站点上加入静态化缓存,绝大多数游客看到的其实是预先生成好的静态页面,从而减少了数据库加载、渲染、计算的成本。

    而想要实现静态化,则需要借助于 Cache Enabler 插件和 Caddy 配置来完成。

    安装插件并启用

    安装 Cache Enabler 插件,并启用插件,启用后,在后台设置中,配置过期时间和对应的清除策略,并保存。这个时候,Cache Enabler 就会自动帮你去生成不同的页面了。

    image 5

    配置 Caddy 路由转发

    首先,你应该在你的 php_fastcgi unix//run/php/php-fpm.sock 前面加入缓存的代码并重启 Caddy,具体如下

    image 4

    缓存配置如下

         @cache {
    		not header_regexp Cookie "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_logged_in"
    		not path_regexp "(/wp-admin/|/xmlrpc.php|/wp-(app|cron|login|register|mail).php|wp-.*.php|/feed/|index.php|wp-comments-popup.php|wp-links-opml.php|wp-locations.php|sitemap(index)?.xml|[a-z0-9-]+-sitemap([0-9]+)?.xml)"
    		not method POST
    		not expression {query} != ''
        }
    
        route @cache {
            try_files /wp-content/cache/cache-enabler/{host}{uri}/https-index.html /wp-content/cache/cache-enabler/{host}{uri}/index.html {path} {path}/index.php?{query}
        }
           
    Code language: JavaScript (javascript)

    这部分配置先定义了一个 @cache 块,用于后续判断,并在其中加入了多种条件判断,说明了不使用缓存的情况:

    • 如果用户有以下 Cookie,就不使用缓存:
      • comment_author(评论作者)
      • wordpress_[a-f0-9]+ (WordPress 的会话 Cookie)
      • wp-postpass(密码保护文章的 Cookie)
      • wordpress_logged_in(登录状态的 Cookie)
    • 如果当前请求命中了以下路径则不缓存
      • /wp-admin/(后台管理页面)
      • /xmlrpc.php(XML-RPC 接口)
      • 所有 wp-*.php 文件(WordPress 系统文件)
      • /feed/(RSS 订阅)
      • sitemap 相关文件
    • POST 请求不缓存(比如评论)
    • 带查询参数的缓存不请求。

    随后,通过 route @cache 定义了命中缓存部分的查找顺序:

    1. 先找 HTTPS 版本的缓存:/wp-content/cache/cache-enabler/{host}{uri}/https-index.html
    2. 再找普通缓存:/wp-content/cache/cache-enabler/{host}{uri}/index.html
    3. 如果找不到缓存,就尝试原始路径:{path}
    4. 最后尝试 PHP 文件:{path}/index.php?{query}

    查看效果

    打开一个无痕窗口,访问你的网站,如果在 html 底部看到 <!-- Cache Enabler by KeyCDN @ Sat, 04 Jan 2025 03:05:34 GMT (https-index.html) --> ,则说明你已经成功启用静态化缓存了!

    参考资料

    silver mercedes benz emblem on blue surface

    使用 Caddy 处理 WordPress

    在用了很久的 Docker 托管 WordPress 后, 近期我把 Blog 迁移到了腾讯云的香港轻量云主机上,以获得更快的访问体验。在这次迁移后,出于 Hack 方便的目的,我将 Nginx 替换成了 Caddy。你目前访问的站点便是一个基于 Caddy 托管的 WordPress 站点。

    安装 Caddy

    安装 Caddy 的过程不需要太过赘述,遵循 Caddy 官方安装文档当中的指南安装即可。

    安装 PHP

    完成了 Caddy 的安装后,则是安装 PHP,这里我使用的是 ondrej 维护的仓库

    sudo add-apt-repository ppa:ondrej/php
    sudo apt update
    Code language: Bash (bash)

    执行上述命令安装 PPA 仓库,就可以继续执行 apt install php 来安装 php & 对应的版本。此外,记得安装相关的依赖包

    apt-get install php-fpm php-mysql php-curl php-gd php-mbstring php-common php-xml php-xmlrpc -y
    Code language: Bash (bash)

    配置 Caddy

    完成安装后,就可以正常来配置 Caddy 。得益于社区的集成和 Caddy 官方的支持,Caddy 配置 WordPress 的支持非常简单,可以直接使用 Caddyfile 格式来撰写。

    example.ixiqin.com { # 这个配置是给 example.ixiqin.com
    
        root * /data/caddy/example.ixiqin.com # 我的网站文件都放在 /data/caddy/xxx 下,/data 是我挂载的数据盘
    
        log { #日志配置
            output file /var/log/caddy/example.ixiqin.com.log  # 日志路径
            format console # 日志格式
        }
    
        request_body { # 请求体大小
            max_size 20MB # 最大 20MB
        }
    
        encode zstd gzip # 支持 gzip 和 zstd 压缩
        file_server # 直接提供静态文件(比如图片啥的)
        php_fastcgi unix//run/php/php-fpm.sock # 使用 php_fastcgi 调用 php-fpm 来处理动态 php 文件。
    }
    Code language: PHP (php)

    只需要这样的配置,你就可以完成一个最基础的 WordPress 的站点的配置。

    其他配置

    对静态文件提供单独的 404 返回

    按照上面的配置,其实所有的请求都会转发给 php-fpm 来处理,从而造成额外的 PHP 资源浪费。因此,可以在配置当中加入如下代码,来让 Caddy 直接返回,从而避免对 PHP 性能的浪费。

    @static_404 {  
      path_regexp \.(jpg|jpeg|png|webp|gif|avif|ico|svg|css|js|gz|eot|ttf|otf|woff|woff2|pdf)$  
      not file  
    }  
    
    respond @static_404 "Not Found" 404 {  
      close  
    }
    
    Code language: JavaScript (javascript)

    配置缓存头

    除了静态文件的 404 处理,你还可以在 Caddy 当中配置静态文件的缓存,从而让浏览器更多的应用缓存,减少服务器的流量,提升加载速度。

    @static {  
      path_regexp \.(jpg|jpeg|png|webp|gif|avif|ico|svg|css|js|gz|eot|ttf|otf|woff|woff2|pdf)$  
    }  
    header @static Cache-Control "max-age=31536000,public,immutable"
    
    Code language: JavaScript (javascript)
    a computer screen with a bunch of text on it

    又准备折腾博客了

    我写博客经历过两个阶段 :

    1. 2012 ~ 2014 年:买虚拟主机、云主机,折腾性能和技术。在这个阶段,我的博客也经历过几轮变迁,也因为我自己没做好生产环境和测试环境的分离,导致数据丢了不少。目前的博客只能回溯到 2016 年便是因此。
    2. 2016 ~ 至今:不再折腾博客,开始专注于内容写作,并且保持每年都有更新,做一个活博客。

    写了十年,我也算是没少搞和 WordPress 相关的事情,不过,时间久了,脑子里那些工程师的想法,难免重新冒出来 —— 我是不是应该自己写一个 Blog 系统。对于曾经的我来说,可能是不容易的,我并没有那么丰富的开发经验。但对于如今的我来说,确实是不那么困难的事情。

    不过,写总是要有个目的和收益评估,不能「为了写而写」,而是应该有一个明确的目的,评估 ROI。

    我为什么想写?

    • 觉得 WordPress 还是太臃肿,有不少我用不上的能力。
      • 那么我自己的 Blog 系统应该有一个明确的 Feature List。这样才能避免需求无限的膨胀。
    • 我的需求已经逐步稳定下来,不太需要新的主题/插件了。
      • 这些年的确主要是更新内容,博客的形式、内容啥的,基本上都是稳定不变了。

    我为什么不能写?

    • 写 Blog 对于我来说没有什么特别的收益。
      • 毕竟这玩意没办法卖,除非写的过程中有别的收益,可能还好,不然大概率亏本。

    我如果要写一个 Blog 系统,我需要哪些 Feature?

    • 文章系统:包含标题、描述、内容、Slug、目录、标签几个核心属性和实体。
      • Slug 需要自动翻译:我特别依赖这个功能。懒得自己翻译 Slug
    • 支持图片自动上传 S3
      • 我现在的图片都是用的外链,这样服务器自身的压力没那么大。
    • 支持评论
      • 评论还需要能够自动发送邮件更新。
      • 评论要能实现反垃圾。
      • 历史的 600+ 评论可导入
    • 简洁稳定的主题设计
      • 坦白来讲,这几年我很少做主题方面的变更了,基本上就是在几个景点主题上来回切换。
    • 支持 RSS Feed 等能力
    • 支持在线编写(可能非必需,最近开始逐步用 Ulyssess 写作,其实对于网页写文章的诉求越来越小了,maybe Hexo 是可接受的方案。)
    a computer screen with a drawing of two people talking to each other

    Solution as a Service, not Software as a Service

    在软件领域,我们有非常经典的 IaaS、PaaS、SaaS 模型,我们使用这个模型定义着我们的产品。

    但另一方面,这个定义也局限了很多人的想法 —— SaaS just Software as a Service。

    实际上,如果你是一个独立开发者 / 直面用户的岗位,你需要深刻的明白 —— SaaS 更大的价值不应该是 Software ,而应该是 Solution —— 用户从来不关心你是不是有 Software,用户只关心你的 Solution 能不能解决你的问题。

    如果你不知道你的 Software 是在解决谁的问题 —— 不妨想想,是不是你没有找到你的Software 到底应该如何放在 Solution 当中 ,以及那个 Solution 对应的问题到底是什么。

    持续关注用户的问题,尝试提供靠谱的 Solution 去解决他的问题 —— 而不是关注你的 Software。除了你,没有人真正在意你的 Software。