Lin's Blog - 学习
type: channel
link: https://blog.lin03.cn/category/study/
items: What I cannot create：AI 时代的工程真相
items: SKILL 是什么？如何使用
items: n8n 新手上路：从一无所知到跑出第一个自动化流程
items: Rails 中 N+1 问题的研究
items: Vibe Coding 中，如何提高开发效率
items: MySQL性能优化
items: JUC相关练习
items: 闲鱼详情页json
items: 关于浏览器传参丢失精度的问题的解决办法
items: Vue3实现瀑布流

What I cannot create：AI 时代的工程真相
type: item
date: 2026-03-02T13:05:00+08:00
description: What I cannot create, I do not understand以前我对费曼这句话的理解比较老派：代码你手敲不出来，你就是不懂但在今天这个大模型满天飞的环境下，似乎有了新的理解：如果你脑子里没有一张完整的业务闭环和工程蓝图，那你在 AI 面前其实就是在盲开产品层面的盲区：你连该让 AI 写什么都不知道很多人产生了一种错觉，觉得有了 AI，自己单枪匹马分分钟就能搓出一个商业级应用举个最直白的例子：你想做一个 AI SaaS 软件。但如果你自己都没有作为深度用户去体验过优秀的同类产品，你用 AI 生成出来的东西一定会极其可笑因为你对 SaaS 的业务流根本没有建立起 理解，所以你根本无法向 AI 下达正确的 创造 指令：你只知道让 AI 写个 登录页，却不知道让它写 新手指引，因为你不懂新用户注册后的第一步最容易流失你只知道让 AI 写个 调用大模型 API，却不知道让它设计 Token 计费池和并发限制，结果一上生产环境被人恶意一刷，账单当场爆炸你只知道让 AI 接入 Stripe 支付，却根本不懂怎么处理 Webhook 里的 订阅降级、退款和过期重试，因为你以为支付就是 付钱-开通 这两步，根本不理解 SaaS 订阅制的复杂状态机这时候费曼那句话就应验了：你自以为在 创造 一个产品，其实你只是用 AI 拼凑了一个根本跑不通商业逻辑的玩具工程层面的盲区：跑起来之后，灾难才真正开始退一步讲，就算你把产品逻辑理顺了，让 AI 去写代码。这事儿得看你怎么定义 搞定能 的部分大家都体会过了：AI 确实能把项目 从 0 到 0.6 的时间压缩得很短。你说一句搞个 FastAPI + React，它立马给你吐出一整套路由、组件、接口。一键跑起来，看着挺像那么回事但 不能 的部分，往往是从代码跑起来那一刻才真正开始知乎上有个老哥总结过一个特别典型的 链式翻车 惨案：用户连点两次提交，数据库里莫名其妙多了两条脏数据你让 AI 去修，AI 在前端加了个防抖你想着后端也得兜底啊，让它再加个唯一索引接着为了捕获异常给用户个友好提示，AI 改了返回结构——啪，顺手把其他接口的响应格式全搞崩了修了三轮之后，这项目就成屎山💩了，每次改动都在赌今天会不会踩雷。那位老哥有一句话说得极为精准：主流程只是 30%。AI 最擅长的就是让你产生 主流程跑通了就万事大吉 的错觉，而真正折磨人的，永远是剩下那 70% 的边缘分支和系统状态复杂项目难在约束与边界复杂项目难的从来不是代码量，而是 约束 与 权衡Nginx 团队的工程师(知乎老哥自称)做过一个很有意思的实验，让 AI 从零糊一个系统级的 C 项目（事件驱动、多线程那种）。结论非常贴近工程现实：AI 砌墙的速度贼快，但它手里根本没有图纸模块边界在哪？依赖方向怎么理？这些统统得靠人脑子去定这里面潜伏着一个极其致命的陷阱：看起来一切正常当你依赖同一个模型去生成实现代码和测试用例时，它们的盲区是完全重合的。测试用例照着错误的假设写，跑出来当然是一片绿灯，但这叫 虚假的成功，就是你 被 AI 骗了。 如果你没有跳出代码之外去 review 和推翻它的能力，你连自己是怎么掉坑里的都不知道再看个最近发生的例子。青龙面板最近的一个 Issue（#2933）就非常典型：因为鉴权逻辑对路径大小写处理不一致，配合 Express 默认的不敏感策略，像 /aPi/... 这种变体路径居然能直接绕过 JWT 校验，命中实际路由并打到高权限接口这种坑其实是个特别典型的工程问题。AI 在写接口时，通常只会考虑最理想的执行路径，只要正常请求能跑通就算大功告成。但真实业务里的路由策略、白名单限制、大小写处理，这些需要全局约束的地方，AI 根本照顾不到。它只覆盖 快乐路径（Happy Path），稍微变个形的异常请求，它根本想不到去防这句话放到今天到底指什么我现在觉得，它指的早就不是 你得手写每一行代码 了它的真正意思是：你必须具备对整个产品业务和系统工程的绝对掌控力你想靠 AI 搓出一个真正能上线的商业产品，你敢不敢在发布前回答这几个问题：产品与商业侧：你的核心业务闭环了吗？不管你做的是 AI SaaS 还是社区工具，新手指引、防刷策略和核心资源的消耗控制兜得住吗架构与工程侧：并发一旦上来，用户在核心链路（比如支付或调用大模型）断网了，你的重试、补偿、降级机制和事务边界做好了吗安全与权限侧：默认的接口暴露面有哪些？会不会被变体路径越权访问，甚至被人直接把你后台的高权限 API 拿去当肉鸡如果你答不上来，那你用 AI 堆出来的这个东西就算能跑，也只是 暂时没死 而已。AI 帮你写得越快，你欠的业务债和技术债就越多我现在怎么用 AIAI 该用还是得用，毕竟速度摆在那里。但我现在的心态彻底变了：我是这个产品的产品经理兼架构师，而 …

