阅读视图

发现新文章,点击刷新页面。
🔲 ☆

记一次被街头诈骗的经历

街头借钱诈骗套路复盘.webp

列表的漫展圈年轻人比较多,特意做成了“瓜条”形式希望能被更多人看到。这里也提供了由 AI 改写的大众阅读版本,方便各位博友阅读,大致表达是相似的。

大众版(AI 改写)

受害者:博主
骗子:自称"王艳"的一位阿姨

简要说明:博主热心帮助一位陌生人借钱坐车,却遭遇诈骗。骗子自称是某物流公司的高层员工(生意人),说自己出差后将手机和钱包落在了司机车上,而自己是外地来的孤身一人,丈夫已离异且是限制高消费人员。实际上这些内容全部是编造的,她就是个骗子。

事件经过

3 月 13 日下班后,博主正常休息吃完晚饭,在回公司的路上遇到这位阿姨。她主动过来向博主问路,说自己要去一个很远的地方(深圳宝安机场)。博主正常打开地图给她简单指路,她表示自己没有手机没办法乘车和购票,需要抵达那个位置联系对应的送货司机。博主当时就问她为什么要去这么远的地方,她强调自己是做生意的,在某个物流公司工作(没听清具体名字),在附近谈客户。她还询问博主觉得在附近开一家店的潜力如何,博主表示附近的制造业工厂确实挺多,或许有这个需求(但博主并不是内行人员,她问博主这样的问题博主又该如何回答呢)。

她还会用各种常见话题和博主套近乎,比如问博主年纪多大,是否有谈女朋友。等博主放松警惕后,她开始询问博主的行业、工作地点和薪资,以及是否是本地人、家乡在哪等(博主出于个人保护,都模糊回答了)。她说自己平常在南京发展,如果有机会博主去那边做客,她会请博主吃饭,还强调出门在外多一个朋友能多一份机遇(实际上这是博取信任的话术)。

为了表示自己能还钱,她要加博主微信。那个微信号用的是一个男人的头像,博主当时就表示怀疑,但她说是和丈夫离婚了,这个号现在实际上是她在使用。她还说自己有个儿子做主播,月收入可达到 2W,表示自己是生意人不差这点钱还给博主。

博主用支付宝给她换了现金后,打算给她拍照留存。她当即破防表示博主对她不信任,要求删除照片。博主表示现在骗子太多,这属于正常防范。删除照片后博主本以为事情结束了,但她仍不断用话术影响博主。博主出于好心最终还是借给了她。她当即表示感谢,并附上各种祝愿(祝你工作顺利,生意兴隆?)。

她承诺第二天还钱,但实际并未归还。截止到这篇文章编写时间,微信好友申请依旧处于未通过状态。

事后复盘:骗局中的疑点

现在简单复盘一下,其实她全程的对话充满了各种套路和漏洞。博主此前也遇到过几次街头骗局并成功逃脱,无奈这次对方特别精明,博主着实是大意了。

疑点一:打车方式不合理

从最开始的问路就挺多问题了。她一直强调自己出差公司报销打车的费用,必须要开发票,而打出租车不能提前支付,只能打表后结算费用。这样她就引导博主以现金支付

博主和她去了附近的一家便利店,店长当即表示无法提供这么大面额的现金(实际上店长可能已经在警示博主这个人有问题,让博主好好想想,但当时博主因工作疲惫没想到这个可能性)。

疑点二:询问博主的工作安排

因为博主已经说自己在工作了,她还询问博主明天还要不要上班。这实际上很有可能是担心自己骗了博主之后再次在附近作案会被抓获。因为是小额诈骗,警方一般是不予处理的。

疑点三:不向儿子求助

既然她都有收入这么高的儿子了,怎么不能直接求助他呢?这也是一个巨大的疑问。其实完全可以让博主用手机联系她儿子,然后即时转账,也就不存在借钱这么一回事了(当然她很有可能会用其他理由推辞,比如已经和儿子分家了什么的)。

疑点四:情绪激动后的反向施压

她情绪激动后还在强调自己的着装以及已经加了微信,表示自己是清白的,当即开始反向 PUA 博主,明显表示博主在冤枉普通人。“你把阿姨当作什么人了”“你这是在侮辱我”(实际上这也是一种洗脑话术,恶人先告状,继续利用了博主的善良)。微信号可以是假的,好友是可以不通过的,手机号也可以是空号,总之博主根本没有办法找到她。

总结

人在外地的身份是自己给的,今天她是企业员工,明天就可以是霸道总裁。所有的身份信息都无法核实。

博主的这次经历再次向各位亲朋好友发出警钟:面对陌生人一定要提高警惕,对方可能会疯狂输出话术影响你的判断。如果来不及思考,则最好提出找警察或直接拒绝

瓜条版(原创)

苦主热心帮助陌生人借钱坐车惨遭背刺

苦主:匿了
瓜主:自称“王艳”的一位阿姨

省流版:苦主热心帮助陌生人借钱坐车惨遭背刺,瓜主称自己是某物流公司的高层员工(生意人),出差后将手机和钱包落在了司机车上,而自己是外地过来孤身一人,丈夫离异且是限制高消费人员。实则内容全部编造,骗子一个。

事件经过

苦主在 3 月 13 日下班后正常休息吃晚饭,回公司路上遇到瓜主表示要问路,说自己要去一个很远的地方(深圳宝安机场)。苦主也是正常打开地图简单指路,瓜主表示自己没有手机没办法乘车和购票,需要抵达该位置联系对应的送货司机。苦主当即问为什么要去这么远的地方,瓜主强调自己是做生意的,在某个物流公司(没听清),在附近谈客户,还询问我附近开一家店的潜力如何,苦主表示附近的制造业工厂确实蛮多,或许有这个需求(但苦主并不是内行人员,你问我这样的问题我又要如何回答呢)

瓜主还会以各种常用话题和你套近乎,比如年纪多大,是否有谈女朋友,等你放松警惕后,开始询问你的行业、工作地点和薪资,以及是否本地人,家乡在哪等(苦主出于个人保护,均模糊回答)。瓜主说自己平常在南京发展,如果有机会前往做客,瓜主会请苦主吃饭,强调出门在外多一个朋友能多一份机遇,实际为博取信任的洗脑话术

为了表示自己能还钱加了个微信,该微信号是一个男人的头像,苦主当即表示怀疑,但瓜主说是和自己丈夫离婚了,这个号现在实际上是瓜主在使用。瓜主还说自己有个儿子做主播,月收入可达到 2W,表示自己是生意人不差这点钱还给苦主。苦主为其借钱(ZFB 换现金)后,打算给瓜主拍照留存,瓜主当即破防表示苦主对她不信任要求删除照片,苦主表示骗子太多属于正常防范,删除照片后本以为事情结束,但瓜主后续依旧持续输出洗脑苦主,苦主出于好心最终还是借给了瓜主。对方当即表示感谢,并附上各种祝愿(祝你工作顺利,生意兴隆?)

对方承诺第二天还,但实际未还,截止到瓜条编写时间,微信好友依旧处于未通过状态。

事后复盘

现在简单复盘一下,其实瓜主全程的对话也是充满各种套路和 Bug 疑点的。苦主此前也遇到过几次街头骗局并成功逃脱,无奈此次对方特别精明,苦主着实是大意了。

从最开始的问路就挺多问题了,瓜主一直强调自己出差公司报销打车的费用(必须要开发票,而打出租车不能提前支付,只能打表后结算费用,后续引导苦主以现金支付

苦主和瓜主去了附近的一家便利店,店长当即表示无法提供这么大面额的现金(实际可能已经在警示苦主这个人有问题,好好想想,但当时苦主因工作疲惫没想到这个可能性)

因为苦主已经说自己在工作了,瓜主还询问过苦主明天还要不要上班(实际很有可能是担心自己骗了苦主后再次在附近作案被抓获)因为是小额诈骗,警方一般是不予处理的。

既然你都有收入这么高的儿子了,怎么不能直接求助他呢,这也是一个巨大的疑问,其实完全可以让苦主手机联系对方然后即时转账,也就不存在借钱这么一回事了(当然她很有可能会以其他的理由推辞,比如已经和儿子分家了什么的)

瓜主破防后还在强调自己的着装以及已经加了微信,表示自己是清白的,当即开始反向 PUA 苦主,明显表示苦主在冤枉普通人。“你把阿姨当作什么人了”“你这是在侮辱我”(实际上这也是一种洗脑话术不对么,恶人先告状,继续利用了你的善良)微信号可以是假的,好友是可以不通过的,手机号也可以是空号,总之你根本没有办法找到对方。

总结

人在外的身份是自己给的,今天我是企业员工,明天我就可以是霸道总裁。苦主的此次经历再次向各位亲朋好友发出警钟,面对陌生人一定要提高警惕,对方可能会疯狂输出洗脑影响你的判断,如果来不及思考,则最好提出找警察或直接拒绝

最后让我们来看看这位丑陋的女人,必须得曝光出来:

骗子“王艳”

虚假的微信号

🔲 ☆

记一次升级 Nuxt 4 的诡异问题

前几天 我将 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} 不存在`);
};

我依次尝试了一些改动,发现都没解决...

  1. 重命名文件(styles.module.less -> photo-collection.module.less
  2. 修改文件内容(.collection 下样式只写一条 background 规则)
  3. 将引入方式调整成 ifelse 的形式

generateScopedName 解决方案

只好求助牛逼的 Claude Opus 4.6,初步排查原因是开发模式和生产模式下生产的 CSS Hash 不一致。


找到根本原因了!

  • 客户端 JS bundle 用的哈希:_collection_f6c82_1
  • 服务端 SSR bundle 用的哈希:_collection_x8r5c_1
  • CSS 文件 里的规则:._collection_f6c82_1(和客户端一致,和服务端不一致)

这就是问题所在:CSS Module 的类名哈希在 SSR 构建和客户端构建之间不一致。

具体表现:

  1. 服务端渲染 HTML 时,生成的 class_collection_x8r5c_1
  2. 浏览器加载的 CSS 文件里只有 ._collection_f6c82_1 的规则
  3. 两者对不上,样式完全不生效

这与 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-modulesfrom 路径不一致 → 哈希不同

这是 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 计算

你说得对——generateScopedNamefilename 参数在两次构建里看上去是一样的。这是因为 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]])→ 打进该页面专属的 独立 chunk
Pager5 个页面→ 被提取到 shared chunk
ThemePanellayout(全局)→ 被提取到 shared chunk
Modal、Notice 等多个页面→ shared chunk

Vite 发现 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 整体优化重写,拥抱最新技术吧!

🔲 ☆

记一次升级 Nuxt 4 的诡异问题

前几天 我将 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} 不存在`);
};

我依次尝试了一些改动,发现都没解决...

  1. 重命名文件(styles.module.less -> photo-collection.module.less
  2. 修改文件内容(.collection 下样式只写一条 background 规则)
  3. 将引入方式调整成 ifelse 的形式

generateScopedName 解决方案

只好求助牛逼的 Claude Opus 4.6,初步排查原因是开发模式和生产模式下生产的 CSS Hash 不一致。


找到根本原因了!

  • 客户端 JS bundle 用的哈希:_collection_f6c82_1
  • 服务端 SSR bundle 用的哈希:_collection_x8r5c_1
  • CSS 文件 里的规则:._collection_f6c82_1(和客户端一致,和服务端不一致)

这就是问题所在:CSS Module 的类名哈希在 SSR 构建和客户端构建之间不一致。

具体表现:

  1. 服务端渲染 HTML 时,生成的 class_collection_x8r5c_1
  2. 浏览器加载的 CSS 文件里只有 ._collection_f6c82_1 的规则
  3. 两者对不上,样式完全不生效

这与 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-modulesfrom 路径不一致 → 哈希不同

这是 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 计算

你说得对——generateScopedNamefilename 参数在两次构建里看上去是一样的。这是因为 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]])→ 打进该页面专属的 独立 chunk
Pager5 个页面→ 被提取到 shared chunk
ThemePanellayout(全局)→ 被提取到 shared chunk
Modal、Notice 等多个页面→ shared chunk

Vite 发现 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 整体优化重写,拥抱最新技术吧!

🔲 ☆

记一次移动端 Safari 调试踩坑

最近在做公司项目一个 H5 版本的页面优化,合并到测试环境之后,在手机 Safari 下有部分样式问题,需要联机即时调试更快的解决,但在联机过程遇到了不少坑,这里简单记录一下问题和解决方法。

如果想要在移动端真机上调试,就不能使用 localhost 这种地址了,使用局域网 IP 地址又有可能遇到请求后端 API 出现跨域的问题,我的解决方法是选择建立一个与测试、生产环境共享根域的二级域名(当然前提后端也允许了该二级域名的跨域行为),再路由器上设置 Hosts 连接到开发机的方案。

比如 paul.me 是生产环境,local.paul.me 就是本地开发环境,强制指定 IP 到开发机,这样就能保留此前传递和获取登录态的逻辑代码

访问失败问题

起初在另外一台 Mac 电脑上直接访问开发机对应的页面,访问失败,使用 ping IP 则正常,未分析到具体原因,重新连接网络后得到了解决。

重定向问题

我在手机上打开本地调试地址后,发现会被强制使用 HTTPS,可我完全是本地服务,怎么可能会有重定向?通过搜索得知,这里使用到了一个叫 HSTS 的技术,因为访问生产环境的时候就是根域名,可能在根域名下存在 HSTS 记录,导致所有子域都会强制重定向一次。

在 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 帧的刷新率还是轻轻松松。

鸣潮实机

卡提

🔲 ☆

2024 年终总结

这篇总结酝酿的时间也挺长了,主要还是写了不少去漫展的事情。今年主要加强了在摄影方面的技能点,写码方面没有以前那么上进,已有项目的维护不多,也没有开新项目了。

阅读指南:

本文内容较长,图片数量也比较多,流量预警!如果速度较慢可以尝试使用科学上网!
如果是博客和项目相关可直接往下阅读,漫展相关可直接点击 传送门

项目与开源

新版小窝前台

新版小窝前台截图

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:兼容 PHP8

  • 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 非常之多,集邮已经是必须的操作了,我尝试着和她们“扩列”加好友返图了。这次扩了 @灵音(可琳)、@娓君君(知更鸟)和 @白学公主(刻晴)等几位老师。

可琳集邮,Coser 灵音

知更鸟集邮,Coser 娓君君

刻晴集邮,Coser 白学公主

奈亚子集邮,Coser 里川

扩列之后就有机会看到各位老师的各种 COS 照,乃至一些日常,就有了和他们互动的机会。或许什么时候就有话题闲聊开始做朋友了呢?我认为单纯去找他们集邮碰碰面可能还不太会让他们对你有比较深刻的印象(毕竟是纯路人嘛)不如选择一个自己在漫展中的新身份,常见的要么就是 Coser,要么就是摄影了。

出 COS 需要会的技术还不少,买一件 C 服穿着还仅仅只是自娱自乐呢!还需要化妆和准备道具之后,才算是个基本合格的 Coser,能模仿出其性格体态的话就更还原。或许摄影会比较适合我,自己平时就比较习惯用手机拍照记录生活(不过基本是风光和各种日常),为什么不考虑买一台相机,扩展风光摄影的同时,来尝试下人像摄影呢?

8 月中旬有一次 TC 展,结果我在功课做得不多的情况下,急急忙忙就买了一台我认为价位能接受的索尼 A6700,想着能借此机会来学习相机的使用。这次主要发现了套机镜头 18135 光圈较小上的缺点,需要拉长焦距才能有更好的虚化效果。在场内灯光环境一般的情况下,某些时候并不能拍到较为满意的效果。

带上装备的漫展

10 月初的国庆节也有一次漫展。这次我做足功课准备了新的大光圈镜头,拍摄的虚化效果非常好,但发现买的“豆腐块”常亮补光灯在这里几乎没有任何作用,我也对这次的拍摄做了一个简单的总结。这次漫展的集邮就特别少了,几乎都单独给他们拍照了。

这次认识了 @巧乐兹 和 @淮南枝 老师,给她们拍了互勉场照(不过问题还是出在灯光,会显得很黑),@灵音 和 @江晚 老师也在场,既然是列表了我当然是要找她们拍的。

国庆漫展

11 月初,在前面几次展子的失败经历之后,我终于下定决心买了一盏机顶闪光灯,是神牛的 V860,直接一步到位。并且在前一天的万圣节之夜去了富华里练习使用,虽说参数的调教还有些问题,但至少拍出曝光正常能看的照片了。

