443 条断言全绿,输入框却 9 次提交都不能用:一次关于「假护栏」的复盘

〇、AI 使用声明

这个项目的代码、测试脚本、报错定位、文档整理全程使用了 AI 编码工具。所有架构判断、平台规则核对、真机验证与最终验收由我完成。本文记录的每一个坑都来自项目自身的提交记录与自证日志;初稿借助 AI 协助整理,事实核对与定稿由我完成。

一、先说一件难堪的事

2026 年 9 月,我给自己的微信小程序写完了第一版自动化测试:

  • 功能仿真 443 条断言
  • 属性测试 18 组不变量
  • 静态检查 50 项

全绿。

三天后,用户发来一句话:

所有输入框都不能用。

不是禁用------是没法输入 。框特别矮,打的字不回显,placeholder 也不显示。三个症状同时出现,通常只有一个原因:输入框的高度塌了。

定位到的样式是这样:

css 复制代码
.input {
  padding: 12rpx 24rpx;
  border: 1rpx solid #eeeeee;
  border-radius: 12rpx;
  font-size: 28rpx;
  /* 没有 height */
}

小程序的 input 不会像 Web 那样被内容撑高,只写 padding 不写 height,高度就是塌的 。正确写法是 height 与 line-height 显式成对给出。

这行样式从初始提交 就存在,活了 9 次提交。这期间我每一次交付,说的都是"测试全绿"。

为什么 443 条断言一条都没拦住

因为那 443 条断言验的全是 JS 数据流 :mock 掉 wx 和云数据库,直接调页面方法,断言 data 里的字段。

以"待办输入"为例,测试是这样跑的:

js 复制代码
// 测试做的事
page.onTodoInput({ detail: { value: '写周报' } });
assert(page.data.todoInput === '写周报');   // ✓ 通过

onTodoInput 确实把 todoInput 正确设成了"写周报"。JS 层完全正确,断言必然通过。

但用户看不见 JS 层。用户看见的是一个塌掉的框。挡住用户的是 CSS,而 CSS 不在任何一条断言的作用域里。

这两件事是正交的,我却把前者的绿灯当成了后者的证据。

更糟的是,我当时还有一道"class 存在性"检查:tool.wxss 里有没有定义 .input。答案是有 ------它有边框、有内边距、有圆角、有字号,在那道检查眼里完全合格。

类存在 ≠ 定义能产出可用的框。 检查停在"有没有",没走到"够不够"。

这件事之后我才承认:这不是"疏忽",是验证方法本身有系统性盲区。

二、什么算「假护栏」

我把这类东西统称为假护栏:

一条断言跑过了、绿了,但它测的不是它名字上的那件事。

它有几种典型长相:

  • 名字写着"防止 X 回归",而 X 被删掉之后它照样通过;
  • 扫全文找某个声明,结果扫到的是自己写的注释;
  • 比较两个数组,结果两边都是空数组;
  • 期望值取自被测对象自己,改错了代码和期望值一起变,永远相等。

为什么它比"根本没写"更危险

没写断言,你至少知道这里没人守。假护栏更麻烦,原因有三个:

第一,它占着位置。 一条名字很专业的断言躺在那里,任何人(包括三个月后的你)都不会再去补一条"已经有了"的检查。

第二,它会繁殖到文档里。 我曾经把"代码侧 6 项与后台指引是对得上的"写进上线清单------而这个结论来自一个只比较了"我写的常量"和"我写的代码"的检查。它从未碰过平台后台。同一错误来源推出的结论,写进清单后看起来就像"已经核过了"。

第三,改代码的人,正是让护栏失效的人,而他不看护栏。 这一点最要命。你不是故意绕开检查,你只是在改业务,顺便让检查失去了靶子。

三个自查判据