SKILL 是什么？如何使用
type: item
date: 2026-01-12T18:15:00+08:00
description: 最近看到一些关于 AI 的帖子，发现一个词开始频繁出现：SKILL一开始其实没太在意，直到我发现一件事：几乎所有提到 SKILL 的文章，都是默认你已经知道它是什么的但当我真的停下来想认真回答一句 「SKILL 到底是什么」 的时候，却发现很难给出一个干脆的答案。没有统一定义，也没有哪篇文章是从 为什么需要它 开始的于是我知道大家在用 SKILL，但我并不知道我 自己到底需不需要它为什么使用 SKILL如果只看能力层面，其实 Agent 和 MCP 已经很强了Agent 可以负责推理、规划步骤、决定做什么MCP 可以让模型以一种安全、结构化的方式调用外部能力目前来看，这已经是一个完整的流程了但问题出现在用了一段时间之后当我开始用 Agent 去处理一些长期且重复的事情时，会发现一些现象，比如：有些事情我已经让 Agent 做过很多次每次流程都差不多但 Agent 每次还是会从 0 开始理性上这当然没错，但实际用起来就会发现：有些事情并不值得每次都重新思考， 完全是一个流程接下来 SKILL 就登场了，它补的并不是像 agent mcp 的能力，而是使用方式SKILL 对比 MCP / Agent 的优势在我目前的理解里，SKILL 并不是在“能力”上压过 Agent 或 MCP，而是在职责上补了一层。AgentMcpSKILL负责判断要不要做、怎么拆步骤让模型拥有调用外部的能力负责把一件已经成熟的事情，封装成一个可以被反复调用的能力换句话说：Agent 解决的是「要不要做」MCP 解决的是「怎么调用」SKILL 解决的是「这件事有没有必要每次都重新想一遍」这里有一个很关键、但经常被忽略的点：SKILL 是按需加载、按需调用的，只有当某个场景真的需要它时，它才会被调用这带来的一个很现实的好处是：更省 token，也更干净你不需要把一整套固定流程、风格说明、约束条件反复塞进上下文里该用的时候用，不用的时候完全不存在快速上手这里我用一个现成的 SKILL 来做演示，选择的是这个用来快速生成博客草稿的 SKILL：https://skillsmp.com/zh/skills/nicknisi-dotfiles-home-claude-skills-blog-post-writer-skill-md下载 SKILL把文件夹或者单独的 SKILL.md 放入 .claude/skills 下面使用:我有一堆想法，准备写一篇博客，请用 Nick Nisi 那种风格帮我把它组织成一篇完整的文章： - 最近我在研究 AI 架构里的 SKILL - 看到很多文章默认大家都懂 - 其实我有点困惑 - 想写一篇总结性的博客在发送之后，你会看到 Claude 识别到当前上下文非常符合这个 SKILL 的使用场景，于是开始触发 SKILL 的调用流程。中间会有一段交互过程，大概会确认一些写作方向或细节，例如风格、结构之类的。当这些问题确认完成之后，Claude 会给出一篇完整、可读、已经帮你组织好结构的博客文章。甚至在这个基础上，你还可以继续让它调用其他的 MCP / Agent / SKILL，比如：做二次润色调整结构直接发布到博客系统下面是输出的完整内容 完整内容 Claude Code 中的 SKILL：我花了一周才搞懂的事"你只需要创建一个 SKILL 就行了。"我在文档里看到这句话的时候，内心是崩溃的。等等，我已经有 MCP 工具了，可以调用外部服务。我还有 agent，可以执行复杂的多步任务。现在你告诉我还有个 SKILL？为什么？## 我的困惑最近在深入研究 Claude Code 的架构。看了不少文章，发现大家都在说 SKILL 多好用，SKILL 多强大。但没人解释为什么我们需要它。我是说，我已经有了：MCP (Model Context Protocol): 连接外部工具和数据源Agent: 处理复杂的多步骤任务，可以自主决策那 SKILL 呢？它在这个生态里扮演什么角色？我翻了好几篇文章，都默认你已经懂了。我不懂。## 开始挖掘我做了大部分开发者都会做的事：打开文档，开始一个个翻。MCP 的定义很清楚：标准化的协议，让 Claude 能访问外部工具、数据库、API。想让 Claude 读取你的 Notion 数据？写个 MCP server。想让它查询你的 PostgreSQL？MCP。Agent 也说得过去：一个可以自主工作的实体，能分解任务、使用工具、做决策。需要 Claude 帮你做代码审查、提交 PR、运行测试？用 agent。但 SKILL 呢？文档说它是"可重用的工作流"、"封装的能力"、"用户可调用的命令"。好的，我还是不懂。## 那个瞬间直到我看到一个实际的例子。假设你每次让 Claude 总结对话时，都要说："请帮…