1102 漫展小记 - 拍照史诗级提升

这次付费展的照片相较于此前的可以说是史诗级的提升了!在内场出申鹤的 @小希 老师说今天一个给她拍的摄影都没有,@Hydra 大哥说我这一上去情绪价值不是瞬间就拉满了。在一旁出提纳里的老师是她亲友,给她充分的指导了动作,她问我借了相机给她拍两张,Coser 老师拿相机拍照的样子感觉就非常有意思。

申鹤,Coser 小希

小提老师

克鲁鲁,Coser 虾米

1110 淋唔到漫展小记

11 月中旬在中山影视城有一次漫展,也是我第一次参加以外拍场景为主的漫展。这次叫了老朋友 @MJ 过来。我和他一起逛展,结果这次展子他一个人都没有集邮,就挺可惜的。@灵音、@渲离染 等几位老师他们组队约了个团片,看他们拍的这么认真,我也不好意思蹭拍… 去找了 @梦华 摄影老师蹭了他的模特(这里敲一下黑板后面会考),就是打闪没打好拍炸了。不过最后找了认识的 @白学公主 老师拍了两张,她这次出的绫华非常漂亮。

教堂

玩偶

神里绫华

11 月末的珠海第一届 SN 魂展,主办方估计是想打造成“特摄”主题的,但是市场调研不足,导致出现会展中心这样的大场地却没几个人来玩这样的尴尬情况,我甚至觉得用“摄影比 Coser 多”来形容都好不夸张。

与三月七的约定

@灵音 老师邀请我和她一起逛展,她作为我的主要拍摄对象。逛展期间发生了令人尴尬的事,也有开心的事。我第一次找 Coser 老师单独拍照被拒了,但是品尝到了 @灵音 老师的手工自制小蛋糕,非常好吃。这次闪灯也比之前稍微熟练了一些(但也有过曝的照片,但 RAW 拉回来还是绰绰有余),漫展结束后跟着她认识的摄影一起在场外拍外景,多好的学习机会。拍摄结束后一起在富华里搓了一顿饭,认识了 @柠檬茶、@阿卓、@小安 几位很厉害的摄影老师,以及出小樱的 @卿安 老师。

三月七,Coser 灵音

12 月初在澳门有第一届 IMAC 展,这个展邀请了很多大咖嘉宾,其中有我认识的 @谢莹 老师,她是「刻晴」和「嘉明」的配音演员。这不冲都不行啊!这也是第一次排签售,我不是 SVIP,人看着不算多但还是排了快一个小时。这次展拍的照片蛮多的,我当时并没有来得及写笔记详细记录,就在这里简单介绍一下。

珊瑚宫心海,Coser 小予

这次展遇到了 Coser @理如 和 @阿卓 摄影老师,他买了 VIP 提前摆好了灯阵,我借他的灯阵也跟着拍了一些 Coser 老师,其中认识了出星期日的 @善信 老师。我们那天晚上漫展结束之后还去吃了顿烧烤。

说起来当初加这个 @理如 老师的过程也是很好笑的,我“视奸”了她空间很多次,她居然来主动加我。上次在中山影视城漫展拍炸了的那个 Coser 老师其实是她,好在这次借了 @阿卓 摄影老师给她拍了一张效果还行。

1 月买了引闪器,首次在春节前的中山 AS 展使用,借了 @杰哥 摄影老师的灯阵来玩了一下,不过效果比较平,不太能看出来是用了几盏灯的样子… 还需要继续努力~

旅游

今年去了最多次的城市是深圳,七月份跟着亲戚的车去 Citywalk 了一次,逛了市区一些大商场,和同学 @BB 见了面,吃了一顿日式汉堡肉。十月份去了沙头角、大梅沙和深圳人才公园。

十一月份我自己坐大巴去了两趟,只是为了修我的 MacBook Air,当然和 @MJ 去了深圳的二次元胜地逛玩,还是不亏的。十二月年末也去了一趟,和 @MJ、@凯文 两人一起吃饭聊天和闲逛,途中还体验了深圳刚开通的地铁 13 号线。

深圳次元之旅 / 拜访苹果官方直营店

又去一趟深圳

深圳苹果直营店

修电脑

爱莉

腾讯

这几次在深圳的经历给我的最大感受就是吃的品种特别丰富(但大商场里面价格也蛮贵的),或许是因为大城市总人口多,外来人口也比较多。

其次是澳门,办理了新版的卡式港澳通行证之后不去转转就没意思了。五月是时隔多年(2009)后第一次去澳门玩,和爸妈一起过去,主要在澳门半岛转悠。去了大三巴、大炮台、金莲花广场等著名景点,品尝了小吃街的牛杂,也进了赌场参观表演。

九月第二次去,主要是氹仔那边的酒店为主,首先去了威尼斯人酒店闲逛,去了官也街品尝美食,去伦敦人、巴黎人等酒店拍照打卡,去美高梅蹭了杯招牌奶茶。最后在澳门教科文中心参观画展,见证了我大舅的书画作品被澳门基金会收藏的过程。

其他城市就只有 1-2 次。二月春节期间去了广州番禺的一些小众景点拍照打卡,当天返程还去了中山华侨公园和紫马岭公园,这里的一些现代建筑非常出片。八月份去了香港 Citywalk,主要在九龙和湾仔区,去体验了富士相机和索尼的镜头,在星光大道和明星的手印合影,和 @凯文 见了面并一起吃饭聊天。

这里图一直没弄,先鸽着吧==

数码设备

JBL Flip 6

2 月,我打算购置一个新的蓝牙音箱,一是提高平时听歌的音质,二是方便携带,这款音箱的性价比我认为还不错。和我表哥的 Bose 音箱一起听几乎没什么差异,而他那款比我的更贵。

红米 4K 27 寸显示器

3 月,我原先使用长达 7 年的戴尔,开机时出现了闪烁的横线,看上去是面板出现了问题。再三考虑下还是决定买个 4K 高清的,而不是高刷玩游戏的。看了评测视频后,认为红米的这一款性价比不错,甚至比它自家小米品牌的还要好…

这台显示器配合 MacBook 的体验确实不错,在 Windows 下玩游戏,原先 1080P 的画面必须拉到 2K 才能显示成此前小窗的尺寸,显卡的功耗也上去了,不过我现在玩游戏的时间其实也没有之前那么多了…

索尼 A6700

8 月,我购入了人生第一台相机,索尼 A6700,是在我能承受价格范围之内最好的机型。虽说 A7C2 会更好,但是镜头方面肯定是要比半画幅更贵一些,它裸机的价格就能买到 A6700 带 18135 套头了。

别人都在说,这个价格你为什么不买全画幅。这里还要考虑到我当时的使用场景,因为拿到相机几天之后我就带去漫展拍照学习使用了,当时也是急急忙忙的做了选择。

不过现在看来,我的确可以再花更长的时间先买一台 A7C2,再认真挑选镜头。不过预算也会增加 1-3K 吧。机器开箱之后就掉价了,现在再卖掉并不划算。

A6700 实际上也很不错,并没有让我失望。起初拍的不好其实是补光问题,带上闪光灯效果马上就不一样了。相较于部分老相机来说,这台新机器对焦迅速的优势也显现了出来,它更容易拍到正常对焦的照片。并且还能直接录制 120 帧的 4K 视频,后期还可以考虑买个云台拍点小视频了。

既然相机都买了,大光圈镜头总要配一个吧,闪光灯也得来一个吧,蹭别人的灯总要个引闪器吧,要是自己单打独斗是不是也要有个柔光箱?摄影,就是用光的艺术!把钱花光的艺术!

MacBook Air 送修

11 月末,我去年购入的 MacBook Air 居然也送修了!在我手上的 MacBook 用一台就坏一台?不过还在质保期内,免费维修,就是苦了我去了两趟深圳,不过还好期间和 @MJ 去了深圳一些不错的地方,也算是 City Walk 了一波。

iPad Air 6

12 月初,我再三考虑下还是选择购入了 iPad Air 6,M2 芯片 256G 的版本,相较于新出的 Mini 7,我还是觉得它的性能会更耐用一些。它的尺寸 10 寸确实更大没那么便携,实际用多了也接受了,回看 Mini 怎么这么小!

这个尺寸拍完照片立马就能打开看效果,甚至可以直接修图,卷死其他摄影(不是)。不过 Creator App 并不支持通过软件删除照片,这点还是有些不太方便。(结果实际几次拍摄都没现场修过)

番剧

今年主要看了这几部番剧,《间谍过家家》、《甘城光辉游乐园》和《亚托莉》,都是日常系。

《间谍过家家》就不用多说了吧,其实是因为之前只看了 1-2 集后鸽了,今年直接从第一季补番到第二季了。还去电影院看了剧场版,这个剧场版的剧情可以说是把“浮夸”这个词发挥到极致了,非常建议一看!

《甘城光辉游乐园》是游乐园经营主题的一个番,我曾经是非常喜欢玩《过山车大亨:3》的玩家,因此对这个题材的作品有着不错的好奇心。

《亚托莉》其实是因为喜欢她这个可爱的角色形象,GalGame 没花精力推看一下这个我觉得也不错,虽说看评论说这部番有改写过一部分情节。

截至写下这行文字的这一天,我还看了一集《魔女之旅》,我觉得接下来应该再补一下《孤独摇滚》、《莉可丽丝》和《葬送的芙丽莲》,都是此前很热门的番剧。

情感

自去年主动勾搭过一次女生之后,就再也没有第二次了。一方面我认为找群友做军师的过程过于超纲了,拿捏不住他给我设定的身份。再一个是在父母与其他人无形对自己施加了压力的情况下,我很难摆脱“一定要让她成为我女朋友”的这种观念(即便自己或许只是想做个朋友而已吧)。这件事让我 Emo 了一晚彻夜无眠,令我至今历历在目。

今年家里人并没有安排和介绍过任何人,有相亲活动我也没参加过。

但我还是决定以朋友相处的方式多和女生交流互动,比较松弛有度。可眼看自己年纪也是越来越大了,还有多少时间让我有试错的机会呢?假如真的有人对自己感兴趣了,又该如何识别出她的小心思呢,她是否对其他人也这样呢,这对我来说确实是一个很伤脑筋的问题啊。

总结

在这一年,我在漫展上结交了很多新朋友,总算是在前几年的短板上首次得到了突破,或许能让我没那么社恐了。通过摄影也极大的增加了我想出门走的意愿,但以此同时也减少了当宅男玩游戏、看番剧和写代码的时间。

年末公司开始 996 加班后,发扬爱好的时间进一步被缩短,编码能力的提升更多只能通过公司项目来实现,我应该尽可能的抓住一些机会。个人网站方面还需要更进一步的思考如何保证质量的同时,借助编程和 AI 的力量尽可能的提高维护效率(例如照片的处理等等)。作息方面依旧没有得到改善,除了保证充足的睡眠外,还需要提高一些时间使用效率。

自问自答

玩摄影并不一定能赚钱,学来有啥意义?

摄影本身确实没有什么意义,意义都是自己赋予的不是么。记录当下,在未来能够重新回忆起来,就是摄影本身的意义吧。况且学会摄影也可以给未来的对象拍呀,怎么想都是好处大于坏处吧。游戏帐号什么的不一定自己能持续留住,但是写文,摄影,做项目的成绩绝对是持续积累的。

给对象拍?那你找到对象了吗?有进展么?

依旧没有,凭实力单身了,在漫展上也不是所有摄影都有女朋友啊(更何况还有一些是先有对象才学摄影的不是么),我目前还比较享受摄影过程的,相较于期待找个对象,不如先期待来一个固模吧...

现在在摄影和编程技术方面,你要如何权衡?

现在 996 工作时间比较长,只能在工作方面多花心思看看能不能提升点啥了,AI 其实也是个很好的自学工具,前提是你能很好的调教它。长期在电脑面前工作也比较容易导致职业病(久坐、长时间看屏幕等),有时间休息还是尽量出门走走,让眼睛也好好休息下,这点时间就尽量安排给摄影活动了,无论是拍风光还是人像我觉得都好。

立下 Flag

去年和前年并没有立下任何 Flag,主要是总结的拖延非常严重,已经没有写的必要了好吧。现在回过头来发现就算写了似乎都挺难完成的,好在今年看上去似乎有了那么一丁点转机,看看能不能努力一下吧。

[] 学习一个后端框架,能挺好快速出活的那种
[] 继续学习提升摄影技术,按需购买新设备
[] 看下能否将主页相册遗漏的照片都补全了
[] 争取找到第一任女朋友?

往期

朋友们的总结

@Innei:2024 · 前路未尽,初心犹在

这一年,从乌镇跨年开始,经历了公司团建去大理的愉快旅程,但之后突如其来的裁员让人陷入抑郁,最终加入了 RSS3,参与了 Follow 项目的开发,体验到了开源工作的成就感。
在游戏方面,通关了《黑神话:悟空》和《最后生还者》,感受颇深。尽管只去了南京旅行,但年底买了特斯拉 Model 3,技术上写了多篇技术文章,对技术的理解更深入。虽然对未来抱有悲观态度,但仍希望明年会更好。

@林陌青川:野花做了一场玫瑰梦

这是一篇深情的 2024 年终回忆录。博主林陌青川记录了与“汪小姐”从网络技术群相识,到各自在互联网大厂(携程、字节、B站)实习奋斗,最终在上海奔现的故事。
文章回顾了两人从错过的遗憾到低谷期的相互陪伴,以及见面时同游静安寺与外滩的心动瞬间。尽管博主自比“野花”仰望优秀的她,感叹现实差距,但他仍视这段缘分为人生的“上上签”,以此文致敬这段美好的相遇。
🔲 ☆

将 MO3 音乐导出成 WAV/MP3/OGG 等格式

近期开始回忆杀状态,在 Steam 上购买并游玩了一些以前玩过的经典小游戏,其中包括来自 PopCap 宝开出品的《吞食鱼:2》。

可能人老了吧,新的游戏都玩不动了,要么烧脑要么太肝,还是这种休闲小游戏玩法简单又上头!

当时这部作品也有人将它翻译成《大鱼吃小鱼》,其中“吞食鱼”这个说法来源自《幻想游戏》系列。它也推出过很多“不同版本”的续集(其实就是一个整合包),汇聚了很多来自不同厂商的休闲游戏。

吞食鱼 2.webp

游戏玩到了,BGM 也在反复洗脑了,那么如何将游戏里面的音乐提取出来继续听呢。了解博主过去黑历史的朋友应该知道,当时老喜欢用各种方式去提取各种游戏的资源素材了,其中《摩尔庄园》、《疯狂农场》等游戏都是我的常顾对象,收集各种游戏的 BGM 并且偶尔拿出来播放算是我的一个小众爱好。

我找到了当年的一些素材提取工具(文件的修改日期为 2015 年,实际可能更早),其中有一款工具叫做 XMPlay,它可以用于 MO3 音乐的播放和提取。

MO3 是一种音频文件格式,主要用于模块化音乐,类似于其他 MOD 格式(如 IT、XM、S3M 等)。它由 Ian Luck 为 BASSMOD 引擎开发,MO3 文件的一个显著特点是它使用 MP3 或 Ogg Vorbis 格式来编码音频样本,这使得文件体积相对较小,同时能够保持较高的音质

参考:MO3 是什么格式,为什么现在几乎没人使用了

对于模块化音乐,我的理解就是它将每一个音乐节拍的声音都作为一个片段存储,播放过程则是将所有片段循环复用,有点类似于 MID 音频格式,但是 MID 格式外放出来的音色受操作系统、播放软件等的影响较大

时隔已久,我甚至都忘记了当年是怎么使用它提取音乐了,一番研究后总算是找到了办法。

顺带一提,我 Google 之后得到的结果往往都是要你付费购买某些软件,几乎没什么中文文章提到过这些东西,看来确实有些小众啊,那么我就水一篇文章简单记录一下

首先从 un4seen 下载并打开 XMPlay 播放器,这里我使用的旧版本,界面稍微比较复古。

XMPlay 播放器界面.webp

正常打开一首 MO3 音乐播放(吞食鱼游戏目录下有个 music 文件夹里面就是),此时从播放器主界面右键 -> Options -> Output -> Output Device 选择 "WAV Writer" -> Apply

XMPlay 播放器导出界面.webp

此时点击音乐播放按钮,就会弹出对应的另存为界面,依次确认导出,所有的音乐都会导出成功。

如果导出的音乐存在部分内容丢失或中断的情况,可能是因为你在播放器已经开始播放了,点击“停止播放”按钮再试一次即可

