<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>崔旭的笔记</title><description>记录工作学习点滴</description><link>https://blog.cuixu.cn/</link><language>zh_CN</language><item><title>JSDoc vs TypeScript 选型笔记</title><link>https://blog.cuixu.cn/posts/javascript/jsdoc-vs-typescript/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/javascript/jsdoc-vs-typescript/</guid><description>下面是一份JSDoc vs TypeScript 选型笔记,聚焦纯 JS（如 Express）团队的实际取舍与落地。</description><pubDate>Tue, 11 Nov 2025 09:32:20 GMT</pubDate><content:encoded>&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;小而快、少改造 → 用 JSDoc&lt;/strong&gt;：零编译、成本低，智能提示+基本类型检查够用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多人协作、复杂模型、长期演进 → 上 TypeScript&lt;/strong&gt;：强约束、类型表达力强、生态支持完整。&lt;/li&gt;
&lt;li&gt;还不确定？&lt;strong&gt;先用 JSDoc“铺轨”，4 周后复盘&lt;/strong&gt;：若出现类型灰区/重构痛点，再切 TS。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;两条路线各自的价值&lt;/h1&gt;
&lt;h2&gt;JSDoc（纯 JS + 注释）&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;✅ 零构建成本：不改语法，直接运行。&lt;/li&gt;
&lt;li&gt;✅ 渐进引入：从公共 API 开始补，风险低。&lt;/li&gt;
&lt;li&gt;✅ 编辑器体验好：配合 &lt;code&gt;checkJs&lt;/code&gt;、&lt;code&gt;@types/*&lt;/code&gt; 即可。&lt;/li&gt;
&lt;li&gt;⚠️ 约束力弱：注释易缺失，无法“强制全覆盖”。&lt;/li&gt;
&lt;li&gt;⚠️ 复杂类型表达受限：高级泛型/条件/映射类型较难。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;最适合&lt;/strong&gt;：中小型服务、内部应用、追求快和轻、不想上编译链的项目。&lt;/p&gt;
&lt;h2&gt;TypeScript（.ts 编译）&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;✅ 强约束：类型写进语法，覆盖更彻底。&lt;/li&gt;
&lt;li&gt;✅ 表达力强：条件/映射/模板字面量等高级类型。&lt;/li&gt;
&lt;li&gt;✅ 生态一等支持：工具链、生成 &lt;code&gt;.d.ts&lt;/code&gt;、SDK/库发布友好。&lt;/li&gt;
&lt;li&gt;⚠️ 需要编译：构建链与 CI 复杂度提升。&lt;/li&gt;
&lt;li&gt;⚠️ 渐进成本：历史代码迁移需要计划与时间。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;最适合&lt;/strong&gt;：多人协作、复杂领域模型、公共库/SDK、长期维护和频繁重构的项目。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;适用场景对比（速查表）&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;维度&lt;/th&gt;
&lt;th&gt;JSDoc&lt;/th&gt;
&lt;th&gt;TypeScript&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;上手/改造成本&lt;/td&gt;
&lt;td&gt;低&lt;/td&gt;
&lt;td&gt;中-高&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;类型严格度&lt;/td&gt;
&lt;td&gt;中&lt;/td&gt;
&lt;td&gt;高（可设 &lt;code&gt;strict&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;复杂类型建模&lt;/td&gt;
&lt;td&gt;限制较多&lt;/td&gt;
&lt;td&gt;很强&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;团队协作/重构&lt;/td&gt;
&lt;td&gt;基本满足&lt;/td&gt;
&lt;td&gt;最佳&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;发布库/SDK&lt;/td&gt;
&lt;td&gt;勉强可行&lt;/td&gt;
&lt;td&gt;一等公民&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;运行/构建复杂度&lt;/td&gt;
&lt;td&gt;低&lt;/td&gt;
&lt;td&gt;中-高&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;演进与长远性&lt;/td&gt;
&lt;td&gt;中&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h1&gt;决策树（按“是/否”走）&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;是否需要强约束（类型缺失视为错误）？&lt;/strong&gt;
是 → 倾向 &lt;strong&gt;TS&lt;/strong&gt;；否 → 2&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;是否存在复杂领域模型/广泛复用的内部包？&lt;/strong&gt;
是 → &lt;strong&gt;TS&lt;/strong&gt;；否 → 3&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;是否强烈希望零编译、保留纯 JS 运行链路？&lt;/strong&gt;
是 → &lt;strong&gt;JSDoc&lt;/strong&gt;；否 → 4&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;团队对 TS 是否有经验/意愿维护构建？&lt;/strong&gt;
有 → &lt;strong&gt;TS&lt;/strong&gt;；无 → 先 &lt;strong&gt;JSDoc&lt;/strong&gt;，后评估升级&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h1&gt;最小可用配置（落地模板）&lt;/h1&gt;
&lt;h2&gt;路线 A：JSDoc（纯 JS）&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;jsconfig.json&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;compilerOptions&quot;: {
    &quot;checkJs&quot;: true,
    &quot;lib&quot;: [&quot;ES2022&quot;],
    &quot;moduleResolution&quot;: &quot;node&quot;,
    &quot;types&quot;: [&quot;node&quot;, &quot;express&quot;]
  },
  &quot;include&quot;: [&quot;src/**/*.js&quot;],
  &quot;exclude&quot;: [&quot;dist&quot;, &quot;node_modules&quot;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;安装类型声明（开发依赖）：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;npm i -D @types/node @types/express
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;在关键文件顶部可加：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// @ts-check
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;给公共 API/中间件/配置对象补 JSDoc（从“对外接口”开始，收益最大）。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;路线 B：TypeScript（增量迁移）&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;tsconfig.json&lt;/code&gt;（最小严格模式）&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;ES2022&quot;,
    &quot;module&quot;: &quot;NodeNext&quot;,
    &quot;moduleResolution&quot;: &quot;NodeNext&quot;,
    &quot;strict&quot;: true,
    &quot;esModuleInterop&quot;: true,
    &quot;skipLibCheck&quot;: true,
    &quot;outDir&quot;: &quot;dist&quot;
  },
  &quot;include&quot;: [&quot;src&quot;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;增量迁移策略：
&lt;ul&gt;
&lt;li&gt;第 1 周：保留 JS 运行，新增 TS 构建（Babel/tsc 任一）。&lt;/li&gt;
&lt;li&gt;第 2–3 周：核心域模型与公共模块改为 &lt;code&gt;.ts&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;第 4 周：把“边缘模块”迁移/隔离，补充 &lt;code&gt;.d.ts&lt;/code&gt; 输出和 CI 阶段类型检查。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h1&gt;选型建议清单（给 Express 团队）&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;满足即可&lt;/strong&gt;：内部服务、接口清晰、少人维护 → &lt;strong&gt;JSDoc&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;追求稳健&lt;/strong&gt;：多人、多模块、频繁重构、长期维护 → &lt;strong&gt;TS&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;灰度策略&lt;/strong&gt;：JSDoc 起步，记录“类型痛点”，当痛点达到阈值（如 3 次以上重构失误/PR 反复）→ 立项迁移 TS。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;常见反模式与规避&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JSDoc 只写几处&lt;/strong&gt;：缺少全局 &lt;code&gt;checkJs&lt;/code&gt; 或关键路径无注释 → 约束形同虚设。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;编辑器与运行时别名不一致&lt;/strong&gt;：&lt;code&gt;jsconfig.paths&lt;/code&gt; 生效但 Node 不认；ESM 下用 &lt;code&gt;package.json#imports&lt;/code&gt; 对齐。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TS 一上来全量重写&lt;/strong&gt;：风险高、节奏乱；应优先迁移“被多处依赖的核心模块”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TS 关掉严格模式&lt;/strong&gt;：失去大量价值；若负担大，先放开个别规则而非整体关闭。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;实操建议（一句话版）&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;能用 JSDoc 先解决 80% 问题就用 JSDoc；当复杂度与团队协作要求上来，再切 TypeScript。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;把“是否迁移 TS”变成数据驱动的决策：统计类型相关故障/重构回滚次数、PR 讨论成本作为阈值。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;需要的话，把你项目的目录结构与典型模块贴给我，我可以根据现状给出“JSDoc 够用 or 值得上 TS”的个性化建议与实施计划。&lt;/p&gt;
</content:encoded></item><item><title>结合 Vite 和 Webpack 说一说打包工具都做了什么</title><link>https://blog.cuixu.cn/posts/frontend/what-does-the-bundlers-do/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/frontend/what-does-the-bundlers-do/</guid><description>结合 Vite 和 Webpack 说一说打包工具都做了什么</description><pubDate>Wed, 05 Nov 2025 07:40:11 GMT</pubDate><content:encoded>&lt;hr /&gt;
&lt;h2&gt;1) 入口与模块图：把项目“地图”画出来&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;功能：&lt;/strong&gt; 告诉工具“从哪儿开始找代码”，再顺着每个文件里的 &lt;code&gt;import/require()&lt;/code&gt; 把所有会用到的文件串成一张网（依赖图）。
&lt;strong&gt;直觉类比：&lt;/strong&gt; 从首页（入口）出发，顺着“超链接”（import）把整站爬一遍，得到网站地图。
&lt;strong&gt;Vite：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dev：浏览器请求哪个模块，就&lt;strong&gt;现查现用&lt;/strong&gt;地把它和它的依赖挂到图上。&lt;/li&gt;
&lt;li&gt;Build：交给 Rollup 从入口把整张图一次走完。
&lt;strong&gt;Webpack：&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;不分 Dev/Prod，都是&lt;strong&gt;先把整张图建好&lt;/strong&gt;再工作。
&lt;strong&gt;常用配置：&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Vite：&lt;code&gt;resolve.alias&lt;/code&gt;、&lt;code&gt;build.rollupOptions.input&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Webpack：&lt;code&gt;entry&lt;/code&gt;、&lt;code&gt;resolve.alias&lt;/code&gt;、&lt;code&gt;resolve.extensions&lt;/code&gt;、&lt;code&gt;resolve.mainFields&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;2) 模块解析与格式兼容：不同“口味”的文件都能吃&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;功能：&lt;/strong&gt; 让 ESM、CommonJS、CSS、图片、JSON… 这些不同格式都能作为“模块”被导入。
&lt;strong&gt;直觉类比：&lt;/strong&gt; 餐厅后厨收菜，有中餐配料、西餐配料、饮料、甜点——都要能接收并分门别类。
&lt;strong&gt;Vite：&lt;/strong&gt; Dev 期用&lt;strong&gt;原生 ESM&lt;/strong&gt;；遇到 CJS 用 &lt;strong&gt;esbuild&lt;/strong&gt; 转成 ESM；CSS/图片会被转成“虚拟模块”。
&lt;strong&gt;Webpack：&lt;/strong&gt; 用 &lt;strong&gt;loader&lt;/strong&gt; 把各种格式变成 JS 能理解的模块；静态资源用 &lt;code&gt;asset/*&lt;/code&gt; 类型统一管理。
&lt;strong&gt;配置抓手：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vite：多数开箱即用；特殊包用 &lt;code&gt;optimizeDeps.include/exclude&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;Webpack：&lt;code&gt;module.rules&lt;/code&gt;（&lt;code&gt;css-loader/file-loader&lt;/code&gt; 等已被 &lt;code&gt;asset&lt;/code&gt; 取代）、&lt;code&gt;type:&apos;asset/resource|inline|source&apos;&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;3) 源码转换（TS/JSX/ESNext）：把“新语法”变成“大家都懂的”&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;功能：&lt;/strong&gt; 把 TS、JSX、以及浏览器还不完全支持的新 JS 语法，变成兼容目标环境的普通 JS。
&lt;strong&gt;直觉类比：&lt;/strong&gt; 把方言翻译成普通话；再按不同城市口音做点适配。
&lt;strong&gt;Vite：&lt;/strong&gt; Dev 用 &lt;strong&gt;esbuild&lt;/strong&gt; 快速转；Build 用 &lt;strong&gt;Rollup 插件链 + esbuild&lt;/strong&gt;；必要时再上 Babel。
&lt;strong&gt;Webpack：&lt;/strong&gt; 常用 &lt;code&gt;swc-loader&lt;/code&gt; / &lt;code&gt;esbuild-loader&lt;/code&gt;（快）或 &lt;code&gt;ts-loader + babel-loader&lt;/code&gt;（灵活）。
&lt;strong&gt;配置抓手：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vite：&lt;code&gt;esbuild.target&lt;/code&gt;、&lt;code&gt;@vitejs/plugin-react&lt;/code&gt;/&lt;code&gt;-vue&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;Webpack：&lt;code&gt;module.rules&lt;/code&gt; 里接对应 loader；目标环境通过 Babel/SWC 配置或 &lt;code&gt;target&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;4) 代码分割：该拆就拆，别一次塞完&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;功能：&lt;/strong&gt; 把代码拆成若干 &lt;strong&gt;chunk&lt;/strong&gt;；首屏先下关键的，其他页面或功能再按需加载。
&lt;strong&gt;直觉类比：&lt;/strong&gt; 搬家别一次搬完全部家具，先把床和锅搬过去能住就行，其他慢慢运。
&lt;strong&gt;Vite（Rollup）：&lt;/strong&gt; 遇到 &lt;code&gt;import()&lt;/code&gt; 自动拆；可用 &lt;code&gt;output.manualChunks&lt;/code&gt; 把第三方库/路由块分组。
&lt;strong&gt;Webpack：&lt;/strong&gt; &lt;code&gt;import()&lt;/code&gt; 变异步块；&lt;code&gt;optimization.splitChunks&lt;/code&gt; 把公共依赖抽出来；&lt;code&gt;runtimeChunk&lt;/code&gt; 拆运行时。
&lt;strong&gt;常见坑：&lt;/strong&gt; 拆得太碎 → 请求多；拆得太粗 → 首屏肥。
&lt;strong&gt;配置抓手：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vite：&lt;code&gt;build.rollupOptions.output.manualChunks&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Webpack：&lt;code&gt;optimization.splitChunks&lt;/code&gt;、&lt;code&gt;optimization.runtimeChunk&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;5) Tree Shaking：把没用到的函数“抖掉”&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;功能：&lt;/strong&gt; 删除没有被引用的导出和死代码，减小体积。
&lt;strong&gt;直觉类比：&lt;/strong&gt; 行李只带穿的衣服，压缩包里把没穿的都拿掉。
&lt;strong&gt;Vite：&lt;/strong&gt; Rollup 基于 &lt;strong&gt;静态 ESM&lt;/strong&gt; 天然能摇树，要注意包的 &lt;code&gt;sideEffects&lt;/code&gt; 标注。
&lt;strong&gt;Webpack：&lt;/strong&gt; &lt;code&gt;usedExports&lt;/code&gt; + &lt;code&gt;sideEffects&lt;/code&gt; + 压缩器 DCE 一起配合。
&lt;strong&gt;关键点：&lt;/strong&gt; 第三方包要写 &lt;code&gt;&quot;sideEffects&quot;: false&lt;/code&gt;（或列出有副作用的文件），否则工具不敢删。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;6) 压缩 + 作用域提升：把包尽量“压瘪”&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;功能：&lt;/strong&gt; 压缩空格注释、缩短变量名；把多个小函数“内联”到一个作用域，进一步省体积。
&lt;strong&gt;直觉类比：&lt;/strong&gt; 把衣服抽真空打包，能塞更小的行李箱。
&lt;strong&gt;Vite：&lt;/strong&gt; 默认用 &lt;strong&gt;esbuild/terser&lt;/strong&gt; 压缩；Rollup 自带激进 hoist。
&lt;strong&gt;Webpack：&lt;/strong&gt; &lt;code&gt;TerserPlugin&lt;/code&gt; / &lt;code&gt;swcMinify&lt;/code&gt; / &lt;code&gt;esbuildMinify&lt;/code&gt;；&lt;code&gt;optimization.concatenateModules: true&lt;/code&gt;。
&lt;strong&gt;抓手：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prod 必开压缩；若构建慢，可试 swc/esbuild 压缩器加速。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;7) CSS 管线：样式也要编译、抽取、瘦身&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;功能：&lt;/strong&gt; 支持 Sass/Less，跑 PostCSS（自动补前缀等），生产把 CSS 抽成独立文件并压缩。
&lt;strong&gt;直觉类比：&lt;/strong&gt; 把布料（Sass）裁剪成衣服（CSS），最后折叠打包单独装袋。
&lt;strong&gt;Vite：&lt;/strong&gt; 内置预处理、PostCSS；&lt;code&gt;cssCodeSplit&lt;/code&gt; 控制是否按 chunk 拆 CSS。
&lt;strong&gt;Webpack：&lt;/strong&gt; Dev 用 &lt;code&gt;style-loader&lt;/code&gt; 直接插 &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt;；Prod 用 &lt;code&gt;MiniCssExtractPlugin&lt;/code&gt; 抽出 &lt;code&gt;.css&lt;/code&gt;，再用 &lt;code&gt;css-minimizer&lt;/code&gt; 压缩。
&lt;strong&gt;抓手：&lt;/strong&gt; Tailwind/UnoCSS 这类原子化框架构建期会扫描类名，别忘了开启缓存。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;8) 资源与哈希：文件要“可缓存又不串台”&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;功能：&lt;/strong&gt; 小文件直接内联成 base64；大文件复制到输出目录并在文件名里带 &lt;strong&gt;contenthash&lt;/strong&gt;，方便 CDN 长缓存。
&lt;strong&gt;直觉类比：&lt;/strong&gt; 零食（小文件）直接放口袋；大家电（大文件）单独打包并标注“按内容生成的编号”。
&lt;strong&gt;Vite：&lt;/strong&gt; &lt;code&gt;assetsInlineLimit&lt;/code&gt;、产物默认带 hash。
&lt;strong&gt;Webpack：&lt;/strong&gt; &lt;code&gt;type:&apos;asset&apos;&lt;/code&gt; 系列统一；&lt;code&gt;output.filename: [name].[contenthash].js&lt;/code&gt;。
&lt;strong&gt;关键点：&lt;/strong&gt; “contenthash”基于内容，改一处不至于让所有文件名都变。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;9) HTML 注入与启动：自动把产物挂到页面&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;功能：&lt;/strong&gt; 把打包出来的 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;/&lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; 自己插到 HTML 里，顺便加预加载标签。
&lt;strong&gt;直觉类比：&lt;/strong&gt; 菜做好了，自动摆盘上桌。
&lt;strong&gt;Vite：&lt;/strong&gt; &lt;code&gt;index.html&lt;/code&gt; 就是配置入口；自动注入入口脚本与关键 &lt;code&gt;preload&lt;/code&gt;；&lt;code&gt;import.meta.env&lt;/code&gt; 注环境变量。
&lt;strong&gt;Webpack：&lt;/strong&gt; 用 &lt;code&gt;html-webpack-plugin&lt;/code&gt; 读取模板并把资源注进去；需要时配 &lt;code&gt;preload/prefetch&lt;/code&gt; 插件。
&lt;strong&gt;抓手：&lt;/strong&gt; 多页面应用（MPA）就配多个入口/多个 HTML 插件实例。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;10) Dev Server 与 HMR：改一行，页面“秒变”&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;功能：&lt;/strong&gt; 起本地服务器、反向代理、支持&lt;strong&gt;热更新&lt;/strong&gt;（只替换改动的模块，不整页刷新）。
&lt;strong&gt;直觉类比：&lt;/strong&gt; 裁缝给你改衣服，改袖子不用让你把整套衣服脱下来。
&lt;strong&gt;Vite：&lt;/strong&gt; 基于 ESM 的模块级热替换，**依赖预构建（esbuild）**把 node_modules 先“熬一锅”。
&lt;strong&gt;Webpack：&lt;/strong&gt; &lt;code&gt;webpack-dev-server&lt;/code&gt; + Runtime 补丁；图上只重建受影响的模块。
&lt;strong&gt;抓手：&lt;/strong&gt; HMR 不稳的模块可先降级成整页刷新，先稳后快。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;11) 缓存、并行与增量：速度来自“别重复干活”&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;功能：&lt;/strong&gt; 把中间结果落盘、任务多进程并行、只重建受影响的那一小撮文件。
&lt;strong&gt;直觉类比：&lt;/strong&gt; 裁缝把常用版型留底（缓存），多人同时干活（并行），衣服改口袋不必重做衣领（增量）。
&lt;strong&gt;Vite：&lt;/strong&gt; &lt;code&gt;.vite&lt;/code&gt; 目录缓存预构建与转换；Rollup/Esbuild 自带并行。
&lt;strong&gt;Webpack：&lt;/strong&gt; &lt;code&gt;cache:{ type:&apos;filesystem&apos; }&lt;/code&gt;、&lt;code&gt;thread-loader&lt;/code&gt;、&lt;code&gt;parallel&lt;/code&gt;、&lt;code&gt;experiments.lazyCompilation&lt;/code&gt;。
&lt;strong&gt;抓手：&lt;/strong&gt; 大仓项目开启文件系统缓存可以直接砍掉二次构建大半时间。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;12) Source Map：线上出错也能看懂&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;功能：&lt;/strong&gt; 让压缩后的代码能“对回去”原始源码，定位 Bug；生产可用&lt;strong&gt;隐藏 map&lt;/strong&gt;配合错误上报。
&lt;strong&gt;直觉类比：&lt;/strong&gt; 城市地铁图（压缩产物）配一张“对照表”（map），告诉你真实街道名（源码）。
&lt;strong&gt;Vite：&lt;/strong&gt; &lt;code&gt;build.sourcemap: true|&apos;hidden&apos;|&apos;inline&apos;&lt;/code&gt;（生产推荐 &lt;code&gt;hidden&lt;/code&gt;）。
&lt;strong&gt;Webpack：&lt;/strong&gt; &lt;code&gt;devtool&lt;/code&gt;：开发常用 &lt;code&gt;cheap-module-source-map&lt;/code&gt;，生产 &lt;code&gt;hidden-source-map&lt;/code&gt;。
&lt;strong&gt;抓手：&lt;/strong&gt; 别把完整 map 直接公开在生产站点；交给 Sentry 等平台私有存储。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;可选项（按需添加的一揽子）&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;手工分包策略&lt;/strong&gt;：Vite &lt;code&gt;manualChunks&lt;/code&gt; / Webpack &lt;code&gt;splitChunks.cacheGroups&lt;/code&gt; 精细切 vendor/路由块。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;图片优化自动化&lt;/strong&gt;：Vite &lt;code&gt;vite-imagetools&lt;/code&gt;；Webpack &lt;code&gt;image-minimizer-webpack-plugin&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;预加载/预取精调&lt;/strong&gt;：按路由/关键路径加 &lt;code&gt;&amp;lt;link rel=&quot;preload/prefetch&quot;&amp;gt;&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;旧浏览器兼容&lt;/strong&gt;：Vite &lt;code&gt;@vitejs/plugin-legacy&lt;/code&gt;；Webpack 降 &lt;code&gt;targets&lt;/code&gt; + polyfill。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模块联邦（微前端）&lt;/strong&gt;：Vite 社区插件；Webpack Module Federation。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSR/SSG 同构&lt;/strong&gt;：Vite 配 &lt;code&gt;ssr&lt;/code&gt; 或直接用 Nuxt/SvelteKit 等；Webpack 走 &lt;code&gt;target:&apos;node&apos;&lt;/code&gt; + externals。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PWA/Service Worker&lt;/strong&gt;：Vite &lt;code&gt;vite-plugin-pwa&lt;/code&gt;；Webpack &lt;code&gt;workbox-webpack-plugin&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;类型检查 / ESLint 并入构建&lt;/strong&gt;：Vite &lt;code&gt;vite-plugin-checker&lt;/code&gt;；Webpack &lt;code&gt;fork-ts-checker-webpack-plugin&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;体积分析与 CI 守门&lt;/strong&gt;：&lt;code&gt;rollup-plugin-visualizer&lt;/code&gt; / &lt;code&gt;webpack-bundle-analyzer&lt;/code&gt; + 体积阈值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;哈希稳定性细化&lt;/strong&gt;：Webpack &lt;code&gt;moduleIds/chunkIds: &apos;deterministic&apos;&lt;/code&gt;；合理固定 vendor 边界。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;安全工艺&lt;/strong&gt;：依赖审计、SRI、生产 Source Map 私有化。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;i18n 构建期优化&lt;/strong&gt;：提取文案、按语言分包。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;一句话总括&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;把 12 步当“主干线”先跑通&lt;/strong&gt;：能编译、能分包、能热更、能缓存、能定位错误。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有了瓶颈再从“可选项”里点菜&lt;/strong&gt;：是首屏慢？体积大？冷启动慢？针对性加一两项就行。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>jsconfig 配置及注意事项</title><link>https://blog.cuixu.cn/posts/frontend/jsconfig-basic/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/frontend/jsconfig-basic/</guid><description>jsconfig文件里定义**项目包含的源文件**与**语言服务选项**，从而影响智能提示、自动导入、转到定义、错误检查等编辑器体验</description><pubDate>Mon, 03 Nov 2025 11:13:48 GMT</pubDate><content:encoded>&lt;h2&gt;jsconfig 配置的作用&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;在某个目录放一个 &lt;code&gt;jsconfig.json&lt;/code&gt;，VS Code 就把它当作&lt;strong&gt;JavaScript 项目根&lt;/strong&gt;。文件里定义&lt;strong&gt;项目包含的源文件&lt;/strong&gt;与&lt;strong&gt;语言服务选项&lt;/strong&gt;，从而影响智能提示、自动导入、转到定义、错误检查等编辑器体验。(&lt;a href=&quot;https://code.visualstudio.com/docs/languages/jsconfig?utm_source=chatgpt.com&quot;&gt;Visual Studio Code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;VS Code 没有 &lt;code&gt;jsconfig&lt;/code&gt; 也能工作，但当你的工作区里并非所有 JS 文件都属于同一个项目，或需要路径别名/包含范围等配置时，就应该创建它。(&lt;a href=&quot;https://code.visualstudio.com/Docs/languages/javascript?utm_source=chatgpt.com&quot;&gt;Visual Studio Code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;它本质上是 &lt;code&gt;tsconfig.json&lt;/code&gt; 的“JS 版变体”，只作用于编辑器的 JS/JSX 语言服务（不参与打包）。(&lt;a href=&quot;https://code.visualstudio.com/docs/languages/jsconfig?utm_source=chatgpt.com&quot;&gt;Visual Studio Code&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;为什么/什么时候需要&lt;/h2&gt;
&lt;p&gt;VS Code JS 支持有两种模式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;File Scope（无 jsconfig）&lt;/strong&gt;：每个文件独立，没有统一的项目上下文。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Explicit Project（有 jsconfig）&lt;/strong&gt;：用 &lt;code&gt;jsconfig.json&lt;/code&gt; 明确项目边界与解析规则。
当你需要更准确的 IntelliSense、跨文件跳转、非相对导入/别名，建议使用 &lt;code&gt;jsconfig&lt;/code&gt;。(&lt;a href=&quot;https://github.com/Microsoft/vscode-docs/blob/master/docs/languages/jsconfig.md?utm_source=chatgpt.com&quot;&gt;GitHub&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;常用配置项（高频）&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;include&lt;/code&gt; / &lt;code&gt;exclude&lt;/code&gt;：工程边界。只把 &lt;code&gt;src/&lt;/code&gt; 等需要的目录纳入项目。(&lt;a href=&quot;https://code.visualstudio.com/docs/languages/jsconfig?utm_source=chatgpt.com&quot;&gt;Visual Studio Code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;compilerOptions.baseUrl&lt;/code&gt; + &lt;code&gt;paths&lt;/code&gt;：配置&lt;strong&gt;路径别名&lt;/strong&gt;（如 &lt;code&gt;@/&lt;/code&gt;），让 VS Code 能解析到定义并提供自动导入。(&lt;a href=&quot;https://code.visualstudio.com/docs/languages/jsconfig?utm_source=chatgpt.com&quot;&gt;Visual Studio Code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;checkJs&lt;/code&gt;：对 &lt;strong&gt;.js/.jsx&lt;/strong&gt; 做 &lt;strong&gt;TS 级别类型检查&lt;/strong&gt;（配合 JSDoc 与 &lt;code&gt;.d.ts&lt;/code&gt;）以发现更多类型错误。(&lt;a href=&quot;https://code.visualstudio.com/docs/nodejs/working-with-javascript?utm_source=chatgpt.com&quot;&gt;Visual Studio Code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jsx&lt;/code&gt;：设置 JSX 处理方式（如 React 17+ 的 &lt;code&gt;react-jsx&lt;/code&gt;）。(&lt;a href=&quot;https://code.visualstudio.com/docs/languages/jsconfig?utm_source=chatgpt.com&quot;&gt;Visual Studio Code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;其他常见：&lt;code&gt;module&lt;/code&gt;、&lt;code&gt;moduleResolution&lt;/code&gt;、&lt;code&gt;resolveJsonModule&lt;/code&gt;、&lt;code&gt;types&lt;/code&gt; 等，影响编辑器如何解析模块与可见的全局类型（例如 Node/React）。(&lt;a href=&quot;https://code.visualstudio.com/docs/languages/jsconfig?utm_source=chatgpt.com&quot;&gt;Visual Studio Code&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;小技巧：用命令面板 &lt;strong&gt;JavaScript: Go to Project Configuration&lt;/strong&gt; 可检查当前文件是否被某个 &lt;code&gt;jsconfig.json&lt;/code&gt; 管理。(&lt;a href=&quot;https://code.visualstudio.com/docs/languages/javascript?from=20423&amp;amp;from_column=20423&amp;amp;utm_source=chatgpt.com&quot;&gt;Visual Studio Code&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;与 tsconfig 的关系&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;语义上：&lt;code&gt;jsconfig.json&lt;/code&gt; 是 &lt;code&gt;tsconfig.json&lt;/code&gt; 的子集/对应物，用于纯 JS 项目；TS 项目应只维护 &lt;code&gt;tsconfig.json&lt;/code&gt;（不要两者并存，避免冲突）。(&lt;a href=&quot;https://code.visualstudio.com/docs/languages/jsconfig?utm_source=chatgpt.com&quot;&gt;Visual Studio Code&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;常见坑&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;有 &lt;code&gt;jsconfig&lt;/code&gt; 但 &lt;code&gt;include&lt;/code&gt; 没覆盖源码（如漏了 &lt;code&gt;src/&lt;/code&gt;），会导致“转到定义/自动导入”异常。(&lt;a href=&quot;https://code.visualstudio.com/Docs/languages/javascript?utm_source=chatgpt.com&quot;&gt;Visual Studio Code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;配了打包器 alias（如 Rsbuild/Vite/Webpack），却没在 &lt;code&gt;jsconfig&lt;/code&gt; 同步 &lt;code&gt;baseUrl/paths&lt;/code&gt;，编辑器跳转会失效（打包能过但编辑器不认）。(&lt;a href=&quot;https://code.visualstudio.com/Docs/languages/javascript?utm_source=chatgpt.com&quot;&gt;Visual Studio Code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;开启 &lt;code&gt;checkJs&lt;/code&gt; 后导入 &lt;code&gt;*.scss&lt;/code&gt;、&lt;code&gt;*.jpg&lt;/code&gt; 等非 JS 资源需要 &lt;code&gt;.d.ts&lt;/code&gt; 声明，不然会报“找不到模块/类型声明”。这是语言服务在做类型检查的预期行为。(&lt;a href=&quot;https://code.visualstudio.com/docs/nodejs/working-with-javascript?utm_source=chatgpt.com&quot;&gt;Visual Studio Code&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;和 Biome/ESLint/打包器的区别&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;维度&lt;/th&gt;
&lt;th&gt;jsconfig.json&lt;/th&gt;
&lt;th&gt;Biome&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;主要作用&lt;/td&gt;
&lt;td&gt;配置 &lt;strong&gt;VS Code JavaScript 语言服务&lt;/strong&gt;：项目边界、路径解析、类型检查选项（&lt;code&gt;checkJs&lt;/code&gt;）等&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;格式化 + Lint&lt;/strong&gt; 工具：风格统一、可疑代码模式、自动修复等&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;生效位置&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;编辑器内&lt;/strong&gt;（tsserver），不参与打包&lt;/td&gt;
&lt;td&gt;CLI/编辑器/CI，检查并修复代码风格与部分问题&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;能否做类型检查&lt;/td&gt;
&lt;td&gt;可（对 JS 通过 &lt;code&gt;checkJs&lt;/code&gt;+JSDoc/类型声明）&lt;/td&gt;
&lt;td&gt;以规则式静态检查为主；不是完整的类型系统&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;是否影响打包&lt;/td&gt;
&lt;td&gt;否&lt;/td&gt;
&lt;td&gt;否（与 bundler 配置分离）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;Biome 的定位是&lt;strong&gt;超快的格式化器 + Linter&lt;/strong&gt;，替代 Prettier/ESLint 组合；它强调“格式化由 formatter 处理，lint 规则不覆盖格式化”，并提供大量分组规则与自动修复。(&lt;a href=&quot;https://biomejs.dev/?utm_source=chatgpt.com&quot;&gt;Biome&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Biome 不提供像 TS 那样的完整类型推断/检查；&lt;code&gt;jsconfig&lt;/code&gt; 的 &lt;code&gt;checkJs&lt;/code&gt; 则能把 &lt;strong&gt;.js/.jsx&lt;/strong&gt; 纳入类型检查（配合 JSDoc 与声明），捕获 API 误用、形状不匹配等类型问题。(&lt;a href=&quot;https://code.visualstudio.com/docs/nodejs/working-with-javascript?utm_source=chatgpt.com&quot;&gt;Visual Studio Code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;二者是&lt;strong&gt;互补&lt;/strong&gt;关系：Biome 保证风格/常见陷阱；&lt;code&gt;jsconfig&lt;/code&gt; 让编辑器理解你的项目结构与别名，并在需要时提供类型层面的保护。(&lt;a href=&quot;https://code.visualstudio.com/Docs/languages/javascript?utm_source=chatgpt.com&quot;&gt;Visual Studio Code&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;给你的一份实用模板（纯 JS + React）&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;compilerOptions&quot;: {
    &quot;baseUrl&quot;: &quot;.&quot;,
    &quot;paths&quot;: { &quot;@/*&quot;: [&quot;src/*&quot;] },
    &quot;jsx&quot;: &quot;react-jsx&quot;,
    &quot;checkJs&quot;: true, // 若暂不想要类型错误，可设为 false
    &quot;module&quot;: &quot;ESNext&quot;,
    &quot;moduleResolution&quot;: &quot;bundler&quot;,
    &quot;resolveJsonModule&quot;: true,
    &quot;types&quot;: [&quot;react&quot;, &quot;react-dom&quot;, &quot;node&quot;]
  },
  &quot;include&quot;: [&quot;src&quot;],
  &quot;exclude&quot;: [&quot;dist&quot;, &quot;node_modules&quot;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;如果你开启了 &lt;code&gt;checkJs&lt;/code&gt;，并且会 &lt;code&gt;import &apos;./x.scss&apos;&lt;/code&gt; 或图片等静态资源，请加一份最小的 &lt;code&gt;assets.d.ts&lt;/code&gt;（告诉编辑器这些模块的“类型”），以避免无意义报错。(&lt;a href=&quot;https://code.visualstudio.com/docs/nodejs/working-with-javascript?utm_source=chatgpt.com&quot;&gt;Visual Studio Code&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>Laravel IDE Helper 使用笔记</title><link>https://blog.cuixu.cn/posts/php/laravel-ide-helper/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/php/laravel-ide-helper/</guid><description>`laravel-ide-helper` 是一个为 **Laravel 项目生成 PHPDocs** 的开发辅助工具。它可以让 IDE 更懂 Laravel</description><pubDate>Tue, 28 Oct 2025 03:51:06 GMT</pubDate><content:encoded>&lt;h2&gt;📦 一、包简介&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;项目地址&lt;/strong&gt;：
https://github.com/barryvdh/laravel-ide-helper&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;作者&lt;/strong&gt;：Barry vd. Heuvel
&lt;strong&gt;最新版本&lt;/strong&gt;：v3.x支持 Laravel 10+&lt;/p&gt;
&lt;h3&gt;✅ 作用&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;laravel-ide-helper&lt;/code&gt; 是一个为 &lt;strong&gt;Laravel 项目生成 PHPDocs&lt;/strong&gt; 的开发辅助工具。它可以让你的 IDE （PhpStorm / VS Code ）能够：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;智能补全 Laravel Facade 方法&lt;/li&gt;
&lt;li&gt;识别 Eloquent 模型的属性与关系&lt;/li&gt;
&lt;li&gt;添加 IoC 容器解析类的类型提示&lt;/li&gt;
&lt;li&gt;自动生成 macro / mixin / Fluent 方法文档&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;🔍 说白了：让 IDE 更懂 Laravel。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;⚙️ 二、安装与配置&lt;/h2&gt;
&lt;h3&gt;1. 安装&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;composer require --dev barryvdh/laravel-ide-helper
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;🛠️ 三、常用命令&lt;/h2&gt;
&lt;h3&gt;1. Facade PHPDocs&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;php artisan ide-helper:generate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;🔹 生成 &lt;code&gt;_ide_helper.php&lt;/code&gt;，使 IDE 识别如 &lt;code&gt;DB&lt;/code&gt;, &lt;code&gt;Auth&lt;/code&gt;, &lt;code&gt;Cache&lt;/code&gt; 等 Facade。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;2. 生成模型注释&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;php artisan ide-helper:models -RW
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-W&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;写入模型文件中&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-N&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;不写入模型，只生成文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-R&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;重置注释&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-M&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;仅添加 &lt;code&gt;@mixin&lt;/code&gt; 标记&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-E&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;生成 &lt;code&gt;_ide_helper_eloquent.php&lt;/code&gt; 简版&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;🔍 需正常数据库连接。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h3&gt;3. PhpStorm Meta 文件&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;php artisan ide-helper:meta
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;🔹 生成 &lt;code&gt;.phpstorm.meta.php&lt;/code&gt; 文件，使 PhpStorm 能识别 &lt;code&gt;app(&apos;events&apos;)&lt;/code&gt;等的对象类型。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;🧩 四、进阶功能&lt;/h2&gt;
&lt;h3&gt;1. Composer 自动生成&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&quot;scripts&quot;: {
  &quot;post-update-cmd&quot;: [
    &quot;@php artisan ide-helper:generate&quot;,
    &quot;@php artisan ide-helper:meta&quot;
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;2. Fluent 方法支持&lt;/h3&gt;
&lt;p&gt;开启后，IDE 可识别 &lt;code&gt;$table-&amp;gt;string()-&amp;gt;nullable()-&amp;gt;index();&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;include_fluent&apos; =&amp;gt; true,
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;3. Macros / Mixins&lt;/h3&gt;
&lt;p&gt;当使用 &lt;code&gt;Str::macro()&lt;/code&gt; 时，如果定义有类型提示，IDE Helper 会自动生成相应 PHPDocs。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;4. 自定义 Model Hooks&lt;/h3&gt;
&lt;p&gt;允许在模型生成过程中动态添加属性或方法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;model_hooks&apos; =&amp;gt; [
   MyCustomHook::class,
],
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;5. 忽略某些模型&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;php artisan ide-helper:models --ignore=&quot;App\Models\User,App\Models\Post&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;📂 五、生成文件解释&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;文件&lt;/th&gt;
&lt;th&gt;用途&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_ide_helper.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Facade 和 macro 文档&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_ide_helper_models.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;模型属性与关系&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.phpstorm.meta.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;IoC 容器提示&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;config/ide-helper.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;配置文件&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;🖊️ 六、使用建议&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;仅在开发环境下使用&lt;/li&gt;
&lt;li&gt;将 &lt;code&gt;_ide_helper.php&lt;/code&gt; 等添加到 &lt;code&gt;.gitignore&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;确保数据库连接正常&lt;/li&gt;
&lt;li&gt;定期更新生成文件&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;🔺 七、总结&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;优点&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;🔍 IDE 智能补全&lt;/td&gt;
&lt;td&gt;加强代码推断能力&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;📝 自动生成&lt;/td&gt;
&lt;td&gt;基于代码和数据库分析&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⚙️ 可配置&lt;/td&gt;
&lt;td&gt;可自定义路径和生成方式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;💡 PhpStorm 集成&lt;/td&gt;
&lt;td&gt;自动识别 IoC 对象&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;总结&lt;/strong&gt;：&lt;code&gt;laravel-ide-helper&lt;/code&gt; 是 Laravel 开发者必备的辅助工具，可以大大提升代码编写效率和维护性。&lt;/p&gt;
</content:encoded></item><item><title>不同系统中的卷（Volume）与分区管理对比</title><link>https://blog.cuixu.cn/posts/volumes-diff/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/volumes-diff/</guid><description>卷（Volume）”是存储空间的逻辑单位。不同操作系统对“卷”的定义和管理方式不同</description><pubDate>Fri, 24 Oct 2025 04:33:10 GMT</pubDate><content:encoded>&lt;h2&gt;一、基本概念&lt;/h2&gt;
&lt;p&gt;“卷（Volume）”是存储空间的逻辑单位。不同操作系统对“卷”的定义和管理方式不同：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Windows&lt;/strong&gt; 以固定分区为主；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;macOS (APFS)&lt;/strong&gt; 采用共享空间的容器模型；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Linux&lt;/strong&gt; 使用 LVM（逻辑卷管理）实现灵活可扩展的结构。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;二、三大系统卷管理方式对比&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;系统&lt;/th&gt;
&lt;th&gt;底层技术&lt;/th&gt;
&lt;th&gt;结构层次&lt;/th&gt;
&lt;th&gt;空间分配机制&lt;/th&gt;
&lt;th&gt;是否自动共享空间&lt;/th&gt;
&lt;th&gt;是否支持跨硬盘&lt;/th&gt;
&lt;th&gt;是否可加密&lt;/th&gt;
&lt;th&gt;调整难度&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Windows&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;NTFS 分区&lt;/td&gt;
&lt;td&gt;硬盘 → 分区&lt;/td&gt;
&lt;td&gt;每个分区&lt;strong&gt;固定大小&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ 否&lt;/td&gt;
&lt;td&gt;❌ 否&lt;/td&gt;
&lt;td&gt;BitLocker&lt;/td&gt;
&lt;td&gt;高（需重新分区）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;macOS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;APFS 容器 + 卷&lt;/td&gt;
&lt;td&gt;硬盘 → 容器 → 卷&lt;/td&gt;
&lt;td&gt;容器内卷&lt;strong&gt;共享空间&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ 是&lt;/td&gt;
&lt;td&gt;❌ 否&lt;/td&gt;
&lt;td&gt;✅ 原生支持 (APFS Encrypted)&lt;/td&gt;
&lt;td&gt;极低（自动）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Linux&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LVM（PV → VG → LV）&lt;/td&gt;
&lt;td&gt;硬盘 → 分区 → 物理卷 → 卷组 → 逻辑卷&lt;/td&gt;
&lt;td&gt;卷组内卷&lt;strong&gt;可手动调整大小&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⚙️ 半自动（需命令调整）&lt;/td&gt;
&lt;td&gt;✅ 可将多盘合并&lt;/td&gt;
&lt;td&gt;✅ LUKS / dm-crypt&lt;/td&gt;
&lt;td&gt;中等（需命令行）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;三、形象理解&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;系统&lt;/th&gt;
&lt;th&gt;类比场景&lt;/th&gt;
&lt;th&gt;特点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Windows&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;固定大小的房间&lt;/td&gt;
&lt;td&gt;空间一旦划定就不能自动调整。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;macOS (APFS)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;多人共享仓库&lt;/td&gt;
&lt;td&gt;所有卷共享一个空间池，自动伸缩。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Linux (LVM)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;模块化仓库&lt;/td&gt;
&lt;td&gt;管理员可自由扩展、合并或缩小卷组。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;四、文件系统与大小写敏感性&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;系统&lt;/th&gt;
&lt;th&gt;常见文件系统&lt;/th&gt;
&lt;th&gt;是否区分大小写&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Windows&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;NTFS / exFAT / FAT32&lt;/td&gt;
&lt;td&gt;默认不区分&lt;/td&gt;
&lt;td&gt;对大小写敏感软件不友好。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;macOS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;APFS / HFS+&lt;/td&gt;
&lt;td&gt;可选（Case-sensitive 或不敏感）&lt;/td&gt;
&lt;td&gt;默认不区分；开发环境可选敏感模式。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Linux&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ext4 / XFS / Btrfs / ZFS&lt;/td&gt;
&lt;td&gt;默认区分&lt;/td&gt;
&lt;td&gt;文件系统天然区分大小写。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;五、加密机制对比&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;系统&lt;/th&gt;
&lt;th&gt;加密方式&lt;/th&gt;
&lt;th&gt;特点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Windows&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;BitLocker&lt;/td&gt;
&lt;td&gt;简单易用，系统集成。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;macOS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;APFS (Encrypted) / FileVault&lt;/td&gt;
&lt;td&gt;系统级集成，安全方便。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Linux&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LUKS + LVM&lt;/td&gt;
&lt;td&gt;灵活可组合，适合进阶使用者。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;六、操作方式示例&lt;/h2&gt;
&lt;h3&gt;1️⃣ macOS 新建卷（Case-insensitive）&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;打开 &lt;strong&gt;磁盘工具 (Disk Utility)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;选中 APFS 容器 → 点击 “+”&lt;/li&gt;
&lt;li&gt;格式选择 &lt;code&gt;APFS&lt;/code&gt; 或 &lt;code&gt;APFS (Encrypted)&lt;/code&gt;（不要选 Case-sensitive）&lt;/li&gt;
&lt;li&gt;完成后复制数据过去即可使用&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;→ 所有卷自动共享同一空间。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;2️⃣ Linux LVM 示例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 创建物理卷
sudo pvcreate /dev/sda1 /dev/sdb1

# 创建卷组
sudo vgcreate vg_data /dev/sda1 /dev/sdb1

# 创建逻辑卷
sudo lvcreate -L 200G -n lv_backup vg_data

# 格式化
sudo mkfs.ext4 /dev/vg_data/lv_backup

# 扩容示例
sudo lvextend -L +100G /dev/vg_data/lv_backup
sudo resize2fs /dev/vg_data/lv_backup
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;七、总结一句话&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;💬 &lt;strong&gt;Windows 是固定的，macOS 是自动的，Linux 是灵活的。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Windows&lt;/strong&gt;：每个分区独立，调整麻烦。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;macOS&lt;/strong&gt;：卷共享空间，系统自动伸缩。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Linux&lt;/strong&gt;：通过 LVM 灵活管理、扩展和加密空间。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>为什么不应该在 Sidebar 组件中直接使用 useSessions Hook</title><link>https://blog.cuixu.cn/posts/frontend/why-not-use-hooks-in-components/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/frontend/why-not-use-hooks-in-components/</guid><pubDate>Thu, 21 Aug 2025 06:59:14 GMT</pubDate><content:encoded>&lt;h2&gt;引言&lt;/h2&gt;
&lt;p&gt;在 React 开发中，我们经常面临一个重要的架构决策：是否应该让组件直接使用业务逻辑 hooks，还是通过 props 传递数据和回调函数？本文将通过一个实际的聊天应用案例，分析为什么建议保持组件的数据流清晰，而不是在组件中直接使用业务逻辑 hooks。&lt;/p&gt;
&lt;h2&gt;问题背景&lt;/h2&gt;
&lt;p&gt;在我们的聊天应用中，有一个 &lt;code&gt;Sidebar&lt;/code&gt; 组件负责显示聊天会话列表。最初的设计是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 当前架构：通过 props 传递数据
&amp;lt;Sidebar
  sessions={sessions}
  currentSessionId={currentSessionId}
  onSelectSession={selectSession}
  onNewSession={createNewSession}
  onDeleteSession={deleteSession}
  isOpen={sidebarOpen}
  onToggle={() =&amp;gt; setSidebarOpen(!sidebarOpen)}
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有人可能会问：为什么不直接在 &lt;code&gt;Sidebar&lt;/code&gt; 组件中使用 &lt;code&gt;useSessions&lt;/code&gt; hook 呢？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 不推荐的架构：直接在组件中使用 hook
export function Sidebar() {
  const {
    sessions,
    currentSessionId,
    selectSession,
    createNewSession,
    deleteSession,
  } = useSessions();
  // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;为什么不建议在组件中直接使用业务逻辑 Hooks？&lt;/h2&gt;
&lt;h3&gt;1. 关注点分离 (Separation of Concerns)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;组件应该专注于 UI 渲染，而不是业务逻辑&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// ✅ 好的做法：纯展示组件
export function Sidebar({
  sessions,
  onSelectSession,
  onNewSession,
  onDeleteSession,
}) {
  return (
    &amp;lt;div&amp;gt;
      {sessions.map((session) =&amp;gt; (
        &amp;lt;button key={session.id} onClick={() =&amp;gt; onSelectSession(session.id)}&amp;gt;
          {session.title}
        &amp;lt;/button&amp;gt;
      ))}
    &amp;lt;/div&amp;gt;
  );
}

// ❌ 不好的做法：组件混合了业务逻辑
export function Sidebar() {
  const { sessions, selectSession } = useSessions(); // 业务逻辑混入组件
  // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 可测试性 (Testability)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;纯组件更容易进行单元测试&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// ✅ 容易测试：只需要模拟 props
test(&quot;Sidebar renders sessions correctly&quot;, () =&amp;gt; {
  const mockSessions = [
    { id: &quot;1&quot;, title: &quot;Chat 1&quot; },
    { id: &quot;2&quot;, title: &quot;Chat 2&quot; },
  ];

  render(&amp;lt;Sidebar sessions={mockSessions} onSelectSession={jest.fn()} /&amp;gt;);

  expect(screen.getByText(&quot;Chat 1&quot;)).toBeInTheDocument();
  expect(screen.getByText(&quot;Chat 2&quot;)).toBeInTheDocument();
});

// ❌ 难以测试：需要模拟 hook 和依赖
test(&quot;Sidebar with useSessions&quot;, () =&amp;gt; {
  // 需要模拟 useSessions hook
  jest.mock(&quot;./useSessions&quot;, () =&amp;gt; ({
    useSessions: () =&amp;gt; ({
      sessions: mockSessions,
      selectSession: jest.fn(),
    }),
  }));
  // 测试变得复杂...
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 可复用性 (Reusability)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;纯组件可以在不同场景下复用&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// ✅ 可以在不同场景下复用
// 场景1：聊天应用
&amp;lt;Sidebar sessions={chatSessions} onSelectSession={handleChatSelect} /&amp;gt;

// 场景2：文件管理器
&amp;lt;Sidebar sessions={fileSessions} onSelectSession={handleFileSelect} /&amp;gt;

// 场景3：测试环境
&amp;lt;Sidebar sessions={mockSessions} onSelectSession={mockHandler} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 状态管理集中化 (Centralized State Management)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;所有状态变化都在一个地方管理，便于调试&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// ✅ 状态管理集中化
export function useChat() {
  const { sessions, selectSession, createNewSession, deleteSession } =
    useSessions();
  const { messages, sendMessage } = useMessages();
  const { user, logout } = useAuth();

  // 所有业务逻辑都在这里
  const handleSessionSelect = async (sessionId: string) =&amp;gt; {
    await selectSession(sessionId);
    await loadMessages(sessionId);
  };

  return {
    sessions,
    onSelectSession: handleSessionSelect,
    // ...
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. 数据流清晰 (Clear Data Flow)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;遵循 React 的数据流原则：props 向下，事件向上&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// ✅ 清晰的数据流
App
├── useChat (业务逻辑层)
│   ├── useSessions
│   ├── useMessages
│   └── useAuth
└── Chat (组件层)
    └── Sidebar (纯展示组件)
        ├── sessions (props 向下)
        ├── onSelectSession (事件向上)
        └── onNewSession (事件向上)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6. 便于调试和维护 (Debugging and Maintenance)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;当出现问题时，可以快速定位到业务逻辑层&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// ✅ 问题定位清晰
// 如果会话选择有问题，直接查看 useChat 中的 handleSessionSelect
// 如果 UI 显示有问题，直接查看 Sidebar 组件的渲染逻辑

// ❌ 问题定位困难
// 如果直接在 Sidebar 中使用 useSessions，问题可能分散在多个地方
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;实际案例分析&lt;/h2&gt;
&lt;p&gt;让我们看看当前项目中的实现：&lt;/p&gt;
&lt;h3&gt;当前架构的优势&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// useChat.ts - 业务逻辑集中管理
export function useChat() {
  const { sessions, selectSession, createNewSession, deleteSession } =
    useSessions();

  // 包装业务逻辑，添加额外的处理
  const selectSessionWrapper = async (sessionId: string) =&amp;gt; {
    clearMessages(); // 清除当前消息
    setRequest(&quot;&quot;); // 清空输入
    setError(&quot;&quot;); // 清除错误
    await selectSession(sessionId);
    await loadChatMessages(sessionId); // 加载消息
  };

  return {
    sessions,
    onSelectSession: selectSessionWrapper,
    // ...
  };
}

// Chat.tsx - 组件层
export function Chat() {
  const { sessions, onSelectSession, onNewSession, onDeleteSession } =
    useChat();

  return (
    &amp;lt;Sidebar
      sessions={sessions}
      onSelectSession={onSelectSession}
      onNewSession={onNewSession}
      onDeleteSession={onDeleteSession}
    /&amp;gt;
  );
}

// Sidebar.tsx - 纯展示组件
export function Sidebar({
  sessions,
  onSelectSession,
  onNewSession,
  onDeleteSession,
}) {
  return (
    &amp;lt;div&amp;gt;
      {sessions.map((session) =&amp;gt; (
        &amp;lt;button key={session.id} onClick={() =&amp;gt; onSelectSession(session.id)}&amp;gt;
          {session.title}
        &amp;lt;/button&amp;gt;
      ))}
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;如果直接在 Sidebar 中使用 useSessions 的问题&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// ❌ 不推荐的实现
export function Sidebar() {
  const { sessions, selectSession, createNewSession, deleteSession } =
    useSessions();
  const { clearMessages, loadChatMessages } = useMessages(); // 需要额外的 hook

  const handleSelectSession = async (sessionId: string) =&amp;gt; {
    clearMessages(); // 业务逻辑混入组件
    await selectSession(sessionId);
    await loadChatMessages(sessionId);
  };

  return (
    &amp;lt;div&amp;gt;
      {sessions.map((session) =&amp;gt; (
        &amp;lt;button
          key={session.id}
          onClick={() =&amp;gt; handleSelectSession(session.id)}
        &amp;gt;
          {session.title}
        &amp;lt;/button&amp;gt;
      ))}
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;何时可以考虑在组件中使用 Hooks？&lt;/h2&gt;
&lt;p&gt;虽然我们建议保持组件的数据流清晰，但在某些特定情况下，可以考虑在组件中直接使用 hooks：&lt;/p&gt;
&lt;h3&gt;1. UI 相关的 Hooks&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// ✅ 适合在组件中使用：UI 相关的 hooks
export function Sidebar() {
  const [isOpen, setIsOpen] = useState(false);
  const isMobile = useMediaQuery(&quot;(max-width: 768px)&quot;);

  return (
    &amp;lt;div className={isMobile ? &quot;mobile-layout&quot; : &quot;desktop-layout&quot;}&amp;gt;
      {/* ... */}
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 组件内部状态管理&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// ✅ 适合在组件中使用：组件内部状态
export function Sidebar() {
  const [searchTerm, setSearchTerm] = useState(&quot;&quot;);
  const [sortBy, setSortBy] = useState(&quot;date&quot;);

  const filteredSessions = useMemo(() =&amp;gt; {
    return sessions.filter((session) =&amp;gt;
      session.title.toLowerCase().includes(searchTerm.toLowerCase()),
    );
  }, [sessions, searchTerm]);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;input
        value={searchTerm}
        onChange={(e) =&amp;gt; setSearchTerm(e.target.value)}
      /&amp;gt;
      {filteredSessions.map((session) =&amp;gt; (
        &amp;lt;SessionItem key={session.id} session={session} /&amp;gt;
      ))}
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 第三方库的 Hooks&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// ✅ 适合在组件中使用：第三方库的 hooks
export function Sidebar() {
  const { data, loading, error } = useQuery([&quot;sessions&quot;], fetchSessions);
  const { mutate: deleteSession } = useMutation(deleteSessionApi);

  if (loading) return &amp;lt;Spinner /&amp;gt;;
  if (error) return &amp;lt;ErrorMessage error={error} /&amp;gt;;

  return (
    &amp;lt;div&amp;gt;
      {data.map((session) =&amp;gt; (
        &amp;lt;SessionItem key={session.id} session={session} /&amp;gt;
      ))}
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;最佳实践总结&lt;/h2&gt;
&lt;h3&gt;1. 分层架构&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;业务逻辑层 (Hooks)
├── useChat
├── useSessions
├── useMessages
└── useAuth

组件层 (Components)
├── Chat
├── Sidebar
├── MessageList
└── ChatInput
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 数据流原则&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Props 向下传递&lt;/strong&gt;：数据从父组件流向子组件&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;事件向上传递&lt;/strong&gt;：用户交互从子组件流向父组件&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;业务逻辑集中&lt;/strong&gt;：所有业务逻辑都在 hooks 中处理&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 组件设计原则&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;单一职责&lt;/strong&gt;：每个组件只负责一个功能&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;纯函数&lt;/strong&gt;：组件应该是纯函数，相同的 props 产生相同的输出&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可测试&lt;/strong&gt;：组件应该易于测试&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可复用&lt;/strong&gt;：组件应该可以在不同场景下复用&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;结论&lt;/h2&gt;
&lt;p&gt;虽然在某些情况下可以在组件中直接使用业务逻辑 hooks，但为了保持代码的可维护性、可测试性和可复用性，建议遵循以下原则：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;保持组件的数据流清晰&lt;/strong&gt;：通过 props 传递数据，通过回调函数处理事件&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;集中管理业务逻辑&lt;/strong&gt;：将所有业务逻辑放在专门的 hooks 中&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关注点分离&lt;/strong&gt;：组件专注于 UI 渲染，hooks 专注于业务逻辑&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;遵循 React 最佳实践&lt;/strong&gt;：props 向下，事件向上&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这种架构模式不仅符合 React 的设计理念，也为项目的长期维护和扩展奠定了良好的基础。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;本文基于实际项目经验总结，希望对您的 React 开发有所帮助。如果您有任何问题或建议，欢迎讨论交流。&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Zustand Store 状态同步问题分析与解决方案</title><link>https://blog.cuixu.cn/posts/frontend/zustand-store-state-sync-problem-solution/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/frontend/zustand-store-state-sync-problem-solution/</guid><description>在使用 Zustand 构建复杂状态管理时，经常会遇到需要组合多个子 store 的场景。本文通过一个实际案例，分析了在组合 store 时遇到的状态同步问题，并提供了相应的解决方案</description><pubDate>Sat, 02 Aug 2025 04:53:07 GMT</pubDate><content:encoded>&lt;h2&gt;问题背景&lt;/h2&gt;
&lt;p&gt;在使用 Zustand 构建复杂状态管理时，经常会遇到需要组合多个子 store 的场景。本文通过一个实际案例，分析了在组合 store 时遇到的状态同步问题，并提供了相应的解决方案。&lt;/p&gt;
&lt;h2&gt;问题描述&lt;/h2&gt;
&lt;h3&gt;原始代码结构&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const useDeviceStore = create((set, get) =&amp;gt; ({
  // 问题：这种方式只获取初始状态，不会响应子store的变化
  ...useDeviceIdentityStore.getState(),
  ...useNetworkStatusStore.getState(),
  ...useOperationStatusStore.getState(),
  ...useDeviceInfoStore.getState(),

  _initialized: false,
  init: async () =&amp;gt; {
    // 初始化逻辑
    await useDeviceIdentityStore.getState().init();
    set({ _initialized: true });
  },
}));

// src/pages/mainTab/index.jsx - 组件使用
const MainTab = () =&amp;gt; {
  const deviceId = useDeviceStore((state) =&amp;gt; state.deviceId); // 始终为空
  const accessCode = useDeviceStore((state) =&amp;gt; state.accessCode); // 始终为空
  // ...
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;问题现象&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;设备初始化成功（控制台显示 &lt;code&gt;deviceId: &apos;E00E40&apos;, accessCode: &apos;CJRA88&apos;&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;但组件中获取到的 &lt;code&gt;deviceId&lt;/code&gt; 和 &lt;code&gt;accessCode&lt;/code&gt; 始终为空字符串&lt;/li&gt;
&lt;li&gt;子 store 状态正常，主 store 状态不同步&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;问题根源&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;状态组合方式错误&lt;/strong&gt;：使用 &lt;code&gt;...useDeviceIdentityStore.getState()&lt;/code&gt; 只会在 store 创建时获取一次初始状态，不会建立响应式连接。当子 store 状态更新时，主 store 不会自动同步。&lt;/p&gt;
&lt;h2&gt;解决方案对比&lt;/h2&gt;
&lt;h3&gt;方案1：订阅机制（推荐）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;保持原有 API 结构&lt;/li&gt;
&lt;li&gt;自动状态同步&lt;/li&gt;
&lt;li&gt;类型安全&lt;/li&gt;
&lt;li&gt;易于维护&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;代码稍复杂&lt;/li&gt;
&lt;li&gt;需要额外的订阅逻辑&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// src/store/device/index.js - 修复后的主 store
const useDeviceStore = create((set, get) =&amp;gt; ({
  _initialized: false,
  init: async () =&amp;gt; {
    await useDeviceIdentityStore.getState().init();
    set({ _initialized: true });
  },
}));

// 订阅子store的变化并同步到主store
useDeviceIdentityStore.subscribe((state) =&amp;gt; {
  useDeviceStore.setState({
    deviceId: state.deviceId,
    accessCode: state.accessCode,
  });
});

useNetworkStatusStore.subscribe((state) =&amp;gt; {
  useDeviceStore.setState({
    networkConnectedStatus: state.networkConnectedStatus,
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;方案2：直接使用子 store&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;实现简单&lt;/li&gt;
&lt;li&gt;性能更好（减少一层代理）&lt;/li&gt;
&lt;li&gt;状态直接响应&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;破坏原有 API 结构&lt;/li&gt;
&lt;li&gt;组件需要知道具体的子 store&lt;/li&gt;
&lt;li&gt;可能导致组件耦合度增加&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// src/pages/mainTab/index.jsx - 直接使用子 store
import useDeviceIdentityStore from &quot;@/store/device/deviceIdentity&quot;;
import useNetworkStatusStore from &quot;@/store/device/networkStatus&quot;;
import useDeviceInfoStore from &quot;@/store/device/deviceInfo&quot;;

const MainTab = () =&amp;gt; {
  const deviceId = useDeviceIdentityStore((state) =&amp;gt; state.deviceId);
  const accessCode = useDeviceIdentityStore((state) =&amp;gt; state.accessCode);
  const networkConnectedStatus = useNetworkStatusStore(
    (state) =&amp;gt; state.networkConnectedStatus,
  );
  const linkStatus = useDeviceInfoStore((state) =&amp;gt; state.linkStatus);
  // ...
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;最终选择&lt;/h2&gt;
&lt;p&gt;根据项目实际情况，我们选择了&lt;strong&gt;方案2&lt;/strong&gt;，原因如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;简单直接&lt;/strong&gt;：避免了复杂的订阅逻辑&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能优化&lt;/strong&gt;：减少了不必要的状态代理&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;代码清晰&lt;/strong&gt;：组件直接使用需要的子 store，意图明确&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;维护性好&lt;/strong&gt;：减少了中间层，降低了复杂度&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;修复后的代码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// src/pages/mainTab/index.jsx
import useDeviceIdentityStore from &quot;@/store/device/deviceIdentity&quot;;
import useNetworkStatusStore from &quot;@/store/device/networkStatus&quot;;
import useDeviceInfoStore from &quot;@/store/device/deviceInfo&quot;;

const MainTab = () =&amp;gt; {
  // 直接从对应的子 store 获取状态
  const deviceId = useDeviceIdentityStore((state) =&amp;gt; state.deviceId);
  const accessCode = useDeviceIdentityStore((state) =&amp;gt; state.accessCode);
  const networkConnectedStatus = useNetworkStatusStore(
    (state) =&amp;gt; state.networkConnectedStatus,
  );
  const linkStatus = useDeviceInfoStore((state) =&amp;gt; state.linkStatus);
  const updateAccessCode = useDeviceIdentityStore(
    (state) =&amp;gt; state.updateAccessCode,
  );

  // 现在 deviceId 和 accessCode 能正确获取到值
  console.log(&quot;deviceId:&quot;, deviceId); // &apos;E00E40&apos;
  console.log(&quot;accessCode:&quot;, accessCode); // &apos;CJRA88&apos;

  // ... 其他组件逻辑
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;经验总结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;避免静态状态组合&lt;/strong&gt;：不要使用 &lt;code&gt;...store.getState()&lt;/code&gt; 来组合状态&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;选择合适的状态管理策略&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;简单场景：直接使用子 store&lt;/li&gt;
&lt;li&gt;复杂场景：使用订阅机制&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;保持状态响应性&lt;/strong&gt;：确保状态变化能够正确传播&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;考虑性能影响&lt;/strong&gt;：减少不必要的状态代理和订阅&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;相关技术要点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Zustand 订阅机制&lt;/strong&gt;：&lt;code&gt;store.subscribe(callback)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态响应性&lt;/strong&gt;：确保状态变化能够触发组件重新渲染&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Store 组合模式&lt;/strong&gt;：合理设计 store 的层次结构&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能优化&lt;/strong&gt;：避免不必要的状态同步和组件重渲染&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过这个案例，我们深入理解了 Zustand 中状态组合的陷阱，并学会了如何正确设计复杂的状态管理架构。&lt;/p&gt;
</content:encoded></item><item><title>Socket.io 中间件机制与错误事件传递详解</title><link>https://blog.cuixu.cn/posts/websocket/socketio-middleware/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/websocket/socketio-middleware/</guid><pubDate>Thu, 17 Jul 2025 08:30:35 GMT</pubDate><content:encoded>&lt;h2&gt;中间件机制（Middleware）&lt;/h2&gt;
&lt;h3&gt;什么是 Socket.io 中间件&lt;/h3&gt;
&lt;p&gt;Socket.io 中间件是在客户端连接建立&lt;strong&gt;之前&lt;/strong&gt;执行的函数，用于验证、授权或预处理连接请求。&lt;/p&gt;
&lt;h3&gt;中间件的执行时机&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;客户端发起连接 → 中间件验证 → 连接建立/拒绝 → 触发相应事件
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;基本语法结构&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;io.use((socket, next) =&amp;gt; {
  // 验证逻辑
  if (验证通过) {
    next(); // 继续连接流程
  } else {
    next(new Error(&quot;错误信息&quot;)); // 拒绝连接并传递错误
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;中间件参数详解&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;socket&lt;/strong&gt;: 即将建立的 socket 连接对象，包含 &lt;code&gt;handshake&lt;/code&gt; 信息&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;next&lt;/strong&gt;: 回调函数，控制中间件流程的继续或中断&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;错误事件传递机制&lt;/h2&gt;
&lt;h3&gt;服务端错误传递&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// ✅ 正确方式：在中间件中传递错误
io.use((socket, next) =&amp;gt; {
  try {
    const token = socket.handshake.auth.token;
    if (!token) {
      // 通过 next 传递错误到客户端
      return next(new Error(&quot;Authentication error: No token provided&quot;));
    }

    jwt.verify(token, SECRET_KEY);
    next(); // 验证通过，继续连接
  } catch (err) {
    // JWT验证失败
    next(new Error(&quot;Authentication error: Invalid token&quot;));
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;客户端错误接收&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const socket = io({
  auth: {
    token: &quot;your_token_here&quot;,
  },
});

// 连接成功事件
socket.on(&quot;connect&quot;, () =&amp;gt; {
  console.log(&quot;✅ 连接成功&quot;);
});

// 连接错误事件 - 接收中间件传递的错误
socket.on(&quot;connect_error&quot;, (error) =&amp;gt; {
  console.log(&quot;❌ 连接失败:&quot;, error.message);
  // error.message 包含服务端 next(new Error()) 中的错误信息
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;对比：正确 vs 错误的实现方式&lt;/h2&gt;
&lt;h3&gt;❌ 错误方式：连接后断开&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 问题：连接已建立，disconnect 不会触发 connect_error
io.on(&quot;connection&quot;, (socket) =&amp;gt; {
  const token = socket.handshake.auth.token;
  if (!token) {
    socket.disconnect(true); // 客户端不会收到 connect_error
    return;
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;问题分析&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;连接已成功建立&lt;/li&gt;
&lt;li&gt;&lt;code&gt;socket.disconnect()&lt;/code&gt; 只是断开连接，不是连接失败&lt;/li&gt;
&lt;li&gt;客户端先收到 &lt;code&gt;connect&lt;/code&gt; 事件，然后收到 &lt;code&gt;disconnect&lt;/code&gt; 事件&lt;/li&gt;
&lt;li&gt;客户端的 &lt;code&gt;connect_error&lt;/code&gt; 事件永远不会触发&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;✅ 正确方式：中间件验证&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 解决方案：在连接建立前验证
io.use((socket, next) =&amp;gt; {
  const token = socket.handshake.auth.token;
  if (!token) {
    next(new Error(&quot;No token provided&quot;)); // 正确触发 connect_error
    return;
  }
  next(); // 验证通过
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;优势&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;连接根本不会建立&lt;/li&gt;
&lt;li&gt;客户端直接收到 &lt;code&gt;connect_error&lt;/code&gt; 事件&lt;/li&gt;
&lt;li&gt;错误信息清晰传递&lt;/li&gt;
&lt;li&gt;资源使用更高效&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;中间件的执行流程&lt;/h2&gt;
&lt;h3&gt;多个中间件的执行顺序&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 中间件1：基础验证
io.use((socket, next) =&amp;gt; {
  console.log(&quot;中间件1：基础验证&quot;);
  next();
});

// 中间件2：Token验证
io.use((socket, next) =&amp;gt; {
  console.log(&quot;中间件2：Token验证&quot;);
  const token = socket.handshake.auth.token;
  if (!token) {
    return next(new Error(&quot;Missing token&quot;));
  }
  next();
});

// 中间件3：权限检查
io.use((socket, next) =&amp;gt; {
  console.log(&quot;中间件3：权限检查&quot;);
  // 其他验证逻辑...
  next();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;执行顺序&lt;/strong&gt;：按注册顺序依次执行，任何一个中间件调用 &lt;code&gt;next(error)&lt;/code&gt; 都会中断流程。&lt;/p&gt;
&lt;h3&gt;中间件中的数据传递&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;io.use((socket, next) =&amp;gt; {
  try {
    const decoded = jwt.verify(token, SECRET_KEY);
    // 将解码后的用户信息挂载到 socket 对象
    socket.user = decoded;
    next();
  } catch (err) {
    next(new Error(&quot;Invalid token&quot;));
  }
});

// 在连接处理器中可以访问 socket.user
io.on(&quot;connection&quot;, (socket) =&amp;gt; {
  console.log(&quot;用户信息:&quot;, socket.user); // 可以访问中间件中设置的数据
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;实际应用场景&lt;/h2&gt;
&lt;h3&gt;1. JWT Token 验证&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;io.use((socket, next) =&amp;gt; {
  const token = socket.handshake.auth.token;
  if (!token) {
    return next(new Error(&quot;Authentication required&quot;));
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    socket.userId = decoded.userId;
    socket.userRole = decoded.role;
    next();
  } catch (err) {
    next(new Error(&quot;Invalid authentication token&quot;));
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. IP 白名单验证&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;io.use((socket, next) =&amp;gt; {
  const clientIP = socket.handshake.address;
  const allowedIPs = [&quot;127.0.0.1&quot;, &quot;192.168.1.0/24&quot;];

  if (!isIPAllowed(clientIP, allowedIPs)) {
    return next(new Error(&quot;IP address not allowed&quot;));
  }
  next();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 连接频率限制&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const connectionAttempts = new Map();

io.use((socket, next) =&amp;gt; {
  const clientIP = socket.handshake.address;
  const now = Date.now();
  const attempts = connectionAttempts.get(clientIP) || [];

  // 清理1分钟前的记录
  const recentAttempts = attempts.filter((time) =&amp;gt; now - time &amp;lt; 60000);

  if (recentAttempts.length &amp;gt;= 10) {
    return next(new Error(&quot;Too many connection attempts&quot;));
  }

  recentAttempts.push(now);
  connectionAttempts.set(clientIP, recentAttempts);
  next();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;最佳实践总结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;始终使用中间件进行连接前验证&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通过 &lt;code&gt;next(new Error())&lt;/code&gt; 传递具体的错误信息&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;在中间件中设置 socket 属性，供后续使用&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;保持中间件逻辑简洁和高效&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;合理组织多个中间件的执行顺序&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;在客户端正确处理 &lt;code&gt;connect_error&lt;/code&gt; 事件&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这样的机制确保了 Socket.io 应用的安全性和用户体验的一致性。&lt;/p&gt;
</content:encoded></item><item><title>Loki 和 Sentry 的适用场景对比</title><link>https://blog.cuixu.cn/posts/devops/sentry-and-loki/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/devops/sentry-and-loki/</guid><pubDate>Sat, 28 Jun 2025 06:28:10 GMT</pubDate><content:encoded>&lt;p&gt;在选择 Grafana Loki 和 Sentry 时，需根据你的需求（如错误跟踪、日志管理、部署复杂度和预算）进行权衡。以下是对两者在错误跟踪和日志管理方面的对比，以及选择建议，结合了你的上一个问题（Loki 单体模式部署）及搜索结果的上下文。&lt;/p&gt;
&lt;h2&gt;1. 概述&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Grafana Loki&lt;/strong&gt;：轻量级日志聚合系统，专注于存储和查询日志，设计上类似 Prometheus，强调高效和低成本。适合集中化日志管理和查询，尤其与 Grafana 仪表板集成时效果最佳。&lt;a href=&quot;https://stackshare.io/stackups/loki-vs-sentry&quot;&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sentry&lt;/strong&gt;：专注于错误跟踪和性能监控，提供详细的错误堆栈、上下文和调试信息，同时支持日志摄取。适合开发团队快速定位和修复代码问题。&lt;a href=&quot;https://stackshare.io/stackups/loki-vs-sentry&quot;&gt;&lt;/a&gt;&lt;a href=&quot;https://stackshare.io/stackups/grafana-vs-sentry&quot;&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. 对比分析&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特性&lt;/th&gt;
&lt;th&gt;Grafana Loki&lt;/th&gt;
&lt;th&gt;Sentry&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;主要功能&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;日志聚合与查询&lt;/td&gt;
&lt;td&gt;错误跟踪、性能监控、部分日志功能&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;日志管理&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;专为日志设计，支持海量日志存储和查询（如通过 LogQL）。适合基础设施和应用日志集中管理。&lt;/td&gt;
&lt;td&gt;支持日志摄取，但日志功能较弱，主要作为错误上下文的补充。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;错误跟踪&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;不提供原生错误跟踪功能，需依赖其他工具（如 Grafana Tempo 或 Sentry）来补充。&lt;/td&gt;
&lt;td&gt;强大的错误跟踪能力，提供详细堆栈跟踪、用户上下文和重现步骤，适合开发调试。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;可视化&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;与 Grafana 深度集成，提供灵活的仪表板和日志可视化。&lt;/td&gt;
&lt;td&gt;自带仪表板，专注于错误和性能趋势，定制化程度低于 Grafana。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;部署复杂性&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;单体模式简单（Docker/Helm 部署，参考上文），但微服务模式复杂。适合 Kubernetes 环境。&lt;/td&gt;
&lt;td&gt;SaaS 为主，开箱即用；自托管选项存在，但配置较复杂。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;成本&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;开源免费，成本主要来自存储和基础设施（如 S3）。&lt;/td&gt;
&lt;td&gt;免费层有限，付费计划按事件量计费，成本可能较高。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;集成性&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;与 Prometheus、Tempo 和 Grafana 生态无缝集成，适合全栈可观测性。&lt;/td&gt;
&lt;td&gt;支持多种语言和框架，可与 Grafana 集成（如通过插件将 Sentry 数据可视化）。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;适用场景&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;- 日志量大、需集中存储和查询&amp;lt;br&amp;gt;- 已有 Grafana 生态&amp;lt;br&amp;gt;- 预算有限&lt;/td&gt;
&lt;td&gt;- 需深入错误调试&amp;lt;br&amp;gt;- 开发团队关注代码健康&amp;lt;br&amp;gt;- 愿意为 SaaS 付费&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;3. 选择建议&lt;/h2&gt;
&lt;p&gt;基于你的问题背景（询问 Loki 单体模式部署），推测你可能倾向于简单、开源的解决方案。以下是具体建议：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;选择 Loki 的场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你需要一个&lt;strong&gt;集中化日志系统&lt;/strong&gt;来管理基础设施或应用日志（例如 Kubernetes 日志、服务器日志）。&lt;/li&gt;
&lt;li&gt;你已经使用或计划使用 Grafana 生态（Prometheus、Tempo、Grafana），希望通过 Grafana 仪表板实现统一的日志和指标可视化。&lt;/li&gt;
&lt;li&gt;你倾向于&lt;strong&gt;开源&lt;/strong&gt;和&lt;strong&gt;低成本&lt;/strong&gt;，愿意自托管并接受单体模式（参考上文 Docker/Helm 部署）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;选择 Sentry 的场景&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;你的主要需求是&lt;strong&gt;错误跟踪&lt;/strong&gt;，希望快速定位代码问题（如 Python 异常、JavaScript 错误）。&lt;/li&gt;
&lt;li&gt;你需要详细的错误上下文（堆栈跟踪、用户会话、设备信息）来加速调试。&lt;/li&gt;
&lt;li&gt;你更倾向于&lt;strong&gt;SaaS 解决方案&lt;/strong&gt;，不想过多管理基础设施。&lt;/li&gt;
&lt;li&gt;你预算允许按事件量付费，或仅需免费层支持小规模项目。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;组合使用&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果你需要&lt;strong&gt;全栈可观测性&lt;/strong&gt;（日志 + 错误跟踪），可以结合两者：
&lt;ul&gt;
&lt;li&gt;用 &lt;strong&gt;Loki&lt;/strong&gt; 存储和查询日志，** 利用其高效的日志聚合能力。&lt;/li&gt;
&lt;li&gt;用 &lt;strong&gt;Sentry&lt;/strong&gt; 处理错误跟踪，补充 Loki 的不足。&lt;/li&gt;
&lt;li&gt;通过 Grafana 的 Sentry 插件，将 Sentry 的事件数据导入 Grafana 仪表板，实现统一监控。&lt;a href=&quot;https://sentry.io/integrations/grafana/&quot;&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;这种方法适合有 Grafana 生态且开发团队需要深入调试的场景。许多团队将基础设施日志送至 Loki，应用错误日志送至 Sentry。&lt;a href=&quot;https://sentry.io/vs/logging/&quot;&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;针对你的单体模式需求&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果你选择 Loki 的单体模式（可能是测试或小规模部署），Loki 是更合适的起点。它的单体模式部署（Docker 或 Helm）简单，适合快速验证日志功能（参考上文）。但如果你发现需要错误跟踪，需额外集成 Sentry 或 Grafana Tempo。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4. 总结&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;优先 Loki&lt;/strong&gt;：如果你专注于日志管理、已有 Grafana 生态、或预算有限，Loki 单体模式是低成本、高效率的选择。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优先 Sentry&lt;/strong&gt;：如果你更关心错误跟踪和代码调试，Sentry 的专业功能更适合开发团队。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;组合方案&lt;/strong&gt;：Loki 用于日志，Sentry 用于错误，搭配 Grafana 可视化，适合复杂需求。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你有更具体的场景细节（如日志量、语言框架、是否已有 Grafana），我可以进一步优化建议！&lt;/p&gt;
</content:encoded></item><item><title>Intro Trickle Ice</title><link>https://blog.cuixu.cn/posts/webrtc/intro-trickle-ice/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/webrtc/intro-trickle-ice/</guid><pubDate>Wed, 25 Jun 2025 06:50:21 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;Trickle ICE&lt;/strong&gt;（增量式 ICE）是一种优化WebRTC连接建立速度的技术，属于 &lt;strong&gt;Interactive Connectivity Establishment (ICE)&lt;/strong&gt; 机制的一种扩展方式。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;一、基本概念与原理：&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;传统的 &lt;strong&gt;ICE&lt;/strong&gt; 流程是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;候选地址收集&lt;/strong&gt;：获取所有网络接口的本地候选地址（如本地IP、STUN服务器反射地址、TURN服务器中继地址）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;候选地址交换&lt;/strong&gt;：候选地址完全收集完成后，一次性发送给对端（通常通过SDP）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;连接检查&lt;/strong&gt;：双方获取地址后尝试相互连接，找到可用的路径并建立媒体通信。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;但这个过程存在一个问题：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;收集地址可能需要一定时间，特别是STUN、TURN服务器响应可能较慢。
&lt;strong&gt;因此，在传统模式下，ICE需要等待候选地址完全收集完毕后再交换，延迟较高。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;为优化连接建立速度，&lt;strong&gt;Trickle ICE&lt;/strong&gt; 被提出：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;增量式发送&lt;/strong&gt;候选地址，而非一次性发送全部。&lt;/li&gt;
&lt;li&gt;只要有新的候选地址出现，就立即发送给对方。&lt;/li&gt;
&lt;li&gt;对方收到后也会立即开始连接检查，不再等待完整候选地址列表。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;二、Trickle ICE的工作流程：&lt;/strong&gt;&lt;/h3&gt;
&lt;h4&gt;&lt;strong&gt;步骤：&lt;/strong&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;发起Offer&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Caller (Offerer)创建一个初始Offer，通常携带已有的少量候选地址（或为空），标明使用a=ice-options:trickle。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;快速响应Answer&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;被呼叫方（Answerer）尽快响应Answer（也可能是空或少量地址），也标注a=ice-options:trickle。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;增量发送候选（Trickling）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;双方后续每次获得新候选地址时，都通过信令通道单独传送给对方，不再等待所有候选地址完全收集完毕。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;连接检查同步进行&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;每收到一个候选地址，双方立即开始对该地址进行连通性检查（connectivity check）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;连接建立&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一旦某条路径通过连接检查成功，就建立连接并开始媒体通信。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Trickle ICE 完成标记&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当候选地址全部发送完毕后，会发送一个特殊标记表示候选地址收集完成，例如end-of-candidates。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;&lt;strong&gt;三、Trickle ICE的优势：&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;更快的连接建立&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;候选地址收集和连接检查并行进行，连接延迟大幅降低（从几秒降低到几百毫秒）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;灵活性提升&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;特别适用于移动或不稳定的网络环境中，可以快速响应网络状况变化。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;四、典型应用场景：&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;实时音视频通信&lt;/strong&gt;（如WebRTC通话、视频会议、语音通话）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实时游戏或协作应用&lt;/strong&gt;，对连接建立时延敏感的场景。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;五、Trickle ICE示例SDP片段：&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Offer示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;v=0
...
a=ice-options:trickle
a=candidate:1 1 UDP 2130706431 192.0.2.1 3478 typ host
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;之后，通过信令通道逐步发送新候选：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;candidate&quot;: &quot;candidate:2 1 UDP 1694498815 198.51.100.1 8998 typ srflx raddr 192.0.2.1 rport 3478&quot;,
  &quot;sdpMid&quot;: &quot;audio&quot;,
  &quot;sdpMLineIndex&quot;: 0
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;候选地址发送完毕：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;candidate&quot;: &quot;&quot;,
  &quot;sdpMid&quot;: &quot;audio&quot;,
  &quot;sdpMLineIndex&quot;: 0
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;六、需要注意的问题：&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;信令通道要求&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;需要支持实时的候选地址推送。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;兼容性&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;部分老旧设备或客户端可能不支持Trickle ICE，需要提供回退方案（即ICE完成后再交换完整的候选地址）。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;七、与非Trickle ICE的对比总结：&lt;/strong&gt;&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;特性&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;非Trickle ICE（传统）&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Trickle ICE（增量式）&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;候选地址发送&lt;/td&gt;
&lt;td&gt;一次性全部发送&lt;/td&gt;
&lt;td&gt;逐步增量发送&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;连接延迟&lt;/td&gt;
&lt;td&gt;较高（数秒）&lt;/td&gt;
&lt;td&gt;较低（毫秒级）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;兼容性&lt;/td&gt;
&lt;td&gt;好&lt;/td&gt;
&lt;td&gt;较新，可能需要兼容性处理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;复杂性&lt;/td&gt;
&lt;td&gt;较低&lt;/td&gt;
&lt;td&gt;略高&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;&lt;strong&gt;总结&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Trickle ICE&lt;/strong&gt; 显著提高了连接建立的速度，成为WebRTC实时通信领域的最佳实践之一。实际开发过程中，建议优先使用Trickle ICE，并考虑适当的回退机制以兼容老旧设备和客户端。&lt;/p&gt;
</content:encoded></item><item><title>Node.js 和 Go：哪个更适合写爬虫？</title><link>https://blog.cuixu.cn/posts/node/spider-compoared-with-golang/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/node/spider-compoared-with-golang/</guid><description>在选择使用 Node.js 或 Go 来实现站点爬取和内容分析时，需要根据项目需求和各语言的特点来权衡。本文从性能、开发效率、并发处理和复杂网页支持等方面对两者进行对比分析。</description><pubDate>Wed, 23 Oct 2024 08:54:27 GMT</pubDate><content:encoded>&lt;p&gt;在实现站点爬取和内容分析时，Node.js 和 Go 是两种常见的选择。它们各有优劣，适合不同的场景。本文将从性能、开发效率、并发处理和复杂网页支持等方面对两者进行详细对比，帮助你根据项目需求做出选择。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 性能对比&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Go&lt;/strong&gt;：作为编译型语言，Go 的性能非常优越，尤其适合处理高并发任务。Go 内置的 goroutines 使得并发爬取变得简单高效，在处理大量网络请求时表现尤为突出。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Node.js&lt;/strong&gt;：Node.js 基于事件驱动的异步非阻塞模型，在 I/O 密集型任务中表现良好。然而，由于其是解释型语言，性能稍逊于 Go。在处理大量任务时，可能会遇到内存和性能瓶颈。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：如果项目对高并发和高性能有较高要求，Go 是更好的选择。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 开发效率与生态&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Go&lt;/strong&gt;：Go 的语法简洁，开发效率高，错误处理清晰，适合编写健壮的爬虫程序。然而，Go 的网页解析库相对较少，处理 HTML 等操作时需要更多手动工作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Node.js&lt;/strong&gt;：Node.js 拥有丰富的第三方库和工具，例如 &lt;code&gt;axios&lt;/code&gt;、&lt;code&gt;cheerio&lt;/code&gt; 和 &lt;code&gt;puppeteer&lt;/code&gt;，可以快速实现爬虫功能。这些库极大地降低了开发复杂度，尤其是在处理 HTML 解析和动态页面时。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：如果优先考虑开发速度和生态丰富性，Node.js 更适合。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 并发与内存管理&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Go&lt;/strong&gt;：Go 的 goroutines 非常轻量，能够高效处理大量并发任务。同时，Go 的内存管理机制更加高效，适合大规模爬取任务。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Node.js&lt;/strong&gt;：Node.js 的事件循环机制在一定规模的并发任务中表现良好，但在处理过多并发请求时，可能会面临内存压力和事件循环阻塞的问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：对于高并发和内存密集型任务，Go 更具优势。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 动态网页处理能力&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Go&lt;/strong&gt;：Go 在处理动态页面（如 JavaScript 渲染内容）时并不占优势。虽然可以使用 &lt;code&gt;chromedp&lt;/code&gt; 等库实现浏览器自动化，但开发复杂度较高。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Node.js&lt;/strong&gt;：Node.js 借助 &lt;code&gt;puppeteer&lt;/code&gt; 或 &lt;code&gt;playwright&lt;/code&gt; 等工具，可以轻松控制浏览器，处理动态内容和模拟用户行为。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：如果需要处理 JavaScript 渲染的动态内容，Node.js 是更好的选择。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;对比维度&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Go&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Node.js&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;性能&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;高性能，适合高并发任务&lt;/td&gt;
&lt;td&gt;性能稍逊，适合 I/O 密集型任务&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;开发效率&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;语法简单，但生态不如 Node.js 丰富&lt;/td&gt;
&lt;td&gt;丰富的库和工具，开发速度快&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;并发与内存管理&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;goroutines 高效，适合大规模爬取任务&lt;/td&gt;
&lt;td&gt;事件循环机制，适合中小规模并发任务&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;动态网页处理&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;支持有限，开发复杂度高&lt;/td&gt;
&lt;td&gt;借助工具轻松处理动态内容&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;选择 Go&lt;/strong&gt;：适合高并发、大规模爬取、对动态内容处理需求较少的项目。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;选择 Node.js&lt;/strong&gt;：适合快速开发、处理动态网页、依赖丰富第三方库的项目。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;根据项目需求权衡选择，才能更高效地完成爬虫开发任务&lt;/p&gt;
</content:encoded></item><item><title>减少 Tailwind 中重复和冗长的 className</title><link>https://blog.cuixu.cn/posts/tailwind/reduce-classnames/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/tailwind/reduce-classnames/</guid><description>在使用 Tailwind 时，很容易出现大量重复冗余的 className， 下面是减少重复和冗长 className 的方式</description><pubDate>Wed, 23 Oct 2024 08:48:32 GMT</pubDate><content:encoded>&lt;p&gt;有几种方法可以减少 Tailwind 中重复和冗长的 &lt;code&gt;className&lt;/code&gt;：&lt;/p&gt;
&lt;h3&gt;1. 使用 &lt;code&gt;@apply&lt;/code&gt; 语法&lt;/h3&gt;
&lt;p&gt;在自定义的 CSS 文件中，使用 Tailwind 的 &lt;code&gt;@apply&lt;/code&gt; 语法将常用的样式组合成一个自定义的类。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* styles.css */
.btn-primary {
  @apply bg-blue-500 text-white py-2 px-4 rounded;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在组件中使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;button className=&quot;btn-primary&quot;&amp;gt;Click me&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样可以减少 JSX 文件中的 &lt;code&gt;className&lt;/code&gt; 冗余。&lt;/p&gt;
&lt;h3&gt;2. 封装组件&lt;/h3&gt;
&lt;p&gt;将常用的样式封装成可复用的组件。例如，将按钮样式封装为一个 &lt;code&gt;Button&lt;/code&gt; 组件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const Button = ({ children, className, ...props }) =&amp;gt; (
  &amp;lt;button
    className={`bg-blue-500 text-white py-2 px-4 rounded ${className}`}
    {...props}
  &amp;gt;
    {children}
  &amp;lt;/button&amp;gt;
);

// 使用
&amp;lt;Button className=&quot;my-2&quot;&amp;gt;Click me&amp;lt;/Button&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过这种方式，可以保持代码的简洁并提高复用性。&lt;/p&gt;
&lt;h3&gt;3. 使用 &lt;code&gt;clsx&lt;/code&gt; 或 &lt;code&gt;classnames&lt;/code&gt; 库&lt;/h3&gt;
&lt;p&gt;这些库可以帮助你有条件地组合多个 &lt;code&gt;className&lt;/code&gt;，减少重复性。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import clsx from &quot;clsx&quot;;

const isActive = true;
const buttonClass = clsx(&quot;bg-blue-500 text-white py-2 px-4 rounded&quot;, {
  &quot;opacity-50&quot;: !isActive,
});

&amp;lt;button className={buttonClass}&amp;gt;Click me&amp;lt;/button&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;clsx&lt;/code&gt; 或 &lt;code&gt;classnames&lt;/code&gt; 可以根据条件动态添加类名，避免硬编码所有的 &lt;code&gt;className&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;4. 使用 Tailwind 的 &lt;code&gt;theme&lt;/code&gt; 配置&lt;/h3&gt;
&lt;p&gt;通过修改 Tailwind 配置文件（&lt;code&gt;tailwind.config.js&lt;/code&gt;）来扩展主题或者自定义一些快捷类。例如，可以为常用的颜色或者尺寸创建更易读的别名：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        primary: &quot;#3490dc&quot;,
      },
    },
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在项目中直接使用自定义颜色：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div className=&quot;bg-primary text-white&quot;&amp;gt;Hello, Tailwind!&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. 结合 Tailwind 和 CSS-in-JS 库&lt;/h3&gt;
&lt;p&gt;可以搭配一些 CSS-in-JS 库（例如 &lt;code&gt;styled-components&lt;/code&gt; 或 &lt;code&gt;Emotion&lt;/code&gt;），结合使用 Tailwind 和自定义样式。这样可以在样式复用性和代码简洁性之间取得平衡。&lt;/p&gt;
&lt;p&gt;通过这些方法，可以有效减少重复的 &lt;code&gt;className&lt;/code&gt;，提高代码的可维护性和可读性。&lt;/p&gt;
</content:encoded></item><item><title>Err Ossl Evp Unsupported</title><link>https://blog.cuixu.cn/posts/node/err-ossl-evp-unsupported/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/node/err-ossl-evp-unsupported/</guid><description>error:03000086:digital envelope routines::initialization error,问题原因是nodejs升级到17后，openssl3.0 对允许算法密钥大小增加了严格的限制，临时的解决办法是配置node_options </description><pubDate>Tue, 22 Aug 2023 02:59:50 GMT</pubDate><content:encoded>&lt;p&gt;一个比较老的Vue项目，在启动时候，报错：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  opensslErrorStack: [ &apos;error:03000086:digital envelope routines::initialization error&apos; ],
  library: &apos;digital envelope routines&apos;,
  reason: &apos;unsupported&apos;,
  code: &apos;ERR_OSSL_EVP_UNSUPPORTED&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;问题原因是nodejs升级到17后，openssl3.0 对允许算法密钥大小增加了严格的限制，临时的解决办法是配置node_options.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
# Windows 环境

$env:NODE_OPTIONS=&quot;--openssl-legacy-provider&quot;
#or
set NODE_OPTIONS=--openssl-legacy-provider


# Linux or Mac
export NODE_OPTIONS=--openssl-legacy-provider
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上述的方法是一次性的，也可以在package.json中对script进行修改，如&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
...
&quot;serve&quot; : &quot;SET NODE_OPTIONS=--openssl-legacy-provider &amp;amp;&amp;amp; vue-cli-service serve&quot;
...

&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>PHP代码质量工具之Laravel Pint</title><link>https://blog.cuixu.cn/posts/php/code-quality-tools/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/php/code-quality-tools/</guid><description>这五款工具旨在提高代码质量、及早发现错误、确保统一的编码风格并遵循最佳实践，让你的 Laravel 代码保持最佳状态，避免出现错误。</description><pubDate>Thu, 10 Aug 2023 04:19:13 GMT</pubDate><content:encoded>&lt;p&gt;作为开发人员，我们有责任尽可能保持代码的最佳状态。这不仅有助于维护和扩展代码，还能避免出现错误。&lt;/p&gt;
&lt;p&gt;在 PHP 中，有许多工具可以帮助我们实现高质量和无错误的代码，包括 PHP Mess detector、PHP CodeSniffer、PHPStan 和 Psalm。这些工具都很不错，但要集成到 Laravel 项目中并不容易。&lt;/p&gt;
&lt;p&gt;后面我们会介绍五款专门用于帮助你及早捕捉错误、提高 Laravel 代码质量、确保编码风格统一的工具，让你在 Laravel 项目中轻松遵循最佳实践，现在我们先来说说 Laravel Pint。&lt;/p&gt;
&lt;p&gt;我们还将介绍文本编辑器集成和使用这些工具的持续集成。&lt;/p&gt;
&lt;h3&gt;前提条件&lt;/h3&gt;
&lt;p&gt;本教程假定您已设置好 PHP 开发环境。本教程还假定您有 PHP 和 Laravel 方面的经验。&lt;/p&gt;
&lt;p&gt;要充分利用本教程，我强烈建议您跟读本教程，以便亲自了解和测试这些工具是如何工作的。&lt;/p&gt;
&lt;p&gt;为此，请克隆以下 repo：food-order-app-laravel-tdd：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/anchetaWern/food-order-app-laravel-tdd
cd food-order-app-laravel-tdd
git checkout tdd

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来，将 .env.example 文件重命名为 .env，并用本地数据库密码更新 DB_PASSWORD。
完成上述操作后，执行以下命令设置项目：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;composer install
npm 安装
php artisan config:cache
php artisan migrate
php artisan db:seed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样就能安装所有的 composer 和前端依赖项，更新配置，迁移数据库并为其播种。&lt;/p&gt;
&lt;p&gt;我建议你安装 GitHub for desktop 或其他 Git 图形用户界面工具，这样你就能看到这些工具对你的代码做了什么。&lt;/p&gt;
&lt;h3&gt;什么是静态代码分析？&lt;/h3&gt;
&lt;p&gt;静态分析允许您在不运行代码的情况下检查代码是否存在问题。它基本上是读取代码、理解代码并将代码与特定规则进行比较，以验证代码的正确性。下面是一个例子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function calculateSalary(int $days_worked, int $hours_worked_per_day): int
{
    ...
}
calculateSalary(&apos;20&apos;, 8)；
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;仅仅看一下，我们就已经知道代码会失败，因为我们为第一个参数提供的是字符串而不是整数。这就是静态代码分析的基本工作原理。不过，为了让静态代码分析更好地发挥作用，您需要在代码中使用类型（通过类型声明或 DocBlocks）。这将使检查更有效，有助于发现代码中的问题。&lt;/p&gt;
&lt;p&gt;需要注意的是，静态分析器已经足够聪明，可以推断出大多数类型，因此在旧项目中添加类型并不是利用静态分析器的必要条件。&lt;/p&gt;
&lt;p&gt;静态分析器会一次性扫描整个代码库。这意味着它们的工作环境与人类的代码审查环境不同。静态分析器知道不同文件之间是如何连接在一起的，这就是为什么它们能够发现代码审查中通常无法发现的问题。&lt;/p&gt;
&lt;p&gt;你可能会想，既然我们已经使用了 PHPUnit 或 Pest 等工具来测试代码，为什么还需要静态分析器呢？这是因为这些工具不能立即使用。您必须先编写测试，然后才能使用它们来检测代码中的问题。通常情况下，运行测试也很耗时，因为它们需要执行你的代码。这就是静态分析和动态分析的主要区别。前者扫描代码，后者运行代码来检测问题。&lt;/p&gt;
&lt;p&gt;使用代码分析工具有以下好处：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;执行团队制定的编码标准。&lt;/li&gt;
&lt;li&gt;确保遵循统一的编码风格。&lt;/li&gt;
&lt;li&gt;及早发现潜在问题，这意味着向生产交付的错误更少。&lt;/li&gt;
&lt;li&gt;提高代码质量。&lt;/li&gt;
&lt;li&gt;检测安全漏洞。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Laravel pint&lt;/h3&gt;
&lt;p&gt;Laravel Pint 是检查代码质量的最新工具。从 Laravel 9 开始，创建新的 Laravel 应用程序时，Pint 会自动安装。它还自带检查的默认配置。这使得它很容易上手，因为你不需要做任何其他事情。唯一的缺点是它只支持 PHP 8 项目。&lt;/p&gt;
&lt;h4&gt;安装&lt;/h4&gt;
&lt;p&gt;如果你使用的是旧版本的 Laravel，可以用下面的命令安装 Pint：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;composer require laravel/pint --dev
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;使用方法&lt;/h4&gt;
&lt;p&gt;要使用 Pint，请执行以下命令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./vendor/bin/pint --test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这将显示 Pint 在你的代码中检测到的所有问题：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.vexrobot.cn/docs/mgentomgentopint-test.png&quot; alt=&quot;Output of running the Pint test command, showing several code style issues&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这样你就能知道它使用了哪些规则。输出结果并不容易阅读，因此建议设置 Pint 自动帮你解决问题：&lt;/p&gt;
&lt;p&gt;./vendor/bin/pint
然后，你就可以在自己选择的 Git GUI 工具上查看输出结果，了解它做了哪些改动：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.vexrobot.cn/docs/mgentomgentopint-changes.png&quot; alt=&quot;Changes made by Pint, displayed on a diff checker tool&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在上面的截图中，你可以看到它自动删除了未使用的导入，并加入了额外的空白来提高可读性。不过，它还会根据你的代码做更多改动。在提交之前，请务必查看它所做的更改。如果感觉有些地方不对，你可以直接还原更改，然后手动执行它建议的更改。&lt;/p&gt;
&lt;p&gt;如果你没有安装 Git 图形用户界面工具，可以让 Pint 帮你打印它所做的修改：&lt;/p&gt;
&lt;p&gt;./vendor/bin/pint -v
下面是输出示例：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.vexrobot.cn/docs/mgentomgentoprint-pint-changes.png&quot; alt=&quot;Output of changes made by pint, displayed in the terminal&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Configuration&lt;/h3&gt;
&lt;p&gt;Pint 默认使用 laravel 预设规则，因此如果要使用其中的规则，无需做任何其他操作。您可以在项目目录根目录下创建 pint.json 文件来配置 Pint：&lt;/p&gt;
&lt;p&gt;{
&quot;preset&quot;: &quot;laravel&quot;
}
其他支持的预设有 symfony 和 psr12。您可以这样更改&lt;/p&gt;
&lt;p&gt;pint --preset psr12&lt;/p&gt;
&lt;p&gt;如果不想使用这些预设，唯一的选择就是手工指定所有规则。Pint 建立在 PHP CS Fixer 之上，因此可以使用它的所有规则。您可以使用 PHP-CS-Fixer 配置器来挑选您自己的规则集。&lt;/p&gt;
&lt;p&gt;由于规则很多，所以您很可能会以现有的规则集为基础。例如，有 CS Fixer 套件：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.vexrobot.cn/docs/mgentomgentocs-fixer-set.png&quot; alt=&quot;PHPCS fixer set in PHPCS fixer configurator&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Here&apos;s a sample &lt;code&gt;pint.json&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;preset&quot;: &quot;laravel&quot;,
    &quot;rules&quot;: {
        &quot;simplified_null_return&quot;: true,
        &quot;braces&quot;: false,
        &quot;new_with_braces&quot;: {
            &quot;anonymous_class&quot;: false,
            &quot;named_class&quot;: false
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;请注意，您只能禁用现有预设中的规则。您不能指定一两个规则，然后指望 Pint 只检查这些规则。&lt;/p&gt;
&lt;p&gt;有关 Pint 的更多信息，请参阅 Laravel 文档。&lt;/p&gt;
</content:encoded></item><item><title>magento2中opensearch或mysql作为catalog search engine的区别</title><link>https://blog.cuixu.cn/posts/magento/catalog-engine-mysql/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/magento/catalog-engine-mysql/</guid><description>magento2中opensearch或mysql作为catalog search engine的区别</description><pubDate>Wed, 26 Jul 2023 06:42:33 GMT</pubDate><content:encoded>&lt;p&gt;在Magento 2中，你可以使用OpenSearch或MySQL作为Catalog Search Engine。这两种搜索引擎在功能上有一些显著的不同：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;性能和规模&lt;/strong&gt;：如果你需要处理大量的数据并且需要更好的性能和扩展性，那么OpenSearch很可能是更好的选择。OpenSearch是一种基于分布式架构的搜索引擎，可以通过增加节点来扩展性能和容量。MySQL作为关系型数据库，性能和容量的扩展受到硬件和软件的限制。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;搜索质量&lt;/strong&gt;：OpenSearch使用先进的全文搜索算法，可以更好地处理模糊查询、拼写错误和语言处理等问题。相比之下，MySQL的搜索算法可能更加基础，但它也可以通过使用全文索引等技术来提高搜索质量。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;集成和配置&lt;/strong&gt;：集成OpenSearch可能需要更多的配置和工作，因为它是一个独立的搜索引擎。相比之下，Magento 2已经默认集成了MySQL，因此使用MySQL作为Catalog Search Engine可能需要更少的配置和工作。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;总的来说，如果你需要更好的性能和扩展性，以及更高质量的搜索结果，那么OpenSearch可能是更好的选择。如果你正在寻找一种简单易用的搜索引擎，并且你已经熟悉了MySQL，那么使用MySQL作为Catalog Search Engine可能是更好的选择。&lt;/p&gt;
</content:encoded></item><item><title>PHP 8.2：动态属性被弃用</title><link>https://blog.cuixu.cn/posts/php/dynamic-properties-deprecated/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/php/dynamic-properties-deprecated/</guid><pubDate>Wed, 05 Jul 2023 09:16:27 GMT</pubDate><content:encoded>&lt;p&gt;在PHP类中，有可能动态地设置和检索未被声明的类属性。这些属性不遵守特定的（类似于类型化的属性），它需要使用&lt;strong&gt;get()和&lt;/strong&gt;set()魔法方法来有效地防止或控制动态属性的设置和检索方式。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class User {
    private int $uid;
}

$user = new User();
$user-&amp;gt;name = &apos;Foo&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在上面的片段中，User类没有声明一个名称为name的属性，但由于允许动态属性，PHP允许设置它。&lt;/p&gt;
&lt;p&gt;虽然动态属性提供了创建类的灵活性，如没有严格的类声明的价值对象，但它为应用程序中潜在的错误和意外行为提供了可能性。例如，由于PHP默许所有的动态属性，设置属性的语句中的一个错别字可能会被忽略。&lt;/p&gt;
&lt;p&gt;在 PHP 8.2 及以后的版本中，为未声明的类属性设置一个值是被废弃的，并且在应用程序执行过程中第一次设置该属性时，会发出废弃通知。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class User {
    private int $uid;
}

$user = new User();
$user-&amp;gt;name = &apos;Foo&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Deprecated: Creation of dynamic property User::$name is deprecated in ... on line ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;从类中设置属性也会发出废弃通知：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class User {
    public function __construct() {
        $this-&amp;gt;name = &apos;test&apos;;
    }
}

new User();
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Deprecated: Creation of dynamic property User::$name is deprecated in ... on line ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;动态属性有合法的用例，比如从动态JSON响应中得到的值对象，或者允许任意值的配置对象。&lt;/p&gt;
&lt;p&gt;理想情况下，类应该在类中声明动态属性以避免废弃通知。不需要用一个属性类型来声明该属性。&lt;/p&gt;
&lt;h3&gt;豁免的动态属性模式&lt;/h3&gt;
&lt;p&gt;这种废弃有三种例外情况。使用下面的方法之一可以避免废弃的通知。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://php.watch/versions/8.2/dynamic-properties-deprecated#AllowDynamicProperties&quot;&gt;Classes with &lt;code&gt;#[AllowDynamicProperties]&lt;/code&gt; attribute&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://php.watch/versions/8.2/dynamic-properties-deprecated#stdClass&quot;&gt;&lt;code&gt;stdClass&lt;/code&gt; and its sub-classes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://php.watch/versions/8.2/dynamic-properties-deprecated#__get-__set&quot;&gt;Classes with &lt;code&gt;__get&lt;/code&gt; and &lt;code&gt;__set&lt;/code&gt; magic methods&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;参考： https://php.watch/versions/8.2/dynamic-properties-deprecated#exempt&lt;/p&gt;
</content:encoded></item><item><title>GD库导致Magento2图片处理异常的问题处理</title><link>https://blog.cuixu.cn/posts/magento2-image-resize-issue/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/magento2-image-resize-issue/</guid><pubDate>Mon, 19 Jun 2023 07:38:37 GMT</pubDate><content:encoded>&lt;h4&gt;问题描述&lt;/h4&gt;
&lt;p&gt;部分产品图片无法正常加载，手动resize图片，报错。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
bin/magento catalog:images:resize

# warning show:
# ... imagecreatefrompng(): gd-png: libpng warning: Interlace handling should be turned on when using png_read_image ...

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查了一下这个问题，因该是 gd 库的bug 导致的报错，受系统环境制约，升级php版本及相关插件风险比较大，好再 Magento 提供了 GD2（默认） 和 ImageMagic 两种库的选择，可以绕过该问题。&lt;/p&gt;
&lt;h4&gt;安装配置 imageMagic&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;
yum install php-pear php-devel gcc
yum install ImageMagick ImageMagick-devel ImageMagick-perl
pecl install imagick

echo extension=imagick.so &amp;gt;&amp;gt; /etc/php.ini
systemctl restart php-fpm.service

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;修改默认Adapter&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;
# show default adapter
bin/magento config:show dev/image/default_adapter
GD2
# set imagick as default adapter
bin/magento config:set dev/image/default_adapter IMAGEMAGICK

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后刷新缓存，重新resize，一切正常。&lt;/p&gt;
</content:encoded></item><item><title>brew切换php版本不生效的改进方案</title><link>https://blog.cuixu.cn/posts/switch-php-version/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/switch-php-version/</guid><pubDate>Tue, 23 May 2023 08:10:15 GMT</pubDate><content:encoded>&lt;p&gt;mac环境随时随地方便的切换php版本，前提如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mac 环境, zsh为默认shell&lt;/li&gt;
&lt;li&gt;安装了brew&lt;/li&gt;
&lt;li&gt;通过brew 安装了多个版本的php&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;基本方式&lt;/h2&gt;
&lt;p&gt;举例，从php.7.4切换到php8.1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;brew unlink php7.4 &amp;amp;&amp;amp; brew link --overwrite --force php@8.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;到这一步，如果运行 php -v ，那么版本还是7.4 ，因为还需手动更新.zshrc文件中的PATH路径，以确保使用正确的PHP版本。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vim ~/.zshrc

#注释掉下面两行
export PATH=&quot;/usr/local/opt/php@7.4/bin:$PATH&quot;
export PATH=&quot;/usr/local/opt/php@7.4/sbin:$PATH&quot;

#手动添加如下两行
export PATH=&quot;/usr/local/opt/php@8.1/bin:$PATH&quot;
export PATH=&quot;/usr/local/opt/php@8.1/sbin:$PATH&quot;

#保存后运行下面命令之使生效
source ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;改进方式&lt;/h2&gt;
&lt;p&gt;将上面提到的命令写成bash脚本，一次运行。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
#!/bin/zsh
if [ &quot;$1&quot; = &quot;7.4&quot; ]; then
    brew unlink php@8.1 &amp;amp;&amp;amp; brew link --overwrite --force php@7.4
    sed -i &apos;&apos; &apos;/php@[0-9\.]*\//d&apos; ~/.zshrc
    echo &apos;export PATH=&quot;/usr/local/opt/php@7.4/bin:$PATH&quot;&apos; &amp;gt;&amp;gt; ~/.zshrc
    echo &apos;export PATH=&quot;/usr/local/opt/php@7.4/sbin:$PATH&quot;&apos; &amp;gt;&amp;gt; ~/.zshrc
elif [ &quot;$1&quot; = &quot;8.1&quot; ]; then
    brew unlink php@7.4 &amp;amp;&amp;amp; brew link --overwrite --force php@8.1
    sed -i &apos;&apos; &apos;/php@[0-9\.]*\//d&apos; ~/.zshrc
    echo &apos;export PATH=&quot;/usr/local/opt/php@8.1/bin:$PATH&quot;&apos; &amp;gt;&amp;gt; ~/.zshrc
    echo &apos;export PATH=&quot;/usr/local/opt/php@8.1/sbin:$PATH&quot;&apos; &amp;gt;&amp;gt; ~/.zshrc
else
    echo &quot;Invalid argument. Please enter either &apos;7.4&apos; or &apos;8.1&apos;.&quot;
    exit 1
fi

source ~/.zshrc

&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;文件需要保存为.zsh后缀，首行声明 #!/bin/zsh , 否则会报错：Oh My Zsh can&apos;t be loaded from: sh. You need to run zsh instead. 因为脚本在Bash中运行，而不是在zsh中运行。解决办法是将脚本转换为zsh脚本，将脚本的文件扩展名从&lt;code&gt;.sh&lt;/code&gt;更改为&lt;code&gt;.zsh&lt;/code&gt;，然后在脚本的开头添加以下行：#!/usr/bin/env zsh； 或者脚本的开头添加以下行：#!/bin/zsh&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;然后运行下面的命令执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
./switch-php.zsh 7.4
#OR
./switch-php.zsh 8.1

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然而，脚本中的最后一行似乎没有生效。原因是在脚本中执行&lt;code&gt;source ~/.zshrc&lt;/code&gt;，则会在子shell中运行，该子shell无法影响父shell的环境。因此，即使在脚本中添加了该行，也不会影响当前的终端会话。&lt;/p&gt;
&lt;p&gt;为了解决这个问题，可以将脚本转换为函数，并将其添加到您的.zshrc文件中。这样，每次打开终端时，都会将该函数加载到终端会话中，并且可以在当前终端会话中直接调用该函数来切换PHP版本。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;因为脚本会更改 .zshrc 文件自身，为了防止函数本身被修改，我们将函数放在另一个文件中，然后从.zshrc中引入。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;新建文件custom.zsh&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/zsh
function switch-php() {
if [ &quot;$1&quot; = &quot;7.4&quot; ]; then

    brew unlink php@8.1 &amp;amp;&amp;amp; brew link --overwrite --force php@7.4
    sed -i &apos;&apos; &apos;/php@[0-9\.]*\//d&apos; ~/.zshrc
    echo &apos;export PATH=&quot;/usr/local/opt/php@7.4/bin:$PATH&quot;&apos; &amp;gt;&amp;gt; ~/.zshrc
    echo &apos;export PATH=&quot;/usr/local/opt/php@7.4/sbin:$PATH&quot;&apos; &amp;gt;&amp;gt; ~/.zshrc
elif [ &quot;$1&quot; = &quot;8.1&quot; ]; then
    brew unlink php@7.4 &amp;amp;&amp;amp; brew link --overwrite --force php@8.1
    sed -i &apos;&apos; &apos;/php@[0-9\.]*\//d&apos; ~/.zshrc
    echo &apos;export PATH=&quot;/usr/local/opt/php@8.1/bin:$PATH&quot;&apos; &amp;gt;&amp;gt; ~/.zshrc
    echo &apos;export PATH=&quot;/usr/local/opt/php@8.1/sbin:$PATH&quot;&apos; &amp;gt;&amp;gt; ~/.zshrc
else
    echo &quot;Invalid argument. Please enter either &apos;7.4&apos; or &apos;8.1&apos;.&quot;
    exit 1
fi

source ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在在.zshrc 尾部加入一行，引入该文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;source ~/custom.zsh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;保存后执行 source ~/.zshrc. 使修改生效，然后就可以随时随地一条命令切换php版本了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;switch-php 7.4
#or
switch-php 8.1
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>php7.4 与 php8差异</title><link>https://blog.cuixu.cn/posts/php74-vs-php8/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/php74-vs-php8/</guid><pubDate>Mon, 22 May 2023 15:17:28 GMT</pubDate><content:encoded>&lt;p&gt;PHP 8.0 是 PHP 7.x 系列的下一个主要版本，与 PHP 7.4 相比有一些重大的变化和改进。以下是 PHP 8.0 相对于 PHP 7.4 的一些重要的变化和改进：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;JIT 编译器：PHP 8.0 引入了一个全新的 JIT 编译器，可以提高代码的执行速度，尤其是在大型应用程序中。&lt;/li&gt;
&lt;li&gt;新的语言特性：PHP 8.0 引入了一些新的语言特性，例如命名参数、联合类型、match 表达式等，可以提高代码的可读性和可维护性。&lt;/li&gt;
&lt;li&gt;函数和类的改进：PHP 8.0 对一些函数和类进行了改进，例如 str_contains()、str_starts_with()、str_ends_with() 函数，以及 DateTime 类等。&lt;/li&gt;
&lt;li&gt;错误处理的改进：PHP 8.0 对错误处理进行了改进，例如引入了新的 Throwable 接口、Union Types 等，可以提高代码的健壮性和可靠性。&lt;/li&gt;
&lt;li&gt;移除了一些废弃的特性：PHP 8.0 移除了一些废弃的特性，例如 magic_quotes_gpc、realpath_cache_size 等，可以提高代码的安全性和稳定性。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;总的来说，PHP 8.0 相对于 PHP 7.4 有一些重大的变化和改进，尤其是 JIT 编译器和新的语言特性等，可以提高代码的执行速度和可读性。但是，这些变化和改进也可能导致一些不兼容性问题，因此在升级到 PHP 8.0 之前，需要仔细评估其对项目的影响，并进行充分测试。&lt;/p&gt;
</content:encoded></item><item><title>Javascript Fetch Note</title><link>https://blog.cuixu.cn/posts/javascript-fetch-note/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/javascript-fetch-note/</guid><pubDate>Mon, 08 May 2023 09:40:09 GMT</pubDate><content:encoded>&lt;p&gt;An Example about Fetch&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fetch(&quot;https://api.example.com/data&quot;, {
  method: &quot;POST&quot;, // 请求方法
  headers: {
    &quot;Content-Type&quot;: &quot;application/json&quot;, // 请求头中的 Content-Type
  },
  body: JSON.stringify({
    // 请求体中的数据
    name: &quot;John Doe&quot;,
    age: 30,
  }),
})
  .then((response) =&amp;gt; {
    if (response.ok) {
      // 判断响应是否成功
      return response.json(); // 将响应转换为 JSON 格式
    } else {
      throw new Error(&quot;Network response was not ok.&quot;); // 抛出异常
    }
  })
  .then((data) =&amp;gt; {
    console.log(data); // 处理 JSON 格式的响应数据
  })
  .catch((error) =&amp;gt; {
    console.error(&quot;There was a problem with the fetch operation:&quot;, error); // 处理异常
  });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;fetch 默认是异步执行的。当你调用 fetch 函数时，它会立即返回一个 Promise 对象，然后在后台发起网络请求。一旦请求完成，Promise 对象就会被解析为一个 Response 对象，然后你可以使用 then() 方法来获取响应数据。&lt;/p&gt;
&lt;p&gt;由于 fetch 是异步执行的，它不会阻塞 JavaScript 主线程。这意味着你可以在发送请求时继续执行其他的 JavaScript 代码，不必等待网络请求完成。这种异步执行的方式可以提高应用程序的性能和响应速度。&lt;/p&gt;
&lt;p&gt;如果你需要使用 fetch 函数来进行同步请求，可以使用 async/await 或者使用同步的 XMLHttpRequest 对象。但是，这种同步方式可能会降低应用程序的性能和响应速度，因此不建议在大多数情况下使用。&lt;/p&gt;
</content:encoded></item><item><title>以root用户运行php-fpm</title><link>https://blog.cuixu.cn/posts/run-php-fpm-as-root/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/run-php-fpm-as-root/</guid><pubDate>Sat, 06 May 2023 09:50:53 GMT</pubDate><content:encoded>&lt;p&gt;在开发环境中，为了避免文件权限带来的问题，可以将php-fpm的运行用户设为root.&lt;/p&gt;
&lt;p&gt;但在默认情况下，直接修改php的运行用户为root，重启php-fpm进程后会报错&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ERROR: [pool www] please specify user and group other than root
[26-Jun-2014 00:39:07] ERROR: FPM initialization failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过查看php-fpm的文档得知，如果要以root用户运行，需要加上特定参数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# php-fpm --help
...
 -R, --allow-to-run-as-root
               Allow pool to run as root (disabled by default)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如何添加这一参数：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;找到 php-fpm.service , 可以使用命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;find / -name php-fpm.service
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;添加参数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ExecStart=/usr/sbin/php-fpm --nodaemonize -R
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;重启php-fpm.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl restart php-fpm
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;注意，切勿在正式环境中使用root来运行php-fpm&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Warning: php-fpm.service changed on disk. Run &apos;systemctl daemon-reload&apos; to reload units. 修改后如有这个警告，执行即可。&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>magento2中Index Management两种模式的差异</title><link>https://blog.cuixu.cn/posts/magento-index-management/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/magento-index-management/</guid><pubDate>Fri, 28 Apr 2023 14:08:59 GMT</pubDate><content:encoded>&lt;p&gt;Magento 2的Index Management有两种模式：Update on Save和Update by Schedule。它们的区别如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Update on Save：这是Magento 2的默认模式。在此模式下，当商品、类别、客户等数据发生更改时，索引会立即更新。这意味着在每次保存数据时都会更新索引，因此可以确保索引始终是最新的。但是，这种模式可能会影响性能，因为每次保存数据时都会触发索引更新。&lt;/li&gt;
&lt;li&gt;Update by Schedule：在此模式下，索引更新是按计划进行的。您可以在Magento 2后台的“索引管理”中配置索引更新计划，例如每小时、每天或每周更新一次。这种模式可以减少索引更新对性能的影响，因为索引更新不会在每次保存数据时触发。但是，这种模式可能会导致索引不是最新的，因为在计划更新之前，数据可能已经发生了更改。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因此，选择适当的索引更新模式取决于您的应用程序的需求。如果您需要确保索引始终是最新的，并且可以容忍性能影响，则可以选择Update on Save模式。如果您更关注性能，并且可以容忍索引不是最新的，则可以选择Update by Schedule模式。&lt;/p&gt;
</content:encoded></item><item><title>Git游离状态commit内容的找回</title><link>https://blog.cuixu.cn/posts/git-head-commit-missing/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/git-head-commit-missing/</guid><pubDate>Tue, 11 Apr 2023 10:15:26 GMT</pubDate><content:encoded>&lt;p&gt;情况，在一个游离的分支上提交了commit，然后checkout到某一分支，发现commit的内容找不到了。&lt;/p&gt;
&lt;p&gt;解决办法：&lt;/p&gt;
&lt;p&gt;1、通过 git reflog 查看所有分支的操作记录，找到那个commit id&lt;/p&gt;
&lt;p&gt;2、切换到目标分支，例如 git checkout developer&lt;/p&gt;
&lt;p&gt;3、通过 git cherry-pick {commit_id}把那个游离的commit 内容重新提交到当前分支上&lt;/p&gt;
&lt;p&gt;记录一下这两个命令：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;git reflog是Git操作的一道安全保障，它能够记录几乎所有本地仓库的改变，包括所有分支的commit提交，以及已经被删除的commit&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;git cherry-pick &amp;lt;commitHash&amp;gt; 将指定的commit提交到当前分支。（与merge类似，merge是合并一个分支的所有变更，cherry-pick 更加精确）&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>REMOTE HOST IDENTIFICATION的错误解决</title><link>https://blog.cuixu.cn/posts/ssh-error/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/ssh-error/</guid><pubDate>Fri, 31 Mar 2023 13:25:14 GMT</pubDate><content:encoded>&lt;p&gt;通过 ssh 连接远程主机的时候，经常会碰到如下错误，这通常是由于服务器IP\用户名等信息变更导致的&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[root@localhost ~]# ssh 192.168.1.x
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that the RSA host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
...
Please contact your system administrator.
Add correct host key in /root/.ssh/known_hosts to get rid of this message.
Offending key in /root/.ssh/known_hosts:33
RSA host key for 192.168.1.x has changed and you have requested strict checking.
Host key verification failed.

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有两种解决办法&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;直接删除下面的文件，或者删除know_hosts中的对应192.168.1.x的连接&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;
~/.ssh/known_hosts

&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;使用 ssh-keygen&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;
ssh-keygen -R 192.168.1.x

&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>ubuntu环境下彻底卸载docker</title><link>https://blog.cuixu.cn/posts/uninstall-docker/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/uninstall-docker/</guid><pubDate>Fri, 31 Mar 2023 13:25:14 GMT</pubDate><content:encoded>&lt;p&gt;安装docker的时候，建议直接去docker的官网看文档，其他的博客或者工具安装，容易出现纰漏，导致后面报错。如果已经安装且出现了不知道如何应对的问题，那么就需要彻底删除掉之前的安装。&lt;/p&gt;
&lt;p&gt;Ubuntu系统下彻底删掉docker的步骤&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dpkg -l | grep -i docker
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;确定安装了什么包&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
sudo apt-get purge -y docker-engine docker docker.io docker-ce docker-ce-cli docker-compose-plugin
sudo apt-get autoremove -y --purge docker-engine docker docker.io docker-ce docker-compose-plugin

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上述命令不会删除主机上的镜像、容器、卷或用户创建的配置文件。如果你想删除所有的镜像、容器和卷，请执行以下命令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo rm -rf /var/lib/docker /etc/docker
sudo rm /etc/apparmor.d/docker
sudo groupdel docker
sudo rm -rf /var/run/docker.sock
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Docker 安装文档：https://docs.docker.com/engine/install/&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>laravel 和 lumen 两个框架的差异是什么</title><link>https://blog.cuixu.cn/posts/laravel-diif-lumen/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/laravel-diif-lumen/</guid><pubDate>Fri, 31 Mar 2023 02:08:21 GMT</pubDate><content:encoded>&lt;p&gt;Laravel 和 Lumen 都是由 Taylor Otwell 开发的 PHP Web 开发框架，它们有很多相似之处，但也有一些明显的差异。&lt;/p&gt;
&lt;p&gt;以下是 Laravel 和 Lumen 之间的一些主要差异：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;框架大小：Lumen 框架比 Laravel 框架更轻量级。Lumen 框架具有更小的内存占用和更快的启动时间，这使得它成为构建小型微服务和 API 的理想选择。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;功能：Lumen 框架的功能比 Laravel 框架更加简洁，去除了一些不必要的组件，比如 Session 和 Cookie 管理等。Lumen 框架的主要重点是快速构建 Web 应用程序和 RESTful API。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;性能：由于 Lumen 框架比 Laravel 框架更轻量级，因此它在处理大量请求时表现更佳。Lumen 框架也支持更多的缓存驱动器和队列驱动器，这使得它在高并发环境中表现更加出色。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用场景：Lumen 框架适用于构建小型 Web 应用程序和 RESTful API，而 Laravel 框架则适用于构建大型 Web 应用程序和企业级应用程序。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;总之，Lumen 框架是 Laravel 框架的轻量级版本，它们在设计和实现上都有很多相似之处，但是在应用场景和功能方面有一些显著的不同。&lt;/p&gt;
</content:encoded></item><item><title>Intel Standard Manageability主要功能及试用</title><link>https://blog.cuixu.cn/posts/intel-standard-manageability/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/intel-standard-manageability/</guid><pubDate>Wed, 22 Mar 2023 09:05:52 GMT</pubDate><content:encoded>&lt;p&gt;Intel vPro® 除了提供完整 Intel AMT manageability 之外，还提供 Intel Standard Manageability(英特尔标准可管理性模式)，是 AMT 功能的一个子集。&lt;/p&gt;
&lt;p&gt;Intel Standard Manageability 是与Intel AMT 5.0版一起推出的，支持以下 AMT 功能的子集。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Hardware Inventory 硬件资源管理，查询客户机可用的硬件，如BIOS功能、CPU、内存、附加卡等&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Boot Control 启动管理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Power State Management 电源状态管理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Redirection ， Serial Over LAN and Storage Redirection (USB-R or IDE-R) over TCP。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Alarm Clock 定时唤醒&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Agent Presence 可以远程配置的“看门狗”程序&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Access Monitor 用户活动监测&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;System Defense Filters 远程配置宿主机的网络过滤&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;参考：https://software.intel.com/sites/manageability/AMT_Implementation_and_Reference_Guide/default.htm?turl=WordDocuments%2Fsupportforotherintelplatforms.htm&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;以下是几个功能的使用截图，所使用的软件是MeshCommander，这套软件不久前英特尔已经放弃做支持，但是已经迭代和很久，功能比较全也比较稳定。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;MeshCommander : https://www.meshcommander.com/meshcommander&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Redirection 串口设备重定向&lt;/h3&gt;
&lt;p&gt;简单的讲，可以操作BIOS、挂载镜像文件用来引导远程系统，下面是通过 Serial-Over-Lan看到的BIOS界面，这个界面是以字符的形式显示的，控制端默认的格式和换行有问题，试着调整了一下还是这个样子，这个使用的工具有关。&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&quot;https://img.vexrobot.cn/docs/mgento截屏2023-03-23 17.44.11.png&quot; alt=&quot;截屏2023-03-23 17.44.11&quot; style=&quot;zoom: 33%;&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;下面是使用Serial挂载ISO或者 IMG文件的操作&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&quot;https://img.vexrobot.cn/docs/mgento截屏2023-03-23 17.34.47.png&quot; alt=&quot;截屏2023-03-23 17.34.47&quot; style=&quot;zoom:33%;&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;这个实际使用的时候速度比较慢，我实际试了几次，挂载，读取，设置系统启动引导都没问题，但是速度特别慢，大概在 10Mbs 以下，用一个4G的镜像来远程安装系统，预计得几个小时。&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&quot;https://img.vexrobot.cn/docs/mgento截屏2023-03-23 13.19.58.png&quot; alt=&quot;截屏2023-03-23 13.19.58&quot;  style=&quot;zoom:20%;&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;这个速度慢的问题 在github上也看了，目前应该就是这样，有人给出的建议是 使用小的镜像文件引导，然后再通过网络下载的方式。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;关于这个速度的讨论：https://github.com/Ylianst/MeshCentral/issues/951 有人提到了这个工具 https://netboot.xyz/docs/&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Agent Presence&lt;/h3&gt;
&lt;p&gt;Agent Presence Hight-Level-API 特性包含两个主要接口&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一组接口提供从远程计算机执行的API功能&lt;/li&gt;
&lt;li&gt;一组本地AMT宿主机上的本地主机调用的API&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面是从远程控制端添加 看门狗的操作，添加完这个看门狗，还需要从本地宿主机调用接口来完成和具体程序的关联等操作。&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&quot;https://img.vexrobot.cn/docs/mgento截屏2023-03-23 18.00.17.png&quot; alt=&quot;截屏2023-03-23 18.00.17&quot; style=&quot;zoom:33%;&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;如何在设备端本地调用接口，完成这个看门狗的配置，目前还没找到方便的测试方法。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;参考 https://software.intel.com/sites/manageability/HLAPI_Documentation/default.htm?turl=Documents%2Fagentpresence.htm&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;System Defense&lt;/h3&gt;
&lt;p&gt;可以从控制端来配置一个网络包的过滤器，可以对数据包进行允许，丢弃，以及限速的操作。&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&quot;https://img.vexrobot.cn/docs/mgento截屏2023-03-23 18.06.11.png&quot; alt=&quot;截屏2023-03-23 18.06.11&quot; style=&quot;zoom: 50%;&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;Alarm Clock 定时唤醒&lt;/h3&gt;
&lt;p&gt;这个功能比较简单，可以从远程控制端来配置定时，或者定期唤醒设备。使用场景的话，比如每天7点定时开机执行任务，任务完成自动关机。&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&quot;https://img.vexrobot.cn/docs/mgento截屏2023-03-23 16.49.48.png&quot; alt=&quot;截屏2023-03-23 16.49.48&quot; style=&quot;zoom: 50%;&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;Access Monitor&lt;/h3&gt;
&lt;p&gt;这个功能也叫做 Audit Log ， 主要记录的是 AMT 相关的一些操作，如下图所示：&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&quot;https://img.vexrobot.cn/docs/mgento截屏2023-03-23 19.05.56.png&quot; alt=&quot;截屏2023-03-23 19.05.56&quot; style=&quot;zoom: 33%;&quot; /&amp;gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;参考：https://software.intel.com/sites/manageability/AMT_Implementation_and_Reference_Guide/default.htm?turl=WordDocuments%2Faccessmonitor.htm&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>docker 中使用的 kong 是干什么的</title><link>https://blog.cuixu.cn/posts/what-is-kong/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/what-is-kong/</guid><pubDate>Tue, 21 Mar 2023 09:15:21 GMT</pubDate><content:encoded>&lt;p&gt;Kong 是一个用于构建微服务架构的开源 API 网关和微服务管理平台。在 Docker 中使用 Kong 通常是为了在微服务架构中实现 API 管理和微服务发现。&lt;/p&gt;
&lt;p&gt;Kong 可以作为一个 API 网关，代理客户端请求并将请求路由到对应的后端微服务。Kong 还提供了丰富的插件，可以对请求进行身份验证、流量控制、日志记录、监控等处理。&lt;/p&gt;
&lt;p&gt;在 Docker 中，使用 Kong 可以快速搭建一个高效、可扩展的 API 网关，并且可以方便地部署和管理。Kong 还可以与 Docker 中的其它容器化应用程序无缝集成，实现完整的微服务架构。&lt;/p&gt;
&lt;p&gt;下面是几个使用 Docker 中 Kong 实现微服务架构的例子：&lt;/p&gt;
&lt;p&gt;假设你正在构建一个电子商务网站，并使用多个微服务来处理订单、库存、支付和用户管理等任务。你可以使用 Kong 作为 API 网关来统一管理这些微服务，并为每个微服务配置不同的 API 策略和安全机制。&lt;/p&gt;
&lt;p&gt;假设你正在构建一个面向企业的 SaaS 应用程序，该应用程序由多个独立的微服务组成，包括用户管理、权限控制、数据分析和营销自动化等功能。你可以使用 Kong 来将这些微服务整合在一起，并为每个微服务提供统一的入口点和安全机制。&lt;/p&gt;
&lt;p&gt;假设你正在构建一个在线教育平台，并使用多个微服务来处理课程管理、学生管理、支付和证书颁发等任务。你可以使用 Kong 作为 API 网关来为每个微服务提供独立的 API 策略和流量控制，以确保平台的安全和稳定性。&lt;/p&gt;
</content:encoded></item><item><title>记第n次开博</title><link>https://blog.cuixu.cn/posts/restart-blog/</link><guid isPermaLink="true">https://blog.cuixu.cn/posts/restart-blog/</guid><pubDate>Wed, 15 Mar 2023 15:06:05 GMT</pubDate><content:encoded>&lt;p&gt;博客关了开，开了关，不记得这是第几次了。&lt;/p&gt;
&lt;p&gt;hexo的火还没熄，新的轮子hugo又开始了，一个go写的博客工具，可以很方便的整理和发布markdown格式的笔记，那就试试，来吧。&lt;/p&gt;
&lt;p&gt;最近做的事情比较杂，东研究一下，西研究一下，记录一下放这里也许可以帮到别人。&lt;/p&gt;
</content:encoded></item></channel></rss>