n8n 新手上路：从一无所知到跑出第一个自动化流程
type: item
date: 2025-11-27T14:47:00+08:00
description: AI 自动化这几年真的越来越重要了不管是处理重复任务、整理数据，还是用 AI 做一些智能化的流程，现在这些都能被自动化接管这是我第一次接触 n8n ，也是一无所知的新手，但在简单的了解过后，我已经跑通了一个完整的自动化工作流：从 RSS 读取今日热点 → AI 自动总结 → AI 自动制图 → 生成 HTML 日报页什么是 n8n如果你没用过 n8n，可以简单把它想象成开源的 Coze不同的是它：能自托管拖几个节点就能把各种 API 接起来和 AI 配合的时候真的很爽以前做自动化要写 Python / NodeJS，配定时任务，自己处理数据结构、容错之类的。现在很多东西拖一拖就完了n8n 的强大之处不仅在于它能做传统自动化（如备份、同步、发通知）而是在 AI 时代，它可以成为「调度大脑」它自己不做什么复杂逻辑，但非常擅长“把事情串起来”例如：使用 AI 进行复杂抓取与筛选多源 RSS / API / Webhook 信息聚合使用 AI 自动判断价值度对新闻进行 分类、比对、摘要、聚合自动过滤重复内容按重要程度排序，生成清晰结构化数据我的第一个流程：RSS → AI 总结 → AI 制图 → HTML 日报这是我第一次在 n8n 里动手做出的完整流程：从 RSS 获取热点 → AI 自动总结 → AI 自动生成日报图 → 生成 HTML 页面这里推荐一个持续跟进官方的 汉化版本 ，方便学习部署docker volume create n8n_data docker run -it --name n8n -p 5678:5678 -e N8N_RELEASE_TYPE=stable -e N8N_DIAGNOSTICS_ENABLED=false -e N8N_VERSION_NOTIFICATIONS_ENABLED=false -e N8N_HIDE_USAGE_PAGE=true -e N8N_LICENSE_AUTO_RENEW_ENABLED=false -e N8N_RUNNERS_ENABLED=true -e NODE_ENV=development -e N8N_DEFAULT_LOCALE=zh-CN -e N8N_ENTERPRISE_MOCK=true -v n8n_data:/home/node/.n8n ghcr.io/deluxebear/n8n:chs随后直接访问 http://localhost:5678 即可流程展示Webhook → RSS Read → AI Summary → AI Image → Markdown → HTML → Webhook Response整个流程非常直观，基本看一眼图就能理解整个 workflow 是如何工作的通过 Webhook 触发 RSS Read, 交给 AI 进行总结内容，随后将输出结果使用 AI 制作出图片,我这里使用的是 Nano Banana Pro 返回的是 Markdown 格式的 Base64,需要转换成 HTML 才能响应 Webhook，展示在前端。预览效果如下各节点分析Webhook（流程入口）这是整个流程的触发器, 只要访问 http://localhost:5678/webhook/xxxxx 就能出发整个流程的运行RSS Read这个节点负责获取信息源作为简单的项目演示，采用了 知乎每日新闻的 RSS https://plink.anyfeeder.com/zhihu/daily 运行返回结果如下[ { "title":"为什么古代中国没有其它文明那么多大型石砌建筑？", "link":"https://daily.zhihu.com/story/9785710", "pubDate":"Invalid Date", "content":" \n \n\n \n\n\n\n \n\n \n\n\n\n\n \n \n\n \n\n \n \nBrian C，有问题请私信加微信\n查看知乎原文\n \n\n \n 为什么古代中国没有其它文明那么多大型石砌建筑？ \n 因为任何大型建筑本质上就是两门科学“物理和数学”的应用。 \n 物理学上讲， \n 中国古代人无法把向下的力转化为水平的力， ...这些内容随后被送入 AI 做内容提炼与聚合AI 总结这一节点是整个工作流的核心我这里用的不是官方 OpenAI 接口，而是一个第三方聚合平台，所以 n8n 里直接用 HTTP Request 节点 来调用 AI。看起来很高端，其实流程非常简单：就是发一个接口，把 prompt 丢进去，让 AI 返回你想要的文本或是一些其他的如图片等拿到结果后，再用 n8n 的 Code 或 Set 节点稍微处理一下，就能继续用在下一步流程（例如生成 Ma…