目前博主已知使用 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”了,这样的架构,代码也不太可能能写得多好了。

所以回到最前面的话题,写代码正常发挥即可。至于可替代性什么的,只要你的能力不差,去哪里都会发光发亮不是么。

🔲 ☆

排查了一个导致页面白屏的问题

我在公司维护的 Felo Search 项目近期收到了大量投诉,部分页面会出现白屏现象。我负责去重点排查,有位同事用自己的手机测试后,发现在 iOS 16 系统下会稳定出现,这大概率就是 JS 执行出错导致了。我的分析过程大致如下:

首先在 Mac 上安装 iOS 16 较低版本的模拟器(我选择安装了 15.2,因为有用户反馈使用 15 系统),通过 Mac 上的 Safari 调试在 iOS 模拟器内的 Safari,并通过控制台定位错误。

SyntaxError: Invalid regular expression: invalid group specifier name

错误信息表明存在一个不受支持的正则表达式规则(我想起来之前写过一篇文章,也是正则的问题),但由于 JS 代码被压缩无法定位到具体行数,我将该文件复制出来并格式化,通过另外一个网站去加载该存在异常的 JS 文件。

初步定位到具体的出错代码属于外部依赖库,只能通过检查近期依赖项的变动进一步确认,大概率是某个依赖升级后导致。检查 Git 提交记录发现 package.json 文件并没有明显改动,只好继续检查 pnpm-lock.yaml 文件,发现存在锁文件版本被降级的情况(pnpm 9x 版本,后续有人用了低于 9x 的版本安装了依赖,就会导致依赖锁被破坏)

最终发现有一个叫 mdast-util-gfm-autolink-literal 的库被升级了,比较此前发布的版本后发现从 2.0.0 升级到了 2.0.1,通过 why 命令可查出为什么它被安装。

pnpm why mdast-util-gfm-autolink-literal

dependencies:
@tryfabric/martian 1.2.4
└─┬ remark-gfm 1.0.0
  └─┬ mdast-util-gfm 0.1.2
    └── mdast-util-gfm-autolink-literal 0.1.3
remark-gfm 4.0.0
└─┬ mdast-util-gfm 3.0.0
  └── mdast-util-gfm-autolink-literal 2.0.1

我去找了下该库关于 2.0.1 版本的一些改动和发布信息,他的确使用到了一个不受支持的正则表达式规则,还有人对此提了 Issues,结果作者明确拒绝向下兼容,坚持要用。很明显这对于一个商业化项目来说是绝对不允许的,相当于是直接就砍掉这大半用户了...

这个正则表达式的规则叫“反向断言”,我早在 2021 年就写过文章 《JS 正则使用反向断言及踩坑》,结果没想到它的兼容性居然在今天都还能差的这么离谱。

我最终的解决方案就是对比旧版本的 pnpm-lock.yaml 内容,直接修改了对应的版本号和签名,亲测可用。

☑️ ☆

从零开始部署 Ubuntu 服务器环境

前段时间我购买了几年的 CloudCone 特价机炸了,导致服务中断快一个星期,再加上没有定期做备份,服务无法快速恢复。我开始寻找其他服务商的机器,兜兜转转最终选择了搬瓦工的,因为看它比较老牌,口碑还不错,且国内直连的线路良好,在此发出一个 我的邀请码,你可以通过这个链接购买服务器,为博主提供微薄返利以资助网站的持续运营。

另外小扯一句,你可能会注意到我博客的更新频率有所下降。但其实是因为我以此同时还在维护另外一个名为 保罗的小窝 的网站。个人对于博客的定义是稍微正式的内容,而这个小窝更多的是分享随笔和流水账,各位可以自行选择订阅。

此前的方案

此前一直都在用 OneInstack 脚本来部署环境,尝试用它重新部署,却发现 MariaDB 无法正常安装。国内国外访问同一个包的下载地址居然行为是不一样的(国外出现 404)。我选择构建安装,时间太长太复杂,且选择 Caddy 作为 Web 服务器后,居然虚拟主机都没有创建成功,我怀疑脚本是不是根本就没做好 Caddy 的适配...

OneInstack 此前也爆过供应链挂马的问题,据说是被国内公司收购了之后出现,虽然作者承诺修复,但我仍对其安全性存在质疑。再三抉择后,只能忍痛放弃 OneInstack,打算从零开始学习部署属于自己的 Ubuntu 服务器环境了!

你需要一定的 Linux 使用基础才能更好的阅读本文,实际安装过程可能有些许曲折,本文对部分流程做了次序优化,可能存在欠缺需要自行分析和解决(例如某些软件包需要自行安装),虽然有一部分在编写过程中在虚拟机上重新执行确认过,但还是以实际操作为准吧!

环境配套

选择自己纯手工配置环境最主要的原因就是 PHP,它除了一个 FPM 服务以外,还需要一个 Web 服务器才能将项目跑起来,它并不能像 NodeJS 那样自己就是一个 HTTP 服务。再加上数据库服务放在相对实际的环境下可以更好的编写脚本实现一键备份等功能。使用 K8S / Docker 部署应用会更方便,但受限于成本、服务器数量和硬件配置等因素,自己纯手工配置环境依旧是首选方案。

  • Caddy
  • MariaDB
  • Redis
  • PHP 8.3

    • phpMyAdmin
  • NodeJS

    • FNM(Fast Node Manager)
    • PM2

在使用 Caddy 之前我都是选择 Nginx 作为 Web 服务器,Nginx 技术成熟但配置起来没有 Caddy 那么容易,Caddy 作为后起之秀其评价也还不错,它还自带 SSL 证书签发功能,而且不会出现因 HTTPS 证书导致的 IP 泄漏问题。此前也使用过它 部署环境 有些许经验,这次直接使用它作为生产环境我认为也是没有问题的!

MariaDB 是开源版本的 MySQL,此前保罗一直在用,因此就没有考虑 MySQL。

PHP 版本此前都是 7x,最新版本都是 8x,如果你要使用 PHP 的包管理器 Composer 管理项目,那么直接用 PHP 8 是最省时的选择(如果要用 7x,你还需要旧版本的 Composer,太麻烦了,我一个新手根本不会弄,还不如升级自己的代码)

phpMyAdmin 是一款运行在 PHP 下的老牌数据库管理软件,我们首先安装它也能确认 PHP 是否能正常与数据库进行连接。

NodeJS 也是我现在使用 Nuxt (Vue) 和 Remix (React) 框架构建应用所必备的,这里我们使用 FNM 安装,并配置 PM2 用于持续化启动网站。

实用软件

在开始之前我也推荐安装一些实用软件,你可以根据自己的需求选择安装。

安装 htop 以实时查看服务器配置:

sudo apt install vim htop

sudo add-apt-repository ppa:zhangsongcui3371/fastfetch
sudo apt install fastfetch

安装 net-tools 以查看网络相关信息

sudo apt install net-tools

安装 trash-cli,防止文件直接删除,无法被恢复:

sudo apt install trash-cli
vim ~/.bash_aliases

alias rm='trash-put'

安装 ufw 以控制服务器的入站出站流量,选择性打开服务端口,防止被外部 IP 扫描减少安全风险。如果你的云服务商支持在后台自定义设置防火墙规则(例如阿里云),那么可以选择不安装。

sudo apt update
sudo apt install ufw

# 设置默认策略为拒绝所有传入连接
sudo ufw default deny incoming

# 设置默认允许所有传出连接
sudo ufw default allow outgoing

# 允许 TCP 端口 6755
sudo ufw allow 6755/tcp

# 检查状态
sudo ufw status verbose

# 启用 UFW
sudo ufw enable

安装 openssh-server 以允许远程操控服务器。如果是服务商提供的 Ubuntu,可能已经默认安装,直接跳过。

sudo apt install openssh-server

修改 SSHD 配置

编辑配置文件,修改端口,禁用密码访问,改为使用公私钥形式访问服务器。

sudo vim /etc/ssh/sshd_config

Port 5678
PubkeyAuthentication yes
PasswordAuthentication no

编辑文件 authorized_keys 添加私钥,以后将会用主机的公钥与服务器中的私钥配对连接。如果没有密钥对可以使用 ssh-keygen 生成一个,这里建议使用 ssh-keygen -t ed25519 指定算法生成(因为 Mac 那边新版本已经不支持默认的 RSA 了)

vim ~/.ssh/authorized_keys

重启服务器的 sshd 服务

sudo systemctl restart sshd

(可选)配置主机的 config 文件,指定使用证书命中到服务器。

Host MyServer
    Port 5678
    User root
    Hostname 10.7.9.103
    PreferredAuthentications publickey
    IdentityFile ~/.ssh/myserver_ed25519

安装 Caddy

参考 官网说明 安装即可。

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy

Caddy 已经完成安装并自动启动。如果你选择安装了 ufw 防火墙,允许外部访问 80 443 端口:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

新建站点目录

mkdir /var/www
mkdir /var/www/html

此时我们还不需要配置任何网站,你可以使用 wget 或在浏览器上访问服务器对应的 IP,确认 Caddy 已经正常启动。

安装 MariaDB

sudo apt install mariadb-server
sudo mysql_secure_installation

注意这里 Enter current password for root 第一次要求输入密码直接跳过,到后续第二次询问 Change the root password? 再设置。

至于它为什么会问两次密码,我也不是很清楚。我尝试过只在第一次要求的时候输入,但在第二次询问时跳过,这样做会导致最后什么密码都无法登录...

NOTE: RUNNING ALL PARTS OF THIS SCRIPT IS RECOMMENDED FOR ALL MariaDB
      SERVERS IN PRODUCTION USE!  PLEASE READ EACH STEP CAREFULLY!

In order to log into MariaDB to secure it, we'll need the current
password for the root user. If you've just installed MariaDB, and
haven't set the root password yet, you should just press enter here.

Enter current password for root (enter for none): 
OK, successfully used password, moving on...

Setting the root password or using the unix_socket ensures that nobody
can log into the MariaDB root user without the proper authorisation.

You already have your root account protected, so you can safely answer 'n'.

Switch to unix_socket authentication [Y/n] n
 ... skipping.

You already have your root account protected, so you can safely answer 'n'.

Change the root password? [Y/n] n
 ... skipping.

By default, a MariaDB installation has an anonymous user, allowing anyone
to log into MariaDB without having to have a user account created for
them.  This is intended only for testing, and to make the installation
go a bit smoother.  You should remove them before moving into a
production environment.

Remove anonymous users? [Y/n] y
 ... Success!

Normally, root should only be allowed to connect from 'localhost'.  This
ensures that someone cannot guess at the root password from the network.

Disallow root login remotely? [Y/n] y
 ... Success!

By default, MariaDB comes with a database named 'test' that anyone can
access.  This is also intended only for testing, and should be removed
before moving into a production environment.

Remove test database and access to it? [Y/n] y
 - Dropping test database...
 ... Success!
 - Removing privileges on test database...
 ... Success!

Reloading the privilege tables will ensure that all changes made so far
will take effect immediately.

Reload privilege tables now? [Y/n] y
 ... Success!

Cleaning up...

All done!  If you've completed all of the above steps, your MariaDB
installation should now be secure.

Thanks for using MariaDB!

安装 Redis

输入一条命令完成安装:

sudo apt install redis-server -y

安装 PHP

在安装之前,我们还需要新增一个 Ubuntu 软件源

sudo add-apt-repository ppa:ondrej/php
sudo apt update

安装 phpcomposer 及其对应的扩展(你可以根据自己的需要来安装):

sudo apt install php8.3 php8.3-curl php8.3-fpm php8.3-mysql php8.3-redis php8.3-mbstring php8.3-xml
sudo apt install composer
⚠️ 注意:使用 apt 安装部分 PHP 扩展时可能会导致安装 apache2 依赖项,后续需要将其禁用,可能使用 apt 来安装并不是一个最佳方案

编辑 php.ini 配置文件,取消生产环境隐藏报错的设置,好让我们接下来更好的排查问题,后期可视情况还原。

vim /etc/php/8.3/fpm/php.ini

error_reporting = E_ALL & ~E_NOTICE & ~E_WARNING

display_errors = On

修改完记得重启进程

service restart php8.3-fpm

通过命令 service php8.3-fpm status 查看当前 php8.3-fpm 服务配置文件的路径。