写完之后问三句,能问出大部分假护栏:

  1. 把它名字上要防的缺陷造回去,它会不会红?
  2. 它的期望值是从哪来的? 如果被测对象自己也参与生产这个期望值,它无效。
  3. 它能不能靠只改另一个文件绕过?

第 1 条是核心。"造了缺陷却拦不住",等于宣告这条断言测的不是它名字上的东西。 这句话我后来在自己身上验证了十几次。

三、二十多种失效,归成四类

这一个多月我把踩过的空转逐个记录、逐个造缺陷复核,整理下来有二十多种。按病因归四类,比按现象列清单好记,也更容易在新场景里认出来。

A 类|条件根本走不到

检查写对了,但执行路径从来没被走到。

A1 缺陷绕过了前置条件

还是输入框那个坑。修复时我加了一道"充分性"检查:扫所有用在 <input> / <textarea> 上的类,若 box-sizing: border-box 且有上下 padding,却既没有 height 也没有 min-height,判失败。

自证时我删掉了一个 height------检查全绿。

原因:我删的那个类,不满足这条检查的前置条件(它没有上下 padding),于是检查压根没扫它。缺陷绕过去了,不是被拦住了。

修法不是改条件,是加一条前置断言:先确认"确实解析到了输入类元素",解析不到就报"前置失败",而不是让检查静默通过。

A2 前提假设本身是假的

给条形码模块(CODE128)做自证时,我设计的缺陷是:把 String(b).padStart(11, '0') 里的 padStart 去掉,前导零被吞掉,图案编码应该出错,护栏应该红。

实际全绿。

去查 CODE128 的图案表才明白:107 项里没有任何一项以 0 开头 (段数分布是 {"11": 106, "13": 1},后者是终止符)。也就是说 String(b) 和 String(b).padStart(11, '0') 输出完全相同。

我构造的那个"缺陷",根本不产生任何变化。护栏拦不到一个不存在的变化。

自证"没拦住"时,第一反应应当是怀疑自己的假设是否成立,而不是怀疑护栏写错了。

A3 样本够不到阈值

给首页"最近使用"写不变量:不超上限、无重复、最新在前。

其中"不超上限"写成 next.length > MAX。MAX = 8,而我的测试只准备了 4 个 key ------列表最多只能长到 4 条。> 8 永远为假,这条断言一次都没执行过。

改完(14 个 key)立刻再自证,"去重失效"这一组反而漏了 :14 个 key 每次都用一个新的 → 新 key 插到队首、最老的被挤出窗口 → 即使完全不做去重,结果看起来也永远"无重复"。缺陷显不了形,护栏自然拦不到。

最后要两个条件同时满足 才有效:key 种类数 > 上限,且同一个 key 要连续用几次。

阈值类断言,先问"样本够不够得到这个阈值";再问"参数组合会不会把缺陷掩掉"。

B 类|匹配范围不对

检查会跑,但瞄错了地方。这一类比 A 类隐蔽得多,因为它会给你一个漂亮的绿灯。

B1 条件太宽,被无关引用满足

检查"结果区是否渲染了处理结果":

js 复制代码
ok(/\{\{[^}]*textOut[^}]*\}\}/.test(toolWxmlSrc), '8.42 结果区渲染了 {{textOut}}');

自证时我把结果区正文清空(<view class="text__result__body"></view>)------没拦住。

因为同一文件里,复制按钮的文案也含 textOut:

html 复制代码
<button bindtap="copyText">复制{{textOut ? '结果' : '文本'}}</button>

宽松正则被这处无关引用满足了。目标节点已经清空,检查依然通过。

收紧到承载该语义的那一个节点才生效:

js 复制代码
/text__result__body[^>]*>\{\{\s*textOut\s*\}\}/
B2 注释里写的字,也是字

检查全屏页的光面是否真的铺满:

js 复制代码
ok(/100vh/.test(lightWxssSrc) && /100vw/.test(lightWxssSrc), '7b.8.28 光面铺满');