Rails 中 N+1 问题的研究
type: item
date: 2025-10-25T15:01:00+08:00
description: 为什么 includes 让内存暴涨，而 SQL 子查询却如此高效 —— Rails N+1 查询的真相本文基于 Akshay Khot 的文章 和本地实验结果，深入剖析 Rails 中 N+1 查询的本质、为什么 includes 会消耗更多内存，以及 SQL 子查询（Subquery）在时间与空间上的取舍。🧩 什么是 N+1 查询在 Rails 开发中，N+1 查询是常见的性能陷阱。以 User 和 Post 两个模型为例：class User < ApplicationRecord has_many :posts end class Post < ApplicationRecord belongs_to :user end控制器中简单地读取文章列表：def index @posts = Post.order(created_at: :desc) end在视图中渲染每篇文章及其作者：<% @posts.each do |post| %> <%= post.title %> User：<%= post.user.name %> <% end %>这段代码背后会执行：一条查询读取所有 posts，随后对每个 post 再单独查询对应的 user。形成了“一条主查询 + N 条子查询”的模式，被称作 N+1 查询。当数据量较大时，多次数据库往返会显著拉长响应时间。⚙️ 性能分析工具：Rack Mini Profiler为了更好地观察这些查询带来的开销，我们使用 Rack Mini Profiler。在开发环境中安装并配置后，在浏览器 URL 后加 ?pp=profile-memory 可以看到页面顶部的性能面板，显示 SQL 查询次数、执行时间和内存占用等信息。🧪 实验环境与数据生成使用 Rails 8 和 SQLite 创建简化的 users、posts 表。每个用户生成 5 000 篇文章，共两名用户，以便观察不同查询方式的差异。⚖️ 三种方案的性能对比性能对比一览模式查询数页面加载时间SQL 总耗时内存分配Ruby 对象数N+1 查询10 0015.8 s146.3 ms5.9 KB86includes2188 ms56.7 ms455.4 KB3675子查询10 0015.7 s128.8 ms7.2 KB91🚨 方案一：N+1 查询在控制器中简单地 @posts = Post.order(created_at: :desc)，然后在视图中访问 post.user.name。这种方式代码简洁，但会导致每次访问关联字段都单独执行一次查询。截图： 下图展示了 N+1 查询在 Rack Mini Profiler 中的总览。可以看到页面渲染时间超过 5.8 秒，SQL 概述中显示查询次数达到 10001 条。内存分配： 另一张图给出了 N+1 查询的内存分配情况小结：简单但最慢——因为往返次数爆炸⚙️ 方案二：includes 预加载通过 includes 让 Rails 预先加载所有关联用户，减少数据库往返：@posts = Post.includes(:user).order(created_at: :desc)Rails 会先取出所有 posts，再批量查询这些 post.user 对象：截图： Rack Mini Profiler 显示 includes 方式只发起了 2 次查询，页面响应时间从 8 秒级降至约 188 毫秒内存占用： includes 虽然快了很多，但在内存里实例化了用户对象，仍有一定开销。测试中分配的内存为 455.4 kb，生成了 3675 个对象小结：用内存换查询次数。当仅需要某个字段时，把整批关联对象都载入内存，往往不划算。🧮 方案三：SQL 子查询（Subquery）第三种方式是将关联逻辑交给数据库，通过子查询直接把 users.name 字段放在 posts 查询结果中：subquery = User .select(:name) .where("users.id = posts.user_id") .limit(1) .to_sql @posts = Post .select("posts.*, (#{subquery}) AS author_name") .order(created_at: :desc)生成的 SQL 如下：SELECT posts.*, (SELECT users.name FROM users WHERE users.id = posts.user_id LIMIT 1) AS author_name FROM posts ORDER BY created_at DESC;理论上，这能减少 Rails 与数据库的往返和对象生成。但在测试环境中，每一条 post 的…