include=/etc/php/8.3/fpm/pool.d/*.conf

可以看到引入了一个 pool 的配置文件,ls 列出当前文件夹下所有文件。

ls /etc/php/8.3/fpm/pool.d/
www.conf

使用 vim /etc/php/8.3/fpm/pool.d/www.conf 查看配置文件,即可看到 sock 的地址。

listen = /run/php/php8.3-fpm.sock

复制下来,后续我们需要将路径粘贴在 Caddy 的配置里面,后续粘贴到 Caddy 的字符串如下:

unix//run/php/php8.3-fpm.sock

新增第一个 Caddy 网站

首先创建所需的文件夹:

sudo mkdir /var/www/
sudo mkdir /var/www/html
sudo mkdir /var/www/html/default

还记得前面提到的 PHP Socks 路径嘛,这里将会用到,首先开始编辑 Caddyfile 配置文件:

vim /etc/caddy/Caddyfile

新增一条虚拟主机记录,由于访问地址是服务器 IP 自身,无法签 SSL 证书,因此直接选择自签即可。

10.7.9.103 {
        tls internal
        root * /var/www/html/default

        php_fastcgi unix//run/php/php8.3-fpm.sock
        file_server
}

重启服务,理论上没有任何问题。

service caddy restart

我们可以创建一个 index.php 文件,测试是否可以正常连接到 PHP:

vim /var/www/html/default/index.php
<?php

phpinfo();

使用浏览器访问链接 https://10.7.9.103,理论上将会显示当前 PHP 的运行信息。

安装 phpMyAdmin

官网 上下载最新版本,之后解压在 /var/www/html/default/phpMyAdmin 目录下。

官网上的版本已经内置了所需要的 Composer 依赖,我尝试过本地自行安装,但遇到了错误,原因不明,也不想花心思继续排查了,只要自己项目正常就行。

使用浏览器访问链接 https://10.7.9.103/phpMyAdmin,输入账号密码,你可能会遇到如下错误:

mysqli::real_connect(): (HY000/1698): Access denied for user 'root'@'localhost'`

此时主要检查两种情况,一个是前面密码的设置问题(可以用 mariadb 命令尝试登录),另外一个则可能是 root 账号所对应的 Host 的问题。

截至本文编写过程在虚拟机里重新执行确认时,并未出现这个错误,就挺奇怪的,但还是提供一下之前的解决方案

解决 root 账号访问问题

使用 mariadb -u root -p 命令登录,输入下面的命令,这里参考了 OneInstack 的源码。

# xxxx 是你的密码,可以和之前设置的一致
GRANT ALL PRIVILEGES ON *.* TO root@'localhost' IDENTIFIED BY "xxxx" WITH GRANT OPTION;
GRANT ALL PRIVILEGES ON *.* TO root@'127.0.0.1' IDENTIFIED BY "xxxx" WITH GRANT OPTION;
FLUSH PRIVILEGES;

再次尝试,应该就能正常登录了。

解决 PHP 文件大小限制问题

我的小窝程序需要支持上传视频,所以至少需要 20M 的文件尺寸,否则可能会遇到 $_POST$_FILES 对象为空的情况,导致程序出错。

vim /etc/php/8.3/fpm/php.ini

post_max_size = 20M
upload_max_filesize = 20M

service restart php8.3-fpm

安装 RClone 与设置备份

我注册了 CloudFlare R2 存储桶来完成服务器的备份数据存储,实测国内的银联信用卡也可以使用。可以在服务器上使用命令行工具 rclone 来将文件存储至 R2 存储桶,后期整理一个备份脚本定期执行即可。

https://developers.cloudflare.com/r2/examples/rclone/

Ensure you are running rclone v1.59 or greater (rclone downloads ↗). Versions prior to v1.59 may return HTTP 401: Unauthorized errors

CloudFlare 文档描述对 rclone 版本有要求,apt 下的似乎比较老,因此直接根据 官网教程 直接安装最新版本(截至本文编写过程,最新版本是 v1.68.0)

sudo -v ; curl https://rclone.org/install.sh | sudo bash

先在 CloudFlare 后台新建 R2 存储桶,并创建一个 API 令牌以供 rclone 程序进行连接。你将会得到以下几个数据:

  • 令牌值:env_auth
  • 访问密钥 ID:access_key_id
  • 机密访问密钥:secret_access_key
  • 为 S3 客户端使用管辖权地特定的终结点:endpoint

在编写自动化脚本之前,需要添加对应的存储桶配置,后期可直接使用对应的命令和配置上传文件。

rclone config

No remotes found, make a new one?
n) New remote
s) Set configuration password
q) Quit config
n/s/q> n

Enter name for new remote.
name> cloudflare

Option Storage.
Type of storage to configure.
Choose a number from below, or type in your own value.

4 (Amazon S3 Compliant Storage Providers including AWS, Alibaba, ArvanCloud, Ceph, ChinaMobile, Cloudflare, DigitalOcean, Dreamhost, GCS, HuaweiOBS, IBMCOS, IDrive, IONOS, LyveCloud, Leviia, Liara, Linode, Magalu, Minio, Netease, Petabox, RackCorp, Rclone, Scaleway, SeaweedFS, StackPath, Storj, Synology, TencentCOS, Wasabi, Qiniu and others) -> 6 (Cloudflare R2 Storage)

Option provider.
Choose your S3 provider.
Choose a number from below, or type in your own value.
Press Enter to leave empty.

 6 / Cloudflare R2 Storage
   \ (Cloudflare)


Option env_auth.
Get AWS credentials from runtime (environment variables or EC2/ECS meta data if no env vars).
Only applies if access_key_id and secret_access_key is blank.
Choose a number from below, or type in your own boolean value (true or false).
Press Enter for the default (false).

 1 / Enter AWS credentials in the next step.
   \ (false)
 2 / Get AWS credentials from the environment (env vars or IAM).
   \ (true)

回车继续,输入 access_key_idsecret_access_key

Option access_key_id.
AWS Access Key ID.
Leave blank for anonymous access or runtime credentials.
Enter a value. Press Enter to leave empty.
access_key_id>

Option secret_access_key.
AWS Secret Access Key (password).
Leave blank for anonymous access or runtime credentials.
Enter a value. Press Enter to leave empty.
secret_access_key>

Option region.
Region to connect to.
Choose a number from below, or type in your own value.
Press Enter to leave empty.
 1 / R2 buckets are automatically distributed across Cloudflare's data centers for low latency.
   \ (auto)
region> 1

Option endpoint.
Endpoint for S3 API.
Required when using an S3 clone.
Enter a value. Press Enter to leave empty.
endpoint>

Edit advanced config?
y) Yes
n) No (default)
y/n> n

Configuration complete.
Options:
- type: s3
- provider: Cloudflare
- access_key_id:
- secret_access_key:
- region: auto
- endpoint:
Keep this "cloudflare" remote?
y) Yes this is OK (default)
e) Edit this remote
d) Delete this remote
y/e/d> y

配置完成后,可使用命令列出存储桶的数据和上传文件,确保连接正常。

rclone tree cloudflare:存储桶名称

理论上能够正常列出存储桶下的内容,如果不能正常显示,可以从下面几个方向排查:

  • 存储桶名称不对
  • 代理设置问题
  • API Tokens 的权限设置问题(至少给到 Object Read & Write 权限)
  • API Tokens 的存储桶访问限制
  • API Tokens 的 IP 地址限制

打包生成备份的过程在这里不作详细介绍,大致就是使用 mysqldump 导出数据库,之后使用 tar 打包压缩站点数据,最后使用 rclone copy 命令将所有文件上传,定期手动或自动化执行都是可以的。

cd ~/backup/2024-10-21/
mysqldump -u root -p home > home.sql
rclone copy . cloudflare:存储桶名称/2024-10-21/

安装 NodeJS

官网 上选择对应的环境,即可生成一键安装脚本,我选择了当前最新的 LTS 版本和 fnm,你可根据自己的需要安装对应的版本。

# installs fnm (Fast Node Manager)
curl -fsSL https://fnm.vercel.app/install | bash

# activate fnm
source ~/.bashrc

# download and install Node.js
fnm use --install-if-missing 20

# verifies the right Node.js version is in the environment
node -v # should print `v20.17.0`

# verifies the right npm version is in the environment
npm -v # should print `10.8.2`

接着来安装 PM2,这是项目持久化运行在服务器上所必备的。

pm2 startup
pm2 start ./ecosystem.config.cjs(应用程序的示例,以实际为准)
pm2 save

这样服务器重启后,也能自动启动 pm2 服务以及对应的应用程序。

新建一个 NodeJS 虚拟主机

配置一个 NodeJS 项目到 Caddy 非常简单,基本上就只是一个端口转发规则而已。

paul.ren {
        reverse_proxy localhost:3001

        # 添加以下配置以处理 /upload 路径
        handle /upload/* {
                root * /var/www/html/legacy.paul.ren/public
                file_server
        }

        handle /static/* {
                root * /var/www/html/legacy.paul.ren/public
                file_server
        }
}

其他

可以编辑 ~/.bash_aliases 文件自定义自己的快捷命令,快速定位到 Caddyfile 配置等。

最后

感谢 @提莫 同学对本文中一些内容的指正,由于本人的运维经验不足,虽然已经花了不少时间重试其中的某些步骤,但仍然可能有所不足和缺漏,有什么问题可以在下方留言,我会尽力为大家解答。

🔲 ⭐

使用 Docker 自动化部署的 NextJS 镜像大小优化

近期要把公司的新官网项目给收尾了,准备打包部署发布到线上环境,我们主要采用的 CircleCI 和 K8S 负责 CICD,就是期间经常会遇到 K8S 的超时错误导致构建失败。

虽然不清楚具体的错误原因,但我发现构建过程中 Dockerfile 生产出来的镜像文件实在是太大了,达到了惊人的 1G 多,想着这样传输镜像的时间肯定会慢,是否因此导致构建失败的概率提升呢?前文详见日记《继续准备 NextJS 新官网项目(二)

这篇文章我们将以官方的配置文件作为基础二次修改,将应用的构建过程放在当前系统环境来完成,最后将产物打包成 Docker 镜像,以实现大小优化。

修改配置

想着之前自己部署 NuxtJS 的时候发现它在生产环境下最终运行的是一个 server.mjs 文件,这意味着我或许并不需要安装一大堆 node_modules 依赖,然后再执行 pnpm build && pnpm start 的方式来启动服务。这些最终构建好的代码,小到不足 50MB。

那么 NextJS 可以吗,简单搜索看了下,它是可以做到的。我是从它们官方提供的 Dockerfile 里面找到的这个设置项 output,比较隐蔽。

Next.js can automatically create a standalone folder that copies only the necessary files for a production deployment including select files in node_modules.

To leverage this automatic copying you can enable it in your next.config.js:

module.exports = {
  output: "standalone",
}

修改成这种模式后,意味着项目生产环境的启动方式不再是 pnpm start 了,继续这样操作的时候 NextJS 的命令行工具也会对此进行提示。

"next start" does not work with "output: standalone" configuration. Use "node .next/standalone/server.js" instead.

旧版 Dockerfile

那么在此之前我是怎么做的呢,这是项目之前的 Dockerfile,可以看到构建、运行应用的过程均在里面完成(并非阶段构建),也因此导致最后的镜像略大。

FROM node:20.15-alpine AS runner

# 定义一个名为 ENV 的参数,默认值为 dev
ARG BUILD_ENV=prod

# Create app directory
WORKDIR /app

RUN addgroup --system --gid 941 nodejs
RUN adduser --system --uid 941 nextjs

COPY . ./

WORKDIR ./

# 如果 BUILD_ENV 为 dev,则复制 .env.dev 到 .env.local
RUN if [ "$BUILD_ENV" = "dev" ]; then cp .env.dev .env.local; fi

RUN chmod 0777 .

RUN npx --yes pnpm install

RUN npx pnpm build

USER nextjs

EXPOSE 3000

ENV PORT 3000

CMD ["npm", "start"]

调整后的 Dockerfile

考虑到我司已经在使用 CircleCI 负责构建应用,K8S 只负责打包并运行构建结果即可,我根据官方的 Dockerfile 最终整理出了一份自己的,供各位参考:

FROM node:20.15-alpine AS runner

ENV NODE_ENV production

# Create app directory
WORKDIR /app

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY public /app/public

# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --chown=nextjs:nodejs .next/standalone ./
COPY --chown=nextjs:nodejs .next/static .next/static

USER nextjs

EXPOSE 3000
ENV PORT 3000

CMD ["ls", "-l"]

# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
CMD HOSTNAME="0.0.0.0" node server.js

这份 Dockerfile 相较于前面的版本,他多出了一个复制 static(位于项目内 .next/static) 和 public(位于项目内 /public)文件的步骤,据官方描述说是这些文件应由 CDN 处理,但实际情况我们用的 CDN 属于融合 CDN(不知道是不是这么说,类似 CloudFlare 那种自动缓存和回源的),因此不需要额外处理单独托管的静态文件。

因为没有在 Dockerfile 里面安装依赖和构建应用了,因此需要在当前的系统环境下,已经通过 pnpm build 完成 NextJS 的构建过程。

使用 Jenkins 或其他方式

我自己的服务器并没有强大的资源和性能,只有一个机器跑多个服务的使用场景。如果改用传统 Jenkins + SSH + PM2 的部署方式,也是一样轻松了不少,以往需要在运行机器上执行极其缓慢的 pnpm build 也将提前在 Jenkins 机器上完成。通过 SCP 的方式传输构建产物,到运行机器上只需替换掉对应的资源,重启 PM2 就能完成,这里就不再具体提供实现过程了,有需要建议自行尝试摸索。

🔲 ☆

Caddy 简单配置允许跨域的反向代理

这 NextJS 可真是把我给恶心 🤢 到了,项目里使用 next-international 这个库配置了站点多语言,按照其文档中的 配置说明,需要修改 middleware 中间件的配置。

而项目当中遇到了跨域的接口请求,不知道什么原因后端配置不生效。于是我打算增加 next.config.js 文件中编写的 rewrites 规则。结果我发现一旦使用了 NextJS 的中间件,这些 rewrites 配置居然通通全部直接无视了 🤡🤡🤡

const nextConfig = {
  async rewrites() {
    return [
      {
        source: '/paul/:slug*/',
        destination: 'https://paul.ren/:slug*/',
      }
    ]
  }
};

// 此时访问 项目链接/paul 显示 404

因为注释掉多语言需要加入中间件的代码之后,rewrites 中的规则重新生效了...

也不确定这是不是 Bug,但这种巨型框架层面的 Bug 可不是我一个小彩笔能解决的,Bug 解决不了,但需求仍然要继续做的。我想到直接使用 Caddy 来帮我做这件事,反向代理某一个业务的接口之后强行设置 CORS 的 Header 头,允许任意的跨域请求,随意使用。

使用 Docker

通过 Docker 使用 Caddy 是最简单的方法,且不会干预到实体系统环境,这里我使用了 Docker Desktop 作为演示。点击顶部搜索栏搜索 caddy,下载最新版本的镜像,点击 Run 按钮使用它创建启动容器。

在设置里填入对应的端口号,这里我只设置 80 端口的映射,确保端口号没有被占用就行。

Docker.jpg

修改配置文件

设置完成后容器将会自动启动并且持续运行,使用浏览器访问对应映射好的端口号(我这是 7888),如果能正常访问则服务正常运行,就可以继续设置了。此时点开容器面板选择 Exec Tab 进入该容器的终端界面,输入以下命令进入配置文件的编辑界面:

vi /etc/caddy/Caddyfile

修改配置文件的内容,具体如下:

:80 {
  reverse_proxy https://paul.ren {
    header_up Host {upstream_hostport}
    header_up X-Real-IP {remote}
    header_up X-Forwarded-For {remote}
    header_up X-Forwarded-Port {server_port}
    header_up X-Forwarded-Proto {scheme}
  }

  @cors_preflight {
    method OPTIONS
    header Origin *
  }

  handle @cors_preflight {
    respond 204
    header Access-Control-Allow-Origin "*"
    header Access-Control-Allow-Methods "GET, POST, OPTIONS"
    header Access-Control-Allow-Headers "*"
  }

  header {
    Access-Control-Allow-Origin *
    Access-Control-Allow-Methods "GET, POST, OPTIONS"
  }
}

保存后重启容器,使用浏览器重新访问对应链接,应该会直接显示对应「被反向代理」网站的内容,说明反向代理配置成功。

之后修改 NextJS 项目那边的环境变量,使接口请求经过我们配置好的 Caddy 代理,如无意外则一切正常使用,这就变相解决了 NextJS 内置的反向代理存在 Bug 的问题。

☑️ ☆

关于胖猫事件的一些个人看法

蓝底白字的 官方通告 已经出了,结果也是在预料之内,看完这篇懂得都懂,这就是语言的艺术。官方的主要出发点是为了平息事件对于公众的影响,但即便如此我相信大多数人的看法都是明智的。

前有假结婚后分财产,后有谈恋爱“同居”大额生活费不算诈骗,版本也在不断快速迭代,但总体而言法律依旧是在偏向女性的,这也就是为什么会出现这么多令人讨厌的 T0 “小仙女”了吧。

关于这份通告不同人也有不同的解读,一方面认为这是“真反转”,认为女方不是诈骗,反倒是胖猫姐姐涉嫌夸大诬陷女方。另一方面则是和我想的一样,女方确实存在诈骗行为了。

我认为从胖猫角度上来说,他可能认为钱给够了女方就会喜欢他。(可能太恋爱脑了,真以为自己满足对方的要求就能得到她了)从女方角度上来说,如果根本就不喜欢他,她应该及时拒绝,而不是让男方一昧的“爆金币”。(都没长期共同居住过都敢要这么多钱了,难道官方鼓励在恋爱中大手大脚的花费吗,显然不可能啊)这就涉及到个人的道德底线问题了。

针对这份通告也是有个知乎回答特别有意思,说是给真正的捞女提供了思路:

  1. 男方转给女方的钱要适当返还,这样可以被警方认定是正常男女恋爱关系
  2. 女方要让男方用恋爱记、小荷包等 App 功能共同存钱,并且坚定表示是奔着结婚去的,这样即使钱到手了之后立马分手,警方无法认定自己诈骗
  3. 要带男方去见见自己的亲友,介绍给家属,之后再以感情不和为理由分手即可

想起来之前也有个段子,说唯一合法的方式就是和别人的老婆在一起,既不会分财产,也不会让男方承担较大风险(虽然还有强奸罪,但最起码财产保住了)。

看完上述例子之后感觉不无道理,只是这样做也确实不道德。

博主的呼吁

我朋友说“古代男尊女卑 是有他的道理的”,虽然说着有点难听,但前人之鉴的确不能忽视,现实情况的确有不少东西来来去去都是一次轮回罢了。不然你看苹果的设计,以及前端框架的 SSR,本就是一场轮回,玩的都是前面玩剩下的。

女人狠起来连自己人都打,法律偏袒女性的本意是保护女性,但众多事件的发生已经足以证明它已经渐渐变为了某些人的特权,作为广大男同胞的一员,只能对此唏嘘不已。法律的本意是保护,而不是滥用,是为了让你真正遇到危险时能发挥出最大的作用。

T0 “小仙女”们的恶心行为,应该是被众人所鄙夷的,而不是被她们的“逐渐深入”渐渐变成“官方正确”。

在这里我只能呼吁身边的女性朋友们都能够清醒起来,不要变成像她们那样的负面教材吧... 对不合适的人勇敢拒绝,而不是反复纠缠。

男生真的不是不想谈恋爱不想结婚,只是想要遇到一个合适的人确实太困难了。胖猫事件警醒了不少男同胞,告诉他们这并不是少见的个例,这种畸形的价值观已经影响到不少人了。再加上现在各种 App 大数据的推送机制,只会推送用户更喜欢的内容,进一步加大了这种毒鸡汤的扩散。只会让这些人更自以为是,认为男性单方面的付出就是理所应当的,可男生的钱也不是大风刮来的不是么。

那你还相信爱情吗

我自然还是期望能遇到真正欣赏我、喜欢我的人,尽管可能比较难,但并不代表没有可能。纯爱战士依旧存在,只是概率比较低罢了。(很荣幸自己的一位友链朋友就是这样)

只是从我的角度来看,自由恋爱确实变得越来越困难和不可信,宁愿相信自由恋爱或许还不如相信家里人介绍的,至少家庭背景是相对有保障的,并且通过他们的父母也能一定程度上可以了解他们的子女可能是怎么样的一个人,除非是那种关系并不熟络的朋友。

至于为什么我至今一直都单身,有人说这是上天在保护你,虽然有点自我安慰的感觉,但总比急急忙忙遇到一个要我爆金币的捞女要好吧... 这大环境就是被这样的人搅浑浊了,我结婚的同事都表示已经跟不上版本了。

