博文

ESP32 + 数码管

图片
  感觉自己现在强的可怕🙈🙈🙈另外,指甲挺疼的……

AI 辅助编程有感

我想写这个内容很久了,但是迟迟不知道该站在何种立场上来写。是凸显自己如何精于 AI 结对编程,提升了 XXX 的效率,创造了 XXX 的价值?是嘲笑现在的 AI 大跃进,批判资本家穷途末路不得不 AI 造神,终将被海量的技术债务反噬?还是假装理性,说一些模棱两可、似是而非的废话,来确保无论 AI 未来是陷入瓶颈还是再次爆发潜能,我的文字都能自圆其说?非常纠结,而且心态实时随着 AI 的产出内容而摇摆不定——它出乎意料的智能,我就开始疯狂地焦虑,觉得程序员的末日要来了;它出乎意料的愚蠢,我就暗自得意,AI 总归还是个辅助工具嘛~ 想来想去,我就只写史实吧,只当是记录一下自己的 AI 编程经历和心得。现在这个年代,不用 AI 生成和润色的长篇文字,已经可以算得上珍贵的记录了。 2022 年 12 月,我第一次听到 ChatGPT,试用了一下觉得很愚蠢。12 月 9 号的下午,我还发了朋友圈嘲笑它的回答,“一本正经的胡说八道,难怪被 Stack Overflow 给禁了”。 2023 年初,Copilot 横空出世,那时候还是完全免费的。尝鲜可以,但真正在项目开发里用起来只能说优劣参半吧!思考的速度有时候会比人慢,在人开始敲代码的时候又突然给出提示,反而形成干扰。夏天的时候知道它要开始收费了,就卸载掉了。 2024 年 4 月,对国产的模型及编程工具实在失望,正式成为 Copilot 的付费用户,时至今日,它依然是我心目中最好用的代码助手。还是很怀念那段时光的,AI 就静静的辅助我编程,不会喧宾夺主(agent 模式在我看来有点吵闹了),也没有那些“使用 AI 就一定要提高多少多少效率”的外界压力。 2025 年 2 月,DeepSeek 的深度思考能力震撼到了我。2 月 5 号那天,我和我老公的聊天记录里充满了对 DeepSeek 的崇拜和对 ChatGPT 的鄙视,哈哈。 2025 年 6 月,Grok 进入我的视野,这是第一个让我和 AI 聊天聊上瘾的模型。它的能力中规中矩,但是免费且速度快,所以性价比在我这里排名第一,我也多次和同事好友推荐。它同时也是第一个让我用提示词来创建定时任务的模型,虽然效果一般,但是在当时看来,这个能力还是挺有趣的。 等到了 2025 年年底的时候,画风就开始渐渐转变了。此前,AI 一直是我的好帮手,可以帮我省去无聊的工作,可以帮...

小记

我始终苦恼于【UI 设计稿 → 前端样式代码】这一步无法 AI 化,图像识别不精准、语言描述很匮乏。直到今天我突然顿悟了,设计稿可以转成 .html 文件啊!真不错,感觉离自己被 AI 完全替换掉又近了一步呢😂

部署 OpenClaw + 飞书协同

图片
  一、目标: 1. 公司内部 Windows 服务器上部署 OpenClaw 2. 集成任意模型,免费的最好 3. 对接飞书机器人 二、用时: 整整一天😪 三、成果: 四、归纳流程: 1. 前期准备:服务器上的可用工具少到令人发指,中途不得不多次暂停以解决如下这些问题。 ① 配置代理:OpenClaw 有依赖需要从 GitHub 上拉代码,所以必须要配置代理,不然无论是 install.cmd 还是 npm 的方式,都会失败。 ② 安装 node 和 git:重要性无需多言。 ③ 配置 WSL 内存:服务器上的 docker 运行在 WSL 环境里,之前分配的内存太少了(半个月前,服务器故障,频繁自动关机,当时以为是 docker 太耗内存了,所以修改了配置,结果后来发现是硬件故障,可是这个配置就没有再改回来),运行不了本地大模型。 2. docker 安装 ollama docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama 3. 拉个免费的通义千问模型 docker exec -it ollama ollama run qwen3.5:2b 4. npm 安装 OpenClaw npm install -g openclaw@latest 5. 配置 OpenClaw: ① provider 那一步选择 vLLM,实测 Qwen provider 验证可能失败,通过 vLLM 以 OpenAI 兼容模式调用阿里云 API 更稳定; ② 如果选择基于 ollama 的模型,base url 写  http://localhost:11434/v1 ,api key 随便填; ③ 因为免费模型速度实在太慢了,我后面又增加配置了 qwen-plus 模型,base url 写  https://dashscope.aliyuncs.com/compatible-mode/v1 ; ④ 我原本还想接 claude 模型来着,中转平台实在不给力,屡次验证失败,最终只能遗憾放弃。 6. 协同飞书: ① 创建飞书机器人:进入 飞书开放平台 ,创建企业自建应用 → 添加机器人 → 配...

