继续失业在家无所事事中，打算找点 Vibe Coding 能落地的实际业务，巩固一下后端和运维相关知识。看到之前认识的一摄影师哥们被公司集体裁员后，目前选择自己创业开影棚，于是在想能不能做一个这样的系统结合实际，于是便有了此项目。前几天冲凉想到之后初步规划过，写了一份约 2500 多字的文档，随后让 AI 输出了一份完整的「产品设计文档」和对应项目的「技术说明文档」，我再核实确认修改了细节。本文仅作过程和踩坑记录，不包含具体 Prompt 和对应的业务细节。项目背景制作一个影棚管理系统，主要面向于摄影工作室场景开发，包含官网（作品展示、预约影棚等）、管理后台（管理员维护）、后端三个项目。简要记录今天的 Vibe Coding 已经完成制作了官网、服务端、后台三端的核心代码，期间也确实遇到不少问题，这里简单记录一下。模型主要是采用 GPT-5.5，部分流程自动降为 Cursor 的 Composer 2.5。官网部分官网方面，我首先是另外找 AI 对话探讨得到一套靠谱的设计，最终得到结果如下，其实从生成结果来看，整体还不错，就是顶上那个菜单风格真的是有点太熟悉了，哈哈。整体风格： - 高级感 - 沉浸式 - 年轻化 - 二次元审美 - 作品驱动 - 内容社区风格 避免： - 企业官网风格 - 传统婚纱摄影网站 - 影楼宣传页 - 粉色二次元模板风 - 动漫论坛风格 参考风格： - Pinterest - Instagram - Behance - ArtStation - 小红书 - Linear - Raycast 视觉关键词： - Dark Mode First - Editorial Design - Cinematic - Immersive - Premium - Modern - Elegant - Gallery Focused - Large Typography - Minimal UI - Rich Photography Showcase后端部分后端方面，问题主要是 Prisma 的接入相关的坑比较大，还引出了一个 ESM 和 TypeScript 打包配置的问题。最开始遇到的问题是 PrismaClient 实例失效，直接报错找不到，排查后发现是 schema.prisma 的配置问题，需要指定 output 路径到当前项目下，它默认存的位置有问题，导致 TS 识别不到。这里基本对着 官方文档 接就能解决，AI 自己写的有问题。我自己写的是如下，只要放在当前项目内就行。generator client { provider = "prisma-client" output = "./generated" }Prisma 接入方式从 7x 版本后发生了变化，包括 seed.ts 文件，需要手动传递对应的适配器包过去才行，AI 这里就搞晕了，用了旧版本的写法，我的解决方式是直接把最新文档的链接甩过去让它修改，问题真的解决了吗... 并没有！import "dotenv/config"; import { Pool } from "pg"; import { PrismaPg } from "@prisma/adapter-pg"; // 这里必须写 .js 不然找不到文件 import { PrismaClient } from "./generated/client.js"; import * as bcrypt from "bcrypt"; const connectionString = process.env.DATABASE_URL; if (!connectionString) { throw new Error("DATABASE_URL is not set"); } const pool = new Pool({ connectionString }); const adapter = new PrismaPg(pool); const prisma = new PrismaClient({ adapter });此时报错提示找不到模块（错误信息被删了我也找不到了）这种 CommonJS 和 ESM 的打包问题，其实我自己也理解的不是很透彻，首先第一步 Prisma 要你在 package.json 里面增加 "type": "module" 就能使 NodeJS 认为当前包默认走 ESM 模式运行，因此你得确保构建后的产物也是 ESM 的代码（之前我有一个「小窝后端 Ext」项目是 KoaJS，用 ESBuild 构建成了 ESM 的 JS，就能直接运行，本质上就是 node index.js 这种执行方式），而 NestJS 自己有一套 CLI 工具，本质上是包装了一次 TSC，具体生成啥根据 type。说白了问题就这样解决：❌ Prisma 默认 ESM、NestJS 默认 CommonJS（项目没写 type=module）✅ Prisma 强制成 CommonJS（配置 moduleFormat=cjs）、NestJS 默认 CommonJS✅ Prisma 默认 ESM、NestJS 也改成 ESM（项目写 type=module）Prisma 官方文档 的说明，就要求将包设置成 ESM 模式。In package.json, add the type field set to "module"{ "type": "module" }我选择了这种方式，于是所有的 import 全部报错，都需要手动加入 .js 后缀才能正常访问。我看别人的项目没这个问题，为什么呢，后面单独列个说明。而 NestJS 自己文档 中的描述则截然相反，要求将 Prisma 自己的输出构建为 CommonJS 模式...Set moduleFormat in the generator to cjsgenerator client { provider = "prisma-client" output = "../src/generated/prisma" moduleFormat = "cjs" }The moduleFormat configuration is required because Prisma v7 ships as an ES module by default, which does not work with NestJS's CommonJS setup. Setting moduleFormat to cjs forces Prisma to generate a CommonJS module instead of ESM.这就是集成第三方最坑的问题，双方各有其词，似乎就没有最正确的实践。以上问题解决后，项目基本就正常跑起来了。为什么要加 .js 后缀对比配置后发现是 tsconfig.json 有两个关键配置：{ "module": "esnext", "moduleResolution": "bundler" }在我这个 NestJS 后端项目写的均是 nodenext。这两个到底啥区别，这是 AI 的解释：第一个：前端打包器专用module: esnext：编译输出原生 ESM 语法不降级，保留 import/export，不转成 CommonJS；仅给打包器（Vite/Webpack/Rollup）使用，不能直接丢给 Node 运行。moduleResolution: bundler：TS 模拟打包器解析规则，适配前端工程习惯。第二个：Node 原生运行专用module: nodenext：跟随新版 Node 模块规范，自动根据 package.json type、.mts/.cts 区分输出 ESM/CJS 产物，产物可直接 node dist/main.js 运行。moduleResolution: nodenext：1:1 复刻 Node.js 原生模块解析逻辑，严格遵循 Node ESM/CJS 互操作规则。Nest CLI 编译底层逻辑nest start = nest build + 运行 dist 主文件；nest build 底层调用原生 tsc 编译，完全遵循你 tsconfig.json 里的 module/moduleResolution 配置，Nest 不做特殊转译，仅做装饰器元数据收集。问：也就是说第一种模式是走了一次打包器，需要打包器自己解决引入问题。而我这个是直接用 NestJS 的 CLI 直接构建输出了，它本身并没有打包的功能。是这样吗1. 先分清楚两个概念：tsc 编译 vs 打包（bundle）① module: esnext, moduleResolution: bundler不代表自动走打包器，只是让 TS 的类型解析规则模拟打包器（webpack/vite/rollup）。TS 本身只是转译 TS → JS，没有打包、没有文件合并；bundler 只是一套解析逻辑：允许省略 .js、自动识别 index、兼容前端库的导出字段；如果你只用 nest build（底层纯 tsc），不会触发任何打包，只会输出一堆零散 dist 文件。所以你的理解一半对一半错：正确部分：这套配置是给打包器环境设计的，依赖打包器运行时补齐导入路径、处理模块；错误部分：TS / Nest CLI 本身不会自动调用打包器，只是类型校验规则对齐打包器。② Nest CLI 原生能力默认 nest build = 调用 tsc 做转译，不打包：输入：零散 .ts 文件输出：dist 下一一对应的零散 .js 文件，没有代码合并、没有依赖内联；nest start 只是 node dist/main.js 直接跑编译后的零散文件，全程无打包步骤。Nest 只有一种情况会打包：你手动启用 webpack 构建（nest build --webpack），否则完全无打包逻辑。2. 为什么 esnext + bundler 配 Nest 直接跑会崩tsc 输出纯 ESM 代码；源码导入全是 import './utils'（不带 .js）；没有打包器介入补全后缀、解析路径；Node 原生 ESM 规则强制要求完整后缀，直接运行就报模块找不到；且 dist 无 "type":"module" 时，node 会把文件当成 CommonJS，遇到 import 直接报错。这套配置的设计初衷：源码交给 vite/webpack 打包，打包器内部会处理路径补全、模块格式兼容，不会直接用 node 执行 tsc 输出的裸文件。3. nodenext 为什么适配 Nest（纯 tsc 编译后直接 node 跑）moduleResolution: nodenext 是模拟 Node.js 原生模块解析规则：TS 在编译+类型检查阶段就强制你写 import './utils.js'；tsc 输出的产物完全符合 Node 运行标准；不需要打包器中转，nest start 直接 node dist 就能正常加载所有模块。一句话总结你的疑问esnext + bundler 这套规则依赖打包器处理导入路径/模块兼容，但 Nest 默认 CLI 只是 tsc 转译、不带打包；如果你不用 webpack 打包，只靠原生 nest build，就不能用 bundler 模式，否则编译产物无法直接 Node 运行；后端 Nest 裸编译场景，正确配套是 nodenext（或传统 commonjs），而非 bundler。所以解决办法无非就两个，要么继续走 nodenext 模式直接用（NestJS 官方默认 Starter 模板就是这个），每个文件携带 .js，要么用 bundler 自己配置走个打包器，就免除这个困扰了。
这次面试，主要还是对面主动联系为主，应该是看我写了 NextJS 和电商相关经历吧，他们认为和岗位比较匹配，于是得到了这次宝贵的面试经历。面试过程还是挺紧张刺激的，毕竟人家问到的，不一定你平时经常用。HR 面HR 面没有具体考察技术，但我看也问了不少涉及技术、协作相关的话题，和一般公司的人事还是有些不同，整体回答我感觉正常水平吧。以下回答都是在对话里面完成的，所以可以先思考再回答，并完整摘录。HR：你说说你工作的心得吗，之前工作的部门规模，参与其中的角色作用答：上家公司挺好的 是创业公司自营业务 大家相处起来氛围也很好 我们团队 20 人左右，我参与的项目里面 10 人这样。因为做了四年 我这边参与维护过的东西也多，就以近期的 AI 项目来说我这边主要是参与制作一个无限画布的功能，因为工期有一点紧，代码质量有一些不够好，我就去对这块做各种优化改进，以及最影响使用的一些性能问题吧 主要是涉及到 React 的组件渲染这块HR：因为公司的全技术都是老板做起来的，他比较喜欢直接开会对接工作，我们没有产品经理，需求大多是一起讨论、简单文档，原型和说明反而要技术这边编写。你之前有没有经历过没有产品、直接对接技术负责人/老板的工作模式？遇到需求比较模糊、不完整的时候，你一般怎么处理？答：有的 我第一份工作就是这样 当时在旅行社那边技术负责人就是和其他部门做对接 相当于是直接对接具体使用系统的人 不过当时这块的具体流程应该没有这么细 都是他们大佬带头开干 我这边做对接为主不过结合之前的工作经历 其实我们也并非每个开发任务都有很详细的产品文档，也有不少是口头需求 临时改动 不过都是针对用户反馈和数据去做的调整 这种需求模糊挺多的，一般分两种情况，一个是业务层面的，这就只能拉上级详细确认，另外一种是交互层面的，我会自己简单设计方案让产品看一下，或者他觉得有更好的方案，再决定用哪个更好的方案HR：今晚 8 点 45 分左右，进行技术面试，你时间上可以么答：可以HR：你期待工资多少？答：（想先了解上班时长）公司正常上班时间是？HR：10~7点，期间有1个小时休息，答：双休吗HR：是的答：也就是 12-1 点休息这样是吗HR：不是固定的，因为多数 11 点半左右开始开会到 1 点左右，你最近的工作时间和待遇如何答：之前工作时间都比较固定 早上也是 10 点这样，中午是休息 2 小时，晚上我一般 6 点半到七点走，看任务完成情况吧HR：我们每天会有 IT 技术会议，老板亲自带队跟进进度、同步需求、解决问题。需要你按时参会、主动汇报进度、主动反馈卡点。前后端错开开会，例如今天中午前端开，明天后端开HR：我们团队没有测试，所有功能都是开发自己自测、自己验收、自己保证上线质量。你平时开发完，有没有自测的习惯？一般会检查哪些内容？如果上线后出现自己没测出的问题，你会怎么处理？答：功能自测肯定是必须的 包括我每次提交代码之前 都会人工确认一次 确保不会把临时改动也带上去不过也得分情况比如说有些功能需要有多个前置步骤，可能就会跳过一部分主要测改动后的部分，如果时间允许的话肯定需要完整跑几次上线后出现问题的情况客观来说肯定会存在 不过因为代码合并产生的冲突导致的比较多 比如多个人在改同一个模块的情况下 所以每次合并的时候还是得确认清楚比较好 关于线上问题当时我们的方案是做 sentry 监听线上的报错信息，以及 clarity 跟踪用户的操作所以还有一种方案就是 尽可能拆出不同的项目 比如说商城是前台，管理业务时另外一个后台项目，这样也能确保不会相互影响前端这边有几位同事一起是吗HR：你是指什么？就是你说的前端开会HR：目前 IT 团队配置前 2 后 2，大家都会参与HR：你上一份薪资待遇如何？答：税前 xx-xx技术面技术面考察的知识点非常丰富，基本上简历上能问的都问了一遍，面试官还是很有水平的。简单复盘下来，有些问题我完全答对，有些回答不够直接明了但也答中关键点了，有些比如 Jenkins、Electron 相关的就确实是不太清楚，也着实是没有答上。以下回答部分为印象记忆，可能和实际有出入。面试官：http 1.1 2 3 的主要区别是什么答：印象中区别在于连接重用，之前用过 Lighthouse 提示我有些资源走的是 1.1，优化过面试官：1.1 和 2 呢答：没回答上复盘：这里主要区别的确是并发，1.1 和 2 的最大区别，而 3 增加了 QUIC，使用 UDP 协议AI 的回答挺详细的，这里分享出来记录 传送门面试官：这里不行不要紧，那这个浏览器内核有哪一些答：主要三个，Safari、Chrome、IE面试官：它们的内核是什么，这是浏览器答：Blink（Chrome），还有一个叫什么来着，Webkit（Safari）复盘：其实还有一个 Firefox 浏览器，用的是 Gecko，不过其实很少人用，IE 是 Trident面试官：可以说明下 Restful API 的 Methods 吗答：主要用的是 GET、POST、PUT、DELETE 这些面试官：POST、PUT、PATCH 的区别是啥答：我理解 POST、PUT 都是提交数据，PATCH 是更新复盘：这里就答错了，POST 是创建，PUT 是全量更新、PATCH 是部分更新，参考 传送门（之前自己小窝的后端也有这个问题，一个更新接口既要支持全量更新也要支持部分更新咋弄了，我倒是用了一套很诡异的实现，默认按照 PATCH 的逻辑形式来，传什么改什么，全量传默认等同于 PUT 了其实面试官：Tailwind 里面 Container 是干什么用的答：Container 主要是容器查询，比如根据容器大小去设置子元素的宽高啥的面试官：Tailwind 里面哪个呢，怎么用答：（这里我理解成 md:xx sm:xx 这个东西了，他问的是 className 里面写的那个）这里没有很好回答出来复盘：直接看 文档 吧，就是这样的一个东西 我理解其实就是基于 CSS 容器查询的一个实现。最近确实用到了这个东西，不过是在我 Vue 3 哪个主页项目上，用的还是纯 CSS，哈哈。面试官：Grid 和 Flex 的区别答：现在我 Grid 用的多一些，Flex 兼容性更好，两者主要区别一个是宫格，宽高固定，一个是宽高自由可以单独指定。面试官：block、inline-block、inline 区别是啥答：block 就是最常见的占满一行的，inline-block 我记得是基线对齐那种模式，主要是和 inline 一起，inline 就是常见的文本排版，这个 inline-block 稍微有点不一样是个整体，也参与到那个基线对齐复盘：这里其实也算答到了一点，如果更具体一点比如说 inline 的 margin 上下无效、padding 不影响行高计算估计更加分，依旧是 AI 传送门面试官：align-items 和 justify-content 区别是啥答：一个垂直居中一个横向居中（实际上这个根据 Flex 的对齐方式，会有出入）面试官：justify-content 的 space-around、space-between、space-evenly 区别是啥答：三个 div 举例的话，一个是左右有间距、一个是中间有间距，再一个是比较居中（这里其实没讲清楚，但大概意思一样）复盘：其实这几个都不咋常用的，大多都是宫格形式布局... 直接上 MDN 链接space-between：首个元素放置于起点，末尾元素放置于终点space-around：每个元素周围分配相同的空间类似于 Float/Flex + Padding + 负数 Margin 实现宫格间距的那种默认实现现在有 Gap 属性可以解决间距，但是不能解决一行摆几个元素占比多大的问题，这种场景上 Grid 更舒服space-evenly：每个元素之间的间隔相等面试官：React 的生命周期答：现在新版 React 已经没有生命周期函数了，都是改成 useEffect 这种 Hooks 了。（他其实想问我组件挂载到结束，React 都干了些啥，但是一开始没表达清楚我当时也没转过来，死磕在 Vue 那种生命周期函数上了，之后回答完全偏了）复盘：我的理解是这样，首先组件挂载，先执行渲染函数，得到虚拟 DOM 然后加入渲染队列，渲染成实际 DOM，再执行 useEffect，更新组件状态同理，最后卸载组件。还是看 AI 完整解答 吧，它讲的更详细。面试官：useCallback 和 useMemo 的区别答：一个是类似 Vue computed 合并计算结果，一个是缓存函数，都需要传 deps（完美回答）面试官：为什么这段 TypeScript 后面要加一个 as constconst obj = { city: { name: "ShangHai", zip: 200000, } } as const;答：as const 主要是确定变量类型，假如不写的话就有可能识别成 string 或者联合类型 string | number 这种，就不方便去判断具体类型复盘：也算是讲到了一点但回答其实不太准确，其实它会把所有值设置成 readonly 表示这个值不会发生任何变化，并且每个对象的类型固定，这里涉及到了“类型拓宽”概念，传送门。面试官：SPA、MPA 区别是啥答：SPA 就是 React 这种（我理解成 CSR 了），MPA 就是 NextJS 那种（其实这里是 SSR）复盘：这里基本答错。CSR、SSR、SSG 是一个概念，SPA、MPA 又是另外一个概念，现在的框架基本上都是融合操作，并不能算具体的 SPA 了，MPA 其实就是之前 PHP 那种老的，依旧 AI 传送门面试官：interface 和 type 什么区别答：我这边常用可能还是 interface，type 主要是做联合类型，虽说 interface 也可以做（其实说的是 extends，继承和联合还是有区别的）复盘：其实区别也挺多，不过建议定义对象类型优先使用 interface，其次再去使用 type。传送门面试官：我看到你提到了 Electron，我要在视窗里面建立独立的悬浮部分怎么做答：这个确实没做过（上一次搞 Electron 都是 2 年前了，很多 API 也没印象了，彻底挂）复盘：传送门，其实我也不确定 AI 的回答是不是他想问的。面试官：Electron 的 contextBridge 是干什么用的答：我记得是主进程和子进程去通信使用的，一个前台一个后台，需要用这个传数据复盘：直接看 文档 吧，确实差不多是这样子，但我认为这个回答并不是很好，毕竟真的好久没摸过 Electron 了（看了眼我 Electron Fiddle 是 24 年安装的，之后一直吃灰）近 2 年都在搞 Felo，他们也没有说需要做客户端。面试官：来一道 IQ 题（印象记得是猜谁才是凶手，只有一个人回答是正确的）复盘：这题似乎没有标准答案，主要看你怎么分析每个分支下会产生什么结果，第一次遇到这样的面试题，确实给困惑住了，这是 AI 对这道题内容的猜测，传送门 应该差不多是这样子。面试官：你不是做了个 AI 项目么，你觉得当时最核心的业务目标是什么，是提高用户留存还是付费还是验证产品方向呢（其实一开始也没听清楚）答：主要是文员这块吧，我们做的是 PPT、网页 什么的，竞争也挺大的面试官：你觉得哪些功能是正需求哪些是伪需求答：我觉得网页多一些复盘：其实这里我也不知道如何回答，毕竟是运营和产品的策略嘛，我们感觉没啥用也得做不是么，仔细想想似乎这种需求挺多的，有一些还直接被其他优先级更高的需求给埋没掉了，印象最深的是要给 PPT 里面的 SVG 做一个编辑器，虽然我引入了一个库尝试了一下但是发现有个不小的 Bug 挺难解决的，就没继续看了面试官：你推动切换组件库和优化代码这块本质上是解决什么问题答：组件数据重构是开发的时候不够顺面试官：你这个是个人项目还是公司项目，公司项目怎么协调这个平衡答：因为当时是我们大量推 Vibe Coding 嘛，每个人自己做一套，缺乏统一约束嘛，所以就想着给它做稳定。面试官：你有没有考虑过需求排期问题，怎么解决的答：有需求肯定是需求为主嘛，这些都是有空去优化的，当然我也希望一开始流程就能写清晰，这样最好面试官：那你项目约定怎么做好答：首先第一个就是组件目录结构那些嘛，哪些公用哪些单独的，Vibe Coding 也是一样你得指清楚哪里哪里嘛，就这块比较重要一些面试官：我看你用过 Jenkins，那你解释一下 WebHook 是干嘛用的答：主要是触发构建，你肯定要去 Git 平台绑定这个 Hook 才能发这个创建请求嘛面试官：Jenkins SCM 是什么答：这个词挺熟悉的，但是没想起来是什么，是以 Jenkins 本地作为驱动的啥（彻底挂）复盘：SCM = Source Code Management（源码管理），其实是 Jenkins 里面拉项目仓库代码的方式，传送门 虽说这个 AI 的回答我看的也挺懵的，实际使用差不多就是设一下仓库地址，拉到临时的位置，就可以用 Shell 去做构建等操作了。我自己写的一个简单的 CI 工具，直接只有 Shell 功能，直接在服务器构建，其他全没有...面试官：Nginx 你主要拿去干什么，有弄过 SSL 吗答：网站托管，反向代理，我现在用 Caddy 多一些，因为它自动处理 SSL，不用我自己搞，之前用面板操作所以用 Nginx，它里面有个签发 SSL 的工具这样。结果挂HR：“中午好，上周的面试结果出来，很抱歉，本次未能入围，谢谢您参与面试。祝您早日找到适合的工作。”
众所周知，威联通 NAS 内置的“内网穿透”服务在国内是有些问题的，myQNAPcloud 实际上是需要一个公网 IP 才能链接到他们家。此前博主使用的是「自建 FRP 服务器」方案来实现反向代理实现穿透，后续 Docker 一直没配置好且无法安装新的镜像，搁置了许久。其实问题本源在于国内访问 DockerHub 的网络环境并不友好，只需要简单配置下即可。这里博主使用的是几年前购买的 TS212P3 型号（也是当时入门买的最低配的）作为演示。本文章于 26.04.30 立坑，结果于 26.05.19 花了半天时间才写完...配置 Docker 源首先打开 ContainerStation，点击左侧「属性」菜单项，在右侧页面点击切换到「Registry 服务器」Tab。这里我们使用 https://docker.1panel.live 这个镜像源，确保国内的网络环境也能正常拉取镜像。获取镜像从 DockerHub 上获取 最新的镜像地址，这里我选择的是 latest-arm64 版本，因为 TS212P3 是 ARM 处理器的，并不支持传统 X86 的。docker pull cloudflare/cloudflared:latest-arm64这是正常的获取镜像的命令，但是我们需要替换成对应镜像源获取。点击 ContainerStation 左侧「镜像」菜单项，在右侧页面点击「提取」按钮，Registry 服务器选择我们刚创建的，「镜像文件名称」和「映像版本」分别输入 cloudflare/cloudflared 和 latest-arm64，点击「提取」。此时镜像已经拉取完成，我们前往 CloudFlare 后台开通 Tunnel 服务。连接 Tunnel登录到 CloudFlare 后台，点击「保护与连接 -> 联网 -> Tunnels (新)」左侧菜单项，在右侧页面点击「创建隧道」按钮，输入「隧道名称」，点击「创建隧道」。点击 Docker 选项，下面会出现你的启动指令以及对应 Token，这里直接复制出来。docker run cloudflare/cloudflared:latest tunnel --no-autoupdate run --token {你的 Token}回到刚刚获取镜像的 ContainerStation 界面，点击镜像右侧「+」按钮，在出现的弹窗里面修改「进入点」的命令，粘贴前面在 CloudFlare 后台拿到的启动指令，修改前面部分改成 cloudflared。点击「高级设置」，点击「网络」Tab 页，「网络模式」选择「Host」主机模式，最后点击「创建」。此时应该能创建成功并正常运行，在 CloudFlare 后台也会提示“您的隧道现已可以使用”。回到 CloudFlare 后台，找到刚创建的 Tunnel 实例，点击「添加路由」按钮，选择「已发布的应用程序」选项。之后指定自己的主域名和二级域名，在「服务 URL」处输入 http://localhost:5000此时访问你设置的对应 URL，应该就能正常访问 NAS 后台界面了。参考在 QNAP 中安装 CloudFlare Tunnel, 内外穿透到外网
列表的漫展圈年轻人比较多，特意做成了“瓜条”形式希望能被更多人看到。这里也提供了由 AI 改写的大众阅读版本，方便各位博友阅读，大致表达是相似的。大众版（AI 改写）受害者：博主骗子：自称"王艳"的一位阿姨简要说明：博主热心帮助一位陌生人借钱坐车，却遭遇诈骗。骗子自称是某物流公司的高层员工（生意人），说自己出差后将手机和钱包落在了司机车上，而自己是外地来的孤身一人，丈夫已离异且是限制高消费人员。实际上这些内容全部是编造的，她就是个骗子。事件经过3 月 13 日下班后，博主正常休息吃完晚饭，在回公司的路上遇到这位阿姨。她主动过来向博主问路，说自己要去一个很远的地方（深圳宝安机场）。博主正常打开地图给她简单指路，她表示自己没有手机没办法乘车和购票，需要抵达那个位置联系对应的送货司机。博主当时就问她为什么要去这么远的地方，她强调自己是做生意的，在某个物流公司工作（没听清具体名字），在附近谈客户。她还询问博主觉得在附近开一家店的潜力如何，博主表示附近的制造业工厂确实挺多，或许有这个需求（但博主并不是内行人员，她问博主这样的问题博主又该如何回答呢）。她还会用各种常见话题和博主套近乎，比如问博主年纪多大，是否有谈女朋友。等博主放松警惕后，她开始询问博主的行业、工作地点和薪资，以及是否是本地人、家乡在哪等（博主出于个人保护，都模糊回答了）。她说自己平常在南京发展，如果有机会博主去那边做客，她会请博主吃饭，还强调出门在外多一个朋友能多一份机遇（实际上这是博取信任的话术）。为了表示自己能还钱，她要加博主微信。那个微信号用的是一个男人的头像，博主当时就表示怀疑，但她说是和丈夫离婚了，这个号现在实际上是她在使用。她还说自己有个儿子做主播，月收入可达到 2W，表示自己是生意人不差这点钱还给博主。博主用支付宝给她换了现金后，打算给她拍照留存。她当即破防表示博主对她不信任，要求删除照片。博主表示现在骗子太多，这属于正常防范。删除照片后博主本以为事情结束了，但她仍不断用话术影响博主。博主出于好心最终还是借给了她。她当即表示感谢，并附上各种祝愿（祝你工作顺利，生意兴隆？）。她承诺第二天还钱，但实际并未归还。截止到这篇文章编写时间，微信好友申请依旧处于未通过状态。事后复盘：骗局中的疑点现在简单复盘一下，其实她全程的对话充满了各种套路和漏洞。博主此前也遇到过几次街头骗局并成功逃脱，无奈这次对方特别精明，博主着实是大意了。疑点一：打车方式不合理从最开始的问路就挺多问题了。她一直强调自己出差要公司报销打车的费用，必须要开发票，而打出租车不能提前支付，只能打表后结算费用。这样她就引导博主以现金支付。博主和她去了附近的一家便利店，店长当即表示无法提供这么大面额的现金（实际上店长可能已经在警示博主这个人有问题，让博主好好想想，但当时博主因工作疲惫没想到这个可能性）。疑点二：询问博主的工作安排因为博主已经说自己在工作了，她还询问博主明天还要不要上班。这实际上很有可能是担心自己骗了博主之后再次在附近作案会被抓获。因为是小额诈骗，警方一般是不予处理的。疑点三：不向儿子求助既然她都有收入这么高的儿子了，怎么不能直接求助他呢？这也是一个巨大的疑问。其实完全可以让博主用手机联系她儿子，然后即时转账，也就不存在借钱这么一回事了（当然她很有可能会用其他理由推辞，比如已经和儿子分家了什么的）。疑点四：情绪激动后的反向施压她情绪激动后还在强调自己的着装以及已经加了微信，表示自己是清白的，当即开始反向 PUA 博主，明显表示博主在冤枉普通人。“你把阿姨当作什么人了”“你这是在侮辱我”（实际上这也是一种洗脑话术，恶人先告状，继续利用了博主的善良）。微信号可以是假的，好友是可以不通过的，手机号也可以是空号，总之博主根本没有办法找到她。总结人在外地的身份是自己给的，今天她是企业员工，明天就可以是霸道总裁。所有的身份信息都无法核实。博主的这次经历再次向各位亲朋好友发出警钟：面对陌生人一定要提高警惕，对方可能会疯狂输出话术影响你的判断。如果来不及思考，则最好提出找警察或直接拒绝。瓜条版（原创）苦主热心帮助陌生人借钱坐车惨遭背刺苦主：匿了瓜主：自称“王艳”的一位阿姨省流版：苦主热心帮助陌生人借钱坐车惨遭背刺，瓜主称自己是某物流公司的高层员工（生意人），出差后将手机和钱包落在了司机车上，而自己是外地过来孤身一人，丈夫离异且是限制高消费人员。实则内容全部编造，骗子一个。事件经过苦主在 3 月 13 日下班后正常休息吃晚饭，回公司路上遇到瓜主表示要问路，说自己要去一个很远的地方（深圳宝安机场）。苦主也是正常打开地图简单指路，瓜主表示自己没有手机没办法乘车和购票，需要抵达该位置联系对应的送货司机。苦主当即问为什么要去这么远的地方，瓜主强调自己是做生意的，在某个物流公司（没听清），在附近谈客户，还询问我附近开一家店的潜力如何，苦主表示附近的制造业工厂确实蛮多，或许有这个需求（但苦主并不是内行人员，你问我这样的问题我又要如何回答呢）瓜主还会以各种常用话题和你套近乎，比如年纪多大，是否有谈女朋友，等你放松警惕后，开始询问你的行业、工作地点和薪资，以及是否本地人，家乡在哪等（苦主出于个人保护，均模糊回答）。瓜主说自己平常在南京发展，如果有机会前往做客，瓜主会请苦主吃饭，强调出门在外多一个朋友能多一份机遇，实际为博取信任的洗脑话术）为了表示自己能还钱加了个微信，该微信号是一个男人的头像，苦主当即表示怀疑，但瓜主说是和自己丈夫离婚了，这个号现在实际上是瓜主在使用。瓜主还说自己有个儿子做主播，月收入可达到 2W，表示自己是生意人不差这点钱还给苦主。苦主为其借钱（ZFB 换现金）后，打算给瓜主拍照留存，瓜主当即破防表示苦主对她不信任要求删除照片，苦主表示骗子太多属于正常防范，删除照片后本以为事情结束，但瓜主后续依旧持续输出洗脑苦主，苦主出于好心最终还是借给了瓜主。对方当即表示感谢，并附上各种祝愿（祝你工作顺利，生意兴隆？）对方承诺第二天还，但实际未还，截止到瓜条编写时间，微信好友依旧处于未通过状态。事后复盘现在简单复盘一下，其实瓜主全程的对话也是充满各种套路和 Bug 疑点的。苦主此前也遇到过几次街头骗局并成功逃脱，无奈此次对方特别精明，苦主着实是大意了。从最开始的问路就挺多问题了，瓜主一直强调自己出差要公司报销打车的费用（必须要开发票，而打出租车不能提前支付，只能打表后结算费用，后续引导苦主以现金支付）苦主和瓜主去了附近的一家便利店，店长当即表示无法提供这么大面额的现金（实际可能已经在警示苦主这个人有问题，好好想想，但当时苦主因工作疲惫没想到这个可能性）因为苦主已经说自己在工作了，瓜主还询问过苦主明天还要不要上班（实际很有可能是担心自己骗了苦主后再次在附近作案被抓获）因为是小额诈骗，警方一般是不予处理的。既然你都有收入这么高的儿子了，怎么不能直接求助他呢，这也是一个巨大的疑问，其实完全可以让苦主手机联系对方然后即时转账，也就不存在借钱这么一回事了（当然她很有可能会以其他的理由推辞，比如已经和儿子分家了什么的）瓜主破防后还在强调自己的着装以及已经加了微信，表示自己是清白的，当即开始反向 PUA 苦主，明显表示苦主在冤枉普通人。“你把阿姨当作什么人了”“你这是在侮辱我”（实际上这也是一种洗脑话术不对么，恶人先告状，继续利用了你的善良）微信号可以是假的，好友是可以不通过的，手机号也可以是空号，总之你根本没有办法找到对方。总结人在外的身份是自己给的，今天我是企业员工，明天我就可以是霸道总裁。苦主的此次经历再次向各位亲朋好友发出警钟，面对陌生人一定要提高警惕，对方可能会疯狂输出洗脑影响你的判断，如果来不及思考，则最好提出找警察或直接拒绝。最后让我们来看看这位丑陋的女人，必须得曝光出来：
前几天 我将 Nuxt 从 3 升级到了 4，升级之后才发现有一个页面出现了异常，CSS 样式出现丢失。出现问题的 CSS 文件是 styles.module.less，它是一个 CSS Modules 形式的 Less 文件。对应组件的引入方式有些特别，是根据文章内容来渲染的。// 组件映射表 const componentMap: Record<string, Component> = { "photo-collection": PhotoCollection, }; // 渲染组件的函数 const renderComponent = (componentName: string, props: any) => { const Component = componentMap[componentName]; if (Component) { return h(Component, props); } // 如果组件不存在，返回错误提示 return h("blockquote", {}, `组件 ${componentName} 不存在`); };我依次尝试了一些改动，发现都没解决...重命名文件（styles.module.less -> photo-collection.module.less）修改文件内容（.collection 下样式只写一条 background 规则）将引入方式调整成 if 和 else 的形式generateScopedName 解决方案只好求助牛逼的 Claude Opus 4.6，初步排查原因是开发模式和生产模式下生产的 CSS Hash 不一致。找到根本原因了！客户端 JS bundle 用的哈希：_collection_f6c82_1服务端 SSR bundle 用的哈希：_collection_x8r5c_1CSS 文件 里的规则：._collection_f6c82_1（和客户端一致，和服务端不一致）这就是问题所在：CSS Module 的类名哈希在 SSR 构建和客户端构建之间不一致。具体表现：服务端渲染 HTML 时，生成的 class 是 _collection_x8r5c_1浏览器加载的 CSS 文件里只有 ._collection_f6c82_1 的规则两者对不上，样式完全不生效这与 componentMap 的写法无关，所有前面的方案当然也都没用——核心问题是 Vite 在 SSR 和 client 两次构建中，传给 CSS Modules 的 id（文件路径）不一致，导致哈希不同。修复方案是在 nuxt.config.ts 里显式配置 generateScopedName，让两次构建使用同样的规则生成类名，消除哈希不一致问题：export default defineNuxtConfig({ vite: { css: { modules: { generateScopedName(name: string, filename: string) { const normalizedPath = filename .replace(/\?.*$/, "") .replace(/\\/g, "/"); const relativePath = path.relative(process.cwd(), normalizedPath).replace(/\\/g, "/"); const hash = crypto .createHash("md5") .update(relativePath) .digest("hex") .slice(0, 5); return `_${name}_${hash}`; }, }, }, }, });总感觉这是一个很奇怪的修复方式，理论上 Vite 自己的配置都是经过验证的，自己魔改的配置越多，后续的升级只会更容易出现问题，想着能不能从其他方面再继续分析下其他的解决方法。修改后缀名方案我把这件事告诉给了 @Innei，先被他 PUA 了一遍怎么还在用这么古老的技术栈骂归骂，能有些解决问题的思路也好。他说他现在使用的“不古老”的方式是 vanilla-extract 这个库，看上去是类似 CSS in JS 的那种编写方式，但是实际背后处理返回的是一个 CSS 文件，看上去亮点主要是 TypeScript 友好。用 Less 有啥优势我说没有啥优势，只是好过全局 CSS 而已（因为是 CSS Modules 形式），当时没转过来指的是 Less 本身。选择 Less 主要是主题色盘的色值可以直接用函数生成，而不是自己算好再写上去。并且它有 CSS 嵌套功能，从小窝后台一直用了好几年，也没啥问题就一直在用。当然现在这些特性其实原生都有支持了，颜色计算有 color-mix，嵌套兼容性还不够好但也能用（Chrome 要求 120 版，iOS Safari 要求 17.2 的系统），继续沿用也只是为了保持兼容性而已。我尝试直接将文件的后缀从 .less 改成 .css，居然也是解决的。Innei 看到我的惊喜若狂（卧槽，去掉还真好了，什么原理啊），什么话都没说。为什么，什么原理其实到这里就已经找到了最合适的方案了，因为简单直接还不需要改配置。我其实也好奇这两个修改方式为什么有效，也让 AI 替我分析了一下：修改后缀名的原理Vite 处理 CSS Modules 时，哈希是根据传给 postcss-modules 的 from（即文件路径）计算的。.module.css（纯 CSS）的处理流程：style.module.css → postcss-modules（from = 原始文件路径）→ 哈希路径简单直接，SSR 和 client 两次构建完全一致 → 哈希相同.module.less（LESS 预处理器）的处理流程：style.module.less → LESS 编译器 → 中间产物（id 可能附带 ?used / ?inline 等 query） ↓ postcss-modules（from = 中间产物的路径）→ 哈希Client build 和 SSR build 内部对中间产物的 id 处理方式不同，传给 postcss-modules 的 from 路径不一致 → 哈希不同这是 Vite 在处理 CSS 预处理器（LESS/SCSS/Stylus）与 CSS Modules 组合时的一个长期存在的 SSR 一致性问题，在 LESS 这里表现得尤为明显。你用 .css 替换 .less 已经是最干净彻底的修复方式了，不需要任何额外配置。我之前加的 generateScopedName 现在可以去掉，因为它只是用来绕过这个问题的。这里我单独重命名更新了那个出问题的 .module.less 文件后，其实并没有彻底解决问题。generateScopedName 的原理奇怪的是，第一种方法，那怕采用最直接的 文件名 + 路径 生成 Hash，也是正常的，根本不像 AI 说的那样，是文件路径的问题。export default defineNuxtConfig({ vite: { css: { modules: { generateScopedName(name: string, filename: string, css: string) { const hash = crypto .createHash("md5") .update(filename) .digest("hex") .slice(0, 5); return `_${name}_${hash}`; }, }, }, }, });我继续追问它，为什么简化了这个处理函数，也能解决 CSS 的异常：真相：问题不在 filename，在 Vite 内部的 hash 计算你说得对——generateScopedName 的 filename 参数在两次构建里看上去是一样的。这是因为 Vite 在把 filename 传给用户定义的 generateScopedName 之前，已经做了 clean url 处理（去掉 query 参数）。但 Vite 自己内部不走这个用户函数。它有一套独立的 hash 计算逻辑，用的是原始的 Vite module ID（可能带 query 参数）。用简图表示：.module.less（SSR build） → id = "/path/style.module.less?used" → hash = x8r5c .module.less（client build）→ id = "/path/style.module.less" → hash = f6c82 .module.css（SSR build） → id = "/path/style.module.css" → hash = abc12 .module.css（client build）→ id = "/path/style.module.css" → hash = abc12 ✅LESS/SCSS 等预处理器文件在 SSR 构建过程中会被打上 ?used 这类内部标记（Vite 用它区分 SSR 上下文里"已使用"的 CSS），导致原始 id 和 client build 不一样。纯 CSS 文件不走预处理器流程，没有这个标记，id 始终一致。用户定义 generateScopedName 之所以能修复问题，不是因为 replace 做了什么归一化，而是因为只要你定义了这个函数，Vite 就把 hash 计算权交给你，并传入 cleaned filename。你写 filename + name 直接 hash 就够了，两次构建拿到的是同一个 cleaned filename，自然一致。所以结论是方案原因换成 .module.css绕开了预处理器流程，Vite 不会附加 ?used 标记加 generateScopedName接管了 hash 计算，Vite 给你的是 cleaned filename你换 .css 是最干净的解法，不引入任何额外配置。如果项目里还有其他用 .module.less/.module.scss 的组件，遇到同样问题时，加一个简单的 generateScopedName 也能解决，不需要任何 replace，直接用 filename + name 哈希就够了。这个答案我也不确定是否正确，我就将自己的“所见所得”告诉 AI，因为我发现其他的 .module.less 并没有遇到这个问题。其实这里误导了 AI，后面的内容就当看看笑话吧。因为那些没有问题的 .module.less 其实都是走的 CSR 渲染，用了 <ClientOnly> 组件包裹，一个页面通过路由方式进入的都是 CSR，必须刷新才是 SSR，而我验证其他组件发现没问题，都是因为走了 CSR 模式这时候 AI 给我的“猜测”就开始有偏差了，说是什么代码分割的问题，还说是因为我这个组件只有一个页面在用，实际我也验证过了和这个根本没关系，一个组件被多个页面使用也是一样的问题（地址栏直接输入对应 URL，走的是 SSR 渲染）关键差异：代码分割（Code Splitting）看引用次数：组件被引用的页面数Vite 打包结果PhotoCollection1 个页面（slug）→ 打进该页面专属的 独立 chunkPager5 个页面→ 被提取到 shared chunkThemePanellayout（全局）→ 被提取到 shared chunkModal、Notice 等多个页面→ shared chunkVite 发现 PhotoCollection 只有一个页面用，就把它和它的 CSS 打进了 slug 页面专属的 code-split chunk 里。问题就出在这里：在 Vite 的 SSR 构建中，对于这种"只属于某一个页面"的 code-split chunk，模块 ID 的生成方式和 client build 不一致（SSR build 会附加额外标记来防止 CSS 被注入到所有页面），导致 postcss-modules 拿到不同的 hash 输入，输出不同的类名。Shared chunk 里的组件因为 ID 在两次构建中是稳定一致的，所以没有这个问题。PhotoCollection（独立 chunk）： SSR build: id = "/path/photo-collection.module.less?used" → x8r5c ❌ client build: id = "/path/photo-collection.module.less" → f6c82 Pager（shared chunk）： SSR build: id = "/path/pager.module.less" → a1b2c ✅ client build: id = "/path/pager.module.less" → a1b2c如果你把 PhotoCollection 改成也在两个以上的页面引用，Vite 就会把它提取进 shared chunk，.module.less 的问题自然也就消失了。 所以这个问题的触发条件非常精确：.module.less + 只被单个页面引用。这个推理也是错误的，我继续追问它另外一个组件的 .module.less 没问题，其实也是有问题的（因为是 CSR），最后它也没办法了。总结如果想要彻底修复这个 Bug，需要将所有的 .module.less 都改成 .module.css 才行。它并不是其中一个文件才会引发的。要么就是按照上面的方案修改 generateScopedName 配置，看来这个的确是 Vite 内部和 Less 衔接导致的异常，只是我一直没找到具体的证据。当然也如 @Innei 所言，Less 现在确实被淘汰了，Antd 也早就迁移变成了自己的 CSS in JS 实现，我还是让 AI 整体优化重写，拥抱最新技术吧！
最近在做公司项目一个 H5 版本的页面优化，合并到测试环境之后，在手机 Safari 下有部分样式问题，需要联机即时调试更快的解决，但在联机过程遇到了不少坑，这里简单记录一下问题和解决方法。如果想要在移动端真机上调试，就不能使用 localhost 这种地址了，使用局域网 IP 地址又有可能遇到请求后端 API 出现跨域的问题，我的解决方法是选择建立一个与测试、生产环境共享根域的二级域名（当然前提后端也允许了该二级域名的跨域行为），再路由器上设置 Hosts 连接到开发机的方案。比如 paul.me 是生产环境，local.paul.me 就是本地开发环境，强制指定 IP 到开发机，这样就能保留此前传递和获取登录态的逻辑代码访问失败问题起初在另外一台 Mac 电脑上直接访问开发机对应的页面，访问失败，使用 ping IP 则正常，未分析到具体原因，重新连接网络后得到了解决。重定向问题我在手机上打开本地调试地址后，发现会被强制使用 HTTPS，可我完全是本地服务，怎么可能会有重定向？通过搜索得知，这里使用到了一个叫 HSTS 的技术，因为访问生产环境的时候就是根域名，可能在根域名下存在 HSTS 记录，导致所有子域都会强制重定向一次。为什么有些浏览器我输入 http 他也会强制使用 https 我怎么判断是谁的行为在 iPhone 的 Safari 上，需要在 Safari 设置里面找到网站数据，将整个域名下的清空（包括登录态等均被删除），之后重新打开浏览器访问本地调试环境，就没遇到 307 跳转了。联机调试崩溃我原本想用电脑上的 iOS 模拟器来试试的，但是发现 Safari 的调试工具一直有问题，无论是“元素”还是“控制台”都是空白无内容的，重开多次均无效。后尝试使用 iPhone 13 真机调试，但是首次设置就遇到了登录态设置上去但没用的情况。最后我的解决方法是直接在控制台里面设置 Cookie，然后去调整 Cookie 对应的域名和 Path 值为 /，后续请求其他页面就没有掉登录态的情况。但是在我需要调试的关键页面，手机一打开就出现崩溃甚至闪退的情况。经过检查发现本地调试环境该页面的打包代码居然有 100MB+，可能就是这个原因导致的，手机直接不堪重负。因为该页面的路由完全和 PC 共享，仅仅只是一个 if 条件判断来返回手机版的页面，因此这里有完整打包的 PC 端的代码（屎山），目前我直接注释掉了 PC 端的代码，NextJS 自动给我去除掉了，JS 文件瞬间缩小到了 2MB，调试终于得以正常进行...样式问题记录简单来说就是有一个 button 元素，内部有一个 div 设置了 aspect-ratio: 1/1 强制设置比例，结果发现元素塌陷，并没有支撑起来。对于这个问题我发现有个最简单的办法，就是将元素替换成 div，就解决了。挺诡异的，我只知道 button 相较于 div 会有一个额外的 appearence 属性，但我修改它也并没有用。具体 Demo 可见 Safari 按钮高度问题这件事说明目前还是不能完全信任 AI 写出来的代码，依旧得自己充分验证才知道有没有问题。
记一次装机没有一次点亮的排查过程
上周日我帮同事组装了一台主机，配件弄好之后一步一步安装，到最后发现按下电源键居然没有任何反应，我顿时有点慌了...配置单CPU：英特尔 酷睿 i5 12700KF主板：微星 B650M-A WIFI内存：阿斯加特 16+16G DDR4 3600硬盘：闪迪 西部数据 SN510C 1TB显卡：华硕 DUAL RTX 5060电源：微星 铜牌 650W A650BNL散热器：利民 PA120SE按道理主板这玩意也不会这么脆弱，除非从最开始压 CPU 就压坏了，那就确实是完全不可逆的了。参考了网上的一些视频，我最后总结了下面的排查方案和过程。硬件排查排查过程主要还是从硬件开始，首先是电源方面是否完全接到位，一个是主板的接口，一个是 CPU 的接口，这两个供电缺一不可。这里我看 CPU 部分主板有 8 + 4 Pin 的接口，而我只接了其中一部分，我首先把这里额外的 4 Pin 接上，这里的 4 Pin 似乎是给更高规格的 CPU 预留的，我也不是很确定接与不接是否可能有什么影响。接上后发现并没有用，和我自己的华硕主板不同的是他这款微星的主板并没有自带灯光，没启动电源之前没有任何明显的效果，我尝试插了个 USB 设备上去也是没电的，但有可能在首次启动之前它就是不会有任何供电的。接下来我在排查电源键的接线，是否可能接错，不排除线本身的问题的话可以试试螺丝刀短接一次电源部分（我之前就是没机箱这样裸板启动），结果我发现这也没有用。那么这里最大的嫌疑就是电源接口压根就不在这里，主板本身的安装说明写的位置不太清楚，我在网上搜同型号的主板安装教程，发现这里确实是接错位置了，接到了另外一处孔位一致的地方。这时我按下电源键，主板上的风扇灯亮起来了，这让我悬着的心放了下来，但是屏幕却始终黑屏，主板上的 Debug 灯也一直在亮，提示在 Boot 状态，为什么呢。这里我首先怀疑的是显卡，果不其然显卡的风扇被一处线头卡住了，但解决好之后也并不管用，可显卡风扇能转说明接线和电源肯定也是 OK 的，为什么呢？在我录制视频给 @提莫 看的时候，发现显示器突然就能亮了，非常的奇怪。并且能够正常的进入主板 BIOS 设置以及 Windows PE 系统。待我将 Windows 11 写入磁盘重启后，似乎它怎么重启都无法点亮。强制断电之后有概率能进去，但是提示系统损坏需要重新部署安装。我重新部署了一次系统镜像，重启后也是一样的问题，只要按下开机键之后就一直黑屏，似乎走到部署系统阶段之后就是稳定黑屏状态。现在的系统都是 UEFI 模式混合启动，从开机的瞬间就已经到系统层面了，这里有可能是软件问题导致的。软件排查我在网上搜索类似案例，得到的方案绝大多数都在说是电源问题，但有一个人的经验给了我非常大的帮助，说是装机不能使用 4K 屏幕。巧的是我去年确实就弄了一个 4K 显示器作为主力使用，客厅的电视也是 4K 的，就没有那种低分辨率的屏幕了啊，怎么办呢。死马当活马医，试了下客厅电视，结果能用，被电视识别成了 1080P 模式。安装 Windows 的界面也因此正常显示，虽然有花屏的情况（但我更怀疑是电视的问题，因为打开设置界面也是花的），在这里我只能说都 2025 年了，怎么这 Windows 内置的驱动还是这么垃圾，4K 显示器居然都不能原生支持...提莫：Windows 驱动太老了，因为默认的驱动是 CPU 软解，那个叫做 Microsoft 基本显示适配器来着？那个是 CPU 做的，因为没 5060 的驱动至此我在客厅电视完成了系统和驱动的安装，之后再插入到 4K 显示器操作，尝试了几次都能正常启动。后续安装了游戏《鸣潮》简单测试了一下性能和流畅度，4K 60 帧的刷新率还是轻轻松松。
这篇总结酝酿的时间也挺长了，主要还是写了不少去漫展的事情。今年主要加强了在摄影方面的技能点，写码方面没有以前那么上进，已有项目的维护不多，也没有开新项目了。阅读指南：本文内容较长，图片数量也比较多，流量预警！如果速度较慢可以尝试使用科学上网！如果是博客和项目相关可直接往下阅读，漫展相关可直接点击 传送门项目与开源新版小窝前台3 月末，我酝酿了几个月的新版设计风格，正式上线。主要使用了 Vue 3 + Nuxt 框架来开发，依旧是手搓组件和功能，虽说旧版依旧还保留着，只是前端部分已经不再维护了。此后都在不断优化各种小细节，这些改动我认为比较值得列出来：4/12：背包页设备点击可查看详情4/13：在首页和歌单页以波形动画形式，展示当前播放的音乐5/28：获取表情数据更换为 setting/list 新接口，并改为状态管理存储初始值Nuxt 和 Pinia 集成度不错，能直接初始化值到 Store 里6/15：修改 SEO 关键词，解决 SVGO 砍掉动画的问题，更新滚动条样式这个修改记录了一个比较奇葩的问题7/27：新增在读详情快照页，调整在读页面布局给在读增加了快照功能，以对抗链接可能失效的问题9/8：相册单独的灯箱组件，展示照片 EXIF 详细参数为相册单独制作了一个灯箱组件，算是正式把相册“专业化”了，摄影佬的那种感觉。12/24：优化日记列表页媒体过多的阅读体验，默认折叠显示剩余图片数量这条记录的更改几乎都在使用 AI 编写代码，当然样式细节还是由本人优化小窝后端基本上都是以服务前端为主的改动，也是简单挑出一些值得列出来的：2/18：新增在读功能2/27：增加 useTokenPermission 授权方案，在读增加接口支持 Token 授权给插件做了一个类似“授权码”的机制，不需要登录即可执行某些接口的操作3/27：尝试解决 B 站视频不能获取的问题B 站再一次更新了 API 的校验，参考网上的方案尝试解决4/14：API 请求错误显示成 JSON询问 AI 用了一种兜底的方案，防止 PHP 逻辑出错打印出非 JSON 格式的内容4/21：修复路由 GET 的参数只带一个问号去请求会把问号当成请求名称的一部分的问题4/21：新增追番进度更新接口通过插件来实施更新追番进度，数据维护更自动化了5/7：引入 Composer 和 PHPMailer芜湖，终于在 PHP 上使用包管理器引入第三方代码了5/10：基础页面的 Sitemap 功能，后续需要补上日记、项目的目前博客的流量依旧远大于小窝，增加 Sitemap 功能或许能让小窝的收录比之前更好？5/27：增加判断是否有丢失源文件的媒体接口之前不是因为我误操作丢失过很长一段时间的图片么，这个接口是进一步检查缺失问题的，目前 404 的媒体应该减少到 30 条以内了7/27：完善在读接口的快照参数配合前端完成快照功能7/27：新增快照内容转存图片资源的接口就是提取快照里的图片转存一份到服务器上，不然可能会有防盗链的问题，不过后台并没有此功能的入口9/19：兼容 PHP89 月 12 日购买了 Bandwagon 的服务器，全新配置了一套 PHP 8 的环境，之后修复适配详见 尝试配置 OneInstack 环境失败，开始手工配置9/20：不知道为什么查表结果不是 string 而是 int 了，先改了吧不知道是升级了 PHP 版本还是 Medoo，返回数据类型变了导致我的判断出错了9/21：修改跨域相关设置，允许我的网站访问后台地址更换成二级域名来部署了，又遇到了一些新的问题尝试解决11/20：概括 RSS 日记中事情实际发生的时间主要是让 Follow 订阅的用户能了解原文的实际发生时间小窝后台1/11：批量上传媒体，未上传完毕阻拦跳转其他页面1/11：批量上传媒体，中间出现错误会直接结束循环并提示错误，可重新编辑上传队列1/14：分页器仅显示部分页码1/20：修复通知组件动画异常问题这就涉及到 React 的渲染机制了，如果 key 使用 index 则是原地重新 document.createElement 了（猜测）反过来至少用时间戳+随机字符串定义一个唯一值，则动画正常，不会因为其中一个元素消失重新播放2/19：新增在读功能2/29：在读字段扩展，增加作者、站点名、标签3/5：杂项内容修改保存前校验 JSON 格式是否合法5/30：useRequest Hooks 新增缓存功能虽然只是函数名称作为键名，主要是给相册分类使用6/12：新增精选媒体功能7/12：页面新增插图参数8/21：编辑在读文章快照内容9/21：增加跨域允许设置 Cookie 相关逻辑12/6：日记音乐播放器改为全局，页眉可控制（AI 写的12/11：存储音乐播放列表功能小窝工具箱插件在去年的总结里我首次提到了这个项目，但我似乎并没有讲清楚它到底是干嘛的。它主要就是程序自动化为了提高效率而生的，随着网站功能的不断更新，手动维护网站上的数据只会越来越难，这个项目就是为了解决这个问题，直接把对应功能集成在浏览器上，点击一下就能完成我曾经需要手动复杂操作的流程。例如我的赛博手办展示页，需要不断的增加新手办的图片和资料，按照以往只能使用 FTP 手动上传并维护 JSON 数据，而现在只需要打开 B 站对应的购买页面，就可以自动填写信息，提交表单后也同时完成了手办图片的上传。以及追番进度的保存，原先只能在后台手动填写编辑，现在虽说还是没能做到完全自动化，需要进入对应番剧的详情页面后手动点击按钮完成同步，但也比原先要更快捷了！还有一些方便我写内容的实用功能，例如复制当前网页链接，以 Markdown 格式复制到剪贴板，都是浏览器没有内置，但对于我而言使用频繁的琐碎需求。因为这个插件而给网站新开发的「在看」功能，倒是没有特别多的突破和改进。我主要在 7 月份增加了快照功能，用于保存网站的文章内容，以对抗国内动不动 404 的特色。想要抓取的更精准，只能根据不同的网站做一些适配，或者后期直接允许选择内容。社交或许是我这几年没做出过太惊艳的东西，线上社交平台方面的进步不是特别大，而且编程方面的技术提升也不算太高（不像隔壁 @Innei 已经是 NextJS 人气博主了）。下半年的社交活动都主要在线下了，通过漫展和摄影，认识了不少新的朋友，在后面我也会单独详细介绍。人生就像一列有去无回的列车，中途陆陆续续有人上车，也有人下车。你会遇到形形色色的人，有人只是擦肩而过，有人会短暂停留，有人陪你看沿途的风景，很少有人陪你到一直到终点！但是值得庆幸的是一路上都有人上车陪你。这句话我挺认同的，有时候一段关系就慢慢的变得很难维持了。有位好兄弟结婚了之后，可能圈子变了吧，就几乎没有主动和我有过联系了，而我也不知道如何再继续和他保持联系。漫展上的邂逅看过我此前写的博客/日记的朋友应该都知道我比较腼腆、社恐，且圈子小（同学/同事/社团/网站博主/同行）没有多少异性朋友。自从上班之后就已经被工作困住，几乎没有什么别的认识人的机会。这样下去怎么可能会遇到一个能互相看对眼的人呢？我决定扩展交际圈，并尝试着克服社恐。我的群友给我提出了一些想法，去年 11 月（应该是），他们首次提到了去漫展这个话题。我了解到本地比较有知名度的 AS 漫展，加了企鹅群一段时间，也在观察着他们平时讨论的内容，找机会与他们互动。刚开始的时候其实挺困难的，群里都是陌生面孔，发“找搭子”也没人理会，加群之后开了几次展子我都没有去。今年 6 月，再三打听后我注意到一个免费展，在约不到朋友的情况下，我还是选择独自一人去看了看。第一次去漫展在现场我顿时觉得非常的尴尬，没有一个认识的人，也感觉自己和他们完全不在一个频道。我在群里尝试捞人混个脸熟，在大哥 @Hydra 的激励下，我总算得到了第一张集邮的照片，我第一次这么主动找人拍照，表现得特别腼腆（表情很猥琐），但这次来能拍到就不亏了！6 月末有一次原神的 Only 免费展，我叫上了朋友过来。但这次我依然挺怂的，不过大哥 @Hydra 现场给我演示了几次集邮的常规操作，我算是能鼓起勇气了，陆续找了几位老师集邮。大哥和我介绍了 @江晚 老师，他帮我们一起合了影。现场还设有一些小游戏，我最后得到了一份奖品吧唧。7 月暑假已经开始了，此前在苹果店联动活动认识加好友的 @小优，她居然也开始玩 Cosplay 了！那天她出了原神里的愚人众「少女」，约了我出来一起逛富华里，了解到这里的书店就是珠海的二次元圣地，主要是因为这里有卖各种二次元周边。她买了自己推「艾尔海森」的周边，而我最后买了一只「纳西妲」的指偶。这是我少数能被女生约出来的经历，对如此社恐+家里蹲的我来说可以说是极其稀缺了！￼那天也把至尊同学叫了过来，他本以为就我一个人，合照的 Coser 老师是相互不认识的。我们一起逛了书店闲聊，喝了饮料吃了顿饭。他还尝试穿了她的 COS 服体验了一下，场面一度非常的搞笑（只要我不尴尬，尴尬的就是路人）￼8 月初，我去了第一场珠海最大的 AS 展。它位于会展中心，这次来见到的 Coser 非常之多，集邮已经是必须的操作了，我尝试着和她们“扩列”加好友返图了。这次扩了 @灵音（可琳）、@娓君君（知更鸟）和 @白学公主（刻晴）等几位老师。扩列之后就有机会看到各位老师的各种 COS 照，乃至一些日常，就有了和他们互动的机会。或许什么时候就有话题闲聊开始做朋友了呢？我认为单纯去找他们集邮碰碰面可能还不太会让他们对你有比较深刻的印象（毕竟是纯路人嘛）不如选择一个自己在漫展中的新身份，常见的要么就是 Coser，要么就是摄影了。出 COS 需要会的技术还不少，买一件 C 服穿着还仅仅只是自娱自乐呢！还需要化妆和准备道具之后，才算是个基本合格的 Coser，能模仿出其性格体态的话就更还原。或许摄影会比较适合我，自己平时就比较习惯用手机拍照记录生活（不过基本是风光和各种日常），为什么不考虑买一台相机，扩展风光摄影的同时，来尝试下人像摄影呢？8 月中旬有一次 TC 展，结果我在功课做得不多的情况下，急急忙忙就买了一台我认为价位能接受的索尼 A6700，想着能借此机会来学习相机的使用。这次主要发现了套机镜头 18135 光圈较小上的缺点，需要拉长焦距才能有更好的虚化效果。在场内灯光环境一般的情况下，某些时候并不能拍到较为满意的效果。带上装备的漫展10 月初的国庆节也有一次漫展。这次我做足功课准备了新的大光圈镜头，拍摄的虚化效果非常好，但发现买的“豆腐块”常亮补光灯在这里几乎没有任何作用，我也对这次的拍摄做了一个简单的总结。这次漫展的集邮就特别少了，几乎都单独给他们拍照了。这次认识了 @巧乐兹 和 @淮南枝 老师，给她们拍了互勉场照（不过问题还是出在灯光，会显得很黑），@灵音 和 @江晚 老师也在场，既然是列表了我当然是要找她们拍的。国庆漫展11 月初，在前面几次展子的失败经历之后，我终于下定决心买了一盏机顶闪光灯，是神牛的 V860，直接一步到位。并且在前一天的万圣节之夜去了富华里练习使用，虽说参数的调教还有些问题，但至少拍出曝光正常能看的照片了。1102 漫展小记 - 拍照史诗级提升这次付费展的照片相较于此前的可以说是史诗级的提升了！在内场出申鹤的 @小希 老师说今天一个给她拍的摄影都没有，@Hydra 大哥说我这一上去情绪价值不是瞬间就拉满了。在一旁出提纳里的老师是她亲友，给她充分的指导了动作，她问我借了相机给她拍两张，Coser 老师拿相机拍照的样子感觉就非常有意思。1110 淋唔到漫展小记11 月中旬在中山影视城有一次漫展，也是我第一次参加以外拍场景为主的漫展。这次叫了老朋友 @MJ 过来。我和他一起逛展，结果这次展子他一个人都没有集邮，就挺可惜的。@灵音、@渲离染 等几位老师他们组队约了个团片，看他们拍的这么认真，我也不好意思蹭拍… 去找了 @梦华 摄影老师蹭了他的模特（这里敲一下黑板后面会考），就是打闪没打好拍炸了。不过最后找了认识的 @白学公主 老师拍了两张，她这次出的绫华非常漂亮。11 月末的珠海第一届 SN 魂展，主办方估计是想打造成“特摄”主题的，但是市场调研不足，导致出现会展中心这样的大场地却没几个人来玩这样的尴尬情况，我甚至觉得用“摄影比 Coser 多”来形容都好不夸张。与三月七的约定@灵音 老师邀请我和她一起逛展，她作为我的主要拍摄对象。逛展期间发生了令人尴尬的事，也有开心的事。我第一次找 Coser 老师单独拍照被拒了，但是品尝到了 @灵音 老师的手工自制小蛋糕，非常好吃。这次闪灯也比之前稍微熟练了一些（但也有过曝的照片，但 RAW 拉回来还是绰绰有余），漫展结束后跟着她认识的摄影一起在场外拍外景，多好的学习机会。拍摄结束后一起在富华里搓了一顿饭，认识了 @柠檬茶、@阿卓、@小安 几位很厉害的摄影老师，以及出小樱的 @卿安 老师。12 月初在澳门有第一届 IMAC 展，这个展邀请了很多大咖嘉宾，其中有我认识的 @谢莹 老师，她是「刻晴」和「嘉明」的配音演员。这不冲都不行啊！这也是第一次排签售，我不是 SVIP，人看着不算多但还是排了快一个小时。这次展拍的照片蛮多的，我当时并没有来得及写笔记详细记录，就在这里简单介绍一下。这次展遇到了 Coser @理如 和 @阿卓 摄影老师，他买了 VIP 提前摆好了灯阵，我借他的灯阵也跟着拍了一些 Coser 老师，其中认识了出星期日的 @善信 老师。我们那天晚上漫展结束之后还去吃了顿烧烤。说起来当初加这个 @理如 老师的过程也是很好笑的，我“视奸”了她空间很多次，她居然来主动加我。上次在中山影视城漫展拍炸了的那个 Coser 老师其实是她，好在这次借了 @阿卓 摄影老师给她拍了一张效果还行。1 月买了引闪器，首次在春节前的中山 AS 展使用，借了 @杰哥 摄影老师的灯阵来玩了一下，不过效果比较平，不太能看出来是用了几盏灯的样子… 还需要继续努力～旅游今年去了最多次的城市是深圳，七月份跟着亲戚的车去 Citywalk 了一次，逛了市区一些大商场，和同学 @BB 见了面，吃了一顿日式汉堡肉。十月份去了沙头角、大梅沙和深圳人才公园。十一月份我自己坐大巴去了两趟，只是为了修我的 MacBook Air，当然和 @MJ 去了深圳的二次元胜地逛玩，还是不亏的。十二月年末也去了一趟，和 @MJ、@凯文 两人一起吃饭聊天和闲逛，途中还体验了深圳刚开通的地铁 13 号线。深圳次元之旅 / 拜访苹果官方直营店又去一趟深圳这几次在深圳的经历给我的最大感受就是吃的品种特别丰富（但大商场里面价格也蛮贵的），或许是因为大城市总人口多，外来人口也比较多。其次是澳门，办理了新版的卡式港澳通行证之后不去转转就没意思了。五月是时隔多年（2009）后第一次去澳门玩，和爸妈一起过去，主要在澳门半岛转悠。去了大三巴、大炮台、金莲花广场等著名景点，品尝了小吃街的牛杂，也进了赌场参观表演。九月第二次去，主要是氹仔那边的酒店为主，首先去了威尼斯人酒店闲逛，去了官也街品尝美食，去伦敦人、巴黎人等酒店拍照打卡，去美高梅蹭了杯招牌奶茶。最后在澳门教科文中心参观画展，见证了我大舅的书画作品被澳门基金会收藏的过程。其他城市就只有 1-2 次。二月春节期间去了广州番禺的一些小众景点拍照打卡，当天返程还去了中山华侨公园和紫马岭公园，这里的一些现代建筑非常出片。八月份去了香港 Citywalk，主要在九龙和湾仔区，去体验了富士相机和索尼的镜头，在星光大道和明星的手印合影，和 @凯文 见了面并一起吃饭聊天。这里图一直没弄，先鸽着吧==数码设备JBL Flip 62 月，我打算购置一个新的蓝牙音箱，一是提高平时听歌的音质，二是方便携带，这款音箱的性价比我认为还不错。和我表哥的 Bose 音箱一起听几乎没什么差异，而他那款比我的更贵。红米 4K 27 寸显示器3 月，我原先使用长达 7 年的戴尔，开机时出现了闪烁的横线，看上去是面板出现了问题。再三考虑下还是决定买个 4K 高清的，而不是高刷玩游戏的。看了评测视频后，认为红米的这一款性价比不错，甚至比它自家小米品牌的还要好…这台显示器配合 MacBook 的体验确实不错，在 Windows 下玩游戏，原先 1080P 的画面必须拉到 2K 才能显示成此前小窗的尺寸，显卡的功耗也上去了，不过我现在玩游戏的时间其实也没有之前那么多了…索尼 A67008 月，我购入了人生第一台相机，索尼 A6700，是在我能承受价格范围之内最好的机型。虽说 A7C2 会更好，但是镜头方面肯定是要比半画幅更贵一些，它裸机的价格就能买到 A6700 带 18135 套头了。别人都在说，这个价格你为什么不买全画幅。这里还要考虑到我当时的使用场景，因为拿到相机几天之后我就带去漫展拍照学习使用了，当时也是急急忙忙的做了选择。不过现在看来，我的确可以再花更长的时间先买一台 A7C2，再认真挑选镜头。不过预算也会增加 1-3K 吧。机器开箱之后就掉价了，现在再卖掉并不划算。A6700 实际上也很不错，并没有让我失望。起初拍的不好其实是补光问题，带上闪光灯效果马上就不一样了。相较于部分老相机来说，这台新机器对焦迅速的优势也显现了出来，它更容易拍到正常对焦的照片。并且还能直接录制 120 帧的 4K 视频，后期还可以考虑买个云台拍点小视频了。既然相机都买了，大光圈镜头总要配一个吧，闪光灯也得来一个吧，蹭别人的灯总要个引闪器吧，要是自己单打独斗是不是也要有个柔光箱？摄影，就是用光的艺术！把钱花光的艺术！MacBook Air 送修11 月末，我去年购入的 MacBook Air 居然也送修了！在我手上的 MacBook 用一台就坏一台？不过还在质保期内，免费维修，就是苦了我去了两趟深圳，不过还好期间和 @MJ 去了深圳一些不错的地方，也算是 City Walk 了一波。iPad Air 612 月初，我再三考虑下还是选择购入了 iPad Air 6，M2 芯片 256G 的版本，相较于新出的 Mini 7，我还是觉得它的性能会更耐用一些。它的尺寸 10 寸确实更大没那么便携，实际用多了也接受了，回看 Mini 怎么这么小！这个尺寸拍完照片立马就能打开看效果，甚至可以直接修图，卷死其他摄影（不是）。不过 Creator App 并不支持通过软件删除照片，这点还是有些不太方便。（结果实际几次拍摄都没现场修过）番剧今年主要看了这几部番剧，《间谍过家家》、《甘城光辉游乐园》和《亚托莉》，都是日常系。《间谍过家家》就不用多说了吧，其实是因为之前只看了 1-2 集后鸽了，今年直接从第一季补番到第二季了。还去电影院看了剧场版，这个剧场版的剧情可以说是把“浮夸”这个词发挥到极致了，非常建议一看！《甘城光辉游乐园》是游乐园经营主题的一个番，我曾经是非常喜欢玩《过山车大亨：3》的玩家，因此对这个题材的作品有着不错的好奇心。《亚托莉》其实是因为喜欢她这个可爱的角色形象，GalGame 没花精力推看一下这个我觉得也不错，虽说看评论说这部番有改写过一部分情节。截至写下这行文字的这一天，我还看了一集《魔女之旅》，我觉得接下来应该再补一下《孤独摇滚》、《莉可丽丝》和《葬送的芙丽莲》，都是此前很热门的番剧。情感自去年主动勾搭过一次女生之后，就再也没有第二次了。一方面我认为找群友做军师的过程过于超纲了，拿捏不住他给我设定的身份。再一个是在父母与其他人无形对自己施加了压力的情况下，我很难摆脱“一定要让她成为我女朋友”的这种观念（即便自己或许只是想做个朋友而已吧）。这件事让我 Emo 了一晚彻夜无眠，令我至今历历在目。今年家里人并没有安排和介绍过任何人，有相亲活动我也没参加过。但我还是决定以朋友相处的方式多和女生交流互动，比较松弛有度。可眼看自己年纪也是越来越大了，还有多少时间让我有试错的机会呢？假如真的有人对自己感兴趣了，又该如何识别出她的小心思呢，她是否对其他人也这样呢，这对我来说确实是一个很伤脑筋的问题啊。总结在这一年，我在漫展上结交了很多新朋友，总算是在前几年的短板上首次得到了突破，或许能让我没那么社恐了。通过摄影也极大的增加了我想出门走的意愿，但以此同时也减少了当宅男玩游戏、看番剧和写代码的时间。年末公司开始 996 加班后，发扬爱好的时间进一步被缩短，编码能力的提升更多只能通过公司项目来实现，我应该尽可能的抓住一些机会。个人网站方面还需要更进一步的思考如何保证质量的同时，借助编程和 AI 的力量尽可能的提高维护效率（例如照片的处理等等）。作息方面依旧没有得到改善，除了保证充足的睡眠外，还需要提高一些时间使用效率。自问自答玩摄影并不一定能赚钱，学来有啥意义？摄影本身确实没有什么意义，意义都是自己赋予的不是么。记录当下，在未来能够重新回忆起来，就是摄影本身的意义吧。况且学会摄影也可以给未来的对象拍呀，怎么想都是好处大于坏处吧。游戏帐号什么的不一定自己能持续留住，但是写文，摄影，做项目的成绩绝对是持续积累的。给对象拍？那你找到对象了吗？有进展么？依旧没有，凭实力单身了，在漫展上也不是所有摄影都有女朋友啊（更何况还有一些是先有对象才学摄影的不是么），我目前还比较享受摄影过程的，相较于期待找个对象，不如先期待来一个固模吧...现在在摄影和编程技术方面，你要如何权衡？现在 996 工作时间比较长，只能在工作方面多花心思看看能不能提升点啥了，AI 其实也是个很好的自学工具，前提是你能很好的调教它。长期在电脑面前工作也比较容易导致职业病（久坐、长时间看屏幕等），有时间休息还是尽量出门走走，让眼睛也好好休息下，这点时间就尽量安排给摄影活动了，无论是拍风光还是人像我觉得都好。立下 Flag去年和前年并没有立下任何 Flag，主要是总结的拖延非常严重，已经没有写的必要了好吧。现在回过头来发现就算写了似乎都挺难完成的，好在今年看上去似乎有了那么一丁点转机，看看能不能努力一下吧。[] 学习一个后端框架，能挺好快速出活的那种[] 继续学习提升摄影技术，按需购买新设备[] 看下能否将主页相册遗漏的照片都补全了[] 争取找到第一任女朋友？往期2023 年度总结2022 年终总结2021 年度总结2021 年度总结2020 年度总结2019 年度总结2018 年度总结2017 年度总结朋友们的总结@Innei：2024 · 前路未尽，初心犹在这一年，从乌镇跨年开始，经历了公司团建去大理的愉快旅程，但之后突如其来的裁员让人陷入抑郁，最终加入了 RSS3，参与了 Follow 项目的开发，体验到了开源工作的成就感。在游戏方面，通关了《黑神话：悟空》和《最后生还者》，感受颇深。尽管只去了南京旅行，但年底买了特斯拉 Model 3，技术上写了多篇技术文章，对技术的理解更深入。虽然对未来抱有悲观态度，但仍希望明年会更好。@林陌青川：野花做了一场玫瑰梦这是一篇深情的 2024 年终回忆录。博主林陌青川记录了与“汪小姐”从网络技术群相识，到各自在互联网大厂（携程、字节、B站）实习奋斗，最终在上海奔现的故事。文章回顾了两人从错过的遗憾到低谷期的相互陪伴，以及见面时同游静安寺与外滩的心动瞬间。尽管博主自比“野花”仰望优秀的她，感叹现实差距，但他仍视这段缘分为人生的“上上签”，以此文致敬这段美好的相遇。
近期开始回忆杀状态，在 Steam 上购买并游玩了一些以前玩过的经典小游戏，其中包括来自 PopCap 宝开出品的《吞食鱼：2》。可能人老了吧，新的游戏都玩不动了，要么烧脑要么太肝，还是这种休闲小游戏玩法简单又上头！当时这部作品也有人将它翻译成《大鱼吃小鱼》，其中“吞食鱼”这个说法来源自《幻想游戏》系列。它也推出过很多“不同版本”的续集（其实就是一个整合包），汇聚了很多来自不同厂商的休闲游戏。游戏玩到了，BGM 也在反复洗脑了，那么如何将游戏里面的音乐提取出来继续听呢。了解博主过去黑历史的朋友应该知道，当时老喜欢用各种方式去提取各种游戏的资源素材了，其中《摩尔庄园》、《疯狂农场》等游戏都是我的常顾对象，收集各种游戏的 BGM 并且偶尔拿出来播放算是我的一个小众爱好。我找到了当年的一些素材提取工具（文件的修改日期为 2015 年，实际可能更早），其中有一款工具叫做 XMPlay，它可以用于 MO3 音乐的播放和提取。MO3 是一种音频文件格式，主要用于模块化音乐，类似于其他 MOD 格式（如 IT、XM、S3M 等）。它由 Ian Luck 为 BASSMOD 引擎开发，MO3 文件的一个显著特点是它使用 MP3 或 Ogg Vorbis 格式来编码音频样本，这使得文件体积相对较小，同时能够保持较高的音质参考：MO3 是什么格式，为什么现在几乎没人使用了对于模块化音乐，我的理解就是它将每一个音乐节拍的声音都作为一个片段存储，播放过程则是将所有片段循环复用，有点类似于 MID 音频格式，但是 MID 格式外放出来的音色受操作系统、播放软件等的影响较大时隔已久，我甚至都忘记了当年是怎么使用它提取音乐了，一番研究后总算是找到了办法。顺带一提，我 Google 之后得到的结果往往都是要你付费购买某些软件，几乎没什么中文文章提到过这些东西，看来确实有些小众啊，那么我就水一篇文章简单记录一下首先从 un4seen 下载并打开 XMPlay 播放器，这里我使用的旧版本，界面稍微比较复古。正常打开一首 MO3 音乐播放（吞食鱼游戏目录下有个 music 文件夹里面就是），此时从播放器主界面右键 -> Options -> Output -> Output Device 选择 "WAV Writer" -> Apply此时点击音乐播放按钮，就会弹出对应的另存为界面，依次确认导出，所有的音乐都会导出成功。如果想直接导出成 mp3、flac 和 ogg 格式，则需要下载对应的 CLI 工具，比如 Output Device 选择 "Encoder - LAME" 则是导出 mp3，需要先使用 winget 安装 lame 编码器winget install lame如果导出的音乐存在部分内容丢失或中断的情况，可能是因为你在播放器已经开始播放了，点击“停止播放”按钮再试一次即可目前博主已知使用 MO3 格式作为背景音乐解决方案的游戏包括：吞食鱼：2（Feeding Frenzy）幻幻球（Peggle）仓鼠球（Hamster Ball）理论上多数同期游戏都采用了这个方案，性价比特别高，比存储完整的 MP3、OGG 等格式都小，保真度还高。可惜从宝开《植物大战僵尸》等作品开始，都采用了 PAK 打包普通的音乐格式，此方案不再流行，也算是时代的眼泪了吧！
该写好代码吗？写好了也可能变得不好了
最近同事分享了一篇文章：《该写好代码吗？我也迷茫了。写的代码好被替代，写的代码差到处救火》，引发了我的思考。我问了问他的看法，是写好还是不写好呢。答案可以说是意料之中，“正常写就好”。他觉得正常写也会有 Bug，代码也不会很糟糕。确实是这样，因为即便你当时认为写好了，这需求变化速度实在太快，后续很有可能就不能满足需求了，就产生了所谓的 Bug。这让我很自然的联想到我现在在公司做的 Felo 搜索 项目，确切来说它现在的定位已经渐渐不是一个纯粹的搜索网站了，而是各种 AI 工具的混合体，主要竞争对手是 Perplexity、Genspark、天工 等。前端方面我的评价是基本上已经乱的一锅粥，原先的逻辑很多不能满足现在的需求，就是一个超级缝合怪，既要 Perplexity 的那种搜索总结功能，又要 Genspark 的 Agent 型对话交互模式... 两种完全定位不同的东西被强行融合到了一起。但是换个角度来说，大公司做的东西不也是堆屎山么，不见得有多好，除非推倒重新开发。我们现在 996，就是飞快的加功能，老大天天都想发布新版本，怎么可能愿意干这种事？最近我们在做的 AI 生成 PPT 功能也差不多是这样子，越演越烈，产品交互方面并不统一，此前 PPT 是一个独立页面，任意入口点击后打开新窗口生成，之后是独立一套交互流程。而现在为了兼容“PPT Agent”模式（单独一个工具页，通过上传文档什么的触发创建一条记录），强行让其他入口套用它的逻辑。作为对比，和它入口旁边的同级功能，思维导图、智能图形，目前都不会单独创建新的记录（虽然它们也各自有一个 Agent 模式）。原先交互：打开一条搜索记录，找到生成的 PPT，打开独立页面现在交互：上述入口保留，生成的 PPT 会产生单独一条搜索记录，生成成功后则点击直接打开独立页面，并且作为 PPT 其入口还不在“文档库”里面，很迷后续需求还说得兼容之前搜索的逻辑，把原先那套模式给搬到新的 UI 上面，主打一个“抄谁不像谁”，代码逻辑的耦合性实在太强，很难想象之后要产生多少 Bug... 只能感慨地说一声，现在它已经彻头彻尾变成了“Felopark”了，这样的架构，代码也不太可能能写得多好了。所以回到最前面的话题，写代码正常发挥即可。至于可替代性什么的，只要你的能力不差，去哪里都会发光发亮不是么。