Vibe Coding 中，如何提高开发效率
type: item
date: 2025-09-27T20:20:56+08:00
description: Vibe Coding 中，如何提高开发效率随着 AI 技术的发展，越来越多的开发者开始尝试 Vibe Coding ——也就是把 AI 当作“结对编程的伙伴”，通过对话的方式完成代码编写、调试和优化一、现阶段主流 AI 工具对比目前我常用到的 AI 模型有 GPT-5、Gemini 2.5（Pro/Flash）以及 Claude 4.x。不同工具各有优势，适合的场景也不一样。工具优势适用场景GPT-5语言理解和推理能力极强，支持复杂的多轮对话和跨领域问题；适合生成计划以及修复 BUG复杂项目、跨栈开发、需要较高逻辑推理的任务Gemini 2.5 Pro上下文记忆更持久，长对话能力强，适合规划大型项目需要详细讨论和规划的长期开发Claude 4.x编码能力极强，且 UI 方面也非常不错。Plan 模式有助于结构化开发，代码逻辑清晰需要详细规划、文档和代码整合的场景补充：作为全栈开发，GPT-5 往往能提供更强的逻辑和上下文理解，利用其强大的理解能力，在配合上 Claude 的 Plan 模式。做到 先规划在执行 的场景，这样的开发会很流畅二、Vibe Coding 中常见问题在实际项目中利用 AI 开发，新手往往会遇到包括但不限于以下问题：1. Prompt 不清晰案例：我在 xxx 项目中想加一个团队管理功能，直接对 GPT-5 说“帮我做一个团队系统”，它给我生成了一个全新的 Team 模型和用户关联，但忽略了代码或者模板本身提供的如 Jumpstart 内置的组织/用户模型结构的功能。导致结果冲突很多，花了不少时间重构。重复造轮子2. 功能过于宽泛案例：我让 AI “帮我实现订阅功能”，它直接写了 Stripe 集成的控制器和视图，AI 可能会直接去帮你实现，但是确实按照了它的逻辑，如果你没有主动告诉 AI 你已经有部分实现的代码，那么大概率最终代码重复且冲突。3. 缺乏计划阶段案例：让 Claude 帮我“加一个发票模块”，Claude 写的代码看似完整，但路由、前端样式和 TailwindCSS 组件完全没有和前端模板对齐，导致的后果就是，同一个项目，像是不同的人在施工，参杂了各种冗余且复杂的代码。如果在一开始有计划的先让 AI 输出“发票模块所需的字段、数据库迁移、UI 组件和权限控制”，再进入编码，效率会更高。4. 上下文管理不当案例：在一次对话中不断添加新需求，比如“加多租户支持”“再加 API Key 管理”，但是没有维护一份需求文档。导致 GPT-5 忘记了我项目已经使用 Apartment gem 做多租户，后来又建议我用另外一种方案，冲突严重。三、解决思路与效率提升方法针对上述问题，总结出以下几条经验：1. 提升 Prompt 质量在 Vibe Coding 中，Prompt 就是需求说明书。AI 能输出什么样的结果，往往取决于你给它的输入。模糊的 Prompt 会让 AI 胡乱发挥，而一个精准的 Prompt 则能让 AI 像资深开发一样给出贴合需求的代码。Prompt 的作用明确边界：告诉 AI 该做什么、不该做什么，避免生成多余或冲突的代码。统一风格：通过 Prompt 说明项目用的框架、依赖和代码风格（例如 Rails 8 + Hotwire + TailwindCSS + Flowbite），让 AI 始终保持一致。减少返工：当 Prompt 已经涵盖关键细节，AI 生成的结果往往能直接使用，而不是需要开发者大规模重写。为什么好的 Prompt 能事半功倍降低沟通成本：把复杂需求转化成清晰的文字，AI 能一次性理解，大幅减少多轮交互。提高输出质量：明确的上下文信息，让 AI 生成的代码更可维护，避免“屎山代码”。提升团队效率：在多人协作时，标准化的 Prompt 能确保每个开发者和 AI 的交互保持一致，不会因为表达差异而导致风格混乱。优秀 Prompt 的参考资源在 GIthub 里，已经有不少优秀的 Prompt 示例可以直接拿来学习和套用：awesome-chatgpt-prompts 经典的 Prompt 收集库，涵盖开发、调试、代码审查等场景。prompt-engineering-for-developers 面向开发者的 Prompt 工程指南，详细解释了如何写高质量 Prompt，以及不同场景下的策略。高质量 Prompt 不仅告诉 AI 做什么，还明确了 如何做、用什么技术做，结果往往能直接落地，极大提升效率。2. 分解任务，逐步引导在 Vibe Coding 中，最容易犯的错误就是“一股脑把需求抛给 AI”。这样做的结果往往是：AI 给出一大段貌似完整的代码，但和你的项目框架、依赖不兼容，甚至出现“屎山代码”。解决办法有两个核心原则：任务拆解：把大需求分成小模块，逐步实现；计…