iPhone 科学上网

图片
换了新手机以后最苦恼的就是如何重新下载 Shadowrocket,风控越来越严格,折腾废了 3 个账号,终于在农历的大年初一,我又可以看见外面的花花世界了哈哈哈😉 1. Mac 上注册新的 Apple 账户:使用全新的手机号和邮箱,地区选择美国。这一步成功率非常高,几乎没有失败过;不放心的话,可以开美国节点的代理,Mac 里的地区也设置成美国,Chrome 设置语言为英文。 2. iPhone 上登录该账户: ① 重置 App Store:这一步卡了我好久啊😭每次一切换账号,美区就变成国区,快被逼疯了。具体方法就是在 Safari 中打开链接(itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/resetAndRedirect?dsf=143441&cc=us),自动跳转至 App Store,完成重置和重定向。 ② 登录账号:没什么好说的,简单确认下还在美区就可以了。 3. 下载 Shadowrocket: ① 购买 App Store 礼品卡:我尝试过无数次按照常规流程绑定 PayPal,最终都宣告失败了,后面还是决定采取更稳妥的充值礼品卡的方式。选用的是 Pockyt Shop(https://shop.pockyt.io/pc/home),充值了 $3,获取到礼品卡号后转至 App Store 中进行兑换。 ② 下载软件:点击“获取”,会弹窗提示填写账单地址,我是找了一个【美国随机地址生成器】,这一步也没什么难度。 4. 胜利✌

Claude Code Skills 实践

最近 agent skills 这个名词很火啊,怎么能不掺和一下呢? 从日常开发工作中挑选了一个非常流程化的场景——根据后端提供的 swagger 文档,在前端项目中写入所需接口及数据类型——进行试验。 简易的 Skills 本质上就是放在 .claude/commands/ 目录下的 Markdown 文件。文件名就是命令名,文件内容就是提示词。这次选中的场景比较简单,提示词自然也不复杂,借助 AI 用了半个小时就写好并测试通过了。大概包含了如下几块内容: 1. 核心流程: ① 用户输入:定义用户需要提供的内容,包含环境和所需接口 url; ② 数据获取:提示 AI 通过 curl 命令获取对应环境下的 swagger 文档; ③ 信息提取:给出示例脚本,告知 AI 需要获取的字段信息,例如后端定义的接口方法名称、HTTP 方法、请求参数、响应类型等; ④ 命名规则:强制 AI 使用提取到的字段信息组成方法名、类型名,确保前后端命名一致; ⑤ 类型转换:针对特殊字段进行特别的处理,例如 id 字段往往被后端声明为 Long 类型,而后在 VO 类的字段级别借助 @JsonSerialize 注解进行转换,以字符串的格式传给前端,故而需要特别定义来覆盖 swagger 文档中读取到的类型; ⑥ 冲突处理:例如出现重复命名时,必须中断并询问客户; 2. 代码生成位置: ① 接口方法代码存放位置; ② 接口类型代码存放位置; 3. 代码模板:给出几个典型案例; 4. 用户额外需求占位符 - $ARGUMENTS。 流程跑通后,选择了一个接口(总计需增加 32 行代码)进行耗时测试: 1. AI 生成代码: ① 编写 prompt:只包含 skill 命令 + 环境名 + 接口名,用时几乎可以忽略不计; ② AI 思考并编写代码:2 分 43 秒,期间多次申请操作权限,使用者需要持续在场响应; ③ 人工检查生成的代码:49 秒。 2. 人工开发代码:2 分 36 秒。当然,这其中是有 Copilot code completion 的功劳的。即使禁用了 AI,我也习惯借助  typeof-jsonc 等插件帮助写入类型字段,总体耗时差距不会太大。 以我目前对 AI 的理解,它能够大幅提效的主要场景还是集中在: 1. 自己不熟悉的领域...

从 prop drilling 的泥潭到请求共享的天堂

图片
亲爱的朋友,你有遇到过这样的场景吗? 1. 希望严格遵循 React 哲学、维护单向数据流,于是状态提升提升再提升,提升到最后发现这个状态值似乎和当下组件没什么逻辑关联,只是它凑巧了,是共用这个状态值的那两个远房孙组件最近的共同父组件; 2. 写出一版提测了,产品经理指着页面说,“还是把这个统计数据移到上面那块展示吧,排版更好看一点,不难吧?”于是吭哧吭哧重写了组件树中所有被牵涉到的组件的 props,加班加点荣获了「效率低下」的美名,代码也被评价为「扩展性太差」; 3. 眼看着层层的 prop drilling 太反人类了,想想干脆祭出 useContext 吧,可转念又觉得大材小用、加剧耦合、倍感不值,只能背地里吐槽产品的设计毫无章法、后端的接口乱组数据,硬着头皮做他们的粘合剂; 4. 下定决心不能再被 best practice 捆绑了,放飞自我,就是天王老子来了也得是在直接使用到数据的组件里调用获取数据的接口,但是后端赶在天王老子之前来了,“你这一个页面里怎么能同时调用两次接口呢?会不会写前端啊?知不知道服务器压力有多大啊?” 前端已死,都去报名学习 AI 吧……呃,不是🚫 经人指点,在项目中封装了一个 Hook -- useSharedRequest,顾名思义,就是方便在多个组件间共享接口请求,但共享的不是接口返回的数据,而是这个请求的 promise。该 Hook 基于 ahooks 库的 useRequest 封装而来,写法完全一致,只需要在配置项中增加一个 shareKey 作为请求的唯一标识,就可以实现: 1. 在直接使用到数据的组件里调用获取数据的接口,保证组件逻辑内敛; 2. 同一页面多个组件调用相同接口时,只发送一次真实的网络请求,减轻后端压力; 3. 改造和维护成本极低,不管是接口替换还是组件层级变化,都不会牵扯到大面积修改。 基础用法如下图所示: 详细来讲, 1. 定义一个请求共享管理器类 RequestShareManager。 2. 在 RequestShareManager 中声明一个 Map<shareKey, Promise> 类型的私有属性 pendingRequests,用于存储正在进行中的请求。 3. 在 RequestShareManager 中实现...

Web 应用 ---> 桌面应用

图片
目标安装机器是储能柜的 EMS 触控一体机,核心需求是:低内存、少空间、高性能。于是选定了 Tauri 框架进行构建。 大致步骤如下: 1. 安装 Rust 及配套工具链(下载速度慢的话可以考虑换国内镜像) 2. 安装 Visual Studio Build Tools(在 Rust 编译时要用到) 3. 安装 tauri-app 依赖 4. 执行 tauri init 命令进行配置初始化 5. 执行 tauri dev 命令启动开发模式 6. 执行 tauri build 命令生成桌面应用安装包 最后,安装包 3.7M,应用程序 7.1M,运行时内存占用 3.6M,基本满足需求😏 除了前两步卡住了一下,其它流程整体上还是 love and peace,但我有种不详的预感:我只在自己 Windows 系统的电脑上跑通了,但是据说真实的安装环境是 Linux 的😥可能后期跨平台构建和样式兼容的工作量要远大于现在吧?

浅试 Three.js 绘制储能柜

图片
话不多说,直接上效果图: 从 0 创建到 1 还是很容易的,从 1 优化到 100 就很耗时了。动画的流畅度,交互的灵敏度,边缘情况的特殊处理,每一步都是个坎儿啊!很遗憾 resize 的部分纠缠了很久也没有处理好,但其余的效果都达到了我的目标值。技能点 + 1 ~~~

部分 AI 热门名词归纳

图片
  偷得浮生半日闲,画了一个思维导图,把一些 AI 相关的热门概念理一理。当然了,AI 的知识浩如烟海,我了解到的也只是沧海一粟,权当一个简陋的备忘录吧!

接口使用情况查询工具

图片
 这周还算清闲,做了一个【接口使用情况】的查询工具,主要是应对后端和测试总时不时来问“这个接口前端用到了吗”“那个接口在哪里调用的啊”……忙到飞起的时候真的会顾不过来😒 页面很简单,列表页主要负责基础查询,可以了解指定接口是否有被前端调用,以及是在哪些系统里被调用的;详情弹窗则负责更进一步的查询,可以详细看到使用该接口的菜单,方便用户前往核查。 后端是用 node 搭建的,对接代码仓库的 API,实时读取前端各项目的源码文件,进而借助正则匹配和 AST 解析来提取接口信息,并查找使用接口的文件,根据文件路径匹配到菜单信息,返回给前端。 实现这个工具主要的难点在于源代码的精准解析,前置的工作很多,几个系统有关接口调用的部分我几乎都重构了一遍,就是为了保证代码格式尽可能的统一;还有的系统因为各种历史原因,存在大量的未使用但也未移除的代码,导致我不得不又写了几个脚本文件,检测未被引用的接口、类型和常量,删除干净以后整个人都神清气爽了。即便如此,解析源代码也需要额外兼容很多的情况,比如微信小程序和 web 项目工程结构的差异、动态路由的拼接、不同后端服务的分辨,以及花了好大力气才排查出来并发读取源文件时可能有丢包的情况(我一直当是自己解析文件的代码哪里写错了😓😓) 总之,这个工具已经运转起来了,满足了同事们简单场景下的查询需求,稍微复杂点的场景肯定还是远远不够用的啦!静待同事的随时召唤🙇

浅试 MCP

图片
  Copilot 非复吴下阿蒙。效果可以说非常惊艳了,大概只用了三个小时的时间,就把流程跑通了。但是 AI 时代,0-1 是最简单的,1 之后的工作可就难讲咯! 附一张概念图,实操一遍后,理解更深刻了:

Creating a Nice Kitty with AI

图片
A cute cartoon cat co-created with GitHub Copilot (Claude 3.7 Sonnet), where I played the role of the human co-pilot😏 Feel free to check out the live demo at https://nice-kitty.vercel.app/ and explore the code at https://github.com/huangyuting-lab/nice-kitty to see how it's implemented. Plus, turns out AI is still only good at creative tasks with high error tolerance. Guess I won't be out of a job just yet!

一些抱怨

写一个巨长巨臭又要求各种智能回显的表单,真的写到崩溃…… React 的状态更新为什么要异步执行呢?就算异步,提供一个 async 版的 setState 不行吗?就算不提供,哪怕给个传回调函数的机会呢?非要借助 useEffect,逻辑越是混乱的地方,越容易误触 useEffect,这个 api 到底有什么好啊?只想精准控制,就要设置一个和业务毫无关系的 flag state,不脏吗? 每到这个时候就会怀念起 Vue 了😓响应式状态同步更新,顺序执行的逻辑代码耦合紧密,遇到实在复杂的场景还可以用 nextTick 打个补丁。React 写到走投无路的时候就只能重构了,看着那个六百多行怎么拆都拆不开的表单真是想死的心都有了…… 留下个场景记录吧!也许哪天变厉害了,回头再来看这个场景,就知道该怎么改了吧! 1. 调用接口获取一组文件的地址; 2. useState 更新 fileParserList,isParsingList,parseResultList 的状态,将值设为 Array.from({ length : fileUrlList.length }, () => initialValue ); 3. 并发执行文件解析,Promise.all(fileUrlList.map((url, index) => parseFile(index, url))); 4. 在 parseFile 函数中实现对应文件的 fileParser、isParsing、parseResult 的更新。

ProFormList 包裹的表单项使用了 transform 属性导致的无法正常更新的 bug

图片
被坑了两次,每次费半天力气 debug 到最后发现都是这个问题导致的,甚至开发历程都是一模一样,我真的累…… 时间线: 1. 封装了一个上传 csv 的组件 / 上传图片的组件; 2. 考虑借助 transform 属性实现统一的值转化,格式大概如下: 3. 数月后,产品经理提出扩展需求,字段数组化; 4. 直接使用 <ProFormList /> 包裹; 5. 发现字段修改后无法触发正常的更新; 6. 开启漫长的 debug 历程…… 7. 最后发现 namepath 取值为 ProFormList 的 name,而非实际字段的 name,卒。 解决方案:

如何解决 Visual Studio Code Extensions 卡在 Installing 而无法完成安装的问题?

图片
 1. 点击这里找到 Extension 文件所在目录。 2. 手动删除这个 Extension 目录。 3. 重启 VS Code。 4. 在插件市场搜索这个 Extension。 5. 下载 VSIX 文件。 6. 安装 VSIX 文件。 7. 视情况勾选 Auto Update。

一行代码实现通用化柯里函数

图片
  啊!我悟到了……

记录一下对于原型的理解

图片
  可以简单记忆: __proto__是每个对象都有的属性; prototype是函数才有的属性。 当然,会存在一些奇奇怪怪的例外。

对于闭包的理解

图片
  一 、变量提升 1. JS代码运行机制:分段预编译 → 逐行执行。 2. 预编译阶段可能存在【变量提升】的情况,将【变量声明的语句】提升至作用域顶端(不包括【变量赋值的语句】)。 3. var声明变量时,变量自动初始化为undefined;const和let声明变量时,变量不会自动初始化,只有在代码执行阶段,遇到赋值操作时,才会被初始化。 4. function声明函数时,整个函数体得到提升。 5. class声明类时,不存在提升。 二 、作用域 1. 全局作用域 2. 函数作用域 3. 块级作用域:{...}大括号内的代码块    ① 只有使用const和let声明变量时,会针对这个变量形成一个封闭的块级作用域。    ② 声明变量前访问该变量,会提示ReferenceError(块级作用域外 !!! 更正: 这里也属于块级作用域内 );声明变量后访问该变量,可正常运行(块级作用域内)。    ③ 暂时性死区,temporal dead zone:起始于函数开始 ( !!! 更正: 起始于块级作用域开始,比如if的{} ) ,终止于相关变量声明。 三、调用栈 1. 作用域是在预编译阶段确定的;作用域链是在函数调用后,执行上下文创建后,才确定的。 2. 执行上下文包括:变量对象、作用域链、this。 3. 调用栈:管理执行上下文的栈(管理函数调用关系的栈)。 4. 函数被调用 → 入栈 → 函数执行 (可访问其内部变量) → 函数执行完毕 → 出栈 (该函数的执行上下文被销毁) 四、闭包 1. 嵌套函数中,内层函数引用了外层函数作用域下的变量。 2. 外层函数执行结束,上下文被销毁,但被内层函数引用的变量不会消失。 3. 开发者仍可通过调用内层函数来访问外层函数中的该变量。 4. 内层函数及其捆绑的外层函数变量的引用,被称为闭包。 五、内存管理 1. 内存空间 = 栈空间 + 堆空间 2. 基本类型数据保存在栈空间中;引用类型数据保存在堆空间中,其地址值保存在栈空间中。 3. 注意避免内存泄漏。    常见情况:addEventListener - removeEventListener、setInterval - clearInterval。 4. 闭包使用不当,易引发内存...

对于this指向性的理解

图片
  1. 简单地调用函数时,    ① 严格模式下,函数内的this会被绑定到undefined上。    ② 非严格模式下,函数内的this会被绑定到全局对象window/global上。        2. 通过上下文对象调用函数时,函数内的this会被绑定到该对象上。    this指向的是最后调用它的对象。    一道面试题。    如果需要o2.fn()返回'o2',且不使用bind/call/apply呢? 3. 通过bind/call/apply方法调用函数时,函数内的this会被绑定到指定参数的对象上。    一道面试题。 4. 使用new方法调用构造函数时,构造函数内的this会被绑定到新创建的对象上。    如果构造函数中显式地返回了一个值,    ① 如果该值是一个引用类型,那么直接返回该值。    ② 如果该值是一个基本类型,那么正常返回实例化对象。 5. 箭头函数中,this的指向由外层作用域决定。 6. this的优先级问题    ① 显式绑定:bind/call/apply/new对this进行直接绑定。(优先级高)    ② 隐式绑定:根据调用关系确定this的指向。(优先级低)    new的优先级高于bind。 7. 面试题:如何实现一个bind函数? 8. 面试题:如何实现一个apply函数? 至此,我已经被完全绕晕了,大概只能自我安慰下,“答题的准确率不重要,解决问题的思路更重要,大不了就打印下吧!”【完】