在真正开始一段恋爱关系之前,我也在认真思考过,自己是否能付出一个合格男友必要的责任。如果不行我自然也不会随意和她们建立关系,我认为这才是对对方的尊重。宁可不爱,也别伤害。因为感情也确实需要一定的磨合,这也无疑是真正的挑战...

我曾经有一段黑历史,在这段黑历史的加持下让我有很长的一段时间惧怕异性,我或许更适合先结识异性朋友,从普通朋友做起,消除恐惧,后面才能更从容的面对真正适合自己的人。

🔲 ☆

拖沓了一年的 2022 年终总结

个人感觉 2022 年编写的日记质量都蛮高的,不如采用原先的分类形式+时间排序链接对应日记的形式,算是个小的创新?

由于相册内容堆积了将近一年的量没上传,再加上本次总结涉及到的信息量略大整理起来比较有难度,所以咕咕到了现在。2023 年的总结,估计还是不太适合继续沿用这样的模式了。

截至今天(2023 年 12 月 23 日),这篇总结依旧没有被完成,此时此刻我还得开始筹划编写 2023 年的总结了,先挂在这里后续随着图片上传再慢慢完善吧。

技术

安排了一个直播间模板

用 PS 自制了一个直播间的模版,还以此写了一个网页弹幕机(目前已经失效,暂不考虑重写了),和其他人项目不同的特点是可以使用浏览器的语音播报功能。装修的貌似是挺漂亮了,但观众却还是只有那几个人。

曲线救国配上了 CI/CD

上家是使用 Jenkins 完成项目的自动化部署流程的,只要 Git 提交代码,就会触发一个 WebHook 发送到 Jenkins 对应的链接,无疑这么做能大大节省新代码上线所需要的人力成本。

而我的服务器资源匮乏,决定采用了曲线救国的方法,将自己家的 Windows 电脑通过 Frp 转发服务到 Gitea 服务器上就实现了这个操作,这么做部署性能强劲,不可能造成其他服务卡顿,缺点就是不想一直开机(有独立显卡功耗高),不能保证随时可用。也许弄一个远程启动电源的方案可以解决这个问题。

配置 Jenkins 期间主要遇到了发送 Curl 消息时出现编码错误的问题,是 @Eric 大佬帮我更换系统集成环境解决的。

  • 日记链接

以及各种个性化的操作,但基本都是用系统环境变量解决。例如获取 Git 最新提交记录的内容,并发送给机器人

编写了一段自动化 Shell

主要还是年末操作 Jenkins 的时候误删了上传的所有文件,这种大批量的老数据最后的备份只有 2021 年的,之后产生的新数据都只能重新更新上去,这种反复枯燥的操作,只能把希望放在编写自动化脚本上了。

在春节假期期间,我成功编写了一段自动化生成文章占位图的 Shell 代码,可以将不同尺寸不同分辨率的动漫插画,统一处理成 16:10 的分辨率,并且保持原有的比例进行裁切。原理是遍历文件夹下所有图片,并采用 imgmagick 工具进行处理,最终重新编号导出到指定的文件夹下。

相册图片则还需要后期补上,估计加上 Curl 的流程就可以实现。拍摄的照片是最容易的,少数内容是手动拼接或打码的则还需要重新手工处理。

开源

今年和去年一样,并没有新增什么好看实用的开源项目,以个人学习尝试与经验分享的角度分别开设了两个文档类项目。前者记录我折腾捣鼓过程的草稿性质代码,后者分享我编写代码的一些个人实践经验。都是我入职新公司之后发起的。

其他项目维护的并不频繁,我的 Single 和 Fantasy 主题也没增加什么新的特色功能(追求稳定)

项目

个人网站 依旧是我主要的维护对象,包括但不限于以此发起的功能代码维护、日常内容编写,各种小设计等等。隔壁 @Innei 的开源项目 Mix Space 越来越有人气了,但我的程序还是挺垃圾的,考虑到自己服务器配置较低、老数据兼容等各种问题(我日记的数据最早从 2018 年就开始积累起来了),因此暂时不考虑更换后端的技术栈。

PHP 它也的确足够用了不是么,为了学技术故意上一套看似很厉害的方案,实际配置起来还更麻烦的。关于自己个人网站的维护经历,我在后期也会整理出一篇文章,欢迎持续关注本博客。

保罗的小窝

前端方面,前台有计划考虑使用 Vue 3 和 Nuxt,目前推出了一个 测试版本,重新设计了一套新的 UI,依旧是水绿色为主要颜色设计的,这个颜色和初音未来的主题色十分的相似呢。

新版小窝前台

后端方面,主要重构了项目相关内容,将 JSON 存储改为了数据库存储,这样大概可以优化程序执行的内存占用(所有的项目内容都存在了一个 JSON 文件里面,而且后台是直接编辑 JSON 文件而不是提供表单式的界面)

  • 统一 Media, Say, Note 接口传入传出都是 boolean 参数类型而不是 0 和 1
  • 重构前台 JS 代码,替换为自己的 Pjax 实现,简单实现生命周期的效果(3-14)
  • 小窝媒体视频格式支持(6-27)

【待完善】

小窝后台 Vite

偶然通过 QQ 空间看到了这个后台项目设计起初的模样,原来早在 2020 年就换成了现在的这套设计风格了呀。去年增加了换配色的功能,但感觉并不满意,考虑接下来结合 Less 实现更多样化的配色,尝试增加更多样化的底纹?

小窝后台 Vite

  • 各个页面和组件的代码整理,拆分成独立的模块化 CSS 而不是全局 CSS
  • 引入在上家项目实践编写的 useStat Hooks,替换掉原先的 useParams
  • 增加抽屉通用组件,编写日记媒体编辑器组件,使用弹窗形式创建新媒体,提交将自动绑定该日记
  • Input 组件加入 enterKeyHint,回车自动失焦
  • 语录增加长评论字段,可供筛选
  • 批量上传媒体页面,可批量编辑详情信息,按序依次上传
  • 日志页面增加详情抽屉,可查询 IP 归属地;优化日志列表页面展示
  • 接口请求函数统一独立到 service 处,结合 TS 类型判断,不在页面上编写请求函数
  • 优化按钮音乐播放器逻辑,解决播放中切歌未还原状态的问题
  • 增加日记字数统计和计时(待续)功能
  • 媒体增加索引选择器组件,可供关联到现有的日记
  • 增加评论管理页面(但前端还没有接入评论功能)
  • 新增 InputHidden 组件默认隐藏输入的内容
  • 日记编辑页面可直接选择推送到企鹅群(和 @Eric 的机器人做对接)
  • 增加访问凭证管理页面(但还没有接入到任何服务)
  • 增加 useMenu Hooks 管理页面展示菜单项,提高复用性
  • 增加产品管理页面
  • 增加 StringArrayInput 组件,可供编辑字符串数组到内容和排序

其他项目

奇趣起始页

  • 替换新 Logo,增加搜狗搜索,修复用户名模式数据错误的问题,解决重置本地设置错误问题,调整导航项 DOM 结构,修改版权展示时间
  • 优化代码命名和逻辑,增加可读性,更新为现代 JavaScript 写法,窗口关闭按钮改为事件委托,修复用户模式下的 Bug(无法进行设置)
  • 导入导出用户设置,删除 setStorage 的无效逻辑
  • 增加夜间模式(需要在设备上开启),调整部分色彩设定,搜索方式下拉框显示对应图标
  • 拖动排序+样式更正 @苏莫
  • 识别运行环境,在线模式下不允许进行拖拽编辑和设置。顺便给在线模式提供 Fallback,初始化成本地的效果
  • 新增管理导航项目抽屉,可即时的修改看到的东西
  • 解决增加站点时,出现拖拽功能失效的问题(还调整了拖拽初始化功能)并增加了已选项高亮效果
  • 修改设置后,直接修改当前展示的背景效果,而无需刷新
  • 减淡动画和视觉效果 @戴兜

Single 主题

  • 打印样式优化
  • 提升夜间模式暗度,调整配色,优化代码格式,顶部菜单增加键盘交互方式

Fantasy 主题(赞助版)

  • 更新奇趣框架,修复样式异常
  • 完成遗留的文章目录树功能,手机版也支持
  • 顶部菜单和归档页面只展示一级分类的文章,不显示二级
  • 新版随机文章图片算法
  • 追番页和友链页不要跟踪目录树
  • 修复登录用户无法进行评论的问题
  • 获取番剧内容失败的纠错处理
  • 优化引用块样式,而不是原先的大虚线,顺带解决夜间模式下的颜色问题
  • 修复二维码分享和剪贴板无法复制的问题
  • 支持移除 Prism 的场景
  • 更新为现代 JavaScript 写法,格式化部分语句

一些整活过程

一些与项目维护(包括公司项目)与整活的过程,可以看下这些日记:

工作

今年的工作和求职过程可以说是坎坎坷坷,好在我运气不错,有个比较好的结局。至于为什么离职了,那你大可听我继续往下细说。

3 月初被叫去谈话了一次,说是公司接下来开始全员居家办公,公司的电脑什么的大概率要被收回去,只能用自己电脑干活。这说明了一个问题,就是公司的现金流估计紧张起来了。虽然公司也有不少同事是远程工作的,但我还是喜欢出门走走。

明天要把公司电脑的数据清空一下了,接下来我就是全程在家办公的「死宅」一个了,在颠废的路上持续前进着,工资没涨、技术没进步...

在干活的某一天,因为此前我的一些个人想法,有部分图片资源没经过打包器,导致发布生产环境时命中了旧的缓存,但又不允许清除 CDN 缓存(可能是没有管理权限)被老板说了一句,这个操作简单但反复,修改的项目很多,能用简单的办法解决谁想用难的呢。

老板:你得搞掂这个事,不然换图片这个事都搞不掂,是不是该考虑考虑是否胜任当前岗位?

我从这句话感觉到他貌似 Diss 了我,有那种“随时被开”的威胁口气。


后续就被老板说工作态度消极,扣了不少工资(年前公司已经全员扣了一次了,年初的时候还说有机会调整正常,实际并没有,这能不消极吗)

下午和老板私下谈话了,他还是觉得我现在这样的工作态度有问题,最后结果就是继续降工资咯,我也不知道还会在这里待多久了。

做业务真的就是“解决问题”而已,即便是用最烂,可维护性最差的方法也多多少少能做出来。也许这就是为什么公司此前的项目平均水准都不太高的原因吧。写了这么久的 React 之后,第一感觉就是接触到好的项目源码之后,即便代码看不懂,但之后会逐渐明白他为什么要这么做,有什么好处,眼光确实是会不一样的。接下来这段时间我还是尽量多去看看书和文章吧,不然再这样下去迟早也会废了的。

这件事发生之后,我只想着怎么能在这里继续挖掘出剩余的潜在价值了,合作关系已经开始逐渐崩塌,呆不长久了。


随后我的事情很快就被传到隔壁深圳同学那了,真是丢脸丢到外地去了。

再仔细思考一下当时和老板谈话的时候,他口中说的能继续加薪的同事,其实干的杂活也确实不少,甚至晚上经常加班解决(所有人的提交记录都有个可视化图,他确实显得特别积极),这要是不给加就很过分了。
我依然还在尝试着把代码写得更好,但这样做貌似在公司里体现的意义并不大。毕竟客户确实可不是看你代码写的好不好的,而是看能不能及时的满足需求而已。写得好,也就只是对自己的工作负责,接下来修改起来也会没那么吃力些。

甚至因为这件事情,我爸还想过我要不要去转行做设计,我自然是拒绝了。一是我完全没经验,二是我缺乏必要的技术。例如插画设计我就完全不会,同行竞争力很低,就算有机会面试不也是现场被拒啊。


我把这件事情发到了脉脉,并没有人抓住重点不说,还被人咬文嚼字继续 PUA 了一顿,这种事情发生之后,一度把我的心态搞到炸裂。估计是因为大厂员工普遍高压力无处释放把,自己过的不顺也好好 PUA 别人一把,反正彻底对这个平台无语了。但在意外看了一份别人本科生的简历过后,我感觉我也许并不算太差。

继续呆在这家公司,一个人孤军奋战维护一个“屎坑”后台项目,并不能让我获得什么新的能力提升,和朋友、群友们聊过之后,果断的选择了裸辞。我的学历不高,学历焦虑还是挺严重的,但是升学考试对我来说还是太困难了。

准备简历的过程也比较麻烦,相较于之前的简历,主要做了这些改进:

  1. 展示自己折腾项目遇到的难题,用数字可视化形式展示
  2. 不要写上学时的个人奖项了(@Lencx)

离职相关的事情也和朋友聊过,@Eric 准备去字节继续做开发,@Kevin 打算去香港读研,@MJ 想着去外国留学持续提升。不得不说,他们个个都比我要强多了!

失业期间我也在网上刷(学)着各种面试题,之后我在 Boss 上投递简历陆续去面试,一共面试了 3 家。

在 @Innei 的引介下认识了现在公司的老板,进而入职了现在的公司(在此之前还面试了两家都没成,我也没有什么合适的选择了)入职后阅读代码的能力还是 Debug 的能力都有所提升,由于维护的是一个在线会议项目,我也借此熟悉了 WebRTC 及一系列的相关技术(虽然都不算深入),团队氛围还是工作环境整体都是比较不错的。

面试之途

这些都是我入职公司之后的一些事,我挑了一些或许比较有料的内容出来。可能和上面的有所重复,但我不想修改了,整理起来确实太困难了。

...

我在新公司里主要做的事情基本都是迭代产品和修复 Bug(必要时重构),也算是上手项目代码比较合适的方法。即便如此也不能保证自己编写过程中不会产生新的 Bug,因为你没法想象其他人之前会用什么奇怪的方式写。在你的认知里「这个东西」是这个作用,在他们的认知里或许是别的作用(还有一些 Bug 的修复,解决了问题但命名与实际作用不符),想要彻底解决这种问题就只能慢慢重构优化了。

按照大佬们的计划,今年暂时没有自己主导的新项目。但我认为在接下来的一年一定会有的!

生活

送行了两位老人

今年年初我奶奶去世了。到老家的第一天去探望的大姑丈,后续也因病去世。人生有时候就是这么短暂,所以忙碌过后,还是好好享受下生活吧。

本来奶奶年纪大了手脚不灵活,想着买车之后开车回家,让奶奶亲自坐上我开的车,看来这个想法现在是做不到了啊。

走夜路

又一次送行

并没有选择做房奴

约了好多家中介看了好几次房,新楼盘和老楼盘都看了下,但几乎都是过了过眼瘾。毕竟我家境确实一般,并不像其他人家里有矿想买就买,在我上班之前家里基本上就是“月光族”,这也就是为什么我一台爸妈买的游戏本能从上学用到上班还在坚持了。在家里收入不高的情况下,的确应该节俭一些。

看房

亲戚的婚礼

参加了一次隆重的婚礼,朋友圈也围观了不少人的婚礼。可能是我与他们关系普通,或者对方家庭条件问题只叫了家里人吧,我并没有被他们受邀参加。对于我这种从来没谈过恋爱的人来说,只能是无比的羡慕啊。

婚礼现场

购置鱼缸

和老妈商量后,给家里购置了鱼缸,养了点小宠物的感觉还行,有活物之后,可以闲着没事对着它发呆了。

购置了鱼缸

晚上回到家,我老妈下单的鱼缸送到了。花时间拼装了下水泵,清洁了缸体和装饰物,就差点小鱼了。我还是更希望能再去淘点大件的装饰物(小桥城堡房子什么的),这样使得整体内容就更加丰富一些了。

首次做了造型

年前花 300 多块钱做了一套造型。虽然看上去效果还不错,但是依旧没有女生会多瞧我一眼。

寻找可爱之物

现在日常喜欢在淘宝、B 站、咸鱼等平台上查找可爱的周边,像是动漫抱枕,手办,T 恤等等,因为实在是太可爱了,简直是猛男的最爱啊,忍不住的说不定就下手了啊。

遭遇过两次诈骗

第一次是在西区里遇到一个骑电动车的做推广,说是什么新店开业参与抽奖,结果要求我给他看支付宝的花呗记录什么的。感觉就挺有问题。

疑似诈骗店铺

第二次则是在 🐑 了的期间,我妈接到了一个自称京东客服的电话,他很熟练的爆出我家地址,在对方要求我安装钉钉共享屏幕我就感觉到不对劲了,上网搜索之后就是实打实的诈骗,保留了对话语音,但是我懒,没整理出对应的日记。

始终存在的学历焦虑

