[{"content":"背景 最近这段时间一直在用 Codex 开发项目，解决了各种小毛病之后 Codex 堪称开发神器， 目前给我带来了很多开发上的帮助。\n然而前几天由于一直开着 GPT5.5 High, 然后发现周额度刷新时间还没到一半额度就被我用完了，\n所以我就去研究了下各种省Token的方法，这里有一些收获想分享一下。\n先放结果 我先把研究成果的提示词放文章最前面，\n有需要的同学复制后加入到 ~\\.codex\\AGENTS.md直接用就行了。\n理解性内容可以后面想了解时再看。\n推荐提示词:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 - 回答要保持简洁精炼。 只回答问的内容，默认不铺垫、不总结、不扩展。 能一句话说清就一句话。 除非我要求详细解释，否则不要展开背景知识。 ### 文件读取规则 - 读取文件前的检查 读取任何文件正文前，必须先检查文件大小，除非文件大小已在当前上下文中明确可知。 如果文件大小 \u0026gt;100KB，禁止全文读取。应改用搜索、抽样、分块读取等方式，只读取与任务相关的片段。 优先使用字节数限制输出，避免大文件、长行文件、压缩文本、日志、JSON 等内容污染上下文。 Linux bash 示例： COMMAND 2\u0026gt;\u0026amp;1 | head -c 4000 Windows pwsh示例: $Output = COMMAND 2\u0026gt;\u0026amp;1 | Out-String $Output.Substring(0, [Math]::Min(4000, $Output.Length)) - 不要重复读取已经读取过的文件内容 除非用户明确要求重新读取，或有证据表明文件已经变化， 否则当文件此前已经进入上下文时， 优先基于已有信息推理，不要再次读取相同内容。 如需确认文件是否变化，应优先检查修改时间、大小、hash 或 git diff，而不是重新全文读取。 - 依赖锁文件限制 除非任务明确涉及依赖分析、版本冲突、构建复现相关任务， 否则默认禁止读取 lock / sum / vendor 等依赖明细文件正文，例如： package-lock.json / go.sum / uv.lock 等。 不要让AI废话。 要对模型的价格心理有数\nGPT API 价格\n模型 输入 token 缓存输入 token 输出 token GPT-5.5 $5.00 / 1M $0.50 / 1M $30.00 / 1M GPT-5.4 $2.50 / 1M $0.25 / 1M $15.00 / 1M GPT-5.4 mini $0.75 / 1M $0.075 / 1M $4.50 / 1M 来源：OpenAI 官方 API Pricing https://openai.com/api/pricing/\n表中价格按 2026-05-21 页面信息整理，后续可能变化\n从上表可以发现两个提示\n同样一段文字 Token，输出价格 = 6x输入价格\n并且输入还能命中缓存，平时会更便宜，所以结论:\n输出很贵，你要限制AI讲废话。\n大模型厂商有天然的动力让模型多吐字，可以多赚钱\n先进模型很贵。\nGPT-5.5 = 2x GPT-5.4 = 6x GPT-5.4 mini\n先进模型比如GPT-5.5它们的优势在于复杂问题的处理上，解题效率更高。 但是很多时候，面对一些简单、环境单一的小任务 GPT-5.4 mini 也能做掉，此时没必要开 GPT-5.5\n平时额度紧张时，GPT-5.4也是很香的选择。\n推荐提示词:\n1 2 3 4 - 回答要保持简洁精炼。 只回答问的内容，默认不铺垫、不总结、不扩展。 能一句话说清就一句话。 除非我要求详细解释，否则不要展开背景知识。 优化Agent行为 我这次研究 AGENTS.md 时，发现一些提示词真的适合每个工作者都写进去。\n这些提示词，不仅能减少消耗的 token ，并且更小更干净的上下文会让 AI 解决问题更有效率。\n设想一下，当 AI 窗口有大量的无效主题不明的上下文，大模型自然抓不到问题重点，解决问题效率直线下降。\n我现在推荐写进去的，主要就是下面这三类。\n1. 读取文件前先看大小 这是我最推荐的一条。\n这里有一个常见的误区，很多人会觉得 AI 在执行过程中调用命令时返回的结果，比如 cat 一个文件的内容，这些 output 不算输入的上下文。 实际上这些命令的 output 和你在窗口手动输入的文本一样，全算有效上下文。\n所以，当很多 AI 处理问题时，加载文件一上来就读全文。看起来很全面，实际上很容易把上下文塞满，把真正关键的信息挤出去。\n更合理的做法，是先判断文件大小，再决定是全文读、抽样读，还是只读相关片段。\n这条规则的本质很简单：先控制信息摄入，再开始分析。\n我建议这样写提示词：\n1 2 3 4 5 6 7 8 9 10 11 12 13 - 读取文件前的检查 读取任何文件正文前，必须先检查文件大小，除非文件大小已在当前上下文中明确可知。 如果文件大小 \u0026gt;100KB，禁止全文读取。应改用搜索、抽样、分块读取等方式，只读取与任务相关的片段。 优先使用字节数限制输出，避免大文件、长行文件、压缩文本、日志、JSON 等内容污染上下文。 Linux bash 示例： COMMAND 2\u0026gt;\u0026amp;1 | head -c 4000 Windows pwsh 示例: $Output = COMMAND 2\u0026gt;\u0026amp;1 | Out-String $Output.Substring(0, [Math]::Min(4000, $Output.Length)) 这条规则的价值，不只是省 token。\n它还能逼着 AI 按正确顺序工作：先判断，再读取。先筛选，再读取。\n2. 不要重复读取已经读取过的内容 这条看起来普通，但很关键。\nAI 很容易为了“稳妥”反复读同一份内容，结果不是更准确，而是更浪费。 真正好的工作方式，是复用已经拿到的信息，而不是每一步都重新扫描一遍。\n所以我会明确加上这一条：\n1 2 3 4 - 不要重复读取已经读取过的文件内容 除非用户明确要求重新读取，或有证据表明文件已经变化， 否则当文件此前已经进入上下文时，优先基于已有信息推理，不要再次读取相同内容。 如需确认文件是否变化，应优先检查修改时间、大小、hash 或 git diff，而不是重新全文读取。 这条的目标不是偷懒，而是让 AI 学会复用上下文。 会复用，才会省 token。\n3. 默认不碰依赖锁文件 这条也很值得写，对开发人员很有价值\n除非当前任务就是在查依赖冲突、版本锁定、构建复现，不然 go.sum、package-lock.json、uv.lock 这类文件通常都不该主动读。 它们信息密度高，但对大多数任务帮助不大。\n我建议这样写提示词：\n1 2 3 4 - 依赖锁文件限制 除非任务明确涉及依赖分析、版本冲突、构建复现相关任务， 否则默认禁止读取 lock / sum / vendor 等依赖明细文件正文， 例如： package-lock.json / go.sum / uv.lock 等。 这条的作用很直接：把 AI 从“什么都想看”拉回到“只看和当前问题有关的东西”。\n最后 现在我觉得大模型在使用时现在缺的不是解决问题的能力，而是在工程上管理上下文的能力。\n怎么样让大模型的上下文保持干净，才是日常使用提升大模型解决问题效率的关键之一。\n而 AGENTS.md 的作用，就是尽量提前掐掉这些低效行为，让 AI 少犯一些低级的工作流错误。\n暂时先这样了，\n现在 AI 发展太快了，我感觉学习的知识和经验都在快速变，目测几个月后又变了，\n大家一起慢慢学慢慢卷吧~\n原创声明: 本文首发于我的个人博客 原文链接地址\n","date":"2026-05-21T11:12:34+08:00","permalink":"/p/write-agents-md-save-token/","title":"AI编程心得2：写好 AGENTS.md，AI 更会干活，也更省 token"},{"content":"场景 现在是晚上1点，今天被一个shadcn-ui的问题坑了3个小时……\n啊， 我人生宝贵的三小时，够我打10把红警小块地或者魔兽世界打完一个完整的团队本。\n这里记录一下，假如你们遇到了，希望能节约一点你们的时间！\nShadcn获取组件ref Shadcn增加UI组件的命令我想大家都知道\n1 2 pnpm dlx shadcn@latest init pnpm dlx shadcn@latest add input 今天我遇到一个需求，在input组件加载后实现输入框自动聚焦，因此需要拿到input组件的ref\n然后神奇的事件来了，我发现我没办法拿到shadcn组件的ref\n因为使用了pnpm dlx shadcn@latest add input后\n他生成的input代码长这样:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import * as React from \u0026#34;react\u0026#34; import { cn } from \u0026#34;@/lib/utils\u0026#34; function Input({ className, type, ...props }: React.ComponentProps\u0026lt;\u0026#34;input\u0026#34;\u0026gt;) { return ( \u0026lt;input type={type} data-slot=\u0026#34;input\u0026#34; className={cn( \u0026#34;file:text-foreground placeholder:text-muted-foreground ...\u0026#34;, className )} {...props} /\u0026gt; ) } export { Input } 发现没，里面压根没有React.forwardRef\n要知道React.forwardRef这条命令是在 React v16.3.0 版本中正式推出的，所以shadcn早就应该带这个引用了\n排查 代码对比 然后我去官网比对了下代码版本:官网的Input组件\n点开Installation - Manual，这里是他手动安装的版本:\n1 2 3 4 5 6 7 8 import * as React from \u0026#34;react\u0026#34; import { cn } from \u0026#34;@/lib/utils\u0026#34; const Input = React.forwardRef\u0026lt;HTMLInputElement, React.ComponentProps\u0026lt;\u0026#34;input\u0026#34;\u0026gt;\u0026gt;( ({ className, type, ...props }, ref) =\u0026gt; { return ( // ... 可以发现他的版本是有React.forwardRef的。\n这说明，我的代码版本不对。\n排查缓存问题： 代码文件是pnpm dlx shadcn@latest add input这条命令生成的。\n首先想到可能是 pnpm dlx 拉代码时自身可能有缓存。\n执行pnpm store prune，删除整个node_modules，重新安装，没解决问题。\n排查 Registry 我很奇怪，难道是pnpm dlx 因为某种原因没有拉取到最新的 CLI 版本？\npnpm对应的Registry给了我旧版本的代码？\n找了下查和改Registry的方式\n1 2 pnpm config get registry pnpm config set registry https://registry.npmmirror.com/ --global 我原来用的腾讯源，换到了淘宝源\n拉下来的组件代码还是没有带React.forwardRef！\n又换了次官方源。还是一样。\n问了下Gemini, 他让我直接改源码，在源码里加上React.forwardRef实现传递ref\n因为这样的话相当于改了自动工具生成的文件，显然这个方案不好。\nTailwind v4 + React19 这时候脑子就抓狂了，感觉这事也太奇怪了。 耐着性子继续查(反正老子今天不上班)。\n为了缩小可能的范围，我准备创建一个新的最小包含Shadcn的mini项目来实验:\nshadcn官网的安装流程\n按照官网的说明，重新安装的过程中，我发现了一个细节， Shadcn官网提到了 Tailwind v4和React19的升级问题。\n我的本地版本是Tailwind v4和React 18.3\n因为React19是前几个月刚出的，我没更新，怕不稳定。(我习惯于用上一个大版本的最后一个小版本)\n然后我开始检查React19和React.forwardRef的关系，然后发现了一个重要信息:\nreact官方的React.forwardRef\n这个API竟然已经被React19打上了Deprecated！终于有了线索。\n后面接着查，我发现了这个shadcn-ui的issue，事情大体清楚了。\n真相大白 到了新的React19这个版本后，想获取组件的ref不需要再使用React.forwardRef\n而shadcn为了支持Tailwind v4和React 19，已经完成了代码更新。\n包括shadcn官网目前提供的Installation说明, 实际上是支持了React 19的版本\n而在用户这一边，当用户增加新的UI组件时，使用的默认命令行是\npnpm dlx shadcn@latest add input\n因为用户本身输入的要求是shadcn@latest。\n既然是latest，也就把最新的支持React19的版本，没有使用React.forwardRef的代码给用户了。\n坑点就是\ndlx shadcn@latest这个命令行不会识别你用的react具体版本，自动把最新的组件代码给你了 官网的Installation - Manual没同步更新也没说明，使用的反而不是react19版本, 和CLI代码不同。 解决方案 解决方式有两种\n别用shadcn@latest，而是指定一个旧版本的组件。一切依然岁月静好。\n激进。升级react19。 我是本地小项目，所以直接就升级了。我把升级的命令行贴一下:\n1 2 3 4 5 6 7 // 升级react19 只推荐有把握的小项目直接升级 pnpm install --save-exact react@^19.0.0 react-dom@^19.0.0 pnpm install --save-exact @types/react@^19.0.0 @types/react-dom@^19.0.0 pnpm update 新版本React19获取组件的ref很简单\n1 2 3 4 5 6 7 8 9 10 11 const inputRef = useRef\u0026lt;HTMLInputElement\u0026gt;(null); // 组件的Props const InputProps = { ref: inputRef // rect19 Props里可以直接传ref }; return ( \u0026lt;Input {...InputProps} /\u0026gt; ); 感想: 程序员的很多知识和技能，特别是对生产工具/框架相关的，贬值真的很快。\n假设我们有一位前端程序员，学习使用React工作了一段时间，他获得了如下的开发经验:\n在 React 中，如果你想获取一个函数组件内部渲染的 DOM 元素的 ref，这个函数组件必须使用 React.forwardRef 来包装。\n否则，直接传递 ref prop 给这个函数组件是无效的（React 会发出警告），因为函数组件本身没有实例，ref 的目的是指向底层的 DOM 节点或类组件实例。\n而在2025年的4月的今天，这个经验已经贬值为0。\n这也是很多程序员永远会疲于奔命的原因，主流的开发技能，这部分的知识更新速度是很快的。\n不仅自身需要时时学习，更何况现在还有AI编程的入场。 真的是脚步停下来就会被淘汰。\n收工！\n","date":"2025-04-27T01:04:49+08:00","permalink":"/p/shadcn-react.forwardref/","title":"Shadcn的一个坑 forwardRef问题"},{"content":"中文 长话短说 假如你使用vscode写python，在导入一个模块时希望 vscode 给出的导包建议，\n是建议从包的根路径（__init__.py）导入类，而不是从深层文件路径导入，请记住修改以下配置：\n在设置里搜索includeAliasesFromUserFiles，设置为true\n1 keywords: \u0026#34;python\u0026#34;, \u0026#34;import suggestions\u0026#34;, \u0026#34;__init__.py\u0026#34;, \u0026#34;__all__\u0026#34; 背景 今天在实践 Python 包导出的最佳方式时，遇到了 VS Code 无法正确给出“短路径”导入提示的问题。 研究了一个小时才解决，这里记录一下，希望能帮大家避坑。（这个问题连 Gemini 3.0 和 Claude 都没有第一时间给出正确原因，大多以为是路径配置问题）。）\nPython组织包导出的最佳实践 假如我们有以下的项目结构，其中pkg_a是我们自己写的包 1 2 3 4 5 📁 /project_root ├── 📁 pkg_a │ ├── 📄 __init__.py │ └── 📄 user_define.py └── 📄 start.py 我们在user_define.py中定义了一个类型 1 2 3 4 # pkg_a/user_define.py class MyUser(): name: str = \u0026#34;\u0026#34; 通过__init__.py来定义导出\n在这个文件夹的__init__.py中，我们可以通过__all__来定义这个包有下面什么内容是对外导出的。\n这是许多优秀开源项目（如 Requests, Pandas）的标准做法。 1 2 3 4 5 6 7 # pkg_a/__init__.py from pkg_a.user_define import MyUser __all__ = [ \u0026#34;MyUser\u0026#34; ] 当我们想引用这个MyUser定义时，导入定义会有两种方式，目前社区的最佳实践是更推荐第二种导入方式 1 2 3 4 5 6 7 8 9 # start.py # Method 1 ❌-\u0026gt; Use file import # from pkg_a.user_define import MyUser # Method 2 ✅ -\u0026gt; Using the Package Import from pkg_a import MyUser user_tom :MyUser | None = None 为什么第二种方式更好呢\n更好的封装/抽象\n包的调用者无需了解包内部的文件结构。\n假如你稍后将 user_define.py 重命名为 models.py，只需要更改 __init__.py 中的导入语句 ， 外部的调用方start.py不用修改也不会出错，他是无感知的。\n更简洁的导入 API\n更短的导入语句通常更容易阅读。\nfrom pkg_a import MyUser 比 from pkg_a.sub_folder.user_files.definitions import MyUser 更简洁 。\n精确控制导出内容\n通过 all，你可以严格控制当用户使用 from pkg_a import * 时会污染命名空间的内容。\n问题：VS Code 默认不支持提示 这就引出了我今天遇到的问题：VS Code 无法提示文件级导入路径。\n当我输入 MyUser 尝试自动导入时，IDE 往往建议 from pkg_a.user_define import MyUser，而不是我希望的 from pkg_a import MyUser。\n然后我排查了一圈，发现这是 VS Code 的 Python 语言服务器 Pylance 的默认行为。\n配置项 python.analysis.includeAliasesFromUserFiles 默认是 false（为了性能考虑，默认不索引用户文件中的别名导出）。\n解决方法： 将该配置改为 true 后，VS Code 就能理解你在 init.py 中做的“别名导出”是为了给外部使用的，从而正确地给出简短的包导入建议。\n又花了我1个多小时，希望对你们有用！\nEnglish Best Practices for Python Package Exports \u0026amp; Fixing VS Code Intellisense TL;DR If you use VS Code for Python development and want the IDE to suggest imports from the package root (__init__.py) instead of the specific file path, you need to tweak a setting.\nSearch for includeAliasesFromUserFiles in your settings (or set python.analysis.includeAliasesFromUserFiles in settings.json) and set it to true.\nBackground While practicing the best way to organize Python package exports today, I encountered an issue where VS Code failed to suggest the \u0026ldquo;clean\u0026rdquo; import path. I spent an hour troubleshooting this, so I\u0026rsquo;m documenting it here to verify it works for you. (Even AI assistants like Gemini 3.0 and Claude didn\u0026rsquo;t pinpoint the exact configuration cause immediately).\nBest Practices for Python Package Organization Project Structure Suppose we have the following structure, where pkg_a is our custom package: 1 2 3 4 5 📁 /project_root ├── 📁 pkg_a │ ├── 📄 __init__.py │ └── 📄 user_define.py └── 📄 start.py Defining a Class We define a class inside user_define.py: 1 2 3 4 # pkg_a/user_define.py class MyUser: name: str = \u0026#34;\u0026#34; Exporting via __init__.py In the package\u0026rsquo;s __init__.py, we expose the content using __all__. This pattern is widely used in open-source projects. 1 2 3 4 5 6 # pkg_a/__init__.py from pkg_a.user_define import MyUser __all__ = [ \u0026#34;MyUser\u0026#34; ] Importing the Class When importing MyUser in start.py, there are two methods. The community strongly recommends Method 2. 1 2 3 4 5 6 7 8 9 # start.py # Method 1 ❌ -\u0026gt; File Import (Implementation detail leak) # from pkg_a.user_define import MyUser # Method 2 ✅ -\u0026gt; Package Import (Facade Pattern) from pkg_a import MyUser user_tom: MyUser | None = None Why is Method 2 better?\nBetter Encapsulation/Abstraction The consumer of the package doesn\u0026rsquo;t need to know the internal file structure. If you later rename user_define.py to models.py, you only need to update the import in pkg_a/__init__.py. The external code in start.py remains unchanged and unbroken.\nCleaner Import API Shorter import statements are generally more readable. from pkg_a import MyUser is much cleaner than from pkg_a.sub_folder.user_files.definitions import MyUser.\nControl Over Exports It allows you to control exactly what is exported when someone uses from pkg_a import * via the __all__ list.\nThe Issue: VS Code Default Behavior Here is the problem I faced: VS Code defaults to suggesting the file import path.\nWhen I typed MyUser hoping for an auto-import, the IDE suggested from pkg_a.user_define import ... instead of the cleaner package import.\nIt turns out this is a setting in Pylance. The setting python.analysis.includeAliasesFromUserFiles defaults to false (likely for performance reasons).\nThe Fix: Setting this to true allows Pylance to recognize that the alias created in __init__.py is intended for public use, enabling it to suggest the correct, shorter import path.\n","date":"2025-12-16T22:01:17+08:00","permalink":"/p/python-package-export-best-practices-vscode-fix-import-suggestions/","title":"Python包导出最佳实践与VSCode智能提示修复"},{"content":"作为一名多年经验的服务端开发，习惯了 JAVA/Go/Python 那种自上而下、逻辑确定的执行流。\n当我转到全栈，深入使用了 React 18/19 后，虽然觉得React的生态真的是繁荣，比如光一个状态管理就有很多选择。\n但是在日常的开发中，我越来越感觉到一种框架把它应该做的活扔给了我的疲惫感。\n所以我在想，为什么我写react时会觉得心累？ 大概有如下几个原因。\nreact的问题 React是有渲染状态的， React 的核心哲学是 UI = f(State)，这很好。\n但为了维持这个纯函数式的幻觉，React 引入了很多非业务逻辑需要的复杂度。\n各种hook的负担: 从useState开始，直接有useRef, useEffect, useContext, useReducer, memo, useMemo, useCallback\u0026hellip;等一堆概念需要学习，熟悉，了解最佳实践和坑点。\n需要担心依赖数组： 大家都知道写很多hook都有依赖数组\n比如写 useEffect 时，就必须小心翼翼地维护依赖数组。少写了导致闭包陷阱，多写了会导致无限循环。\n需要担心性能优化: React只有手动挡的性能优化：\n本来只是想渲染一个列表，结果发现父组件一动，子组件全部重新渲染了。\n我有单次渲染强迫症，我就希望最好只有一次渲染。\n于是被迫到处包一层 useMemo、useCallback 和 React.memo。\n这感觉就像是在手动管理内存，而不是在写业务。开发者的注意力就被分散了。\nHooks 的闭包陷阱： 在很多异步操作中，或者在闭包的场景下，假如你引用了State，就需要小心拿到的变量值是旧值。 初学者可能需要很久才会发现这的问题。因为这并不直观。\n总结一下： 我发现我在使用react时，需要花费很多时间在思考我这代码是不是符合react的最佳实践呢，是不是不小心重复渲染了呢? 有时候这部分用掉的时间，超过了“思考业务逻辑”的时间。这很不好。\n寻找替代品 SolidJS vs Svelte 由于这些痛点，我开始寻找更直观的框架。\n目光自然锁定在了两个在论坛上被经常提起的名字：SolidJS 和 Svelte。\nSolidJS：极致的性能，细粒度更新，没有 Virtual DOM。它的 createSignal 比 React 的 useState 更符合逻辑直觉。 Svelte：编译型框架，在很多场景下，代码量能比 React 减少 30% 以上，同样没有 Virtual DOM，主打像写原生 HTML/JS 一样简单。 为什么我最终选择了 Svelte？\n在我细致地去了解这两个框架的能力之后，我个人感觉他们都很好，都成功解决了react的痛点 \u0026ndash;\u0026gt; 让写业务更加符合直觉，性能也更好。\n既然都能解决核心问题，那么我作为一个非专业向的前端，我最关心的其实就是社区热度。\n有社区热度的框架，活得更久，适配的轮子更多，遇到问题也好查资料。\n那从目前的情况来看(今天是2025-12-13) ，Svelte目前比solid有更好的社区热度和社区支持，\nSvelte \u0026ndash;\u0026gt; github star 80k / NPM 周下载量 200万+ solid \u0026ndash;\u0026gt; github star 35k / NPM 周下载量 20万+ 企业背景与就业市场\n企业背书： Svelte 的创始人 Rich Harris 加入了 Vercel（Next.js 的开发商）\n这意味着 Svelte 拥有更好的资金保障，未来发展会比较稳定，不存在突然停止更新的情况。 就业机会： 虽然远少于 React 和 Vue，但在很多初创公司 Svelte 的招聘需求也正在增加。\n上次爆出一个新闻，apple用Svelte重写了网页版App Store。只也是 Svelte 热度的表现吧。 最后结语 我觉得个人项目写代码，最后还是要回归开发本身的乐趣的。\nSvelte成功让我找回了刚开始学编程时的那种简单和快乐 -\u0026gt; 定义变量，修改它的值，界面随之改变。就这么简单。\n可能 Svelte 不是大厂招聘的硬通货（那是 React/Vue 的地盘），但它绝对是独立开发者的好东西。\n所以，作为一个独立开发者，所以我使用Svelte来代替React\n","date":"2025-12-13T19:38:23+08:00","permalink":"/p/try-to-use-svelte/","title":"我决定暂时放下React 转用Svelte"},{"content":"sync.once的使用 sync.Once是go标准库的一个类型，用于在并发环境中保证某一段代码只被执行一次。\n通常，我们会用它来进行一些初始化的工作。比如:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func Test_Once(t *testing.T) { var once sync.Once done := make(chan bool) for i := 0; i \u0026lt; 10; i++ { go func() { once.Do(onceFunc) // 执行十次 done \u0026lt;- true }() } for i := 0; i \u0026lt; 10; i++ { \u0026lt;-done } fmt.Println(KeyCount) // output: 1 } var KeyCount int func onceFunc() { KeyCount++ } 上面的例子中，onceFunc会被执行10次，但是因为我们用了sync.Once，所以最终KeyCount只会被加一次。\nsync.once的源码 所以，他是怎么实现的呢？ 我们在IDE中点开sync.Once的实现。\n1 2 3 4 type Once struct { done uint32 m Mutex } 结构很简单，done用来标记执行过的状态，m就是锁。说白了还是靠锁。 继续看：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 func (o *Once) Do(f func()) { if atomic.LoadUint32(\u0026amp;o.done) == 0 { o.doSlow(f) } } func (o *Once) doSlow(f func()) { o.m.Lock() defer o.m.Unlock() if o.done == 0 { defer atomic.StoreUint32(\u0026amp;o.done, 1) f() } } 这里就是比较有趣的地方了，\n首先他通过atomic.LoadUint32这个原子操作来判断done这个Int是否已经被改过了。\n并发操作有个原则，能用原子操作的地方别用锁。原子操作效率更高。\n在doSlow中，可以看到加锁操作，临界区里再进行一次状态判断，随后改掉状态值。\n问题：明明已经拿到锁了，为什么还要再执行一次if o.done == 0的判断呢? 仔细考虑了下，猜测在并发环境下，两个协程有机会同时通过了atomic.LoadUint32(\u0026amp;o.done) == 0的检查，他们先后调用doSlow\n比如协程A先拿到锁进入临界区，然后释放锁。 此时协程B会把临界区代码再执行一次！\n因此临界区里也需要加入if o.done == 0来让协程B早点返回。\nsync.once的代码写得真好啊，又短又精，很适合让我们学习。\nsync.once的扩展 sync.Once可以保证代码只被执行一次，看完源码我的脑洞就来了。\n工作中有一些执行比较重的后端接口（比如计划任务）需要有调用频率保护，\n前端不小心连续点击了5次，而后端只希望5次中的1次可以成功调用，在这个接口执行完毕并返回以前，其他点击都要废弃。\n相当于保证同时只有一个实例在跑。是不是可以借用这个Once的实现呢？\n然后我就写一版。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 type LimitOne struct { done uint32 m sync.Mutex } var LimitError = fmt.Errorf(\u0026#34;We are working on the task! please try again later\u0026#34;) func (o *LimitOne) Do(f func() error) error { if atomic.LoadUint32(\u0026amp;o.done) == 1 { return LimitError } o.m.Lock() defer o.m.Unlock() if o.done == 0 { atomic.StoreUint32(\u0026amp;o.done, 1) defer atomic.StoreUint32(\u0026amp;o.done, 0) err := f() return err } return LimitError } 大体按照sync.once的结构抄一遍， 唯一不同的是当传入的函数执行完之后，需要把done再设置为0。\n此时又处于就绪状态，又可以接新的请求了。\n以此来保证不论有多少请求，保证每次只有一个task在跑。\n下班~\n","date":"2025-01-05T08:00:00+08:00","permalink":"/p/%E7%90%86%E8%A7%A3sync.once/","title":"理解sync.once"},{"content":"懒人阅读版 找到你需要去掉的commit的前一次的commitID\n执行\n1 git rebase -i commit_id -X their 进入编辑后 在vi里 drop掉不需要的commit\n1 git push -f # 覆盖远程分支 这样远程分支的那次commit就彻底消失了。\ngit push -f是高危操作 不是特殊时刻不要用\n原文 最近遇到一个问题，我们的项目代码里，之前把一些密码配置明文传到了github，\n但是公司的安全策略不希望我们在代码里直接留有敏感的账号凭证信息，否则会被扫出来合规性问题。\n按照我同事之前的经历：安全部门扫出来之后就会发邮件给你老板，让你半夜起来马上消掉。\n因此产生了一个需求，怎么把github上已提交的敏感信息抹掉呢？\n我调研了下，目前已知有几种方式：\n通过git revert commit_id来实现 git revert 可以撤销某次操作，他是通过提交一次新的commit来回滚之前的一次commit的内容， 大部分情况下这个功能是够用的。\n不适用我们的场景，因为旧的commit实际上不会消失，在github直接打开旧的commit，还是可以看到对应密码信息，不合规。\n通过git reset \u0026ndash;hard commit_id 直接重置到密码提交前的那一次commit，然后git push -f重置分支\n这样做的前提条件是需要提交密码的那一次commit是最近发生的，\n不然就需要手动把后面的历史commit都补回来，工作量很大\n通过git rebase -i 来实现丢弃一个commit 这是今天发现的解决方案，有点类似于以前git合并多个commit的操作。\n下面说下流程：\n找到你需要丢弃的commit_id 在我的例子里，我在bdfb32a这次提交里提交了密码\n这次commit的前一次是0119c21 所以执行\n1 git rebase -i 0119c21 -X their 解释:\n-i 进入交互模式\n-X their 方便后续的commit自动合并，不然你需要手动操作冲突。\n这时进入了vi的编辑界面，此时git会把commit_id之后发生的commit列出来， 我们按i进入编辑，手动把不需要的那个commit bdfb32a前面的pick改成drop.\n然后vi和rebase的基本操作： 1 2 3 esc, :wd git add . git rebase --continue 手动push 覆盖远程分支 此时你通过查看历史，比如git log -10\n应该可以看到commit的历史已经改变了，bdfb32a已经彻底消失了(被丢弃)。\n执行git push -f\n再去github看一下\n0119c21之后的这次bdfb32a已经抹除。\n收工~！\n","date":"2024-09-18T08:00:00+08:00","permalink":"/p/git%E5%88%A0%E9%99%A4%E8%BF%9C%E7%A8%8B%E5%88%86%E6%94%AF%E6%9F%90%E6%AC%A1commit%E7%9A%84%E6%96%B9%E6%B3%95/","title":"Git删除远程分支某次commit的方法"},{"content":"场景: 这是一个Git在日常工作中非常核心的问题，很多工作了多年的同事也没吃透这个问题，没有正确理解merge和rebase的区别。 今天我花时间自己做了几个实验，也算是明白了，记录一下。\n常见场景\n自己fork了一个分支进行一个特性功能的开发，开发完了准备发起了PR\n结果发现在自己开发期间，主分支有了几次新的合入。\n这时候你想把主分支的改动更新到本地。\n为了让合并的历史更优雅, 此时执行了git reabase upstream main\n此时问题来了，你会发现你的这个本地的分支push不上去了。\n原因\n简单来说，git rebase 操作修改了你本地分支的提交历史，使其与远程分支的提交历史产生了分歧。\nGit为了保护远程分支不被意外覆盖，会拒绝你的non-fast-forward推送。\n正常的 git push 流程 在没有冲突的情况下，git push 遵循一个快进式（Fast-forward）的原则。\n远程分支 (origin/my-feature) 的历史是:\nA \u0026mdash; B\n你拉取了代码，在本地 (my-feature) 继续工作，增加了提交 C:\nA \u0026mdash; B \u0026mdash; C\n当你执行 git push 时，Git会比较你的本地分支和远程分支。\n它发现你的本地分支只是在远程分支提交B的基础上加了一个提交C\n于是它会执行一次Fast-forward，直接把远程分支的指针移动到C。\n推送后，远程分支也变成了:\nA \u0026mdash; B \u0026mdash; C\n这个过程是最常见的，也是安全的，因为它只是在原有历史的末尾继续添加新内容，不会丢失任何东西。\ngit rebase 的流程解释 rebase 的中文意思是变基，它的核心作用是重写提交历史，让分支历史变成一条直线更美观。\n假设你从 main 分支切出了 my-feature 分支并开始工作。\nmain 分支: A \u0026mdash; B\nmy-feature 分支: A \u0026mdash; B \u0026mdash; C (你增加了提交 C)\n在你工作的时候，你的同事向 main 分支推送了一个新的提交 D。\nmain 分支现在是: A \u0026mdash; B \u0026mdash; D\n你的 my-feature 分支还是: A \u0026mdash; B \u0026mdash; C\n此时，你的分支和 main 分支从提交 B 开始分叉了。\n为了让你的分支包含 main 的最新更改，你执行了 git rebase main。\nrebase 会做以下事情：\na. 暂时\u0026quot;收起\u0026ldquo;你在 my-feature 分支上的独有提交（也就是 C）。\nb. 从与 main 分支最后的共同提交B开始，抓取新增加的改动 D 到 my-feature 分支\nc. 将刚才收起的提交 C 在新的起点 D 上重新应用一遍。\n此时my-feature 分支变成了: A \u0026mdash; B \u0026mdash; D \u0026mdash; 收起的C\n关键点来了：\n重新应用的 C 这个提交，虽然代码内容没变，但它的父提交从原来的 B 变成了现在的 D。\n在Git中，一个提交的唯一标识SHA-1哈希值是由其内容、作者、时间戳、以及父提交等信息共同决定的。\n父提交变了，哈希值就会变！所以新的 C 对应的hashID，和原来的 C 是不同的 你实际上得到一个内容完全一样，但是hashID变了的提交 C'。\n所以Rebase之后，my-feature 分支历史: A \u0026mdash; B \u0026mdash; D \u0026mdash; C'\n为什么 rebase 后 push 会失败？ 现在，我们来比较一下 rebase 后的本地分支和远程分支\n本地 my-feature 分支: A \u0026mdash; B \u0026mdash; D \u0026mdash; C'\n远程 origin/my-feature 分支: A \u0026mdash; B \u0026mdash; C\n当你执行 git push 时，Git会进行比较，然后它会发现： 这两个分支从共同的祖先 B 开始就分道扬镳了。本地 my-feature的历史里并没有包含远程的 C 提交。\n如果接受推送，远程的 C 提交就会丢失，这太危险了！所以拒绝这次推送。\n这就是你看到的 (non-fast-forward) 错误。\nGit通过这个机制，防止你无意中覆盖掉远程仓库可能存在的、你本地没有的提交。\n解决方式: 方式1 在使用fork后的分支开发后，使用merge策略来合并改动。\n缺点: commit的历史线会比较混乱，不好看\n方式2 使用reabse后，搭配push -f来强行更新远程自己的分支，\nCommit ID的历史会是一条直线，就像前面例子的A --- B --- D --- C'，很会优雅\n解释push -f 的作用和风险 git push --force (或简写 -f) 就是你给Git下的一个强制命令，意思是：\n“别管什么快进不快进了，也别管远程分支上有什么。我push给你的这个版本就是最终版本，你就用我这个版本去覆盖”\n执行 git push -f 后，\n远程的 origin/my-feature 被强制更新为: A \u0026mdash; B \u0026mdash; D \u0026mdash; C'\ngit push -f 是一个比较危险的操作，千万不要向公共分支（如 main, develop）执行 push -f\n对于自己的特性分支执行是没有问题的。通常，一个特性分支只有你一个人在开发。\n在你准备合并到主分支之前，用 rebase 来保持分支的整洁，然后用 push -f 更新你自己的远程分支，这是非常常见的做法。\n更安全的选择：git push --force-with-lease\n它在强制推送前会增加一个检查：只有当远程分支的状态和你本地最后一次拉取时一模一样，它才会执行强制推送。\n换句话说，如果在你执行 rebase 到你准备 push 的这段时间里，有其他人也向这个远程分支推送了新的提交， --force-with-lease 就会失败。这可以防止你覆盖掉别人在你不知情的情况下推送的工作。 日常工作中，推荐使用 git push --force-with-lease 代替 git push -f。\n","date":"2023-04-28T08:00:00+08:00","permalink":"/p/git-rebase-push-rejected/","title":"git rebase有个坑"},{"content":"从去年 12 月开始，我开始尝试用 AI 工具做尽量少手动干预的全托管开发。目前经过 3 个多月的实践，整个项目也已经到了 3 万行，确实积累了一些心得，想整理出来分享一下。\n工作流边界 这篇文章只讲我个人验证过的协作工作流，我比较关心 AI 协作编程这件事怎么组织会比较好，哪些习惯能长期省事，哪些习惯只会把项目搞乱。\n这里不展开具体的技术工具踩坑细节，那些我会单独放到另一篇文档里，免得和方法论混在一起。\n几个心得 心得1 通过文档指引来实现 AI 管理项目 如果一个项目真的打算长期交给 AI 托管，我现在越来越觉得，文档管理非常重要。\n原因很简单，AI 不像人，不能靠长期记忆掌握项目。每次打开一个新窗口进项目，本质上它都要重新从文档和代码里找上下文。所以最重要的就是有一份文档能起到地图的作用，并且做到层次清楚，职责分开。\n根据我目前对 AI 托管项目的理解，项目文档最好分成几层：\n入口层：比如根目录的 AGENTS.md，告诉 AI 先看什么、先遵守什么、项目现在处于什么状态 长期规约层：比如 ai_doc/ai-standards/，放默认要遵守的开发标准，尽量稳定，别频繁改 业务说明层：比如 ai_doc/business/，解释业务知识，专业术语的意思、规则、背景等等 架构说明层：比如 ai_doc/arch/，系统的架构，讲清楚系统怎么分模块、怎么协作、哪些边界不能碰 动态记录层：比如 ai_doc/status/，放当前开发进度、阶段性任务，随项目变化而更新 技能/知识层：比如 ai_doc/skills/，放一些业务的专题经验、特殊流程、可复用做法，按需加载就行 这样拆完以后，AI 的使用方式就会很顺：\n先从入口文档知道该去哪里找信息 再去看对应的知识层，而不是一上来把整个项目扫一遍 真正修改代码之前，先确认开发规约和架构设计上的边界 做阶段性任务时，只看动态记录，不把历史噪音一起带进来 总之，一份好的 AI 指导文档，核心的验收标准只有一个：\n让新窗口的大模型只靠少量文档，就能迅速掌握项目结构和核心业务。\n心得2 主动多开新窗口 我现在越来越习惯一件事：主动多开新窗口。\n原因其实不复杂。大模型的对话机制本来就不是“越聊越聪明”，而是“上下文越长，越容易被历史内容拖住”。前面聊过的东西会一直留在上下文里，越聊越多以后，模型很容易被无关信息干扰，回答也会开始变钝。更现实一点说，这些无关上下文还会继续吃 token，最后就是既不够精准，又不够节约。\n所以我现在的做法很直接：一件事对应一个窗口。\n一个窗口专门改代码 一个窗口专门看报错和排查问题 一个窗口专门让它整理方案或做复盘 另一个窗口专门处理和当前任务无关的临时问题 这样做的好处很明显：\n每个窗口的上下文更干净，模型更容易抓住当前任务 任务边界更清楚，不会互相污染 你自己也更容易验收，不会把多个问题混在一起看 真遇到跑偏的时候，直接重开比在一个脏对话里拉回来更省事 总之，该新开就新开，专事专用，养成好习惯，别舍不得。\n心得3 先 plan 后执行 还有一个我现在越来越坚持的习惯：先 plan，后执行。\n注意，这里的 plan 不只是 Codex 或 Claude Code 里的 Plan 执行模式，而是要上升成一种工作思维。\n很多返工，其实不是因为 AI 不会写，而是因为一上来就让它直接改文件了。上下文没聊清楚，目标也没对齐，它只能边想边改，最后改出来的东西经常和你的真实预期有偏差。\n比如我现在做重构的时候，在思维上就会分成三步：\n先让工具解释当前实现。先把它对现状的理解说清楚，看看它是不是真的理解了模块现在是怎么工作的。 再让它提出方案。这一步要把修改范围限制住，别让它顺手开始扩散到别的模块。 最后才让它改文件。前面两步没聊透，后面就不要急着动代码。 这个流程看起来慢一点，但实际反而更快。因为你把前面的理解、方案、边界先对齐了，后面落地的时候返工会少很多。\n不然很容易出现这种情况：AI 改了一轮，你看完觉得不对；再改一轮，还是偏；最后发现根本是第一步目标就没讲清楚。\nAI编程年代，比的不是谁对话次数多，而是比谁能把问题定义清楚。\n偷偷说一句，我觉得我们这些开发dev，目前的新定位就是：人肉 Harness，也就是负责监督和兜底的那一层。\n心得4 不要放任 AI 堆代码 这个点我现在看得越来越重：不要放任 AI 一直往项目里加代码。\nAI，尤其是 Agent，天然有一个倾向，就是在当前上下文里做“最小修复”。它看到哪里有问题，第一反应往往不是把整个模块重新想一遍，而是在现有结构里继续补、继续改、继续往下长。短期看，这种方式很快；长期看，问题会越来越多。\n我见过最典型的情况就是：一个模块本来就设计得很别扭，AI 会在这个模块里反复迭代、反复补丁式修改，业务逻辑越加越长，但它从来不会从全局视角主动问一句：这个模块是不是本来就不该存在？\n所以我现在非常强调，工程师本人必须作为项目架构师这样的监督层而存在。\nAI 可以干活，但架构判断不能全交给它。你得时不时停下来问自己：\n这里能不能向上抽象？ 这些重复实现能不能收拢成一个接口？ 这些分叉代码是不是该统一到同一套约束里？ 这个功能是不是应该删掉重构，而不是继续补丁式修修补补？ 如果这些问题没人管，AI 很容易一路产新代码，越写越多，越分越散。最后项目表面上是“功能越来越全”，实际上是结构越来越烂，直到后面根本没法管理，也没法控制。\n所以我现在的态度很明确：AI 适合帮人实现，人主要负责收敛。\n结论 暂时先写这么多。现在 AI 大模型的辅助编程，已经不是去年那种在聊天框里复制粘贴的阶段了，自动化程度确实上了一个台阶。\n但它再强，也只是把工程师从低价值重复劳动里往外拉一点，真正的架构判断、功能抽象和结果兜底，还是得人来做。\n那就先这样，预祝各位51快乐~\n原创声明：本文首发于我的个人博客 https://knowckx.pages.dev/，未经授权请勿转载。\n","date":"2026-04-30T08:00:00+08:00","permalink":"/p/ai-collaboration-workflow-notes/","title":"AI编程心得1: AI 协作编程的个人工作流心得"},{"content":"Part 4 - 2026年 “无人驾驶”编程 在进入正题前，我得给非技术背景的朋友解释一下IDE的概念。\n你可以把它想象成一个“数字化车间”。\n一个木匠干活不能徒手劈柴，他需要工作台，上面整齐地摆放着锯子、刨子、量尺等很多工具。 现代程序员干活，也不可能白板写代码(某些公司的奇怪面试除外)。\nIDE就是一个让程序员高效写代码的地方，它集成了代码提示工具，调试器（Debug 寻找代码里的虫子）以及各种各样的自动化工具 AI 对 IDE 支持分为几个阶段，有点类似于汽车的自动驾驶。\n辅助驾驶时代（2021-2023）： 代表作是 GitHub Copilot。它更多是一个高级版的“自动补全”，你只需要写部分代码，他会猜出来你的意图，然后提示你可以一键补全剩下的代码。它只是个称职的副驾驶，能帮你省点力气。 半自动驾驶时代（2024）： 代表作是 Cursor 。这一次 IDE 发生了两个重大的质变： 全局视野（Context）：它不只看着你目前正在写的那个文件，它会读遍你整个项目，他比你更清楚你需要的那个函数在项目的什么位置，所以提供了更好的提示支持。 原地重构（Cmd + K）: 你选中一段屎山代码，你敲个 Cmd/Ctrl + K，要求重写，要更优雅，它会按照它所知道的项目背景和其他代码文件内容生成适合的代码。 全自动驾驶时代（2025） 这就是我今天要说的“终局形态”， 编程界的“L4 级自动驾驶” 汽车行业折腾了十年，还没实现真正意义上的 L4 级别自动驾驶（即：在限定区域内，驾驶员可以完全不干预）。 在 2026 年初，我们悲哀地发现：自动驾驶在马路上还没完全实现呢，但在程序员世界里 L4 级先降临了~\n马路上的自动驾驶：需要处理复杂的物理世界、极端的天气，路边横穿出来的野猫，奇奇怪怪的马路杀手。\n代码里的自动驾驶：编程语言是逻辑自洽的，框架是标准化的。项目内的文件都是可以读取并且掌握的。\n对于 AI 来说这环境简直太舒服了，这里不会没有随机的风雨，只有固定的逻辑需求。\nAI 像上帝一样已经全部掌握了这个纯逻辑世界。\n于是2026年，最新的编程流程已经变成了：\n1 2 3 4 5 6 用户提出意图（帮我写个订单取消功能） -\u0026gt; AI 提出实现方案 -\u0026gt; AI 拆解实施步骤 -\u0026gt; AI 执行步骤1 -\u0026gt; AI 产出步骤1的解决方案 -\u0026gt; AI 编写代码 -\u0026gt; AI 运行环境 -\u0026gt; 步骤1交付 -\u0026gt; AI 执行步骤2 .... 步骤2交付 -\u0026gt; AI 执行步骤N .... 步骤N交付 AI 自动打开浏览器，像真人一样视觉验收功能 -\u0026gt; 用户意图完成 这是一种令人恐惧的自主性。\nAI可以实现自己长时间无需外来介入直至完成目标的闭环能力。\n我知道，目前的AI IDE还会卡住，它高阶抽象代码的能力也不好，甚至有些解决方案方向就是错的，但是全自动 AI 编程在结构上已经完备了，后续只需要等待大模型逐渐提升解决问题的能力就行了。\n在这个过程中，程序员变成了什么呢？\n“监考老师”。AI是那个天赋异禀的考生，而你唯一的价值，是防止它在考卷上偶尔会乱画。\n每个月准时给Google/ChatGPT/Anthropic充值订阅费。\n老板那边，账本算得更清：\n35 岁的程序员: 没办法加班吧？要的还多。\n25 岁的程序员: 经验怕不够吧，便宜点就算了，昨天竟然告诉我要求加薪？？？\nAI：7x24 小时待命，不闹情绪，不买五险一金，一年订阅费只要几千块钱。 真香！！\nPart5 死与新生（Death\u0026amp;Rebirth） 程序员曾经引以为傲的那些技术——会C++/JAVA\u0026hellip;N种语言的开发，能写出来质量很高的Clean Code，常见的技术问题倒背如流 ——在 2025 年的这场海啸中，已经全部失去了意义。\n以前的编程是“过程导向”的。 我们会关注这个功能是“如何实现的”，提交上去的代码质量高不高。\n现在的编程是“结果导向”的。 我们只关注“要什么”，什么功能是高价值的，具体的实现大部分托管给 AI。\n未来的编程，而且随着 AI 的快速进化，我相信一个完全不懂编程的普通人，告诉AI想要一个什么功能的APP，AI 直接现场给你搓出来的时刻离我们不会太遥远。\n所以，现实是非常残酷的：传统的手工编码，已经在事实上死亡了。\n无论你是出身大专还是 985，无论你是拥有 3 年还是 15 年的经验，只要你还在以“手工”的方式输出代码，你就在被淘汰的名单上。\nPart5.1 传统编程的最后三块“自留地” 根据我的经验和见识，我认为未来人类程序员只剩下最后三个地方具有防御性的价值\n第一，架构设计与高级抽象能力。 AI 可以写出完美的零件代码，但它还无法在没有任何指导的情况下，全局思考如何让复杂的系统既健壮又灵活，这种“上帝视角”的全局观，依然需要人类的指导。\n第二，少数偏门与前沿方向。 AI最强的编程领域是什么？ 传统的前后端开发。这些领域在Github上有数不尽的训练材料喂给AI。而那些 AI 还没吃过足够多训练语料的领域——比如某些冷门的工业协议、前沿的量子计算、或者是私募基金内部密不透风的自研量化模型。AI没有训练材料，自然不会产生威胁。\n第三，对业务的深度洞察与“翻译”能力\n当用户提出大而空泛的整体性需求，例如\u0026quot;提高用户付费转化率\u0026quot;，AI并不能很好地解决这种问题。 AI 只擅长处理输入输出很明确的逻辑问题，只有人类能处理复杂业务场景下产出一系列的解决方案。\n另外，AI也不擅长发现用户的需求点。所以在未来，没有程序员，只有“带着技术大脑”的产品经理\nPart5.2 回归创意的本质 麦克阿瑟曾说: 老兵不死，只是凋零\n中文: 危机。\n他同时带有危和机两个字。 实际上每一次遇到危险的时候，往往也是一种难得的机会。\n2025 年的这场技术海啸，对很多人来说是危机（Danger），但我觉得，它也是酝酿着很多的机会（Opportunity）。\n在过去的就职岁月中，我经历过被公司\u0026quot;优化\u0026quot;的绝望，也经历过被 AI 碾压的无力。 但我惊喜地发现，在我放下了对“程序员”身份的执念时，我反而找回兴奋的感觉。\n我确实有很多想做的东西。\n“然后，旧的程序员死去了，新的创造者诞生了。”\n我不再是一个这也不会，那也不会需要花大量时间去自学的独立开发者， 我现在管着一支由 50 个 Agent（AI 智能体） 组成的“虚拟开发小组”。\n每一个AI时代的人，都可以成为一个超级个体，一个人可以完成过去一个开发小组的产出能力。\nAI出产品文档，AI出设计稿，AI实现前后端编码，AI实现自动部署运维\u0026hellip;\n我不再关心那一行代码写得漂不漂亮，我只关心需求是不是合理的，是否解决了现实世界的痛点，产出的功能点是不是能吸引用户。\nPart5.3 最后 “那些灵魂被重力束缚的人，终将被时代留在身后。”\n请别哀悼石器时代你用来砍树的斧头了，此时此刻，一台名为“AI Agent”的新时代高达正停在你的面前。 驾驶舱已经开启，我们该出发了。\n让我们一起去驾驶那台新时代的高达吧。\n“自由高达，出击！”\n以上。\n原创声明: 本文于2026年元旦后首发于我的个人博客 https://knowckx.pages.dev\n","date":"2026-01-08T23:00:15+08:00","permalink":"/p/ai-programming-2025-retrospective-part4/","title":"一个被淘汰的程序员，复盘 AI 编程突破性的2025(P4)"},{"content":"Part3 - 2025年：技术海啸的奇点时刻 如果说 2025 年以前 AI 编程的进化还遵循着某种\u0026quot;线性\u0026quot;的发展，那么整个 2025 年，AI编程是以\u0026quot;指数级的姿态\u0026ldquo;在狂飙突击。\nPart3.1 - 第一场海啸 成本屠夫 DeepSeek 2025 年初，当整个行业还在小心翼翼地计算着 Token 成本，硅谷巨头们还在喋喋不休地抱怨算力多么昂贵时，DeepSeek 带着它的 V3 和 R1 模型横空出世。\n它像一柄重锤，直接把大模型的价格打到了地板上。 它的成本比硅谷同类的大模型整整降低了 80% 到 90%！\n我清晰地记得，当时在DeepSeek的官网为 API-Key 充进去 10 块钱，竟然产生了一种\u0026quot;能用到地老天荒\u0026quot;的错觉。 因为 Token 太便宜了，我经常直接让AI一次性输出5个版本的解决方案，然后让他们互相辩论哪种才是最佳实践，只选一个最好的。\n这种\u0026rdquo;火力覆盖\u0026ldquo;的暴力美学在2025年前是不可想象的。\n但这件事的第一个深层价值不只是在于\u0026quot;省钱\u0026rdquo;，而在于它彻底终结了\u0026quot;Token 焦虑\u0026quot;，为后续的\u0026quot;多 Agent 协作\u0026ldquo;铺平了基础\n因为成本足够低，AI 终于摆脱了\u0026quot;一问一答\u0026quot;的简易对话模式，而是进入了\u0026quot;规模化折腾\u0026quot;的新纪元。 它可以肆无忌惮地加载你项目中数十万行代码的完整上下文，也可以在后台跑 100 次不同的重构方案，自我验证，互相比较，直到找出那个最优解扔到你脸上。\n当然成本只有几毛钱，比你去菜市场买棵大白菜还便宜。\nPart3.2 - 第二场海啸 Thinking模式普及 如果说成本降低是\u0026quot;量变\u0026rdquo;，那么 Thinking 模式（推理模型） 的普及则是 AI 编程从\u0026quot;辅助工具\u0026quot;迈向\u0026quot;生产力工具\u0026quot;的\u0026quot;质变\u0026quot;。\n这场变革的起点可以追溯到 2024 年 9 月 OpenAI 发布的 o1 系列。\n在那之前，大模型本质上是在进行\u0026quot;快思考\u0026quot;：根据你已经输入的内容，基于概率预测，蹦出下一个最可能的单词。 这种模式下，模型更像是在靠\u0026quot;猜\u0026quot;，虽然反应很快，但在并不擅长处理复杂问题，经常会产生\u0026quot;幻觉\u0026quot;现象。 也就是大家常提到的 AI 在一本正经地胡说八道，有时候还能编造一些现实中不存在的东西。\n进入 2025 年时，推理模型开始被各家大模型普及。Claude 3.7 Sonnet、Google Gemini 以及国产 DeepSeek R1 纷纷跟进，将 Thinking 模式列为标配。\n也是在这一年，大部分用户在使用大模型时习惯了输入完提示词后需要看着界面上的Thinking等待一段时间。 在这段沉默的时间里，AI 实际上在进行自我纠错，它会不断质疑自己提出的方案，自问自答，在输出结果前就过滤掉低级的逻辑错误，提高了回答的严谨性。\n当幻觉问题被攻克之后，AI 在开发流程中的定位发生了变化。 它不再是一个只能写写细节代码的\u0026quot;半个玩具\u0026quot;，而是一个能够参与复杂问题处理并提出有效解决方案的可靠工具。\nPart3.3 - 第三场海啸 Claude Code 如果说 DeepSeek 的成本优势 和 Think模式的普及使得AI编程真正落地成为了生产力工具，\n那么 2025 年2 月下旬 Anthropic 发布的 Claude Code 工具，则直接解决了AI下场动手的能力问题。\n简单科普一下：Claude Code 是一个可以安装在用户电脑上的应用程序，安装之后 AI 可以直接在用户电脑上阅读文件，修改文件、运行测试、执行命令。\n这是一个标志性的进展。\n在此之前，AI 始终是一个被锁死在浏览器对话框里的\u0026quot;咨询顾问\u0026quot;，它写出的代码，必须由我这位\u0026quot;资深开发工程师\u0026quot;亲自审查一遍，然后“复制、粘贴”到项目里才算数。\n我依然是代码的造物主，AI只是个生成部分代码，提高生产效率的工具。\n但是从此之后，AI就变成了一个可以亲自下场干活的\u0026quot;员工\u0026quot;。\n在 Claude Code 的支持下，我只需要输入一句，\u0026ldquo;帮我把 PrintDetail 这个函数的注释写一下\u0026rdquo;。\n然后 AI 会自己动起来:\n它先是用 grep 命令搜索项目里的文件，定位函数位置 然后读取文件内容，分析目标函数的定义和上下文，产生最佳修改方案 直接改写文件，把最工整的注释填上 整个过程，我只是坐在位子上喝咖啡。\n屏幕上的光标飞速闪烁，仿佛有一个隐形的“助手”就坐在我旁边，用比我快百倍的速度敲击键盘。\n我甚至没有打开 IDE，回头一看，需求就搞定了。\n此时此刻，AI 这已经不是“编程助手”了，而是“全自动代码机器人”。\n在我第一次看到终端里会自动跳出 grep、vi、git commit 的时候， 我感到一种后背发凉的恐惧。\n它不是在帮我，它是在真的在取代我的手。我意识到，人机的边界已经彻底模糊了。\n当一个 Git Commit（代码的提交记录）出现在代码仓库里，人类已经无法通过代码的质量、风格、甚至是其中的Bug，来判断这究竟是资深程序员写的，还是 AI 在三秒钟内生成的。\n当然，质量差的代码，我相信是人类写的。\n所以，当一个东西（高质量的代码）可以被低成本大规模无限量产，无法分辨也没必要分辨来源时，它的稀缺价值也就归零了。\n原创声明: 本文于2026年元旦后首发于我的个人博客 https://knowckx.pages.dev\n","date":"2026-01-07T23:00:15+08:00","permalink":"/p/ai-programming-2025-retrospective-part3/","title":"一个被淘汰的程序员，复盘 AI 编程突破性的2025(P3)"},{"content":"Part2 - 2025年初 粘贴/复制的黄昏 让我们回到 2025 年初吧。\n那时候，距离 ChatGPT 震撼世界其实也就两年左右。\n这段时间里 AI 在编程界的进展并没有那么快，在程序员圈子中，AI 只是从新闻里的“新鲜玩意儿”变成了“编程辅助必备”。\n当时的市场上，Claude 3.5 Sonnet 刚刚封神，GPT-4o 还在和它反复拉锯，而 Google 的 Gemini 1.5 Pro 以超长的上下文窗口被大家称赞。\n那时的我们，是怎么写代码的？\n大多数人的屏幕上都挂着一个网页窗口，进行着一种我感觉有点像是代码搬运工的操作：\n当我遇到一个记不清的API接口，或者一个输入输出都很明确的函数需求 把需求像喂饭一样喂给 AI，看着它一行行吐出代码 最关键的一步来了：全文选中（Ctrl+A） 手动复制（Ctrl+C），回到 代码编辑器(IDE) 粘贴（Ctrl+V） 注意，复制粘贴的过程很关键，这是我们作为程序员的尊严。尊严你懂吧？\n那时候的 AI，其实更像是一个能写出好代码的初级实习生。 它能写出漂亮的单点函数，带上详尽的注释说明，甚至能处理一些正则的撰写(一种对人类非常不友好的复杂的规则表达式)。\n不过，也就这样了。它就像一个只有“短暂记忆”的鱼，它并不能认识你项目里那几千个错综复杂的文件，更没有能力去你的电脑里执行一些简单命令。\n那还是一个\u0026quot;手工\u0026quot;的时代，一个“辅助”的时代\n那时候，行业里大家还是有一些傲慢的。\nAI编程，最多只能算一个加速开发的小工具罢了，有时候这个工具还会吐出一些不存在的API(AI幻觉)。\n真正的代码审核，架构设计、跨模块解耦、复杂的 Bug 排查，还是得靠我们这些‘人类高级码农’。\n当时的 GitHub Copilot 就像一个增强版的 Tab 语法补全，Cursor 还未大显身手，\n我一直以为这种“人机协作”的平衡会维持很久。\n原创声明: 本文于2026年元旦后首发于我的个人博客 https://knowckx.pages.dev\n","date":"2026-01-06T00:00:15+08:00","permalink":"/p/ai-programming-2025-retrospective-part2/","title":"一个被淘汰的程序员，复盘 AI 编程突破性的2025(P2)"},{"content":"Part1 - 代码正在变成“废钞” 2026 年 1 月 4 日，深夜。\n今天是元旦假期的最后一天，窗外是喧嚣渐散的灯火。我坐在书房里，屏幕的光映在发际线略微后移的额头上(只！是！略！微！)\nGoogle 新推出的 Antigravity 编辑器正安静地悬浮在我的主显示器上，配合着当今最强大的编程模型 Claude Opus 4.5 (Thinking)，光标正以一种非人的速度在屏幕上狂奔。\n我对着输入框给出了一个自然语言的指令：\n“为我的行情浏览 App 增加一个全局的数据缓存吧，所有模块请求数据时都优先走全局缓存入口”\n屏幕那头，AI 开始多线程并行翻阅了整个项目的近万行代码，随后进入了数十秒的Thinking(思考)模式。 然后输出了一套可靠的重构方案文档。\n我喝了一口已经有些凉掉的咖啡，只回复了两个字：\n“批准。”\n接下来的五分钟，就是属于 2026 年 AI 编程最新的科幻时刻：\n1 2 3 4 5 6 7 8 -\u0026gt; AI 自动制定了执行方案 -\u0026gt; 开始写核心的业务逻辑 -\u0026gt; 补全单元测试 -\u0026gt; 自动执行单元测试 -\u0026gt; 更新旧代码中的依赖 -\u0026gt; 自动唤起内置的 Chrome 完成功能验收 -\u0026gt; 最后，侧边栏弹出一个温柔的通知： “功能已完成，所有测试用例都通过了” 我靠在椅背上，翻阅着 AI 生成的那些从命名风格再到逻辑都没法挑剔的代码。\n回想起过去十年里那些在 Google 和 Stack Overflow 上疯狂翻找、在深夜为了处理一个线上问题常常 Debug 到眼眶通红的时光。\n那些让我引以为傲的编程技巧，那些曾经支撑我拿着高薪的技术护城河，在这一刻显得如此原始，就如同石器时代的农耕手作一样。\n我不仅是被职场淘汰了，现在也被 AI 淘汰了。\n站在 2026 年这个时间节点，我不得不承认： 我从大学开始苦练了十几年的编码技能和非常多的经验，正在像一张张过期的“废钞”一样，迅速失去价值。\n所以，我觉得我们需要坐下来，回头看看过去，特别是2025年这场正在发生的“AI 编程”大变革。\n这绝非过往那种 Go 挑战 Java、Rust 挑战 C++ 的渐进式演进，而是一场彻底的推翻式大变革。\n是每一个还在这个行业里挣扎的人，所必须面对的未来。\n原创声明: 本文于2026年元旦后首发于我的个人博客 https://knowckx.pages.dev\n","date":"2026-01-05T01:45:15+08:00","permalink":"/p/ai-programming-2025-retrospective-part1/","title":"一个被淘汰的程序员，复盘 AI 编程突破性的2025(P1)"},{"content":"最近这几天，我把常见的技术移民国家的政策情况，收集并整理了一下。\n我觉得这些信息对于其他人也是需要的，所以干脆写篇文章列清楚。 我的初心是尽量用最少的文字把各个国家和地区技术移民的关键信息展示出来。\n本文来源于个人Blog文章，我的个人博客 knowckx\u0026rsquo;s blog 源文地址 awesome-run\n假如觉得有问题或者需要补充，请回帖告诉我\n📁 本项目 GitHub 仓库 (欢迎 Star ⭐)\nawesome-run 是一份为技术人才准备的全球机遇指南 \u0026amp; 移民攻略。 本项目旨在为程序员这类专业技术人才，提供一个清晰、简洁、不断更新的各国（地区）永居与入籍路径对比。 通过标准化的表格，直观地展示关键信息，帮助你快速了解和比较不同选择。 免责声明：\n本项目所有信息仅供参考，不构成任何法律或移民建议。在做出任何决定前，请务必查阅各国官方移民局网站，以获取最准确、最新的信息。\n🔥 技术移民新风向 (更新日期 2026-04-01) 核心总结：\n英语国家（美加澳英）：难度史诗级提升。不再是\u0026quot;有个计算机学位就能润\u0026quot;的时代，拼的是高薪、高级职位、STEM特定领域（如AI/工程）。普通码农（Devs）分数线极高。 非英语国家（日德）：目前仍是政策红利期。不仅降低学历/语言门槛，还推出了\u0026quot;无Offer找工作\u0026quot;签证。 1. AU 澳大利亚：高不可攀 189独立技术移民：软件工程师 (Software Engineer) 这类热门IT职业的获邀分数普遍飙升至 90-100分。 这意味着 雅思8炸(20分) + 年龄满分(30分) + 学历(15分) + 单身/配偶(10分) + 工作经验(5-10分) 几乎要全部拉满。 485毕业生工签（重大削减）： 年龄上限从50岁断崖式降至 35岁（研究型硕士/博士除外）。这直接切断了很多大龄留学转码的路。 时长缩短，不再享有以前的\u0026quot;超长待机\u0026quot;。 2. 🇨🇦 加拿大：收紧 大幅削减配额：2025年永久居民目标下调至 39.5万（原定50万）。 工签收紧：毕业工签 (PGWP) 更难拿，部分专业不再符合资格；配偶工签限制增多。 临时居民上限：首次设立临时居民（留学/工签）上限，意在减少总人数。 3. 🇬🇧 英国：门槛大涨 工签年薪门槛暴涨：2024/2025周期，Skilled Worker Visa 的基础门槛涨至 £38,700（较之前大幅上涨）。 初级开发 (Junior Dev) 的薪资往往达不到这个标准，导致企业很难为应届生或初级人员提供Sponsorship。 4. 🇩🇪 德国：无需学位也能润，机会最大 机会卡 (Chancenkarte)：无需德国Offer，凭分数（满6分）即可拿签证去德国找工作1年。 利好IT：IT专业如果有2年以上相关工作经验，无需大学学位也能申请。 欧盟蓝卡 (Blue Card) 放宽： IT紧缺职业的年薪门槛降至 €43,759.80 (2025标准)。 无学历者：只要有3年相关IT工作经验，即可替代大学学位申请蓝卡。 5. 🇯🇵 日本：顶级人才优待，普通IT门槛低 J-Skip (特别高度人才)： 硕士学历 + 年收2000万日元（约100万人民币）= 1年后直接拿永住。 适合国内大厂P7/P8以上级别的精英直接空降。 J-Find (未来创造人才)： 全球Top 100大学毕业生（如清北复交浙科中等），毕业5年内，可直接申请2年签证去日本找工作/创业，无需Offer。 普通工签：最大门槛是日语，大部分岗位至少要有日语N2的水平。日本的工作机会多，对只有本科学历甚至大专的普通程序员依然非常友好，几乎没有配额限制 6. 🇸🇬 新加坡 (精英化) EP 门槛提高：2025年1月起，EP最低月薪门槛涨至 $5,600（金融业 $6,200） 基础知识 技术移民一般分为三个阶段\n工签，申请人拿到工签后就可以在这个国家合法工作。工签都没有那就是黑工，查到会被送到小黑屋遣返 PR(Permanent Resident), 也叫永居, 绿卡。拥有后可以在这个国家长期自由居住，工作。\n大部分权利和本国公民差不多，只是因为不能换护照，法理上你还是中国公民。\n大部分国家的PR都有移民监，例如每5年需要在这个国家呆满2年，否则PR可能被取消。 换国籍，完全成为那个国家的公民，可以换护照，从此你不再是中国公民，而是\u0026quot;外籍华人\u0026quot;。 技术移民的常见国家（地区）整理 国家地区具体整理表 国家/地区 获取永久居留权(PR) 移民监 - 保持永久居留 获取国籍/护照 公民身份福利 双国籍 亚洲 中国香港 香港永久性居民身份\n需在香港工作生活满七年 如果不是中国公民，且连续36个月或以上不在香港，可能会丧失永久居民身份。 - 申请香港特区护照，要求是中国公民和拥有香港永久居民身份\n- 一个外国人可以同时持有原国籍+拥有香港永久性居民身份，但是无法申请香港护照 - 中国籍香港永久居民可以申请\u0026quot;回乡证”到内地生活工作\n- 低税率，无资本利得税，无股息税，海外收入免税 不承认\n日本 永住者 PR\n- 常规路径，在日本连续居住10年\n- 高度人才签证，最快1年或3年后可申请PR\n- 审查纳税、社保缴纳情况及无犯罪记录 - 每7年更新一次在留卡\n- 离开日本超过一年，需要办理“再入国许可” 归化\n- 在日本连续居住510年以上(26年新政策)\n- 日语能力至少N3以上，需要和法务局官员进行面谈\n- 全球免签国最多的护照之一 不承认 新加坡 永久居民 (PR)\n- 持有工作准证(EP/SP)一段时间后申请\n- 审批不透明，没有明确的打分标准\n- 通常情况下，中国公民需要3到5年时间\n- 每5年续签一次再入境许可(REP)\n- 续签时移民局会评估与新加坡的联系情况，纳税、公积金CPF缴纳情况 归化：\n- 成为PR后至少2年。\n- 同样为评估制。\n- 第二代男性公民有服役义务 - 通过新加坡专属的“H1B1签证”去美国工作\n- 低税率，无资本利得税，无股息税，海外收入免税 不承认 大洋洲 澳大利亚 永久居民 (PR)\n- 根据技术移民打分制（189/190/491），准备好材料，提交EOI申请，进入池子里排队\n- 假如被移民局捞上了，他们发邀请，一步到位获得PR身份(甚至此时你从未登陆过澳洲，也没有澳洲的工作)\n- 竞争激烈。获邀的时间非常依赖职业和分数，从数月到数年不等； 移民监: 每5年在澳洲住满2年 - 持PR身份，过去4年内住满3年\n- 申请前1年住满9个月。\n- 通过公民考试（英语） 1. 获得新西兰\u0026quot;准PR\u0026quot;，可以去新西兰生活和工作\n2. 可以去美国工作，专属的\u0026quot;E-3签证\u0026quot; 承认 新西兰 永久居民 (PRV)\n- 获得新西兰的全职offer(核心)\n- 技术移民打分(SMC)超过6分，申请居民签证\n- 持有居民签证，在新西兰居住工作2年以上可申请 全球唯一永久绿卡\n回头签(PRV)是真正永久的，无任何居住要求，可无限期离境并随时返回。 - 申请前5年内，每年都住满240天\n- 新西兰国籍拥有澳洲的\u0026quot;准PR\u0026quot;，可以去澳洲工作，最快4年申请澳洲国籍 承认 北美 加拿大 永久居民 (PR)\n- 通过联邦打分系统、省提名计划\n- 需要2年左右时间 移民监: 每5年在加拿大住满至少2年 - 已经是PR，并且过去5年内至少住满3年\n- 审查纳税记录\n- 通过语言和公民考试\n- 通过\u0026quot;TN签证\u0026quot;去美国工作 承认 美国 永久居民（绿卡, Green Card）\n- 典型路径：H1B抽签 -\u0026gt; 雇主支持办EB2/3排期\n- 不确定性很大，中国申请人排期通常很长 - 离开美国超过6个月可能会受审查，\n超过1年则可能被视为放弃绿卡 - 持有绿卡满5年\n- 这5年内在美实际居住30个月\n- 通过英语和公民知识考试 - 地球最强国家的公民身份 承认 欧洲 德国 德国永居（PR）\n- 找到工作，持欧盟蓝卡 (EU Blue Card)登陆德国工作\n- 持蓝卡工作21个月（德语达到B1水平）或33个月（德语A1）\n注: 德语A1需要3~5个月左右\n- 不能连续离开德国超过6个月 - 连续居住5年以上\n- 通过入籍考试\n- 要求德语水平B1以上\n- 在整个欧盟区自由生活工作 承认 爱尔兰 可续签的长期居留许可 (Stamp 4)\n- 找到工作，申请关键技能就业许可(CSEP)\n- 持有CSEP工作2年，申请Stamp 4\n- Stamp 4 有效期为2年或5年\n- 续签时移民官会审查与爱尔兰的联系紧密度，无犯罪记录，纳税情况 入籍\n- 9年内累计住满5年\n- 申请前连续住满1年\n- 可以去英国工作生活\n- 唯一的，只会英语又想通过技术移民获得欧盟护照的国家 承认 荷兰 - 找到拥有担保资质(Sponsor)的雇主，薪资达标（30岁以上约 €5,331/月，30岁以下约 €3,909/月，2024标准）即可获签\n- 连续居住并缴纳社保满5年\n- 通过融入考试 (目前为A2水平) - 连续离开荷兰超过6个月，或连续3年每年离开荷兰超过4个月，PR可能失效。 - 连续合法居住满5年\n- 通过融入考试 - 英语环境极佳\n- 欧盟区自由生活工作\n- 顶尖的医疗与养老体系 不承认 ","date":"2025-08-09T23:15:47+08:00","permalink":"/p/awesome-run/","title":"awesome-run 中国打工人的润学指南"},{"content":"今天在写一个股票行情的接口爬虫，为了方便我写了一个发送 POST 请求的函数。\n签名类似于 do_post(url, post_data={})\n然后我把全部代码扔给Gemini进行检查优化，它竟然提示我 post_data={}这个写法存在一个经典的陷阱\n我半信半疑去写了个简单的测试，结果发现还真是这样。\n我觉得很多人和我一样，也不知道这一点，过来记录一下\n复现 代码真的很简单，你可以先预测一下它的输出是什么。\n1 2 3 4 5 6 7 8 9 10 def fn_test(input_map={}): print(f\u0026#34;函数开始时, input_map 是: {input_map}\u0026#34;) input_map[\u0026#34;token\u0026#34;] = 123 print(f\u0026#34;函数结束时, input_map 变成: {input_map}\\n\u0026#34;) print(\u0026#34;第一次调用\u0026#34;) fn_test() print(\u0026#34;第二次调用\u0026#34;) fn_test() 如果你认为两次调用的输出会一模一样，那就掉进陷阱里了。\n实际的输出结果是：\n1 2 3 4 5 6 7 --- 第一次调用 --- 函数开始时, input_map 是: {} 函数结束时, input_map 变成: {\u0026#39;token\u0026#39;: 123} --- 第二次调用 --- 函数开始时, input_map 是: {\u0026#39;token\u0026#39;: 123} 函数结束时, input_map 变成: {\u0026#39;token\u0026#39;: 123} 第一次调用的输出是正常的\n但是第二次调用的输出就不对了，为什么第二次调用的第一次打印会是{'token': 123} ？ 默认值不应该是一个空字典{}吗？\n原因 这里python为我们留了一个坑，当函数声明的默认参数是一个可变对象时，比如dict或者list类型，\n所有外面在调用此函数未提供具体参数的，他们实际上都会共享同一个对象。\n也就是第一次无参调用此函数，和第二次无参调用此函数，他们指向了同一个dict.\n那么假如在某次调用过程中，无意间修改了这个dict，这个修改就会污染后续所有无参的调用过程\n解决方法: 把签名改成\n1 2 def fn_test(inputMap:dict|None = None): pass 只声明他可能会属于dict这个类型，但是实际值默认是None\n反正记住一个简单的规则：不要使用可变对象（如 [] 或 {}）作为函数的默认参数就可以了。\nAI写业务代码真的太强了，我准备好退休了\n","date":"2025-07-24T11:12:34+08:00","permalink":"/p/python-mutable-default-arguments-pitfall/","title":"Python函数的默认参数为何不能是[]或{}？"},{"content":"gemini-cli 是 Google Gemini 官方提供的一款强大的命令行工具，但是我自己在使用时，第一步——认证授权时就遇到了问题。\n长话短说 我分别了测试了几种的挂梯子方式，最后的结论是:\ngemini-cli的auth流程依赖于启动浏览器进行OAuth 2.0认证流程，整个流程不仅需要你在浏览器有科学上网环境，终端内部也需要通过HTTP_PROXY来实现科学上网\n进一步说，为什么终端需要挂代理？\n因为gemini命令会启动一个本地Web服务器来接收Google的回调，并启动浏览器，所以整个过程都需要能顺畅访问Google服务。\n解决方案：为你的终端设置代理 请先在你的代理工具（如 Clash, V2RayN, Surge 等）中找到 HTTP 代理的端口号，通常是 7890, 10809 等。 然后根据你的不同操作系统输入以下命令 Windows (PowerShell) 1 2 3 $env:all_proxy=\u0026#34;socks5://127.0.0.1:7890\u0026#34; $env:HTTP_PROXY=\u0026#34;http://127.0.0.1:7890\u0026#34; $env:HTTPS_PROXY=\u0026#34;http://127.0.0.1:7890\u0026#34; Linux/MacOS 1 2 3 4 5 # 为当前终端会话设置代理 export HTTP_PROXY=\u0026#34;http://127.0.0.1:7890\u0026#34; export HTTPS_PROXY=\u0026#34;http://127.0.0.1:7890\u0026#34; # (可选，但推荐) export all_proxy=\u0026#34;socks5://127.0.0.1:7890\u0026#34; ","date":"2025-07-15T11:12:34+08:00","permalink":"/p/gemini-cli-auth-proxy-solution/","title":"一行命令解决gemini-cli在国内卡认证的问题"},{"content":" 吃饭时想对未来几年再做一次规划，突然想到了这个问题。所以花了2小时，把我目前认知下社会阶层金字塔梳理了一遍 其实这个认知，最好在上大学时或者刚工作几年能获得，可以提前少走很多弯路。\n很多人出身普通，又没人指导，就会觉醒得很慢。比如像我一样，30多岁了，才觉悟社会的运行方式。 中国社会层级表:\n层级 角色等级 体制内 泛体制 体制外 权力-资源-影响力 塔尖 国家领导人 政治局常委 无 无 决定国家大政方针，战略发展方向 上层 高级权力\n顶尖科学家\n精英资本层 副国级\n省部级 大型央企董事长/党委书记\n两院院士，上将 顶级民营企业家\n权力资本精英 对特定领域/地区有显著影响力\n与政治权力有着千丝万缕的联系\n对很多社会资源拥有分配权 中上层 高级管理\n专家学者\n中等资本层 厅局级\n大型事业单位/国企领导层\n知名专家学者\n少将、中将\n大型民企/外企高管层\n知名投资人\n知名艺术家/明星/作家\n头部主播，顶尖电竞选手 一定的政策建议权，高层的人脉资源\n行业内较高的话语权，影响行业标准\n富人阶层，生活优渥，日常消费奢侈品\n顶部择偶优先权 中层-3 中层管理\n各行业精英代表 处级\n副处级 事业单位/国企中层管理\n头部大学资深教授\n医院科室负责人\n上校、大校 大型民企/外企中层管理\n核心技术的专家岗位\n中等规模的企业主\n基金经理，律所合伙人\n知名主播，知名电竞选手 拥有一定社会地位\n通常拥有多套房产，拥有被动收入\n无明显生活压力，选择性消费奢侈品\n可能有发达国家的绿卡/国籍/房产\n高择偶优先权 中层-2 基层管理\n资深专业人士\n科级\n副科级\n事业单位/国企基层管理\n资深教师/医生\n少校、中校 大型民企/外企的基层管理\n资深技术岗\n小型企业主\n资深律师\n拥有一定专业技能或管理经验\n通常拥有一套自住房(可能有房贷)\n收入的主要构成还是工资性收入\n生活压力存在但是可以应对\n偶尔会消费奢侈品\n一流大学学历/海外留学经历 中层-1 社会的中坚力量\n一般专业人士 科员 普通事业单位/国企员工\n普通教师/医生 一般白领，普通专业岗/技术岗\n普通财务、工程师、设计师\n初级律师，高级技工\n个体工商户，小型餐厅经营者 所在城市没有房产，租房/存首付中\n依赖工资收入，存在明显的生活压力\n努力寻求向上流动的机会 中下层 基础性工作的劳动者\n低收入人员群体 / 事业单位编外人员 小微企业职员\n基础性服务业人员\n兼职外卖骑手\n普通农民/底层建筑工人 综合收入不高，对物价敏感\n工作岗位强度大，加班多，福利少\n可能没有好的学历\n择偶市场优先权低 底层 生活困难群体\n/ / 无积蓄的失业人员\n偏远地区贫困户，困难户\n微薄养老金的老人\n偏远地区的农民 经济状况差，生活拮据，缺少积蓄。\n缺乏稳定收入来源。\n依赖社会保障体系。 提几点感想:\n各阶层之间没有明确的界限，存在大量中间状态和交叉地带。\n比如一个拥有多套房产的退休普通教师，很可能比一些小型企业主更富裕。\n大部分人在社会中的一生，就是在玩一个角色扮演游戏:\n一个不断消耗角色有限的生命，进行智力上的投入，努力上的投入，来争取更高的社会层级的过程。\n大到改变事情走向，小到搞钱择偶，越高的层级优势越明显。\n从过去的历史看来，越到中后期，阶级的流动性越减少，阶级固化越严重，社会的活力也逐渐消失\n其实也不用那么卷，每个生命都有自己的活法，提升社会层级只是可选任务。\n每天正常上班，下班打打游戏整点个人兴趣，就这么度过一生，也是一种活法。自己开心才是最重要的。\n","date":"2025-05-25T02:40:34+08:00","permalink":"/p/china-social-class-pyramid/","title":"中国社会阶层金字塔"},{"content":"Mac在终端下通过设置环境变量来配置代理，Linux的终端也是一样，这里记录一下。\n打开你的代理软件，我用的是ClashX.\n找到软件的代理端口，我这边http和socks5都是7890\n终端里输入:\n1 export https_proxy=http://127.0.0.1:7890 http_proxy=http://127.0.0.1:7890 all_proxy=socks5://127.0.0.1:7890 设置好代理后，对该终端下执行的二进制都会生效，比如golang开发中的go get\n设置好之后可以用curl测试一下\n1 2 3 4 5 6 7 8 9 curl google.com # 输出HTML \u0026lt;HTML\u0026gt;\u0026lt;HEAD\u0026gt;\u0026lt;meta http-equiv=\u0026#34;content-type\u0026#34; content=\u0026#34;text/html;charset=utf-8\u0026#34;\u0026gt; \u0026lt;TITLE\u0026gt;301 Moved\u0026lt;/TITLE\u0026gt;\u0026lt;/HEAD\u0026gt;\u0026lt;BODY\u0026gt; \u0026lt;H1\u0026gt;301 Moved\u0026lt;/H1\u0026gt; The document has moved \u0026lt;A HREF=\u0026#34;http://www.google.com/\u0026#34;\u0026gt;here\u0026lt;/A\u0026gt;. \u0026lt;/BODY\u0026gt;\u0026lt;/HTML\u0026gt; ","date":"2024-12-05T08:00:00+08:00","permalink":"/p/%E4%B8%BAterminal%E8%AE%BE%E7%BD%AE%E4%BB%A3%E7%90%86/","title":"为Terminal设置代理"},{"content":"我发现身边不少写了3年以上的go开发并未注意到这一点, 感觉很多人都没养成好习惯。\nGo的默认写法，对错误处理的支持太简单了，这会导致排查错误不方便。\n最常见错误处理方式是在调用链一层一层向上抛错误，但是每层调用处直接return err这样是不好的， 因为最上层打印错误的时候只有一行错误字符串，中间的错误栈全部丢失了，这不方便定位错误在代码中的传递过程。\n推荐日常工作中应该尽量使用errors包带上栈信息。 包：\u0026quot;github.com/pkg/errors\u0026quot;\n这个包最大的作用就是用error加上stack记录\n示例代码：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;testing\u0026#34; \u0026#34;github.com/pkg/errors\u0026#34; ) func Test_Errors(t *testing.T) { err := CallFunc1() // 第1层调用 fmt.Printf(\u0026#34;%+v\\n\u0026#34;, err) // 必需是%+v 才能打印出stack信息 } func CallFunc1() error { err := CallFunc2() // 第2层调用 return errors.WithStack(err) } func CallFunc2() error { err := GetBaseError() // 第3层调用 return errors.WithStack(err) } func GetBaseError() error { return fmt.Errorf(\u0026#34;base error\u0026#34;) } 输出结果(调用栈)：\n1 2 3 4 5 6 7 8 9 10 11 12 === RUN Test_Errors base error github.com/Knowckx/Asuka/QuickTest.CallFunc1 /Users/i51111/dev/AsukaProj/Asuka/QuickTest/main_test.go:18 github.com/Knowckx/Asuka/QuickTest.Test_Errors /Users/i51111/dev/AsukaProj/Asuka/QuickTest/main_test.go:11 testing.tRunner /usr/local/go/src/testing/testing.go:1194 runtime.goexit /usr/local/go/src/runtime/asm_amd64.s:1371 --- PASS: Test_Errors (0.00s) PASS 注意：\n打印错误的时候需要%+v 才能打印出stack信息\n假如你的日志包用的zerolog，需要配置成这样:\n1 zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack 假如想判断具体的错误就使用Cause方法\n","date":"2024-10-29T08:00:00+08:00","permalink":"/p/golang%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86-%E4%BD%BF%E7%94%A8errors%E5%8C%85/","title":"golang错误处理 使用errors包"},{"content":"最近遇到一个需求，需要在数据库保存一组配置，每个配置都是一个bool值，表示表的那一列是否开启了\n按照最常见的做法，需要每有一个bool值就加一个表字段， 这样做有两个问题:\nbool值比较多的时候，需要在数据库里对应创建的字段就比较多，打开表一看全是bool值，信息密度低。 不方便动态调整，比如上线后业务说想要加一个bool配置，数据库表加字段并不方便，特别是生产环境 这时候我想起linux那个用来改文件权限的命令， chmod 777.\n这里的777是有含意的. 简单来说，\n1 = 001 执行权限\n2 = 010 写权限\n4 = 100 读权限\n7 = 111 全部权限\n3个7，777表示三种用户，都有全部权限。\nchmod 777意思就是把这个文件的所有权限都开放出来\n按照这个思路，我们日常遇到多个bool配置的时候，也可以使用这个方式通过一个int字段来存储大量的bool值，\n唯一需要注意的是需要在代码里固定好bool字段对应的顺序。\nTalk is cheap, show the code below:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 // golang code // a lot of bool fields type BoolConfig struct { IsAddName bool //1 IsAddAddress bool //2 IsAddEamil bool //3 IsAddAge bool //4 IsAddPwd bool //5 } // ToBin return binary like: FTFFF -\u0026gt; \u0026#34;01000\u0026#34; func (c *BoolConfig) ToBin() string { v := reflect.ValueOf(*c) rst := \u0026#34;\u0026#34; switch v.Kind() { case reflect.Struct: for i := 0; i \u0026lt; v.NumField(); i++ { val := v.Field(i).Interface().(bool) if val == false { rst = rst + \u0026#34;0\u0026#34; continue } rst = rst + \u0026#34;1\u0026#34; } } return rst } // binary to Decimal 1010 -\u0026gt; 10 | 111 -\u0026gt; 7 func BinDec(b string) (int, error) { ss := strings.Split(b, \u0026#34;\u0026#34;) l := len(ss) d := float64(0) for i := 0; i \u0026lt; l; i++ { f, err := strconv.ParseFloat(ss[i], 10) if err != nil { return -1, err } d += f * math.Pow(2, float64(l-i-1)) } return int(d), nil } func Test_ToBin(t *testing.T) { cf := new(BoolConfig) cf.IsAddAddress = true fmt.Printf(\u0026#34;Config: %+v\\n\u0026#34;, cf) resBin := cf.ToBin() fmt.Printf(\u0026#34;Bin: %+v\\n\u0026#34;, resBin) // 01000 dec, err := BinDec(resBin) if err != nil { panic(err) } fmt.Printf(\u0026#34;dec: %+v\\n\u0026#34;, dec) // 8 return } 上面这段代码，主要的意思就是把配置视为一个二进制数，二进制的每一位通过1和0两种状态表示true和false\n这样通过存一个uint类型就可以实现存很多个bool类型的配置\n","date":"2024-04-27T08:00:00+08:00","permalink":"/p/%E4%BD%BF%E7%94%A8%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%9D%A5%E5%AD%98%E5%82%A8%E5%A4%9A%E4%B8%AA%E5%B8%83%E5%B0%94%E5%80%BC/","title":"使用二进制来存储多个布尔值"},{"content":" 最近个人Blog建好了，慢慢把过去零散的文章进行整合\n原创声明: 本文首发于我的个人博客 原文链接地址\n写这篇文章来源于自己切身的看牙经历后，对背景知识的调研，希望可以唤起更多人对牙齿保健知识的重视\n牙齿深龋时牙医为什么优先做根管呢？\n保髓治疗+补牙 根据炎症情况，保活髓的尝试存在失败概率，假如患者不理解保髓的意义，容易导致医患纠纷。\n医生视角:我在帮你保活髓，给了你最好的治疗方案。别的医生都懒得替你做保髓治疗。\n患者视角:我去你那看了一次，没成功，又要给我做根管？要我付两次钱？？ 庸医！！\n实际上从客观视角来看，一旦保髓成功，对患者来说这是最好的结局，保住了一颗活牙。\n并且从费用的角度出发，保髓治疗费用低，国内价格通常在1000元以内\n关于根管治疗 根管治疗在名字上有误导性，很多患者在接受这种术式前，并不明白根管实际上是一种\u0026quot;杀死牙神经\u0026quot;的治疗方式\n1.根管本身成功率高，不容易导致医患纠纷\n2.根管花费更多。治疗+牙套 费用3000元~6000元\n（请注意牙套的费用，很多医生不会自动和你说这事）\n3.还有一个隐藏的信息差：根管后这颗牙就是没有牙髓的死牙。\n因为没有了牙髓提供的养分，无论你戴不戴牙冠，最后牙齿都会脆化崩裂。\n所以在患者做完根管之后的那一刻起，时间短一点5年，时间长一点15年，走到种牙这一步只是时间早晚的问题罢了。\n后续的流程就是 拔牙 -\u0026gt; 种牙，种牙治疗费用上万。\n正是因为这个原因，国内有部分无良牙医滥用根管，把一个患者可以挽救保髓的龋齿直接做根管，通过过度医疗实现长线收费牙奴化患者。\n这是个利益至上的世界，假如有一个医生愿意告诉你除了根管之外还有保髓的方案可以尝试，\n恭喜你，你遇到了一个愿意为患者考虑的好医生，他在设身处地为你提供最好的方案。\n(推荐加上他微信，值得长期深交)\n而事实上，很多医生利用信息差滥用根管，中龋，深龋近髓也直接根管。\n从来不想给你保髓的机会，甚至都不想告诉你有这个选项。\n毕竟，又有谁会和钱过不去呢？\n含氟牙膏 最后，我想提示一下，平时刷牙一定要用含氟的牙膏！！\n很多牙膏厂商为了标新立异打广告，利用消费者专业知识的匮乏，出了很多无氟牙膏，这种行为属于为了商业利益毫无道德下限！\n买牙膏一定要看配料表，一定要含氟。\n我推荐每个人都去补含氟牙膏的历史由来和背景知识\n含氟牙膏是人类历史上保护牙齿性价比最高的方案！没有之一！\n","date":"2024-03-10T08:00:00+08:00","permalink":"/p/tooth-needs-pulp-preserved-before-root-canal-treatment/","title":"先保髓，再根管：牙齿保健与就诊经历记录"},{"content":"今天写一个SQL时需要使用开窗函数，结果发现Mysql在8.0后才开始支持CTE和开窗函数.\n我们目前所使用的Mysql版本比较低，所以想了一种通过子查询来模拟的方法， 记录一下。\n举个例子：\n现在有一张普通的表 table_age，只有三列：ID, name, age.\n现在想让查询结果的每一行都带上年龄的总和，也就是实现类似开窗函数的效果\n在Mysql 8.0之后 直接用开窗函数就行\n1 2 3 4 5 SELECT SUM(age) OVER() AS \u0026#39;总年龄\u0026#39;, table_age.* FROM table_age; 在Mysql 8.0之前， 可以像下面这样写：\n1 2 3 4 5 6 select tempT.totalAge as \u0026#39;总年龄\u0026#39;, table_age.* from table_age, (select sum(age) as \u0026#34;totalAge\u0026#34; from table_age) tempT 其他开窗函数也可以用这样子查询的方法，去模拟实现。\n","date":"2024-01-26T08:00:00+08:00","permalink":"/p/mysql%E6%A8%A1%E6%8B%9F%E5%BC%80%E7%AA%97%E5%87%BD%E6%95%B0/","title":"mysql模拟开窗函数"},{"content":"int32的取值范围 今天刷题时需要用到int32的取值范围，查了下是“-2147483648”到“2147483647”，有没有办法快速表示这个数呢？\n在我翻了其他人的leetcode提交后发现一个有趣的代码：\n1 2 3 4 5 6 7 8 9 const ( int32min = -1 \u0026lt;\u0026lt; 31 int32max = 1\u0026lt;\u0026lt;31 - 1 ) // 写个UT打印一下, 也是正确的。 func Test_PrintConstInt32(t *testing.T) { fmt.Println(int32min, int32max) // -2147483648 2147483647 } 关于位运算很多东西都扔回给老师了，今天再补一下知识\n位运算，左移和右移 \u0026laquo; 表示左移 对于一个正数 左移N位 = 乘2n次方\n比如：\n1100（12） 左移1位 –\u0026gt; 11000(24)\n\u0026gt;\u0026gt; 表示右移 对于一个正数 右移N位 = 除2n次方\n比如：\n1100（12） 右移1位 –\u0026gt; 110(6)\nint32的范围大小: [-1 \u0026lt;\u0026lt; 31，1\u0026lt;\u0026lt;31 - 1]\n精准，高逼格，High Level!\n","date":"2023-07-29T08:00:00+08:00","permalink":"/p/%E4%BD%8D%E8%BF%90%E7%AE%97%E8%A1%A8%E7%A4%BAint%E7%9A%84%E8%8C%83%E5%9B%B4/","title":"位运算表示int的范围"},{"content":"想要实现一个无锁的并发程序编写，那么直接对变量进行原子操作就是很好的选择，趁这个机会把go提供的几个原子方法学习一下。\ngo在sync/atomic包里对Int32，Int64等几个基本类型都定义了相同的一套方法， 下面以Int64为例：\n几个方法的含义比较简单，从函数名就能看出含义。\nStoreInt64 存\nLoadInt64 取\nAddInt64 加法\nSwapInt64 交换\nCompareAndSwapInt64 判断并交换\n1 2 3 4 5 6 7 8 9 10 11 12 13 func Test_AtomicFunc(t *testing.T) { var counter int64 = 0 atomic.StoreInt64(\u0026amp;counter, 10) fmt.Println(counter) // output: 10 res := atomic.LoadInt64(\u0026amp;counter) fmt.Println(res) // output: 10 atomic.AddInt64(\u0026amp;counter, 2) fmt.Println(counter) // output: 12 atomic.SwapInt64(\u0026amp;counter, 22) fmt.Println(counter) // output: 22 atomic.CompareAndSwapInt64(\u0026amp;counter, 22, 32) fmt.Println(counter) // output: 32 } 我们平时用地比较多的是CompareAndSwap（简称 CAS）方法，假如值不等于old，那就设置成一个新值new. 这可以在不加锁的前提下完成对变量值的判断和更新\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func Test_CompareAndSwap(t *testing.T) { var first int64 = 0 for i := 1; i \u0026lt;= 1000; i++ { go func(i int) { if atomic.CompareAndSwapInt64(\u0026amp;first, 0, int64(i)) { log.Println(\u0026#34;第一个完成赋值的goroutine\u0026#34;, i) } }(i) } time.Sleep(2 * time.Second) log.Println(\u0026#34;最后的值 num:\u0026#34;, atomic.LoadInt64(\u0026amp;first)) } // 输出结果: === RUN Test_CompareAndSwap 2022/05/07 00:11:18 第一个完成赋值的goroutine 13 2022/05/07 00:11:20 最后的值 num: 13 我们可以看出虽然开了1000个goroutine,但是只有第13个抢到了赋值的机会.\n在他赋值之后，其他的goroutine都会compare失败而退出，从而不需要加锁就完成了竞争条件的设定。\n简单好用~\n","date":"2023-03-09T08:00:00+08:00","permalink":"/p/go%E7%9A%84%E5%8E%9F%E5%AD%90%E6%93%8D%E4%BD%9C/","title":"go的原子操作"},{"content":"今天刷题leetcode no.78-子集问题，其中遇到一个Slice的语法坑，\n查了半小时最后才定位是切片的使用问题。\n搞定之后顺便翻阅了一下slice的实现原理，把以前总结的点也放一起做个小记录。\nslice的实现 先看一下slice的结构体(https://go.dev/src/runtime/slice.go)定义：\n1 2 3 4 5 type slice struct { array unsafe.Pointer len int cap int } 一个切片本身指向了一个底层数组，\nlen表示目前的元素数量，cap表示这个slice最大的元素容量\n注意区分len和cap len和cap是第一个要理清的概念，特别是当我们使用make去创建新切片时\n1 2 3 s1 := make([]int, 3) s2 := make([]int, 0, 3) fmt.Println(s1, s2) 在上面的代码中，s1的输出结果是[0 0 0]， cap=len=3,有3个元素，最大容量是3，3个元素都是这个类型的初始值，也就是0\n而s2的结果是[]，cap=3 len=0, 目前是0个元素，是个空切片。\n扩容的性能问题 容量 = cap, 是目前切片已经预分配的内存能够容纳的最大元素个数\n我们通过append操作向切片加元素，一但超过了切片的容量，就需要分配新的内存，并将当前切片所有的元素拷贝到新的内存块上。\n这里就有一道喜闻乐见的面试题了，\ngo的切片扩容机制是什么样的\n而这个答案直接看源码就能找到，注释是这么写的：\n1 2 // Transition from growing 2x for small slices // to growing 1.25x for large slices. This formula 切片在容量在比较小的时候，容量是通过x2的倍数扩大的， cap当达到一个阈值时，以1.25倍的方式扩容。\n所以我们写代码的时候要注意两点：\n尽量在声明时就确定切片的大小，免得反复扩容。 大容量的切片应该复用，减少频繁申请内存。 Slice的切片操作不会生成新的数组 这个问题就是我今天遇到的，当我们对一个切片执行切片操作时，新的切片指向的是原来切片的底层数组。\n所以当我们向新的切片append的时候，也改变了老切片的值\n示例代码：\n1 2 3 4 5 6 7 func Test_Slice(t *testing.T) { s1 := []int{1, 2, 3, 4} s2 := s1[:2] // 切片操作 s2为[1 2] s2 = append(s2, 5) // s2为[1 2 5] fmt.Println(s1) // 现在s1是多少呢? } 最后s1的值是 [1 2 5 4]!! 很难理解吧。\n因为上面的代码里，s2是从s1切片出来的，向s2添加元素后，实际上也在修改s1的值\n想要生成一个完全不同的底层数组怎么办？\n引申：拷贝一个切片的最佳实践\n1 2 3 4 5 func CopySlice() { nums := []int{1, 2, 3} newNums := make([]int, len(nums)) copy(newNums, nums) } ","date":"2023-01-02T08:00:00+08:00","permalink":"/p/%E5%85%B3%E4%BA%8Egolang%E5%88%87%E7%89%87%E7%9A%84%E5%87%A0%E4%B8%AA%E6%B3%A8%E6%84%8F%E7%82%B9/","title":"关于Golang切片的几个注意点"},{"content":"k8s端口转发原理 我们知道有一些数据库或者服务的API设有白名单机制, 只有在特定的生产集群内部才能访问。\n因为不对公网暴露，我们本地肯定是访问不了的。\n而在日常工作嘛，总会有一些紧急需求需要连上这些服务，在本地进行一些调试、触发、执行SQL什么的临时性操作， 那这时候就需要把对应的端口转发出来。\n我们知道kubectl port-forward可以把k8s的service转发到你的本地，\n因此一个常见的做法就是在k8s上创建一个pod专门作为转接，由这个Pod去访问目标端口，\n同时通过port-forward把对应的pod和端口转到你本地，这样就可以在本地进行连接调试了。\n下面发一下我日常使用的脚本：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 set -e # args KubeCfg=test--prod # 目标集群的kubeconfig -- kubecm的列表项 NameSpace=test-ns # 目标ns REMOTE_HOST=test.remotehost.com # 需要转发的目标host REMOTE_PORT=5432 # 需要转发的目标port LOCAL_PORT=5439 # 本地port TEMP_POD_NAME=test-portjump # 用于转发的pod名称 kubecm switch $KubeCfg # 脚本退出时自动清理掉pod function cleanup { echo \u0026#34;cleanup...\u0026#34; kubectl delete -n $NameSpace pod/$TEMP_POD_NAME --grace-period 1 --wait=false } trap cleanup EXIT kubectl run -n $NameSpace --image marcnuri/port-forward $TEMP_POD_NAME \\ --env REMOTE_HOST=$REMOTE_HOST \\ --env REMOTE_PORT=$REMOTE_PORT \\ --env LOCAL_PORT=$REMOTE_PORT \\ --port $REMOTE_PORT \\ --restart=Never kubectl wait -n $NameSpace --for=condition=Ready pod/$TEMP_POD_NAME kubectl port-forward -n $NameSpace pod/$TEMP_POD_NAME $LOCAL_PORT:$REMOTE_PORT \u0026amp; while true ; do sleep 60 ; nc -vz 127.0.0.1 $LOCAL_PORT ; done # 保持连接 两个注意点：\n这个脚本需要安装一个kubecm，这是一个常用的切kubeconfig的工具 脚本的最后写了一个循环，是因为kubectl port-forward这个命令在5min中没有操作的话会自动断开连接。\n实际工作中你可能出去倒杯咖啡就断了。因此需要需要这个循环来保持连接。\n(这个东西我翻了很多资料，这应该是client这边最好的保持连接的方式了) 不要滥用这个脚本。\n理论上这个脚本可以让你连上生产环境的一切地址~ 一定要小心呐。一定要小心呐。一定要小心呐。\n","date":"2022-07-22T08:00:00+08:00","permalink":"/p/k8s%E7%AB%AF%E5%8F%A3%E8%BD%AC%E5%8F%91%E8%84%9A%E6%9C%AC/","title":"k8s端口转发脚本"},{"content":"这个需求其实在日常写代码中比较常见，比如调一个接口返回来一个结果，\n这时想看一下这个变量有什么内容\n假如使用fmt的%v 或者 %+v, 那只能打印简单类型的结构体，对于嵌套的复杂结构体就不行了。\n而使用反射去一层一层解析又太麻烦，我想找一种简单方便的方式。\n昨天中午正在吃饭时，突然想到可以通过json的Marshal方式输出变量的内容。\n写一下示例代码：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 type User struct { Name string Age int Home *Home } type Home struct { Address string PeopleLives []string PeopleInfo map[string]string } // 输出一个结构体嵌套的变量 func GenTestUser() *User { ho := Home{} ho.Address = \u0026#34;beijing\u0026#34; ho.PeopleLives = []string{ \u0026#34;Raman Kalita\u0026#34;, \u0026#34;Abhishek Garg\u0026#34;, } ho.PeopleInfo = map[string]string{ \u0026#34;Raman Kalita\u0026#34;: \u0026#34;civil servant\u0026#34;, \u0026#34;Abhishek Garg\u0026#34;: \u0026#34;teacher\u0026#34;, } us := User{} us.Name = \u0026#34;Alise\u0026#34; us.Age = 31 us.Home = \u0026amp;ho return \u0026amp;us } func Test_PrintJson(t *testing.T) { us := GenTestUser() PrintJson(us) } // PrintJson 通过json序列化打印数据内容 func PrintJson(in interface{}) { res, err := json.MarshalIndent(in, \u0026#34;\u0026#34;, \u0026#34;\\t\u0026#34;) // beautiful json if err != nil { log.Error().Stack().Err(err).Send() return } Printf(string(res)) } 输出的结果:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 { \u0026#34;Name\u0026#34;: \u0026#34;Alise\u0026#34;, \u0026#34;Age\u0026#34;: 31, \u0026#34;Home\u0026#34;: { \u0026#34;Address\u0026#34;: \u0026#34;beijing\u0026#34;, \u0026#34;PeopleLives\u0026#34;: [ \u0026#34;Raman Kalita\u0026#34;, \u0026#34;Abhishek Garg\u0026#34; ], \u0026#34;PeopleInfo\u0026#34;: { \u0026#34;Abhishek Garg\u0026#34;: \u0026#34;teacher\u0026#34;, \u0026#34;Raman Kalita\u0026#34;: \u0026#34;civil servant\u0026#34; } } } 可以看到Home这个结构体也打印出来了，无论是map还是slice输出都是OK的， PrintJson这个函数才几行？ 简单好用~\n","date":"2022-07-02T08:00:00+08:00","permalink":"/p/go%E6%89%93%E5%8D%B0%E5%B5%8C%E5%A5%97%E7%9A%84%E5%A4%8D%E6%9D%82%E7%BB%93%E6%9E%84%E4%BD%93/","title":"go打印嵌套的复杂结构体"},{"content":"k8s secret 作为一个后端来Infra Team工作有一年了，总结一下目前我已知的获取k8s secret的方式，因为这个需求日常经常会用到\n1. 手动Decode 这最常见的方式，使用命令\n1 kubectl get secret testsecret -o yaml 然后手动把base64编码后的字符串复制出来\n再去执行echo xx | base64 -D\n这种应该是大部分使用k8s的开发人员最常用的方式。\n缺点是中间有一段手动复制的操作，用上了鼠标，效率比较低，不够high level~\n2. 使用jsonpath 这个其实是今天发现的，用jsonpath可以一条命令里完成取secret的操作\n比如有下面的secret\n1 2 3 4 5 kind: Secret apiVersion: v1 data: properties: ClJFRElTX0hPU1Q9ZGFhcy1yZWRpcy1iZD user.password: hcy1yZWy1yZWRpcyRpcy1iZDClJFRElTX0hPU1Q9ZGF 这个secret有两个值， 假如我们想取properties的信息,可以使用\nkubectl get secrets/testsecret -o jsonpath=\u0026quot;{.data.properties}\u0026quot; | base64 -D\njsonpath可以把对应需要的文本筛选出来，就很舒服\n另外, 假如key里面已经有了一个字符.应该怎么办？ 可以使用:\nkubectl get secrets/testsecret -o jsonpath=\u0026quot;{.data\\.properties}\u0026quot; | base64 -D\n3. 使用插件kubectl view-secret kubectl view-secret是一个kubectl的插件， 安装之后可以直接通过kubectl view-secret命令看secret的内容， github可以搜到，不多说了\n我身边很多同事在用这个插件，肯定是好用的\n4.安装Lens Lens是一个k8s可视化工具，可视化工具嘛，鼠标点点就出来了。\nLens对应查pod，查pod里的log什么的都挺方便的，懒人必备~\n综述，以上几种方式，我的推荐顺序是 3 \u0026gt; 4 = 2 \u0026gt; 1\nkubectl view-secret装好后方便一些\n下班~！\n","date":"2022-03-02T08:00:00+08:00","permalink":"/p/%E6%80%BB%E7%BB%93%E4%B8%80%E4%B8%8Bk8s%E6%9F%A5%E8%AF%A2secret%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F/","title":"总结一下k8s查询secret几种方式"},{"content":" 最近个人 Blog 迁到 .top 域名后，我花了不少时间排查 Google 收录的问题。\n这不是一篇 SEO 教程，只是一次很直接的踩坑复盘。\n背景 最开始我的 Blog 是放在 github pages 的，一切岁月静好。\n去年中的时候想买个独立域名玩，然后看 .top 域名便宜，就直接买了10年，随便拿来放个人网站。\n一开始我的想法很简单。\n域名能用，站点能跑，内容能发，就够了。至于后面 SEO 怎么样，我默认 Google 和 Bing 都会自己处理。\n后来我把 sitemap 分别提交给了 Bing 和 Google。\n结果很快就分出来了。\nBing 大概一天后就收录了。 sitemap对应的文章都能在 Bing 里搜索到。\nGoogle 那边却一直卡着。\n页面状态长期停在：\n已抓取 - 尚未编入索引\n此类网页未编入索引或不会显示在 Google 搜索结果中\n这个状态最让人难受的地方，不是它完全没抓到，而是它已经抓到了，却迟迟不放进索引。\n经过 我不是没折腾。\n我一直以为是自己的站点有问题，这段时间里，我试过不少常规调整，也用 GPT 5.4 / 5.5 做过全站扫描和优化，把 AI 能想到的优化方向基本都过了一遍。\n比如：\n检查 sitemap和robots.txt 文章 SEO 优化，调整页面标题和描述 增加外部的外链提高权重 其他一系列 AI 提示的细节优化 手动提交新文章 URL 观察 Google Search Console 里的状态变化 但折腾下来，最明显的事实还是没变。\nGoogle 继续抓取，继续观察，继续不编入索引。\n反过来看 Bing，几乎没怎么拖，直接就进了。\n这两个搜索引擎的差异，让我最后不得不接受一个很现实的判断。\n结论 对个人 Blog 来说，Google 确实会更挑剔域名后缀本身的“权威”。\n这不是说 .top 一定不能收录。\n也不是说换了 .top 就必然完蛋。\n但至少在我这次站点上，.top 这种便宜后缀 + 个人新站，明显没有给 Google 足够的信任感。\n.top、.xyz 这类后缀，只适合“先搭起来再说”的网站，没有很强大的外链资源不要指望 Google 收录。\n所以这篇文章就是给后来者提个醒：\n买域名别只看便宜。\n如果你重视 Google 收录，域名后缀这件事，最好一开始就想清楚，优先找对 SEO 友好的后缀。\n以上。\n原创声明: 本文首发于我的个人博客 原文链接地址\n","date":"2026-05-09T07:07:29+08:00","permalink":"/p/top-domain-google-not-indexed/","title":"建站经验分享1: 避雷 .top 域名"},{"content":"场景 我的笔记本是 机械革命无界15x暴风雪 CPU是AMD R7-H-255，也就是7840H/8745H/8845H的马甲U\n最近遇到的问题是笔记本在长时间低负载待机时，大概2个小时后就会自动重启，\n对应的Windows系统事件是41，也就是非正常关机\n这很恼火，因为我有些服务跑需要7x24小时运行。\n解决 然后我查了一圈资料，发现这是一个复杂问题，和系统/BIOS/芯片组驱动都有关系。\n核心原因是长时间待机时，CPU的负载太低了，AMD的CPU就会自动重启。\n网上给的很的多方案都是尝试加负载来解决:\nBIOS关闭CPU的节能模式 / 用软件锁最低频率 / 电源计划设置最低处理器 等等\u0026hellip;\n我相信这些方案都是有效的，但是解决问题都不够优雅。\n经过我的多次测试，发现了一个最简单的解决方案:\n卸载你目前的AMD的驱动软件 去AMD官网安装最新的25.12.1版本 然后点开管理更新-\u0026gt; 更新AMD的芯片组驱动（Chipset Drivers） 更新完成后，经过我这一周的实机测试，没有再出现异常的重启。\n这个问题困扰我3个小时，希望对你们有用。\n","date":"2025-12-21T10:24:38+08:00","permalink":"/p/solving-amd-cpus-standby-restarting/","title":"解决AMD CPU 长时间待机会自动重启的问题"},{"content":" 个人文章备份 -\u0026gt; V2EX原帖\n最近\u0026quot;全民社保\u0026quot;的事沸沸扬扬，这个问题我之前就关注了很久，这里想发表一下看法\n历史背景 在2014年之前，公务员和事业单位人员是不缴纳养老金的，个人账户是空的。\n为了补偿这段“工龄”，国家出了一个规则，他们之前的工作年限被视为“视同缴费年限”。\n这段长长的“视同缴费年限”是导致很多老人能拥有每月8千甚至1万+养老金的主要原因。 那么，因为“视同缴费年限”所导致的巨额资金缺口从哪里出？\n财政给补了一部分(而实际上财政的钱也是全民的) 划转部分国有资本填入社保基金(“全民所有”的资产为历史缺口买单) 还有大部分钱从目前参保者的统筹账户里转移。 老龄化的危机 这次事件的起因，就是因为现行的养老金随着少子化，会有一个巨大的收支窟窿\n之前的10年，老人少，年轻人多，几个年轻人养一个老人，体系能运行。\n但是未来年轻人会越来越少，老人越来越多，肯定运行不下去(参见社科院对2035年养老基金结余耗尽的预测\n所以，现行的养老金体系，在人口结构快速老化的背景下，表现出了类似‘庞氏游戏’的特征：\n非常依赖新增参保人来维持对退休者的转移支付。\n一旦新增不足，这个体系将面临巨大的支付压力甚至崩溃风险。\n现在强制每个人都缴，可以扩大新增参保人数量，通过他们的统筹账户来修补收支窟窿，延缓体系崩溃的时间点。\n支持者的解析 然后为什么一些人支持\u0026quot;全面社保\u0026quot;的政策。这些人在我看来，主要分为两类:\n认知偏差。部分人特别是学生，或者工作没多久，其实并未花时间去深入了解现行养老金体系背后的历史问题和代际转移问题，更多地是从‘社会保障’和‘老年福祉’的宏大叙事出发的。 利益相关。部分支持者本身是该制度的直接或间接受益者，他本人或者家庭成员目前享受着视同缴费带来的较高退休金，ta有维护现有体系的天然立场。 无法回避的五个问题 假如有人觉得现行养老金制度是公平的 可以来试着回答以下问题:\n为什么只强制社保，而不是在国内强制双休，强制加班必须给加班费？ 为什么社保账目不能公开透明化的，不受公众监督审查？\n部分地区已经在对社保基金上下其手，讲不清资金去了哪。对养老金的具体分配遮遮掩掩。 很多人退休之前因为意外发生了断缴，或者缴费不满15年(未来可能是20年)，那他的统筹账户里这么多钱，这块“香饽饽”去了哪？ 为什么社保明明是一种福利，但是又不能放弃。你见过\u0026quot;不能放弃\u0026quot;的\u0026quot;福利\u0026quot;吗？ 如果我们转向一个更透明、更公平的改革方向。使用完全的个人储蓄账户制度（类似新加坡的公积金模式）\n养老金缴费完全进入个人账户，权责清晰，多缴多得，外人也不能伸手进去乱摸。\n请问全民社保还会被抵触缴纳吗？ ","date":"2025-08-12T17:21:56+08:00","permalink":"/p/the-pension-system-and-its-young-tributaries/","title":"我愿称之为史上最大的财富转移：养老金与年轻的供奉者"},{"content":" 我始终认为，唤醒更多的人来关注我们社会中确实存在的问题，是有公共价值的。\n我在其他平台发的帖子可能会被删。所以在自己的blog里把文字留存一份。\n正文 🦊对🐔说：“你每个月给我5个鸡蛋🥚，我帮你存着。等你老了不能下蛋了，我再按月还你5个。”\n整个村的鸡，都开始给狐狸交蛋。\n狐狸呢，自己吃一些，再给家族里的亲戚送15个，手头紧了就卖掉一些过渡下，小日子过得那叫一个滋润。😎\n\u0026hellip;\n🐔觉得自己过得太苦了，活得不明不白，干脆决定不生小鸡了。\n狐狸一看急了：”哎呀！你怎么能不生了呢？没有小鸡，鸡村的鸡岂不是越来越少了？“\n它清了清嗓子宣布：“好消息！现在生一只小鸡🐣，奖励一个鸡蛋哦！”\n\u0026hellip;\n又过了好多年，鸡终于老了，颤颤巍巍地去找狐狸要蛋。\n狐狸慢悠悠地说：“哦，最近啊文件改了，领蛋的年龄推迟了，你再等几年吧。”📜\n\u0026hellip;\n鸡没办法，又等了几年，它更老了，背都直不起来了。它再次找到狐狸。\n这一次，狐狸给了它\u0026hellip;\u0026hellip;\n一个鸡蛋🥚。\n\u0026hellip;\n你看，狐狸还是很有信用的，说给，就真的给了。\n","date":"2025-08-06T16:22:29+08:00","permalink":"/p/the-fable-of-the-fox-and-the-chicken/","title":"狐狸和鸡的故事：养老金与代际转移的思考"},{"content":"场景: Ctrl+Fn唤醒小娜的问题 联想的笔记本，按住键盘上的Ctrl+Fn后，会尝试唤醒微软小娜。\n假如你的电脑已经卸载了小娜，那么就会出现一个弹窗，提示:\n需要使用新应用以打开此ms cortana2\n这是因为小娜卸载后，他找不到这个应用，引导你去windows的应用商店下载。\n解决方案 这个问题很烦人，所以我花了1小时多搜了一圈资料，找到了解决方案。\n有两个方式可以解决，可以试一下:\n方案1 卸载联想的应用Lenovo Hotkeys\nLenovo Hotkeys是联想的一个系统快捷键设置应用，可能装机时自带了，也可以后期从win10商店里下载。 这个应用管理了Fn键的效果，还有开启大小写的面板提示，开启NumLock键的面板提示等。\n直接卸载此软件可以解决Ctrl+Fn的问题:\n进入Windows设置（快捷键: Win+I）\u0026gt; 应用 \u0026gt; 搜索:联想快捷键 或者 Lenovo Hotkeys \u0026gt; 卸载\n方案2 关闭联想的对应服务\n快捷键Win+S, 输入服务, 打开Win的服务窗口 在服务里找Lenovo Function and Fn Key Service。 右键属性 -\u0026gt; 点击停用 然后启动类型改成禁止 此时应该 Ctrl+Fn 不会触发了。测试后重启电脑再次确定一下。\n禁用后有什么好处: 解放了Home键和End键。 在编辑长文件时，可以试下这两个快捷键:\n快捷键Ctrl + Fn + 左箭头 -\u0026gt; 实现跳转到文件顶部（Home键功能）\n快捷键Ctrl + Fn + 右箭头 -\u0026gt; 实现跳转到文件末尾（End键功能）\n备注:联想的Fn键的其他效果 Fn+空格 控制键盘背光\nFn+M 启用/禁用触摸板\nFn+Q 不同操作模式间切换（节能、性能、智能）\nFn+Esc 开启和关闭Fn\n","date":"2025-04-30T12:00:00+08:00","permalink":"/p/lenovo-laptops-disabling-fn-ctrl-cortana/","title":"联想笔记本Fn+Ctrl键会唤醒cortana的问题"},{"content":"最近迷上了魔兽世界的PVP，还是打JJC和战场比下副本有意思多了。\n因为魔兽世界的效果类型太多了，在这里归纳一下各职业的驱散能力，方便查询\n牧师 萨满 骑士 德鲁伊 法师 魔法 进攻驱散+防御驱散 仅进攻驱散 仅防御驱散 - 进攻驱散(法术偷取) 疾病 可以解 可以解 可以解 - - 毒 仅限神牧 可以解 可以解 可以解 - 诅咒 - 仅限奶萨 - 可以解 可以解 拥有部分驱散能力的职业\n猎人的宁神射击的可以实现一次进攻驱散并且同时去除激怒效果，但是这个技能有8秒的冷却时间 术士的宠物地狱猎犬，可以实现进攻驱散/防御驱散，8秒的冷却时间 防战的盾牌猛击，有50%机会驱散一个魔法效果，6秒的冷却时间 魔法效果细节\n进攻驱散是指解除敌人身上的增益魔法效果，防御驱散是指解除队友和自己身上的不利魔法效果 牧师的驱散技能分为大驱和小驱，牧师的大驱可以驱散法师的冰箱，骑士的无敌(圣盾术) 战士在80级增加的碎裂投掷可以进攻驱散法师的冰箱，骑士的无敌 骑士给队友加的保护之手(免疫一切物理伤害)是一种魔法效果，可以被进攻驱散掉，也可以被法术偷取 有很多重要的技能都是魔法效果，比如猎人的急速射击， 萨满变狼，都可以被驱散 ","date":"2024-10-16T12:00:00+08:00","permalink":"/p/wow-wlk-dispel-skills/","title":"魔兽世界 WLK 各职业驱散能力表与说明"},{"content":"收集一下WLK PVP中常见的抗性相关知识，假如有什么不对的地方可以提醒我修改\n骑士 萨满 猎人 牧师 火焰抗性 火抗光环 +130抗性 火抗图腾 +130抗性 - - 自然抗性 - 自然抗图腾 +130抗性 野性守护 +130抗性 - 冰霜抗性 冰抗光环 +130抗性 冰抗图腾 +130抗性 - - 暗影抗性 暗抗光环 +130抗性 - - 暗影防护 暗抗+130 法术穿透是一个纯PVP属性， 在PVE中0价值。 各职业的抗性效果无法叠加，取最高值\nPVE中骑士开了火抗光环，萨满就不需要插火抗图腾了 99%的场合，PVP中法穿堆到130就够用了。 假如法穿不够会出现技能被抵抗/减伤 常见的全属性抗性有两种:\n德鲁伊的爪子可以增加全抗性54点，天赋加成后增加全抗性76点，因此PVP中法系职业法穿最低要求75点\n法师的奥术第二层天赋\u0026quot;魔法吸收\u0026quot;可以增加80点全抗性，同时可以叠加法师护甲技能40点全抗性，\n你在战场或者JJC中遇到的法师大部分会有120点全抗性\n职业伤害类型细节\n骑士和牧师都可以为队友提供130点的暗影抗性，邪DK/暗牧/SS有大量的伤害属于暗影伤害，所以这些职业需要至少堆法穿130 元素萨的主要伤害类型是火焰伤害和自然伤害 生存猎有大量的伤害是火焰伤害(你可以用火抗对抗生存猎) 盗贼的毒药伤害是一种自然伤害，可以被自然抗性减少伤害 鸟德的伤害类型是奥术和自然伤害 毁灭术的主要伤害类型是火焰 ","date":"2024-09-02T12:00:00+08:00","permalink":"/p/wow-wlk-spell-resistance-spell-penetration/","title":"魔兽世界 WLK 抗性与法术穿透说明"},{"content":"最近在用selenium写一个内部站点的自动化工具，自动填资料注册账号然后激活啥的。\n可我们这个站点有时候证书会签不出来，这会导致Chrome连接时出现TLS安全检查的Warning。\n虽然证书是有问题的，但是我们还想要继续访问，因此要设置忽略https的TLS检查。\n然后我在网上搜这部分的配置，网上的示例全是JAVA的，\n我这边用的golang的API，各种文档找了1个小时，最终跑起来了。\n代码很简单，但是想把代码片段留一下，希望可以帮助需要的人节约一点小小的时间，不要重复浪费我这一小时\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // 忽略TLS检查 func SetClientIgnoreTls() (selenium.WebDriver, error) { caps := selenium.Capabilities{ \u0026#34;browserName\u0026#34;: \u0026#34;chrome\u0026#34;, } prefs := map[string]interface{}{ \u0026#34;acceptInsecureCerts\u0026#34;: true, } chromeCaps := chrome.Capabilities{ Prefs: prefs, Path: \u0026#34;\u0026#34;, Args: []string{\u0026#34;--ignore-certificate-errors\u0026#34;}, } caps.AddChrome(chromeCaps) remoteAddr := \u0026#34;127.0.0.1:9515\u0026#34; return selenium.NewRemote(caps, remoteAddr) } ","date":"2024-05-05T08:00:00+08:00","permalink":"/p/selenium%E8%AE%BE%E7%BD%AEchrome%E5%BF%BD%E7%95%A5https%E8%AF%81%E4%B9%A6/","title":"selenium设置Chrome忽略https证书"},{"content":"背景 最近我改了几篇旧文章之后，发现google没有对我的更新内容进行及时收录\n我查了一下原因，发现hugo自动生成的sitemap.xml里，\n对于我手动更新了内容的那篇文章，他的lastmod并没有变化。\n关于lastmod字段 lastmod字段的作用，是告诉搜索引擎的爬虫，这个页面最后的修改时间。\n假如你的页面实际内容改了，但是这个值没有更新，就会导致爬虫判断这个页面没有改动 这就导致了搜索引擎没有及时收录你这次的改动(其实最终会收录，但是时间会很久)\nhugo的lastmod时间 Hugo在生成sitemap时，确定值有一个顺序，优先级从高到低：\n文章头部Front Matter里手动指定的lastmod字段 1 2 3 4 5 --- title: \u0026#34;我的文章\u0026#34; date: 2023-01-01T10:00:00+08:00 lastmod: 2024-03-15T14:30:00+08:00 # Hugo会用这个 --- 文章头部Front Matter里指定的publishDate字段\n如果找不到 lastmod，Hugo会查找 publishDate\n文章头部Front Matter里指定的date字段\n如果以上两者都没有，Hugo会使用 date 参数\nGit 提交信息 (如果 配置项里enableGitInfo 为 true)：\n如果你的项目是一个Git仓库，并且你在Hugo的配置文件 (hugo.toml 或 config.toml) 中设置了 enableGitInfo = true，\nHugo会尝试使用最后一次影响该文件的Git提交的作者日期 (author date) 作为 lastmod。这通常是最准确的自动方式。 :git 从文件的 git 提交记录获取\n好像和CI有联系\n文件系统修改时间 (Fallback)：\n如果以上都不可用，它可能会尝试使用文件的实际修改时间，但这个显然在跨系统或CI/CD环境中不可靠。 \u0026lsquo;:fileModTime\u0026rsquo; 我测试了下，在CI模式下，这个值会把所有文章的lastmod改成最近这次CI执行时间\n最后解决方案 方式1 使用git记录的文件修改时间 -\u0026gt; 推荐！\n1 2 3 4 5 # config.toml enableGitInfo = true # 启用GitInfo支持 [frontmatter] lastmod = [\u0026#39;lastmod\u0026#39;, \u0026#39;modified\u0026#39;, \u0026#39;:git\u0026#39;, \u0026#39;:fileModTime\u0026#39;, \u0026#39;date\u0026#39;] # 按顺序依次获取 具体的配置解释参考\n","date":"2023-11-29T17:12:34+08:00","permalink":"/p/hugo-lastmod-update/","title":"Hugo 的 lastmod 更新问题：sitemap 中的最后修改时间"},{"content":"各级标题 1 2 3 # H1 ## H2 ### H3 文字效果 斜体 + 加粗 这是文字斜体效果\n1 这是*文字斜体*效果 这是文字加粗效果\n1 这是**文字加粗**效果 删除线 + 下划线 这是一段加了删除线的文本\n1 这是一段~加了删除线的文本~ 这是一段 下划线示例文本 内容\n1 这是一段\u0026lt;u\u0026gt; 下划线示例文本 \u0026lt;/u\u0026gt;内容 高亮 Most salamanders are nocturnal\n1 Most \u0026lt;mark\u0026gt;salamanders\u0026lt;/mark\u0026gt; are nocturnal 别的编辑器可以用\u0026quot;==高亮文本==\u0026ldquo;来实现高亮，hugo好像不行 超链接 百度一下，你就知道\n1 [百度一下，你就知道](http://www.baidu.com) 拓展标记 键盘按键 Ctrl + X\n1 \u0026lt;kbd\u0026gt;Ctrl\u0026lt;/kbd\u0026gt; + \u0026lt;kbd\u0026gt;X\u0026lt;/kbd\u0026gt; 上下标 下标 H2O\n上标 Xn + Yn = Zn\n1 2 3 下标 H\u0026lt;sub\u0026gt;2\u0026lt;/sub\u0026gt;O 上标 X\u0026lt;sup\u0026gt;n\u0026lt;/sup\u0026gt; + Y\u0026lt;sup\u0026gt;n\u0026lt;/sup\u0026gt; = Z\u0026lt;sup\u0026gt;n\u0026lt;/sup\u0026gt; 引用 块内引用 这是块内引用效果\n1 这是`块内引用`效果 区块引用 这是区块引用的效果\n1 \u0026gt; 这是区块引用的效果 代码块 缩进推荐四个空格\n1 \u0026lt;p\u0026gt;A paragraph\u0026lt;/p\u0026gt; 列表 有序列表 First item Second item Third item 无序列表 List item Another item And another item 多级列表 Fruit Apple Orange Banana 1 2 3 4 * Fruit * Apple * Orange * Banana 图片 图片需要放在和.md文件同一个文件夹里 hugo一行可以引用多个图片，支持横排图片 1 ![Image 1](1.jpg) ![Image 2](2.jpg) 水平分割线 下面是两种水平分割线：\n1 2 3 代码: --- *** 高级功能 表格 Name Age Bob 27 Alice 23 1 2 3 4 Name | Age --------|------ Bob | 27 Alice | 23 注释 注释的内容对用户不可见\n1 \u0026lt;!-- 这里是注释的内容 --\u0026gt; 视频地址的引用 For more details, check out the documentation.\nYouTube video 格式:\n{{ + 视频标签 + }}\n4种视频标签格式:\n1 2 3 4 \u0026lt; youtube \u0026#34;0qwALOOvUik\u0026#34; \u0026gt; \u0026lt; bilibili \u0026#34;BV1d4411N7zD\u0026#34; \u0026gt; \u0026lt; tencent \u0026#34;g0014r3khdw\u0026#34; \u0026gt; \u0026lt; video \u0026#34;https://www.w3schools.com/tags/movie.mp4\u0026#34; \u0026gt; # 通用视频地址 ","date":"2023-08-21T12:00:00+08:00","permalink":"/p/hugo-markdown/","title":"Hugo 的 Markdown 语法示例与写作规范"},{"content":"Markdown All in One插件 比较实用的功能 Ctrl + B -\u0026gt; 文字加粗\n打开命令行，输入toc, 根据多级标题实现目录功能\n先选中文本，再粘贴链接，快速创建超链接\nAlt + Shift + F -\u0026gt; 表格文本格式化（惊奇）\n","date":"2023-01-01T12:00:00+08:00","permalink":"/p/vscode-markdown/","title":"VS Code 中的 Markdown 写作技巧"},{"content":"Tips: 这是之前老blog的旧文章，从其他blog复制过来时图丢了\nonenote这个笔记我大概使用了8年多， 现在重度依赖onenote来组织和收集知识。\n最近在研究给一个已定义好分级标题的页面，生成大纲目录的方法。\n(真想吐槽一下 这么简单的功能onenote就是不做)\n开始在网上搜集信息，已知的方法：\n通过自带的自定义标签来实现 通过付费插件 onetastic、数字珍宝来实现 通过开源插件Onemore来现实 我最后测试了一下方案3，一个开源的插件：OneMore\n开源地址在github，传送门\n在release里可以下载安装。\n注意：需要office版本的onenote，mac不支持，onenote for win10也不支持。\n安装完成后可以在开始里看到多了一栏菜单。 在文中在设置好文本的各级标题后 选择片段-目录, 会自动生成目录 目录生成后带了超链接 可以实现刷新，回到顶部等\n感谢开源作者的付出！\n","date":"2022-09-17T08:00:00+08:00","permalink":"/p/onenote%E7%94%9F%E6%88%90%E7%9B%AE%E5%BD%95/","title":"onenote生成目录"},{"content":"git重置本地修改很方便，可以git reset \u0026ndash;hard\n查了下svn对应的操作:\n1 2 svn revert . -R # 还原所有更改 svn cleanup . --remove-unversioned # 清理未被版本控制的文件 ","date":"2022-08-18T08:00:00+08:00","permalink":"/p/%E5%85%B3%E4%BA%8Esvn%E8%BF%98%E5%8E%9F%E5%92%8C%E9%87%8D%E7%BD%AE%E5%91%BD%E4%BB%A4/","title":"关于svn还原和重置命令"},{"content":"最近因为工作的原因在对接Azure的API，其中常常遇到一些接口，需要传入的参数是某一周或者某一月这段时间的起点和终点。\n写多了以后我想把这个事抽出来单独写个包，用来产生当天，当周，当月的时间范围，或者输入一个时间点输出那天所在的天，周，月的时间范围。\ngithub地址\n作用: 输入一个时间点，求该时间点所在的一天，一周，一月的起止时间范围，\n用法示例:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 获取某天的时间范围 input := time.Now() // 2022-07-20 17:37:21 tr := asukatime.GetDayRange(input) fmt.Println(tr.Start) // 2022-07-20 00:00:00 fmt.Println(tr.End) // 2022-07-20 23:59:59 // 获取某周的时间范围（默认周一是第一天） input := time.Now() // 2022-07-20 17:37:21 tr := asukatime.GetWeekRange(input) fmt.Println(tr) // [2022-07-18 00:00:00, 2022-07-24 23:59:59.999] // 获取某月的时间范围 input := time.Now() // 2022-07-20 17:37:21 tr := asukatime.GetMonthRange(input) fmt.Println(tr) // [2022-07-01 00:00:00, 2022-07-31 23:59:59.999] 欢迎大家直接调（懒惰是程序员进步的源泉233）\n","date":"2022-07-02T08:00:00+08:00","permalink":"/p/golang%E8%8E%B7%E5%8F%96%E4%B8%80%E4%B8%AA%E5%91%A8%E6%9C%9F%E7%9A%84%E6%97%B6%E9%97%B4%E8%8C%83%E5%9B%B4/","title":"golang获取一个周期的时间范围"},{"content":"metabase 以前使用过Metabase作为可视化分析工具，其中有一个特性就是作为转入参数的变量是可以被定义为可选的。\n适应于有些写在where的条件但有时不需要被执行的子条件。\n1 select * from table where name = {{valName}} [[and age = {{valAge}}]] 在上面这个例子里，valAge假如不填值时整个and age = {{valAge}}语句就不会被执行。\nredash 我们项目目前选了redash作为数据分析工具，主要是看重他的开发语言是python，方便以后的二次开发。\n但是我今天发现Redash默认不支持类似metabase字段筛选option的功能。\n这很麻烦，因为有很多图表刚打开时就是需要一个默认的空白值存在。\n而在redash里要实现这个功能，我今天想了一下需要一点SQL技巧，这里记录一下。\n在DropList中加入一个自定义选项，比如月份除了1~12这12个数字外，需要加上一个AlL值。 每一个需要可选性的变量都按这下面的方式写\n1 SELECT * FROM table where 1=1 and (\u0026#39;{{ valMonth }}\u0026#39;= \u0026#39;ALL\u0026#39; or month = \u0026#39;{{ valMonth }}\u0026#39;) 这样，当用户的默认输入选项值配置为AlL时，实际上这个筛选项就没有生效。\n即:通过SQL的语法短路间接实现可选筛选项的功能\n虽然丑了点，但是确实是可以工作的。\n其实这个特性在BI工具里非常常见，Redash至今没有支持也是一个遗憾。\n","date":"2022-06-15T08:00:00+08:00","permalink":"/p/redash%E5%8F%AF%E9%80%89%E7%9A%84%E6%9D%A1%E4%BB%B6%E5%8F%98%E9%87%8F/","title":"Redash可选的条件变量"},{"content":"注: 从现代国情和政治格局出发，架空历史，描述意大利重建罗马的幻想历史故事。\n作为一个P社游戏玩家的基础操作\n第一阶段：地中海的“新文艺复兴” (2025-2035) 起点 - 危机与不满： 2020年代末，意大利深陷多重困境：持续的经济疲软、高额的国家债务、南北发展不平衡、以及作为地中海前线国家承受的巨大移民压力。欧盟内部的争吵和援助的不足，让许多意大利人感到被边缘化和背叛。国家自豪感低落，社会弥漫着对现状的强烈不满。\n“罗马遗产”运动兴起： 一个极具魅力的民族主义政治家和他的政党登上了历史的舞台。他们不再仅仅谈论经济或移民，而是唤醒深埋在意大利文化中的“罗马情结”。他们宣扬一种“新罗马主义”（Neo-Romanesimo），声称意大利民族是古罗马的正统继承者，有责任重振罗马精神，恢复意大利在地中海乃至世界的“应有地位”。他们将当前的困境归咎于“日耳曼-法兰克主导的欧洲”的压制和“盎格鲁-撒克逊文化”的侵蚀。\n军事“正常化”： 以“保护国家利益”、“维护地中海航运安全”和“有效管理人道主义危机”为名，新上台的强硬政府开始大规模投资军事力量，特别是海军和快速反应部队。这最初被许多国内外观察家解读为应对现实威胁的“正常”反应。然而，军队内部开始清洗“不可靠”的将领，并秘密灌输“新罗马主义”思想，强调进攻性和“收复我们的失地”（Recupero）的理念。\n文化与宣传攻势： 政府大力赞助与古罗马历史相关的文化项目、电影、教育，强烈的民族主义和扩张主义思维在民间传播。古罗马的武功、秩序和“文明使命”被无限拔高，SPQR（元老院与罗马人民）的标志被重新设计并广泛使用。\n第二阶段：重塑“我们的海”（Mare Nostrum） (2035-2045) 经济与外交施压： 意大利利用其地理位置和日益增长的军事存在，开始对地中海沿岸较弱的国家施加影响。首先是利比亚（借口稳定局势，实则控制石油资源和海岸线）、阿尔巴尼亚（利用历史联系和经济渗透）、突尼斯（以打击极端主义和控制移民为名）。意大利通过提供“安全保障”和经济援助，换取这些国家的港口使用权、资源开采权，并扶植亲意政权。\n“蓝色扩张”： 意大利海军以打击海盗、走私和“恐怖主义威胁”为由，在地中海上建立起事实上的霸权。意大利主导并设立了“地中海联合巡逻区”，强制推行自己的规则，并开始干预他国（如希腊、马耳他）的领海争端，处处以“罗马秩序的维护者”自居。\n与欧盟/北约的决裂： 意大利的种种扩张倾向行为日益挑战欧盟的共同外交与安全政策以及北约的框架。面对国际社会的批评和制裁威胁，意大利政府煽动国内民粹情绪，宣称这是“嫉妒者对罗马复兴的阻挠”，并逐步退出或架空这些国际组织对其的约束。\n第三阶段：鹰旗的征服 (2045-2055) “统一战争”的打响： 当外交和经济手段达到极限时，意大利开始动用武力。目标是那些被视为“古罗马失地”且具有战略意义的区域：\n北非： 对利比亚、突尼斯和埃及部分沿海地区发动全面军事行动，宣布要重建罗马的阿非利加行省。 巴尔干西岸： 进攻克罗地亚的达尔马提亚海岸、阿尔巴尼亚全境、染指希腊西部，试图恢复罗马对伊利里亚和希腊部分地区的控制。 地中海岛屿： 对马耳他、塞浦路斯（与土耳其和希腊冲突）提出主权要求并发动突袭。 军团再临： 新的意大利军队采用现代化装备，战术和番号模仿古罗马军团。战争手段残酷无情，强调快速推进和彻底摧毁抵抗意志。占领区实行军事管制，压制当地文化，强制推行意大利语和“罗马化”政策。\n孤立与核讹诈： 意大利的侵略行为使其在国际上空前孤立。为了自保并威慑大国干预，意大利不惜代价秘密研发获取核武器，连续的核爆实验成功加上新型的洲际导弹技术，这将成为维持“新罗马帝国”的终极手段。\n第四阶段：地中海惊雷 (2055-2056) 惊悚与恐惧： 意大利在地中海和北非的种种扩张行为引起了英法两国极大的警惕，两国联合其他欧洲国家对意大利进行外交警告与经济制裁。2055年，军事动员完毕的意大利开始公开声称科西嘉岛自古以来属于罗马共和国的领土，法国应当归还科西嘉岛的主权。这次对法国领土的索取，直接越过了法国的红线，彻底结束了两国在外交上解决争端的可能性。\n英法联盟： 英法两国不顾国内部分绥靖声音，组建了海军联合特遣舰队，决心以武力“遏制新罗马的野心”，保护其在地中海的战略利益（苏伊士航线、科西嘉和马耳他的安全）。然而，时代变了。西方世界的传统领袖:美国，正深陷自身的经济危机、极端政治内耗和社会意见分裂的泥潭，加上奉行彻底的“新孤立主义”政策，明确表示不会介入“欧洲内部的过时冲突”，将战略重心完全置于太平洋和国内事务。美国的缺席，极大地改变了力量平衡。\n决战“我们的海”： 意大利等待的就是这个机会。凭借数十年精心准备、高度适应地中海环境、被“新罗马主义”精神浸透到骨髓的士兵，并且还秘密装备了不对称的“杀手锏”武器:超高音速反舰导弹集群、先进水下无人作战系统和强大的区域拒止电子战能力。\n意大利在地中海中部和西西里海峡附近与英法联军爆发了一系列惨烈的海空大战。意大利海军利用本土作战和技术突袭的优势，重创了准备不足、协调不畅并且缺乏美国情报与后勤支持的英法舰队。其中关键性的胜利——在一场混乱的近海战斗中，意大利海军在陆基航空兵的支援下，通过超高音速反舰导弹集群的饱和攻击，连续击沉了英法特遣舰队三艘航母和多条大型驱逐舰，重创了特遣舰队的主力，震惊了世界。\n英法两国国内厌战情绪爆发，面对巨大的损失和美国袖手旁观的现实，被迫接受了屈辱的停战协议，承认意大利在地中海拥有支配的地位。\n第五阶段：罗马的再临 (2056-2060) 罗马的狂热： 曾经，古罗马击败了迦太基强大的海军。2000多年后，意大利再一次战胜了欧洲最强大的海军舰队。战胜昔日的海上霸主和欧洲强权的消息传回意大利之时，引发了史无前例的民族主义狂热。那位独裁领袖被捧上了神坛，被誉为“当代凯撒”、“海洋征服者”。宣传机器全力开动，将这场胜利描绘成正义战胜堕落、拉丁精神压倒日耳曼-盎格鲁传统的伟大复兴，是罗马雄鹰在沉寂两千年后再次翱翔于天际。\n万神殿前的加冕： 在这场代价高昂的胜利之后不久，这位权势达到顶峰的独裁者，选择在罗马，在修复一新的万神殿前，在能俯瞰古罗马广场的帕拉丁山上，举行一场规模空前、融合了古罗马仪式与现代军事元素的加冕典礼。不再满足于“领袖”（Duce）或“总统”的头衔，他要的是那个至高无上的名号: 凯撒（Caesar）。\n“第四罗马帝国”的宣告： 在万千狂热民众的山呼海啸中，在身着仿古军服、手持鹰旗的“新罗马军团”护卫下，这位领袖发表了激动人心的演说。他历数意大利（罗马）的“屈辱”历史，颂扬新政权的“丰功伟绩”，痛斥英法的“腐朽”与美国的“背信弃义”，最终，他庄严宣告：“两千年的等待已经结束！今天，在诸神与先祖的见证下，凭借我们军团不可战胜的力量和意大利人民不屈的意志，罗马帝国，永恒的帝国，再次重生！我，将以凯撒之名，引领我们的人民，重建地中海的秩序，光复罗马应有的荣耀！”\n天空被战机划破，广场上是钢铁洪流般的阅兵。无数手臂狂热地伸向前方，爆发出震耳欲聋的呼喊：“凯撒！凯撒！凯撒！”。\n古罗马的雄鹰旗帜再次飘扬在地中海上空，罗马的时代，再次来临。\n","date":"2025-05-02T11:12:34+08:00","permalink":"/p/rebuild-mare-nostrum/","title":"第四罗马帝国 我们的海 (Mare Nostrum)"},{"content":"使用yfinance获取数据 最近有需求，需要取一下美股的K线数据，搜了一圈最后选择了yfinance这个包，结果第一次使用就报错了\nToo Many Requests. Rate limited. Try after a while.\n查了一圈最后发现，目前在国内调这个包需要挂代理了\n我把示例代码贴这里，有需要的人可以直接粘贴过去调试。\n记得改成你本地的科学上网代理端口\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import os import yfinance as yf # 定义纳斯达克100指数期货的代码 ticker_symbol = \u0026#39;NQ=F\u0026#39; # 设置代理，指定HTTP 请求的代理服务器 | 使用你自己的代理端口 proxy = \u0026#39;http://127.0.0.1:7890\u0026#39; os.environ[\u0026#39;HTTP_PROXY\u0026#39;] = proxy os.environ[\u0026#39;HTTPS_PROXY\u0026#39;] = proxy # 创建 Ticker 对象 ticker = yf.Ticker(ticker_symbol) # 获取最新的市场数据 ticker_info = ticker.info # 获取当前价格和前收盘价 current_price = ticker_info[\u0026#39;regularMarketPrice\u0026#39;] previous_close = ticker_info[\u0026#39;regularMarketPreviousClose\u0026#39;] # 计算涨幅 price_change = current_price - previous_close percent_change = (price_change / previous_close) * 100 # 输出结果 print(f\u0026#34;当前价格: {current_price}\u0026#34;) print(f\u0026#34;前收盘价: {previous_close}\u0026#34;) print(f\u0026#34;涨跌额: {price_change}\u0026#34;) print(f\u0026#34;涨跌幅: {percent_change:.2f}%\u0026#34;) 除了yfinance之外，国内的新浪财经也是很好的数据来源。 后面我再贴一些。\n","date":"2025-03-07T08:00:00+08:00","permalink":"/p/yfinance%E4%BD%BF%E7%94%A8%E7%9A%84%E7%A4%BA%E4%BE%8B/","title":"yfinance使用的示例"},{"content":"Tips: 此文章只是为了测试评论系统的效果。\n","date":"2022-09-17T08:00:00+08:00","permalink":"/p/%E8%AF%84%E8%AE%BA%E7%B3%BB%E7%BB%9F%E6%B5%8B%E8%AF%95%E9%A1%B5/","title":"评论系统测试页"}]