2026年1月

“今年上半年我在山东临沂见了一位满头白发的 90 后老板,他们公司的年销售额超过 3 亿元,但利润却不到 1000 万。”1688商家发展中心总经理王强在日前接受媒体采访时讲道,“白天睡觉、晚上陪客户,这是他们为此生意的主要方式。”

事实上,这不是个例,而是中国数百万中小工厂主的真实缩影——规模在增长,利润在萎缩;订单在增加,确定性在流失。

这种悖论背后,是一场深刻的结构性撕裂,是当前 B2B 产业面临的系统性挑战:合规成本持续攀升,环保、税务、用工等要求日益刚性;与此同时,供给极端碎片化、需求高度非标化,交易决策链冗长,产业经验变得难以沉淀和复用。这使得过去依靠压价和人情关系维系的生意模式,在今天已经难以为继。

换言之,中国产业带的底层逻辑正在被彻底重构,“K 型复苏”成为新常态,头部企业加速扩张,尾部企业加速出清,一个尖锐的问题摆在所有中小工厂面前——出路究竟在哪?

1688 在刚刚发布的《2025 中国产业带发展趋势报告》(以下简称“报告”)中给出了他们的答案:2025 年,中国产业带开始迈向 AI 原生时代。这不是又一次简单的工具升级,而是一场历史性跨越:从“数字化”向“智能化”的范式转变。在这场变革中,AI 不再是可有可无的“外挂”,而是决定生死存亡的“操作系统”。

该报告基于 1688 平台 26 年产业带深耕经验,整合覆盖全国 70%一级产业带、超百万家源头厂商的真实交易大数据,系统分析了 AI 在产业带的演进路径。

从“卷成本”到“拼确定性”,中国产业带的游戏规则正在被重写

如果说过去二十年产业带的竞争关键词是“规模”与“成本”,那么今天,“确定性”正迅速取代“低价”,成为新的护城河。所谓确定性,不只是按时交货,更包括产品品质稳定、服务响应及时、需求预测精准、合规风险可控。并且,这些变化的覆盖范围不仅限于国内市场,更是全球性的。

报告指出,产业带“江湖规矩”和“权力地图”正被彻底重写,“外转内”与“内转外”并行,产能外迁与产业内移共振,工厂正从“代工者”蜕变为“品牌主”。

而 AI,成了构建确定性的核心引擎,基于 AI 原生的下一代供应链呼之欲出。

据王强分享,深圳一家 3C 配件厂商深圳众鑫通泰曾深陷“低价、欠款、无客”的死循环,但他们通过 AI 分析亚马逊上的用户差评发现,一款手机支架的核心痛点是“粘不牢、价格高”,并据此开发出了采用真空磁吸技术的新品,同时,借助 AI 生成的油管爆款视频进行推广,这家工厂最终实现了跨境业务占比突破 70%,毛利率远超同行。

另一家位于安徽芜湖的一个 6 人鞋企也在 2024 年借助 AI 工具实现了惊人跃升:上新效率提升 4 倍,支付转化率提升 41%,全年销售额达 1.5 亿元。他们没有设计师,就用 AI 完成换色、场景图和视频生成;没有客服团队,就部署 7×24 小时自动响应系统;甚至通过 AI 分析 TikTok 热门搜索词,捕捉全球潮流趋势。“AI 已经是趋势了,等别人都试完再上,你就被淘汰了。”芜湖苏禾鞋业张云这样说。

类似的故事正在更多产业带上演。报告显示,和这些工厂一样,越来越多的中小企业正通过 AI 将内贸积累的柔性快反、品控能力“翻译”为跨境竞争力。AI 不再只是巨头的游戏,它同样成为了小微商家对抗规模劣势的“杠杆”。

具体而言,AI 已深度融入商家端的三大核心场景:选品方面,通过全球电商平台评论与社媒热词,反向定义产品;小单快返方面,基于柔性供应链使得响应周期大幅缩短;智能质检方面,用图像识别替代人工目检,使得效率大幅提升。

1688 公共事务部总经理范敏强调,这些变化背后指向了这样一种进化逻辑——AI 正在驱动三大“位移”:

第一,决策机制位移,从依赖“老师傅经验”转向依赖“AI 产业大脑”;第二,组织形态位移:从“人盯人”管理转向“AI 调度+人机协同”;第三,核心竞争力位移,从“模具和产能”转向“数据驱动的快速迭代能力”。

正如报告所示,2026 年起,增长将向能稳定交付、直连用户、自主开款的源头工厂集中。未来的赢家,不是规模最大、也不是成本最低的,而是将 AI 融入血液,构建起“效率×合规×确定性”新护城河的企业” 。在这个新规则下,“确定性”本身就成了最稀缺的资源,能否用好 AI,则成了企业穿越周期的关键。

超越“生意搭子”,AI 从来不只是一个工具

当然,这场跃迁并非坦途。数据孤岛、模型泛化能力不足、复合型人才短缺仍是大多数企业在 AI 应用落地过程中遇到的主要瓶颈。尤其在传统产业带,许多工厂连基础的 ERP 系统都没能打通,导致 AI 系统缺乏高质量、结构化的数据输入。

面对这些现实约束,企业可以选择从最小可行场景切入,以业务价值反推技术建设。以深圳众鑫通泰为例,他们并不是从一开始就试图构建“全厂智能大脑”,而是聚焦一个具体痛点——“用户为什么差评我们的手机支架?” 通过抓取公开电商平台评论这一无需内部系统打通的外部数据源,快速验证了 AI 选品的价值,再逐步将成功经验延伸至生产排程与质检环节。这种“由外而内、由点及面”的路径,有效绕开了初期数据孤岛的制约。

针对模型泛化难题,目前市场上已经有平台开始探索基于跨商家、跨品类的聚合数据,提炼具有行业共性的智能能力。比如 1688 依托其覆盖全国 70%一级产业带的交易网络,正尝试把成功案例中从选品洞察、质检规则到履约优化的 AI 应用逻辑,抽象为可借鉴的方法论甚至工具原型,以降低单个工厂的试错成本。

而破解人才困局的关键,在于构建“人机协同”的新工作流,而非追求全能型个体。拿芜湖苏禾鞋业这个 6 人企业来说,他们并没有 AI 工程师,也没有技术背景,仅仅通过应用 AI 工具,就实现了设计、营销与客户服务的全面提效。

王强表示:“未来的 AI 的卷应该是,你是不是让 AI 把你做成一套的体系或系统?而不仅仅是一个工具。” 换言之,AI 的价值不在于炫技,而在于能否系统性解决已知问题。

报告中对中国产业带进化进行了三阶段划分,随着 AI 应用深度的变化,企业将从“AI 外挂”进入“AI 共生”乃至“AI 原生”。而这也恰恰是企业应对以上一系列挑战的底层逻辑,企业不应该把 AI 当作一个孤立的技术项目,而是将其视为重构业务流程、组织协作与客户价值的契机。

对于身处这一变革浪潮中的企业,报告还给出了三条行动建议:

第一,要信仰 AI,用 AI 做生意,把工厂变成真正的“硅基工厂”——让每一条产线都听得懂需求、看得见订单、控得住质量;第二,要做足确定性,品控要硬、履约要稳、服务要好;第三,要布局双循环,AI 正在模糊内需与跨境的边界,因此企业需要一套 AI 系统,通做全球生意。

当 AI 不再是“生意搭子”,而成为产业运行的“智能中枢”,中国制造业的下一轮红利,才真正拉开序幕。这场变革不会一夜完成,但方向已然清晰:谁能把不确定性转化为确定性,谁就能在新秩序中占据主动。而 AI,正是那把最关键的钥匙。

现在,大模型可以独立写完整整一个浏览器了?

 

Cursor CEO Michael Truell 最近分享了一项颇为吸睛的实验:他们用 GPT-5.2 让系统连续不间断运行一周,从零构建出一个“可用”的 Web 浏览器。按他的描述,产出规模达到:超过 300 万行代码、横跨数千个文件,全部通过这套 AI 驱动的编程平台生成。

 

数百个 Agent “从零”写了一个浏览器?

 

按照他的说法,这个项目并没有依赖现成的渲染引擎,而是用 Rust 从零实现了一整套渲染引擎,其中包括 HTML 解析、CSS 级联规则、布局计算、文本排版(text shaping)、绘制(paint)流程,甚至还实现了一个自定义的 JavaScript 虚拟机。

 

Truell 也坦言,这个浏览器目前只是“勉强能用”,距离 WebKit 或 Chromium 等成熟引擎还有很大差距;但团队依然“感到震惊”,因为简单网站在它上面渲染得很快,而且整体效果在很大程度上是正确的。

 

与此同时,Cursor 还发布了一篇博客文章,题为《Scaling long-running autonomous coding》(扩展长时间运行的自主编程)。文章回顾了一系列实验:让“编程 agent 连续自主运行数周”,目标是“理解在那些通常需要人类团队耗费数月完成的项目中,agentic coding 的能力边界究竟可以被推进到什么程度”。

 

在这篇文章里,他们重点讲的是多 Agent 如何协同:如何在单个项目上同时运行数百个并发 Agent、如何协调它们的工作,并观察它们写出超过一百万行代码和数万亿个 token 的过程与经验。

 