感觉现在本科就是入场券,IT 行业内普遍认为本科达到了烂大街的水平了。因为这个原因,我有几位同学也尝试开始自学考试升本。

他那句话「往死里学」让我印象深刻,(考本科,尤其是数学)明明就是自己不擅长的事情,为什么还要反复再逼自己去做呢。没有爱好支撑,凭什么去坚持下去?问他几点钟睡觉,结果也是两三点。近期互联网公司这么多的猝死事件,实在是让我感到害怕。

但他们在深圳平常工作就挺劳累了,下班还时不时在群里约其他同学上号玩游戏,想要在这种疲惫的环境下好好学习还是非常考验一个人的心态的。


也有其他朋友提供了建议,例如远程授课的海外大学,就是需要语言能力,以及不少的 Money,你懂的。


刷微信的时候又看到了那种提升学历的广告文,说是考试会变难,什么专业都要考高等数学和英语了,这么搞真的是难上加难咯!(虽然计算机专业也必须要考)不过得知了自学考试的概念,看百科上的说明说是这种考试比全日制要难,可现在的工作大多数都需要全日制吧,自学考试的水分和全日制比,难道还是全日制的更吃香么?

也有人说,求职的时候公司看你不是全日制本科,也会直接 Pass 掉,既然如此那我还有什么信心去争取呢,不如多提升一下自己的技术水平或影响力,争取获得更多的内推机会更合适。

新冠与健康

三月中旬左右去打了最后一针疫苗,至少自己不会变成重症患者了。

三月底,自己住的地方直接来了一个密切接触者,直接整栋楼封楼了,每个人都要做登记。

吃着晚饭的时候,外面突然响起了敲门声,在想着是不是楼下邻居又来找麻烦(噪音)了。结果我妈看到是穿着防护服的工作人员,没错,我们这栋楼被封控了...

封楼

六月离职后独自一人出去吃了顿麦当劳,结果却感冒了。@Innei 说估计是离职之后太焦虑了免疫力下降了,我觉得有道理。在有工作的时候感到不快,在没工作的的时候感到更不快了!

11 月底,疫情又开始扩散起来了,公司办公地点附近一片区域全部封锁。一部分同事不能过来上班,结果没两天,公司所在位置直接被封了。那天刚下车准备上班,结果就只能回家远程办公了。

管控区域

结果年初突然宣布全部解除封锁,也不提供免费且强制性的核酸检测了,自然年底都如约而至“羊”了,全员陆陆续续感染新冠在家休息,还好公司所有前端几乎都是错开休息的,项目方面貌似并没有太大的影响。

近期疫情已经逐渐常态化了,我这开始不提倡低风险人群做核酸,且开始付费做核酸。价格 2.5 混管,13 块单管。呆在家的好处自然是能降低感染风险,但女朋友嘛,总不可能天上飞下来一只吧...

后续我爸单位也有同事 🐑 了,导致我们一家自觉“隔离”,不参加聚会等活动了,少吃了一顿大餐。

圣诞节那天,我妈也说自己开始有症状(估计也是自己同事传过来的),但我还没事,因为常态化之后,只要没症状就得去上班。结果那天去到公司立马就开始喉咙不舒服了... 之后就开始在家休息,我妈吐槽说现在你们吃的都是 🐑 人做的 🐑 餐了。

我妈发烧了,而且头微微疼,可能羊了,但我还没有症状,按照规定即使我 🐑 也得正常回公司上班

发烧了

甚至我不舒服的其中一天,我妈那还遇到了诈骗电话,还好我人没有彻底傻掉。加上她并不会操作,骗子气急败坏,什么话都让他说出来了。

这鬼病毒都 6 天了,结果自己还没完全好起来。还干了一件或许终身难忘的一件事情,为了配置自动化备份网站的功能,直接把自己网站的图片资源全部删除了,糟透了!躲得过初一,还是躲不过初五啊...

为了弥补这个错误,我只好开始研究自动化脚本,以快速可靠的方法覆盖掉 404 的资源(截止 2023 年 12 月 31 日,依旧存在不少资源无法被恢复)详见上面 编写了一段自动化 Shell 章节。

恋与提瓦特

还没开始就结束了。这件事情的起源还是因为我在 B 站一个原神的视频下方发了一条评论。

原神这么火 我还是遇不到一个玩原神的女朋友 😭

其他一句话简单概括

游戏

游戏方面基本上和去年一样,由于购入了新的台式主机,因此玩了四海兄弟和大表哥,感觉需要时间才能好好体验,不像现在的快餐游戏随时肝完体力就想赶紧下线做其他事情。

换设备之前的体验是真的糟心,《四海兄弟:重制版》最低画质分辨率 30 FPS 都不能稳定,也不知道我是怎么玩下去的==

番剧

依旧是休闲萌系番。

  • RPG 不动产
  • 测不准的阿波连同学

数码

给老爸买了台红米

老爸用的红米 Note 8 购于 2020 年,今年年末他的手机电池续航下降,我细看出现了鼓包的现象,他嫌弃手机比较慢,就给他更换了一台红米 Note 12,是刚出不久的新机。原先想买一台二手更大内存的红米 K30,到货后感觉屏幕有些许瑕疵,考虑到后期的系统更新年限,我还是选择退货买了新的机型。

自己的第一台台式电脑

9 月组装了自己的第一台台式电脑,是真正意义上自己的电脑,自己的工资,自己完成的组装。其中显卡是大头,去掉它不到 5K,加上它差不多 9.5K 了。而上一台台式电脑还是我上小学的时候爸妈通过亲戚介绍去电脑城装的,配置很低,就是一般的办公配置。

组件

内部

这台电脑的配置如下:

主板:华硕 B660M D4 Wifi
CPU:英特尔 i5 12400
内存:金士顿 32G DDR4
硬盘:西数 1TB SN570(不太够用,现在非常后悔)
GPU:蓝宝石 RX6750 XT 超白金

公司配了台 M1 Mac Mini

我想加设一台显示器提高办公效率,找老板申请了,最终选择了小米的 27 寸 2K 分辨率的一款。之前用的 Windows 主机居然带不动两台显示器(Intel 你也太不争气了吧),于是征用了公司一台闲置的 Mac Mini,M1 处理器,8 + 256 的丐版,但貌似还能胜任我的日常工作。

工位

2022-11-12:简单买了点东西的双十一 / 公司配了显示器和 Mac

淘了台二手 iPad Mini 6

年前购买了 iPad Mini 6,不是全新的,屏幕上有细微划痕,边框无磕碰。就是在环境温度很低(15 - 20度左右)的时候貌似性能释放会有问题,出现游戏画面掉帧,不知道是我机器的问题还是通病。

这个现象在我 iPhone 上也偶尔会出现,并不是百分之百会有

iPad

后续这台机器玩了几个月就给弄坏寄修了,CPU 烧坏不能用 Wifi 上网,低价出给维修的了,血亏血亏!

年后购买了一台雷鸟电视

家里的 39 寸联想智能电视购于 2014 年,今年除夕前一天就坏了,春晚都没得看了。年后参考网上的评测还是选择了它,价格最有性价比,具体体验咋样就后期再说吧。

雷鸟电视

愿望

  • 持续寻找一只心仪的可爱的对象
  • 持续提升自己的技术能力,节奏就看自己了,不要完全躺平了就好
  • 多做一些有意思的开源项目、持续写文输出
  • 调整一个较为良好的作息时间

往期

朋友们的总结

如果你对这类文章都比较感兴趣,也欢迎你来看看我朋友的同类文章,写的都挺不错的 😂

@Innei:2022 · 在绝望中前行
@折影轻梦:2022,没有记忆的一年

相较以往写总结的人更少了,而我也咕到了现在才完成。首先感谢你能看到这里,其次感谢在这一年里不断让我进步,让我快乐的各位朋友们,祝你们新年快乐~

☑️ ☆

一个由时间字符串引出的风波

起因

后端接口返回的时间数据,是一个不带时区的 UTC 字符串。大概长这样:

2023-11-08T08:48:39

翻译到中国时区,就是

2023-11-08 16:48:39

我查找资料后发现,如果想让 new Date()dayjs 默认解析并转换到当前时区,需要在此字符串的末尾加上一个 Z 代表它是 UTC+0 时区,这样前端就能直接识别出服务器返回的时间与对应的时区。

如果是一个带时区的时间,它应该是

2023-11-08T08:48:39Z(ISO 8601 规范,MDN 的推荐)
2023-11-08T08:48:39+0000(前端也能解析)

这两种日期格式交给 new Date() 都是可以直接解析的

MDN 对此的备注:

当用 Date 构造函数(和 Date.parse,它们是等价的)解析日期字符串时,一定要确保输入符合 ISO 8601 格式(YYYY-MM-DDTHH:mm:ss.ssZ),其他格式的解析行为是实现定义的,可能无法在所有浏览器上运行。对 RFC 2822 格式字符串的支持只是惯例。如果要适应许多不同的格式,库可以提供帮助。

仅有日期的字符串(例如 "1970-01-01")被视为 UTC,而日期时间的字符串(例如 "1970-01-01T12:00")被视为本地时间。因此,我们也建议你确保这两种类型的输入格式是一致的。

但后端说这就是一个“UTC 时区”的时间,让我用 UTC 的方式解析,使用 dayjs 可以做到(dayjs.utc()),但需引入对应的 UTC 插件。

对于这个字符串本身,我认为服务端能返回时区是最优解,因为其他项目之前也是这么做的,和客户端对接就没有任何沟通上的成本。

由于这个后端对接的不仅仅是一个 Web 前端,还对接了 iOS App 端,我询问了 App 端的实现过程,则和上面的情况类似,还说这是之前定下的“约定”。

说白了就是后端不返回时区的情况下,你都按照 UTC+0 的方式去解析这个时间字符串。

可我并不知道有这样的约定,我和其他前端对了下,发现他们也并不知道有这样的“约定”,甚至也有直接用 new Date() 解析的。也就是说这个时间字符串的问题在其他项目中也会是一个新 Bug。

后端把 JS 可以处理的方法甩过来之后,搞得我当即气急败坏。仿佛就是我做错了一样,这个问题难道后端就不背锅了么?

分析

考虑到我们团队的前端人数较多,App 开发就只有 2 人,如果要把这个“约定”告诉所有可能参与到项目维护的人来说是不现实的,且容易被疏忽,这不就是埋坑行为么。明明有个很好的官方规范在那,都是人家长期实践过的结果,为什么要自作主张搞一个额外的“约定”出来,增加沟通成本呢?

但现在项目已经完成到了这个程度,这个“错误”也一并存在了许久,就像 Referer 是一个错别字,Referrer 才是正确的一样,一个错误一旦影响程度面大了,后面就只能将错就错了。

如果让后端修改这个时间字符串的格式,他说除了调整当前项目,还需要将相关的几个后端项目一并修改。如果后端不修改,前端目前做好的项目恐怕也需要排查修改一遍,将来不排除还会存在前面提到的“埋坑”问题。

就算后端改了,前端又涉及到 App 端和 Web 端,还需要确认修改是否存在兼容性的问题才行。

前端这边展示过时间的位置确认都是错误的时区,理论上不需要做额外修改。就算按照 UTC 处理,也不存在这个问题:

