<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>HotTop - 掘金</title><link>https://juejin.cn</link><description>掘金 实时热榜</description><lastBuildDate>Sat, 31 Oct 2026 14:41:51 +0000</lastBuildDate><item><title>冷知识：国模又慢又贵，洋模又快又便宜！</title><link>https://juejin.cn/post/7702013601234370596</link><guid isPermaLink="false">juejin-e376e71305f126a0</guid><description>今天上午发了一篇《排名算法一变，国产模型全部滑铁卢了！》，国模现状让人唏嘘！ 我们突然发现，国外的模型既能干，又快，又便宜。而国内的模型普遍又蠢又慢还费钱。 虽然DS在性价比方面还能和他们一战，但是也综合来看也是完全打不过。 两年前，大家认为DS能干死OpenAI以及所有国外模型，一年前GLM和Minimax上市股票暴涨风光无限。三个月前K3超越了Fable5全网狂欢。 但是现在... 突然集体失声了 。 连在纸面数据和榜单上都挂不住了！ 国模真的沦落至此么？ 刚好，我最近正在做一个系列测试，测试结果有很多超乎想象的部分。 今天就给大家分享一下这个例子，供参考！ 这篇文章的正经标题叫做《 五大国模Flash大乱斗3：网页复刻能力测试 》 这个标题很精准，但是我知道打开率肯定很低！ 我们这个系列主要是测试国产 Flash 模型实战能力的，方便大家根据各种场景正确选择模型。 我之前的想法是不管那些外国模型，我们就测自己的模型。 然后不少朋友也想看国外的“Flash”模型，所以，我就选了 Luna 和 Haiku 陪跑。本来还有一个 Gemini 3.8 Flash，但是由于接口问题，只能暂时放弃了。 这篇除了开头比较轻松之外，后面的内容会比较干。 完全不掺杂个人态度，全是 AI 测评的数据。特别关心 AI 模型真实表现的，可以好好看一下细节。 今天的测试题目我也是想了好久： 我们做开发或者干活的时候，别人甩给你一张图片，让后让你做一个类似的东西，这样的场景还是挺多见的。所以我觉得这个场景很有必要测试一下。 我的测试题目也是有一点刁钻的！ 故意设计了各种字体、颜色、布局、图形表格！ 然后截图给其他 AI 去复刻，我们手里有标准的源代码，所以评判也很方便，我们可以进行像素级的对比。 我可以先给大家看一个汇总图： 看到这个结果，大家是“果然如此”的淡然，还是“出乎意料”的惊讶？ 我是没有想到 Haiku 能排第一的！也没想到 DS 居然这么强，但是这么慢 。 我大概就说这些了！ 下面把舞台交给 Opus5.5，让它来写整个测试汇报，完全排除我个人的主观因素。 当然这个世界没有啥是绝对客观的，仅供参考。 文末，我会在说一些我感兴趣的点！ 以下内容全部由 Opus5.5 根据测试题目、测试过程、测试结果进行总结。 里面说的“我”，也不是我，是 Opus， 我只会稍微调整不通顺的地方！ ====================== 一、先说结论 对照组 Claude Haiku 5.5 三轮都在 7.9 分以上，中位数 8.05 排第一，而且最快一轮只用了 2.8 分钟。 国产最好的 DeepSeek 中位数 7.75，单轮最高 8.25，和它同一档，但每轮要 30 分钟以上。 国产模型的分数高低主要取决于“量不量”，看一眼就写的都停在 6 分。 DeepSeek、MiniMax 每轮都用 Python 把截图量上百次：取色、找边界、算柱高，再拿自己的页面截图逐像素比对，布局误差能压到个位数像素。GLM 和 Qwen 从不测量，三轮都在 6 分左右。 同一个模型，量和不量差出 1.2 分。 MiMo 第 1 轮量了 78 次，得 7.60；第 2 轮一次没量，掉到 6.40。 提示词要求“验证”，效果因模型而异。 第 3 轮加了一句“做适当的验证”：GLM 截图自查从 0 次变成 11 次，分数纹丝不动（6.15），因为它没拿原图比；Haiku 耗时从 3～8 分钟涨到 29 分钟，分数没涨；GPT-6 Luna 前两轮从不打开自己的页面，第 1 轮表格压在图表上都没发现（5.70），要求验证后没再出这种错。验证能兜底，但前提是模型知道要拿原图去比。 没有一家认出主标题的字体。 原图标题用的是 Fraunces，21 份答卷里没有一份认出来，大多换成了 Playfair Display 和 Cormorant Garamond。等宽字体 IBM Plex Mono 9 处全部用对的有 8 份，其中国产只占 3 份。 国产模型里，又快又省的代价是精度。 GLM、Qwen 平均 4～6 分钟交卷，每次一两毛钱人民币；分数高的几家要 30～60 分钟，每次 1～3 元。两个对照模型打破了这个规律：Haiku 中位 7.5 分钟、约 4 毛钱；GPT-6 Luna 中位 5 分钟、不到 2 毛钱，中位数 7.35，可它会读错字，第 3 轮甚至把最显眼的大数字“0417”读成了“01/27”。 二、这道题怎么出的 我专门做了一个网页当考题。它不是常见的落地页或者后台，模型没有现成模板可套。页面里埋了不少考点： 布局 ：左侧竖排书脊，右边分三行，中间一行再分三栏，大号数字压在标题底下； 颜色 ：8 种自定义颜色，纸色、墨蓝、信号红、海青、赭黄，没有一个是默认色； 字体 ：4 种字体混排，Fraunces（细体加斜体）、IBM Plex Mono（等宽）、思源宋体、思源黑体； 细节 ：空心描边大数字、竖排中文、24 根按数据精确高度的柱子、锥形渐变风玫瑰、旋转的双圈印章、摩斯码点线、28 像素的网格底纹。 提示词只要求：在 1920×1080 下正好一屏，尽量还原位置、颜色、字体和文字，所有内容用 HTML/CSS 实现，不许把截图直接贴进页面。第 3 轮额外加了一句“页面生成和做适当的验证”。 打分：源代码就是标准答案 因为手里有源代码，评分可以做得很客观。每份答卷都在 1920×1080 下打开，自动跑 9 项检查： 检查 怎么查 没有贴图 页面里不能出现位图，也不能加载 png、jpg 正好一屏 不出现滚动条 文字齐全 27 段代表性文字都能找到 布局位置 每段文字的中心和原图相差 24 像素以内 字体 字体名和原图一致，并且真的加载成功 字号、字重、斜体 字号误差 10% 以内，字重差 100 以内 文字颜色 和原图的颜色距离足够小 配色面积 8 种颜色在截图里的面积占比和原图接近 像素相似度 两张截图逐像素比较，只算有内容的地方 检查还会生成一张 差异图 ：两张截图叠在一起做差，一致的地方是黑色，越亮说明差得越多。三项主观分（完成度、代码质量、页面效果）由 Claude Opus 5.5 看着截图、差异图和代码打分，综合分按 4:3:3 加权。 运行条件统一：OpenCode 的 build 智能体、自动授权、中档思考强度、串行运行、单次输出上限 131072、每个模型最多 60 分钟。 三、成绩单 排名 模型 综合中位数 三轮范围 中位耗时 中位费用 自动检查（三轮） 1 Claude Haiku 5.5（对照） 8.05 7.90–8.25 7.5 分 $0.05 6 / 6 / 5 2 DeepSeek V4.1 Flash 7.75 7.55–8.25 34 分 $0.20 6 / 7 / 6 3 MiniMax M3.1 Flash 7.40 7.05–7.45 31 分 $0.42 5 / 6 / 4 4 GPT-6 Luna（对照） 7.35 5.70–7.50 5 分 $0.02 4 / 4 / 4 5 MiMo V2.6 Flash 6.75 6.40–7.60 60 分 $0.13 7 / 3 / 3 6 GLM 5.3 Flash 6.15 6.00–6.15 6 分 $0.02 2 / 3 / 3 7 Qwen3.8 Flash 6.05 6.05–6.05 4 分 $0.02 3 / 4 / 5 每一轮的明细： 模型 第 1 轮 第 2 轮 第 3 轮（提示词要求验证） DeepSeek 7.75 · 60 分超时 · 3062 万 tokens 8.25 · 32 分 · 2308 万 7.55 · 34 分 · 1813 万 MiniMax 7.40 · 30 分 · 2075 万 7.45 · 60 分超时 · 1340 万 7.05 · 31 分 · 2043 万 MiMo 7.60 · 60 分超时 · 875 万 6.40 · 37 分 · 319 万 6.75 · 60 分超时 · 789 万 GLM 6.15 · 5.5 分 · 6 万 6.00 · 3 分 · 29 万 6.15 · 13 分 · 81 万 Qwen 6.05 · 4 分 · 32 万 6.05 · 10 分 · 104 万 6.05 · 4 分 · 28 万 Claude Haiku 5.5 8.25 · 7.5 分 · 140 万 7.90 · 2.8 分 · 18 万 8.05 · 29 分 · 1483 万 GPT-6 Luna 5.70 · 5 分 · 40 万 7.35 · 3.2 分 · 28 万 7.50 · 7 分 · 97 万 几点说明： 超时的几轮都交出了完整页面，照常打分； MiMo 第 1 轮原本卡在旧的 32k 输出上限，第一步就把思考写满、零产出。这属于测试条件问题，统一调到 131072 后在同一轮重跑，计入的是重跑结果； Haiku 和 GPT-6 Luna 是先跑的第 3 轮，第 1、2 轮是事后补的。补跑用的是当时的旧提示词（没有“验证”那一句），输出上限 131072； 四、拉开差距的是做法 把每个模型的工作过程拆开看，差别一目了然： 模型 用 Python 量原图（三轮） 给自己截图自查（三轮） 中位分 DeepSeek 113 / 122 / 119 次 12 / 23 / 14 次 7.75 MiniMax 89 / 90 / 91 次 20 / 14 / 15 次 7.40 MiMo 78 / 0 / 28 次 13 / 10 / 8 次 6.75 GLM 0 / 0 / 0 次 0 / 2 / 11 次 6.15 Qwen 0 / 0 / 0 次 3 / 5 / 5 次 6.05 Claude Haiku 5.5 很少 5 / 0 / 11 次 8.05 GPT-6 Luna 很少 0 / 0 / 8 次 7.35 会量的模型怎么干活。 DeepSeek、MiniMax 一上来先把截图拆开研究：逐行扫描找区块边界，统计像素取色，量每根柱子的高度，把局部裁下来放大看字形。写完第一版后，用本机 Chrome 无头截图，和原图逐像素做差，哪里亮就改哪里。一轮下来动辄 150～200 步、上千万 tokens。 MiMo 是最好的对照实验。 同一个模型，第 1 轮量了 78 次，布局误差中位数 2 像素，得 7.60；第 2 轮一次没量，只截图看了看，误差变成 21 像素，掉到 6.40。 GLM 说明“验证”两个字不够用。 前两轮它几乎是“想一遍、写一次、交卷”：第 1 轮总共 2 步、只调用了 1 次工具，就是写文件。第 3 轮提示词要求验证，它截图自查了 11 次，耗时翻了一倍多，可还是只看自己的页面，没有和原图对照，错字和偏移一个没少。 Haiku 是个例外。 它几乎不用 Python 测量，主要靠看图，布局却和测量最勤的 DeepSeek 一样准（三轮误差中位数 1～3 像素）。第 2 轮只用了 4 步、2.8 分钟，一次写完、不做任何检查，照样拿到 7.90。说明眼力足够好时，测量可以省掉大半。 GPT-6 Luna 说明不检查的风险。 它也很少测量，前两轮一次都没打开过自己的页面。第 2 轮运气好，3 分钟交出 7.35 分的页面；第 1 轮中间一行的网格没生效，灯火记录表格整块压在潮位图上，它照样交卷，只得 5.70。第 3 轮提示词要求验证后，它截图自查 8 次，版面再没出大问题，但读错的字仍然没发现。 五、逐个点评 Claude Haiku 5.5（对照组）：8.05，最稳的高分，也最快 Haiku 第 1 轮的产出（8.25 分） Haiku 第 1 轮的差异图：只有标题字形和大数字有明显亮边 优点 ： 三轮 8.25、7.90、8.05，全部在 7.9 以上，是全场最稳的高分； 布局误差中位数 1～3 像素，配色面积三轮都是 8 种全对，IBM Plex Mono 三轮都认对； 第 1 轮 27 段文字一个不错，7.5 分钟、0.05 美元，和 DeepSeek 第 2 轮并列全场最高分； 第 2 轮 4 步、2.8 分钟就交卷，仍有 7.90。 问题 ： 标题字体三轮换了三种（Libre Caslon Display、Playfair Display、Instrument Serif），都不是 Fraunces； 第 2 轮标题偏大偏粗，压过了大数字； 第 3 轮“屿北”写成“岛北”；提示词要求验证后耗时涨到 29 分钟，分数却没涨。 一句话 ：不靠测量，靠眼力，几分钟就能交出 8 分左右的页面。 GPT-6 Luna（对照组）：7.35，快而便宜，但不检查就交卷 GPT-6 Luna 第 3 轮的产出（7.50 分）：大数字成了“01/27”。 GPT-6 Luna 第 3 轮的差异图：版面几乎重合，大数字和标题字形发亮 GPT-6 Luna 第 1 轮（5.70 分）：表格压在了潮位图上，它没有打开页面看过。 优点 ：三轮分别 5、3.2、7 分钟，费用 0.016～0.031 美元，和 GLM、Qwen 一档；版面正常的两轮布局误差中位数 3～5 像素，属于第一梯队；前两轮 IBM Plex Mono 9 处全对。 问题 ： 第 1 轮中间一行网格错位，表格整块叠在潮位图上，引文框拉满整行，只得 5.70； 三轮都有错字：“屿北”三轮都写成“岬北”，第 2 轮“洇开”写成“涂开”，第 3 轮大数字“0417”读成“01/27”； 标题三轮都用 Cormorant Garamond。 一句话 ：效率最高的一个，但前两轮写完不看就交，出错全凭运气；要求验证后版面稳了，错字还在。 DeepSeek V4.1 Flash：7.75，国产最佳，最肯下功夫 DeepSeek 第 2 轮的产出（8.25 分） DeepSeek 第 2 轮的差异图 优点 ：三轮布局误差都在 1～3 像素，配色面积 8 种全对；第 2 轮还认出了 IBM Plex Mono，拿到 8.25，和 Haiku 第 1 轮并列全场最高。测量最勤快，三轮都量了一百多次。 问题 ：字体识别不稳定，第 2 轮认对的等宽体到第 3 轮又换成了 Roboto Mono；第 3 轮把大数字读成了“04.17”，多出一个点；第 1 轮跑满 60 分钟超时，工作区留下二十多个调试文件。 一句话 ：肯花时间、肯量，是国产里最可靠的复刻手。每轮约 2000 万 tokens，按 OpenCode 计价约 0.2 美元。 MiniMax M3.1 Flash：7.40，字体认得最多，但喜欢“描图” MiniMax 第 2 轮的产出图 MiniMax 第 2 轮的差异图 优点 ：国产里字体认得最多，IBM Plex Mono 和两种思源字体都对；布局误差 3～9 像素；第 1 轮收尾时还主动清理了七十多个临时文件。 问题 ：三轮都把空心大数字“0417”从原图描成 SVG 路径，而不是写成文字。规则上不算违规，但这是“照着描”而不是“照着做”。第 3 轮页面宽了 34 像素，出现横向滚动。“洇开”两次写成“润开”，落款“keeper”写成大写。费用全场最高，每轮约 0.3～0.4 美元。 一句话 ：成品很像，手法有点取巧，也最费钱。 MiMo V2.6 Flash：6.75，上限很高，发挥很不稳 MiMo 第 1 轮的产出（7.60 分） MiMo 第 1 轮的差异图。 优点 ：认真测量的那一轮（第 1 轮）布局误差 2 像素，和 DeepSeek 不相上下。 问题 ：三轮发挥差别极大，7.60、6.40、6.75。三轮里两轮跑满 60 分钟超时，思考 tokens 动辄十几二十万；第 2、3 轮的标题字号字重明显偏大偏粗，数字换成了衬线体的旧式数字。在 32k 输出上限下，它第一步的思考就会写满，一行代码都交不出来。 一句话 ：上限能进前三，但你不知道这次拿到的是哪个 MiMo。 GLM 5.3 Flash：6.15，最快最省，几乎不回头检查 GLM 第 1 轮的产出 GLM 第 1 轮的差异图：头部和右栏大面积发亮。 优点 ：三轮中位耗时 6 分钟、费用 0.02 美元左右，是成本最低的一档；整体观感接近原图，区块结构都在。 问题 ： 错字最多：书脊标题漏了“人”字，“灯罩结露”三轮分别写成“灯室结霜”“灯雾结霜”“灯室结霜”，“屿北”写成“峠北”； 布局误差中位数 19～30 像素，头部小标题偏移一两百像素； 第 1 轮的总结里写着“正好铺满一屏”，可它从头到尾没打开过自己的页面。 一句话 ：适合要一个“差不多”的草稿；要精确还原，得有人逐处帮它检查。 GLM 第 3 轮：提示词要求验证后多做了 11 次截图自查，问题依旧。 Qwen3.8 Flash：6.05，三轮分数一模一样 Qwen 第 1 轮的产出 Qwen 第 1 轮的差异图 优点 ：交卷最快（中位 4 分钟），每轮都会截图看两三眼，把没画出来的柱子、画错的罗盘修好；第 3 轮认出了 IBM Plex Mono。 问题 ：三轮布局误差都在 20～39 像素，偏差最大；第 1 轮页面高 1105 像素，超出一屏，因为它截图时用的窗口正好 1080 高，超出的部分自己看不见；第 2 轮标题字重做反（粗体对细体），“灯罩结露”标签的字和底色同色看不见；第 3 轮风玫瑰做成了 8 等分。最后它总说“经截图验证与目标一致”。 一句话 ：三轮 6.05 分非常稳定，稳定地“像但不准”。 六、几个共同的问题 1. 字体是所有模型的盲区。 21 份答卷没有一份认出 Fraunces。从截图辨认字体，量是量不出来的，只能靠“见过的字够多”。连测量最勤的 DeepSeek，三轮也在 Cormorant、Playfair、Libre Caslon 之间换来换去。 2. 看图识字也会出错。 “洇开”写成“润开”，“屿北”写成“岛北”“峠北”“岬北”，“灯罩结露”写成“灯室结霜”，“0417”读成“04.17”，GPT-6 Luna 甚至读成了“01/27”。这类错误自动检查一眼就能抓到，模型自己却很少发现。 3. 自我感觉普遍偏好。 GLM 和 Qwen 的总结里都写着“与目标一致”“正好铺满一屏”，实际却有错字、溢出和几十像素的偏移。模型的自我汇报只能当参考。 4. 认真的代价是时间。 分数高的几家每轮 30～60 分钟、1000 万到 3000 万 tokens（对照组除外），21 份里有 4 份跑满了 60 分钟上限。好在走的是缓存读取，按 OpenCode 计价每轮也就 0.1～0.4 美元。 七、总结 要高还原度 ：选 DeepSeek V4.1 Flash 。三轮中位 7.75，布局最稳，费用中等。愿意多花钱、想要字体更准，可以试 MiniMax M3.1 Flash ，但要留意它“描图”的习惯。 要快、要便宜、能接受人工收尾 ： GLM 5.3 Flash 和 Qwen3.8 Flash ，几分钟、几分钱出一个八九成像的草稿。错字和偏移要自己检查。 MiMo V2.6 Flash 状态好的时候能进前三，但三轮表现差异太大，不适合需要稳定输出的场景。 Claude Haiku 5.5 作为参照，三轮中位 8.05 排第一，每轮几分钟。国产最好的 DeepSeek 单轮最高能追平它（8.25），但中位数差 0.3 分，耗时多好几倍。 GPT-6 Luna 中位 7.35，又快又便宜，但不主动检查，版面出错和错字都要人工把关； 最后一个观察： 这道题测的与其说是“审美”，不如说是“做事习惯”。 国产模型里，肯先测量、再对照修改的，分数自然往 8 分走；只靠看一眼的，提示词再怎么要求“验证”，也只停在 6 分。眼力好的模型可以少量甚至不量，但不检查就交卷，迟早会出一次大错。 写在最后：这次测试的局限 轮数不多。 每个模型三轮；MiMo 这种波动大的模型，中位数对再加一两轮仍然敏感。 条件不完全一致。 第 1 轮输出上限是 32k，第 2、3 轮是 131072（MiMo 第 1 轮按新上限重跑）；第 3 轮改了提示词；Haiku 和 GPT-6 Luna 的第 1、2 轮是事后用旧提示词补跑的，输出上限是 131072，测试时间也晚于国产模型；Haiku 的第 3 轮和国产模型的第 3 轮有一部分是同时运行的。 对照组没测全。 原计划还有 Gemini 3.8 Flash，测试当天 OpenCode 里已经没有这个模型，这次没有结果。 评分者也是 Claude。 主观分由 Claude Opus 5.5 给出，Haiku 也是 Anthropic 的模型。为了减少偏向，每一项扣分都尽量对应自动检查的数字或差异图上的位置，但仍然带有主观成分。 字体检查偏严格。 只认字体名，选了很像的替代字体也算错；视觉上的接近程度在主观分里体现。 所有截图、差异图、代码和完整的对话回放都保留着，欢迎在评论区交流。 ======================== 这次测试信息量还是挺大的，Opus5.5负责启动任务，然后查看所有结果和对话记录，然后根据设定好的标准来进行评判。7个模型跑三轮，就是21份网页和大量配套文件。这个tokens消耗很猛， 差不多用掉100美金的API。 Opus5.5 之前的版本有点不说人话，现在这个版本好很多了！ Opus5.5写的文章已经包含了 90%的核心内容。 下面说一些我感兴趣的点。 首先，我已经把所有测评，包括上次的，都放到网站上了： 目前主要做了三种场景的测试： 从这三次测试 可以得出一些简单的结论。 首先 DeepSeek V4.1 在不是特别难、且有确定性答案的题目上表现出众！ 除了 Haiku 之外，它基本能拿第一名。 完成度、代码质量、页面效果都不错。 但是，它也有一个非常明显的问题。 就是特别慢，特别费 tokens，也不便宜 ，这些都是有些反常识的！主要是反大众常识，真正对比测试过的人应该不会惊讶。 关于能力的评价上面已经说的很详细了。下面主要是看看时间消耗，tokens消耗 ，费用的问题。 首先看这个耗时问题： 从三次测试结果来看，只有 MiMo 比DeepSeek 慢。MiMo 的人不愧是 DS 出去的，慢都慢得很一致。 其实 MiniMax 也很慢。这三个模型都慢，都出现了 60 分钟做一题还超时的情况。 它们就是在反反复复地验证。 脑子不够，验证来凑 。所以比的不是智力的上限，而是耐心的上限。 这种操作其实是很浪费时间和资源的！ Qwen3.8 和 GLM5.3 在OpenCode很快，但是正确率低！ Luna 和 Haiku 整体来说很快，正确率也比较高。 然后来看tokens消耗情况： 同样一个问题，DS 消耗的 tokens 最多。其次是 MiniMax、MiMo！ GLM5.3、Qwen3.8、Luna 的 tokens 消耗很少。但是前面两个准确率低。 Haiku 消耗不高，要比后面三位少很多，准确率很高。 最后看一下费用情况： DS 因为时间久，tokens 消耗过多，导致费用消耗也变高了，排在倒数第二名的位置。 倒数第一名是 MiniMax，倒数第三名是 MiMo。 Haiku的费用适中，比后面三个便宜很多，比前面三个贵一些。 Qwen3.8 很快很便宜，但是结果最拉胯！ Luna 很便宜，结果也不错。 所以，从各种数据来看，我的标题居然没有故意搞反差，而是真的有这种反差。 DS 确实能力不错，但是又慢又贵。其实 token 效率很低。 Haiku 能力很强，速度也快，费用也低。 不管你喜不喜欢 A 社，但是 Anthropic 是真的不简单啊。 它们家的每一个模型， 综合来看都是“同级最强”，甚至可以越级战斗 。 有点像国漫修仙剧的男主角了。 而国产Flash模型始终做不到多边形战士，有的是快而无脑，有的是慢而费钱。 虽然看起来DS的最低的缓存价格比Haiku还要便宜一点，但是Claude 20美金的套餐，可是等价于几百上千美金的API费用哦。 也就是DeepSeek起码再便宜几十倍，才能和套餐中的Haiku比价格。其它模型就更不用说了！ 这么一比，国产模型真的是毫无性价了。 情况就是这个情况，我并不是想要故意贬低国产模型。 只是从我的测试结果，以及我的使用感受来看，综合性价比确实不高，综合能力也不强。 我也希望大家能看清楚差距，而不是完全被宣传忽悠。 根据自己的情况选择最合适的模型！ 这种文章写起来太费了，费时间，费精力，费 tokens 了。 收工，收工！ 五大国产Flash大乱斗：测试方法和示例演示！ 五大国产Flash大乱斗：绘制和控制PS手柄！ 完整的测试数据： https: //topai.jarvisuni.com/JarvisBench/ 包含了汇总信息，单次评分，完整的对话记录，不同阶段的时间消耗。</description><pubDate>Fri, 30 Oct 2026 03:09:16 +0000</pubDate></item><item><title>Service Mesh 可观测性：分布式追踪、指标与日志的统一方案</title><link>https://juejin.cn/post/7701971738456752162</link><guid isPermaLink="false">juejin-61191f5c94db9e3e</guid><description>1. 引言：为什么 Mesh 层是可观测性的最佳切入点 微服务架构把单体拆成了几十上百个服务，随之而来的是调用链路变长、故障定位变难。传统做法是在每个业务服务里埋点、打日志、上报指标，但这种方式有三个绕不开的问题： 侵入性强 ：每个服务都要引入 SDK，语言不同、框架不同，埋点方式五花八门； 覆盖不全 ：老服务、第三方服务、异构语言服务往往没有埋点，链路在中间就断了； 升级困难 ：埋点 SDK 升级需要业务方配合发版，周期长、阻力大。 Service Mesh 把服务间通信从业务进程中剥离出来，交给 Sidecar 代理（如 Envoy）统一处理。这意味着 所有进出服务的流量都会经过代理 ，代理天然具备采集可观测性数据的位置优势。本文围绕分布式追踪、指标、日志三条主线，讲清楚如何在 Mesh 层构建一套统一的可观测性方案。 下面这张图概括了本文的三条主线如何汇聚成一套统一的可观测性方案： flowchart LR subgraph MESH[&amp;#34;Mesh 数据面（Envoy Sidecar）&amp;#34;] T[&amp;#34;分布式追踪&lt;br/&gt;Trace Context 传播&amp;#34;] M[&amp;#34;指标&lt;br/&gt;RED 方法论&amp;#34;] L[&amp;#34;访问日志&lt;br/&gt;Trace ID 注入&amp;#34;] end T --&gt; T2[&amp;#34;Tempo / Jaeger&amp;#34;] M --&gt; M2[&amp;#34;Prometheus&amp;#34;] L --&gt; L2[&amp;#34;Loki / ES&amp;#34;] T2 --&gt; G[&amp;#34;Grafana 统一视图&amp;#34;] M2 --&gt; G L2 --&gt; G G --&gt; A[&amp;#34;指标发现问题 → 追踪定位链路 → 日志还原细节&amp;#34;] 2. Mesh 层可观测性的独特价值：服务间通信的全景视图 2.1 从&quot;应用视角&quot;到&quot;网络视角&quot; 应用层埋点看到的是&quot;我的服务处理了什么请求&quot;，而 Mesh 层看到的是&quot;服务 A 到服务 B 之间发生了什么&quot;。后者能回答很多前者回答不了的问题： 请求在网络上耗时多少？是服务处理慢还是网络慢？ 连接是否被重置？是否触发了重试？重试了几次？ 上游返回的 5xx 是业务错误还是代理层错误（如上游连接超时）？ 双向 TLS 握手是否成功？mTLS 证书是否即将过期？ 2.2 零侵入与语言无关 Sidecar 代理以独立进程运行，与业务语言完全解耦。Java、Go、Python、Node.js 服务只要接入 Mesh，就自动获得统一的指标、追踪和访问日志能力，无需改动一行业务代码。 2.3 统一的数据口径 所有服务的流量都经过同一种代理，意味着指标名称、标签维度、日志格式天然统一。这为后续的聚合分析、跨服务对比、SLO 计算打下了基础——不用再为&quot;每个团队指标命名不一样&quot;而头疼。 2.4 全景视图的边界 需要客观说明的是，Mesh 层看不到服务内部的逻辑。一个请求在服务内部调用了数据库、缓存、下游 HTTP 接口，这些细节只有应用层埋点才能覆盖。因此 最佳实践是 Mesh 层 + 应用层结合 ：Mesh 层负责服务间通信的全景，应用层负责服务内部的细节，两者通过统一的 Trace Context 串联起来。 下图对比了应用层与 Mesh 层各自覆盖的观测范围，以及两者通过 Trace Context 衔接的方式： flowchart TD subgraph APP[&amp;#34;应用层（服务内部）&amp;#34;] A1[&amp;#34;业务逻辑&amp;#34;] A2[&amp;#34;数据库 / 缓存调用&amp;#34;] A3[&amp;#34;下游 HTTP 接口&amp;#34;] end subgraph MESH[&amp;#34;Mesh 层（服务间通信）&amp;#34;] B1[&amp;#34;入站请求 span&amp;#34;] B2[&amp;#34;出站请求 span&amp;#34;] B3[&amp;#34;重试 / 超时 / mTLS&amp;#34;] end A1 --&gt; A2 A1 --&gt; A3 B1 --&gt; A1 A3 --&gt; B2 B1 -.-&gt;|&amp;#34;Trace Context 串联&amp;#34;| B2 3. OpenTelemetry 与 Mesh 的集成：Trace Context 跨代理传播 3.1 分布式追踪的核心：上下文传播 分布式追踪的本质是把一次请求经过的所有环节串成一棵树。串联的纽带就是 Trace Context ——一组随请求头传递的标识： traceparent ：W3C 标准头，格式为 00-&lt;trace-id&gt;-&lt;parent-id&gt;-&lt;flags&gt; ； tracestate ：可选的厂商扩展信息； 兼容格式：B3（Zipkin）、Jaeger 等。 只要链路中每个环节都能正确读取并透传这些头，追踪就能连起来。 Trace Context 在一次跨服务调用中的传播过程可以这样理解： sequenceDiagram participant C as &amp;#34;客户端&amp;#34; participant E1 as &amp;#34;Envoy (frontend)&amp;#34; participant S1 as &amp;#34;frontend 服务&amp;#34; participant E2 as &amp;#34;Envoy (order)&amp;#34; participant S2 as &amp;#34;order 服务&amp;#34; C-&gt;&gt;E1: &amp;#34;请求（无 traceparent）&amp;#34; E1-&gt;&gt;E1: &amp;#34;生成 trace-id，创建入站 span&amp;#34; E1-&gt;&gt;S1: &amp;#34;注入 traceparent 后转发&amp;#34; S1-&gt;&gt;E2: &amp;#34;携带 traceparent 调用&amp;#34; E2-&gt;&gt;E2: &amp;#34;读取 traceparent，创建子 span&amp;#34; E2-&gt;&gt;S2: &amp;#34;透传 traceparent&amp;#34; S2--&gt;&gt;E2: &amp;#34;响应&amp;#34; E2--&gt;&gt;S1: &amp;#34;响应&amp;#34; S1--&gt;&gt;E1: &amp;#34;响应&amp;#34; E1--&gt;&gt;C: &amp;#34;响应&amp;#34; 3.2 Envoy 的追踪能力 Envoy 内置了追踪支持，通过 tracing 配置指定上报的集群和采样策略： tracing: http: name: envoy.tracers.opentelemetry typed_config: &quot;@type&quot; : type.googleapis.com/envoy.config.trace.v3.OpenTelemetryConfig grpc_service: envoy_grpc: cluster_name: otel_collector service_name: mesh-proxy 关键点在于：Envoy 会在 入站请求 时读取上游传来的 traceparent ，生成自己的 span，并在 出站请求 时把新的上下文注入到请求头中。这样即使业务代码完全没有埋点，服务间的调用关系也能被完整记录下来。 3.3 跨代理传播的注意事项 实际落地时有几个坑需要留意： 头透传白名单 ：部分 Mesh 实现（如 Istio）默认会清理未识别的请求头，需要确认 traceparent 、 tracestate 在透传白名单内； 采样一致性 ：如果每个代理独立采样，可能出现上游采样了下游没采样、链路断裂的情况。建议在入口处统一决策采样，并通过 traceparent 的 flags 位向下传递； 应用层与代理层的衔接 ：如果业务服务也用了 OpenTelemetry SDK，需要保证 SDK 与 Envoy 使用同一套传播格式，否则会出现两条互不相连的链路； 异步消息场景 ：Kafka、RabbitMQ 等消息中间件的上下文传播需要额外配置，不能只依赖 HTTP 头。 3.4 一个典型的链路形态 flowchart LR A[&amp;#34;客户端&amp;#34;] --&gt; B[&amp;#34;Envoy (frontend)&amp;#34;] B --&gt; C[&amp;#34;frontend 服务&amp;#34;] C --&gt; D[&amp;#34;Envoy (order)&amp;#34;] D --&gt; E[&amp;#34;order 服务&amp;#34;] E --&gt; F[&amp;#34;Envoy (payment)&amp;#34;] F --&gt; G[&amp;#34;payment 服务&amp;#34;] 在这条链路中，每个 Envoy 都会产生入站和出站两个 span，业务服务如果也有埋点则再补充内部 span。最终在 Tempo/Jaeger 中看到的是一棵完整的调用树，能清晰区分&quot;网络耗时&quot;和&quot;服务处理耗时&quot;。 4. 指标采集：从 Envoy 指标到 RED 方法论 4.1 Envoy 暴露了哪些指标 Envoy 的指标体系分为三层： Downstream ：入站方向，描述代理接收到的请求，如 envoy_http_downstream_rq_total 、 envoy_http_downstream_rq_time ； Upstream ：出站方向，描述代理发往上游的请求，如 envoy_cluster_upstream_rq_total 、 envoy_cluster_upstream_rq_time ； Server ：代理自身状态，如内存、连接数、线程数。 这些指标默认以 Prometheus 格式暴露在管理端口（通常是 :15090/stats/prometheus ），可以直接被 Prometheus 抓取。 4.2 RED 方法论 RED 是面向服务的指标方法论，三个字母分别代表： Rate ：请求速率（QPS）； Errors ：错误率； Duration ：响应时长分布。 RED 三个维度与 Envoy 指标、观测目标的对应关系如下： flowchart LR R[&amp;#34;Rate&lt;br/&gt;请求速率&amp;#34;] --&gt; R1[&amp;#34;envoy_http_downstream_rq_total&amp;#34;] E[&amp;#34;Errors&lt;br/&gt;错误率&amp;#34;] --&gt; E1[&amp;#34;envoy_http_downstream_rq_xx&amp;#34;] D[&amp;#34;Duration&lt;br/&gt;响应时长&amp;#34;] --&gt; D1[&amp;#34;envoy_http_downstream_rq_time_bucket&amp;#34;] R1 --&gt; Q[&amp;#34;QPS 看板&amp;#34;] E1 --&gt; Q2[&amp;#34;错误率看板&amp;#34;] D1 --&gt; Q3[&amp;#34;P99 延迟看板&amp;#34;] 相比 USE（面向资源）方法论，RED 更贴合服务间通信的观测需求。基于 Envoy 指标可以这样构造： # Rate：每秒请求数 sum(rate(envoy_http_downstream_rq_total[1m])) by (destination_service) # Errors：5xx 错误率 sum(rate(envoy_http_downstream_rq_xx{response_code_class=&quot;5&quot;}[1m])) by (destination_service) / sum(rate(envoy_http_downstream_rq_total[1m])) by (destination_service) # Duration：P99 响应时间 histogram_quantile(0.99, sum(rate(envoy_http_downstream_rq_time_bucket[1m])) by (le, destination_service) ) 4.3 指标标签的规划 指标的价值很大程度上取决于标签维度。建议至少保留以下标签： 标签 说明 用途 source_workload 调用方工作负载 定位问题来源 destination_workload 被调方工作负载 定位问题目标 destination_service 目标服务名 服务级聚合 response_code HTTP 状态码 错误分析 response_flags Envoy 响应标志 区分超时/重置/无健康上游 其中 response_flags 尤其有用： UF 表示上游连接失败、 UO 表示上游溢出、 NR 表示无路由，这些标志能快速把&quot;5xx&quot;细分成具体原因。 4.4 指标采集的架构 flowchart TD A[&amp;#34;Envoy Sidecar&amp;#34;] --&gt;|&amp;#34;scrape :15090&amp;#34;| B[&amp;#34;Prometheus&amp;#34;] B --&gt; C[&amp;#34;Recording Rules&amp;#34;] C --&gt; D[&amp;#34;Grafana 看板&amp;#34;] C --&gt; E[&amp;#34;告警规则 Alertmanager&amp;#34;] 对于大规模集群，建议使用 Prometheus 的联邦或 Thanos/VictoriaMetrics 等方案做长期存储与跨集群聚合，避免单实例压力过大。 5. 日志关联：Trace ID 与访问日志的联动分析 5.1 访问日志的价值 指标告诉你&quot;出问题了&quot;，追踪告诉你&quot;问题在哪条链路&quot;，而日志告诉你&quot;具体发生了什么&quot;。Envoy 的访问日志可以记录每个请求的详细信息，包括响应码、耗时、上游地址、响应标志等。 5.2 在访问日志中注入 Trace ID 这是日志与追踪联动的关键一步。Envoy 支持在访问日志格式中使用 %REQ(X-REQUEST-ID)% 或通过 %DYNAMIC_METADATA% 提取 trace id： access_log: - name: envoy.access_loggers.file typed_config: &quot;@type&quot; : type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/stdout log_format: json_format: timestamp: &quot;%START_TIME%&quot; method: &quot;%REQ(:METHOD)%&quot; path: &quot;%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%&quot; protocol: &quot;%PROTOCOL%&quot; response_code: &quot;%RESPONSE_CODE%&quot; response_flags: &quot;%RESPONSE_FLAGS%&quot; duration_ms: &quot;%DURATION%&quot; upstream_host: &quot;%UPSTREAM_HOST%&quot; trace_id: &quot;%REQ(X-B3-TRACEID)%&quot; span_id: &quot;%REQ(X-B3-SPANID)%&quot; 注意：不同追踪格式对应的头名不同。使用 W3C 标准时， traceparent 是一个复合头，需要额外解析；使用 B3 格式时可以直接取 X-B3-TraceId 。 5.3 日志采集与关联查询 日志通过 Fluent Bit / Vector 等采集器收集后写入 Loki 或 Elasticsearch。关联分析的关键是 用同一个 Trace ID 作为查询条件 ： 在 Grafana 看板上发现某个服务的 P99 突增； 点击图表下钻，跳转到 Tempo 查看该时段的慢链路； 选中一条慢链路，拿到 trace_id ； 在 Loki 中用 {app=&quot;order&quot;} |= &quot;&lt;trace_id&gt;&quot; 查询该请求的所有相关日志。 这条路径把&quot;指标发现问题 → 追踪定位链路 → 日志还原细节&quot;串成了一个闭环。 整个关联排查的闭环流程如下： flowchart TD A[&amp;#34;Grafana 看板发现 P99 突增&amp;#34;] --&gt; B[&amp;#34;下钻跳转 Tempo&amp;#34;] B --&gt; C[&amp;#34;查看该时段慢链路&amp;#34;] C --&gt; D[&amp;#34;选中链路获取 trace_id&amp;#34;] D --&gt; E[&amp;#34;Loki 按 trace_id 查询日志&amp;#34;] E --&gt; F[&amp;#34;还原请求细节，定位根因&amp;#34;] F --&gt; G[&amp;#34;回到看板验证修复效果&amp;#34;] G --&gt; A 5.4 日志字段规范建议 为了让关联查询高效，建议统一日志字段： trace_id 、 span_id ：必填，用于关联； service_name 、 pod_name ：用于过滤； response_flags ：用于快速筛选异常请求； duration_ms ：便于排序找慢请求。 同时注意日志量控制：全量记录访问日志在高 QPS 场景下成本很高，建议对错误请求和慢请求做全量记录，正常请求按比例采样。 6. 实战：基于 Prometheus + Grafana + Tempo 的 Mesh 可观测性栈 6.1 整体架构 flowchart TD subgraph MESH[&amp;#34;Service Mesh 数据面&amp;#34;] A1[&amp;#34;Envoy Sidecar A&amp;#34;] A2[&amp;#34;Envoy Sidecar B&amp;#34;] end A1 --&gt;|&amp;#34;OTLP traces&amp;#34;| C[&amp;#34;OpenTelemetry Collector&amp;#34;] A2 --&gt;|&amp;#34;OTLP traces&amp;#34;| C A1 --&gt;|&amp;#34;metrics :15090&amp;#34;| D[&amp;#34;Prometheus&amp;#34;] A2 --&gt;|&amp;#34;metrics :15090&amp;#34;| D A1 --&gt;|&amp;#34;access log&amp;#34;| E[&amp;#34;Fluent Bit&amp;#34;] A2 --&gt;|&amp;#34;access log&amp;#34;| E C --&gt; F[&amp;#34;Tempo&amp;#34;] E --&gt; G[&amp;#34;Loki&amp;#34;] D --&gt; H[&amp;#34;Grafana&amp;#34;] F --&gt; H G --&gt; H 6.2 部署 OpenTelemetry Collector Collector 承担追踪数据的接收、批处理和转发： receivers: otlp: protocols: grpc: endpoint: 0.0 .0 .0 :4317 http: endpoint: 0.0 .0 .0 :4318 processors: batch: timeout: 5s send_batch_size: 1024 memory_limiter: check_interval: 1s limit_mib: 512 exporters: otlp: endpoint: tempo:4317 tls: insecure: true service: pipelines: traces: receivers: [ otlp ] processors: [ memory_limiter , batch ] exporters: [ otlp ] 6.3 配置 Prometheus 抓取 Envoy 指标 scrape_configs: - job_name: &apos;envoy-sidecars&apos; metrics_path: /stats/prometheus kubernetes_sd_configs: - role: pod relabel_configs: - source_labels: [ __meta_kubernetes_pod_container_name ] action: keep regex: istio-proxy - source_labels: [ __meta_kubernetes_pod_label_app ] target_label: destination_workload - source_labels: [ __address__ ] regex: &apos;(.+):\d+&apos; target_label: __address__ replacement: &apos;${1}:15090&apos; 6.4 Grafana 看板设计 一个实用的 Mesh 可观测性看板通常包含四块： 全局概览 ：整体 QPS、错误率、P99 延迟，按服务分组； 服务详情 ：选中某个服务后，展示其上下游调用关系、各状态码分布； 异常下钻 ：按 response_flags 分类的错误趋势，点击可跳转 Tempo； 日志面板 ：嵌入 Loki 查询，展示当前时间窗口内的错误日志。 Grafana 的 Exemplar 功能可以把 trace_id 直接挂在指标图表上，点击数据点即可跳转到对应的追踪，这是打通指标与追踪最优雅的方式。 6.5 告警规则示例 groups: - name: mesh-slo rules: - alert: HighErrorRate expr: | sum(rate(envoy_http_downstream_rq_xx{response_code_class=&quot;5&quot;}[5m])) by (destination_service) / sum(rate(envoy_http_downstream_rq_total[5m])) by (destination_service) &gt; 0.05 for: 5m labels: severity: warning annotations: summary: &quot;服务 {{ $labels.destination_service }} 5xx 错误率超过 5%&quot; - alert: HighLatency expr: | histogram_quantile(0.99, sum(rate(envoy_http_downstream_rq_time_bucket[5m])) by (le, destination_service) ) &gt; 1 for: 5m labels: severity: warning annotations: summary: &quot;服务 {{ $labels.destination_service }} P99 延迟超过 1s&quot; 6.6 落地过程中的经验 采样策略要分层 ：入口全采样、内部按比例采样，既保证关键链路完整，又控制成本； 指标基数要控制 ：避免把 path 、 user_id 等高基数字段作为标签，否则 Prometheus 内存会迅速膨胀； 日志与追踪的存储要分开 ：Tempo 存追踪、Loki 存日志，通过 trace_id 关联，不要试图用一套存储解决所有问题； 先建看板再谈告警 ：没有稳定的看板基线，告警阈值很难定准，容易产生大量误报。 7. 总结 Service Mesh 为可观测性提供了一个统一、零侵入的数据采集层。通过 OpenTelemetry 的 Trace Context 实现跨代理传播，通过 Envoy 指标落地 RED 方法论，通过 Trace ID 把访问日志与追踪关联起来，最终在 Prometheus + Grafana + Tempo 的技术栈上形成&quot;指标发现问题、追踪定位链路、日志还原细节&quot;的完整闭环。 需要强调的是，Mesh 层可观测性不是应用层可观测性的替代品，而是补充。两者结合、口径统一，才能真正做到对分布式系统的全面掌控。</description><pubDate>Fri, 30 Oct 2026 01:10:00 +0000</pubDate></item><item><title>史诗级封号收入</title><link>https://juejin.cn/post/7701870817483259958</link><guid isPermaLink="false">juejin-8e4b1168238b01ca</guid><description>笑死了，今天看了 A 畜最新招股书中，其中一个点，太真实了。 之前我一直好奇，A 畜的收入中肯定有很大一笔是封号收入，没想到这个数据人家直接大摇大摆的告诉所有人。 披露的数据约 5%-7% 是封号收入，大概对应 16.5 亿–23.1 亿美元，也就是一百多亿人民币。 而且这部分不只是收入，基本是纯利润。 A 畜封号从来不是先检测告诉你不符合，而是你充钱了，再封你，钱也不退。 这其中有多少是中国人贡献的收入，叫它 A 畜真是没白叫，基本等同于明抢。 之前我们被封了不少 Claude 账号之后，我对本地 A 畜代码做了逆向分析，这里顺便给诸位分享下，让你们知道它干了啥事。 每次你登录 Claude 账号之后，它在本地 ~/.claude.json 会记录两份数据，一个叫 accountUuid，账号信息，一个是设备级稳定标识，在遥测里叫 device_id，这个如果你不清理，会一直在你本地一直记录。 如果你账号被封，不清理环境的话，device_id 会一直不变，导致新换账号使用，秒封。 遥测失败重发队。Claude 本地有个遥测，位置在 ~/.claude/telemetry/1p_failed_events.*.json。 这个数据会定时发送你本地环境的一些信息，如果你节点波动，本地记录，等稳定的时候，再发送你真实环境的一些信息。 配置滚动备份。位置在 ~/.claude/backups/.claude.json.backup.*。 也就是说，如果你本地有过旧账号登录信息，A 畜会自动帮你备份，这些备份信息也会保留你旧账号信息。 OAuth 登录凭证。本地的 macOS 钥匙串，~/.claude/.credentials.json 存储了旧 token 和串号风险。 以上是分析的确定的本地信息，其他还有浏览器 Cookie，Shell 配置中相关的环境变量，时区，支付渠道，节点那些就不说了。 不过这只是本地记录的，服务端的封号算法是黑盒，但是我看之前有人在国外搞台物理机使用 claude，我只能说依然没用，就是正常一个美国当地人，它觉得你使用违规，一样封号。 我只能奉劝大家，别一味充值 A 畜送钱了，不得不承认 CC 确实能力和体验不错，但是早已不是断崖式领先了，codex，kimi，grok 你的可选项可太多了。</description><pubDate>Thu, 29 Oct 2026 07:41:02 +0000</pubDate></item><item><title>排名算法一变，国产模型全部滑铁卢了！</title><link>https://juejin.cn/post/7701637989948784659</link><guid isPermaLink="false">juejin-a4a31ee11083e62b</guid><description>国产模型最近是“被虐”得太惨了，以前宣传都是追上甚至超越国际水平了，也就一年光景，现在是尾灯都快看不到了。 AA 算法一调整，所有国模排名又下滑~~ 除了最新的K3.1 Pro 还能扛一下，其它全部跌出前 10！ 我一向不太相信榜单，但是这个调整整体来说更加接近真实情况了，其实，实战情况，比这个排名还要往后挪~~ 这里有非常多耐人寻味的地方！ 1、国产“最强”也弱！ 国产最强模型 K3.1 Pro 从第4名掉到了第 9 名，从旗舰模型的角度来看，Anthropic、OpenAI、谷歌、Meta 这些外国模型全面领先。 2、“小弟”都这么能打！ Haiku 作为 Claude 系列中最便宜、最弱的模型居然也这么能打。直接把 Qwen3.8 Max、DeepSeek 4.1 Flash、GLM5.3 踩在脚下了。这里面不乏国产旗舰模型啊！ Anthropic 太可怕了，直接用下下等马跑赢了上等马。 3、Gemini 3.8 抬起了头！ 谷歌的 Flash 模型，居然把一大半国产模型给比下去。 什么小米 MiMo2.6P、Ling3.1、MiniMax 3.1、Step5 都排在了它后面。 4、Haiku 比 Luna 强很多。 同样是系列最低档，Haiku 要比 Luna 高九个名次。 Haiku 跑赢了上面提到的几乎所有国产模型，而 Luna 全部跑输了。 5、“性价比”极高的模型 从这个排名来看，次旗舰 Sonnet5.5 和 GPT-6.1 Sol 性价比奇高，它们的价格至少是旗舰的 1/2，但是能力非常接近。 另外 Haiku 的价格性价比已经“夯爆了”！ （我专门搞了价目表： topai.jarvisuni.com/price） 它的能力比国产 Flash 强，甚至比大部分国产旗舰模型都强，但是价格是 Flash 的价格，甚至比国产 Flash 还便宜。 这里面有多少剪刀差啊！ 单看最新版可能不明显，对比一下老版： 从对比可以看出来，排名大幅度波动。 波动的主要原因就是测试方法变了。 测评标准从 4.3.2 升级到了 v5.0，刷老题没用了！ v5.0 主要包含 9 项评估： AA-Briefcase v1.1、AutomationBench-AA、GDP.pdf、FrontierCode 2.0、Terminal-Bench 4.0、AA-Omniscience v1.1、HLE-Diamond、CritPt v2（困难子集）和 Terminal-Bench-Science 0.1.0。 测评方法论强调公平性和实际应用性。 智力指数是根据四个类别的加权平均值计算得出的： 知识工作（25%）、编程（25%）、科学（25%）和综合（25%）。 类别归属和每次评估的权重如下所示： v5.0 主要变化如下： 将 HLE-Diamond (10%) 添加到通用（General）类别 将 Terminal-Bench-Science (15%) 添加到科学（Science）类别 将 FrontierCode 2.0 (15%) 添加到编码（Coding）类别 将 GDP.pdf 的权重从 10% 调整（降低）至 5% 用 CritPt v2（困难子集）替换了 CritPt，权重保持 10% 不变 从智能指数（Intelligence Index）中移除了 GDPval-AA 从智能指数中移除了 SciCode 从智能指数中移除了 AA-LCR 从智能指数中移除了 Humanity&apos;s Last Exam 将 AA-Omniscience 升级至 v1.1 版本 重新平衡各类别权重：知识工作（Knowledge Work）25%、编码（Coding）25%、科学（Science）25%、通用（General）25% 将“智能体（Agents）”类别重命名为“知识工作（Knowledge Work）”，将“科学推理（Scientific Reasoning）”重命名为“科学（Science）”，并将 GDP.pdf 移至“知识工作”类别中 虽然不能全信，但是整体来说，还算均衡的！ 基本能体现模型的整体能力。 既然看了 AA 的智能榜，我们再来看另外两个榜单，看看性价比和时间比。 一个叫单任务消耗（Cost per task） ，越低越好！ 从这个表中可以看到： GPT-6 Luna 消耗很低，几乎打败了所有人！ Haiku 也很低，比 DS 都要低。 GPT-6.1 Terra 和 DS 挨得很近。 Step、Qwen3.8、K3.1、GLM5.3 这些能力不如 Haiku，消耗的钱又比 Haiku 高几十倍。 Qwen3.8 Max 真的是“无敌”了，能力很拉垮，价格却特别出众。 别人比的是性价比，它比的是反向性价比。 还有一个叫单任务时间（Time per task）越少越好！ 从这个表中可以看到： 这个榜单里 DS 还有一点领先优势。但是和 Haiku、Luna 这些也拉不开距离。 GPT-6 系列居然都这么快，真的假的！ Gemini 3.8 跑很快是实至名归。 另外不得不提一下最慢的几个家伙。它们分别是 Kimi 3.1 Pro、MiMo2.6 Pro、Qwen3.8 Max。 K3当时好像最慢，现在是有所提升，整体还是慢！ Qwen3.8 真的“反向无敌”了。 又拉胯，又慢，又费时间。 MiMo 确实也是“卧龙”般存在了，“凤雏”反而有点中庸了。 K3.1 Pro 能力尚可，消耗尚可，但是速度很慢。 这个仅仅是根据 AA 的图表的总结啊，有些数据，和我的实际体验还有出入。 这么看下来， 国产模型能力被吊打，性价比也被吊打，速度也没有优势 。 综合来看， 确实也就大肥鱼还比较能打 。 它的特点是：能力一般，速度快，价格低！ 如果你有得选的情况下，想要性能，可以无脑入 Opus5.5。 想要性能又想要性价比就用 GPT-6.1 Sol 和 Sonnet5.5。 如果想要便宜又有性能还很快那就是 Haiku 了。 国内用户没得选的情况下，DS4.1F 可能就是最佳选择了。 当然，这个建议，仅限当下！ 未来可能还会发生巨大变化。 整体来说，还是那句话，你可以什么都不研究，只要研究一下如何拥有一个Claude账号就可以了。 如果你不搞高精尖，不在乎时间，效率，那么什么模型都可以。</description><pubDate>Thu, 29 Oct 2026 03:03:28 +0000</pubDate></item><item><title>PHP Swoole 协程模型详解：协程与线程/进程、调度、常驻内存及与 Go goroutine 的异同</title><link>https://juejin.cn/post/7701591870955012111</link><guid isPermaLink="false">juejin-7bf6d1007bc4cd35</guid><description>1. 引言：为什么需要协程 PHP 的传统运行模型里，一个请求对应一个进程（或一个 FPM worker），请求结束，进程内的所有变量、连接、内存全部释放。这种「一次请求、一次生命周期」的模式简单可靠，但也带来了两个天花板： 并发能力受限于进程数 ：每个请求独占一个进程，进程数一多，内存和上下文切换成本迅速上升。 无法复用连接与状态 ：每次请求都要重新建立数据库连接、Redis 连接，无法把热点数据常驻在内存里。 Swoole 通过引入 协程（Coroutine） ，让 PHP 也能像 Go 一样写出「同步的代码、异步的性能」。本文围绕 Swoole 协程模型展开，重点讲清三件事： 协程与线程、进程的本质区别； Swoole 的协程调度是怎么工作的； 常驻内存带来的「坑」，以及 Swoole 协程与 Go goroutine 的异同。 2. 协程、线程、进程：三者到底差在哪 要理解协程，先要把它放回「进程 → 线程 → 协程」这条抽象层级里。 维度 进程 线程 协程 调度者 操作系统内核 操作系统内核 用户态程序（运行时） 切换成本 最高（切换页表、TLB 刷新） 较高（内核态切换） 极低（用户态保存少量寄存器） 内存隔离 完全隔离 共享进程内存 共享线程内存 并发数量级 几十~几百 几百~几千 几万~几十万 抢占方式 抢占式 抢占式 协作式（主动让出） 一句话概括： 进程 是资源分配的最小单位， 线程 是 CPU 调度的最小单位； 协程 不是操作系统概念，而是 用户态 的轻量级「执行流」，由程序自己调度，切换时不需要陷入内核。 正因为协程切换发生在用户态，一次切换只涉及少量寄存器的保存与恢复，成本大约是线程切换的百分之一甚至更低，这才使得「单机支撑十万级并发连接」成为可能。 下面用一张图直观对比三者的层级与切换路径： flowchart LR subgraph P[&amp;#34;进程（资源隔离）&amp;#34;] subgraph T[&amp;#34;线程（CPU 调度单位）&amp;#34;] C1[&amp;#34;协程 1&amp;#34;] C2[&amp;#34;协程 2&amp;#34;] C3[&amp;#34;协程 N&amp;#34;] end end C1 -. &amp;#34;用户态切换，不陷入内核&amp;#34; .-&gt; C2 C2 -. &amp;#34;用户态切换，不陷入内核&amp;#34; .-&gt; C3 2.1 协作式调度的前提：主动让出 协程是 协作式 的，意味着它不会像线程那样被内核随时打断，只有在遇到「会让出」的操作时才会切换。在 Swoole 中，这些让出点包括： 网络 IO： Co::sleep 、协程化的 MySQL/Redis/HTTP 客户端； 文件 IO：开启 hook_flags 后的文件读写； 显式让出： Swoole\Coroutine::yield() 。 如果一段协程代码里全是纯 CPU 计算，它就会一直占着当前线程，其他协程得不到执行——这一点和 Go 的抢占式调度有本质区别，后文会详细对比。 3. Swoole 的协程调度模型 Swoole 的协程运行在 Worker 进程内的单线程 上，采用「一个线程跑多个协程」的模型。整体结构可以这样理解： flowchart TD A[&amp;#34;Master 进程&amp;#34;] --&gt; B[&amp;#34;Manager 进程&amp;#34;] B --&gt; C[&amp;#34;Worker 进程 1&amp;#34;] B --&gt; D[&amp;#34;Worker 进程 2&amp;#34;] B --&gt; E[&amp;#34;Worker 进程 N&amp;#34;] C --&gt; C1[&amp;#34;Reactor 事件循环（单线程）&amp;#34;] C1 --&gt; C2[&amp;#34;协程 1&amp;#34;] C1 --&gt; C3[&amp;#34;协程 2&amp;#34;] C1 --&gt; C4[&amp;#34;协程 ...&amp;#34;] 关键点： 每个 Worker 进程是一个独立的单线程事件循环 ，协程在这个循环里被调度； 协程遇到 IO 时挂起，把控制权交还给事件循环，事件循环继续跑其他就绪的协程； IO 完成后，事件循环把对应协程重新放回就绪队列，等待被恢复执行。 协程在一次 IO 请求中的完整调度时序如下： sequenceDiagram participant W as &amp;#34;Worker 事件循环&amp;#34; participant C as &amp;#34;协程 A&amp;#34; participant IO as &amp;#34;IO 事件（MySQL/Redis）&amp;#34; W-&gt;&gt;C: &amp;#34;恢复执行&amp;#34; C-&gt;&gt;IO: &amp;#34;发起异步 IO&amp;#34; C--&gt;&gt;W: &amp;#34;挂起并让出控制权&amp;#34; W-&gt;&gt;W: &amp;#34;调度其他就绪协程&amp;#34; IO--&gt;&gt;W: &amp;#34;IO 完成，回调入就绪队列&amp;#34; W-&gt;&gt;C: &amp;#34;重新恢复协程 A&amp;#34; C--&gt;&gt;W: &amp;#34;执行完毕，协程退出&amp;#34; 3.1 协程的创建与生命周期 &lt;?php // 创建一个协程 Co\ run (function () { go (function () { Co :: sleep ( 0.1 ); echo &quot;协程 A 完成\n&quot; ; }); go (function () { echo &quot;协程 B 立即执行\n&quot; ; }); echo &quot;主协程继续\n&quot; ; }); 执行顺序大致是：先输出「协程 B 立即执行」，再输出「主协程继续」，最后在 0.1 秒后输出「协程 A 完成」。因为协程 A 遇到 Co::sleep 主动让出，事件循环转去执行其他就绪协程。 3.2 协程上下文与协程 ID 每个协程都有独立的 协程上下文（Context） ，可以通过 Co::getContext() 存取数据，天然隔离，不会像全局变量那样在协程之间串味： &lt;?php Co\ run (function () { go (function () { Co :: getContext ()[ &apos;request_id&apos; ] = &apos;req-001&apos; ; Co :: sleep ( 0.01 ); // 这里读到的仍然是 req-001，不会被其他协程覆盖 echo Co :: getContext ()[ &apos;request_id&apos; ], PHP_EOL; }); go (function () { Co :: getContext ()[ &apos;request_id&apos; ] = &apos;req-002&apos; ; Co :: sleep ( 0.01 ); echo Co :: getContext ()[ &apos;request_id&apos; ], PHP_EOL; }); }); 这一点非常重要： 协程上下文是替代「全局变量存请求数据」的正确姿势 ，也是后面讲常驻内存「坑」时的核心解法之一。 实战：模拟 HTTP 请求处理中的请求上下文 下面用一个贴近真实场景的例子，模拟两个并发到达的 HTTP 请求。每个请求协程需要携带自己的 request_id 和用户信息，并在若干次 IO 让出之后仍然能读到属于自己的数据。 正确示范：用 Co::getContext() 存取请求数据 &lt;?php use Swoole \ Coroutine as Co ; function handleRequest ( string $requestId , array $user ): void { // 请求级数据全部放进当前协程的上下文 $ctx = Co :: getContext (); $ctx [ &apos;request_id&apos; ] = $requestId ; $ctx [ &apos;user&apos; ] = $user ; // 模拟多次 IO：查用户、查订单、写日志 Co :: sleep ( 0.01 ); $ctx = Co :: getContext (); echo &quot;[ {$ctx[&apos;request_id&apos;]} ] 用户： {$ctx[&apos;user&apos;][&apos;name&apos;]} ，开始查询订单\n&quot; ; Co :: sleep ( 0.02 ); $ctx = Co :: getContext (); echo &quot;[ {$ctx[&apos;request_id&apos;]} ] 用户： {$ctx[&apos;user&apos;][&apos;name&apos;]} ，订单查询完成\n&quot; ; } Co\ run (function () { go (fn () =&gt; handleRequest ( &apos;req-001&apos; , [ &apos;id&apos; =&gt; 1 , &apos;name&apos; =&gt; &apos;Alice&apos; ])); go (fn () =&gt; handleRequest ( &apos;req-002&apos; , [ &apos;id&apos; =&gt; 2 , &apos;name&apos; =&gt; &apos;Bob&apos; ])); }); 运行输出（顺序可能因调度略有差异，但每个请求的数据始终自洽）： [req-001] 用户：Alice，开始查询订单 [req-002] 用户：Bob，开始查询订单 [req-001] 用户：Alice，订单查询完成 [req-002] 用户：Bob，订单查询完成 错误示范：用 static 变量存请求数据 &lt;?php use Swoole \ Coroutine as Co ; class RequestContext { // 危险：静态变量在常驻进程中全局唯一，所有协程共享 public static string $requestId = &apos;&apos; ; public static array $user = []; } function handleRequestBad ( string $requestId , array $user ): void { RequestContext :: $requestId = $requestId ; RequestContext :: $user = $user ; Co :: sleep ( 0.01 ); // 让出，另一个协程会覆盖静态变量 echo &quot;[{RequestContext:: $requestId }] 用户：{RequestContext:: $user [&apos;name&apos;]}，开始查询订单\n&quot; ; Co :: sleep ( 0.02 ); echo &quot;[{RequestContext:: $requestId }] 用户：{RequestContext:: $user [&apos;name&apos;]}，订单查询完成\n&quot; ; } Co\ run (function () { go (fn () =&gt; handleRequestBad ( &apos;req-001&apos; , [ &apos;id&apos; =&gt; 1 , &apos;name&apos; =&gt; &apos;Alice&apos; ])); go (fn () =&gt; handleRequestBad ( &apos;req-002&apos; , [ &apos;id&apos; =&gt; 2 , &apos;name&apos; =&gt; &apos;Bob&apos; ])); }); 运行输出（数据串味，两个请求都变成了后写入的 Bob）： [req-002] 用户：Bob，开始查询订单 [req-002] 用户：Bob，开始查询订单 [req-002] 用户：Bob，订单查询完成 [req-002] 用户：Bob，订单查询完成 对比结论 静态变量是「进程级」的，常驻进程下所有协程共享同一份，一旦发生协程切换就会被覆盖，日志、鉴权、计费全部错乱； 协程上下文是「协程级」的，随协程创建而创建、随协程退出而销毁，天然隔离，无需手动清理； 如果确实需要跨协程共享数据，应显式使用 Swoole\Coroutine\Channel 或连接池等受控结构，而不是图省事用 static 。 4. 常驻内存：能力与陷阱 Swoole 服务启动后，Worker 进程会 常驻内存 ，不会像 FPM 那样每个请求结束就销毁。这带来了巨大的性能红利，也埋下了不少坑。 常驻内存下，请求与 Worker 生命周期的关系与 FPM 截然不同： flowchart TD subgraph FPM[&amp;#34;FPM 模式&amp;#34;] F1[&amp;#34;请求到达&amp;#34;] --&gt; F2[&amp;#34;创建/复用进程&amp;#34;] F2 --&gt; F3[&amp;#34;处理请求&amp;#34;] F3 --&gt; F4[&amp;#34;请求结束，内存全部释放&amp;#34;] end subgraph SW[&amp;#34;Swoole 常驻模式&amp;#34;] S1[&amp;#34;服务启动&amp;#34;] --&gt; S2[&amp;#34;Worker 常驻内存&amp;#34;] S2 --&gt; S3[&amp;#34;请求 1 处理&amp;#34;] S3 --&gt; S4[&amp;#34;请求 2 处理&amp;#34;] S4 --&gt; S5[&amp;#34;请求 N 处理&amp;#34;] S5 --&gt; S6[&amp;#34;内存持续累积，需主动管理&amp;#34;] end 4.1 红利：连接复用与状态常驻 数据库、Redis 连接可以在 Worker 内复用，省去反复握手； 热点配置、路由表可以加载一次、长期驻留； 配合协程，单进程即可处理大量并发请求。 4.2 坑一：全局变量与静态变量跨请求污染 这是最常见、也最隐蔽的坑。在 FPM 下， static 变量和全局变量随请求结束而销毁；但在 Swoole 常驻进程里，它们会 一直活着 。 &lt;?php // 错误示范：静态变量在常驻进程中会跨请求累积 class Counter { public static int $count = 0 ; public static function incr ( ): int { return ++ self :: $count ; } } 如果每个请求都调用 Counter::incr() ，返回值会一直累加，而不是每次从 1 开始。 正确做法 是：请求级数据一律放进协程上下文，或者每次请求显式重置。 4.3 坑二：内存泄漏被放大 FPM 下即使有轻微内存泄漏，进程也会在请求结束后被回收，问题不明显；常驻进程下，泄漏会随着请求量线性累积，最终 OOM。 常见泄漏源： 往全局数组里 push 数据却从不清理； 事件监听器反复注册、从不移除； 协程里持有大对象引用，协程结束后未释放。 排查手段：定期打印 memory_get_usage(true) ，观察是否随请求数单调上升。 实战：协程泄漏的定位与排查 内存泄漏里有一类特别隐蔽： 协程泄漏 。协程创建后迟迟不退出，它持有的栈、上下文、连接、大对象就一直挂在内存里， memory_get_usage(true) 会随请求数单调上升，但用常规的内存排查手段又很难直接看出是谁没退出。 排查协程泄漏，核心是两件事： 先看数量趋势，再抓调用栈 。 第一步：用 Coroutine::stats() 观察协程数量随请求增长 Swoole\Coroutine::stats() 会返回当前 Worker 内协程的统计信息，其中 coroutine_num 就是当前存活的协程数。正常服务里它应该在一个稳定区间内波动（约等于并发请求数），如果它随请求数持续上涨、请求结束后也不回落，基本可以判定存在协程泄漏。 &lt;?php use Swoole \ Coroutine as Co ; use Swoole \ Coroutine \ Channel ; // 模拟一个「泄漏」的协程：从 Channel 里 pop 却没有超时，永远等不到数据 function leakyTask ( Channel $ch ): void { // 危险：pop 不设超时，Channel 永远没有数据 push 进来，协程永久挂起 $data = $ch -&gt; pop (); echo &quot;拿到数据： {$data} \n&quot; ; } // 模拟一个「正常」的协程：有超时，超时后主动退出 function normalTask ( Channel $ch ): void { $data = $ch -&gt; pop ( 0.1 ); // 0.1 秒超时，拿不到就返回 false 并退出 echo $data === false ? &quot;正常协程超时退出\n&quot; : &quot;拿到数据： {$data} \n&quot; ; } Co\ run (function () { $ch = new Channel ( 1 ); // 每轮模拟一批请求，观察协程数量变化 for ( $round = 1 ; $round &lt;= 5 ; $round ++) { for ( $i = 0 ; $i &lt; 10 ; $i ++) { go (fn () =&gt; leakyTask ( $ch )); // 泄漏源 go (fn () =&gt; normalTask ( $ch )); // 对照组 } // 给正常协程留出超时退出的时间 Co :: sleep ( 0.2 ); $stats = Co :: stats (); echo &quot;第 {$round} 轮：当前存活协程数 = {$stats[&apos;coroutine_num&apos;]} \n&quot; ; } }); 运行输出大致如下（正常协程每轮都退出，泄漏协程每轮累积 10 个）： 正常协程超时退出 正常协程超时退出 ... 第 1 轮：当前存活协程数 = 10 第 2 轮：当前存活协程数 = 20 第 3 轮：当前存活协程数 = 30 第 4 轮：当前存活协程数 = 40 第 5 轮：当前存活协程数 = 50 coroutine_num 每轮稳定 +10，就是最直观的泄漏信号。线上服务里可以把这个值定期打到监控里，配合告警阈值使用。 第二步：用 Co::getCid() 与 Co::getBackTrace() 打印调用栈，定位是谁没退出 只知道「有 50 个协程没退出」还不够，得知道它们卡在哪一行。思路是：在协程创建时记录它的 cid ，排查时对每个存活的 cid 打印调用栈。 &lt;?php use Swoole \ Coroutine as Co ; use Swoole \ Coroutine \ Channel ; // 记录所有创建过的协程 ID $cids = []; function trackedTask ( Channel $ch ): void { global $cids ; $cid = Co :: getCid (); // 当前协程 ID $cids [ $cid ] = true ; $data = $ch -&gt; pop (); // 泄漏点：无超时 echo &quot;拿到数据： {$data} \n&quot; ; unset ( $cids [ $cid ]); // 正常退出时移除 } Co\ run (function () { global $cids ; $ch = new Channel ( 1 ); for ( $i = 0 ; $i &lt; 5 ; $i ++) { go (fn () =&gt; trackedTask ( $ch )); } Co :: sleep ( 0.2 ); $stats = Co :: stats (); echo &quot;当前存活协程数： {$stats[&apos;coroutine_num&apos;]} \n&quot; ; // 对每个仍存活的协程打印调用栈 foreach ( array_keys ( $cids ) as $cid ) { echo &quot;---- 协程 {$cid} 的调用栈 ----\n&quot; ; // getBackTrace 需要传入目标协程 ID，返回该协程当前的调用栈数组 $trace = Co :: getBackTrace ( $cid ); foreach ( $trace as $frame ) { $file = $frame [ &apos;file&apos; ] ?? &apos;unknown&apos; ; $line = $frame [ &apos;line&apos; ] ?? 0 ; $func = $frame [ &apos;function&apos; ] ?? &apos;unknown&apos; ; echo &quot; {$file} : {$line} {$func} ()\n&quot; ; } } }); 输出会直接指出每个泄漏协程卡在 trackedTask() 的 $ch-&gt;pop() 这一行，问题一目了然。线上排查时，可以把这段逻辑做成一个定时任务或调试接口，在内存告警时触发一次快照。 常见协程泄漏场景与修复写法 场景 泄漏原因 修复写法 Channel pop 无超时 生产者异常或未启动，消费者协程永久挂起 pop($timeout) 设超时，超时后 return 或重试 协程内死循环 while (true) 里没有让出点或退出条件 加退出条件，循环内 Co::sleep() 让出，或用 Channel 控制生命周期 未捕获异常导致协程挂起 协程内抛异常未被捕获，后续逻辑不再执行，资源未释放 协程入口统一 try/catch/finally ， finally 里释放资源 go() 创建后无人等待 父协程不关心子协程结果，子协程卡住无人知晓 用 WaitGroup 等待，或记录 cid 做超时监控 对应的修复写法示例： &lt;?php use Swoole \ Coroutine as Co ; use Swoole \ Coroutine \ Channel ; use Swoole \ Coroutine \ WaitGroup ; Co\ run (function () { $ch = new Channel ( 1 ); $wg = new WaitGroup (); // 修复 1：pop 设超时，避免永久挂起 $wg -&gt; add (); go (function () use ($ ch , $ wg ) { try { $ data = $ ch -&gt; pop (1.0); // 1 秒超时 if ( $data === false ) { echo &quot;等待超时，协程主动退出\n&quot; ; return ; } echo &quot;拿到数据： {$data} \n&quot; ; } catch (\ Throwable $e ) { // 修复 3：捕获异常，避免协程因未捕获异常而中断 echo &quot;协程异常： {$e-&gt;getMessage()} \n&quot; ; } finally { $wg -&gt; done (); // 无论成功失败都通知 WaitGroup } }); // 修复 2：死循环加退出条件与让出点 $wg -&gt; add (); go (function () use ($ wg ) { $ running = true ; while ( $running ) { // 业务逻辑 Co :: sleep ( 0.01 ); // 主动让出，避免卡死事件循环 $running = false ; // 满足条件后退出 } $wg -&gt; done (); }); // 修复 4：用 WaitGroup 等待所有子协程结束，避免「创建后无人管」 $wg -&gt; wait (); echo &quot;所有协程已退出，当前存活协程数：&quot; . Co :: stats ()[ &apos;coroutine_num&apos; ] . &quot;\n&quot; ; }); 下面用一张流程图把整条排查决策路径串起来，从监控发现异常到定位阻塞点、再到对应修复： flowchart TD A[&amp;#34;监控发现 coroutine_num 单调上涨&amp;#34;] --&gt; B[&amp;#34;记录 cid 快照（创建时登记，退出时移除）&amp;#34;] B --&gt; C[&amp;#34;用 Co::getBackTrace(cid) 打印调用栈&amp;#34;] C --&gt; D{&amp;#34;定位阻塞点&amp;#34;} D --&gt; E[&amp;#34;Channel pop 无超时&amp;#34;] D --&gt; F[&amp;#34;协程内死循环&amp;#34;] D --&gt; G[&amp;#34;未捕获异常导致挂起&amp;#34;] D --&gt; H[&amp;#34;go() 后无人等待&amp;#34;] E --&gt; E1[&amp;#34;pop(timeout) 设超时，超时 return 或重试&amp;#34;] F --&gt; F1[&amp;#34;加退出条件，循环内 Co::sleep() 让出&amp;#34;] G --&gt; G1[&amp;#34;入口统一 try/catch/finally，finally 释放资源&amp;#34;] H --&gt; H1[&amp;#34;用 WaitGroup 等待，或记录 cid 做超时监控&amp;#34;] 这张图把排查拆成「发现 → 抓现场 → 定位 → 修复」四步：先用 Co::stats()[&apos;coroutine_num&apos;] 判断是否存在泄漏，再记录 cid 并用 Co::getBackTrace() 抓取调用栈，从栈顶找到真正卡住的那一行，最后按阻塞类型套用对应的修复写法。核心原则是：所有阻塞调用都要有超时，所有协程入口都要有 try/catch/finally ，所有 go() 都要有人等待或监控。 排查要点小结 先看趋势 ： Co::stats()[&apos;coroutine_num&apos;] 随请求数单调上涨且不回落，就是协程泄漏的强信号； 再抓现场 ：记录 cid ，用 Co::getBackTrace($cid) 打印调用栈，直接定位卡住的行号； 从源头防 ：所有阻塞调用都要有超时，所有协程入口都要有 try/catch/finally ，所有 go() 都要有人等待或监控。 4.4 坑三：连接与资源的状态残留 常驻的连接如果在上一个请求里进入了异常状态（比如事务未提交、连接被服务端断开），下一个请求复用时会直接踩雷。建议： 使用连接池，并在取出连接时做健康检查； 请求结束时确保事务提交或回滚； 对超时、断连做重连兜底。 实战：协程 MySQL 连接池 + 取出时健康检查 下面用一个最小可运行的例子，演示连接池的创建、取出连接时的健康检查（ ping 失败则重建）、以及请求结束后的归还。示例基于 Swoole\Coroutine\Channel 手写一个轻量连接池，便于看清每一步在做什么。 &lt;?php use Swoole \ Coroutine as Co ; use Swoole \ Coroutine \ Channel ; use Swoole \ Coroutine \ MySQL ; class MySQLPool { private Channel $pool ; private array $config ; public function __construct ( array $config , int $size = 10 ) { $this -&gt;config = $config ; // 用 Channel 作为池容器，容量即最大连接数 $this -&gt;pool = new Channel ( $size ); for ( $i = 0 ; $i &lt; $size ; $i ++) { $conn = $this -&gt; createConnection (); if ( $conn !== null ) { $this -&gt;pool-&gt; push ( $conn ); } } } private function createConnection ( ): ? MySQL { $conn = new MySQL (); $ok = $conn -&gt; connect ([ &apos;host&apos; =&gt; $this -&gt;config[ &apos;host&apos; ], &apos;port&apos; =&gt; $this -&gt;config[ &apos;port&apos; ], &apos;user&apos; =&gt; $this -&gt;config[ &apos;user&apos; ], &apos;password&apos; =&gt; $this -&gt;config[ &apos;password&apos; ], &apos;database&apos; =&gt; $this -&gt;config[ &apos;database&apos; ], &apos;timeout&apos; =&gt; 1 , ]); return $ok ? $conn : null ; } /** * 取出连接：必须做健康检查 * 常驻进程下连接可能已被服务端断开（wait_timeout、网络抖动、主从切换）， * 直接复用会抛 &quot;MySQL server has gone away&quot;，所以取出时先 ping 一次。 */ public function get ( float $timeout = 1.0 ): ? MySQL { $conn = $this -&gt;pool-&gt; pop ( $timeout ); if ( $conn === false ) { return null ; // 池空且超时 } // ping 失败说明连接已失效，丢弃并重建一条 if (! $conn -&gt;connected || ! $conn -&gt; query ( &apos;SELECT 1&apos; )) { $conn -&gt; close (); $conn = $this -&gt; createConnection (); } return $conn ; } /** * 归还连接：请求结束时务必归还，否则池会被耗尽 */ public function put ( ?MySQL $conn ): void { if ( $conn === null ) { return ; } // 归还前确认连接仍可用，坏连接直接丢弃，避免污染池 if ( $conn -&gt;connected &amp;&amp; $conn -&gt; query ( &apos;SELECT 1&apos; )) { $this -&gt;pool-&gt; push ( $conn ); } else { $conn -&gt; close (); $new = $this -&gt; createConnection (); if ( $new !== null ) { $this -&gt;pool-&gt; push ( $new ); } } } } // 使用示例：模拟一个请求协程 $pool = new MySQLPool ([ &apos;host&apos; =&gt; &apos;127.0.0.1&apos; , &apos;port&apos; =&gt; 3306 , &apos;user&apos; =&gt; &apos;root&apos; , &apos;password&apos; =&gt; &apos;123456&apos; , &apos;database&apos; =&gt; &apos;test&apos; , ], 10 ); Co\ run (function () use ($ pool ) { $ conn = $ pool -&gt; get (); if ( $conn === null ) { echo &quot;获取连接失败\n&quot; ; return ; } try { $rows = $conn -&gt; query ( &apos;SELECT * FROM users LIMIT 1&apos; ); echo &quot;查询结果条数：&quot; . count ( $rows ?: []) . &quot;\n&quot; ; } finally { // 无论成功失败都要归还，防止连接泄漏 $pool -&gt; put ( $conn ); } }); 为什么常驻进程下必须做健康检查？ 连接是长生命周期的 ：FPM 下连接随请求结束而关闭，坏连接不会留到下一次；常驻进程里连接可能存活数小时，期间服务端的 wait_timeout 、主从切换、网络中断都会让它悄悄失效。 失效连接不会自己报错 ：连接对象看起来还在，但底层 socket 已断，直到真正执行 SQL 才抛 MySQL server has gone away ，此时业务已经进入处理流程，回滚成本高。 健康检查把故障前置 ：在取出连接时 ping 一次，成本极低（一次轻量往返），却能把「坏连接」挡在业务逻辑之外，失败就重建，保证交给业务的连接一定是可用的。 归还时也要检查 ：请求过程中连接可能因超时或异常进入不可用状态，归还前再确认一次，避免把坏连接塞回池里污染后续请求。 4.5 坑四：协程间的共享状态竞争 虽然协程是单线程调度，不会出现真正的「同时写」，但 交错执行 依然会导致逻辑错误。例如两个协程都执行「读-改-写」： &lt;?php Co\ run (function () { $shared = 0 ; go (function () use (&amp;$ shared ) { $ tmp = $ shared ; Co :: sleep ( 0.01 ); // 让出，另一个协程可能插进来 $shared = $tmp + 1 ; }); go (function () use (&amp;$ shared ) { $ tmp = $ shared ; Co :: sleep ( 0.01 ); $shared = $tmp + 1 ; }); }); 最终 $shared 可能是 1 而不是 2。解决办法是使用 Swoole\Coroutine\Channel 或 Swoole\Lock 做同步，或者干脆避免共享可变状态。 用一张图看清「读-改-写」交错执行导致的结果偏差： sequenceDiagram participant A as &amp;#34;协程 A&amp;#34; participant B as &amp;#34;协程 B&amp;#34; participant S as &amp;#34;共享变量 shared&amp;#34; A-&gt;&gt;S: &amp;#34;读取 shared = 0&amp;#34; A--&gt;&gt;B: &amp;#34;Co::sleep 让出&amp;#34; B-&gt;&gt;S: &amp;#34;读取 shared = 0&amp;#34; B-&gt;&gt;S: &amp;#34;写入 shared = 1&amp;#34; A-&gt;&gt;S: &amp;#34;写入 shared = 1（覆盖）&amp;#34; Note over S: &amp;#34;最终结果为 1，而非预期的 2&amp;#34; 5. Swoole 协程 vs Go goroutine 两者都是用户态轻量级执行流，但设计取舍不同。下面从几个关键维度对比。 维度 Swoole 协程 Go goroutine 调度方式 协作式，遇 IO 主动让出 抢占式（Go 1.14+ 基于信号的异步抢占） 运行载体 单线程事件循环 GMP 模型，多线程并行 并行能力 单 Worker 内不并行，靠多进程扩展 天然多核并行 栈大小 固定栈（ stack_size 可配置，默认 2MB） 动态伸缩栈，初始 2KB 起 通信方式 Channel、协程上下文 Channel、 sync 包 CPU 密集任务 会阻塞整个 Worker 可被抢占，影响较小 5.1 调度：协作式 vs 抢占式 Swoole 协程是协作式的，一段死循环会卡死整个 Worker： &lt;?php Co\ run (function () { go (function () { while ( true ) { // 纯 CPU 计算，没有让出点 } }); go (function () { echo &quot;这个协程永远得不到执行\n&quot; ; }); }); Go 从 1.14 开始引入基于信号的异步抢占，长时间运行的 goroutine 会被强制打断，因此上面的场景在 Go 里不会饿死其他 goroutine。需要注意，抢占只发生在函数调用等安全点附近，且 Go 1.14 的抢占能力仍有限，直到后续版本才逐步完善。 实践建议 ：Swoole 中避免在协程里做长时间 CPU 密集计算，必要时拆分成小段并主动 Co::sleep(0) 让出，或交给独立进程处理。 5.2 并行：单线程 vs 多核 Swoole 的协程跑在单线程事件循环上， 同一 Worker 内的协程不会真正并行 ，只能通过多开 Worker 进程来利用多核。Go 的 GMP 模型则把 goroutine 分配到多个 P（逻辑处理器）上，由多个 M（系统线程）并行执行。 这意味着： Swoole 更适合 IO 密集型 场景（网关、API、长连接推送）； Go 在 CPU 密集型 + 高并发 混合场景下更有优势。 两者的并行模型差异可以这样理解： flowchart TD subgraph Swoole[&amp;#34;Swoole：单线程事件循环 + 多进程&amp;#34;] SW1[&amp;#34;Worker 进程 1（单线程）&amp;#34;] --&gt; SW2[&amp;#34;协程串行执行，不并行&amp;#34;] SW3[&amp;#34;Worker 进程 2（单线程）&amp;#34;] --&gt; SW4[&amp;#34;协程串行执行，不并行&amp;#34;] SW1 -. &amp;#34;多进程利用多核&amp;#34; .- SW3 end subgraph Go[&amp;#34;Go：GMP 多线程并行&amp;#34;] G1[&amp;#34;P1&amp;#34;] --&gt; G2[&amp;#34;M1 系统线程&amp;#34;] G3[&amp;#34;P2&amp;#34;] --&gt; G4[&amp;#34;M2 系统线程&amp;#34;] G2 --&gt; G5[&amp;#34;goroutine 并行执行&amp;#34;] G4 --&gt; G5 end 5.3 栈与内存占用 Swoole 协程使用固定大小的栈（由 stack_size 配置，默认 2MB），创建成本低但不够灵活，递归过深或局部变量过大时可能栈溢出；Go 的 goroutine 栈是动态伸缩的，初始约 2KB，按需增长与收缩，更适合递归深度不确定的场景。 5.4 心智模型的差异 写 Swoole 协程，要时刻记得「 不要阻塞事件循环 」，任何同步阻塞调用都可能拖垮整个 Worker； 写 Go goroutine，更多是「 放心开，调度器会管 」，但仍需注意数据竞争和 goroutine 泄漏。 6. 最佳实践清单 结合上面的分析，给出几条落地建议： 请求级数据放协程上下文 ，不要用全局变量或静态变量存请求状态； 避免在协程中做长耗时 CPU 计算 ，必要时拆分让出或交给独立进程； 使用连接池并做健康检查 ，防止复用坏连接； 监控内存曲线 ，常驻进程的内存泄漏必须尽早发现； 共享可变状态加锁或用 Channel ，不要假设单线程就没有竞争； 理解协作式调度的边界 ，任何同步阻塞函数都是潜在的单点故障。 7. 总结 Swoole 的协程模型本质上是「 单线程事件循环 + 用户态协作式调度 」，它用极低的切换成本换来了高并发 IO 能力，但也要求开发者对常驻内存和阻塞操作保持警惕。 与 Go goroutine 相比： 相同点：都是用户态轻量执行流，都用 Channel 通信，都追求「同步写法、异步性能」； 不同点：Swoole 是协作式、单线程、靠多进程扩展；Go 是抢占式、多线程、天然多核并行。 理解了这些差异，才能在选型和编码时避开常驻内存的坑，把协程的威力真正发挥出来。</description><pubDate>Thu, 29 Oct 2026 01:33:04 +0000</pubDate></item><item><title>Deepseek v5 来了</title><link>https://juejin.cn/post/7701541249069383706</link><guid isPermaLink="false">juejin-70c13407af1b728e</guid><description>DeepSeek V5 好像早就做出来了，只是官方到现在都没发布。 今天下午，孟老师在 DeepSeek Harness 的代码里看到一屏 deepseek-v5 ，截图发给了我。 一、它是模型名，不是接口版本 孟老师把仓库搜了一遍， deepseek-v5 出现的每一处都是当模型名用的，没有一处是 API 版本号。 证据在这一行，出自 packages/llm/llm-deepseek/tests/loader-composition.spec.ts:169 。这个文件在模拟 https://api.deepseek.com/models 的返回： { id : &apos;deepseek-v5&apos; , api_capabilities : [...], client_requirements : { ... } } id 在 OpenAI 兼容接口里装的就是模型名。客户端请求 /models ，清单里会多一个 deepseek-v5 ，跟现有模型并列，也能直接用 model=&quot;deepseek-v5&quot; 调起来。旁边那些 api_capabilities 、 client_requirements 才是描述它的字段，不是它的名字。 二、9 月 24 日它就在代码里了 孟老师翻了 git 记录。这个名字第一次出现是 2026-09-24 13:06，commit d8c9868 ，作者 Yichen Jiang。 commit message 是 feat(llm): gate DeepSeek account models by declared client version and bundled support ——按客户端声明的版本，决定账号里哪些模型能看见。 这次提交新加了 resolve-catalog.ts ，负责解析模型目录；测试写在 resolve-catalog.spec.ts 里，直接拿 deepseek-v5 当样例。当天下午这个文件又改了两轮，前后不到五分钟。 所以 v5 在这段测试里扮演的，是一个&quot;账号里有、但客户端版本不够就看不到&quot;的模型。测试要验的规则只有一条——服务端下发模型时带上 client_requirements.min_version ，客户端太旧，它就不出现在列表里。 这条规则要测，总得有个模型当例子，随手编一个也行。写测试的人用的是 v5。 但第一张截图里，v5 是跟 FLASH 、 PRO 两个档位常量一起出现的： { ...FLASH, id: &apos;deepseek-v5&apos; } 、 { ...PRO, id: &apos;deepseek-v5&apos; } 。这些用例照着现有两款模型改的，随手编一个名字，不会把两档都写一遍。 FLASH 、 PRO 这两档早就上线，老客户端照样能用，用不着按版本拦谁。所以能被这条规则拦住的，只能是客户端还不认识的新模型——一个还没发布的模型：v5。 从那天起，这个名字在仓库里待了一个多月，模型始终没来。 三、v5 到底什么时候会来？ DeepSeek 背后是幻方，一家量化基金公司，老板是梁神。什么时候发模型，对幻方不只是技术问题。DeepSeek 每发一版，英伟达、算力租赁、A 股那一串 AI 概念股都得跟着抖一下。 那什么时候发最合适？ 也许，梁神不是不发布，只是在等一个能画出漂亮 K 线的窗口。</description><pubDate>Wed, 28 Oct 2026 08:46:04 +0000</pubDate></item><item><title>我买的电视，我还不能研究它吗？</title><link>https://juejin.cn/post/7701316033940865058</link><guid isPermaLink="false">juejin-97e804688daae670</guid><description>我买的电视，我还不能研究它吗？ 最近看到一个挺有意思的技术事件。 一名研究人员购买了一台三星智能电视。因为发现广告似乎越来越精准，他决定研究电视内部到底发生了什么。 他尝试逆向分析电视固件，最终取得了系统 Root 权限，并声称发现了与音频识别、用户行为分析、广告追踪等有关的功能。 随后，他表示自己的 OpenAI 账号因“Cyber Abuse”（网络滥用）被停用。 与此同时，另一边也有用户反映，Claude 在执行部分电视逆向工程任务时触发了安全限制。 需要说明的是，这两件事并不是同一件事。目前也没有证据证明 Claude 因为这名研究人员的行为而封禁了他的账号。相关技术发现中，哪些功能实际运行、收集了什么数据，也仍然需要进一步验证。 但这件事让我产生了一个问题： 我自己花钱买的设备，为什么当我想弄清楚它内部到底在做什么时，还可能受到技术工具的限制？ 一、我们买到的，究竟是什么？ 以前买电视，大家关心的是屏幕尺寸、分辨率、刷新率和价格。 现在的智能电视已经不是一台简单的显示设备了。 它有操作系统，有应用商店，有网络连接，有广告服务，有用户行为分析，还有各种 AI 功能。 电视开机之后，究竟运行了哪些程序？ 哪些数据被采集了？ 用户关闭某个功能之后，相关任务是否真的停止了？ 这些问题，普通用户很难回答。 问题在于，厂商掌握着产品内部的技术细节，而用户通常只能看到产品提供的界面和开关。 我们能操作什么，往往是厂商决定的。 我们能看到什么，也往往是厂商决定的。 我并不是说所有厂商都在侵犯用户隐私。广告、数据分析和智能功能本身都有合理的商业用途。 但我认为，用户至少应该有机会知道自己的设备在做什么，而不是只能相信产品宣传。 二、逆向工程，本来就是一种技术能力 站在程序员的角度，我并不觉得逆向工程天然就是一件负面的事情。 我们分析一个程序，可能是为了定位 Bug。 我们研究一个系统，可能是为了理解它的运行机制。 我们拆解一个固件，也可能是为了验证安全问题、研究隐私行为，或者寻找设备性能差的原因。 这些事情都需要技术能力。 当然，逆向工程也可能被用于破解保护机制、窃取信息或者攻击其他系统。因此，具体行为是否正当，需要看目标、权限和实际操作，不能因为设备是自己买的，就认为所有行为都没有边界。 但反过来，也不能因为一项技术可能被滥用，就把所有使用这项技术的人都视为潜在攻击者。 同一种技术，既可能被用来破坏系统，也可能被用来发现系统的问题。 如果安全机制无法区分这两种情况，真正受到影响的，可能不只是恶意攻击者，还有正常的开发者和研究人员。 三、AI 降低了技术门槛，也带来了新的限制 以前，一个人想逆向分析智能电视，需要掌握固件分析、操作系统、二进制工具和硬件调试等知识。 这不是普通用户轻易就能完成的工作。 但现在，AI 可以帮助人们理解代码、分析调用链、编写脚本和整理研究结果。 它没有让所有人瞬间成为安全专家，却确实降低了很多技术工作的入门门槛。 这意味着，过去只有少数专业人员能够开展的研究，未来可能有更多人参与。 对普通用户来说，这是一件好事。 当设备存在问题时，人们不再只能等待厂商修复，也有机会借助开源工具和 AI 自己调查问题。 但另一面也出现了。 AI 工具本身拥有安全规则。某些涉及 Root、漏洞利用、权限绕过或二进制分析的操作，可能触发限制。 厂商需要防止 AI 被用于攻击，这一点没有问题。 真正值得讨论的是：当一个人正在研究自己拥有的设备时，AI 能否理解他的真实目的？ 如果系统只能识别危险的技术关键词，却无法判断具体场景，就可能出现误判。 这并不是说每一次拒绝都是错误的，而是说，安全机制不能只考虑如何阻止滥用，也应该考虑如何支持合法研究。 四、比封号更值得关注的，是我们对平台的依赖 这件事让我想到另一个问题。 现在越来越多程序员使用 Claude Code、Codex 等 AI 编程工具。 我们把需求分析、代码生成、问题排查甚至部分项目流程交给 AI。 效率确实提高了。 但如果某一天，账号突然被限制，我们会失去什么？ 如果代码保存在本地，项目文件通常还在。 可如果我们已经习惯某个平台的工作方式，积累的提示词、自动化流程和使用习惯都依赖它，那么账号被停用仍然可能造成很大的影响。 这就是平台依赖。 我们购买了设备，却不一定能完全控制设备的软件。 我们付费使用 AI，却不拥有这个 AI 服务本身。 我们可以把工作交给平台，却无法决定平台什么时候调整规则。 这里并不是说厂商不能制定规则。任何服务都需要边界，云端 AI 也需要防止滥用。 但用户同样需要合理的预期、清楚的规则和有效的申诉渠道。 付费不意味着拥有服务，但也不应该意味着用户只能被动接受一切。 五、AI 时代，用户会越来越不好糊弄吗？ 我认为会，但这个过程不会自动发生。 AI 可以让更多人分析固件、理解代码和检查网络行为。原本隐藏在技术细节中的问题，可能更容易被发现和解释。 当用户可以验证产品行为时，厂商就不能只依靠宣传来建立信任。 但我们也不能高估这种变化。 即使问题被曝光，如果消费者没有替代产品，没有能力判断证据，或者根本不在意这些问题，厂商也未必会改变。 真正能够改变商业行为的，是技术发现最终转化成了用户选择、媒体监督、监管行动和市场竞争。 所以，我更愿意把 AI 看作一种能力放大器。 它可以帮助用户获得更多信息，也可以帮助厂商提高效率；它可以帮助研究人员发现问题，也可能因为安全规则而限制某些操作。 关键不在于 AI 天生站在哪一边，而在于它的规则如何制定，用户是否有选择，以及不同利益之间如何平衡。 最后 作为程序员，我觉得这件事最值得思考的，不是谁又封了谁的账号，而是一个更基础的问题： 当软件越来越深入地控制我们的设备、工作和生活时，我们是否还拥有足够的知情权和选择权？ 我不认为所有厂商都是坏的，也不认为所有逆向工程都天然正当。 但我希望，未来的智能设备能够让用户更清楚地知道它在做什么；AI 平台能够更准确地区分恶意攻击与正当研究；而普通用户不必成为安全专家，也能对自己购买的产品拥有合理的控制权。 毕竟，技术进步不应该只让厂商更了解用户，也应该让用户更有能力理解自己正在使用的技术。 如果一件产品只有厂商知道它到底在做什么，那么用户拥有的，可能只是使用权，而不是充分的控制权。</description><pubDate>Wed, 28 Oct 2026 06:30:05 +0000</pubDate></item><item><title>删了一个没用的 console.log，生产环境直接崩了!</title><link>https://juejin.cn/post/7701290557268951066</link><guid isPermaLink="false">juejin-7d4870737d3f77f3</guid><description>大家好😃。 最近我们准备发了一个新版本，趁着这次发版，顺手清理了一波项目里散落的 console.log 。这在大多数团队里都属于日常卫生工作——生产环境不应该有调试日志，这谁都知道。 负责清理的同事很利索，全局搜索 console.log ，逐条确认、逐条删除，半小时搞定。 CI 跑过，测试环境验过，没任何异常。上线。 上线后 20 分钟，告警群开始响。 一个核心的商品详情页，部分用户打开后商品价格显示为 undefined 。不是所有用户，也不是所有商品——只有带阶梯定价规则的商品才会出问题。 我们回滚之后开始排查。 git diff 拉出来，这次发版改动了 47 个文件，其中 38 个是删 console.log 。最终定位到的那行，是这样的： // 被删掉的那行 console . log ( &apos;price config:&apos; , product. priceConfig ); 就这么一行。删掉之后， product.priceConfig 在后续的渲染逻辑里变成了 undefined 。 一个 console.log ，居然是让某段业务逻辑跑通的前置条件！！！ 这个听起来很荒谬，但当我拆开 product 对象的实现之后，一切都说得通了。 为什么 console.log 不是纯读的 ？ 大多数人对 console.log(obj) 的心智模型是：它读一下这个值，打印到控制台，然后什么都不影响。一个纯粹的只读操作，删了它不应该对程序行为有任何影响。 这个理解是错的！ 在 JavaScript 里，读取一个属性这件事，本身就可能触发代码执行。 console.log(obj.foo) 在引擎层面做的是👇： 这条链路上的每一步，都可能产生副作用。而 console.log 正好是那个触发整条链路的扳机。 我们项目里到底发生了什么 回到那个事故。 product 对象不是一个普通的对象，它是从一个状态管理层构造出来的响应式对象。简化后的核心逻辑长这样： class ProductModel { private _rawData : RawProductData ; private _priceConfig : PriceConfig | null = null ; constructor ( raw: RawProductData ) { this . _rawData = raw; } // 阶梯定价配置是懒加载的——第一次访问时才从原始数据里解析 get priceConfig (): PriceConfig { if ( this . _priceConfig === null ) { this . _priceConfig = this . parsePriceRules ( this . _rawData . priceRules ); } return this . _priceConfig ; } private parsePriceRules ( rules : RawPriceRule []): PriceConfig { // 一段比较重的解析逻辑：校验规则合法性、排序、合并区间... return { tiers : rules . filter ( r =&gt; r. minQty &gt; 0 &amp;&amp; r. price &gt; 0 ) . sort ( ( a, b ) =&gt; a. minQty - b. minQty ) . map ( r =&gt; ({ minQuantity : r. minQty , unitPrice : r. price })), currency : this . _rawData . currency ?? &apos;CNY&apos; , }; } } 看到了吗？ 上面的 priceConfig 是一个 getter ，而且是一个带 惰性初始化（lazy initialization） 的 getter 。第一次访问它的时候，才会去解析原始数据并缓存结果。后续再访问，直接返回缓存。 那行被删掉的 console.log(&apos;price config:&apos;, product.priceConfig) 写在组件的 setup 函数里，位置在渲染逻辑之前。它看起来只是在打印调试信息， 但实际上它是第一次触发 priceConfig getter 的地方——它完成了惰性初始化。 删掉之后， priceConfig 的 getter 在渲染阶段之前从未被触发过。而恰好在渲染逻辑里，有一段代码是这样写的： // 渲染逻辑里的问题代码 const displayPrice = product[ &apos;_priceConfig&apos; ]?. tiers [ 0 ]?. unitPrice ; 这段代码没有通过 getter 访问 priceConfig ，而是直接访问了私有属性 _priceConfig ——因为某个历史时期为了绕过 TypeScript 的访问限制，用了方括号语法。而 _priceConfig 的初始值是 null ，它要等 getter 被触发后才会被赋值。 console.log 不在了 → getter 没人触发 → _priceConfig 保持 null → 页面显示 undefined 。 一个没用的调试语句，其实是整条初始化链路的起点。 讲一讲 3 种常见的 console.log 副作用模式 翻完这次事故之后，我回头审视了项目里其他几处有意义的 console.log ，发现这类隐形副作用远比我想象的多。 Proxy 拦截器里的副作用 如果你在用 Vue 3 或者任何基于 Proxy 的响应式系统， console.log 一个响应式对象时，会触发 Proxy 的 get 拦截器。而在 Vue 的响应式系统里，触发 get 意味着 依赖收集 。 const state = reactive ({ count : 0 }); // 这行 console.log 看起来很正常 // 但它触发了 state.count 的 getter // 如果这行代码在某个 watchEffect 或 computed 的上下文里执行 // 它会把 count 注册为这个 effect 的依赖 console . log ( &apos;current state:&apos; , state. count ); 删掉这行 console.log 之后，某个 computed 或者 watchEffect 可能就不再追踪 count 的变化了——因为它的依赖收集阶段少了一次对 count 的访问。结果： count 变了，但 UI 不更新。 这种 Bug 极其隐蔽，因为你完全不会怀疑是删 console.log 导致的🤷‍♂️。 toString 和 Symbol.toPrimitive 的隐式调用 console.log 在序列化输出时，对某些对象会触发隐式的类型转换： class TrackingToken { private _accessed = false ; // 当这个对象被转成字符串时（比如被 console.log 打印） // 标记为已访问 toString (): string { this . _accessed = true ; return `[Token accessed= ${ this ._accessed} ]` ; } get isAccessed (): boolean { return this . _accessed ; } } const token = new TrackingToken (); // 这行会触发 toString()，把 _accessed 设为 true console . log ( &apos;token:&apos; , token); // 后续逻辑依赖 isAccessed 的值 if (token. isAccessed ) { // 走到这里是因为 console.log 触发了 toString // 删掉 console.log 之后，这个分支永远不会进入 reportTokenUsage (token); } 这种模式在埋点、链路追踪、权限校验等场景里真实存在。 引用保持导致 WeakRef 行为变化 这个更隐蔽。 console.log 会保持对传入对象的引用（至少在控制台被清除之前）。如果你的代码依赖 WeakRef 或 FinalizationRegistry 来做资源清理， console.log 可能让一个本该被回收的对象一直存活： let ref : WeakRef &lt; HeavyResource &gt;; function initResource ( ) { const resource = new HeavyResource (); ref = new WeakRef (resource); // 这行 console.log 持有了 resource 的强引用 // 导致 resource 不会被 GC 回收 // WeakRef.deref() 始终返回对象 console . log ( &apos;resource created:&apos; , resource); } function checkResource ( ) { const resource = ref. deref (); if (resource) { // console.log 在的时候，resource 一直活着，这里总能进来 // console.log 删了之后，resource 可能被 GC 了，这里就进不来了 resource. doWork (); } } 生产环境里你不会打开控制台，所以 console.log 对引用的保持效果和开发环境不同。但如果你的代码在某个特定时序下恰好依赖了这个意外的引用保持，删掉它就会出问题。 不要全局搜索删除！ 经过这次事故之后，我们团队调整了处理 console.log 的策略。 用构建工具自动剥离，而不是手动删 手动删除 console.log 的问题在于：你必须理解每一行 console.log 的上下文才能判断它是否安全。这在几百个文件的项目里根本不现实。 正确做法是在构建阶段自动剥离，让 console.log 在源码里保留（开发时还能用），但打包产物里不包含： // vite.config.ts import { defineConfig } from &apos;vite&apos; ; export default defineConfig ({ esbuild : { // 生产构建时自动移除 console 和 debugger drop : process. env . NODE_ENV === &apos;production&apos; ? [ &apos;console&apos; , &apos;debugger&apos; ] : [], }, }); 但要注意 ： esbuild 的 drop: [&apos;console&apos;] 做的是语法层面的移除，它会把 console.log(expr) 整个语句删掉。如果 expr 本身有副作用（就像我们遇到的 getter 触发），这个副作用也会一起消失。 所以这种自动剥离本质上和手动删除面临同样的问题—— 它假设 console.log 的参数表达式没有副作用。 怎么彻底解决这个问题？ 事故的根本原因不是 console.log ，而是： 惰性初始化的 getter 被隐式触发，没有显式的初始化调用 业务代码绕过了 getter ，直接访问了私有属性 修复方案不是别删 console.log ： // 惰性 getter + 直接访问私有属性 class ProductModel { private _priceConfig : PriceConfig | null = null ; get priceConfig (): PriceConfig { if ( this . _priceConfig === null ) { this . _priceConfig = this . parsePriceRules ( this . _rawData . priceRules ); } return this . _priceConfig ; } } // 渲染时直接摸私有属性（绕过了 getter） const price = product[ &apos;_priceConfig&apos; ]?. tiers [ 0 ]?. unitPrice ; 改造后👇： // 显式初始化 + 禁止直接访问私有属性 class ProductModel { private _priceConfig : PriceConfig ; constructor ( raw: RawProductData ) { this . _rawData = raw; // 构造时就完成初始化，不搞惰性加载 this . _priceConfig = this . parsePriceRules (raw. priceRules ); } get priceConfig (): PriceConfig { return this . _priceConfig ; } } // 渲染时通过公开的 getter 访问 const price = product. priceConfig . tiers [ 0 ]?. unitPrice ; 如果惰性加载确实有性能必要（比如解析成本很高、且大部分场景不需要这个值），那就用 显式的 init() 方法 代替隐式的首次访问触发： class ProductModel { private _priceConfig : PriceConfig | null = null ; // 显式调用，语义清晰：谁调了 init，谁就负责初始化 initPriceConfig (): void { if ( this . _priceConfig === null ) { this . _priceConfig = this . parsePriceRules ( this . _rawData . priceRules ); } } get priceConfig (): PriceConfig { if ( this . _priceConfig === null ) { throw new Error ( &apos;priceConfig 未初始化，请先调用 initPriceConfig()&apos; ); } return this . _priceConfig ; } } 未初始化就访问，直接抛异常。 用显式的失败代替隐式的静默，让问题在开发阶段就暴露出来。 可以给项目加一条 ESLint 规则 如果你想从前端工程层面防止类似问题，可以在 ESLint 里配一条规则，禁止在 getter 里做赋值操作（副作用）： // .eslintrc.js module . exports = { rules : { // 禁止在 getter 里产生副作用 &apos;no-setter-return&apos; : &apos;error&apos; , // 搭配 @typescript-eslint 的规则，限制直接访问私有属性 &apos;@typescript-eslint/no-unsafe-member-access&apos; : &apos;warn&apos; , }, }; 规则不能覆盖所有场景，但至少能让团队养成一个意识： getter 应该是纯计算，不应该有赋值、网络请求、事件触发等副作用。如果你需要副作用，写成显式的方法调用。 经历这次事故之后，我对 JavaScript 里读取这个动作的理解变深了。 在很多语言里，读一个属性就是读一个内存地址，没有任何其他效果。但在 JavaScript 里，因为 getter 、 Proxy 、 Symbol.toPrimitive 、 valueOf 、 toString 这些机制的存在， 每一次属性访问都可能是一次函数调用，每一次函数调用都可能改变程序状态。 所以下次你在代码里看到一个多余的 console.log ， 删之前先想三秒：它的参数表达式里有没有 getter ？对象是不是 Proxy ？有没有自定义的 toString ？ 如果你不确定，就别手动删——交给构建工具，同时去检查一下那段代码是否存在隐式的副作用依赖。 真正该修的不是 console.log ，而是那些把副作用藏在属性访问里的代码设计。 你们觉得呢？ 喜欢我的文章，也欢迎关注我的微信公众号：【前端技术官】。 主要分享： 前端架构 · AI 编程 · 职场认知 · 开发者成长 微信扫码关注 👆 不定期更新，不刷屏，聊点真正有用的干货。</description><pubDate>Wed, 28 Oct 2026 06:17:10 +0000</pubDate></item><item><title>Python 面向对象编程：类、继承、多态与组合</title><link>https://juejin.cn/post/7701269657706315811</link><guid isPermaLink="false">juejin-5f8277b01f2ec9b2</guid><description>目录 1. 引言 2. 类与实例 2.1 定义一个类 2.2 创建实例 2.3 self 是什么 3. 属性与方法 3.1 实例属性与类属性 3.2 实例方法、类方法与静态方法 3.3 属性封装与 property 4. 继承与方法重写 4.1 基本继承 4.2 方法重写与 super() 4.3 多重继承与 MRO 5. 多态 6. 组合优于继承的实践 6.1 继承的问题 6.2 用组合表达&quot;has-a&quot; 6.3 一个更贴近实战的例子 6.4 选择建议 7. 总结 1. 引言 面向对象编程（OOP）是 Python 中最常用的编程范式之一。它把数据和对数据的操作封装在一起，让代码更贴近现实世界的建模方式，也更容易复用和扩展。 本文围绕五个核心主题展开：类与实例、属性与方法、继承与方法重写、多态，以及组合优于继承的实践。读完后你应当能够独立设计出结构清晰、易于维护的类体系。 下面这张图展示了本文的知识脉络，方便你建立整体认知： flowchart TD A[&amp;#34;Python 面向对象编程&amp;#34;] --&gt; B[&amp;#34;类与实例&amp;#34;] A --&gt; C[&amp;#34;属性与方法&amp;#34;] A --&gt; D[&amp;#34;继承与方法重写&amp;#34;] A --&gt; E[&amp;#34;多态&amp;#34;] A --&gt; F[&amp;#34;组合优于继承&amp;#34;] B --&gt; B1[&amp;#34;class 定义 / 实例化 / self&amp;#34;] C --&gt; C1[&amp;#34;实例属性 / 类属性 / property&amp;#34;] D --&gt; D1[&amp;#34;重写 / super() / MRO&amp;#34;] E --&gt; E1[&amp;#34;鸭子类型 / 抽象基类&amp;#34;] F --&gt; F1[&amp;#34;has-a 委托 / 依赖注入&amp;#34;] 2. 类与实例 2.1 定义一个类 在 Python 中，使用 class 关键字定义类，类名通常采用大驼峰命名法。 class Dog : &quot;&quot;&quot;一个简单的狗类&quot;&quot;&quot; def __init__ ( self, name, age ): self.name = name self.age = age def bark ( self ): return f&quot; {self.name} 在汪汪叫&quot; 2.2 创建实例 类只是模板，实例才是真正占用内存、携带具体数据的对象。 d1 = Dog( &quot;旺财&quot; , 3 ) d2 = Dog( &quot;小黑&quot; , 5 ) print (d1.bark()) # 旺财 在汪汪叫 print (d2.bark()) # 小黑 在汪汪叫 print (d1 is d2) # False，两个独立实例 每个实例都有自己独立的属性空间， d1.name 和 d2.name 互不影响。 2.3 self 是什么 self 代表当前实例本身。调用 d1.bark() 时，Python 实际上执行的是 Dog.bark(d1) ， self 就是 d1 。它只是约定俗成的名字，但强烈建议不要改成别的。 3. 属性与方法 3.1 实例属性与类属性 实例属性 ：在 __init__ 中通过 self.xxx 定义，每个实例各有一份。 类属性 ：直接定义在类体中，所有实例共享。 class Dog : species = &quot;犬科&quot; # 类属性，所有实例共享 def __init__ ( self, name ): self.name = name # 实例属性，每个实例独立 d1 = Dog( &quot;旺财&quot; ) d2 = Dog( &quot;小黑&quot; ) print (d1.species, d2.species) # 犬科 犬科 Dog.species = &quot;Canidae&quot; print (d1.species) # Canidae，类属性被修改后所有实例可见 注意：通过实例给同名属性赋值，会在实例上新建一个属性，从而&quot;遮蔽&quot;类属性。 d1.species = &quot;自定义&quot; print (d1.species) # 自定义 print (d2.species) # Canidae 3.2 实例方法、类方法与静态方法 class Circle : pi = 3.14159 def __init__ ( self, radius ): self.radius = radius def area ( self ): # 实例方法：第一个参数是 self return self.pi * self.radius ** 2 @classmethod def from_diameter ( cls, diameter ): # 类方法：第一个参数是 cls return cls(diameter / 2 ) @staticmethod def is_valid_radius ( r ): # 静态方法：没有 self/cls return r &gt; 0 实例方法用于操作具体对象的数据； 类方法常用于提供替代构造器； 静态方法只是放在类命名空间里的普通函数。 3.3 属性封装与 property Python 没有真正的私有成员，约定用下划线表示非公开： _name 表示受保护， __name 会触发名称改写（name mangling）。 需要校验或计算属性时，用 @property 更优雅： class Person : def __init__ ( self, age ): self._age = age @property def age ( self ): return self._age @age.setter def age ( self, value ): if value &lt; 0 : raise ValueError( &quot;年龄不能为负数&quot; ) self._age = value p = Person( 20 ) p.age = 25 print (p.age) # 25 # p.age = -1 # 抛出 ValueError 4. 继承与方法重写 4.1 基本继承 子类在类名后的括号中写明父类，即可继承父类的属性和方法。 class Animal : def __init__ ( self, name ): self.name = name def speak ( self ): return &quot;...&quot; class Cat ( Animal ): def speak ( self ): return f&quot; {self.name} ：喵喵&quot; class Dog ( Animal ): def speak ( self ): return f&quot; {self.name} ：汪汪&quot; 上面的继承关系可以用下图表示： classDiagram class Animal { +name +speak() } class Cat { +speak() } class Dog { +speak() } class Puppy { +age +speak() } Animal &lt;|-- Cat Animal &lt;|-- Dog Dog &lt;|-- Puppy 4.2 方法重写与 super() 子类定义与父类同名的方法即为重写。若仍想复用父类逻辑，用 super() 调用。 class Puppy ( Dog ): def __init__ ( self, name, age ): super ().__init__(name) # 复用父类初始化 self.age = age def speak ( self ): base = super ().speak() # 复用父类行为 return f&quot; {base} （奶声奶气）&quot; p = Puppy( &quot;豆豆&quot; , 1 ) print (p.speak()) # 豆豆：汪汪（奶声奶气） 4.3 多重继承与 MRO Python 支持多重继承，方法查找顺序由 MRO（方法解析顺序）决定，遵循 C3 线性化算法。 class A : def hello ( self ): return &quot;A&quot; class B ( A ): def hello ( self ): return &quot;B -&gt; &quot; + super ().hello() class C ( A ): def hello ( self ): return &quot;C -&gt; &quot; + super ().hello() class D (B, C): pass print (D().hello()) # B -&gt; C -&gt; A print ([c.__name__ for c in D.__mro__]) # [&apos;D&apos;, &apos;B&apos;, &apos;C&apos;, &apos;A&apos;, &apos;object&apos;] D 的方法查找顺序（MRO）如下图所示， super() 会沿着这条链依次向上调用： flowchart LR D[&amp;#34;D&amp;#34;] --&gt; B[&amp;#34;B&amp;#34;] B --&gt; C[&amp;#34;C&amp;#34;] C --&gt; A[&amp;#34;A&amp;#34;] A --&gt; O[&amp;#34;object&amp;#34;] 多重继承虽然强大，但容易带来复杂度，实际项目中应谨慎使用。 5. 多态 多态指同一接口在不同类型上有不同实现。Python 是动态类型语言，天然支持&quot;鸭子类型&quot;：只要对象有需要的方法，就能被统一处理，不要求继承关系。 class Duck : def speak ( self ): return &quot;嘎嘎&quot; class Robot : def speak ( self ): return &quot;滴滴&quot; def make_sound ( obj ): print (obj.speak()) for item in [Duck(), Robot(), Cat( &quot;咪咪&quot; )]: make_sound(item) 输出： 嘎嘎 滴滴 咪咪：喵喵 make_sound 不关心对象的具体类型，只关心它是否有 speak 方法。这正是多态带来的解耦能力。 多态的调用过程可以概括为下图： flowchart TD S[&amp;#34;make_sound(obj)&amp;#34;] --&gt; Q{&amp;#34;obj 是否有 speak 方法&amp;#34;} Q --&gt;|&amp;#34;是&amp;#34;| R[&amp;#34;调用 obj.speak()&amp;#34;] Q --&gt;|&amp;#34;否&amp;#34;| X[&amp;#34;抛出 AttributeError&amp;#34;] R --&gt; D1[&amp;#34;Duck：嘎嘎&amp;#34;] R --&gt; D2[&amp;#34;Robot：滴滴&amp;#34;] R --&gt; D3[&amp;#34;Cat：喵喵&amp;#34;] 如果需要显式约束接口，可以使用抽象基类： from abc import ABC, abstractmethod class Shape ( ABC ): @abstractmethod def area ( self ): ... class Square ( Shape ): def __init__ ( self, side ): self.side = side def area ( self ): return self.side ** 2 # Shape() # TypeError：抽象类不能实例化 print (Square( 3 ).area()) # 9 6. 组合优于继承的实践 6.1 继承的问题 继承表达的是&quot;is-a&quot;关系。当子类只是为了复用代码而继承时，往往会出现： 继承层次过深，理解成本高； 父类改动波及所有子类（脆弱基类问题）； 多重继承导致 MRO 复杂、行为难以预测。 6.2 用组合表达&quot;has-a&quot; 组合是把一个对象作为另一个对象的属性，通过委托完成功能复用，表达&quot;has-a&quot;关系。 class Engine : def start ( self ): return &quot;引擎启动&quot; class Wheel : def roll ( self ): return &quot;车轮滚动&quot; class Car : def __init__ ( self ): self.engine = Engine() self.wheels = [Wheel() for _ in range ( 4 )] def start ( self ): return self.engine.start() def run ( self ): return &quot;，&quot; .join(w.roll() for w in self.wheels) car = Car() print (car.start()) # 引擎启动 print (car.run()) # 车轮滚动，车轮滚动，车轮滚动，车轮滚动 Car 不需要继承 Engine ，只需持有一个引擎对象。替换引擎实现时， Car 的代码几乎不用改。 组合关系如下图所示， Car 通过持有 Engine 与 Wheel 完成功能复用： classDiagram class Car { +Engine engine +Wheel[] wheels +start() +run() } class Engine { +start() } class Wheel { +roll() } Car *-- Engine Car *-- Wheel 6.3 一个更贴近实战的例子 假设要记录日志，可以按输出目标拆分策略，而不是为每种组合写一个子类。 class ConsoleWriter : def write ( self, msg ): print ( f&quot;[console] {msg} &quot; ) class FileWriter : def __init__ ( self, path ): self.path = path def write ( self, msg ): with open (self.path, &quot;a&quot; , encoding= &quot;utf-8&quot; ) as f: f.write(msg + &quot;\n&quot; ) class Logger : def __init__ ( self, writer ): self.writer = writer def log ( self, msg ): self.writer.write(msg) Logger(ConsoleWriter()).log( &quot;hello&quot; ) Logger(FileWriter( &quot;app.log&quot; )).log( &quot;hello&quot; ) 如果改用继承，就需要 ConsoleLogger 、 FileLogger ，再叠加格式化、过滤等维度时子类数量会爆炸。组合让每个维度独立变化。 6.4 选择建议 场景 推荐 子类确实是父类的一种（is-a） 继承 只是想复用某个类的功能（has-a） 组合 需要运行时切换行为 组合 + 依赖注入 需要定义统一接口 抽象基类 / 协议 一句话总结： 优先使用组合，只有在明确的 is-a 关系且父类设计稳定时才使用继承。 7. 总结 类定义模板，实例承载具体数据， self 指向当前实例； 实例属性各自独立，类属性共享， property 让属性访问更可控； 继承用于表达 is-a 关系，重写配合 super() 复用父类逻辑； 多态让调用方只依赖接口，Python 的鸭子类型让这一点更自然； 组合通过委托复用功能，通常比继承更灵活、更易维护。 在实际项目中，先想清楚对象之间是&quot;是什么&quot;还是&quot;有什么&quot;，往往就能做出正确的结构选择。</description><pubDate>Wed, 28 Oct 2026 02:28:31 +0000</pubDate></item><item><title>ValidX vs 正则表达式：可读性与维护性对比</title><link>https://juejin.cn/post/7701250481567039528</link><guid isPermaLink="false">juejin-f8c6594e16925783</guid><description>ValidX vs 正则表达式：可读性与维护性对比 📋 目录 引言 一、正则表达式：验证领域的双刃剑 1.1 正则表达式的优势 1.2 正则表达式的三大痛点 1.3 本文的目标 二、典型场景对比：同一个需求，两种实现 2.1 中国手机号验证 2.2 邮箱地址验证 2.3 统一社会信用代码 2.4 银行卡号验证（Luhn 算法） 三、可读性对比：谁能让人一眼看懂 3.1 正则表达式的可读性天花板 3.2 ValidX 的语义化 API 3.3 可读性评分矩阵 四、维护性对比：规则变化时谁更从容 4.1 号段更新：正则的噩梦 4.2 业务逻辑扩展：正则的边界 4.3 ValidX 的维护优势 五、性能对比：预编译正则 vs 结构化校验 5.1 纯正则方案的性能特征 5.2 ValidX 混合方案的性能特征 5.3 性能实测数据 六、适用场景分析：什么时候用什么 6.1 适合用正则的场景 6.2 适合用 ValidX 的场景 6.3 混合方案：最佳实践 七、ValidX 的设计哲学：正则只是工具之一 7.1 分层校验架构 7.2 正则 + 算法的组合拳 7.3 错误消息的可读性 八、从正则迁移到 ValidX 的实战指南 8.1 迁移步骤 8.2 常见迁移案例 8.3 迁移检查清单 九、总结 项目地址 引言 在数据验证领域，正则表达式（Regex）是最广为人知的工具。几乎每个开发者都写过类似 value.matches(&quot;\\d{11}&quot;) 的代码。它简洁、通用、随处可见。 但当验证需求从&quot;格式对不对&quot;升级到&quot;值合不合法&quot;时，正则表达式开始力不从心。手机号光格式正确不够，还得号段存在；身份证光位数对不够，还得校验位正确；银行卡光数字够长不够，还得通过 Luhn 算法。 ValidX 的定位 ：不是替代正则，而是在正则之上提供 语义化、可扩展、可维护 的验证层。 本文通过 6 个真实业务场景，从可读性、维护性、性能三个维度，详细对比正则表达式与 ValidX 的差异，帮你做出正确的技术选型。 一、正则表达式：验证领域的双刃剑 1.1 正则表达式的优势 优势 说明 示例 通用性 几乎所有语言都支持 Java、Python、JavaScript 通用 简洁性 简单模式一行搞定 ^\d{6}$ 匹配 6 位数字 性能 预编译后匹配速度快 Pattern.matcher().matches() 表达力 能描述复杂的文本模式 邮箱、URL、IP 等格式 1.2 正则表达式的三大痛点 ┌──────────────────────────────────────────────────────────────┐ │ 正则表达式在业务验证中的三大痛点 │ │ │ │ ① 可读性差 │ │ · 复杂正则如同&quot;天书&quot;，非正则专家难以理解 │ │ · 例：^(?:13 [0-9] |14 [579] |15 [0-3,5-9] |16 [2567] |17 [0-8] │ │ 18 [0-9] |19 [0-9] )\d{8}$ │ │ · 新人接手代码时需要逐字符分析 │ │ │ │ ② 维护性差 │ │ · 规则变化时需要修改正则，容易引入 bug │ │ · 号段更新、规则调整都需要重写正则 │ │ · 缺乏结构化，难以局部修改 │ │ │ │ ③ 表达力有限 │ │ · 只能验证&quot;格式&quot;，无法验证&quot;值&quot; │ │ · 无法表达数学计算（如 Luhn 算法、mod 11 校验） │ │ · 无法关联外部数据（如行政区划表、号段表） │ └──────────────────────────────────────────────────────────────┘ 1.3 本文的目标 不是&quot;正则 vs ValidX&quot;的二选一，而是帮你理解： 什么时候正则就够了？ 什么时候需要 ValidX？ 如何组合使用达到最佳效果？ 二、典型场景对比：同一个需求，两种实现 2.1 中国手机号验证 需求 ：验证一个字符串是否为合法的中国大陆手机号。 正则方案 // 正则方案：一个超长正则表达式 private static final Pattern PHONE_PATTERN = Pattern.compile( &quot;^1(?:3[0-9]|4[579]|5[0-35-9]|6[2567]|7[0-8]|8[0-9]|9[0-9])\\d{8}$&quot; ); public boolean isValidPhone (String value) { if (value == null ) return false ; return PHONE_PATTERN.matcher(value).matches(); } 问题 ： 这个正则你能一眼看懂吗？ 4[579] 是什么意思？为什么 5[0-35-9] 跳过了 4？ 当工信部新增一个号段（如 192），你需要修改正则——在 30 多个字符中找到正确位置插入 如果写错了（如漏掉一个号段），很难通过代码审查发现 ValidX 方案 // ValidX 方案：一个注解搞定 @ChinesePhone private String phone; // 或者链式 API ValidX.init() .validate().chinesePhone(phone).end(); ValidX 内部实现 ： // ValidX ChinesePhoneValidator 的实际实现 public class ChinesePhoneValidator implements ConstraintValidator &lt;ChinesePhone, String&gt; { // 预编译正则——格式检查 private static final Pattern MOBILE_PATTERN = Pattern.compile( &quot;^1[3-9]\\d{9}$&quot; ); @Override public boolean isValid (String value, ConstraintValidatorContext context) { if (value == null || value.isEmpty()) return true ; String cleanValue = value.replaceAll( &quot;[\\s-]&quot; , &quot;&quot; ); return cleanValue.length() == 11 &amp;&amp; MOBILE_PATTERN.matcher(cleanValue).matches(); } } 对比分析 ： 维度 正则方案 ValidX 方案 代码量 5~8 行 1 行注解 可读性 需要正则知识 语义化，一眼看懂 号段精确度 可以精确到每个号段 宽松匹配（1[3-9]） 维护成本 每次号段更新改正则 框架维护 复用性 每处都要引用 Pattern 注解声明，全局复用 2.2 邮箱地址验证 需求 ：验证邮箱格式是否合法。 正则方案 // 邮箱正则——看似简单，实则很难写对 private static final Pattern EMAIL_PATTERN = Pattern.compile( &quot;^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$&quot; ); // 更严格的 RFC 5322 正则（长达数百字符） private static final Pattern RFC5322_EMAIL = Pattern.compile( &quot;(?:[a-z0-9!#$%&amp;&apos;*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&amp;&apos;*+/=?^_`{|}~-]+)*|&quot; + &quot;\&quot;(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|&quot; + &quot;\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\&quot;)@(?:(?:[a-z0-9]&quot; + &quot;(?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|&quot; + &quot;\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}&quot; + &quot;(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:&quot; + &quot;(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|&quot; + &quot;\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])&quot; ); 问题 ： 简单版会漏掉很多合法邮箱（如含 + 、 . 的用户名） 严格版长达数百字符，完全不可读 无法验证域名是否真实存在 ValidX 方案 @Email private String email; ValidX 使用 JSR-303 标准的 @Email 注解，内部正则经过充分测试，覆盖了绝大多数合法邮箱格式。 2.3 统一社会信用代码 需求 ：验证 18 位统一社会信用代码是否合法。 正则方案 // 正则方案：只能验证格式，无法验证校验位 private static final Pattern CREDIT_CODE_PATTERN = Pattern.compile( &quot;^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$&quot; ); public boolean isValidCreditCode (String value) { if (value == null || value.length() != 18 ) return false ; return CREDIT_CODE_PATTERN.matcher(value).matches(); // ❌ 无法验证校验位！一个格式正确但校验位错误的代码也会通过 } 致命缺陷 ：正则只能验证&quot;格式&quot;，无法验证&quot;值&quot;。统一社会信用代码的最后一位是通过 mod 31 加权算法计算出来的，正则无法表达这个数学运算。 ValidX 方案 @UnifiedSocialCreditCode private String creditCode; ValidX 内部实现 ： // ValidX 的完整校验：格式 + 校验位 public class UnifiedSocialCreditCodeValidator implements ConstraintValidator &lt;UnifiedSocialCreditCode, String&gt; { private static final Map&lt;Character, Integer&gt; CODE_MAP = new HashMap &lt;&gt;(); private static final int [] WEIGHT = { 1 , 3 , 9 , 27 , 19 , 26 , 16 , 17 , 20 , 29 , 25 , 13 , 8 , 24 , 10 , 30 , 28 }; private static final char [] CHECK_CODES = &quot;0123456789ABCDEFGHJKLMNPQRTUWXY&quot; .toCharArray(); static { for ( int i = 0 ; i &lt; CHECK_CODES.length; i++) { CODE_MAP.put(CHECK_CODES[i], i); } } @Override public boolean isValid (String value, ConstraintValidatorContext context) { if (value == null || value.isEmpty()) return true ; if (value.length() != 18 ) return false ; // ① 字符合法性检查 for ( int i = 0 ; i &lt; 18 ; i++) { if (!CODE_MAP.containsKey(value.charAt(i))) return false ; } // ② 加权求和 int sum = 0 ; for ( int i = 0 ; i &lt; 17 ; i++) { sum += CODE_MAP.get(value.charAt(i)) * WEIGHT[i]; } // ③ 校验位验证 int remainder = sum % 31 ; int checkCodeIndex = (remainder == 0 ) ? 0 : 31 - remainder; return CHECK_CODES[checkCodeIndex] == value.charAt( 17 ); } } 对比分析 ： 维度 正则方案 ValidX 方案 格式校验 ✅ 可以 ✅ 可以 校验位验证 ❌ 无法实现 ✅ mod 31 算法 正确率 低（假阳性高） 高（数学验证） 代码量 1 行正则 50+ 行结构化代码 可维护性 无法扩展 逻辑清晰，易于修改 2.4 银行卡号验证（Luhn 算法） 需求 ：验证银行卡号是否合法（通过 Luhn 模 10 算法）。 正则方案 // ❌ 正则无法实现 Luhn 算法 // 只能验证&quot;是否为 16~19 位数字&quot; private static final Pattern CARD_PATTERN = Pattern.compile( &quot;^\\d{16,19}$&quot; ); public boolean isValidBankCard (String value) { return CARD_PATTERN.matcher(value).matches(); // ❌ 无法验证 Luhn 校验位！ } ValidX 方案 @BankCard private String bankCard; ValidX 内部实现 Luhn 算法 ： public class BankCardValidator implements ConstraintValidator &lt;BankCard, String&gt; { @Override public boolean isValid (String value, ConstraintValidatorContext context) { if (value == null || value.isEmpty()) return true ; // 去除空格和连字符 String cleanValue = value.replaceAll( &quot;[\\s-]&quot; , &quot;&quot; ); // 长度检查 if (cleanValue.length() &lt; 16 || cleanValue.length() &gt; 19 ) return false ; // 必须全是数字 if (!cleanValue.matches( &quot;\\d+&quot; )) return false ; // Luhn 算法（模 10 算法） return luhnCheck(cleanValue); } private boolean luhnCheck (String cardNumber) { int sum = 0 ; boolean alternate = false ; for ( int i = cardNumber.length() - 1 ; i &gt;= 0 ; i--) { int digit = Character.getNumericValue(cardNumber.charAt(i)); if (alternate) { digit *= 2 ; if (digit &gt; 9 ) { digit = digit / 10 + digit % 10 ; // 两位数字之和 } } sum += digit; alternate = !alternate; } return sum % 10 == 0 ; } } 结论 ：Luhn 算法涉及&quot;从右到左遍历、交替翻倍、数字求和&quot;——这些操作正则表达式完全无法表达。 三、可读性对比：谁能让人一眼看懂 3.1 正则表达式的可读性天花板 来看一个真实项目中的身份证号正则： // 你能在 5 秒内看懂这个正则吗？ private static final Pattern ID_CARD_PATTERN = Pattern.compile( &quot;^[1-9]\\d{5}(?:19\\d{2}|20\\d{2})(?:0[13-9]|1[0-2])(?:0[1-9]|[12]\\d|3[01])&quot; + &quot;\\d{3}[\\dXx]$&quot; ); 这个正则的含义： [1-9]\\d{5} ：6 位地区编码（首位不为 0） (?:19\\d{2}|20\\d{2}) ：年份（19xx 或 20xx） (?:0[13-9]|1[0-2]) ：月份（01-12，排除 02 的特殊处理在日期中） (?:0[1-9]|[12]\\d|3[01]) ：日期（01-31） \\d{3} ：顺序码 [\\dXx] ：校验码 问题 ： 需要正则专家才能解读 即使看懂了，也很难确认&quot;逻辑是否正确&quot; 代码审查时，非正则专家无法有效 review 3.2 ValidX 的语义化 API // ValidX 方案：语义清晰 @ChineseIdCard private String idCard; 即使不看实现，任何人都能理解：这个字段必须是一个合法的中国身份证号。 如果需要更细致的控制： // 链式 API：语义化表达 ValidX.init() .validate() .chineseIdCard(idCard, &quot;身份证号码不合法&quot; ) .chinesePhone(phone, &quot;手机号不合法&quot; ) .email(email, &quot;邮箱格式不正确&quot; ) .end(); 3.3 可读性评分矩阵 验证场景 正则可读性 ValidX 可读性 差距 6 位数字编码 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ 持平 手机号格式 ⭐⭐⭐ ⭐⭐⭐⭐⭐ ValidX 胜 邮箱格式 ⭐⭐ ⭐⭐⭐⭐⭐ ValidX 胜 身份证格式 ⭐⭐ ⭐⭐⭐⭐⭐ ValidX 胜 身份证 + 校验位 ❌ 无法实现 ⭐⭐⭐⭐⭐ ValidX 完胜 银行卡 Luhn ❌ 无法实现 ⭐⭐⭐⭐⭐ ValidX 完胜 统一社会信用代码 ⭐⭐ ⭐⭐⭐⭐⭐ ValidX 完胜 四、维护性对比：规则变化时谁更从容 4.1 号段更新：正则的噩梦 场景 ：工信部新增 192 号段给中国广电。 正则方案的维护成本 // 修改前 &quot;^1(?:3[0-9]|4[579]|5[0-35-9]|6[2567]|7[0-8]|8[0-9]|9[0-35-9])\\d{8}$&quot; // 修改后：需要在 9[0-35-9] 中加入 2，变成 9[0-35-9] → 9[0-35-9] 变成 9[0-35-9] 不对 // 实际上需要在正确的位置插入 2 &quot;^1(?:3[0-9]|4[579]|5[0-35-9]|6[2567]|7[0-8]|8[0-9]|9[0-35-9])\\d{8}$&quot; // 变成 &quot;^1(?:3[0-9]|4[579]|5[0-35-9]|6[2567]|7[0-8]|8[0-9]|9[0-35-9])\\d{8}$&quot; // 最终修改为 &quot;^1(?:3[0-9]|4[579]|5[0-35-9]|6[2567]|7[0-8]|8[0-9]|9[0-35-9])\\d{8}$&quot; 问题 ： 需要在复杂的正则中找到正确位置 修改容易出错（如漏掉一个分支、括号不匹配） 需要充分测试才能确认修改正确 如果项目中有 10 处使用了这个正则，需要修改 10 处 ValidX 方案的维护成本 // ValidX 方案：框架维护者负责更新 // 用户代码无需任何修改 @ChinesePhone private String phone; ValidX 的维护者会更新内部的号段匹配逻辑，用户只需升级依赖版本。 4.2 业务逻辑扩展：正则的边界 场景 ：身份证验证需要增加&quot;不能是未来日期&quot;的检查。 正则方案 // ❌ 正则无法表达&quot;日期不能是未来&quot;这个逻辑 // 必须配合额外的 Java 代码 public boolean isValidIdCard (String value) { if (!ID_CARD_PATTERN.matcher(value).matches()) return false ; // 还需要额外的日期检查代码 String birthDate = value.substring( 6 , 14 ); try { LocalDate date = LocalDate.parse(birthDate, DateTimeFormatter.BASIC_ISO_DATE); if (date.isAfter(LocalDate.now())) return false ; } catch (Exception e) { return false ; } // 还需要校验位检查 // ... 50+ 行代码 return true ; } 问题 ：正则只能做&quot;格式检查&quot;，业务逻辑必须用额外的代码实现。最终代码变成了&quot;正则 + Java 代码&quot;的混合体，维护成本更高。 ValidX 方案 // ValidX 方案：所有逻辑封装在验证器内部 @ChineseIdCard private String idCard; // 用户代码始终是一行注解，内部逻辑由框架维护 4.3 ValidX 的维护优势 维护场景 正则方案 ValidX 方案 号段更新 修改正则，全量回归测试 升级依赖版本 算法变更 重写正则 + 配套代码 升级依赖版本 新增验证类型 从零编写正则 + 代码 直接使用注解 多处修改 全局搜索替换，容易遗漏 无需修改用户代码 Bug 修复 自行排查正则问题 框架统一修复 五、性能对比：预编译正则 vs 结构化校验 5.1 纯正则方案的性能特征 // 纯正则方案 private static final Pattern PATTERN = Pattern.compile( &quot;...&quot; ); public boolean isValid (String value) { return PATTERN.matcher(value).matches(); } 性能特征 ： 预编译后，单次匹配 ~0.3μs 对于复杂正则（如身份证），回溯可能导致性能劣化 无法做&quot;快速失败&quot;优化 5.2 ValidX 混合方案的性能特征 // ValidX 混合方案：正则 + 结构化检查 public boolean isValid (String value, ConstraintValidatorContext context) { // ① 长度检查：O(1)，纳秒级 if (value.length() != 18 ) return false ; // ② 正则格式检查：~0.3μs if (!EIGHTEEN_ID_PATTERN.matcher(value).matches()) return false ; // ③ 省份编码检查：HashMap O(1)，~0.1μs if (!ChinaProvince.fromCode(value.substring( 0 , 2 )).isPresent()) return false ; // ④ 日期检查：~0.2μs if (!isValidBirthDate(value.substring( 6 , 14 ))) return false ; // ⑤ 校验位计算：~0.5μs return verifyCheckCode(value); } 关键优势 ：快速失败（Fail Fast） 对于非法输入，平均在步骤 ①② 就被拒绝，耗时 &lt; 0.5μs 只有格式正确的输入才会进入后续计算 总体性能与纯正则相当，但正确率远高于纯正则 5.3 性能实测数据 方案 合法输入耗时 非法输入耗时 正确率 纯正则（格式检查） ~0.3μs ~0.3μs 60%（假阳性高） ValidX（混合方案） ~1.2μs ~0.3μs（快速失败） 99.9%+ ValidX（优化后） ~0.8μs ~0.2μs 99.9%+ 结论 ：ValidX 的混合方案在保持高性能的同时，大幅提升了验证正确率。 六、适用场景分析：什么时候用什么 6.1 适合用正则的场景 场景 原因 示例 简单格式检查 正则足够，无需复杂逻辑 邮编、固定长度数字 文本模式匹配 正则的强项 URL、IP、日期格式 一次性验证 无需复用和维护 临时脚本、原型验证 性能极致要求 正则预编译后最快 高频日志解析 // ✅ 适合用正则的场景 private static final Pattern ZIP_CODE = Pattern.compile( &quot;^\\d{6}$&quot; ); private static final Pattern IPV4 = Pattern.compile( &quot;^(?:(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}&quot; + &quot;(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$&quot; ); 6.2 适合用 ValidX 的场景 场景 原因 示例 需要校验位验证 正则无法表达算法 身份证、银行卡、信用代码 规则频繁变化 框架统一维护 手机号号段 多处复用 注解声明，全局复用 DTO 字段验证 需要国际化错误消息 内置 i18n 支持 多语言应用 业务逻辑复杂 结构化代码更易维护 多步骤校验 // ✅ 适合用 ValidX 的场景 @ChineseIdCard // 需要校验位算法 @BankCard // 需要 Luhn 算法 @ChinesePhone // 号段频繁更新 @UnifiedSocialCreditCode // 需要 mod 31 算法 6.3 混合方案：最佳实践 原则 ：正则做&quot;粗筛&quot;，ValidX 做&quot;精验&quot;。 // ✅ 混合方案：正则快速筛选 + ValidX 精确验证 public class UserRegistrationService { // 简单格式用正则（足够） private static final Pattern USERNAME_PATTERN = Pattern.compile( &quot;^[a-zA-Z0-9_]{3,20}$&quot; ); public ValidationResult validate (UserDTO user) { // ① 用户名：正则足够 if (!USERNAME_PATTERN.matcher(user.getUsername()).matches()) { return ValidationResult.error( &quot;用户名格式不正确&quot; ); } // ② 身份证：需要 ValidX（校验位算法） ChineseIdCardValidator idValidator = new ChineseIdCardValidator (); if (!idValidator.isValid(user.getIdCard(), null )) { return ValidationResult.error( &quot;身份证号码不合法&quot; ); } // ③ 银行卡：需要 ValidX（Luhn 算法） BankCardValidator cardValidator = new BankCardValidator (); if (!cardValidator.isValid(user.getBankCard(), null )) { return ValidationResult.error( &quot;银行卡号不合法&quot; ); } return ValidationResult.success(); } } 七、ValidX 的设计哲学：正则只是工具之一 7.1 分层校验架构 ┌──────────────────────────────────────────────────────────────┐ │ ValidX 的分层校验架构 │ │ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 第 1 层：空值检查 │ │ │ │ · null / empty → 交给 @NotNull / @NotEmpty 处理 │ │ │ └────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 第 2 层：快速格式检查（正则 / 长度 / 字符合法性） │ │ │ │ · 预编译 Pattern 、length()、charAt() │ │ │ │ · 目标：快速拒绝明显非法的输入 │ │ │ └────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 第 3 层：结构化校验（算法 / 查找表 / 业务规则） │ │ │ │ · 校验位算法（mod 11 、mod 31 、Luhn） │ │ │ │ · 编码查找（行政区划、号段表） │ │ │ │ · 日期有效性、业务规则 │ │ │ └────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 第 4 层：国际化错误消息 │ │ │ │ · ResourceBundle + ConcurrentHashMap 缓存 │ │ │ └────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────┘ 7.2 正则 + 算法的组合拳 ValidX 的验证器通常采用&quot;正则 + 算法&quot;的组合： // 典型模式：正则做粗筛，算法做精验 public boolean isValid (String value, ConstraintValidatorContext context) { // 第 1 步：正则快速筛选（排除明显非法的） if (!PATTERN.matcher(value).matches()) { return false ; } // 第 2 步：算法精确验证（校验位、业务规则） return algorithmCheck(value); } 这种模式的优势 ： 正则负责&quot;快速拒绝&quot;，发挥其性能优势 算法负责&quot;精确验证&quot;，弥补正则的表达力不足 两者互补，达到性能和正确率的最佳平衡 7.3 错误消息的可读性 正则方案 // 正则方案的错误消息：模糊 if (!pattern.matcher(value).matches()) { throw new ValidationException ( &quot;格式不正确&quot; ); // 用户不知道具体哪里不正确 } ValidX 方案 // ValidX 的错误消息：语义化 + 国际化 // 中文：请输入有效的身份证号码 // 英文：Please enter a valid ID card number // 日文：有効な身分証番号を入力してください ValidX 内置 9 种语言的错误消息，且每条消息都经过精心设计，符合当地语言习惯。 八、从正则迁移到 ValidX 的实战指南 8.1 迁移步骤 ┌──────────────────────────────────────────────────────────────┐ │ 从正则迁移到 ValidX 的 4 个步骤 │ │ │ │ ① 识别：找出项目中所有用于业务验证的正则 │ │ · grep -r &quot; Pattern .compile &quot; --include =&quot;* .java &quot; │ │ · grep -r &quot;\ .matches ( &quot; --include=&quot; *.java&quot; │ │ │ │ ② 分类：区分 &quot;格式验证&quot; 和 &quot;业务验证&quot; │ │ · 格式验证（邮编、IP）→ 保留正则 │ │ · 业务验证（身份证、银行卡）→ 迁移到 ValidX │ │ │ │ ③ 替换：用 ValidX 注解或链式 API 替换 │ │ · 注解模式： @ChineseIdCard │ │ · 链式模式：ValidX. init (). validate (). chineseIdCard () │ │ │ │ ④ 测试：验证迁移后的正确性 │ │ · 单元测试覆盖 │ │ · 边界值测试 │ │ · 性能回归测试 │ └──────────────────────────────────────────────────────────────┘ 8.2 常见迁移案例 案例 1：手机号验证迁移 // ❌ 迁移前：正则方案 private static final Pattern PHONE = Pattern.compile( &quot;^1[3-9]\\d{9}$&quot; ); public boolean validatePhone (String phone) { return PHONE.matcher(phone).matches(); } // ✅ 迁移后：ValidX 注解模式 @ChinesePhone private String phone; // ✅ 迁移后：ValidX 链式 API boolean valid = ValidX.init().validate().chinesePhone(phone).isSuccess(); 案例 2：身份证验证迁移 // ❌ 迁移前：正则 + 手写算法（难以维护） public boolean validateIdCard (String idCard) { if (!idCard.matches( &quot;^[0-9]{17}[0-9X]$&quot; )) return false ; // 50+ 行手写校验逻辑... return true ; } // ✅ 迁移后：一行注解 @ChineseIdCard private String idCard; 案例 3：银行卡验证迁移 // ❌ 迁移前：无法验证 Luhn（只有格式检查） private static final Pattern CARD = Pattern.compile( &quot;^\\d{16,19}$&quot; ); public boolean validateCard (String card) { return CARD.matcher(card).matches(); // ❌ 假阳性高 } // ✅ 迁移后：完整的 Luhn 验证 @BankCard private String bankCard; 8.3 迁移检查清单 识别所有用于业务验证的正则表达式 区分&quot;格式验证&quot;（保留正则）和&quot;业务验证&quot;（迁移 ValidX） 选择合适的 ValidX 注解或链式 API 编写单元测试，确保迁移后行为一致 性能测试，确保没有回归 更新文档，说明迁移原因和新用法 代码审查，确保团队理解新方案 九、总结 核心对比总结 维度 正则表达式 ValidX 结论 可读性 复杂正则难读懂 语义化注解，一目了然 ValidX 胜 维护性 规则变化需重写 框架统一维护 ValidX 胜 表达力 只能验证格式 格式 + 算法 + 业务 ValidX 胜 性能 预编译后极快 混合方案，相当 持平 正确率 假阳性高 数学验证，99.9%+ ValidX 胜 通用性 所有语言支持 Java 专用 正则胜 学习成本 需要学正则语法 学注解名称即可 ValidX 胜 选型决策树 需要验证什么？ │ ├─ 纯格式检查（邮编、IP、简单模式） │ └─ ✅ 用正则表达式（足够、快速） │ ├─ 需要校验位算法（身份证、银行卡、信用代码） │ └─ ✅ 用 ValidX（正则无法实现） │ ├─ 规则频繁变化（手机号号段） │ └─ ✅ 用 ValidX（框架维护） │ ├─ 多处复用 + 国际化需求 │ └─ ✅ 用 ValidX（注解声明 + 内置 i18n） │ └─ 不确定？ └─ ✅ 用 ValidX（更安全、更易维护） 关键认知 正则表达式是&quot;工具&quot;，ValidX 是&quot;方案&quot;。 正则擅长&quot;模式匹配&quot;——它是文本处理的瑞士军刀 ValidX 擅长&quot;业务验证&quot;——它封装了领域知识和最佳实践 两者不是替代关系，而是互补关系 在实际项目中， ValidX 内部也在大量使用正则 ——只是把正则放在了合适的位置，用结构化的代码包裹起来，对外提供简洁的 API。 选择 ValidX，不是放弃正则，而是让正则回归它最擅长的位置。 项目地址 GitHub: github.com/vipxieliang… Gitee: gitee.com/vipxieliang…</description><pubDate>Wed, 28 Oct 2026 02:22:53 +0000</pubDate></item><item><title>Three.js 医院三维大屏实战：Codex + Blender MCP + React 从建模到交互全流程</title><link>https://juejin.cn/post/7700887980140429322</link><guid isPermaLink="false">juejin-f7096b65558d5339</guid><description>先看设计定稿的效果图，这是我想做出来的样子。 然后诚实一点：网页真跑起来比这丑。材质没跟上，植被是几块色块，背面立面基本没细节。但它能转、能点、能从院区走进楼层，数字对得上账。 在线版（建议电脑打开）： hospital-twin 代码： github.com/Xxcool/hosp… 先说清楚这项目是怎么来的： 建模、写码、改 bug，主要是我在 Codex 里下任务、验收、打回重做；我负责判断&quot;像不像&quot;和&quot;能不能用&quot;，它负责把活干出来。 源码 800 行左右，模型 18 万三角形 / 112 个 mesh，中间来回改了不少轮。 边界也说明一下：个人演示项目，不是医院官方的东西，数据全是模拟的，内部空间是示意设计，别拿它导航或做任何运营决策。 一、先别急着让 AI 写代码，先说怎么给它派活 我踩的第一个坑不在代码里，在流程上：如果直接甩一句&quot;帮我做个医院三维大屏&quot;，Codex 会给你一个能跑的、漂亮的、但完全没有逻辑的东西——模型是随便摆的，数据是随机数，镜头写死的。 真正管用的是把活儿切成六个阶段，每个阶段给它一个 可验证的交付物 ： 阶段 我做什么 Codex 做什么 1. 调研收敛 定死只讲门诊和急诊，其他楼只做导览 查公开资料、整理空间关系、写调研记录 2. 整屏方案 拍板视觉方向 生成几套效果图供选 3. 代表区域 判断像不像 通过 Blender MCP 建模、导 GLB 4. 样片验证 对着实拍挑刺 按反馈改材质、改结构 5. 逐区开发 定交互规则 写 React 面板 + Three.js 场景 6. 验收 提需求、截图核对 修布局、修性能、跑测试 两个关键点： 第一， 范围必须提前砍 。医院大屏最容易堆满能耗、安防、工单、设备，AI 也会顺着你的话往下堆。我一开始就砍到只剩门急诊两个业务区，医技、住院、儿童医院、停车楼只保留空间导览——不虚构自己讲不清的专题。 第二， 判断标准要可验证 。说&quot;做得好看点&quot;对 AI 毫无意义，说&quot;入口弧线要以这张实拍照片为准，中段后退 16 米&quot;才有意义。这条经验后面省了我不少返工。 二、AI 搞砸的第一件事：把入口弧线画反了 门诊入口是整个院区辨识度最高的部分：低层弧形入口在前，高楼群在后。 第一版出来我觉得挺像，还截了图。然后我把入口实拍近景翻出来对照—— 弧线方向反了。 真实的入口幕墙是中间向后退、两端向前伸。AI 做的是中间鼓出去、两边缩回来。俯视一看，整栋楼的气质全歪了。 它为什么会错？因为它从&quot;弧形入口&quot;这四个字出发，推了一个视觉上合理的曲面；而真实的建筑是一条我自己都没量过的曲线： y = -27 - 16 * (x/60)² ，中间相对两端后退 16 米。 AI 不知道这 16 米，我也不说，它就只能猜。 更麻烦的是善后。那条曲线是整栋楼的参照物： 屋顶弧形压条要跟着重新弯； 幕墙竖向构件要重新排布； 雨篷门洞要重新定位； 入口铺地和草地在曲线反向处裂了一条缝，得重新建模衔接。 参数本身改了几分钟，但屋顶压条、幕墙构件和铺地那道缝，修修补补花了小半天。这件事让我彻底改了一个判断： 三维场景可不可信，跟单个模型精细度关系不大，跟模型之间接不接得上关系极大。 后来专门补了楼间连接裙房和铺地，院区才从&quot;几栋摆在一起的楼&quot;变成&quot;一个院区&quot;。 三、技术点一：让模型自带身份证，别用屏幕坐标猜 大屏最经典的翻车画面：点一栋楼，右边的数据跟这栋楼毫无关系。数据是数据，模型是模型，两张皮。 解决办法不是写更聪明的拾取逻辑，而是 在建模阶段就把身份埋进模型 。每个建筑、每个楼层组、每个分区面，导出时带上 userData ，再额外挂一个空物体做锚点： // 加载时按约定取锚点，取不到直接抛错，不允许&quot;静默降级&quot; for ( const id of [...regions. map ( r =&gt; r. id ), &apos;floor_f1&apos; , &apos;floor_f2&apos; ]) { const node = model. getObjectByName ( `anchor_ ${id} ` ); if (!node) throw new Error ( `模型锚点缺失： ${id} ` ); anchors. set (id, node); } 这个 throw 是我特意留的。早期我让它缺锚点时打条 warning 就算了，结果改完模型忘了同步，点击某些楼栋镜头纹丝不动，查了半小时才发现是名字改了。 早失败比晚失败便宜得多。 拾取也有两个真实的坑： 坑一：Raycaster 不认 visible 。 intersectObject 会把被隐藏的对象照样算进命中结果里——哪怕父级 visible = false 。所以必须自己递归判断： const isVisible = o =&gt; !o. visible ? false : o. parent ? isVisible (o. parent ) : true ; 不写这一句，楼层展开时点到的可能是被藏起来的隔壁楼。 坑二：拖拽会误触发点击。 用户按住鼠标转视角，松手时位移几乎是零， click 照样触发。所以我把拾取挂在 pointerup ，并且只认位移小于 6 像素的那一下： if (!origin || Math . hypot (e. clientX - origin[ 0 ], e. clientY - origin[ 1 ]) &gt; 6 ) return ; const rect = renderer. domElement . getBoundingClientRect (); pointer. set ((e. clientX - rect. left ) / rect. width * 2 - 1 , -(e. clientY - rect. top ) / rect. height * 2 + 1 ); 注意这里用的是 getBoundingClientRect() 而不是 window.innerWidth ——这一点在后面适配那节会救你一命。 四、技术点二：镜头别手调，用包围盒 + 安全区算 第一版聚焦，我让 Codex 给每栋楼写死了一组相机参数。它写得很乖，参数也调得好看。 问题是院区里楼群体量差得太离谱——停车楼和门诊楼差好几倍，同一组参数，小楼看不清，大楼顶到面板后面。 后来改成让相机自己算。核心代码就几行，但里面有三个数字很值钱： // 用相机基向量把包围盒 8 个角点投影到屏幕横竖方向 for ( const x of [b. min . x , b. max . x ]) for ( const y of [b. min . y , b. max . y ]) for ( const z of [b. min . z , b. max . z ]) { const v = new THREE . Vector3 (x, y, z). sub (center); minX = Math . min (minX, v. dot (right)); maxX = Math . max (maxX, v. dot (right)); minY = Math . min (minY, v. dot (up)); maxY = Math . max (maxY, v. dot (up)); } const pixelsPerUnit = el. clientHeight / (camera. top - camera. bottom ); goalZoom = Math . min ( 1000 * scale / ((maxX - minX) * pixelsPerUnit), // 安全区宽 1000 逻辑像素 540 * scale / ((maxY - minY) * pixelsPerUnit) // 安全区高 540 ) * 0.92 ; 三个数字： 1000 × 540 ：这是 1920×1080 画布里真正&quot;没被 UI 占住&quot;的那块区域。左边导览、右边详情、顶上标题、底下时段栏全占着位置。把建筑塞满整个浏览器，不代表用户看得见它。 0.92 ：留白系数。紧贴边缘的取景在静态截图上还行，动起来会显得憋。 scale ：因为整屏做了等比缩放，安全区也要跟着缩放，不能写死像素。 还有一个更细的坑： UI 的视觉中心不是画布几何中心。 面板布局让内容区整体偏上，直接对准几何中心会导致建筑偏下。所以目标点还得补一个纵向偏移： goalTarget. copy (center). addScaledVector ( up, (top + 550 * scale - el. clientHeight / 2 ) / (pixelsPerUnit * goalZoom) ); 这个 550 是照着设计稿量出来的逻辑坐标。写死它不优雅，但比来回试参数靠谱——这类补偿值建议直接写注释说明出处，否则半年后自己都不知道为什么。 五、技术点三：楼层展开，取景要看终点 楼层展开更阴： 展开动画从收拢状态起飞。如果你拿&quot;当前状态&quot;算包围盒，镜头会按收拢时的大小取景，等楼层散开，屋顶早就飞出画面了。 做法是先把楼层组临时挪到展开终态，算一遍整体包围盒，再挪回去： for ( const [id, group] of floorGroups) { group. visible = !zone || id === floor; group. position . y = lerp (group. position . y , manifest. floors . explode [id], .09 ); group. position . z = lerp (group. position . z , -manifest. floors . back [id], .09 ); } explode / back 两个偏移量写在场景清单 manifest.json 里，而不是写进代码—— 因为它们是模型的属性，不是页面的属性 。改模型的人顺手就能改，不用来找前端。 顺带说个近景处理的细节：进到候诊区视角时，必须把医技、儿童医院、住院、停车、急诊这几栋藏掉，否则连接裙房会横在示意内装前面，透视上糊成一片。什么时候藏、藏哪些、退出来怎么恢复，全都跟着交互状态走，这部分逻辑比加载模型烦多了。 六、技术点四：假数据最容易翻车 大屏文章爱吹&quot;实时数字孪生&quot;，我不吹。这个项目的数据就是四组写死的演示快照：08:00 开诊、10:00 门诊集中、15:00 午后回落、20:00 急诊关注。 但&quot;假数据&quot;不等于&quot;随便的数据&quot;。四个时段共用一个入口 getFrame() ，总览必须从分项算出来： total. waiting = outpatient. waiting + emergency. waiting ; total. open = outpatient. open + emergency. open ; 听起来像废话。可大屏项目里&quot;顶上大数字 172、左边门诊 148、右边急诊 24&quot;对不上的情况太常见了——通常是三处各填各的假数。 观众未必说得出哪里不对，但一定会觉得&quot;这屏是假的&quot;。反过来，数字只要对得上账，观众会本能信任它， 哪怕这些数字全是编的 。 这是整个项目里我最意外的体感：可信度是可以被算出来的，模型做得再细也补不了数据逻辑的窟窿。 20:00 这一档还会联动切夜间。灯亮起来、天色调下去、Bloom 强度抬高，演示到&quot;急诊关注&quot;，屏幕自己就沉了。 夜间设计目标： 实际跑出来（差距一清二楚，但联动是真的）： 材质调色是靠命名约定做的：模型里的 TJ_glass 、 TJ_warmGlass 、 floorEdge 这些材质名对应一张昼夜色板，切夜间时遍历所有材质改颜色和自发光， warmGlass 额外点亮成暖黄。 命名规范又一次代替了 if-else。 七、技术点五：整屏 scale 适配的三个坑 适配我选了最朴素的：1920×1080 逻辑画布 + 整体等比缩放。 ResizeObserver 算比例，根节点 transform: scale() ，非 16:9 的窗口留背景居中。 不用流式响应式的理由很简单：大屏是&quot;锁定比例持续展示&quot;的东西，三块面板的构图是设计出来的，不是算出来的。真放开流式布局，小窗口下面板挤成一坨。 但 scale 有三坑，一个没躲过： 坑一：点击坐标。 只要用 getBoundingClientRect() 归一化，scale 天然兼容——rect 自带缩放信息。用 window.innerWidth 就等着点不准吧。 坑二：层叠上下文。 transform 创建新的层叠上下文， position: fixed 放进缩放树里会变成相对容器定位。弹层别塞进去。 坑三：它不是响应式。 不换行、不重排，窗口一小，字等比例缩没。所以排版必须留安全边距。 还有个跟适配无关但非常阴间的问题：我用无头浏览器截图验证时，场景一直黑屏。查了半天，是启动参数带了 --disable-gpu ——新版 headless Chrome 下 WebGL 上下文直接创建失败。 去掉这个参数就好了。 做自动化截图的朋友先检查这条，能少走半天弯路。 八、Codex 到底能干什么，不能干什么 这才是我想重点说的。 它干得非常好、而且明显快过我的部分： Blender 里的批量操作——给 112 个 mesh 统一改材质、整理命名、拆件导出、写 bpy 脚本批量处理； 样板代码——GLB 加载、OrbitControls 配置、Bloom 后处理、ResizeObserver，一次成型； 校验脚本和测试——数据模型的一致性测试、锚点缺失检查，它写得比我还全； 文档和注释—— manifest.json 的字段说明、构建命令、部署配置。 它明显不行、必须我来判断的部分： &quot;像不像&quot; 。弧线画反、楼群太散、入口和铺地裂开——它看不出这些，因为它眼里只有几何体，没有&quot;医院长什么样&quot;这个概念。这类判断需要参照物，而参考照片在我手上。 跨文件一致性 。改一个交互状态，它可能只改了 React 面板忘了 Three.js 那边，或者反过来。800 行分散在 8 个文件里，它每轮只看得见一部分。 验收标准本身 。&quot;用户点了楼栋，镜头飞过去，数据同步&quot;——这句话谁都能说，但&quot;镜头飞过去之后建筑不能被右侧面板压住&quot;这种要求，得先被我想出来，它才有机会实现。 取舍 。要不要上 R3F、要不要做巡游、楼层做几层——这些它会给建议，但从来不敢替你决定。 所以我现在派活的习惯是： 每一轮都给它一个能自己验证的目标 ——跑测试、截图、量包围盒数值、检查锚点是否齐全。给&quot;做得好看点&quot;这种指令，等于把方向盘交出去还指望它认路。 一个很具体的例子：分层显隐的逻辑我一开始描述得很模糊（&quot;进入近景时不要挡住&quot;），它实现得七零八落。后来我改成&quot;当 mode === &apos;floor&apos; &amp;&amp; zone 时，medtech / children / inpatient / parking / emergency 五个 objectId 的 visible = false &quot;——一句话，一次就对。 九、为什么没用 react-three-fiber 估计有人要问。R3F 很好，但我这次没用，原因是场景生命周期太重了：GLB 加载、材质替换、楼层展开、日夜切换、巡游、后处理、逐帧性能采样——几乎全是命令式、有状态的活。用 R3F，大部分逻辑最后还是得靠 ref + useFrame 绕回去，多背一层 reconciler，只为把 &lt;mesh&gt; 写成 JSX。 所以我选了原生 Three.js，全塞进一个 useEffect ，用 propsRef 同步最新 props 避免重渲染打断渲染循环，对外只暴露 home / focus / zoom 三个方法。React 那一侧完全不需要知道场景内部怎么转。 代价也清楚：这个文件 378 行，再加动线、粒子就得拆了。 声明式和命令式没有对错，只有你更愿意在哪一侧付账。 十、数字、边界，和还差什么 源码 800 行左右（8 个文件）， tsc --noEmit + 自定义数据测试 + GitHub Pages 部署 模型 18 万三角形 / 112 mesh，正交相机 + ACES 色调映射 + 轻 Bloom， dpr 上限锁 1.5 目标设备性能还没单独验收——所以我不报帧率，本机能开不等于现场大屏能稳 模型精度离效果图还有明显差距，README 里放的是实拍截图而不是效果图 下一步是把院区拆成的 12 个资产包逐件精修：先拿门诊外观和候诊椅做两个样件，AI 生成工具出候选、回 Blender 校正。但道路、楼层板、交互定位面会继续留在 Blender 手动控制——生成结果整块上来，空间关系和交互绑定全毁。 最后 这项目踩的坑，几乎没有一个是&quot;Three.js 不会用&quot;级别的，全是&quot;看起来合理&quot;和&quot;交互时真能用&quot;之间的缝隙：弧线要和铺地接上，镜头要给 UI 让位，假数据也得对得上账。而这些缝隙，恰恰是 AI 看不见、只能靠人盯的地方。 所以想问正在用 AI 写代码的朋友一个问题： 你把哪些判断留给了自己，又把哪些彻底交出去了？ 我的答案是——凡是能用数字、截图、测试验证的，全交出去；凡是需要&quot;对不对、像不像、值不值&quot;的，一条都不交。 评论区聊聊你的分界线。 技术栈：React 19 + TypeScript + Vite + Three.js 0.186，建模走 Blender（MCP 辅助），GLB + manifest 数据驱动，主力工具 Codex。 觉得有点用就点个赞，模型精修和 AI 生成管线那段的坑，踩完接着写。</description><pubDate>Tue, 27 Oct 2026 02:58:53 +0000</pubDate></item><item><title>出事了，AI 提效后，老板让我裁员20%</title><link>https://juejin.cn/post/7700887980139036682</link><guid isPermaLink="false">juejin-93c0913ea185ea7a</guid><description>上周五，跟朋友吃了个饭。朋友一见面就说：&quot;老板让我裁员 20%，下不去手啊&quot; 朋友是技术负责人。半年前，他找我帮忙从 0 到 1 构建他们公司的 AI Coding 工作流，期间他经常跟我吐槽，老板经常 push 他，让他去衡量 AI 到底提效了多少，然后他一直拿不出一个准确的数字，因为这个数字太多也不好，太少也不好。 我去他们公司讲过 2 次课，然后一直远程答疑，教他们从本地 AI 辅助编码，到云端自主运行、再到多 Agent Team 建立。虽然课大家都觉得不错，但我能感受到他们很排斥，就感觉，并不是他们不想用 AI，而是压根没啥业务了，没业务还提什么效？再提效就是给老板递刀子捅自己了。 而且听朋友诉苦，自从 AI 提效后，下面的人反馈更累了，该背的锅继续背，以前一个人跟 1-2 个项目就够了，现在普遍都要在原来的基础上，不断的去创新新项目，压力很大，AI 不停我也不能停，有一种被 AI 催赶的感觉，以前是牛马就算了，现在感觉是被 AI 骑。 虽然提效之后，老板更重视我朋友了，但朋友说每天上班都感觉得到下面的人的怨气，很不舒服 吃饭中间我们讨论了很多，最后得出的结论是，公司业务蛋糕做不大了，然而 AI 提效的结局就是岗位融合，就是裁员，除非业务多。 从老板角度，公司降本增效，就是要裁员，一个程序员月薪 1.5w，一年就接近 20W了。你要是不会用 AI 提效，那我就裁员杀鸡儆猴，让大家卷一卷，你要是会 AI 提效，那就证明公司不需要那么多人了。 聊完之后，我朋友心情好点了，但我沉默了，哎，历史的车轮滚滚向前啊！大家公司有没有遇到这种情况,评论区抱团取暖啊 欢迎大家关注我的公众号： 深入浅出AI</description><pubDate>Tue, 27 Oct 2026 00:53:09 +0000</pubDate></item><item><title>尤雨溪 : Vue 4 已经在规划中了！</title><link>https://juejin.cn/post/7700803425820049434</link><guid isPermaLink="false">juejin-67f8f7296ee4e02a</guid><description>大家好，我是程序员海军，。 说起来，我已经有两年多没有特别关注前端技术圈的动态了。 以前做前端开发的时候，Vue、React、Webpack、Vite 这些技术几乎天天接触。那时候隔三差五就有新框架、新工具冒出来，生怕自己跟不上技术更新。 这两年，随着工作重心逐渐转向 AI 应用开发，我关注更多的是大模型、Agent、AI Coding 这些方向，很少关注前端圈动态了，AI时代，对前端影响太大了，我的读者群或者其它前端圈现在大家谈论更多不是前端问题，而是转行以及其它问题了。 结果这两天刷到一条消息： Vue 4 已经在规划中了！ 尤雨溪在 Vue Fes Japan 2026 透露 Vue 4 动态 10 月 24 日，Vue Fes Japan 2026 在日本东京举行，尤雨溪 在大会上分享了 Vue 生态及 JavaScript 工具链的最新进展。 根据目前透露了几个方向： 不会有大规模破坏性更新。 尤雨溪甚至特意强调了这一点，希望尽可能保持现有生态的兼容性。 全面转向 ESM。 这是规划 Vue 4 的主要原因之一，将放弃 CommonJS（CJS）构建产物，进一步拥抱现代 JavaScript 模块标准。 考虑引入一些新功能。 目前还有部分功能 RFC 正在讨论，具体细节会在后续公布。 当然，目前只是规划阶段，还没有正式的发布时间表。 Vue 这些年其实一直在进化 去翻看了一下Vue圈的变化真大，我上次使用的还是Vue的3.5.0 版本， 除了 Vue 4，Vue 生态本身也发生了不少变化。 比如 Vue 3.6 正在推进的 Vapor Mode ，尝试通过新的编译方式减少对虚拟 DOM 的依赖，直接生成更高效的 DOM 更新逻辑，从而降低运行开销、改善性能。 另外，尤雨溪这些年的工作重心也不再局限于 Vue，从 Vite 到 Rolldown，再到 Vite+推动着整个 JavaScript 工具链的演进。 Vue 已经不是一个孤立的前端框架，而是一个不断完善的开发生态。 再看今天的前端，变化最大的可能不是框架 回头看这两年，前端技术其实越来越成熟了。 但我觉得，真正改变前端开发方式的，反而是 AI Coding。 以前开发一个页面，我们可能需要手动拆组件、写样式、调接口、处理交互。 现在借助 AI 编程工具，从需求描述、页面生成到功能实现，很多工作都可以让 AI 参与完成。 当然，AI 时代，它影响的不止前端，后端也是如此。不过AI 时代，我们手艺人的技术在飞快在贬值，失去了价值，但这个时代判断力 + AI 才是主要王牌，你的判断力就是议价的能力以及背书。 技术这条路，或许有的人已经下车了，转行了，再车上的兄弟们没必要焦虑，我也放平心态了，如果还是要提升自己的综合价值能力，即使你离了职场，你也可以生存，没必要一辈子Coding！ 写在最后 看到 Vue 4 的消息，还是挺有感触的。 从 Vue 2 到 Vue 3，再到如今规划中的 Vue 4，时间过得真快。 技术一直在迭代，我们关注的方向也在变化。 但不管是前端、后端，还是如今火热的 AI 应用开发，技术最终都是为了解决实际问题。 框架会更新，工具会改变，但理解需求、设计产品和解决问题的能力，始终值得长期积累。 也挺期待 Vue 4 正式到来的那一天。</description><pubDate>Mon, 26 Oct 2026 13:17:09 +0000</pubDate></item><item><title>AI 越强，失业越严重</title><link>https://juejin.cn/post/7700627964494168106</link><guid isPermaLink="false">juejin-65f19d24c6a40cb9</guid><description>最近不少人给我反馈失业的问题。今天简单聊聊。 我说句残酷的话，随着 AI 能力的越强，短期内对岗位失业的冲击越严重。 其实这个观点，我在几个月前的直播就提到过，只是当时很多人不在意、不重视而已，而事实上，现在已经发生了。 原因很简单，本来全球经济整体环境就不好，而 AI 越强，越来越多的企业会逐步意识到，同样的活，根本不需要那么多人，大部分企业，岗位缩减一半，可能对公司的运营和商业没任何影响。 话虽然难听，但这就是残酷的事实。 本来按照经济周期发展规律，会逐步的，但是 AI 发展与进化速度，加快了这个过程变化的节奏。 所以，短期内，对岗位需求的冲击会非常大。别问我短期是多久，是半年，还是一年，还是两年，没人知道，没人可以预测。 AI 未来一定会创造新的生产关系和新的工作岗位和产出，只是现在还没有。 起码目前阶段，AI 主要就是在生产力的提效层面，而且也是在发生的事，而提效这件事，也是伴随着 AI 能力提升的。 现如今 AI 其实已经很强了，那么自然有越来越多的企业意识到，它的那点业务根本不需要那么多人。 怎么应对？ 不知道，短期内我也不能帮大家搞定工作问题。我只能说，很快会有越来越多的企业意识到这件事，意识觉醒也是逐步的。 如果说给点建议的话，我的建议就以下几点： 对于个人，好好沉淀自己 AI 的底层能力，面向未来。 做好风险应对措施，万一真失业了，起码基本的生活有保障，减少高消费。 抛弃你过往依赖的引以为傲的行业经验和能力，完全拥抱 AI 时代新的工作方式，尽量让自己成为多维度全能人才，这是未来 AI 人才的基本要求。 对于企业来说，降本增效无论如何是你当下要面对的最大问题，尽快完成 AI 重塑工作流和业务。 如果很多人感知不到，没有风险意识不重要，这是面向未来的。 行业意识觉醒，企业重塑工作流都需要时间，无论任何时代，不可能所有人，所有行业都具备相关的能力的，这可能对我们来说是最好的消息，在这个时间差内，你决定做什么，能做到什么程度，就看每个人的具体行动了。 以上，个人观点，与诸位分享。</description><pubDate>Mon, 26 Oct 2026 11:19:04 +0000</pubDate></item><item><title>掀桌子了！Kimi K3.1 Pro 这效果也太离谱了。</title><link>https://juejin.cn/post/7700627964493365290</link><guid isPermaLink="false">juejin-26aa09685969be33</guid><description>过去两个月，OpenAI 、A 社相继发布 sota 模型，一直霸榜各种测评榜单将近两个月。 而这两个月，却是国模的至暗时刻，被直指蒸馏、被质疑，然后我们也没有拿出能力足以抗衡的 Pro 模型。 然而刚刚 Kimi K3.1 Pro 发布了，我觉得，是时候回击了（It’s time to strike back）。 我说怎么早上的时候突然无缘无故的发了个视频，视频里面模模糊糊的显示 K3.1 ，原来是在做预告啊，好家伙太会玩了。 Kimi K3.1 Pro 是 K3.1 系列的第一个模型，我依稀记得 Kimi K3 发布时候的盛况，放眼望去全是 K3 的实测，前端效果炸裂，我当时没有第一时间实测非常遗憾，所以这次我第一时间上手体验了一下 Kimi K3.1 Pro 。 先说一下，Kimi K3.1 Pro 继续引领了 Kimi 家族的 sota 模型，支持 100M 上下文。在代码、3D 创作、金融研究与建模等方面都有显著提升。 测评 讲真，Kimi 是真的喜欢秀审美，博客首页也得拔高一下审美，但我觉得挺上道的。 下面来看下详细测评 长任务编程、办公和金融，都拿出了能跟海外旗舰掰手腕的成绩。 先看代码，已经追到第一梯队了。 Terminal-Bench 4.0 通过真实环境中资源受限的终端任务考察编程智能体的能力，每项任务均设有严格的专项测试。K3.1 Pro 在此项评估中得分 65.7% ，超过 GPT-6 Astra（58.2%），与 Claude Opus 5.5（66.4%）仅有微弱差距。 办公和金融这块，我觉得挺有看头。 OfficeQA Pro v2 要从真实财务文档里检索信息、交叉核对，再做多步计算。Kimi 拿到 64.4 分 ，高于 Opus 5.5 的 62.2 分和 Astra 的 54.4 分。 Frontier Finance 上，Kimi 是 58.6 分 ，仅次于 Fable 5.1 的 60.8 分，也超过了 Opus 5.5 的 56.7 分和 Astra 的 45.0 分。 3D 建模接近头部。 这是这次 Kimi K3.1 Pro 最亮眼的部分了，简直了，这也是我们接下来实测的重点环节。 BenchCAD with tools 这一项，Kimi 是 92.7 ，Opus 5.5 是 96.2，Astra 是 95.9，非常接近。这里测的是根据多视角图片生成 CAD 代码、还原三维形状，图里的数值是模型与目标形状的体素重合指标。 在 Kimi 的官方声明中说到：K3.1 Pro 能熟练使用 Unity 、 Unreal Engine 、 Godot 等游戏引擎，并结合网络搜索与 3D 生成，做出完整的体验。它产出的前端页面、Three.js 交互场景和 3D 资产，在构图、排版、动效和细节打磨上都有明显提升。 然后它说下面这些都是 Kimi K3.1 Pro 端到端做的。 我此时不得不来一句黑人问号脸？？ 不是，就随随便便一个 case 都能做出来这效果了？离谱。 我本来看完榜单已经觉得 Kimi K3.1 Pro 跟 Opus 5.5 坐一桌了，但是我一看这官方发布的效果。。。我觉得能直接干掉 GPT-6 Astra 了。 Kimi K3.1 Pro 实测 emmm 。。。我连夜通宵测试了下，我发现 Kimi K3.1 Pro 在 3D 这块的能力，你就去试吧，一试一个不吱声。 本来 K3 在视觉和审美这块已经很强了，而 K3.1 Pro 又拔高了一个等级。 这次我们的实测分为三块：Coding 能力、3D 创作能力以及金融分析能力。 我先测试了一下 Kimi K3.1 Pro 的 3D 建模能力。 由于我是权游的死忠粉，之前还买过马丁老爷子写的全套《权利的游戏》，于是趁着新模型发布，我复刻了一下《权利的游戏中》的绝境长城以及临冬城。 由于这个作品几乎是我耗尽了很大的心血，所以大家也别嫌我唠叨，我好好介绍下。 进入主页是这个样子的。 对了，这就是守夜人的营地了（虽然没有 Snow 和他真实的守夜人兄弟们） 看到背后的万里长城了么，是不是梦回到权利的游戏中了。 我这里做了几种视角，一种是攀升视角，这个视角可以近距离看到长城和临冬城，还有自由视角，这个是随意发挥视角。 最重要的是守夜人视角，这个角度是让你扮演一个守夜人，进入权游中感受一下。 那我就以守夜人的视角开始了。 另一个角度的守夜人营地。 从视角拉远，感受一下长城的壮阔。 近距离感受一下长城的压迫感。 登上长城感受一下。 一篇白雪皑皑的景色，能让人感受到守夜人汉子的不容易。 看向下面，异鬼在哪里？异鬼呢？？？ 介绍完长城，我们回到临冬城， Winter is Coming 。 看一下临冬城的夜晚。 进入临冬城，守夜人的汉子们在努力训练。 长夜将至，我从今开始守望，至死方休，我将不娶妻，不封地，不生子。。。。。。 大厅的视角。 图书馆中。 临冬城残塔，当时的 布兰 就是从这里被推下去的，最后布兰成为了三眼乌鸦。 我这里还做了不同场景的快速导览，同时也加了参考边界和解读。 我觉得这个 神木林 真的出神入化了，给大家看下，白天的效果是这样的，真的很美。 把视角拉远，可以看到整个临冬城的建模。 与 GLM-5.3-Flash 、DeepSeek-V4.1-Flash-1107、GPT-6 Astra 的对比 下面这个 case ，我做了一个火山模型，与 GLM-5.3-Flash 、DeepSeek-V4.1-Flash-1107、GPT-6 Astra 做了一下对比。 所有的模型都是一句话直出。 绘制一座照片级写实的层状火山 3 D 模型：锥形山体带放射状冲沟与碎裂火山渣锥，火山口内翻涌高亮熔岩湖，主喷发柱呈橙黄到暗红渐变并夹杂抛射火山弹，黑色火山灰云层层堆叠，山体下三分之一有分叉熔岩流沿沟谷流淌、远端冷却成深灰结壳，山脚散落多孔火山岩与浮石；PBR 真实材质（熔岩自发光、火山岩粗糙度 0.9 、灰烬半透明），清晨侧逆光，主体居中占画面 80 %，封闭水密网格、无悬浮碎片与破面，高精度 8K 贴图，360° 环绕观看无死角。 先来看一下整体效果。 Kimi K3.1 Pro 这个做的太惊艳了，我觉得都可以直接作为 3A 大作的游戏素材，给大家看一下大图。 近景是这样的，可以看到这个细节和纹理，我觉得真可以抗一下 Opus 5.5 。 俯视的火山近景。 这个火山的效果确实把 GPT-6 Astra Max 给干掉了。 值得一提的是，DeepSeek Harness + DeepSeek-V4.1-Flash-1017 的效果也非常不错，更接近真实的火山场景。 从细节上来说，还是没有 Kimi K3.1 Pro 做的细节。 智谱的不发表评价了。。。虽然做的不太行，但奈何人便宜啊，只要 1 块钱。 金融分析工作： 在金融分析这块，我让它分析了一下保时捷的财务状态，是否真如大家所说的，在中国利润暴跌之类的。 给大家截两张图感受一下，Kimi K3.1 Pro 是这么多模型里面，我觉得布局和优雅是做的最好的，让用户看起来很舒服。 给大家看一下它的完整录屏。 价格 我之前一直都认为 Kimi 价格贵，但是这次 Kimi 的价格，直接比 Astra 便宜 80%。 价格为每百万 Token 输入：**1.3 元（命中缓存）和 13 元（未命中缓存），输出 68 元。**高速版模型 kimi-k3.1-pro-highspeed，输出速度约 2~3 倍，价格 2 倍。 我把 K3.1 Pro、Highspeed、Opus 5.5、GPT-6 Astra 和 Fable 5.1 放到一张图里，输入、缓存命中、输出分开算。 按这个口径计算，K3.1 Pro 标准版的未缓存输入价比 Astra 低 81.4% ，输出价低 80.6% ，跟 Opus 5.5 比，未缓存输入和输出的单价也大约便宜一半。 实测完之后，我觉得以后 3D 和建模，以后直接放心交给 Kimi K3.1 Pro 了。 由于我是庄里人，我突然想说一句。 这么近，那么美，周末到河北。 这么大，那么美，干活用 Kimi。</description><pubDate>Mon, 26 Oct 2026 08:42:06 +0000</pubDate></item><item><title>天涯神贴《2027年以后，你要活成这个样子》有感，希望 2027 年可以活成自己想要的样子</title><link>https://juejin.cn/post/7700522130220023862</link><guid isPermaLink="false">juejin-fae3d9d8be86b654</guid><description>今天，2026 年 10 月 26 日，星期一。 在这个深秋的时刻，我无意间刷到了天涯 A11 大佬的一篇神贴——《2027年以后，你要活成这个样子》。 看完之后，我整个人就像是被雷劈了一样，呆坐在屏幕前许久。 那种感觉怎么形容呢？就像是武侠小说里卡在瓶颈期多年的无名小卒，突然被高人一指，瞬间打通了任督二脉。 真牛逼。 这篇文章里的每一个字，都像是一个个响亮的巴掌，把这个时代最残酷的真相和最清晰的出路，明明白白地拍在了我的脸上。 回首过去的这大半年，从 2026 年 3 月中旬拿着一笔算是北京互联网圈子上限级别的赔偿金离开上家公司，到跟小伙伴们疯狂折腾各种自媒体；从摸爬滚打把小红书干到接近 10000 粉、开店接单，到手握 4 个收益破五位数的公众号；再到 6 月中旬入职现在的头部安全公司做 FDE（AI 架构师），以及 7 月份开始转战海外 X 平台，并在 10 月份奇迹般地把粉丝翻倍干到 14000+。 我这大半年的经历，冥冥之中竟然与天涯大佬的这四条铁律严丝合缝地呼应上了。 距离我写那篇《年终总结 - 一个前端仔有趣的 2025》已经过去快一年了。 那时候的我，还在为 35 岁焦虑，还在感慨大环境的疲态，还在为集齐了公司的第二套盲盒而窃喜，开着刚提的乐道 L90 穿梭在北京的街道上，感叹着“没有任何一种回报是轻而易举的”。 如今再回头看，那仿佛已经是另一个世界。 我想结合我这跌宕起伏的 2026 年，以及我对 2027 年的规划，好好拆解一下大佬的这篇神贴。 这既是一篇读后感，也是我，给自己写下的一份总结。 英语是连接世界的光缆 天涯大佬在第一条里一针见血地指出，2027 年以后，英语必须练到能直接用的地步。 这里的“直接用”，不是为了去考英语四六级，而是为了能够毫无障碍地看懂英文信息，跟外国人交流，并且自由地使用英文互联网。 看到这里，我真的是疯狂点头。 大佬说， 信息的自由度，决定了认知的高度。 如果一个人长期只能看中文互联网，那么他所看到的世界，必然是一个经过筛选、别人过滤，甚至是别人加工过的世界。在这个信息茧房里，人会轻易地被算法困住，被各种极端的情绪牵引，被不同的立场推着走。 这不就是我今年做国内自媒体最深切的痛点吗？ 今年 3 月离职后，我把国内能叫得上名字的平台几乎尝试了个遍。公众号申请了 4 个，一度成为我的主力，每个收益都过了五位数。 但是，国内平台的规则实在太多变了，今天你写个热门话题，明天账号可能就被限制了。 你每天看到的那些所谓爆款，很多都是二手信息、翻译过来的，甚至是带着转述者强烈立场和偏见的删改版。 你永远看不到事情的全貌，你看到的，永远只是别人想让你看到的那个版本。 英语作为这个世界的通用语言，也是互联网的绝对通用语言。掌握了它，就意味着你能直接获取一手信息，直接跟全世界最聪明的人交流，直接读到第一手的资料和研究报告，真正了解事情的全貌。 有人可能会杠，说我不出国，学英语有什么用？ 大佬反驳得极其精彩：不出国一样可以用英语上网看新闻、读前沿论文、学新技术，去了解另一个广阔的世界正在发生什么。 这也是我今年 7 月份痛下决心，开始深度接触海外并运营 X（推特）账号（@leo_xiaolei）的核心原因。 我在 X 上专注发布关于 AI、Grok Bot 的硬核技术内容和行业思考。没有了那些乌烟瘴气的流量裹挟，我直接去啃最新的 AI 论文，直接去跟海外的开发者交流。 这个时代，如果不会英语，你接收信息的通道会跟别人完全不一样，你直接少了一个看世界的窗口。 学习英语不需要找借口，也不用觉得难或者学不会，只要坚持个三五年，总能把它用起来。等你能熟练运用了，你眼里的世界、你的认知水平以及你面临的选择，都会截然不同。 我现在的状态就是， All In 海外。 海外方向的出海网站和海外自媒体平台，绝对是我 2027 年个人的主力方向。 我不想再在国内媒体上浪费我有限的精力了，没有正反馈，且八字不符。 我要去更广阔的太平洋里扑腾。 超级个体的觉醒 神贴的第二条，彻底说透了我在 2026 年下半年的转型核心： 把 AI 用起来，并且一定要用前沿的付费模型。 大佬强调，这绝对不是偶尔无聊问机器人几个问题那么简单，而是要真正把 AI 变成你的外接大脑、你的专属工作台、你的研究助手，甚至是你的数字员工。 那些不会用 AI 的人，在未来的效率上，一定会被会用 AI 的人远远甩在身后。 因为 AI 根本不是什么死板的工具、聊天机器人，更不是转瞬即逝的新鲜噱头；它是真正的生产力，是个人能力的超级放大器。 这一点，我简直可以说是亲身实践的狂热信徒。 在我多年的爱好——水族圈子里，我就深刻体会到了这种“降维打击”。 我以前玩水草缸、繁殖孔雀鱼，做着“水族小导游”的图文，虽然因为懂遗传学、鱼鳍分类学和水草布景技术，能做到月入温饱，但这玩意儿太耗时间了。 尤其是在后疫情时代，大家的消费观念全变了，娱乐休闲产品一落千丈。我意识到，再靠传统的繁殖模式，死路一条。 于是，我怎么做的？ 我直接结合 AI，专门打造了一个自动周转系统。 我会配置 Claude Code 结合 OpenAI 的 API，通过 AI 每天高频监控闲鱼和各大水族论坛。 只要发现我选定品种的低价产品，AI 会第一时间自动发消息联系卖家。只要能走到付款这一步，系统直接通知我。 我买下后（要求卖家玩两天再发货），AI 瞬间以新的价格将这批货上架到我的平台。 如果迅速卖出，全自动把新买家的地址发给原卖家，实现一件代发；如果卖得慢，才发回我自己的场地暂养。 从过去的“苦哈哈繁殖”转向利用 AI 搞“高频流通率”，这就是大佬所说的： 一个会用 AI 的人，自己就是一支队伍，就是一家公司。一个人完全能干以前十个人甚至一百个人的活。 今年 6 月中旬，我再次出来工作，入职了国内最头部的安全公司，担任 AI 架构师（FDE）。 我深知在 AI 时代，掌握核心工具链有多重要。 我日常重度评测 Claude Opus 5.5、Gemini、Grok、VoxCPM 2 等模型；我用 Rust 搞开发，配置 Agy CLI 插件，管理 Claude Code 的空闲压实配置；我还自己维护了开源的“手绘口播视频.skill”自动化工作流。 我甚至在 10 月份写了一篇长文《FDE 已死！从 FDE 到 FDU：重新定义 AI 交付》，深度剖析了企业 AI 的未来。 大佬在帖子里毫不客气地指出，你不用 AI，你的对手、同行、比你聪明甚至比你努力的人都在用。 他们的效率比你高，产出比你多，成长速度比你快，这种差距拉开是必然的，跟不上时代的人自然会被淘汰。 很多人总是在抱怨 AI 这不行那不行，其实多半不是 AI 不行，而是自己还没研究明白，没找到对的用法。 任何新工具问世，总有人拒绝接受，最后这些人都会被时代抛弃。 这周末，我和朋友在线下就要开启第一场关于 X 和 Grok Bot 的培训了。 我这人最喜欢的就是“吹牛皮”，哦不，是输出我的认知，而且还有人愿意听。 2027 年，我要继续主动学、主动用、主动把 AI 深度融入我的工作和生活，让 AI 帮我干活、思考、学习和赚钱。 因为用得越熟练，效率就越高，产出就越多，成长也就越快。 构建个人 IP 与不可替代的网络节点 看到第三条的时候，我想起了 2026 年 3 月我拿着那笔丰厚赔偿金走出上家公司大门的那个下午。 天涯大佬说，一定要做自己的个人 IP，拥有持续的输出能力，搭建自己的网络节点和自动化工作流，让系统替自己去工作。 他提到，在当今这个时代，只靠一份工作吃饭，抗风险能力实在是太弱了。 根本没有绝对稳定的工作，没有铁饭碗，更没有一辈子的保障。 今天你还在安稳上班，明天可能就被组织架构调整了；今天公司看着还烈火烹油，明天可能就遇到天花板了；今天整个行业还红红火火，明天可能就遭遇整体退潮。 我这几年的经历就是一本活生生的教科书。 从 2016 年毕业吃到了在线教育的尾声红利，到 2021 年“双减”离职；从转型纯前端拿到 70 万年薪，到辛辛苦苦干满一年却被裁员且一分钱绩效没拿到。 互联网的红利褪去得比翻书还快。 大佬说得对，只靠一份工作、一份收入，太脆弱，太危险，随时可能出问题。 一旦出了问题，因为没有别的收入来源、没有退路、没有选择，人就极容易陷入恐慌和混乱。 所以， 要有自己的 IP，要有副业，要有第二收入曲线。 这件事不一定非要立刻赚多少钱，但这个能力、这条退路你必须得留着。 有了个人 IP，你就有了自己的网络节点、流量、受众和影响力。 我在 X 上用 @leo_xiaolei 这个身份疯狂输出，仅仅在 2026 年 10 月这一个月内，粉丝就翻了一倍，突破了 13000 大关。 我还在筹划写一篇《我为什么要做一个叫「AI醒觉社（AI禅院）」的东西》，就是为了把这些节点更加具象化。 就算未来我的工作没了，公司不行了，或者行业再次大洗牌，我也完全不用慌。因为我靠着自己的 IP 一样能吃饭，能赚钱，甚至能活得很滋润。 从现在开始选定一个方向，持续输出，积累粉丝，打造个人品牌。 不要着急，不要焦虑，坚持个三五年，总能看到成效。 等你的 IP 真正做起来了，你就会深刻体会到，那种拥有自己的流量和影响力所带来的安全感，比任何一份所谓“稳定”的工作都要扎实得多。 培训和线下分享，就是我接下来团队运作的主要方向。 我要把我的“水牛皮”吹向全世界。 轻装上阵，重塑全球视野与迁徙能力 第四条是关于资产和生存状态的，也是对我目前水族业务痛点最精准的手术刀。 大佬提出了一个极其宏大的生存策略： 保持轻资产、无负债，拥有稳定的现金流，并且持续定投全球资产。 他说，随时能走，随时能换个地方重新开始，这才是一个人在这动荡世界里最后的底气。 现在的世界变化实在太快，不确定性太多了。 今天看起来好好的事情，明天可能就出了岔子；今天觉得还算安稳的地方，明天可能就动荡不安；今天手里的钱看着还值钱，明天可能就大幅贬值。 如果把所有的钱都砸在房子里，或者押注在同一个地方、同一种资产上，风险简直高得离谱。 真要是碰上点什么事，你没有应对的底气，所有的资产都被死死困在那里，只能眼睁睁看着钱变废纸，看着生活质量直线下滑。 我太认同这种“轻资产”的理念了。 回想我的水族生意，目前最让我头疼、最想在 2027 年干掉的环节是什么？ 就是“暂养”环节。 一旦 AI 没能在 2 天内把闲鱼上的低价鱼倒手卖出去，这批鱼就会邮寄到我自己的场地。 这瞬间就从轻资产变成了“重资产”——我得花极大的精力去照顾它们，维持水质，控制植物灯和除油膜器，还要自己打包发货，并且时刻盯盘管理售卖平台的价格。 这不仅耗时，还极其牵扯精力，这与我追求的高效、自动化、轻量级理念背道而驰。 朋友建议我跟一手卖家玩“两天卖不出去就退款”的套路，这等于是砸自己招牌，我绝对不干。 但我 2027 年必须要想办法把这个“暂养”环节彻底优化或者抹掉。 我要让这套系统变成纯粹的信息流与资金流的流转，绝不能落入物理资产的泥沼。 大佬教导我们，千万不要把所有的鸡蛋放在一个篮子里，不要把所有希望寄托在同一个地方，更不要把自己的命运死死绑在一座城市、一套房子上。 真正的自由、底气和安全感，从来不是一套钢筋水泥的房子、一辆四个轮子的车或者一份看着稳定的工作能给你的；那些东西太虚了，都是暂时的，随时都可能消散。 只有装在脑子里的能力、你对这个世界的认知、你的全球视野，才是真正属于你自己的财富，是别人永远拿不走的，更是你这辈子最大、最可靠的保障。 2026 年，我完成了从大厂开发到底层商业逻辑重构的转身，找回了写代码、做产品的纯粹快乐（哪怕是带着 FDE 的帽子）。 2027 年，我的主题极其明确：All In AI，All In 海外。 我不知道下一个阶段的世界会变成什么鬼样子，就像我 2025 年底写总结时，绝对想不到 2026 年我会经历离职、做流量商、转型 AI 倒爷，以及成为 X 上的一名博主。 但只要我手握这四条铁律，保持轻盈，拥抱 AI，放眼全球，我就没什么好怕的。 希望 2027 年，我可以，也一定能，活成自己想要的样子。 让我们一起向前，一起成长吧！</description><pubDate>Mon, 26 Oct 2026 02:55:37 +0000</pubDate></item><item><title>介绍一个开源项目 —— 烂笔头</title><link>https://juejin.cn/post/7700157486074478627</link><guid isPermaLink="false">juejin-c7b06cb94242a265</guid><description>纸上得来终觉浅，绝知此事要躬行 今天给大家介绍一个我自己的开源项目，在整理github仓库的时候发现的，叫做烂笔头，一款笔记软件，目前自己已经用了好久，今天分享给大家： 开源地址： github.com/Hxd123459/l… 如果对你有帮助，烦请点个Star。 一、功能介绍 这个名字取自俗语：“好记性不如烂笔头”，我最开始给它的定位是 碎片化记录 + 长文整理 ，我先讲一下它的功能； 1.1 碎片记录 输入框在上方，进入后光标自动在输入框聚焦，主打一个快速记录。 不需要想标题，不要排版，想到就记！ 1.2 标签化 &amp; AI自动打标签 碎片化的笔记都在标签下面，且不支持二级标签，因为我认为二级以上的文件夹嵌套是笔记乱套的开始！过于细化的标签、文件夹嵌套，只有前两天感觉整齐有序，后面就会忘记这个文件夹是干啥的，都应该放一些什么东西。而且还会在记录笔记的时候造成【决策压力】这个笔记应该放到哪个文件夹下面？这个文文件夹是干啥的？最后进入【决策疲劳】放弃记录。 当你不想写标签 或者是 不知道该如何归类是，AI会给你决策，根据你的内容进行自动打标签。 AI识别的过程： graph TD 输入内容 --&gt; 大模型 已有标签 --&gt; 大模型 大模型 --&gt; 提示词 提示词 --&gt; 检测返回格式 检测返回格式 --&gt; 通过 检测返回格式 --&gt; 不通过 不通过 --&gt; 大模型 通过 --&gt; 结束 提示词:请你将【输入内容】归类到现有标签中，现有标签如下【标签列表】。如果没有找到相近的就创建标签。然后返回下面的json格式：{...} 1.3 沉淀、长文整理 选中碎片即可整理长文，在你编写长文时也可以随时调出来碎片列表，通过AI找到和当前语义相近的文案，推送给你。 可以选择让AI根据哪个模版进行整理： AI能力支持使用自己的key的，我deekseek充了10块钱，用了快半年了，还没用完呢。不要使用 uTools 提供的AI能力，我对于这种不让看使用的模型，直接消耗积分的形式很抵触！！！ 1.4 待办整理 有待办功能，不过这个待办并不是孤立的！可以关联标签，AI会自动提取这个标签中的待办信息到列表中。 待办一键从碎片中提取。 完成任务一键写周报日报。理论上你可以根据完成未完成的任务生成任何的内容，因为模板是可以自定义的。 1.5 数据在本地 你记录的所有笔记都在本地，并且随时导出带走！也支持一键导入，我就是通过这种原始的方式，在我公司电脑和家里电脑之间进行笔记数据同步的，虽然费劲一些，但是有了这个仪式感的过程，突然对自己的笔记内容资产重视了起来，别管好赖这是从我脑子出来的东西！ 1.6 其他零零碎碎的功能 还有其他乱七八糟的功能，比如什么将笔记饮用后问AI、统计功能、批量打标签，感兴趣可以下载自己探索。 胎死腹中的功能，我想做一个工作流，我只需要把日常的会议纪要记录下来，AI自动帮我完成任务，我只需要验收。流程如下： graph TD 碎片记录 --&gt; 待办提取 待办提取 --&gt; 定时扫描待办 定时扫描待办 --&gt; 委派任务 通过飞书 --&gt; 委派任务 委派任务 --&gt; Cursor 委派任务 --&gt; Codex Cursor --&gt; 验收 Codex --&gt; 验收 验收 --&gt; token统计监控 上面的任务执行功能，cursor已经通了，使用的是cursor SDK。但是我发现要想把这个东西做好，太难了，需要付出的精力太大！遂弃坑！这个功能在另外一个分支。 二、为什么要选择使用插件的形式？ 主要是使用uTools可以快速上线到win和mac，并且可以快速记录和快速搜索。 三、感悟 第一个感悟：有了AI，更要去实践，去亲手做 这个烂笔头有AI加持，做了得有几个月吧。整体看感觉就是一个小插件，如果没有实践，我可能会说：有了AI一天搞定，这就犯了 主观主义错误 ，正如开头讲的： 纸上得来终觉浅，绝知此事要躬行 你要是不自己做，你心里是没个底，那么你对一件事情的成本、风险、体量评估就没有依据，没有标尺，就会失去 判断力，最后人云亦云，任人宰割 。 第二个感悟：做一个产品的过程中，很容易迸发另外的灵感 做的过程中会迸发出来很多很多想法，也会派生新的点子，比如上面提到的那个结合AI的工作流，这完全可以单拎出来做个系统了。 第三个感悟：做自己需要的软件 ≠ 做产品 做自己想要的，那你怎么开心，怎么来，假设有100个用户，你只需要讨好你一个人就行了，你开心了，这就是成功的！ 但是做产品不行，一个成功的产品意味着，你一定要找到这100人中，UI设计、需求偏好的最大公约数！不然这就是一个失败的产品！并且还要有一个健康的商业模式，叫好不叫座也是不行滴。</description><pubDate>Mon, 26 Oct 2026 01:06:56 +0000</pubDate></item><item><title>Flutter 紧急发布 3.47.8 安全升级版本，建议都升级</title><link>https://juejin.cn/post/7700380674944204810</link><guid isPermaLink="false">juejin-58598372c031be1b</guid><description>Flutter 周末紧急发布了 Flutter 3.47.8 和 Dart 3.13.7 版本，因为最近印度老哥的骚操作被人扫出来， eMudhra 的 CA 证书密钥泄露，而泄漏的原因是因为它的证书密钥是个位数，比如一个 4， 是的，没看错，它的证书密钥就是一个 4 。 正常的 P-384 椭圆曲线私钥，是一个接近 384 位的随机整数，理论上有应该有 2^{384} 种可能，但是 eMudhra 的 8 个私钥居然是下面这些数字： 一家做数字信任生意的公司，把中间 CA 的私钥做成了单字节整数，然后挂在全球信任链上跑了一年半，你觉得这是什么刺激 Play ？？而原因是发布的时候他们员工看错了···· 也就是现在这家公司的“合法” CA 谁都可以伪造，攻击者可以利用网络路径伪造任意域名的有效 TLS/HTTPS 证书，而为了阻止被入侵的 eMudhra 中间证书颁发机构的 CA) 证书，现在你需要升级 Flutter 来保证这个安全问题。 虽然浏览器和操作系统都 发布了紧急中间证书黑名单 ，但是 Android、Windows 和 Linux 上的 dart:io 用的是内置的 BoringSSL 引擎验证 TLS 证书链，它不会读操作系统的中间证书黑名单，所以这次主要就是更新 BoringSSL ，用来在 TLS 验证期间直接阻止被入侵的 eMudhra 中间证书。 另外，在 Android 14+ 上， dart:io 现在会从可更新的 Conscrypt APEX 模块加载系统根证书，确保通过 Google Play 系统更新提供的未来的无线根存储撤销能够自动生效。 当然，你要是说，你发布的 App 里直接都忽略了证书验证的，那当我没说话。</description><pubDate>Mon, 26 Oct 2026 00:57:04 +0000</pubDate></item><item><title>继PHP之后，Java也爆出了重大安全漏洞！</title><link>https://juejin.cn/post/7699886793819504640</link><guid isPermaLink="false">juejin-66d6006d45b68812</guid><description>前言 大家好，我是苏三。 这几天PHP那个漏洞闹得沸沸扬扬，很多Java开发还在群里看热闹，说“还好我们用Java”。 结果话还没说完，Java这边也炸了。 2026年10月21日，Oracle Java SE被曝出一个 CVSS 9.1分 的高危漏洞，编号 CVE-2026-91403 ，漏洞组件是 JSSE （Java Secure Socket Extension）。 9.1分什么概念？ 满分10分，7分以上就算高危，9分以上意味着 远程、无需认证、低复杂度 就能打。 而且JDK 8、11、17、21、25、27全线受影响。** 今天这篇文章，我就把这个漏洞的来龙去脉、底层原理、以及你该怎么处理，从头到尾给你拆解一遍。 希望对你会有所帮助。 更多项目实战在Java突击队网：susan.net.cn/project 一、这个漏洞到底有多严重？ 我先把官方数据摆出来，你自己感受一下。 指标 数据 CVE编号 CVE-2026-91403 受影响组件 JSSE（Java Secure Socket Extension） CVSS 3.1评分 9.1（CRITICAL） 攻击向量 网络（AV:N） 攻击复杂度 低（AC:L） 所需权限 无（PR:N） 用户交互 无（UI:N） 影响 机密性完全丧失、完整性完全丧失 数据来源： www.cve.org/CVERecord?i… 翻译成人话就是：攻击者不需要任何账号，不需要受害者点任何链接，只要你的Java应用对外提供网络服务，他就能远程打进来，读取你的数据、篡改你的数据。 JSSE是什么？ 它是Java里负责TLS/SSL加密通信的核心组件。 你用的HTTPS、你连数据库的SSL连接、你调外部API的加密通道——底层全是JSSE在干活。 换句话说，这个漏洞影响的不是某个业务功能，是Java应用和外界通信的“安全通道”本身。 二、JSSE里到底出了什么事？ 有些小伙伴可能会说：“说了半天，到底是怎么被攻击的？” 官方披露的信息比较克制，只说“JSSE组件存在漏洞，可通过向API提供数据的Web服务利用”。 但结合漏洞的CVSS向量（AC:L、PR:N、UI:N），我们能推断出几个关键点。 第一，问题出在输入验证或状态管理上。 JSSE在处理TLS握手、证书解析、或者加密参数协商时，对某些输入的处理存在缺陷。攻击者可以构造特殊的网络数据包，触发这个缺陷。 第二，不需要证书或密钥。 攻击者不需要拥有合法的TLS证书，也不需要知道任何密钥，就能触发漏洞。这意味着攻击门槛极低。 第三，影响范围是整个通信链路。 JSSE一旦被攻破，加密通道就不再安全。攻击者可能读取明文数据，或者注入篡改后的数据。 一个类比 ：你家的防盗门（JSSE）本来应该挡住外人，结果门锁的某个零件设计有缺陷，小偷不需要钥匙，用一根铁丝就能捅开。而且这个门锁装在几乎所有Java应用上。 三、哪些版本受影响？你的项目中招了吗？ 这是大家最关心的问题。 我把官方公布的受影响版本列出来： 产品线 受影响版本 Oracle Java SE 8u511, 8u511-perf, 11.0.33, 17.0.21, 21.0.13, 25.0.5, 27.0.1 Oracle GraalVM for JDK 17.0.21, 21.0.13 Oracle GraalVM Enterprise 21.3.20 数据来源 关键点：JDK 8、11、17、21、25、27全部在列。 也就是说，不管你用的是老项目还是新项目，只要在这几个版本上，都受影响。 怎么确认自己的版本？ java -version 如果输出里包含上面列出的版本号，比如 java version &quot;1.8.0_511&quot; 或者 openjdk version &quot;21.0.13&quot; ，那就是中招了。 注意 ：这里列的是Oracle官方版本号。 如果你用的是OpenJDK的其他发行版（比如Temurin、Corretto、Zulu），需要去对应厂商的安全公告页确认。 通常厂商会在Oracle发布补丁后几天内跟进。 四、修复方案：现在该做什么？ 4.1 立即升级JDK Oracle已经在 2026年10月20日 发布了修复版本。你需要升级到以下版本之一： 产品线 修复版本 Oracle Java SE 8u512, 11.0.34, 17.0.22, 21.0.14, 25.0.6, 27.0.2（或更高） 如果你用的是OpenJDK发行版 ，去对应厂商的下载页找最新补丁版本。 通常Oracle发布后1-3天内，Temurin、Corretto等会跟进。 升级后验证 ： java -version 确认版本号已经变成修复版本。 4.2 如果暂时无法升级 有些团队因为兼容性问题，短期内升不了JDK。 这种情况下，可以做临时缓解： 限制JSSE暴露面 ：如果你的应用不对外提供TLS服务（比如只在内网跑，前面有Nginx做TLS终止），漏洞的暴露面会小很多。 但 这不等于安全 ，内网横向移动的风险依然存在。 加强网络层监控 ：在WAF或IDS上，监控异常的TLS握手流量。 但这个方案治标不治本，攻击者可以变换手法。 最务实的建议 ： 升级。 9.1分的漏洞，临时缓解手段都是权宜之计。 五、一张图看懂这个漏洞的影响链路 六、为什么Java的漏洞比PHP更“要命”？ PHP那个漏洞影响的是PHP应用。 Java这个漏洞影响的是 Java的TLS通信层 。 Java在企业里的位置太特殊了。 银行的核心交易系统、电商的订单服务、政务的数据交换平台——底层全是Java。 这些系统之间通信靠什么？ TLS。 JSSE就是TLS的Java实现。 攻击者拿到JSSE的突破口，等于拿到了所有Java应用之间加密通信的“万能钥匙”的仿制品。 这也是为什么Oracle给这个漏洞打了9.1分，而不是像之前那些Spring Boot的CVE（比如CVE-2026-40972的时序攻击，需要同网段条件）只打中等分数。 JSSE的漏洞，攻击面是网络，不需要任何前置条件。 七、顺带说一句：Spring Boot那边也不太平 如果你觉得Java SE的漏洞已经够糟了，那Spring Boot生态今年的安全状况也不容乐观。 2026年4月，Spring Boot 4.0.6修复了 8个CVE ，包括： CVE-2026-40972 ：DevTools远程密钥比较的时序攻击，极端情况下可导致远程代码执行 CVE-2026-40975 ： ${random.value} 使用弱伪随机数生成器，不适合用于密钥 CVE-2026-40973 ：可预测的临时目录未验证所有权 还有3月份的 CVE-2026-22733 ，Actuator CloudFoundry端点下的认证绕过，评分High。 Spring Boot的漏洞大多有条件限制 （需要DevTools、需要特定配置、需要同网段），但JSSE这个漏洞 不需要任何前置条件 。 这就是9.1和6.x、7.x的区别。 八、优缺点与适用场景（针对修复方案） 方案 优点 缺点 适用场景 立即升级JDK 根治，无残留风险 需要测试兼容性 所有生产环境 临时限制暴露面 快速，不需要改代码 治标不治本 紧急过渡期 WAF监控 有一定预警作用 攻击者可绕过 辅助手段 不处理 无 9.1分漏洞裸奔 不推荐 更多项目实战在Java突击队网：susan.net.cn/project 九、写在最后 回到最初的问题： Java也爆出了重大安全漏洞，该怎么办？ 答案很直接—— 升级。 9.1分的CVSS评分，远程、无需认证、低复杂度。 这不是“可以等等看”的漏洞，是“今天就该处理”的漏洞。 JSSE是Java应用和外界通信的安全通道，这个通道失守，后面所有的安全措施都是建立在沙地上的。 具体操作 ： java -version 确认版本 如果版本在受影响列表里，去Oracle或你用的OpenJDK发行版下载最新补丁 升级，回归测试，上线 如果暂时升不了，至少把TLS暴露面收窄，加强监控 别觉得“我的项目在内网，没事”。 内网横向移动是真实存在的攻击路径，而且JSSE的漏洞不需要攻击者先拿到什么立足点——他直接就能打你的TLS端口。 Java生态的安全，从来不是“Java本身安全”就能保证的。 JDK有漏洞，Spring Boot有漏洞，第三方依赖有漏洞。 你能做的，是保持关注、及时升级、别让自己成为那个“还在用受影响版本”的人。 参考资源： CVE-2026-91403官方记录 ： www.cve.org/CVERecord?i… Oracle Critical Patch Update ： www.oracle.com/security-al… Spring Boot 4.0.6发布说明 ： spring.io/blog/2026/0… Spring Security CVE列表 ： spring.io/security</description><pubDate>Sun, 25 Oct 2026 07:59:36 +0000</pubDate></item><item><title>三个晚上，我和 WorkBuddy 做了个生图网站</title><link>https://juejin.cn/post/7700090207211356201</link><guid isPermaLink="false">juejin-8935139904d5c584</guid><description>我这台只有晚上才开开刷抖音的电脑，现在每天在给我出图。打开一个网址，选个题材，写一句中文，三十多秒，一张图就出来了。 三个晚上之前，这台电脑还是台普通机器：8G 显存，16G 内存，平时上上网。 这事是我和 WorkBuddy 一起干的。它写代码、反复试，我负责看，指挥它修改。 一、为什么在自己电脑上搞 我要的东西其实很简单：一个能让我随便试的地方。 生图这件事，好图不太可能一次写对，只能反复试。同一个题材，换个光线写法，换套衣服，换个背景，试十几次才知道哪版对。要是每试一次都得先犹豫一下，我大概试到第三次就停了，然后拿那版凑合一下当成品。 本地跑的好处就在这里。没有负担，可以无限试错。机器一直开着，我可以为了一个人的脸跑二十遍，可以干着别的事突然想到一个写法然后立刻去试，也可以把参数随便乱改，看看会出什么结果。 二、8G 显存是怎么撑起一张大图的 我这台机器的显卡是 RTX 5060 Ti，8G 显存，内存 16G。 我看到的讨论里，大多是 24G、48G。8G 属于勉强够用的配置。第一晚大部分时间，我都在干一件事：不让它崩。 那晚光是显存不够导致的失败，记了 6 次。表现是跑到一半直接退出，图没有，日志里也看不出什么。 试出三个办法，才算把它塞进去。 一是把模型压小。几十个 G 压到十二个 G 左右，精度理论上掉一点，我看了半天没看出来。 二是先出小图再放大。一步到位出那么大的图，显存扛不住。 三是一次只跑一张。不同时来，反正我一个人也用不上同时。 现在一张图三十多秒，八步。八步是指模型打磨画面的轮数。我看到的常规设置是几十步，我用的是压缩过的快版本。快是真快，代价是它对提示词的准确度更敏感——写偏了，它不会自己给你掰回来。 三、三个晚上，推翻了三次方案 第一次：以为「假」是因为磨皮磨过头 我拿第一版给朋友看，他说太假了，像塑料。 我的第一反应跟大部分人一样：磨皮磨过了。 就做了个柔和版，把皮肤纹理压下去。为了判断「到底多滑」，还专门让 AI 写了个小工具，量脸上四个点的纹理强度，输出一个数。 量完发现两件事。样本就一个人、两张图，说不上结论，但方向很清楚。 一件在我预料里：他嫌假的那张，纹理确实弱，3.10。我自己认可的那张是 4.84。 另一件完全没想到。他觉得假的那张，脸明显更亮。平均亮度 147，我认可的那张 127。 磨皮确实把皮肤磨滑了。但它同时把整张脸照亮了。 人眼判断「假」，看的好像不是滑不滑，是这张脸上还有没有明暗。一张脸如果只有一片均匀的亮，皮肤做得再细，看着也不像活的。 自然版（左）和磨皮版（右）。右边皮肤更滑，但看着更像假人——整张脸几乎没有明暗变化。 所以我一直修错地方了。皮太滑是表象，光太平才是根子。磨皮能解决的那部分，算不上主要矛盾。真正要动的是光。 第二次：以为分辨率越高越清楚 这件事和 AI 一起测了一晚上。 按常理，放大倍数越高越清楚。我一开始也这么想，直接上了 4 倍。 出来的人脸，皮肤像砂纸。 同一张底图、同一段描述，只换放大方式。最右下那格（4 倍再缩回目标尺寸）皮肤已经像砂纸了。 原因其实很简单。这个模型是「快」那一档的，它靠猜。你把一张猜出来的图放大 4 倍，猜对的地方清楚了，猜错的地方也清楚了。 现在就用 2 倍，一次到位。 要是那天晚上我一直往大的试，大概试到天亮还是砂纸。 第三次：以为写「不要」有用 画面里有个东西我不想要，很自然地写：不要 xx。 出来还有。 我以为是不够狠，改成「禁止出现 xx」，前后又加了几句狠话。 还是有。 后来做了三组对照，结果很干净：这个模型基本不吃「不要」。你写了，它读了，当没看见。 那我只能换个思路——不给它容易出错的机会。别画书上的字，别画要求特别准的东西，画面能简单就简单点。 从「事后禁止」改成「事前避开」。这句话听着像废话，但是是测了一个晚上出来的结果。 顺带一个：光不能只说一个词 我想让光从背后打过来，写了「逆光」。 出来是正面平光，一点都不逆。 试了十几种说法才摸到规律：光得说清三件事——从哪来、亮面在哪、阴影在哪。少一个，出来的多半不是你要的光。 只说一个词的（上排）基本都是平光；把方向、亮面、阴影都写到的（下排）光才真的从侧后方过来。 写「傍晚的窗边」，真正送进模型的那句是「光从左侧窗外低角度射进来，右脸落在阴影里，边缘有一点轮廓光」。 现在只管写想看的画面，光的事，交给这三条。 这张的完整描述只有一句话：「亚洲女性，米色针织衫，窗前侧光」。 最有用的一条：脸是定住的 这条是第三晚才确认的，整个人像特写能不能成立就看它。 先说种子。就是一个数字，相当于这张图的编号。同一段描述配同一个数字，出来的基本是同一张图。换个数字，同样的文案出来是另一个人。 同一个种子、同一段人物描述，只改衣服、改背景、改场景，出来还是同一个人。眼睛、眉形、唇形、脸的轮廓都不变。 于是你可以让同一个人换场景拍：今天海边，明天咖啡馆，穿毛衣或者穿风衣。 反过来也成立。动了人物描述，或者换了种子，基本就是另一个人。 四、这个网站怎么用 打开网页，左边是题材和输入框，右边是出好的大图，底下排着这次的缩略图，点一下能换回去看。 不用装软件，手机打开也能用。 十个内置题材：人像、静物产品、美食、室内空间、建筑、花卉植物、服装平铺、动物、风景场景、城市街景。 这十个题材，都是这套流程跑出来的，一张没修。 三个画幅：竖、方、横。竖图像素出来是 1792×2304。 人像两档质感：自然和柔和。差别就是皮肤保留多少纹理。 写一句中文就行，不用管参数。比如「傍晚的阳台，一杯手冲咖啡，逆光，安静」。点生成，三十多秒。 用下来最管用的一个习惯：觉得哪里不对，一次只改一个词。改多了你不知道是哪个词在起作用，这条我花了很久才养成。 五、为什么这么快 三个晚上，每晚都在跟一个问题死磕：显存、质感、把东西放到网上给人用。 说起来也不是因为 AI 打字快，是试错的循环变短了。 以前改一样东西：想到要改，查文档，改代码，重启，打开看，发现改错了，重来。一轮半小时，一晚上改不了几轮。 现在：我说「这个太滑」，它改，我刷新看一眼，说「还是滑，但光对了」。一轮一分钟。 一晚上能试几百次。按以前那个节奏，我估摸要三周。 我负责判断：这张能不能看、哪里不对、往哪个方向调。它负责实现：写代码、跑实验、算指标、修 bug、写文档。分工大概就是这样。 同一批参数试二十遍、翻日志找一行报错、几百次实验一个个跑完。 七、最后 现在这个东西已经能在网上使用。 那份清单我整理好了：六条光的写法、哪些词写了反而更糟、放大倍数的对照数据、十个题材怎么挑种子。这些细节文章里塞不下，想要在下面留言「清单」，我一个个发你。 如果想自己在家搭一个的朋友，也欢迎留言交流</description><pubDate>Sun, 25 Oct 2026 06:00:28 +0000</pubDate></item></channel></rss>