Cursor 先承认了单个 Agent 的局限:任务规模一大、依赖一复杂,推进速度就会明显变慢。并行化看似顺理成章,但他们很快发现,难点不在并发,而在协同。

 

“学习如何协同:我们最初的方法是让所有 agent 具有同等地位,并通过一个共享文件自行协同。每个 agent 会检查其他 agent 在做什么、认领一个任务并更新自己的状态。为防止两个 agent 抢占同一项任务,我们使用了锁机制。

 

这一方案在一些有趣的方面失败了:

 

agent 会持有锁太久,或者干脆忘记释放锁。即使锁机制正常工作,它也会成为瓶颈。二十个 agent 的速度会下降到相当于两三个 agent 的有效吞吐量,大部分时间都花在等待上。

 

系统非常脆弱:agent 可能在持有锁的情况下失败、尝试获取自己已经持有的锁,或者在完全没有获取锁的情况下更新协调文件。

 

我们尝试用乐观并发控制来替代锁。agent 可以自由读取状态,但如果自上次读取后状态已经发生变化,则写入会失败。这种方式更简单、也更健壮,但更深层的问题依然存在。

 

在没有层级结构的情况下,agent 变得非常规避风险。它们会回避困难任务,转而做一些小而安全的修改。没有任何一个 agent 承担起解决难题或端到端实现的责任。结果就是工作长时间在空转,却没有实质性进展。”

 

为了解决这一问题,Cursor 最终引入了更明确的角色分工,搭建一条职责清晰的流水线:将 Agent 分为规划者和执行者。

 

“规划者(Planners) 持续探索代码库并创建任务。他们可以针对特定区域派生子规划者,使规划过程本身也可以并行且递归地展开。

 

执行者(Workers) 领取任务并专注于把任务完成到底。他们不会与其他执行者协调,也不关心整体大局,只是全力处理自己被分配的任务,完成后再提交变更。

 

在每个周期结束时,会有一个评审 Agent 判断是否继续,然后下一轮迭代会从干净的初始状态重新开始。这样基本解决了我们的协同问题,并且让我们可以扩展到非常大的项目,而不会让任何单个 Agent 陷入视野过于狭窄的状态。”

 

在此基础上,Cursor 把这套系统指向一个更具挑战性的目标:从零构建一个浏览器。他们表示,Agent 持续运行了将近一周,在 1,000 个文件中写出了超过 100 万行代码(原文如此,跟 Michael Truell 说的 300 万行不同),并将源码发布在 GitHub 上供外界浏览。

 

Cursor 进一步宣称:即便代码库规模已经很大,新启动的 agent 仍然能够理解它并取得实质性进展;同时,成百上千个 worker 并发运行,向同一个分支推送代码,而且几乎没有冲突

 

一场“全民打假”的开始?

 

这次实验之所以引发强烈反应,很大程度上是因为:Web 浏览器本身就是软件工程里公认的“地狱级”项目。

 

它难的不只是“写代码”,而是工作量的量级、模块之间的高耦合,以及兼容性这条几乎看不到尽头的长尾。

 

在 Hacker News 上,有人顺手抛了一个问题:“开发一个浏览器最难的地方是什么?”很快就有人给出一个类比:“说句真心话,这个问题几乎等同于:开发一个操作系统最难的地方是什么?”

 

因为现代浏览器是千万级代码量的系统,能够运行非常复杂的应用。它包含网络栈、多种解析器、frame 构建与回流(reflow)模块、合成(composite)、渲染(render)与绘制(paint)组件、前端 UI 组件、可扩展框架等等。这里面每一个模块,都必须同时做到:既支持 30 年前的旧内容,也支持复杂得离谱的当代 Web 应用。同时,它还得在高性能、高安全前提下尽可能少占用系统资源,并且往往要跨 Mac、Windows、Linux、Android、iOS 等多个平台运行。

 

还有人提到,最难的是那张超长的任务清单。浏览器里包含多个高复杂度模块,每一个单拎出来都可能要做很久;更麻烦的是,它们之间还要通过一套相当“啰嗦”的 API 连接起来——很多接口你必须实现,至少也得先把壳子(stub)搭出来,否则系统就会崩。

 

对这个浏览器项目,Cursor 在博客中写道:“虽然这看起来像是一张简单的屏幕截图,但从头开始构建一个浏览器是非常困难的。”

然而如果外界自己去尝试编译这个项目,会很快意识到:它离“功能齐全的浏览器”还差得很远,甚至看起来在公开代码状态下,连最基本的构建都很难稳定通过。

 

从仓库公开信息来看,近期 main 分支的多次 GitHub Actions 运行结果显示失败(其中还包括工作流文件本身的错误);不少开发者的独立构建尝试也报告了数十个编译错误。与此同时,最近的一些 PR 虽然被合并,但 CI 仍处于失败状态。

 

更有开发者表示自己回溯 Git 历史,往前翻了约 100 个提交后表示,依然没能找到一个可以“干净编译通过”的版本。

 

这也引出了一个问题:这些被 Cursor 描述为在代码库中长期并发运行的“agent”,在工程链路上到底做到哪一步?至少从当前公开状态看,它们似乎并没有把“能编译、能检查”当成最基础的收敛目标——因为无论是 cargo build 还是 cargo check,都会立刻暴露出成片的编译错误和大量警告。

 

而 Cursor 的博客文章除了提供代码仓库链接外,既没有提供可复现的演示,也没有提供任何已知的有效版本(标签/发布/提交)来验证截图。无论如何,这文章本身给人一种原型功能完备的错觉,却忽略了此类声明应有的基本可复现性特征。

 

有人在 Michael Truell 的 LinkedIn 上直接把结果抛了回去:“构建直接失败,报了 32 个错误,代码本身就是坏的;没有任何 release、没有 tag,CI 也在持续失败,我们甚至连这个所谓‘可用的浏览器’都没法编译、没法试跑。这更像是一场营销活动,而不是一次真正的 agentic 实验。”Michael Truell 至今没有回复。

 

目前唯一一个在社交平台上明确分享“复现成功”的人,是前浏览器开发者 Oliver Medhurst。他表示自己花了大约两个小时修复编译错误和漏洞,才把项目跑起来。至于性能,他的评价也很直接:有些页面加载要整整一分钟,“不算好”。

 

还有一个更敏感的追问也随之出现:“所以这真的是从零开始写的吗?”他给出的回应更像一句反转预告:“剧透:不是。”

 

更多网友通过翻看仓库依赖发现,这个项目直接引入了 Servo (一个最初由 Mozilla 开发的基于 Rust 的浏览器)项目的 HTML 与 CSS 解析器(html parser、css parser),以及 QuickJS 的 Rust 绑定(rquickjs),并非所有关键组件都是自行实现。

 

再加上 selectors、resvg、wgpu、tiny-skia 等一系列成熟库,这个“浏览器实验”更像是直接调用了人类编写的代码,而不是“从零开始”的一整套渲染与执行引擎。

 

更搞笑的是,Cursor 这里用的还是一个发布于 2023 年 6 月的 wgpu 0.17 这种非常旧的老版本,而当前最新版本已经是 28(发布于 2025 年 12 月)。大概因为大模型写代码时往往会直接改版本管理文件(如 package.json、Cargo.toml),而不是通过 npm add、cargo add 这类构建工具来引入依赖。

 

这也不怪网友骂他们:

 

“这简直是胡扯。应用根本跑不起来,功能也缺得厉害。LLM 更像是在把它训练过的现成代码拼起来做个浏览器——毕竟 Chromium 本来就是开源的。最后堆出了 300 万行‘看起来很多’但没有价值的代码,结果还不能用,更谈不上什么新产品。折腾到最后,你还是得让开发者花大量时间去调试、排查安全漏洞,才能把它打磨得像一个早就存在的成熟产品。”

 

“两周时间、数百个 agent,V8 和 Blink 又都是开源的。说到底,这就是在浪费 GPU 和电力。”

 

最后值得一提的是,这个实验还暴露出一个不容忽视的问题:成本。

 