dayjs.utc("2023-11-08T08:48:39”).unix()
// 169943319

dayjs.utc("2023-11-08T08:48:39Z”).unix()
// 169943319

就是不确定 App 端如果将一个「没带时区」的字符串变成「带时区」的字符串,是否又会变成一个“新的错误”。如果 App 端也是相同的结果,我认为后端应该把这个时间字符串更改为标准的 ISO 8601 规范,在将来的项目维护总是利大于弊的。

作为围观了这样一场风波的你,对此又如何选择呢。

☑️ ☆

一个部署 NodeJS 项目遇到的依赖问题

又是一个万恶的周一,回到公司发现上周五改完需求提交的代码在 CI 的时候发生了故障(还好只是测试环境)。简单概括,就是不知道为什么 ua-parser-js 这个依赖的 TypeScript 类型读取不到,需要安装一个叫 @types/ua-parser-js 的库。

Type error: Could not find a declaration file for module 'ua-parser-js'. '/app/node_modules/.pnpm/ua-parser-js@1.0.36/node_modules/ua-parser-js/src/ua-parser.js' implicitly has an 'any' type.

Try `npm i --save-dev @types/ua-parser-js` if it exists or add a new declaration (.d.ts) file containing `declare module 'ua-parser-js';`

  1 | import axios from "axios";
> 2 | import uap from "ua-parser-js";
    |                 ^

而实际上这个库我是已经安装了的,只是它写在了 package.json 文件的 devDependencies 里面。这个项目的 package.json 有个很诡异的点,就是几乎所有的包都写在了 dependencies 里面,包括其他 @types/xxxx 的包,我就觉得很奇怪,为什么读取不到 devDependencies 里面的包呢?

我从 CI 的流程一步一步看,最终定位到了 DockerFile 文件,光看代码的执行流程并没有发现什么奇怪的问题,总体来说就是安装依赖,启动进程,然后导出 3000 端口映射。我尝试用自己本地的 Docker 环境跑了一遍。

FROM node:18.15.0 AS runner

WORKDIR /app

ENV NODE_ENV production

RUN addgroup --system --gid 941 nodejs
RUN adduser --system --uid 941 nextjs

COPY . ./

WORKDIR ./

RUN chmod 777 .
RUN npx --yes pnpm install
RUN npx pnpm build

USER nextjs

EXPOSE 3000

ENV PORT 3000

CMD ["npm", "start"]
docker build -t usercenter .

结果和前文一样,也是一模一样的报错。我尝试排查问题,并把这个配置文件发到了群里,@提莫 认为可能是环境变量 ENV NODE_ENV production 这行设置导致的。这小小的变量设置影响会有这么大么,我去掉它重新打包,发现的确正常了。

devDeps 是干什么的

先不说环境变量的事,为什么要有 devDependencies 而不是直接 dependencies 一把梭呢,我认为这个包如果要提供给其他包使用,总会有一个打包好的版本可以直接使用(不需要在父项目里面再执行构建一遍代码),那么负责构建过程的包是不是就没有必要被再次安装了?

如果你要构建这个项目本身,则还是必须安装 devDependencies 下的依赖才行,可以看看用 Vite 一类的脚手架,他们的 package.json 是不是就是这么写的?

为什么没有安装 devDeps

既然 devDependencies 的使用方式是正确的,那么错就错在其他地方了。仔细看过程,环境变量的设置放在了最前面,后面才安装依赖文件,这就可能导致没能安装上 devDependencies 下的所有依赖。

这个猜想是正确的,我查阅了 PNPM 的文档,的确是这样子。因此这个 DockerFile 的过程是存在问题的,不应该在安装依赖之前强制设置成 production 模式。

pnpm will not install any package listed in devDependencies and will remove those insofar they were already installed, if the NODE_ENV environment variable is set to production. Use this flag to instruct pnpm to ignore NODE_ENV and take its production status from this flag instead.

如果 NODE_ENV 环境变量被设置为 production, pnpm 将不会安装 devDependencies 中列出的任何包,并且将删除那些已经安装的包。使用这个标志可以指示 pnpm 忽略 NODE_ENV,并从这个标志中获取它的生产状态。

同时感谢群友 @咲奈 的回答:

  1. 好像确实是这样,如果你在 pnpm i 安装的时候就给了 NODE_ENV 为生产,就不装 devDeps
  2. Docker 文件里不需要声明 NODE_ENV,因为你也没用到这个变量,还影响了 pnpm i 安装的依赖
  3. Scripts 里不需要使用 cross-env 设定 NODE_ENV,在常规情况下,它的值一般只有 development / test / production,并且由构建工具自动帮你设定,你不需要设定。
  4. 如果在特殊情况下,需要在过程中 productionNODE_ENV 下执行 pnpm i,workaround 时先把 NODE_ENV 改成 development 装完依赖再改回来。
  5. 不应该把 @types/* 下面的依赖放到 deps 里,虽然大部分情况下,依赖在哪都是无所谓的,这只是个通俗约定,但你问的 GPT 骗了你
☑️ ☆

关于开源软件的一些思考

近期看到了有关技术圈的几条新闻,其中一条是 WangEditor 的开源作者提到自己做开源长期没有收益,最终不得不宣布项目暂停维护。

暂停维护.jpg

刚看到这个的时候的我也不得不为此感到唏嘘,想到自己在上家的时候,还将项目里面原有的编辑器替换成了它,因为当时这个项目功能强大,社区和维护也是比较活跃的,最关键的是作者选择将这个项目完全开源不收费。

这个编辑器放在项目里面运行起来挺好的,可我作为一个平平凡凡的打工人,肯定不可能为此让老板为一个开源项目赞助吧。更何况这个项目还是一个外包项目,找客户买单更是不可能的事情。

由此可以引发一个思考,在国内做开源的确不赚钱,甚至是亏本买卖。因为你在开源项目上付出的时间和精力,往往并不能获得等额的回报。

什么是开源

或许有朋友不是很清楚开源软件是什么,此处引用一下万能的 GPT 的回答:

开源软件是指源代码可以被公开查看、使用和修改的软件。它是通过一种许可证模式进行分发,允许任何人可以自由地访问、学习、修改和共享源代码。这与闭源软件相对,闭源软件的源代码不对外公开。

开源软件的主要特点包括:

  1. 公开源代码:开源软件的源代码对任何人都是可见和可访问的。这使得开发者、用户和研究者可以查看代码,理解其工作原理,并对其进行改进、调试和定制。
  2. 自由使用权:开源软件通常以自由、开放的方式分发。用户可以自由地使用该软件的功能,无需购买许可证或支付费用。
  3. 修改和重新分发:开源软件授予用户对源代码的修改和重新分发的自由。这使得用户可以根据自己的需要进行定制和改进,也可以共享和传播软件的更改版本。
  4. 合作和共享:开源软件鼓励用户之间的合作和共享。开发者可以共同参与软件的开发和改进,为社区做出贡献,并受益于其他人的工作。

常见的开源软件项目包括 Linux 操作系统、Apache HTTP 服务器、Firefox 浏览器、MySQL 数据库等。开源软件的发展受益于全球开发者社区的合作和贡献,提供了可靠、安全且经济高效的解决方案。

开源的意义

所以作为一个普通程序员,开源自己软件的意义是什么,我认为或许对于不同人有着不同的答案。

作为一个学生,多写项目可以提高技术水平,丰富自己的编码经历。开源自己的项目代码,或许可以得到其他人的帮助,以写出质量更高的代码。这样你就有了找工作时“谈判的筹码”,相较于其他人要更加突出。而且还可以通过开源项目结识新朋友,相互学习和切磋。

作为一个职场打工人又不同了,需要考虑到生活上的问题,工作过程就已经写了不少代码了,写开源不一定能提高多少自己的技术水平,此时做开源项目的收益就不再是经验第一了,自然问题就变成了能否通过做开源项目获得收入了。

当然现实也存在着为爱发电全职写开源项目的开发者,或许对他们来说还有自己其他方面的追求吧(名气,技术等等)。

我的徒弟 @Innei,他起初是非常看好项目开源的,这点并没有错。昨天他就和我聊到这件事情,他说做开源只能吃土。他做了这么久的开源,唯一收到的赞助是 @Diygod 大佬的(我认为其实算是他帮大佬维护 XLog 的报酬吧)国内环境如此,就是很难通过做开源项目获得赞助。

技术值钱吗

@Innei:唉 怎么样才能开源挣钱啊,要可持续发展,什么业务挣钱啊,搞技术真 TM 不挣钱,全给别人打工,Antfu 这样的每个月就一万多点

他说的没错,纯粹追求技术的确是很难获得变现的。因为公司并不会因为你的技术有多好就能多赚点钱不是么,代码写好一点,Bug 产生更少,后期的迭代能更顺利一些就已经很不错了。

我认为,技术本身并不值钱,技术的诞生最终还是为业务服务的

这句话也在我的 GitHub 个人资料 上写了,也是我学习技术的精髓所在,学习到的新东西,最终都要实际落地的,除非这项技术的使用并不会对项目有任何提升,盲目追求新技术可能会产生更多难于解决的 Bug。

我和他说,我目前就通过开源获得了一部分收入,你可以学学,尽管并不是很多吧。他也想进一步了解(从批判恶龙,理解恶龙,到成为恶龙是吧)

我是如何通过开源获得收入的

我目前主要有两个开源的主题项目,SingleFantasy,它们是可以免费使用和安装的。许可证是 MIT,我拿我自己的软件二次开发并收费,也是没有任何问题的。

只是如果想获得额外的服务支持和功能,需要购买我的「赞助」版本。两款主题分别售价为 ¥11.11 和 ¥33.33,我认为这个价格并不是非常贵。我每年可以通过它获得 ¥200 左右的收入,目前也仅仅可以抵消一部分的服务器费用而已,想完全收回本还是做不到的。实际上这个收入非常的有限,无法和正式工作的薪资做对比。

其实当时主要考虑到我的主题竞争力一般,功能也没有其他人的全。就算做的全,卖太贵又很容易被衍生出破解版,这并不是我希望看到的,因此才折中选择了这个或许更容易被接受的价格。

说到这里,就不得不谈谈国人对软件的消费意愿了。

软件消费意愿

国人为软件消费的意愿依旧不太乐观,至少从我身边来看,他们还是会更偏向使用免费软件,或者是破解版而不是收费的。前段时间我一个亲戚让我给她重装系统,软件自然也要重新安装。

微软的 Office 办公软件作为一个普通白领或许必不可少的软件,实际上它是收费的(Office 365 个人和家庭版收费 498 元一年)。很显然我不可能随便装一个网上的盗版给她,这是不负责任的行为,因为很可能隐藏了不受信任的广告或病毒软件,假如玩坏了就和重装系统之前没啥区别了(一开机就出现了一刀 999 的广告)

当白嫖党占多数,并认为免费使用是一种很常见的操作之后,你就能理解为什么做开源软件没有多少收入了。

说说我个人,我其实是愿意为一部分软件付费的。只要这个软件是买断制,而且价格尚能接受。不少游戏我都在 Steam 上购买了正版,为了学习剪辑甚至也买了苹果的 Final Cut Pro 等等软件(打包买有教育优惠,貌似永久使用),在 iOS 上也购买了熊猫吃短信、小火箭等等应用。

一些建议

从个人学习的角度方面看,开源的好处还是大于坏处的,前提是你不会太在意收入上的问题。如果要考虑到商业化,还是需要谨慎些,盲目开源不一定会带来好处。

如果项目是个可商业化产品,的确不太适合在前期就做完整功能的开源,除非你已经做好了不通过这个开源项目获取流量、赚钱的打算。否则从一开始选择闭源,直到这个产品有了名气之后再开源,也会相对合理。大家都知道谁才是“正版”,即便出现了二次修改的版本,也会偏向于选择最开始的那个,当然前提是最开始的那个功能和维护始终都是最好的。

因为早早开源不一定能得到好果子,GPT 火起来之后,市面上出现了不少二次包装 GPT 的产品。如果你的品牌或公司缺乏流量和宣传,你选择把项目开源了,就很容易被人借鉴模仿出类似的产品。结果人家不参与 PR,给你提很多新需求,你的产品没有人用,别人修改的更受欢迎。自己写的代码被别人小改下拿去赚钱了,甚至你的功能更新也被别人当作卖点。这你可以接受么?

前文提到的编辑器项目,它更像是一个基础性设施,而不是某一个具体的可商业化产品。如果它是一个公司的开源项目,并能在自己的商业产品上使用,且该商业产品已经获得了成功。这种情况下,该开源项目即便没有为公司创造营收,也总会提高公司的名气等等,以不至于到随随便便就暂停维护(因为商业产品需要改进,也可以顺带把开源的也改了)。

前面我已经提到了自己如何通过开源项目获得收入的方式,这种服务+扩展功能的方式是相对可取的。我举两个例子,国外有 RedHat,国内有宝塔。虽然 CentOS 和宝塔面板都有不小的骂声,但是至少人家活下来了啊不是么。

关于我徒弟 @Innei 他现在的情况,我也和他讨论了一下。他写的开源项目叫 Mix Space,是一个用于构建个人主页的平台。他已经把现有的功能已经做的很齐全了,但仍有一些未开发的功能(例如相册、豆瓣书单什么的)。

我给的建议是,推出类似插件或定制版本的方式,给一些有需求的用户增加以上功能,并提供收费技术支持。或者是写一个新的前端模板,提供基础版本和 Pro 版本(和我现在的模式一样)。

如果不想考虑以上方式,也不指望能通过项目获得收入,真的可以完全可以按照自己的需求写程序,别人提供的意见选择性去完成就好,(警惕从天而降的产品经理)这样也能让自己平时维护起来更轻松一些。

他问我如何管理两个版本之间的问题,这也是我一直没良好解决方法的坎,目前我都是手动处理合并代码的(上游代码是开源版本,根据开源版本优化付费版本),各位有什么好的办法也欢迎留言~

以上就是我个人有关开源软件的一些想法,欢迎评论发表相关意见!

☑️ ☆

谈谈我对恋爱/结婚的想法

本文内容间断修改完善超过一个月了,但博主认为依旧存在些许欠缺,将来可能会做小幅度的调整。因此全文转载就不必了,留下原文地址分享出去也算是对作者的一个尊重

不知不觉间就已经母胎单身了 20 余年,又是一年 520,我觉得是时候以自己的角度来说点什么了。

下面简单以几个话题为切入点,来分享下我对谈恋爱/结婚的个人想法。内容可能会在将来存在小幅变动,先暂时把我想到的都发出来吧。

自卑感

我的长相比较一般(感兴趣的可以看我往期在 B 站发过的视频),身高不到 170,由于体质原因已经出现逐渐脱发的现象,在人群中并不会显得非常的出众。不像某些人不搭讪,不说话都能被女生主动要微信。

我出身的家庭情况也很一般,内心深处常常存在一种缺乏自信的感觉。但我妈经常会和我说:“比上不足比下有余”,的确是这样,我爸妈都是农村出身,物质匮乏,一切都是辛辛苦苦打拼出来的。现在我们过着不愁吃穿、并且能满足自己兴趣爱好的生活,其实已经算是不错了。

但要是考虑到结婚,女方父母肯定会想着男方的经济情况,不想让女方受苦,这无疑是对男(难)方施加了不少压力,动不动就是要买房买车,这要求就已经劝退了不少男同志了。不像我爸妈那个年代,基本上没有什么特别的要求,只是觉得合适就结婚了。

在现在这样高房价低收入的时代,并不是谁都能“拼搏一会儿”就能换来幸福。幸福的标准因人而异,我认为踏踏实实的工作生活,符合自己经济情况达到合理的收支平衡,平平淡淡享受生活,就已经足够了。作为一个平凡的人,真的没有必要过于追求面子,什么都要有,什么都要买最好的。面子其实是最不值钱的,只是单纯看你表面来做评判的朋友,或许并不是对你有益的。


2024 年 2 月 20 日更新:

聊到这里,我想起了自己曾经的个人喜好,当时自己太年轻,为了融入群体得到尊重,却牺牲了自己当时最喜欢的事物。我的喜好在他们的评价下,变成了一种“幼稚”的代名词,现在回想起来,这样的经历真的太愚蠢太愚蠢了!


如果女方(假如有的话)和她的家庭对我的期望太高,动不动就是各种超乎能力的条件,那这样的婚的确是没有必要结了。毕竟这样并不一定会带来 1 + 1 > 2 的结果,而且这样的事情多了,双方家庭也过的心累不是吗。

家乡老屋

圈子隔阂

我是一个程序员,兴趣爱好也相对单一,估计现在能提出来的只有动漫和游戏了,可以理解成典型的宅男形象。体育方面从小就比较弱,对篮球什么的也不是很感冒,害怕受伤,唯独有个例外就是疫情前的我会经常去游泳,做一下单杠,还打过点羽毛球什么的。而我目前最擅长的事情几乎都属于计算机相关的,大多数人并不会理解我做的是什么,又有什么意义和价值。

因此和我能有话题甚至有共鸣的人比较少,现在除了上班基本上都在家,也没什么地方去玩,要好的朋友十指都能数过来,社交圈也很难得到扩展,而且他们也基本上都是单身的。现在的年轻人都不太愿意将就,我也一样。谁不希望找到一个和自己有共鸣的灵魂伴侣呢?

你可能会说,那就扩展下自己的兴趣爱好呀。当下社会比较浮躁,再加上工作已经消耗了大量精力,能静下心来学点东西其实是挺困难的。而且就算是自己喜欢的东西,也不一定会合群。

曾经的自己挺喜欢美术,也能通过一些动漫或游戏作品的启发,想出一些天马行空的创意甚至能画出来(这里应该把我小时候画的画展示出来,但是可惜我忘记了)。但现在的我对自己的审美要求高了很多,就算重新开始学习下,短时间内能拿出的结果实在满足不了精神层面的需求,得不到满足和认同感。而且过程消耗的时间和精力还不少,更容易给我一种时间恍恍惚惚过去了,却最终自己什么都没学会了的感觉。

而我现在经常做的事情 - 编写网站前端代码,总会有个比较好的结果,至少感觉自己在时间上花的值得,都是能拿出去给人看的东西,而不是做出来简单粗糙的那种。可以说自己喜欢的东西,都会稍用心思去做打磨。


2023 年 11 月 24 日更新:

我时常会把我的作品和经历发在朋友圈,可这着实得不到多数人的理解,平时不足五个的点赞数已经证明了一切。我发现推特或许会更适合我,因为在那边的同行朋友会多一些,至少 200 个阅读还能有 1-2 条评论不是么。


交友活动还是社死活动

关于拓展人脉还有一种方式,就是主动出击。前面也提到了我的不自信,对于性格比较腼腆的我来说,想要通过社交活动认识人,也是比较困难。有一些男生特别容易受女生追捧,但他们普遍被带上“渣男”的标签吧,他们性格可能本身就不算太腼腆,认识的女生很多,在街上随便撩一个可能都不是问题,他们对谈恋爱是比较有自信心的,不会像我一样而感到恐惧。

我所在的城市有较为权威的青年团组织了交友活动,此前他们大人们总是鼓励我们几个单身汉试试看。想要参与必须填表申请,筛选之后才能参加。每一期的内容和主题都不太相同,但大体流程基本上都是从某一项小活动为题开始:例如喝咖啡、美食品鉴、才艺展示、做手工、玩牌类游戏什么的。

IMG_5380.jpg

光看这样的活动内容,或许确实是挺不错的,可对于我而言这些基本都不行,就算有机会去了,感觉结果也是尴尬无比(在座的各位都互不认识,要是有认识的人缓解下可能还好)。而且年龄段可能也不相符,相差太大,就算认识加了好友接下来也不一定还有机会聊。

那网恋软件呢

你说试试网恋软件呗,其实这类软件的算法设计本身就存在争议。我们只是普通人中的一个,实际情况是特别容易被右滑的。软件为了保证自己的盈利,只能是那些特别漂亮好看的人被曝光的概率较高,靠软件得来的爱情,还是不太现实。

而最受欢迎的前 100 名女用户和男用户,收获的喜欢数量平均为 67 万次和 11 万次

作者:想象事务所
来源:B 站

你或许以为氪金就能提高你被别人刷到的概率,或是提高了你成功匹配的概率,那就真的输了。软件赚到了你的钱,可你匹配成功了还没开始聊天呢。而且和那些“很厉害”的人聊天大概率会被无视,因为她们的列表里出现的聊天对象可能非常多,这还有可能好好聊么...

此时你会意识到,软件里你追寻的关系,已经成了一种明码标价的昂贵商品。于是多数用户都感到,软件上与人建立的关系,是脆弱和不确定的。而男性用户在使用软件后,普遍出现了更显著的自卑心理,和更低的自尊水平。

作者:想象事务所
来源:B 站

还有一部分软件更是离谱,真实用户不多就算了,不知道从哪搞来的“真实信息”,直接用机器人主动联系你聊天,制造出一种你很受欢迎的假象,这不更没意义么。

网上的同好群呢

2023 年 11 月 24 日更新:

我在 QQ 上加入的主要群几乎都是博客和前端技术圈子的,这些人的特点就是或多或少都搭建过自己的网站,分享的文章内容也各具特色。我多数时间会在关系比较不错的博友群里聊天,我自己也是一个博主,也自然有一个这样的群。

但博客圈子的人来来回回就这么多人,实际这个圈子非常的狭窄,玩博客的女生也是稀有品种。

那不考虑一下加入和动漫有关的群?其实我也不是没考虑过加入这样的群,可这样的群实在是太难找了,总不能直接在群搜索里面随便找一个吧。热门群就算你进去了你其实也是普普通通的群员一个,想和群友混熟挺难的。

就拿我现在经常玩的原神来说吧,我到现在就没有加入过任何一个主题和原神有关的 QQ 或微信群。不过也有些例外,例如我很早之前就加入过一个 Galgame 的制作组群,但几乎没在上面发过言,因为实际情况我挺难融入到他们的话题当中。(但有一位群友购买了我的博客主题,还是在加了群不认识我的前提下,就挺巧的)

@狱杰 同学在群里说我可以去看看漫展,加一些漫展群。这其实也是挺好的方法,或许有不少的小姐姐可以勾搭。可实际上我也从未逛过一次漫展,不了解漫展内容,也不懂观展有什么需要注意的事项。如果自己想做 Coser 估计也有各种规矩在里面,这对于我这样一个新人来说,还是需要不少时间和经历去认识的。

所以总体来说,对于现在的我想要加入一些比较有意思的同好群,还是比较困难的,需要做出一些努力才行!

谈恋爱还是打水飘

就算我真的认识到了对自己有兴趣的女生,和她谈恋爱的过程就一定会顺利吗?还真不是。写代码电脑性能太差可以换,出 Bug 可以改掉,没有多大试错的成本,可谈恋爱呢...

没有上门的爱情,只有上门的骗局

近期 520 我发朋友圈吐槽了一下(因为大家都在秀恩爱,我啥都没得秀),结果在评论区得知了我一位好兄弟近期分了。和他聊了聊,他说是和她谈了将近一个月就消费了将近 3W,我刚听到这一句话就给蚌埠住了。我去年年底组装的台式机,加起来也不过就 1W 上下。我为了缓解经济压力,让自己看着钱包没这么痛,甚至还弄了免息的分期付款。实在是难以想象,为什么他们谈个恋爱居然能花掉这么多的?

IMG_5122.jpg

他:给她转了 2W,然后吃喝啥的 1W

他在深圳也是打拼了不少岁月,去了一家不错的公司,现在待遇比我要好很多(他是做安卓客户端开发的,待遇的确是要比我前端高不少)显然他谈恋爱的压力比我要小。可离谱的是,他这赚了多少钱就花出去了多少钱。他谈过的也不少了,这么离大谱的情况还是第一次遇到。(虽然他的前任他也没少给过,甚至学车的钱都是他出了)

说难听点就是他做了大冤种了,老实人都是最好骗的(不得不说,我不少做程序员的朋友也是这种性格)。可能这样的女生就单纯是玩,根本不会把你放在心里的。类似的语句网上也早流传许久,甚至已经没法追朔到原作者了。

我就这么说吧,其实追女孩子很简单的,男孩纸只要有耐心并且主动点多花点心思在她身上,多花点时间陪她玩,多花点钱买她喜欢的,不用多久,你就会知道什么叫打水漂了呢

来源:网络评论

和他聊天的那个夜晚,他独自在酒吧喝酒散心,说是自己第一次独自一人过 520。作为旁观者来说的我,也和他聊到了将近三点半才睡觉,不得不为他的现状而感到唏嘘。

他:好好赚钱啦,我也要存钱啦,谈啥恋爱,一个人多爽

他可能已经享受过谈恋爱的感觉以至于开始感到疲惫了,别忘了,我可是从始至终一直在独处。就算谈上了,还得看对方究竟是怎样的人,社会现状男生普遍比较卑微,以至于谈恋爱的过程中处处忍让对方,而自己却过的非常心累。

她们多以自我为中心,在两性关系中总是一种居高临下的姿态,沟通中频率最高的则是我想,我要,我认为,我以为,几乎不在乎男方的想法和照顾男方的感受,经常会让男人觉得很“累”,时间长了想逃离,逃离不了则会麻痹自己,可能绝大部分男生终其一生都没有经历过一段正常的两性关系,更别说令人愉悦的一段感情了,谈婚论嫁则更像是一种任务。

来源:网络评论

以上案例给我好好的上了一课,可见现实中的确存在着这样的问题。但我始终认为并不是所有女生都会这样,至少从身边人的角度来看,不少朋友表示遇到合适的还是很幸福的。

所以谈恋爱对我而言到底有什么意义,首先第一肯定是期望找到相互都喜欢的人。谁不喜欢新鲜感啊,像我这样从来没谈过的,有什么理由不尝试一下呢。再不济,借助这样的经历提升自己的说话水平和“情商”,也不是一个坏的选择。

没错,我的确因为“情商”太低被人批判过,一段“感情”还没有开始就已经结束了。关于这件事在我去年的日记里也有相关记录,这里不再具体展开了,感兴趣的可以自行前往浏览。

我认为谈恋爱不应该只是“玩”,应该坦诚对待,通过这段经历逐渐认识和了解对方,合适就合适,不合适就不合适,或许还可以继续做朋友不是么。当双方建立起足够的信任,观念相互吻合,再进一步走进婚姻的殿堂就不是什么问题了吧。

结婚的风险

就算恋爱能真正的走到头,转眼就要结婚了。那还得讨论前面提到的经济问题,组建新的家庭,男方给多少彩礼,女方回多少嫁妆呢?结婚之后的生活,要怎么规划呢?

像是前面的案例,有些女生还只是谈恋爱而已就已经开始狮子大开口了,这要是结了婚还得了?新版民法典的问题大家可能都知道了,就是如果是婚后购买的房产,无论是谁出资,最终都是划给双方各一半。这表面上是为女性提供了保障,但实际上男方也承担了不少的风险不是么。

换个角度来说,也有女生认为结婚了她们也得不到保障。例如会有些别有用心的男方找小三,结果离婚分了女方财产的。假如有了私生子,那私生子还要出抚养费吧,这就非常恶心了。(但你要想一下,像我这样的人会有机会找小三么,hhhh)由此可见,相互的信任非常重要。

我也找到一篇男方把女方搞得很不愉快的案例,这在网上也流传很久了吧:

我们最近遇到了一个非常麻烦的婚姻案例,里面出现了一种很新的家务劳动分配问题,相信未来大家只要生孩子养育都会遇到类似麻烦,所以我会把整个情况做一个详细的介绍。我们现阶段因为制度设计的原因对类似情况是没有办法的,尤其是年轻女性希望能仔细读一遍了解下。和我们介绍情况的是女方的律师,从钱的问题上来讲这个案例我认为她的描述是没有问题的。

男方作为一个高薪程序员是一线城市本地人,月平均收入超过4万,有独立婚房在男方母亲名下。女方是从二线城市嫁到一线,也是典型的高学历,月收入1万多。6年多前奉子成婚,根据女方的说法男方当时和她有口头承诺,婚房是母亲名下无法加名,男方收入高,双方将来一起攒钱买房。要是一线买不起,就去女方老家买。

年初时双方再次因为购房问题产生矛盾。女方希望男方兑现承诺拿出钱来买房。因为女方个觉得老家房价出现了大幅度波动,很值得投资,希望男方拿钱出来买房。男方拒绝后,女方就是想说用离婚逼一下男方。简单意思就是只要女方提离婚,男方钱反正也要被分走,不买也得买。

可是没想到女方一提男方就直接非常坚定的要离婚。根据一般的情况这种拼命加班又高薪的男人都是离婚市场上的肥羊,随便宰掉一只离婚弄个100万出来女方自己去买个房子也挺好。可是真到离婚流程中发现男方这么多年下来一分钱存款都没有。女方1万多的收入,自己花花存下来都存了15万。

从财务操作来看,就是这个男的每个月的收入到了月底花剩下的所有钱都被他妈拿走了。也就是家庭开支虽然表面上一直都是男方妈妈在管理,可是账目也就是包括男方父母的开支都是男方账目里支出。然后一个月剩下的钱多则两万多少则一万多,全都被男方每月定期清零转给了男方的妈妈。

那这样给亲妈转账算不算转移夫妻共同财产呢?男方这边律师讲的就是这是男方妈妈养育孩子的开支。一线城市年轻男女都上班,哪里有空带孩子?男方妈妈带孩子又不是免费保姆,每个月要那么点钱很正常。

你看到这里可能还没有意识到类似这样的案例有多麻烦。我们参与讨论的人都懵了,因为这样的操作打中了我们婚姻改革的一个死穴,它真是一个制度性的,我们暂时无法解决的问题。你耐心一点听我讲。

我们讲的都是全心全意无条件为孩子付出。家里面照顾孩子都是孩子父母和老人一起全力付出,不计回报。这些年我们从欧美引入了“带孩子要补偿”的概念,但是却没有引入人家的转移支付。简单说就是人家认为带孩子要补偿,是政府做转移支付,直接给妈妈发钱。所以你能在欧美看到大量的单亲妈妈,靠政府转移支付一个人带好几个孩子生活。

而我们的转移支付是在家庭内部。中国家庭多数时候就是处在低收入的女性带孩子更多。我们过去几年推的家务劳动,就是希望弱势的女方能在离婚时分走更多的“夫妻共同财产”。这也形成了一个新的社会共识,大家都认为带孩子要拿经济补偿,而且还是按家里雇佣保姆的标准拿钱。中国大多数人一个月都赚不到5000块交个税,但是带孩子的补偿说个每月1万大家都嫌少。

最大问题还是一个特殊的国情问题。我们国家存在普遍的隔代抚养。尤其是大城市里孩子父母都工作,奶奶外婆在带孩子。现在我们推女性带娃要额外收钱,钱也不从政府那边来,而是从家庭中来。那么奶奶外婆带孩子该拿钱吗?

家里有足够钱分吗?

假如我们承认了这样的家务劳动,认为男方每个月定期转钱给他妈作为带娃开支的行为是合理的。上面这个案子离婚会出现什么结果?

房子是男方妈妈的,女方分不走。男方本地人,孩子马上就要读小学了也是长期奶奶带,几乎不可能判给女方。因为男方的存款为零,也就是他没有为夫妻共同财产做出任何贡献,所以离婚甚至是女方要给男方补偿,还要每个月给男方钱作为孩子的抚养费。

我们怎么办?

带孩子算家务劳动,家务劳动要钱,可是家里就那么点钱不够分啊。我们把这个问题拿出来展现给大家,目前也没有答案,就是希望大家能够了解下里面问题。

作者:sven_shi
来源:微博

只能说,我并不想做这样形似骗婚的男方,也不希望遇到一个想着通过男方谋取利益的女方。结婚之前双方家庭条件相差应该不能太大,最重要的还是门当户对吧!真正的家庭,我认为应该是相互付出的。

近期“地铁女”的新闻也发酵了很久,一定程度上产生了性别对立的情况,但我认为能够做到这样无耻行为的女人,应该在全国还是占极少数的。否则也就不会有这么多人“坚决不想放过她”了。

既然出身平凡,或许一个和我一样平凡的她,会比较合适吧。如果能遇到同甘共苦,甚至想买房愿意一起还房贷的她,那的确是一种珍贵的缘分啊。

说说我喜欢的类型

其实感觉也没有能说出来的类型,截至目前也没遇到自己真正喜欢的,还是挺想体验一下那种心动的感觉。可能我的确比较在意他人的看法,就算我喜欢对方,对方一定会接受我么。即便现状如此,个人还是比较希望她在某些兴趣爱好上与我能有所重合,例如都是计算机行业内人士,喜欢玩同一款游戏,听歌类型有共鸣,都喜欢二次元什么的。说简单一点就是相互之间能合得来吧,她觉得我 OK,我觉得她 OK,耶!

干净、直接、利落,这是我认为她应该有的最基础的标准,不遮遮掩掩,遇事不压心底,想要什么就大胆的说出来。而对于「女拳」,我是零容忍的。什么是「女拳」?可以简单看下这张表格:

女权女拳
男人能做的我也能做我要是能做还要男人干嘛
靠自己依附
事事让着我就是歧视我弱我有理
男女劳动率相同男人就该多干活
大家生来平等女孩子更娇贵
同样的标准严重的双标
靠实力让你佩服靠歪理道德绑架
平权特权
大家一起挣钱养家男人挣钱养家

即便现实比较残酷,但个人对恋爱还是表示乐观态度,还是比较希望能遇到一个合适的人。大时代背景如此我们无法改变,但我们依旧可以寻找和获得属于自己的一份幸福。一个人无力改变的事情,可以尝试一起协力改变。

单身经历的思考

母胎单身了这么多年,我都是怎么过来的呢。其实二次元占很大一个吧,它是一个几乎完美的虚拟世界,算是一个精神寄托,或许就和小孩子喜欢抱着玩偶睡觉是一样的感觉吧。

伊蕾娜

米哈游的大伟哥也说过几句金言,我也的确喜欢不少由他们设计出来的角色。她们的存在,的确会让我显得不那么的空虚。

“我们最核心用户是宅男。”
“我们自己也是宅男,所以我们深刻理解宅男的需求。”
“宅男是一群很好很善良很有爱却又很可怜的人,有着强烈的跟异性交流的需求,却因为巨大的压力不敢行动,我们就是要解决宅男的这种需求。”
“我们的梦想是让每一个宅男都能拥有自己精神寄托的虚拟偶像,从此告别孤单与寂寞”

作者:刘伟
2011 年 11 月 19 日,“新新创业达人”总决赛


2023 年 10 月 30 日更新:

可这样的小众爱好在部分人眼里看来是比较反感乃至讨厌的,我也始终在思考,我到底是喜欢一个看二次元的女生,还是不看二次元的女生呢。看二次元的我可能聊不上来(她们比我浓度还高),不看二次元的又怕对方一上来就嫌弃。对于这样的问题我确实没有答案,只能让时间证明一切吧。

王者荣耀作为热度很高的一款手游,我发现它也在往“二次元”这个方向走了,或许这能说明还是有相当一部分人能接受的吧,至少她们不太感兴趣但也并不会讨厌

上班的时候很忙,不上班的时候又觉得放假的时间太长非常无聊,打开 QQ 和微信几乎没有人会主动找我,时而有一种被孤立的感觉。写代码和文章分散自己的精力是一个方式,但现在很容易败给“懒惰”和缺乏的激励(阅读量、评论、点赞等,所以恳请多参与互动下这篇文章吧)。

最重要的其实还是尽量让自己的生活变得充实一些吧,可我这个人就是不太喜欢独自做一些事情,像是去逛街、看电影、喝下午茶什么的,一个人出去总认为缺乏目的和实际意义,生活中需要一个相互讨论的人。这就是为什么我总会拉上朋友一起出门,而自己一个人的时候却只会呆在家里了。之所以想找一个对象,也是想尝试着破局吧。

至于这一篇文章

有些朋友可能会问,你写了这篇文章,有没有可能更没有女生会看得上你自己了么。这篇文章虽然提到了很多不好的案例,但更多的其实是表述了我对于这些事情的看法,我自然是希望被人看到。如果她看完觉得我的观念和她不同,无法达成共识,那也算是提前做好了筛选不是么。也许她认可我的想法,对我足够的感兴趣,信任、接受并包容我,才会成为我的另一半吧。

而且本博客的主要受众绝大多数都应该是男生更多一些?女性独立博主本来就挺少的,既然如此,我还不如就把我的想法都描述出来,让你们讨论讨论。如果有合适的,你们各位帮我介绍一下也不是不可以嗯?

相关链接与参考资料

本文的部分内容参考过我所阅读的文章、观看的视频,在此留下链接作为书签吧。

☑️ ☆

解决 SSH 连接 GitHub 出现 Connection Closed

GitHub 早期已经禁止使用 HTTPS 的方式提交代码了,因此我改用 SSH 的方式连接仓库。近期在公司使用第三方代理服务时发现多个节点均不能正常使用,我起初还怀疑是 SSH 密钥配置的问题,结果 GitHub 显示我并没有使用新的密钥连接过...

具体错误信息如下:

kex_exchange_identification: Connection closed by remote host
Connection closed by 198.18.0.15 port 22
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

公司还有一个自建的代理服务,之前使用它提交代码到 GitHub 一直都正常,但由于近期公司的移动网络不太稳定,一直连接超时不可用(联通则正常),才不得不思考其他办法。

尝试了其他代理服务商提供的节点,依旧不可用。我怀疑是服务商做的限制,就让公司对应账号所有者问了下客服。结果是服务商禁用了 22 端口(连接 SSH 服务默认均为 22 端口),替换为 443 即可。

简单阅读完这篇文章后,由于我在多个站点配置了多个 SSH 密钥,所以直接选择了它修改 config 的操作,成功解决以上问题。

# GitHub
Host github.com
Port 443
User git
HostName ssh.github.com
PreferredAuthentications publickey
IdentityFile ~/.ssh/git_ed25519

至于为什么前面提到的公司自建的代理服务可用,估计是他们没做任何奇怪的限制吧。反过来那些公开使用的,自然是需要做一些安全保护了。

❌