把 .lg { height: 100vh } 改成 height: auto(光面塌成一小块)------没拦住。

因为 light.wxss 顶部的注释 里,我恰好写过:「光面必须真的铺满:100vw/100vh + overflow:hidden」。/100vh/ 扫全文时被这行注释满足了。

两处修:先剥注释再断言 ,且锚定到目标规则体内真正生效的声明,而不是扫全文。

js 复制代码
const css = lightWxssSrc.replace(/\/\*[\s\S]*?\*\//g, '');
const lgRule = (css.match(/\.lg\s*\{([^}]*)\}/) || [])[1] || '';
ok(!!lgRule, '7b.8.28a 前置:确实解析到 .lg 规则');
ok(/height:\s*100vh/.test(lgRule) && /width:\s*100vw/.test(lgRule), '7b.8.28 光面铺满');

静态检查的匹配对象,应当是"解析后的结构",而不是"原始文本"。

原始文本里什么都有:注释、字符串、示例、别处的同名引用。

B3 只覆盖了我脑子里那一种写法

检查"工具页的兜底入口不得再把亮度参数带回全屏页":

js 复制代码
// 我以为的坏写法:'?idx=3&pct=80'
ok(/pages\/light\/light\?idx=/.test(src) && !/pages\/light\/light\?idx=[^'"]*pct=/.test(src));

造缺陷时我按拼接表达式 写:'/pages/light/light?idx=' + idx + '&pct=80'------没拦住(拦下它的是另一条断言,这条静默通过)。

原因:?idx= 后面紧跟的是 '(字符串结束),pct 在下一个字面量里。正则压根没扫到它所在的那一段。

修法不是把正则写得更花,而是换匹配范围 :取整个 openLightFull() 的方法体,在方法体里判有没有 pct。

js 复制代码
const body = stripJsComments(
  (toolJsSrc.match(/openLightFull\s*\(\)\s*\{([\s\S]*?)\n  \},/) || [])[1] || ''
);
ok(!!body, '前置:确实解析到方法体(否则下一条会匹配不到而永远通过)');
ok(/pages\/light\/light\?idx=/.test(body) && !/pct/i.test(body), '兜底入口不得带亮度参数');

写"某东西不得出现 "的断言时,正则的边界往往来自"我以为它会怎么写"。

正确做法是把范围放到承载该语义的最小结构单元,而不是围绕某一种拼写去描边。

B4 扫描范围本身就是一条断言

这一例我印象最深,因为它把"前置断言"本身也骗了。

给万年历的「添加纪念日」入口加检查,防止标签又被写成 25 字长句:

js 复制代码
if (!/<button\b/.test(src)) {
  bad('找不到 <button>(锚点失效)→ 按钮标签检查已空转');
  return;
}
const labels = [...src.matchAll(/<button\b[^>]*>([\s\S]*?)<\/button>/g)];

看着很稳:没有 button 就报锚点失效。

同一天我把入口从 <button> 改成了 <view>(要换成细文字行)。于是:

  • 前置断言照常通过 ------页面上还有 60 个 <button>(别人的);
  • 被测的那个入口已经不在扫描范围内了 → 这条检查从此空转。

它是怎么被发现的?给这条护栏造缺陷(把入口文案改回 25 字长句),期望报红,实际全绿。

前置断言要锚"被测对象本身"(那个类名),不能锚"同类元素存在"。

页面越复杂,后者越容易假通过------同类元素到处都是,"存在性"几乎恒真。

C 类|信号源不在了

这一类的共同点是:断言还在跑,但它要检查的东西已经不存在了,于是它安静地退化成一行注释。

C1 字段删了,引用它的断言恒为 undefined
js 复制代码
// lightOn 字段已经随功能一起删掉了
if (q.data.lightOn) bad.push(`id=${id} 离开后打光板仍在接管屏幕亮度`);

q.data.lightOn 恒为 undefined,这个 if 永远不成立。它不报错、不报红,只会安静地从"检查"变成一行注释------而且它挂在"跨 42 个工具切走后资源清理"这么好的位置,看上去像一道防线。

C2 否定式断言:靶子被删掉,就永远成立
js 复制代码
ok(!/lg__row/.test(lightWxmlSrc) && !/\.lg__row\s*\{/.test(lightCss), '7b.8.34e 不留双列死结构');

这条的本意是"防死结构回归"。后来重写浮层时,我把 .lg__row 彻底删了 。此后这条断言无论代码怎么改都成立------它检查的东西已经不存在了。

更糟的是它名字里写着"防死结构回归",看起来很有价值,所以谁也不会去动它。

否定式断言必须同时锚住被否定对象的替代者。

比如".lg__row 没回来" 且 ".lg__chips 真是 flex 行、.lg__chip 各 flex:1"。

这样结构被整体删掉时断言会失败 ,而不是永远通过。

C3 mock 是手抄的,缺字段

填备案号那次。功能仿真里的 wx 和云库都是 mock,而 getApp() 返回的是手抄 的一份 { openid, inviteCode, user }------没有 icpCode。

页面 onShow 读 app.globalData.icpCode || '' → 永远是 ''。

此时如果写一条"首页 data.icpCode 与配置一致",它会在 '' === '' 上通过 ;写"备案号非空"则会失败,但失败原因指向页面而不是 mock。两种写法都测不到"用户首页到底有没有那行字"。

修法:mock 的全局状态必须从真实 app.js 读 (app.js 顶层无 require,可以用 App() 钩子安全捕获 globalData),让 mock 与真机同源,不再漂移。

C4 预处理把要找的东西一起处理掉了

云开发里,集合名写在引号里(getCollection('todos', ...))。而项目的 stripComments() 不只是去注释,它连普通字符串内容一起整段丢弃 ------这是刻意设计,因为判断"某 API 是否被真实调用"时,界面文案里出现的 wx.xxx() 不该算调用。

于是拿默认视图扫源码,得到的是 getCollection( , ...)。两条"未发现某危险模式"的断言,都是在空匹配上通过的。

这次能发现纯属运气:第三条检查恰好是"取值"型(要拿到字符串),它一报错才暴露了真相。如果没写那条,三条会一起绿,报告还更漂亮。

修法:拆出第二个视图------只去注释、保留字符串。分工写进函数注释:

  • 判断"是否调用了某 API" → 用默认视图(防文案误报);
  • 取"写在引号里的值"(集合名 / 字段名 / 备案号)→ 必须用保留字符串的视图。

"未发现 X" 类断言,必须先证明信号源是活的。

前置断言不能只锚"文件读到了"------文件确实读到了,只是内容被掏空 。

要锚在目标本身:必须真能解析出客户端在用的集合名,且数量 ≥ 5。

D 类|断言到不了那个边界

前三类的边界都在一个文件内。这一类的问题是:约定跨越了两个我控制不了的东西,而断言只站在其中一边。

D1 跨文件契约,两边各自全绿

客户端发图给云函数做内容安全检测:

js 复制代码
// 客户端
callCloud({ kind: 'image', mediaBase64, contentType });

// 云函数
const { mediaBase64, contentType } = event;
if (!mediaBase64) return { code: 400, message: '缺少图片数据' };

两边各有 14 条静态护栏 + 56 条功能断言,全绿 。但没有任何一条在管"两边的键名是否还对得上"。

任一边改名 → 另一边拿到 undefined → 云函数在读入参时就 return 400 → 端上被 catch 成「检测服务异常,请稍后重试」→ 用户重试一万次也不会好。

两边分开看都是对的,合起来是坏的。

判据:这条断言,能不能靠只改另一个文件绕过?

修法:双向对账------从云函数源码抽 event.xxx 的键集合,从客户端抽 callCloud({...}) 的键集合,两个方向都查(少传 = 读到 undefined,多传 = 静默被忽略),并加前置断言防止"任一边解析不到"时空转。

D2 只验"我发出去了",没验"对方收得到"

给审核员写整改说明,我把它写进了"项目备注"------那是开发者工具上传时的一个短字段,旁边就写着"备注"两个字。

官方运营的原话是:审核同学能看到【版本描述、测试备注 】,项目备注看不到。

那段精心写的整改说明,写在那个字段里 = 等于没写。

我核实了"这段话该怎么说",却没核实"这个字段对方看不看得到"。

D3 验证了自己写的常量,当成平台上的那份

隐私指引对账。检查的两个输入是:

面 内容 是否真被核实
A 代码里出现的隐私接口(真扫 36 个文件) 真的
B PRIVACY_DECLARED 数组 我自己写的一行字

我验证的是"我写的清单",不是"平台上的声明"。而审核看的是平台那份。

更糟的是这条检查的输出原文写着:

text 复制代码
→ 后台勾选:收集你选中的照片或视频信息 / 访问你的摄像头 / ...

它把"我的常量"直接印成了"后台勾选"。 这句假话让我(以及任何读日志的人)以为后台已经被核对过了。

输出文案也是断言。 一句没有被证据支撑的话,会让整条检查看起来比它实际做到的更多。

修法:仓库里放一份后台正文的快照 ,与代码实际调用双向对账;输出文案改成「代码侧命中(★ 此处未看过后台)」。

D4 同源比较:期望值来自被测对象自己

农历工具的可查范围。我写了三条断言:提示要写出真实下界、picker 范围等于纯函数层范围、面板要用插值。

问题:这三条的期望值全都来自 lunarMod.MIN_DATE 同一个常量 。把常量改错(1900-01-31 → 1900-01-01)时,代码与期望值一起变,断言照样全绿。

它只能防"面板各写一份",防不了"常量本身写错"------而后者才是真正会伤到用户的那一类。

修法是从行为里探出真相:遍历 1900-01-01 到 1900-01-31,取"第一个真能查出农历的日子",再与页面声明的下界比对。改常量 → 探测结果不变、声明变了 → 立刻红。

这条断言的期望值是从哪来的?如果被测对象自己也参与生产这个期望值,它就无效。

四、四条能直接抄的规矩

把上面的二十多种收敛一下,其实只有四条:

1. 前置断言:锚"被测对象本身",不锚"同类元素"

js 复制代码
const body = (src.match(/openLightFull\s*\(\)\s*\{([\s\S]*?)\n  \},/) || [])[1] || '';
ok(!!body, '前置:确实解析到方法体(否则下面那条会匹配不到而永远通过)');

它的作用不是多一条断言,而是把"护栏失效"这件事本身变成红灯。这一条是整个体系里性价比最高的检查。

2. 剥注释、锚最终生效值、限定最小语义单元

三件事说的是同一件事:别在原始文本上做判断。

  • CSS 按规则解析,不扫全文;同名规则按源码顺序取最后生效的那条。
  • JS 取方法体,不扫全文件。
  • 所有"按词判断"的断言,剥注释必须是第一个动作,不是发现问题后的补救。

这里有个我踩过的坑值得单说:.input.ta 在 app.wxss 和 tool.wxss 各定义了一份。按 CSS 加载顺序,页面级后加载、静默覆盖全局。而我的检查用 .some()(任一匹配规则充分即放行),读到的是没生效的那一份 。把真正生效的那份改成 min-height: 0 做自证------没拦住。

"存在即通过"的 .some(),是有同名覆盖场景时的假绿源头。

3. 期望值必须来自独立来源

  • 日期计算:加 N 天再减 N 天,回到原日期(外部参照是数学,不是自己)。
  • 房贷/利息:与独立写出的闭式公式互证。
  • 条形码:校验位独立复算 (START_B + Σ(值 × 序号)) mod 103。
  • 农历范围:从行为探边界,不从常量读常量。

4. 否定式断言必须锚住替代者

js 复制代码
// 只写这一句,靶子被删后就永远成立
ok(!/lg__row/.test(wxml));

// 补上替代者,结构被整体删掉时它会红
ok(/lg__chips/.test(wxml) && /\.lg__chip[^{]*\{[^}]*flex:\s*1/.test(css));

五、自证本身也会骗你

这一节是整件事里最反直觉的部分:你用来证明"护栏有效"的那套脚本,自己也会假绿。

我当时写了一个自证器:往源码里注入一个已知缺陷,跑测试,看它是不是红了。脚本的判定条件只有一句:

js 复制代码
const caught = brokenRes.code !== 0;   // 退出码非 0 = 被拦下了

那一轮全量自证有 81 组,跑完报告显示"全部被拦下",很漂亮。

真相是:其中一组根本没跑起来------沙箱拦掉了子进程读文件,抛了 EPERM,exit=1。环境崩溃被记成了"缺陷被拦下"。

而它比"永远通过"更隐蔽:输出里那个 ✔,看上去正是我们想看到的。

发现方式也很偶然:报告里有一栏叫"拦下断言",那一栏躺着的是一句

text 复制代码
Error: EPERM: operation not permitted, open '...light.wxss'

一条错误信息当上了"断言名"。 判据就这么简单:

"拦下断言"应该是人话。出现异常堆栈,说明拦下它的不是断言,而是环境。

修法是把报告拆成三个数,每次一起看:

计数 含义
✔ 被拦下 真的被断言拦住了
!! 置换未生效 靶子没改到(锚点失效),本组不计入通过
⚠ 环境失败 脚本自己崩了,本组不计入通过

只报第一个数就是自欺。"通过数"是分子,还必须看分母和被排除数。

同样的道理,我一度用 grep -c "✔ 被拦下" 数出 81 个 ✔ 就收工了。但真正该看的是另外两栏:有几个 !! 置换未生效、几个 ⚠ 环境失败。

分子只有在分母和排除数都报出来时,才有意义。

六、把自证成本压到"人愿意跑"

自证有个现实问题:太慢就不会被执行。

全量自证 146 组,跑完 14 分钟。我给自己定的规矩是"改了哪个文件就重跑针对该文件的自证组"------但有一次我只跑了新增的那几组,没跑受影响的那一族。因为改一行文案,要等 14 分钟,人就会开始给自己找理由。

真正的修法不是"更自律",而是把复核做便宜。

死锚点体检:14 分钟 → 2 秒

自证场景靠"把源码里的某段文本替换成缺陷版本"来工作。锚点就是那段文本。

而我用的锚点,很多是整句中文(因为要锚住一段界面文案),比如:

js 复制代码
// 锚点
'...支持 {{calMinYear}}--{{calMaxYear}} 年,不联网。'

后来我把句末的「。」改成了「;」(要接一句纪念日的联网说明)------锚点失效。

String.replace 找不到目标时会静默返回原串,和"改成功了"长得一模一样。那两组从此验证不了任何东西,而且要跑满 14 分钟才会报出来。

所以我加了一个只做一件事的模式:不跑任何测试套件,只检查"每个补丁是否真的改变了内容"。

bash 复制代码
ANCHOR_ONLY=1 node verify-guard.js

141 组锚点,2 秒报完。报告另存,不覆盖全量结果。

只有当复核足够便宜,那条"改了文件就重跑"的规矩才会真的被"人"执行。

顺带一个免费的自证手段

想知道一条新检查到底能不能拦住老问题,不必手工造缺陷------用旧代码跑新检查:

bash 复制代码
git worktree add ../_old HEAD

把新写的检查指向 ../_old,如果它是绿的,说明这条检查对已知的历史缺陷毫无感知。

这是最便宜的一次反向验证:不需要设计缺陷,历史本身就是缺陷样本。

七、现在的账

这个项目是一个微信小程序:46 个日常工具 装在一个入口里。其中 43 个共用 pages/tool/tool?id=N 一个容器(按 id 分发、按元信息表渲染),另外 3 个拆成独立页------全屏时钟、手持弹幕 (需要真正的全屏,带导航栏就拿不到)和打光板 (需要独占 <camera> 原生组件,同一页只能有一个)。

判据可以抄:需要独占屏幕,或需要独占原生组件的功能,老老实实拆页。

现在的四层验证是这样分工的:

层 规模 管什么 明确管不到
静态检查 181 项 配置、路径、云函数、工具表字段、事件绑定、隐私权限、合规词、集合名 运行时行为、渲染结果
功能仿真 1366 条断言 mock wx 与云数据库后,页面方法的数据流 CSS 渲染、真机原生组件
属性测试 52 组不变量 往返、闭合、只有一条放行路径、fail-closed 视觉、交互手感
自证 146 组 上面三层是否真的会红 自证脚本自己的假设

把这张表拉直了看,结论很清楚:这四层加起来,也验不了"用户能不能看见"。

所以每次交付,我现在都会补一句覆盖边界:逻辑层验了什么、视觉层没验什么、需要你在真机上看一眼什么。

这不只是话术。用户收到的"已验证",和我心里的"逻辑已验证、视觉未验证",是两个不同的东西。

八、两条结论

第一,通过数是最容易被自己欺骗的指标。

443 条断言对"输入框塌了"这个问题的价值增量是 0。它们全在同一个维度上堆积,而缺陷在另一个维度。数量给了我虚假的安全感------数字一直涨,看起来越来越稳,但对盲区维度,从 0 到 443 的增量就是 0。

第二,护栏必须自证。

一条断言的价值,不在它写了什么,而在**"把它该拦的缺陷造回去,它会不会红"**。写完不造缺陷,你得到的不是一道防线,是一个看起来像防线的装饰。

写完这篇我又检查了一遍:那些看起来最专业的断言,恰恰是最没被验证过的------因为它们看起来不需要验证。


顺带一句:这个项目叫「肝弟工具箱」,46 个日常小工具收在一个入口里,不收费、不用登录、也没有广告。代码暂时没开源,还在整理。写这篇文章的时候它刚上线不久,上面这些坑全是开发和上线前后这一个月里踩的。

相关推荐
9i编程1 小时前
用 Trae 吃透掘金文章:从拉取 Demo 到跑起 Three.js 太阳能电场,AI 问出来的学习路径
人工智能·openai·ai编程
jlw90092 小时前
超声波自动转盘焊接机直销厂家
自动化
andrsted2 小时前
练题簿在线免费刷题:将资料、Word 和 PDF导入小程序练习
学习·微信小程序·小程序·pdf·word·学习方法
Eyfcom2 小时前
〖实战〗跨境报关单证自动化:XML 报文生成、PDF 套打与单一窗口同步上传的工程实现
xml·pdf·自动化·报关单证·清关自动化
jlw90092 小时前
超声波自动转盘焊接机哪家性价比高
自动化
ii_best3 小时前
ios手机开发安卓脚本开发软件[按键精灵教程]从「找最小值」到「Boss 最近刷新」再到「屏幕范围极值」
android·ios·智能手机·自动化·按键精灵
txc15 小时前
我用 AI 给两个人写了个微信小程序:共享清单、记账和纪念日
微信小程序
Ward的AI产品实战16 小时前
多个订阅接多个 Agent 怎么管?我从 CC-Switch 迁移到了开源 AI 网关 Magpie(上篇)
开源·ai编程
qinglongcn16 小时前
为什么 Agent 循环是 for{} 而不是 DAG——架构选择的深层原因
agent·ai编程