有人翻回 Cursor 的原帖指出,他们还在跑类似实验,比如一个 Excel 克隆项目(https://github.com/wilson-anysphere/formula)。GitHub Actions 的概览数据很夸张:累计触发了 16 万多次 workflow 运行,但成功的只有 247 次——失败的主要原因不是代码本身,而是超出了支出上限。

 

当然,Agent 并不在乎预算;但在真实的软件工程里,可复现的构建、可持续的成本、可验证的产出,才决定一个系统最终能不能被信任、被维护、被继续推进。

 

参考链接:

https://cursor.com/cn/blog/scaling-agents

https://news.ycombinator.com/item?id=46646777

https://www.reddit.com/r/singularity/comments/1qd541a/ceo_of_cursor_said_they_coordinated_hundreds_of/

https://www.linkedin.com/posts/activity-7417328860045959169-PFuT/

https://xcancel.com/CanadaHonk

是的,PHP 拥有光明的未来。各位看官可能会觉得这是玩笑,但您别急,且听我扯几句。这不是标题党,也不是哗众取宠。这是楼主近几天实实在在的有感而发。

这一切源于最近我家小朋友有了编程的兴趣;在尝试学第一门编程语言。让我意想不到的是,他选择了 PHP 。我很惊讶,PHP 不是没落了吗?大家讨论的都是 JS ,Go ,Rust ,Python 等等热门语言,按理说小孩网上怎么搜也不会蹦出 PHP 这三个字母吧。令我更意想不到的是,他学得津津有味。而且已经有了一些成果。观察几天后,我才发现,这一切并非偶然。

最重要一点因素,是 PHP 有最友好的社区,没有之一。不管是内外网,PHP 社区有极高的包容度。PHP 的讨论区很少有无谓的争吵,虚荣的推销。相反,PHP 社区有很多在其他圈子少见的谦逊与耐心 — 这也是我小孩喜欢网上讨论 PHP 的关键因素:当其他社区因为一个语法糖,一个框架,一个包争得面红耳赤时,经验丰富的 PHP 程序员却愿意放下姿态去回答几岁小孩的入门问题。进入 Zig ,Rust 等等社区,你会看到如邪教一般的传道与重写,我一个大人都有点承受不住。为了小孩的身心健康,我打心底更愿意小孩在 PHP 社区成长。

另外,不管喜不喜欢这门语言,少有人会否认 PHP 一直是一门及其实用且稳定的语言。尤其在 web 1.0 时代,PHP 绝对是指哪打哪的大杀器。哪怕是今天,快速迭代一个中小型全栈项目,很多人都会拿起 Laravel/ThinkPHP 。而现代化的 PHP 8 更是吸收了各家所长,OOP ,函数式,协程,可以说要什么有什么。更难能可贵的是在快速迭代的同时依然保持了高度的兼容性。对比乱成一锅粥的 Node/JS ,小孩写的 PHP 代码,不管是老语法,还是旧框架,往往都能运行,正向反馈频繁。我相信现在这些代码 5 年后依然能正常运行。

看到这里,您可能就明白我为什么说 PHP 有光明的未来了。后浪推前浪,世界终归是我们下一代的。当孩子们选择了 PHP ,他们怎么不会再一次为 PHP 带来阳光呢。

— 于 PHP 8.5 发布日

temporal 官网示例

python:

@workflow.defn
class SleepForDaysWorkflow:
    # Send an email every 30 days, for the year
    @workflow.run
    async def run(self) -> None:
        for i in range(12):
            # Activities have built-in support for timeouts and retries!
            await workflow.execute_activity(
                send_email,
                start_to_close_timeout=timedelta(seconds=10),
            )

            # Sleep for 30 days (yes, really)!
            await workflow.sleep(timedelta(days=30))

ruby:


# Send an email every 30 days, for the year
class SleepForDaysWorkflow < Temporalio::Workflow::Definition
  def execute
    12.times do
      # Activities have built-in support for timeouts and retries!
      Temporalio::Workflow.execute_activity(
        SendEmailActivity,
        start_to_close_timeout: 10
      )

      # Sleep for 30 days (yes, really)!
      Temporalio::Workflow.sleep(30 * 24 * 60 * 60)
    end
  end
end

C#:

[Workflow]
public class SleepForDaysWorkflow
{
    // Send an email every 30 days, for the year
    [WorkflowRun]
    public async Task RunAsync()
    {
        for (int i = 0; i < 12; i++)
        {
            // Activities have built-in support for timeouts and retries!
            await Workflow.ExecuteActivityAsync(
                (Activities act) => act.SendEmail(),
                new() { StartToCloseTimeout = TimeSpan.FromSeconds(10) });

            // Sleep for 30 days (yes, really)!
            await Workflow.DelayAsync(TimeSpan.FromDays(30));
        }
    }
}

PHP:

class SleepForDaysWorkflow implements SleepForDaysWorkflowInterface
{
  // Send an email every 30 days.
  public function sleepForDays(): void
  {
      for ($i = 0; $i < 12; $i++) {
          // Activities have timeouts, and will be retried by default!
          $this->sendEmailActivity->sendEmail();

          // Sleep for 30 days (yes, really)!
          Workflow::sleep(30 * 24 * 60 * 60)
      }
  }
}

感觉对于 java 程序员 php 的心智负担好小啊

「一键部署你的专属服务器」——WNMP 一键包,让 Web 环境搭建回归简单

还在为 Nginx + PHP + 数据库 的复杂安装而头疼吗?
WNMP 一键包,让这一切变成——一行命令搞定。

apt install -y curl && curl -fL https://wnmp.org/zh/wnmp.sh -o wnmp.sh && chmod +x wnmp.sh && bash wnmp.sh

一分钟安装完整 Web 环境:

  • Nginx 1.28.0 (支持 HTTP/2 、WebDAV 、Stream )
  • PHP 8.2–8.5
  • MariaDB 10.6 / 10.11 (内置 Mroonga 全文搜索引擎)
  • 自动 SSL 证书( acme.sh
  • WebDAV 云盘支持(拒绝明文 FTP )

系统自动优化:

  • 启用 BBR/FQ 网络加速
  • 关闭 THP ,优化内核参数
  • 全面适配 Debian 12/13 、Ubuntu 22–25 、WSL2
  • 自动生成安全配置,默认防止常见漏洞

安全为先 · 默认即最优:

  • 内置 SSH 密钥登录
  • PHP 默认关闭危险函数
  • phpMyAdmin 启用 BasicAuth 双重防护
  • SSL 证书全自动签发与续期

面向开发者与站长的真正“零阻力”方案:
无论你是独立开发者、云服务商、还是边缘节点运维者,WNMP 让服务器环境部署变得和安装浏览器一样简单。
轻量、稳定、可复制 —— 一次配置,永久受益。

官方网站: https://wnmp.org
社区支持:QQ 群 1075305476 | Telegram @wnmps
Github:[url]https://github.com/lowphpcom/wnmp[/url]
开源协议:GPLv3

WNMP 不仅仅是一个脚本,它是下一代 PHP 运行环境生态的起点 ——
基于 LOWPHP 的常驻内存架构,未来将带来原生级的高性能 PHP 体验。

使用的测试文件 info.php,调用 php.info();
现在网站需要放在其他路径底下,修改了 nginx 中的 root 之后就提示 No input file specified.
但是 index.html 静态文件显示正常

在网上查的和 gpt 问,试过以下几种方式还是不行,求大佬帮忙看下

1 ,php74/etc/php-fpm.d/www.conf 文件中 chroot 和 chdir 参数都是默认注释的,
在 info.php 中,参数显示如下
USER www-data
HOME /var/www

2 ,nginx 中的 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
$document_root 或者修改成绝对路径也不行

3 ,修改 cgi.fix_pathinfo=0

4 ,;cgi.force_redirect=1 取消注释

上述的 4 种方式最多就是某 2 个一起试过。
关于文件权限问题,/var/www 使用的是 777 ,html 和我想放的文件夹 site 的权限也是 777 ,site 和网站文件的属组和属主都是 www-data
php74/etc/php-fpm.d/www.conf 文件中


user = www-data

group = www-data

listen = /run/php74-fpm.sock

listen.owner = www-data

listen.group = www-data

listen.mode = 0660


/run/php74-fpm.sock 的属组和属主是 www-data

求大佬帮忙看下还有什么办法嘛,想放到/var/www/site 文件夹下

PHP 用了十年了,也停滞在某个版本很多年了。

最近项目重构,用新的库,一开始用 laravel ,九牛二虎搞起来,感觉好复杂,还慢,就搞了 flightphp ,快十倍,也简单。但是,现在又发现 go ,flightphp 是猎豹,go 就是火箭啊。作为 web api ,也就基本 crud 工作,go 应该能很好的完成。数据库,ai 时代,完全可以用原生 SQL 了。

这次如果重构完成,那就要和 PHP 拜拜了,因为 WEBAPI 如果用 GO ,就没有地方用他了,测试用 PYTHON 大数据用 PYTHON EXCEL 用 PYTHON ,前端用 SVELTEKIT ,其他用 GO

这样子看,PHP 是不是快死了?微服务+ AI 时代,他没有擅长的技能,各个模块都被其他语言代替?

GitHub 仓库地址:https://github.com/fawdlstty/faml

什么是 FAML ?

FAML 是一种扩展自 TOML 的动态配置语言,专为需要运行时配置计算和更新的场景设计。它保留了 TOML 的简洁语法,同时增加了动态表达式、条件配置和运行时可变性等高级特性。

核心特性对比

特性 TOML KCL PKL FAML
语法风格 TOML 风格 JSON 风格 结构体风格 TOML 风格
动态表达式
条件配置
运行时修改
特殊数据类型

快速示例

基本语法

[server]
port = 8080
host = "localhost"

动态表达式

[database]
host = "localhost"
port = 5432
connection_string = $"postgresql://{host}:{port}/mydb"

条件配置

[app]
env = "production"

@if env == "development"
log_level = "debug"

@if env == "production"
log_level = "error"

特殊数据类型

[cache]
ttl = 5 minutes
max_size = 100 MB

[network]
timeout = 30 seconds
buffer_size = 4 KB

复杂表达式

[user]
age = 25
is_adult = age >= 18
welcome_message = is_adult ? $"Welcome, adult user!" : $"Welcome, young user!"

运行时动态修改

let mut config = FamlExpr::from_str(config_str)?;
config["server"]["port"].set_int(9000);  // 动态修改端口
let connection_string = config["database"]["connection_string"].evaluate()?.as_str();  // 自动更新连接字符串

文末广告 :)

2025

2024

I have much less spare time this year because I have a baby :p. And I'm looking for a sustainable way to contribute.

I joined the Rust compiler team (in 2024! :3).

LLVM: A performance regression in LLVM that affected Ajla and Python

This regression has been discussed elsewhere; see lobste.rs/s/9paxz2/performance_python_3_14_tail_call.

I introduced the regression due to a limit for compile time in llvm#78582.
Finally, I learned a resolve from GCC, and then I fixed the regression in llvm#114990 and llvm#132536.

Rust: Transforming “Clone” to “Copy”

To me, the most interesting issue is rust#128081.

The "Clone" method can be transformed to "Copy" in GVN. I have several PRs for this and am working on more.

The first key PR (rust#128299) exposed variant miscompilations. Camille Gillot identified the root cause in rust#147844:

We can reason with the value behind a reference because it is UB to directly assign to the underlying local while the reference is live. We allow creating new derefs, this means extending the liveness of references, so we are creating UB.

Rust: Debuginfo in MIR Basic Blocks

rust#129931 turns out that handling Debuginfo in MIR Basic Blocks is required. I implemented this in rust#142771.

This left some stuff:

Rust: 4 P-critical

I caused 4 P-critical issues. :(

The rust#124150 and rust#132353 are miscompilations in MIR opt. I'm investigating some translation validation tools, such as Miri, Alive2, and model checker, but I haven't made any progress. So far, I have only read Program Z3, and I have forgotten many things. Furthermore, I'm thinking about picking it up next year. :p

Other

While reviewing PRs can be exhausting, it's also a great learning opportunity. For instance, working through PRs like rust#142707, rust#143784, rust#136840, and rust#133832 taught me a great deal.

I realize that the knowledge of the LLVM backend is essential to me, since more and more issues happened in the LLVM backend. I'm not sure how to tackle these issues, but I have begun studying LLVM Code Generation: A deep dive into compiler backend development.

MIR optimizations are still important to me. I'd like to thank Camille Gillot for their help on MIR.

I'm trying to immerse myself in English, and I have stopped using LLM for Chinese-to-English translation anymore. :p

I'm also learning Japanese for fun. If you are interested in anime and manga, I recommend you read learnjapanese.moe.


家里没地方了 :(,卖掉我的 7950X 主机:

  • CPU:AMD 7950X
  • 主板:华硕 TUF GAMING B650M-PLUS
  • 内存 2 条:金士顿 FURY 32G D5 6000
  • 水冷:华硕 ROG STRIX 飞龙二代 360
  • 硬盘:ZHITAI TiPlus7100 2TB
  • 硬盘:Samsung SSD 980 PRO 2TB
  • 显卡:AMD 撼讯 RX6600
  • 电源:先马 XP850W 白金
  • 机箱:乔思伯 松果 D31

价格 11000 。

我使用 Koa 很多年了,一直很喜欢它简洁的设计哲学。近几年在 Cloudflare Worker 上开发较多,接触到了 Hono 。Hono 也是一个不错的框架,但在深入使用后,我对它的一些设计理念并不是很认同,于是萌生了自己造个轮子的想法。

我为新框架设定了三条核心原则:

  1. 微内核架构:与 Koa 类似,保留了洋葱模型的中间件设计,同时还补充了插件系统
  2. 符合直觉的 API 设计:摒弃 Koa 的 delegates 思路,API 严格区分 ctx/ctx.req/ctx.res ,更加符合语义
  3. 环境无关性:可在 Node.js 、Bun 、Deno 以及 Cloudflare Worker 、Vercel 等边缘环境运行

于是 Hoa 诞生了。目前我跟另一个维护者已经为 Hoa 补充了近 30 个常用中间件,我也已经将手头大部分项目从 Koa 迁移至 Hoa 。今天分享出来,希望更多人去使用,也期待收到更多反馈,共同把 Hoa 框架打磨得更好。

特点

  • ⚡ Minimal - Only ~4.4KB (gzipped).
  • 🚫 Zero Dependencies - Built on modern Web Standards with no external dependencies.
  • 🛠️ Highly Extensible - Features a flexible extension and middleware system.
  • 😊 Standards-Based - Designed entirely around modern Web Standard APIs.
  • 🌐 Multi-Runtime - The same code runs on Cloudflare Workers, Deno, Bun, Node.js, and more.
  • ✅ 100% Tested – Backed by a full-coverage automated test suite.

安装

npm i hoa --save

快速开始

import { Hoa } from 'hoa'
const app = new Hoa()

app.use(async (ctx, next) => {
  ctx.res.body = 'Hello, Hoa!'
})

export default app

License

MIT

今天打开电脑 idea 响应非常慢,会瞬间吃满 CPU ,界面下发提示无响应,我没有兴趣 dump (公司的破项目看着都恶心呢),去官方论坛搜了搜,看起来不止我一个人。
看起来像个内存泄露,在打开大型项目时,较长时间不关机有几率触发。

参考链接:
https://youtrack.jetbrains.com/issue/IDEA-383438/IDEA-2025.3.1-freezes-when-opening-big-Maven-project-at-org.jetbrains.idea.maven.project.MavenProjectCompanion.read

问题

如果对一个 InputStream 调用 read 方法,在没有数据可读取的时候,理论上会处于阻塞状态。

This method blocks until input data is available, end of file is detected, or an exception is thrown.

那么如果一个 IO 流,曾经有数据可读取但已经被读取完毕,但后续仍有可能增加可读取的数据,此时调用 read ,是不是仍然属于这里所说的“blocks until input data is available”,也就是,是否会发生阻塞?

例子

我获取了 Process 的 InputStream ,里面的内容是这个进程的标准输出(以及错误输出)。
显然,进程的输出是间断性的,我想知道现有输出已经被读完的情况下,此时再调用 read ,是否还会处于阻塞状态?

    private ProcessBuilder dumpProcessBuilder;
    ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream(1024);
    ...
    dumpProcessBuilder.redirectErrorStream(true);
    ...
    Process dumpProcess = dumpProcessBuilder.start();
    try (InputStream in = procForReader.getInputStream()) {
        byte[] buf = new byte[1024];
        int bytesRead = 0;
        while ((bytesRead = in.read(buf)) != -1) {
            output.write(buf, 0, outputBuffer);
        }
    }
    ...

最近想搞一搞 agent cli 开发。UI 层面,node 有比较成熟的 ink 方案。

但是看了下 go TUI 相关的解决方案,描述 UI 的方式有点别扭。当然可能是我没找到更好的实现思路。

所以实现了 rego ,取 react + go 的意思。

话不多说,先上代码。

package main

import (
    "fmt"
    "github.com/erweixin/rego"
)

func App(c rego.C) rego.Node {
    count := rego.Use(c, "count", 0)
    
    rego.UseKey(c, func(key rego.Key, r rune) {
        switch r {
        case '+': count.Set(count.Val + 1)
        case '-': count.Set(count.Val - 1)
        case 'q': c.Quit()
        }
    })
    
    return rego.VStack(
        rego.Text("Rego Counter").Bold(),
        rego.Text(fmt.Sprintf("Count: %d", count.Val)),
        rego.Spacer(),
        rego.Text("[+] 增加  [-] 减少  [q] 退出").Dim(),
    )
}

func main() {
    rego.Run(App)
}

运行效果:

Rego Counter
Count: 0

[+] 增加  [-] 减少  [q] 退出

仓库: https://github.com/erweixin/rego

对于多组件的使用可以参考: https://github.com/erweixin/rego/tree/main/examples/gallery

再贴一个 stream 组件的 demo 吧。

https://github.com/erweixin/rego/blob/main/examples/stream/stream_demo.gif

欢迎各位大佬试用、提 Issue 或 PR 。如果你也喜欢这种“在终端写 React”的思路,欢迎给个 Star 支持一下!👏

之前在这发布过的,最近花了些时间给这个小工具写了份比较详细的文档了,请查阅

文档: https://www.gonc.cc/docs/

Github: https://github.com/threatexpert/gonc

自己平时使用的场景:

1 、公司的 VPN 好久不用了,家里 CGNAT 宽带和公司建立 P2P 的 HTTP+SOCKS5 代理隧道,自由访问公司网络。

2 、和分公司内网直接 P2P 快速(实时压缩)传输文件/目录。

3 、内置服务模块满足其他场景,例如 TCP/UDP 端口转发、类 frp 反向代理、甚至科学上网,一个工具都胜任了。

还真别说,通过打洞建立 P2P 的加密隧道,定期端口轮换的功能本来是针对运营商 Qos 的,在科学上网方面有独特的效果。

基于 Pydantic-Resolve 和 FastAPI-Voyager 的 Clean Architecture 实践

篇幅较长无法粘贴全文,原文链接:
https://github.com/allmonday/A-Python-web-development-methodology-for-complex-business-scenarios/blob/main/README.zh.md

一套面向复杂业务场景的 Python Web 开发方法论

目录


1. 背景与问题

1.1 当前主流做法及其痛点

在 Python Web 开发中,处理复杂业务场景时,开发者通常采用以下几种模式:

模式一:直接使用 ORM (如 SQLAlchemy )

@router.get("/teams/{team_id}", response_model=TeamDetail)
async def get_team(team_id: int, session: AsyncSession = Depends(get_session)):
    # 获取团队基本信息
    team = await session.get(Team, team_id)

    # 获取 Sprint 列表
    sprints = await session.execute(
        select(Sprint).where(Sprint.team_id == team_id)
    )
    team.sprints = sprints.scalars().all()

    # 获取每个 Sprint 的 Story
    for sprint in team.sprints:
        stories = await session.execute(
            select(Story).where(Story.sprint_id == sprint.id)
        )
        sprint.stories = stories.scalars().all()

        # 获取每个 Story 的 Task
        for story in sprint.stories:
            tasks = await session.execute(
                select(Task).where(Task.story_id == story.id)
            )
            story.tasks = tasks.scalars().all()

            # 获取每个 Task 的负责人
            for task in story.tasks:
                task.owner = await session.get(User, task.owner_id)

    return team

这种做法在简单场景下确实很直观,能够快速上手。ORM 的类型安全特性也能在编译时发现一些错误,而且与数据库表结构的一一对应关系让代码容易理解。但当我们面对真正的业务场景时,这种方式的缺陷很快就暴露出来了。

最致命的问题是 N+1 查询。虽然代码看起来很清晰,但执行时会产生大量的数据库查询。每当我们访问一个关联关系时,ORM 就会发起一次新的查询。在深层嵌套的情况下,查询数量会呈指数级增长。更糟糕的是,这种性能问题在开发阶段不容易发现,只有当数据量积累到一定程度后才会显现出来,那时候往往已经太晚了。

代码的组织方式也是个问题。数据获取的逻辑散落在各个嵌套的循环中,业务逻辑和数据获取逻辑混在一起,难以阅读和维护。当需要修改业务规则时,开发者不得不在复杂的嵌套结构中寻找修改点,很容易引入新的 bug 。性能更是不可控,随着数据量的增长,查询效率会急剧下降,而这些性能瓶颈很难在代码层面直接观察到。

此外,相似的数据获取逻辑会在多个 API 中重复出现,导致大量代码冗余。当一个 API 需要获取"团队及其 Sprint",另一个 API 需要"团队及其成员"时,即使它们的查询逻辑非常相似,也不得不重复编写。这违反了 DRY ( Don't Repeat Yourself )原则,增加了维护成本。

模式二:使用 ORM 的 Eager Loading

@router.get("/teams/{team_id}", response_model=TeamDetail)
async def get_team(team_id: int, session: AsyncSession = Depends(get_session)):
    # 使用 joinedload 预加载关联数据
    result = await session.execute(
        select(Team)
        .options(
            joinedload(Team.sprints)
            .joinedload(Sprint.stories)
            .joinedload(Story.tasks)
            .joinedload(Task.owner)
        )
        .where(Team.id == team_id)
    )
    return result.scalar_one()

为了解决 N+1 查询问题,ORM 提供了 Eager Loading 机制,让我们可以通过 joinedloadselectinload 等方式预先加载关联数据。代码变得更简洁了,性能问题也得到了缓解。但这种方案也带来了新的挑战。

最明显的问题是笛卡尔积。当我们使用多层 JOIN 预加载关联数据时,数据库返回的数据量会急剧膨胀。比如一个团队有 10 个 Sprint ,每个 Sprint 有 10 个 Story ,每个 Story 有 10 个 Task ,那么 JOIN 的结果集会包含 1000 行数据,即使每行的数据量不大,也会给网络传输和内存占用带来压力。

更严重的问题是灵活性差。Eager Loading 的策略是在代码中硬编码的,所有使用同一个 Model 的 API 都会执行相同的预加载逻辑。但不同的 API 往往需要不同的数据。比如一个 API 只需要团队的基本信息,另一个 API 需要团队的 Sprint ,还有一个 API 需要团队的成员。如果统一使用 Eager Loading 加载所有关联数据,就会出现过度获取的问题,前端不需要的数据也被查询和传输了,浪费了资源。

配置 Eager Loading 本身就很复杂。开发者需要理解 lazyjoinedloadselectinloadsubquery 等多种加载策略的区别,知道什么时候用哪一种,以及它们各自会有什么副作用。这种配置错误很容易导致性能问题或意外的数据加载行为。而且,这种"一刀切"的配置方式意味着所有 API 都使用相同的加载策略,无法针对特定场景进行优化。

模式三:手动组装数据

@router.get("/teams/{team_id}", response_model=TeamDetail)
async def get_team(team_id: int, session: AsyncSession = Depends(get_session)):
    # 1. 批量获取所有需要的数据
    team = await session.get(Team, team_id)

    sprints_result = await session.execute(
        select(Sprint).where(Sprint.team_id == team_id)
    )
    sprint_ids = [s.id for s in sprints_result.scalars().all()]

    stories_result = await session.execute(
        select(Story).where(Story.sprint_id.in_(sprint_ids))
    )
    story_ids = [s.id for s in stories_result.scalars().all()]

    tasks_result = await session.execute(
        select(Task).where(Story.id.in_(story_ids))
    )
    tasks = tasks_result.scalars().all()

    owner_ids = list(set(t.owner_id for t in tasks))
    owners_result = await session.execute(
        select(User).where(User.id.in_(owner_ids))
    )
    owners = {u.id: u for u in owners_result.scalars().all()}

    # 2. 手动组装数据结构
    sprint_dict = {s.id: s for s in sprints_result.scalars().all()}
    story_dict = {s.id: s for s in stories_result.scalars().all()}

    for story in story_dict.values():
        story.tasks = [t for t in tasks if t.story_id == story.id]
        for task in story.tasks:
            task.owner = owners.get(task.owner_id)

    for sprint in sprint_dict.values():
        sprint.stories = [s for s in story_dict.values() if s.sprint_id == sprint.id]

    team.sprints = list(sprint_dict.values())

    return team

为了获得最优的性能和精确的数据控制,有经验的开发者会选择手动组装数据。这种方式完全掌控查询逻辑,可以精确控制每个查询的 SQL 语句,避免不必要的数据库访问。通过批量查询和智能的数据组装,可以获得最佳的性能,而且没有冗余数据。

但这种方式的代价是代码变得非常冗长。如上面的例子所示,为了获取一个团队的完整信息,我们需要编写多个查询,手动构建数据字典,然后通过嵌套循环组装数据。代码的长度和复杂度都大幅增加,而真正表达业务逻辑的代码反而被淹没在数据组装的细节中。

更容易出错也是个大问题。手动组装数据涉及到大量的索引操作和循环嵌套,很容易出现索引错误、空指针引用等 bug 。而且这些错误往往只有在运行时、特定数据条件下才会暴露,难以在开发阶段发现。

维护成本更是高昂。当业务规则发生变化时(比如需要添加一个新的关联关系),开发者需要在所有相关的 API 中修改数据组装逻辑。如果遗漏了某个地方,就会导致数据不一致。而且,相似的数据组装逻辑会在多个 API 中重复出现,违反了 DRY 原则。

最根本的问题是,这种代码已经变成了纯粹的数据搬运工,看不出任何业务意图。代码中充满了字典操作、循环嵌套、索引查找,而这些都是技术细节,与业务需求毫无关系。新加入的团队成员很难从这些代码中理解业务逻辑,业务知识的传递变得异常困难。

模式四:使用 GraphQL

type Query {
    team(id: ID!): Team
}

type Team {
    id: ID!
    name: String!
    sprints: [Sprint!]!
}

type Sprint {
    id: ID!
    name: String!
    stories: [Story!]!
}

type Story {
    id: ID!
    name: String!
    tasks: [Task!]!
}

type Task {
    id: ID!
    name: String!
    owner: User!
}

GraphQL 确实是一个很有吸引力的方案。前端可以按需获取数据,需要什么字段就查什么字段,不会有过度获取的问题。它提供了类型安全的查询接口,而且通过 DataLoader 可以自动解决 N+1 查询问题。这些特性让 GraphQL 在前端开发中广受欢迎。

但 GraphQL 的学习曲线非常陡峭。开发者需要学习全新的查询语言、Schema 定义、Resolver 编写、DataLoader 配置等一堆概念,这与 REST API 的直观性形成了鲜明对比。更麻烦的是,GraphQL 的过度灵活性给后端带来了巨大的挑战。前端可以构造任意复杂的查询,有些查询甚至可能是开发者没有想到过的,这导致后端很难进行针对性的优化。当一个查询嵌套了 10 层,返回了数百万条数据时,数据库和服务器都会面临巨大的压力。

调试 GraphQL API 也比调试 REST API 复杂得多。当一个 GraphQL 查询出错时,错误信息往往很难定位到具体的问题源头。而且 GraphQL 需要额外的服务器和工具链支持,无法直接利用现有的 FastAPI 生态系统。比如 FastAPI 的依赖注入、中间件、自动文档生成等特性,在 GraphQL 中都无法直接使用。

还有一个更深层次的问题是 ERD 和用例的界限模糊。GraphQL 的 Schema 同时扮演了实体模型和查询接口两个角色。当我们设计一个 GraphQL Schema 时,很难确定应该按照实体来组织(一个 Type 对应一个数据库表),还是按照用例来组织(不同的业务场景需要不同的字段)。这导致最佳实践不清晰,不同的项目、不同的开发者可能有完全不同的组织方式。

而且随着业务增长,所有的用例都会堆砌在同一个 Schema 中,导致 Schema 膨胀,难以维护。权限控制也变得异常复杂。不同的 API 端点可能有不同的权限要求,但它们可能都查询同一个实体(比如 User ),在 GraphQL 中很难针对不同的查询场景应用不同的权限规则。

1.2 问题根源分析

上面我们探讨的所有模式,虽然表面上的问题各不相同,但它们的核心困境其实是一致的。

问题 1:业务模型与数据模型混淆

# SQLAlchemy ORM 同时扮演两个角色:
# 1. 数据模型(如何存储)
# 2. 业务模型(业务概念)

class Team(Base):
    __tablename__ = 'teams'

    id = Column(Integer, primary_key=True)
    name = Column(String)

    # 这是数据库的外键关系,还是业务关系?
    sprints = relationship("Sprint", back_populates="team")

在传统的 ORM 开发中,业务模型和数据模型是混在一起的。看看这个例子,Team 类既表达了业务概念(团队是什么),又承载了数据模型的细节(如何在数据库中存储)。当我们在 sprints 字段上定义 relationship 时,这到底是在描述一个业务关系(团队有多个 Sprint ),还是在声明一个数据库外键约束?这种模糊性会导致很多问题。

数据库的设计约束会直接影响我们的业务建模。比如,如果数据库中的 teams 表没有直接到 users 的外键,而是通过中间表 team_members 关联,那么在 ORM 中我们也必须通过这个中间表来定义关系。这意味着业务模型被迫适应数据库的实现细节,而不是反过来。

更严重的是,这种方式无法表达跨库、跨服务的业务关系。现代系统中,数据可能分布在不同的数据库中,甚至存储在外部服务里。比如用户的基本信息在 PostgreSQL ,而用户的偏好设置在 MongoDB ,用户的实时状态在 Redis 中。ORM 的 relationship 无法跨越这些边界,业务模型因此被限制在了单一数据库的范围内。

问题 2:依赖方向错误

传统架构的依赖方向:
┌─────────────┐
│   API Layer │  ← 依赖于
└──────┬──────┘
       │
       ↓
┌─────────────┐
│ ORM Models  │  ← 依赖于
└──────┬──────┘
       │
       ↓
┌─────────────┐
│  Database   │
└─────────────┘

问题:业务规则依赖于数据库实现!

这违反了 Clean Architecture 的依赖规则。正确的依赖关系应该是:业务规则最稳定,不依赖任何外层;数据库是实现细节,应该依赖业务规则;当数据库变化时,业务规则不应该受影响。但传统架构的依赖方向恰恰相反,业务规则被数据库的实现细节所绑架。

问题 3:缺少业务关系的显式声明

# 传统方式:业务关系隐藏在查询中
async def get_team_tasks(team_id: int):
    # "团队的任务"这个业务概念隐藏在 SQL WHERE 中
    result = await session.execute(
        select(Task)
        .join(Sprint, Sprint.id == Task.sprint_id)
        .where(Sprint.team_id == team_id)
    )
    return result.scalars().all()

业务关系没有被显式声明出来,这是个很隐蔽但危害很大的问题。看看这个例子,"团队的任务"是一个清晰的业务概念,但这个概念被隐藏在 SQL 的 JOIN 和 WHERE 子句中。新加入团队的成员需要阅读大量代码才能理解系统中有哪些业务关系,这些关系是如何定义的。更糟糕的是,没有自动化的方式来检查业务关系的一致性。当需求变化需要修改某个关系时,开发者很难找到所有相关的代码,很容易遗漏某个地方,导致业务逻辑的不一致。

问题 4:中间表的技术暴露

在 SQLAlchemy ORM 中,多对多关系需要显式定义中间表,这导致技术细节泄漏到业务层。

# SQLAlchemy ORM:必须定义中间表
class Team(Base):
    __tablename__ = 'teams'
    id = Column(Integer, primary_key=True)
    name = Column(String)

    # ORM relationship 需要指定中间表
    members = relationship("User",
                          secondary="team_members",  # 必须指定中间表
                          back_populates="teams")

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String)

    teams = relationship("Team",
                        secondary="team_members",  # 必须指定中间表
                        back_populates="members")

# 中间表(技术实现细节)
class TeamMember(Base):
    __tablename__ = 'team_members'
    team_id = Column(Integer, ForeignKey('teams.id'), primary_key=True)
    user_id = Column(Integer, ForeignKey('users.id'), primary_key=True)
    role = Column(String)  # 可能还有额外字段

# 查询时需要关心中间表的存在
@router.get("/teams/{team_id}")
async def get_team_members(team_id: int, session: AsyncSession):
    # 必须通过中间表查询
    result = await session.execute(
        select(User)
        .join(TeamMember, TeamMember.user_id == User.id)  # 中间表暴露
        .where(TeamMember.team_id == team_id)
    )
    return result.scalars().all()

这个问题的根源在于,ORM 的多对多关系需要显式定义中间表,这导致技术细节直接泄漏到业务层代码中。业务代码必须知道 team_members 中间表的存在,查询时也需要显式地 join 这个中间表。这增加了代码复杂度,更重要的是,业务逻辑被数据库的实现细节所绑架。

更深层的问题是业务语义变得模糊。TeamMember 到底是一个有意义的业务概念,还是纯粹的技术实现?如果中间表还有额外的字段(比如 role 表示用户在团队中的角色,joined_at 表示加入时间),这些字段应该被建模为独立的实体吗?不同的开发者可能给出不同的答案,缺乏统一的指导原则。

数据组装也因此变得复杂。查询"团队的所有成员"需要 join 中间表,查询"用户所属的团队"也需要 join 中间表。所有涉及多对多关系的查询都变得冗长和难以理解。当业务规则要求"获取用户在所有团队中的角色"时,情况就更加复杂了。这些技术细节让业务逻辑的实现变得异常沉重。

对比:Pydantic-Resolve ERD 的方式

# ERD:业务概念清晰,无需关心中间表
class TeamEntity(BaseModel, BaseEntity):
    """团队实体 - 业务概念"""
    __relationships__ = [
        # 直接表达"团队有多个成员"的业务关系
        Relationship(
            field='id',
            target_kls=list[UserEntity],
            loader=team_to_users_loader  # loader 内部处理中间表
        ),
    ]
    id: int
    name: str

class UserEntity(BaseModel, BaseEntity):
    """用户实体 - 业务概念"""
    __relationships__ = [
        # 直接表达"用户属于多个团队"的业务关系
        Relationship(
            field='id',
            target_kls=list[TeamEntity],
            loader=user_to_teams_loader
        ),
    ]
    id: int
    name: str

# Loader 实现细节:中间表只在这里出现
async def team_to_users_loader(team_ids: list[int]):
    """加载团队成员 - 内部处理中间表"""
    async with get_session() as session:
        # 只有这里需要知道中间表的存在
        result = await session.execute(
            select(User)
            .join(TeamMember, TeamMember.user_id == User.id)
            .where(TeamMember.team_id.in_(team_ids))
        )
        users = result.scalars().all()

        # 构建映射
        users_by_team = {}
        for user in users:
            for tm in user.team_memberships:
                if tm.team_id not in users_by_team:
                    users_by_team[tm.team_id] = []
                users_by_team[tm.team_id].append(user)

        return [users_by_team.get(tid, []) for tid in team_ids]

关键差异

维度 SQLAlchemy ORM Pydantic-Resolve ERD
中间表位置 暴露在业务层 隐藏在 loader 实现中
业务语义 技术关系 (secondary) 业务关系 (团队包含成员)
查询代码 需要 join 中间表 loader.load(team_id)
代码位置 分散在多处 集中在 loader
测试 依赖数据库表结构 可 mock loader

架构优势

传统方式:
Team → TeamMember (中间表) → User
业务层需要知道中间表的存在

Pydantic-Resolve 方式:
Team → User (业务关系)
中间表是数据层的实现细节,业务层不关心

这意味着:

  1. 业务模型纯净:Team 和 User 的关系直接表达业务语义

  2. 技术细节封装:中间表的存在被封装在 loader 中

  3. 灵活的存储策略


    • 数据库可以用中间表实现
    • 也可以用 JSON 字段存储
    • 甚至可以是外部服务(如 LDAP )
    • 业务层代码无需修改
  4. 易于理解:新人看到 ERD 就能理解业务关系,不需要先学习数据库设计


2. Clean Architecture 思想

2.1 核心原则

Clean Architecture 由 Robert C. Martin (Uncle Bob) 提出,核心思想是:

"Software architecture is the art of drawing lines that I call boundaries."
软件架构的艺术在于画界线。

原则 1:依赖规则

外层依赖内层,内层不依赖外层。

                ↓ 依赖方向
    ┌─────────────────────┐
    │   Frameworks &      │  外层
    │   Drivers           │  (实现细节)
    ├─────────────────────┤
    │   Interface         │
    │   Adapters          │
    ├─────────────────────┤
    │   Use Cases         │
    │   (Application)     │
    ├─────────────────────┤
    │   Entities          │  内层
    │   (Business Rules)  │  (核心)
    └─────────────────────┘

遵循依赖规则有几个关键点需要注意。首先,内层不知道外层的存在,这意味着核心业务逻辑不依赖于任何框架、数据库或 UI 的细节。其次,内层不包含外层的信息,比如业务规则不应该知道数据是用 PostgreSQL 还是 MongoDB 存储的。最后,外层的实现可以随时替换而不影响内层,这意味着我们可以从 SQLAlchemy 切换到 MongoDB ,或者从 FastAPI 切换到 Django ,而业务逻辑代码无需修改。

原则 2:业务规则独立

# ❌ 错误:业务规则依赖数据库
class Task:
    def calculate_priority(self, session):
        # 业务逻辑被数据库实现细节污染
        if self.assignee_id in session.query(TeamMember).filter_by(role='lead'):
            return 'high'

# ✅ 正确:业务规则独立
class Task:
    def calculate_priority(self, assignee_roles):
        # 业务逻辑只依赖业务概念
        if 'lead' in assignee_roles:
            return 'high'

原则 3:跨边界的数据传递

# 内层定义数据结构
class TaskEntity(BaseModel):
    id: int
    name: str
    assignee_id: int

# 外层负责转换
def task_entity_to_orm(entity: TaskEntity) -> Task:
    return Task(
        id=entity.id,
        name=entity.name,
        assignee_id=entity.assignee_id
    )

2.2 依赖规则

在 Web 开发中,依赖规则可以这样理解:

┌────────────────────────────────────────────────────┐
│         Presentation Layer (外层)                   │
│  - FastAPI Routes                                   │
│  - Request/Response Models                          │
│  - 依赖: Application Layer                          │
└────────────────────────────────────────────────────┘
                    ↓
┌────────────────────────────────────────────────────┐
│      Application Layer (Use Cases)                 │
│  - 业务用例(获取用户、创建订单)                    │
│  - 依赖: Domain Layer                               │
└────────────────────────────────────────────────────┘
                    ↓
┌────────────────────────────────────────────────────┐
│           Domain Layer (内层)                      │
│  - Entities (业务实体)                              │
│  - Business Rules (业务规则)                        │
│  - Value Objects (值对象)                           │
│  - 不依赖任何外层                                    │
└────────────────────────────────────────────────────┘
                    ↓
┌────────────────────────────────────────────────────┐
│    Infrastructure Layer (最外层)                   │
│  - Database (SQLAlchemy)                           │
│  - External Services                               │
│  - File System                                     │
└────────────────────────────────────────────────────┘

关键洞察

  • Entities 不应该知道 SQLAlchemy 的存在
  • Business Rules 不应该知道数据库表结构
  • Use Cases 不应该知道 HTTP 协议的细节

2.3 在 Web 开发中的应用

传统架构的问题

# 传统方式:所有层次耦合

# Domain Layer (应该独立,但实际上依赖了 ORM)
class User(Base):  # ← SQLAlchemy Base
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)

# Application Layer (应该只依赖 Domain ,但直接使用了 ORM)
async def create_user(data: dict, session: AsyncSession):
    user = User(**data)  # ← 直接使用 ORM Model
    session.add(user)
    await session.commit()

# Presentation Layer
@router.post("/users")
async def api_create_user(data: dict, session=Depends(get_session)):
    return await create_user(data, session)  # ← 暴露了数据库细节

这段代码暴露了传统架构的核心问题。SQLAlchemy 虽然建立了对象关系映射( ORM ),让数据库表可以通过 Python 对象来操作,但这种映射关系过于紧密。ORM Model 既承担了数据持久化的职责,又要表达业务概念,导致对象无法自由地代表业务模型。业务实体被数据库的实现细节所绑架,每个字段、每个关系都必须与数据库表结构一一对应,完全失去了作为独立业务概念存在的自由。

更深层次的问题包括:

  1. Domain Layer 被 SQLAlchemy 绑定:业务实体继承了 SQLAlchemy 的 Base ,无法独立于数据库存在
  2. 业务逻辑无法脱离数据库测试:编写单元测试时必须启动完整的数据库环境,大大降低了测试效率
  3. 切换数据库需要修改所有层:当从 PostgreSQL 迁移到 MongoDB 时,所有使用 ORM Model 的代码都需要重写


。。。

Google Antigravity 使用过程中,遇到的一些问题,总结一下:

  1. 没告诉他规范的时候,他按照自己的性格来。
    你告诉他规范了,他说的好好的,会认真遵守,但是实际操作中,又总会丢三落四,选择性的忽略了/忘记了其中的几条规范。
    等你告诉他这个错了,请严格按照规范来,他又是道歉,重新按照规范来。
    问题是规范约束很多条,我也不一定能记得全。

  2. 退化问题。
    比如说,让他加好的文件注释。在经过几轮对话修改后,他很可能莫名其妙得,不知道什么时候,又把注释给删掉了。(加上的代码,逻辑也有可能被删掉。)你发现了,问他为什么删掉。他又道歉,然后说删错了,又给你补回去。

  3. 幻觉问题
    比如说,正常情况下,一个通用的包,🈶️A ,B ,C ,但是 D 部分是没有的。
    在引入的时候,他一本正经得根据推导,认为 A ,B ,C 存在,按道理 D 也应该存在。然后将 D 引入到你的项目。结果呢。你跑半天,跑不通。然后你问他,这咋回事。他又道歉,删掉 D ,换一种思路,换一种方式来实现。

  4. 规范问题
    每次切换一个模型,你总得告诉他,请认真阅读 aiconfig 规范,并严格遵守。没法做到持久化。当然,就算你告诉他了,他也阅读了 aiconfig 规范,他也是“基本遵守”。大不了,等你发现他又犯贱,没遵守规范。他又道歉,说不好意思,我违反了规范,我马上修改。

想到的就这些。

总之,Vibe Coding 一时爽,粗心早晚泪千行。

大家使用过程中遇到什么问题,也可以分享一下,避免踩坑。

在网页上,输入,一般是一个 <input />
或者 <textarea />
后来有了很多 app ,比如微信
输入这个操作,不再局限于网页
可以建个公众号,设置好服务端处理用户消息
向公众号发送东西,服务端就可以记录下来
然后再写个网页显示记录
网页登录就各显神通吧
微信公众号发送消息有些门槛,但是还有其他平台的可以用
you own your data
数据在自己手中,想怎么处理怎么处理

前情提要

抠腚 API ,公益中转站,无套路,快来帮忙压测

  • 注册就送 每日 20 刀/总共 30 日的套餐,官方倍率没有套路,赶紧蹬起来

  • 官网 https://api.jonwinters.pw

  • 配置文档 https://docs.jonwinters.pw

  • 大家注册后,到图下这个地方复制自己的 id ,然后回帖自己的 id ,我看到了 会给你立即发放套餐

加速节点 无 cloudflare 纯 gcp 服务器直连

https://api-hk.jonwinters.pw
https://api-jp.jonwinters.pw

另外有问题欢迎进群吹水,交流 AI 使用经验,以及 claude code 、gemini cli 等配置相关问题

最近刚收了个 Macmini ,用 VNC 卡顿有些严重(高性能不知道为何无法启用),所以在昨天在 Macmini 和我的 MacBook 以及 iPhone 上都安装了 Rustdesk 的客户端,并且在自己家的 NAS 中自建服务端并启用了自建的服务端。在今天接到了 96110 的电话。

接线员首先问我是不是不在北京了(我的号码是北京的号码),我如实回答。

然后她问我最近有没有手机下载什么远程控制软件。其实我当时想到了是 rustdesk ,但我想的是这个也不能控制我手机,只是我用手机控制电脑,所以就说没有下载。

她就让我提供我具体的地址,说以后会有上海的反诈中心与我联系,紧接着就问了我一句“XXXX 楼”这里是哪儿呢?

我当时听到就有点炸了,因为这就是我现在居住的地址。虽然我知道反诈是能够从运营商那里获取到手机信号地址位置信息的,但在电话中听到一个陌生人说出自己的居住地址,心里面还是会有些发毛。

在我和反诈中心接线员确认了地址后,我就问了一句,是因为我下载的 rustdesk 这个远程控制软件么?然后我解释了一下我使用这个程序的原因。接线员也是表示理解。她还是提了一句,说让我务必接听来自上海反诈的电话,因为我已经被反诈系统标记了,不然会很麻烦。然后就挂断了。


在写这个帖子的时候接到了上海反诈中心的电话,不过这个是机器人客服,问了几个简单的问题,回答了就自动挂断了。根据机器人透漏的信息,我猜如果我拒接的话,北京反诈说的“麻烦“应该就是民警上门。


写这个帖子的初衷原本是想提醒大家,如果用 rustdesk 软件,可以考虑阻断 rustdesk.com 的请求(如果自建的话),或者是走代理。

我猜想反诈能联系上我,大概率是因为我在手机移动网络环境使用 rustdesk app 的原因,因为我公司的宽带和家里面的宽带绑定的都不是我的信息。

但我刚看了一眼网络请求,我三台设备关于 *.rustdesk.com 的请求都是经过代理了的,这就让我很不解了,不知道大家有没有什么想法,反诈系统是如何检测到的。


请大家友好讨论,我分享这个并不是要批判反诈系统,我对这套系统持中立态度,它的确有让我感觉到自己的隐私泄露的不安,但想法还是复杂的,我还是相信这套系统帮助不少人的。

项目历程

初出茅庐

2020 年 7 月,那会刚高中,我正在尝试 Android 原生开发,当前恰好需要缓存一些 B 站的视频素材,就在找手机上能直接缓存提取好的 APP ,那会我还不能经常用电脑,我记得当年也有一个简单实现了功能的 APP ,我当时就在想能不能给自己做一个,就这样,我的第一个 Java 原生应用诞生了——BILIBILIAS ,其中 as 取自单词 analysis ,当时还发布了一篇文章:Bilibili 番剧/视频下载 || BILIBILI AS,这个 APP 算是我整个安卓开发的启蒙了,那会还是用 Java ,当第一个原生程序真正的跑起来,那种感觉是无与伦比的,这个项目当年还非常简陋,只是按钮和图片组成的,我清晰的记得它的样子。
我那会刚开始在 B 站发布一些视频,我希望向其他人分享这个项目的过程,当时这个项目就被开源了,我还可以在 git 记录看到。

那虽然是一个简陋的 APP ,但我还是想分享出去,没想到当时就收获了很多用户,这让我看到了开发程序的另外一种可能,这之后,我决定继续优化这个 APP ,通过这个形式学习安卓开发。同年 11 月,我发布了新的版本[ B 站番剧/视频下载] BILIBILI AS v1.0.7 更新,当时 BUG 非常多,只能说勉强可以用,不过还是有那么多人来反馈。

当时我找了其他伙伴来处理服务器的事情,我们使用最便宜的学生机,轻量机来部署后端服务,用来发布更新和通知,应该这时这个 APP 就不止一个人维护了。

牛刀小试

可能是尝到了甜头,也可能是发现有这么多用户,我觉得需要再做的更好一些,但当时实际上并不懂重构的概念,只是觉得应该在现在的基础上优化优化,我就继续对 UI 进行了一些调整,将之前的代码可能加了一些封装,我正在不断尝试安卓原生开发带来的新 API 和功能,当时真的是如饥似渴,每周放假就扎进去开发,21 年的 2 月,我发布了一个看上去还不错的版本:[教程] 爆肝!自制 B 站缓存软件
当时还做了桌面小组件:搁置已久的安卓版本 B 站桌面小部件来啦!!!
这个时候,这个项目的另外一个使命来了,我发现我们已经有了一部分用户积累,这个时候我开始在 APP 加上轮播图,本来是用来做一些通知的,但我觉得我们已经有有一部分用户,应该有一些担当,就这样,这个轮播图有的时候我会推荐一些新动漫,有的时候会发布 B 站的一些重要通知,部分特殊节日和国家事件,我们也会宣传,当年疫情期间,我们还为大家提供了疫情地图显示。

崭露头角

高中结束的时候,毫无疑问的,我选了计算机相关专业,现在来看是毫无疑问,但回头看实际上是这个 app 影响了我的选择,它是鉴定选择软件行业的后盾。当时参加了一次字节跳动组织的青训营,我才发现,欸,原来架构设计是这样的,原来还有 DataBinding 这样的库,原来还有序列化这么一说,那我之前做的是什么?当时结营后我就将我的 APP 进行了一次重构,当然,现在看其实当时只是修改了一些关键的地方,但,这至少看上去像一个 APP 了。
当时我好像还写了一篇文章,关于饺子播放器的,但是那个版本的截图不太找得到了,当时也没有怎么宣传:饺子播放器实现抖音点击布局暂停及双击效果

小有名气

大学后我才开始真正的接触到 Kotlin ,新东西总是让人畏惧,但是新知识我真的很难不去用,老软件 BUG 实在是太多了,而且还不能多任务下载,所以我当时还是做了个大胆的决定,那就是重新开发!使用 Kotlin 和 DataBinding 技术。
23 年,项目的重构版本 2.0 来了,这次重构实际上是从软件架构上进行了一些设计,虽然现在看当时做的还是拉完了,但在当时我才真正接触到复杂项目开发,设计代码结构,有非常重要的意义。
[分享] 重制 B 站视频缓存工具
这个版本发布后,接下来的几年中都采用了 23 年开发的架构,在这个基础上进行更多功能的扩展,当然,因为当初设计局限性还是很大,导致后期还是没办法继续迭代了。

这些年来,BILIBILIAS 积攒了不少用户,很多动漫爱好者,学生等群体都需要使用它。

光荣进化

到现在,我觉得需要到更进一步的阶段了,JetpackCompose 技术已经逐渐稳定,我开始采用更高效的响应式框架进行开发,这就是我们现在的 3.0 版本!
这既是尝试新技术,也是对老用户的一个交代,还有人需要它,现在我就还需要继续维护一段时间。

项目困境

现在用户的需求逐渐变多,但社区开发人员主力只有我一个,到了现在,我希望去认真的设计项目的每一块内容,确实是跟不上用户的需要,有很大工作,设计都需要一点一点的推进。
所以我希望可以招揽更多对这个项目感兴趣的社区成员参与这个项目的开发

项目地址:bilibilias
项目预览:GooglePlay

这是一个三维地图应用,可以在上面编写动作操作场景变化,我看到 B 站上的 up 发关于世界局势的时候,经常用这种地球视角来讲解,就想着自己做一个看看效果。主要技术栈为 Vue3 + Vite + TS + Cesum 。

特点:

  • 动作编辑。现在就只有两个动作,飞都某地以及环绕某一个地点飞行。
  • 链接分享。可以生成包含动作都链接。
  • 链接导入,导出。

当前还没开源呢,我计划给它增加几个动作,例如切换地图,添加自定义图像,显示文字,播放声音等。

我觉得现在这个 UI 好丑,但是我没有美术功底,期待 V 友反馈哈哈哈。

demo 地址: https://giserlab.cn/app/mapflow/

下面这个很长的是分享链接,进去会有默认动作。

https://giserlab.cn/app/mapflow/?actions=NobwRA1gpgnmBcYCGBGAxgBgGwA4AsArALRICceaReARlhkdQMwAmxSaaOAZgYwOxoATNQxgANGAB2SALZQEYQJHagSBVA-criwzKAGc0AJwCWABwAuBgPaSFGkzCPzEXADZwJXA5IPaAFlGYIuJCdtKAlmJBMkBHAnSwQUFFIAOlwMQSwUCScIhEYMFMYcHAxGCSCTXJK6MIBXPQiLK3gCAF8WsXBoOERGNAJmPKw+IgJ+riouQVIiUhRmPCIhUkFAjDxewRwNaTkFQHRlQEW5QC5lDS1dQ1NG6wlbewVnVzB3Tx8-AKCQsIio+Bi4+BQghQSXweDomTA2Qq8CmINIpAIeBwZSc0JQvFIGFq9TM-z4bQ6kFgCkxeC4gUEjCIGGY1EoeAoxHI8yIuDJaCwWGEaCg1G2sgcYEAF3aAfRsTmEdPpjLimmAbHZBQ8NM8vL5-PBAsFQppvtFIf9AfkgYkcBCoZUkoIMDhGIxBCjoQQMM6sZo6g1-lgCZ1iYgihg+KMkOMMARMFQ+FAmVw6VREaNbRgkHwEvzdohReLNJKLjKFIBVv0AexmZ+V3RBIPTmGqSfxuDyqt4aj7a8KRPWxJqGy0JUimrI5eB5S3W2325CohBW12+AwAc28aJdrvqklnDidzokRgMJjQ3gQcexHs7gm9RO6YDwKCwtIySCopCwCzwtPopGYHKI+GoKD4jBQJSUqQaaCoAs0GAOrK6gSuc0pXIgpaKi4yr1q86qap8Optr8+onvkeCCAQCJ8P20KCHwSQECgOBYIwWBHnm8CkA6CCcgBZ5dAoUBIJyUB8GSRBQLR9KRlADA8oIRBaOQnJWowUBYAQIEKIAHHqAI46UHZjBlz-HKNwKvcSF1i8arvFqXxYX8nZURRCSCIIeAkZOOBJJi9lgvRcGAsx8D4Rg7G+k8BBIOwf4LHwVRUPwlA4KQUAoNSgjMEC1B8FwWBINQikSDsgqALhKgDTmoAY5GAH9qpw5rBOkIQZjwqqhpkYa2PyWfEVpJCayyOYORoEHwRQeZ63kEXQS5tAAukAA