MySQL性能优化
type: item
date: 2025-07-12T22:50:21+08:00
description: 本篇文章只记录 索引优化 和 SQL优化索引优化有无索引对比准备 test 表CREATE TABLE `test` ( `id` bigint NOT NULL, `name` char(4) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;插入 100w 数据import pymysql import random import string import time # 数据库配置 (根据实际情况修改) DB_CONFIG = { 'host': 'localhost', 'user': 'your_username', 'password': 'your_password', 'database': 'your_database', 'charset': 'utf8mb4' } # 生成随机4字符名称 def random_name(): return ''.join(random.choices(string.ascii_letters + string.digits, k=4)) def insert_massive_data(): try: # 连接数据库 conn = pymysql.connect(**DB_CONFIG) cursor = conn.cursor() print("开始插入数据...") start_time = time.time() # 批量插入配置 batch_size = 5000 # 每批插入量 total_records = 1_000_000 batch_count = total_records // batch_size # 循环插入批次数据 for batch in range(batch_count): # 构建批量数据 data = [] for offset in range(batch_size): id_val = batch * batch_size + offset + 1 data.append((id_val, random_name())) # 执行批量插入 sql = "INSERT INTO test (id, name) VALUES (%s, %s)" cursor.executemany(sql, data) conn.commit() # 打印进度 if (batch + 1) % 10 == 0 or batch == 0: processed = (batch + 1) * batch_size print(f"已插入: {processed:,} 条, 进度: {processed/total_records:.1%}") # 插入剩余数据（如有） remaining = total_records % batch_size if remaining: data = [] start_id = total_records - remaining + 1 for i in range(remaining): data.append((start_id + i, random_name())) cursor.executemany(sql, data) conn.commit() print(f"最后一批插入完成: {remaining} 条") # 统计结果 duration = time.time() - start_time print(f"插入完成! 总共插入 {total_records:,} 条记录") print(f"耗时: {duration:.2f} 秒") print(f"平均速度: {total_records/duration:,.0f} 条/秒") except pymysql.Error as e: print(f"数据库错误: {e}") conn.rollback() finally: if conn: conn.close() if __name__ == "__main__": insert_massive_data()有索引情况无索引情况总结性能对比分析查询类型查询语句执行时间扫描行数​​有索引情况​​SELECT * FROM test WHERE id = 7894560.00s1​​无索引情况​​SELECT * FROM test WHERE name = 'abc'0.16s1,000,000关键发现​​索引的性能优势​​：​​数量级差异​​：索引查询仅扫描 1 行，而无索引查询扫描全部 100 万行​​时间差异​​：索引查询接近 0 秒(0.…

JUC相关练习
type: item
date: 2025-07-10T15:16:00+08:00
description: 此文章内容记录学习 JUC 时的各种问题问题虚假唤醒​​虚假唤醒​​是多线程编程中的一个典型问题，它是指线程在未被明确通知（如 signal() 或 signalAll()）的情况下从等待状态中唤醒。这可能导致程序在条件未满足时继续执行，从而引发逻辑错误为什么会发生虚假唤醒？​​底层操作系统机制​​操作系统底层的条件变量实现（如 POSIX 的 pthread_cond_wait()）允许在无显式信号时唤醒线程（通常出于性能考虑）。Java 作为跨平台语言无法完全避免这一问题。​​中断和超时机制​​当等待的线程被外部中断（interrupt()）或达到超时时间时，也可能意外唤醒。class SharedResource { private int item = 0; private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); // 生产者方法：非安全实现 public void produce() throws InterruptedException { lock.lock(); try { // 错误：使用if而非while检查条件 if (item != 0) { condition.await(); // 可能虚假唤醒后继续执行 } item++; System.out.println("生产: " + item); condition.signal(); } finally { lock.unlock(); } } // 消费者方法：非安全实现 public void consume() throws InterruptedException { lock.lock(); try { // 错误：使用if而非while检查条件 if (item == 0) { condition.await(); // 可能虚假唤醒后继续执行 } System.out.println("消费: " + item); item--; condition.signal(); } finally { lock.unlock(); } } public static void main(String[] args) { SharedResource share = new SharedResource(); for(int i=0; i<2; i++) { new Thread(() -> { for (int j = 0; j < 100; j++) { try { share.produce(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "Producer" + i).start(); } for(int i=0; i<2; i++) { new Thread(() -> { for (int j = 0; j < 100; j++) { try { share.consume(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "Consumer" + i).start(); } } }可以看到已经产生了虚假唤醒解决方案使用while代替ifclass SafeResource { private int value = 0; private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); // 安全的生产者 public void safeIncrement() throws InterruptedException { lock.lock(); try { // 修复：使用while循环防御虚假唤醒 while (value != 0) { condition.await(); } value++; System.out.println(Thread.currentThread().getName() + " 安全生产后: " + value); condition.signalAll(); } finally { lock.unlock(); } } // 安全的消费者 public void safeDecrement() throws InterruptedException { lock.lock(); try { // 修复：使用while循环防御虚假唤醒 while (value != 1) …

闲鱼详情页json
type: item
date: 2025-06-11T12:42:00+08:00
description: 最近看到某论坛上有关闲鱼获取详细的帖子，便有了写个脚本的想法闲鱼h5的研究可以看到咸鱼的这个这个接口便是对应的数据url: https://h5api.m.goofish.com/h5/mtop.taobao.idle.pc.detail/1.0/param: jsv=2.7.2&appKey=34839810&t=1749660271468&sign=d99215598a87fc030a45a608269f6906&v=1.0&type=originaljson&accountSite=xianyu&dataType=json&timeout=20000&api=mtop.taobao.idle.pc.detail&sessionOption=AutoLoginOnly&spm_cnt=a21ybx.item.0.0data: {"itemId":"859199211604"}cookie: ..._m_h5_tk=xxx; _m_h5_tk_enc=xxx经过我多次尝试，只有t,sign,_m_h5_tk,_m_h5_tk_enc是变化的，现在目标就很明确了编码实现t为13位时间戳，而经过查阅资料可以分析出，token: _m_h5_tk sign：token&t&appKey&data,然后取MD5_m_h5_tk会有过期时间，通过开发者工具，这个地址https://h5api.m.goofish.com/h5/mtop.gaia.nodejs.gaia.idle.data.gw.v2.index.get/1.0/在首次访问时候，在header里面的set-cookie能找到_m_h5_tk,_m_h5_tk_enc，而首次访问所携带的token必为空，也就是说，计算sign只需要t和data即可代码实现def _get_m_h5_tk(self) -> list: """获取API认证所需的_m_h5_tk令牌""" # 构建一个简单的请求数据和参数 data = '{"piUrl":"https://h5.m.goofish.com/wow/moyu/moyu-project/xy-site/pages/announcement"}' # 生成时间戳 t = str(round(time.time() * 1000)) token = "" appKey = "34839810" # token + & + t + & + appKey + & + data_str sign_string = f"{token}&{t}&{appKey}&{data}" sign = md5(sign_string.encode()).hexdigest() params = { 'jsv': "2.7.2", 'appKey': "34839810", "t": t, "sign": sign, 'api': 'mtop.gaia.nodejs.gaia.idle.data.gw.v2.index.get', 'v': '1.0', "type": "originaljson", "accountSite": "xianyu", "dataType": "json", "timeout": "20000", "sessionOption": "AutoLoginOnly", "spm_cnt": "a21ybx.item.0.0", } # API请求地址 api = 'https://h5api.m.goofish.com/h5/mtop.gaia.nodejs.gaia.idle.data.gw.v2.index.get/1.0/' response = requests.get(api, params=params) # 从响应cookies中提取_m_h5_tk cookies = response.cookies _m_h5_tk = cookies.get('_m_h5_tk', '') _m_h5_tk_enc = cookies.get('_m_h5_tk_enc', '') return [_m_h5_tk, _m_h5_tk_enc]['08c31977d09c9c8c610114029b8e17a5_1749669468863', 'b8d02be3c6b0ee2a0f5a21d8c7023b93']对应的获取商品详细也能写出来了def get_info(self, url: str): """ 获取闲鱼商品信息 """ itemId = self._get_itemId(url) if itemId is None: logging.error("获取商品ID失败") return None cookies…

关于浏览器传参丢失精度的问题的解决办法
type: item
date: 2025-05-27T20:37:00+08:00
description: 今天在写项目时候，需要前端传一个 number 类型的参数，可能由于类型过长，url接收时候直接丢失了精度，导致了一系列的错误前端源码const doEdit = () => { console.log(p.value.id) router.push({ path: '/add', query: { id: p.value.id }, }) }这里用控制台打印了 p.value.id 来确认是否是后端传参前端接收时丢失了精度，但是控制台打印是正常的结果 1927331787680264194, 但是看浏览器重重定向的地址为 http://localhost:8081/api/get/vo?id=1927331787680264200 明显是这里出现了问题问题出在 JavaScript 的数值精度限制上。JavaScript 使用 IEEE 754 双精度浮点数表示数字，最大可精确表示的整数是 2^53 - 1（约 16 位数字或 ±9007199254740991）所以出现了精度丢失的问题解决方案解决问题很简单 String(p.value.id) 只需把高精度的数字转换成 string 类型传入即可

Vue3实现瀑布流
type: item
date: 2025-05-22T20:04:00+08:00
description: vue使用 vue-waterfall-plugin-next 实现瀑布流Vue3瀑布流插件支持PC和移动端，包含Animate的各种动画效果以及图片懒加载功能。根据文档，可以先写出这样的一个例子 <Waterfall ref="waterfall" :list="list" :breakpoints="breakpoints"> <LazyImg class="card_img" :url="url" /> </Waterfall> 跨域问题文档提供的是本地图片，再用远程图片加载时候，不出意外地会出现跨域问题Access to image at 'https://emample.com/1.png' from origin 'http://localhost:5173' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. 仔细研究文档，会看到 Props参数 里面有个跨域的参数参数名类型默认值描述crossOriginBooleantrue图片加载是否开启跨域把默认值修改成 false 即可，即 <Waterfall :crossOrigin="false">