Hello World

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

Chrome Extension 网站登录状态方案初探

前景提要

最近在开发一款电商网站商品素材下载浏览器插件,前几天搞定了图片下载功能,又经过几天焦头烂额的踩坑和尝试,最终把批量下载功能也实现了。昨天想着给插件加上登录功能,方便用户保存相关记录。因为是面向国外用户的插件,想着直接支持google oauth2登录,用户未登录时点击登录按钮,会跳转到插件网站的登录页面,用户选择google登录后会跳转到google登录页面,完成登录后同步登录状态到插件。

也就是要实现登录功能才遇到了这个问题,网上也没搜到太多资料。网上的资料大多是background和content以及popup间的通信方案,这是官方就支持的,因此没有什么好说的,但是我需要的插件和网站直接进行状态/数据同步的方案的资料却不是很多。

好在研究了一天时间(其实大部分时间在解决别的问题),终于还是解决了这个问题。

插件与网站状态同步实现方案

我们的现在的插件有一个background script叫做service_worker.js,以及一个content script叫做content.js

我们假设插件生效的网站为A网站,域名假设为www.website.com,假设我们的插件网站为B网站,域名假设为www.plugin.com。B网站的登录页面为www.plugin.com/login,B网站记录登录状态采用传统的cookies方式+session方式,而没有采用token方式。

如何让插件知道网站B的登录状态发生了变化?

  1. 利用background script 监听网站B的cookies变化

  2. 利用content script 监听网站B的登录状态变化

background script 监听网站B的cookies变化

我们可以利用background script的chrome.cookies.onChanged事件来监听网站B的cookies变化。当B网站的cookies发生变化时,会触发该事件,我们可以在事件回调中判断是否是我们插件网站的cookies变化,如果是,就可以根据cookies来判断用户是否登录。

需要修改manifest.json文件,添加cookies权限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"manifest_version": 3,
"name": "Plugin",
"version": "1.0",
"permissions": [
"cookies"
],
"background": {
"service_worker": "service_worker.js"
},
"content_scripts": [
{
"matches": [
"https://www.plugin.com/*"
],
"js": [
"content.js"
]
}
]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
chrome.cookies.onChanged.addListener(function (changeInfo) {
if (changeInfo.cookie.domain === ".plugin.com") {
// 插件网站的cookies发生了变化
if (changeInfo.cookie.name === "session") {
// session cookies发生了变化,判断登录状态
if (changeInfo.cookie.value) {
// session cookies存在,用户登录
// 发送登录状态到background.js
chrome.runtime.sendMessage({ url: "www.plugin.com/login", status: "loggedIn" });
} else {
// session cookies不存在,用户未登录
// 发送未登录状态到background.js
chrome.runtime.sendMessage({ url: "www.plugin.com/login", status: "notLoggedIn" });
}
}
}
});

整个流程是这样的:

  1. 用户访问登录页面www.plugin.com/login,进行登录操作,登录成功后,服务器写入session cookies。
  2. service_worker.js文件监听到www.plugin.com/login的cookies变化,进行登录相关业务逻辑处理。然后发送登录状态到content.js文件。
  3. content.js文件监听到登录状态变化,执行相应的逻辑,比如显示/隐藏登录按钮。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
┌──────────────────────────┐     ┌──────────────────────────┐
│ │ │ │
│ 用户浏览器 │ │ 插件网站服务器 │
│ │ │ (www.plugin.com) │
└─────────────────┬────────┘ └───────────┬──────────────┘
│ │
▼ │
┌──────────────────────────┐ │
│ │ │
│ 登录页面 /login │────────────────▶│ 写入session cookies │
│ │ │
└──────────────────────────┘ │


┌──────────────────────────┐
│ │
│ 扩展后台服务 │
│ service_worker.js │
│ 监听cookies变化 │
└───────────┬──────────────┘


┌──────────────────────────┐
│ │
│ 扩展内容脚本 │
│ content.js │
│ │
└───────────┬──────────────┘


┌──────────────────────────┐
│ │
│ 登录按钮显示/隐藏 │
│ │
└──────────────────────────┘

其实可以监听的应该不仅仅是session对应的cookies,其他比如token cookies等也可以监听,只是我们用的登录方案是基于cookies的,因此只需要监听cookies变化即可。

content script 监听网站B的登录状态变化

此种方案需要新增一个content script文件,我们假设叫 login_callback_script.js文件,专门用于监听网站B的登录状态变化,当然了,我们也可以在content.js文件中监听,但是为了避免与插件网站B的其他功能冲突,我们单独新建一个文件。

因为content script运行在网站B的上下文中,因此我们需要对网站B进行改造,新增一个/callback路由,用于提供让callback_script.js运行的环境。因此我们需要在manifest.json新增一个规则,如下:

1
2
3
4
5
6
7
8
9
10
// manifest.json
"content_scripts": [
{
"matches": [
"https://plugin.com/callback*" // 仅匹配回调页面
],
"js": ["callback_script.js"],
"run_at": "document_start"
}
]
1
2
3
4
5
6
7
8
9
10
11
// login_callback_script.js
// 你需要处理插件网站的登录状态的相关逻辑,此处cookies仅做示例,不是实际业务逻辑
if (document.cookie.includes("session")) {
// 用户登录
// 发送登录状态到background.js
chrome.runtime.sendMessage({ url: "www.plugin.com/login", status: "loggedIn" });
} else {
// 用户未登录
// 发送未登录状态到background.js
chrome.runtime.sendMessage({ url: "www.plugin.com/login", status: "notLoggedIn" });
}
1
2
3
4
5
6
7
8
9
10
11
// service_worker.js
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
if (request.url === "www.plugin.com/login") {
// 处理登录状态变化
if (request.status === "loggedIn") {
// 用户登录
} else if (request.status === "notLoggedIn") {
// 用户未登录
}
}
});

整个流程是这样的:

  1. 用户访问登录页面www.plugin.com/login,进行登录操作,登录成功后,页面跳转至www.plugin.com/callback
  2. login_callback_script.js文件运行,判断用户是否登录,并发送登录状态到service_worker.js文件。
  3. service_worker.js文件会根据登录状态变化,执行相应的逻辑,比如通知content.js文件登录状态发生了变化。
  4. content.js文件会根据登录状态变化,执行相应的逻辑,比如显示/隐藏登录按钮。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
┌─────────────────────────────────┐     ┌─────────────────────────────────┐     ┌─────────────────────────────────┐
│ 用户浏览器 │ │ 登录回调脚本 │ │ 扩展后台服务 │
│ (访问 www.plugin.com/login) │ │ (login_callback_script.js) │ │ (service_worker.js) │
└─────────────────┬───────────────┘ └─────────────────┬───────────────┘ └─────────────────┬───────────────┘
│ │ │
│ 1. 用户登录成功 │ │
│ 页面跳转至 callback │ │
├─────────────────────────────────────────>│ │
│ │ │
│ │ 2. 判断登录状态并发送消息 │
│ ├─────────────────────────────────────────>│
│ │ │
│ │ │
│ │ │ 3. 处理登录状态变化
│ │ │ 通知内容脚本
│ │ ├─────────────────┐
│ │ │ │
│ │ │ │
┌─────────────────┴───────────────┐ ┌─────────────────┴───────────────┐ └─────────────────┴───────────────┘
│ 扩展内容脚本 │ │ │
│ (content.js) │ │ │
└─────────────────┬───────────────┘ └─────────────────────────────────┘

│ 4. 接收登录状态通知
│ 执行相应逻辑

│ 5. 显示/隐藏登录按钮

└─────────────────────────────────────────────────────────┐


┌─────────────────────────┐
│ 用户界面更新 │
└─────────────────────────┘

看清了有些麻烦是吧,所以我们可以把login_callback_script.js文件中的逻辑放到content.js文件中,但仍然需要新增一个页面路径/callback,让content.js文件可以运行。

修改后的流程是这样的:

  1. 用户访问登录页面www.plugin.com/login,进行登录操作,登录成功后,页面跳转至www.plugin.com/callback
  2. B网站content.js文件开始运行,判断用户是否登录,并发送登录状态到service_worker.js文件。
  3. service_worker.js文件会根据登录状态变化,执行相应的逻辑,比如通知content.js文件登录状态发生了变化。
  4. A网站content.js文件会根据登录状态变化,执行相应的逻辑,比如显示/隐藏登录按钮。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
┌──────────────────────────┐     ┌──────────────────────────┐
│ │ │ │
│ 用户浏览器 │ │ B网站 (www.plugin.com) │
│ │ │ │
└─────────────────┬────────┘ └───────────┬──────────────┘
│ │
▼ ▼
┌──────────────────────────┐ ┌──────────────────────────┐
│ │ │ │
│ 登录页面 /login │────▶│ 回调页面 /callback │
│ │ │ │
└──────────────────────────┘ └───────────┬──────────────┘


┌──────────────────────────┐
│ │
│ B网站 content.js │
│ │
└───────────┬──────────────┘


┌──────────────────────────┐
│ │
│ 扩展后台服务 │
│ service_worker.js │
│ │
└───────────┬──────────────┘


┌──────────────────────────┐
│ │
│ A网站 content.js │
│ │
└───────────┬──────────────┘


┌──────────────────────────┐
│ │
│ 登录按钮显示/隐藏 │
│ │
└──────────────────────────┘

浪费我半天时间timeout 的问题

本来我是想着给插件添加oAuth2第三方登录,我想起我之前有一个demo项目实现了oAuth2,因此想着拿来直接改改就行,哪知道之前的demo很久没用了,google的oAuth2实例超过6个月不用就被删除了,因此我就新建了一个新的实例,结果遇到了一个登录超时的问题。用户在授权之后,服务器需要请求/token接口来获取access token,但是我之前的demo中,/token接口的一直是正常运行的,这次一直是请求超时,我想过是GFW的问题,所以我在运行的时候,一直是给系统设置了代理,但始终没有解决这个问题。

最后我不得不给node-fetch配置代理,不知道为什么,之前的demo中没有配置代理就正常工作,现在就不行了,好在配置了代理之后,就正常运行了。

这里再次诅咒GFW,它的存在导致了中国的程序员要解决很多原本不应该存在的问题,才能和世界上其他的程序员一样。

云服务时代适合中小公司的图片服务架构

文件服务架构的变迁

如今我们随处可见的图片浏览、视频浏览等功能,都依赖于文件服务。而文件服务架构经历了三个阶段,我分别称之为刀耕火种时代、自建服务时代和云服务时代。

刀耕火种时代的文件服务架构

在互联网早起,中小公司通常依赖于自己的服务器来提供文件服务。最常见的一种方式是将文件直接上传到项目服务器,然后通过服务器返回图片URL给客户端。比如很多古老的PHP项目通常是直接上传到uploads目录下(当然Java也类似),这种方式非常原始且粗糙,甚至非常不安全,经常出现忘记做过滤或者过滤规则设置不合理,导致出现上传木马文件到项目目录中的情况,如果遇到项目目录权限配置的疏漏,经常出现安全漏洞,这也是导致以前PHP经常被人诟病不安全的原因之一。

除了上述的安全问题外,这种方式还存在诸多问题,如成本高昂、维护复杂、扩展性差等问题。还是用上面的例子来说明,本就有限的服务器资源和带宽资源除了要提供网页服务和API接口服务之外,还需要提供文件服务,包括但不限于文件上传、存储、分发、处理等功能。这意味着项目服务器需要承担较大的负载压力,同时也增加了项目的维护成本。而且项目服务器本身通常是Apache或Tomcat,这些服务器本身就不是为文件服务器设计的,用来提供文件服务,既不恰当也不高效,而且如果要对图片进行裁切缩放等处理,还要搭配imagick或者GD等应用软件,图片处理本身就非常消耗CPU资源,这就使得本就捉襟见肘的服务器性能更加雪上加霜。

自建服务时代

为了解决以上问题,衍生出一种优化方案,这种方案的基本思想是将文件服务独立出来,部署在一个单独的服务器上,与项目服务器分离。这样做的好处是图片服务器不会影响到业务服务器的性能,业务服务器只需要负责处理业务逻辑,而文件服务器只需要负责提供文件访问以及处理图片相关的操作。同时,将文件服务独立出来还可以将文件服务的负载压力分散到多个服务器上,避免单点故障,同时也可以根据实际需求进行水平扩展。

这个时期文件存储通常需要自己配置Raid磁盘阵列来提高存储性能和可靠性。而且通常会采用Nginx或者Varnish等反向代理服务器来处理访问请求,将请求转发到图片服务器上。当然了,由于Nginx本身出色的性能,这个时期的文件服务器通常也是Nginx。同时,为了提高访问速度,通常会采用CDN(内容分发网络)来缓存文件,将文件分发到全球各地的节点上,实现快速访问。我们熟悉的网宿CDN大概就是发源于此时期。

即使解决了存储和访问的问题,但是文件处理的问题仍然存在。比如图片文件裁切、缩放、压缩等操作,通常需要在事先或事后处理好,然后分发到文件服务器上,这就显得还是不那么灵活。

不过此时期,业内的大公司已经有自己的NFS(Network File System)方案了,比如大名鼎鼎的GFS(Google File System)、淘宝的TFS(Taobao File System)和腾讯的TFS(Tencent File System)等。不过这些方案通常只是在公司内部使用,并未向外提供服务。中小公司依然缺乏合适的的高可用性、高扩展性和高可靠性的文件服务。

云服务时代的文件服务架构

云服务的诞生

不得不说,云服务的出现也促进了互联网的发展和繁荣。低廉的成本使得中小公司也能使用到和大公司一样的互联网基础设施,同时也为中小公司的业务发展提供了更多的可能性。

云服务时代的文件服务架构

自从以AWS为代表的云服务的出现以来,互联网公司的服务架构也经历了很大的变革。云服务提供了一种高度可扩展、高可用、成本低廉的文件服务解决方案。中小公司可以根据实际需求,选择合适的云服务商(如阿里云、腾讯云、七牛云等)来搭建自己的服务架构。

S3(Simple Storage Service)是AWS提供的一种对象存储服务,它提供了高可用性、高扩展性、高可靠性的文件存储解决方案。S3的基本概念是将文件(对象)存储在存储桶(Bucket)中,每个对象都有一个唯一的URL来访问。S3还提供了丰富的API和SDK,方便开发人员集成到自己的应用程序中。各个云服务提供商(如阿里云、腾讯云、七牛云等)也都推出了自家的S3服务,尽管名称各不相同,但都是兼容AWS的S3,毕竟AWS S3已经成为了事实标准,即使后来涌现的各种开源文件服务系统也都兼容S3,S3已经成为一种事实上的文件服务协议。

图片服务的核心需求

对于大多数中小公司来说,图片服务需要满足以下核心需求:

  • 存储需求:安全、可靠的图片存储,支持海量图片
  • 访问速度:全球各地用户都能快速访问图片
  • 图片处理:支持常见的图片处理操作(裁剪、缩放、压缩等)
  • 成本控制:经济实惠的解决方案
  • 可扩展性:随着业务增长可以平滑扩展
  • 简单维护:尽量减少运维成本

基于OSS和CDN的架构设计

整体架构

我推荐的架构如下:

1
用户浏览器/客户端 → CDN节点 → OSS存储 → 图片处理服务

这种架构的核心是利用云服务商提供的OSS(对象存储服务)作为存储基础,配合CDN(内容分发网络)来加速访问,同时利用OSS自带的图片处理功能或第三方图片处理服务来处理图片。

为什么选择OSS?

  1. 高可用性:主流云服务商的OSS都能提供99.99%以上的可用性
  2. 按需付费:按实际使用量付费,避免资源浪费
  3. 自动扩展:存储容量可以自动扩展,无需担心存储上限
  4. 安全可靠:提供多副本存储、访问控制等安全特性
  5. 图片处理:大多数OSS都内置了基础的图片处理功能

为什么选择CDN?

  1. 加速访问:通过就近缓存,显著提升图片加载速度
  2. 降低源站压力:减少对OSS的直接访问请求
  3. 节省流量成本:通过缓存减少重复下载,降低OSS的流量费用
  4. 全球覆盖:即使是小公司也能提供接近全球的快速访问体验

OSS选择与配置

主流OSS对比

服务商 产品 优势 劣势
阿里云 OSS 功能完善,生态丰富 价格相对较高
腾讯云 COS 性价比高,与微信生态结合好 部分高级功能需付费
七牛云 Kodo 专注于图片服务,提供丰富的处理功能 整体生态不如大厂完善
AWS S3 全球覆盖最广,功能最成熟 国内访问可能受限

桶(Bucket)设计建议

对于中小公司,我建议采用以下桶设计:

  1. 按环境分离:开发环境、测试环境、生产环境使用不同的桶
  2. 按业务分类:如用户头像、商品图片、文章配图等使用不同的桶
  3. 按存储类型区分:频繁访问的图片和归档图片使用不同存储类型

文件命名与组织

一种简单的文件命名规范:

1
{业务类型}/{年月日}/{UUID}.{扩展名}

例如:

1
2
avatar/20251022/8e7f9a6b-c3d2-4e1a-9b8c-7d6e5f4a3b2c.jpg
product/20251022/1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d.png

我自己常用的文件命名规范是:

1
{业务类型}/{年月日}/{base62编码后的uid+base62编码后的时间因子+编码后的随机数}.{扩展名}

例如:

1
/_avatar/20251020/Q0v_V0A6WdR_Lc7YFF.png

我来说一下这个命名规范的优势:

  1. 可读性高:文件名中包含了业务类型、日期、uid、时间因子,方便溯源和管理。
  2. 唯一性:每个文件名都是唯一的,避免了文件名冲突的问题。
  3. 时间有序:文件名中包含了日期信息,方便按时间排序和查询。
  4. 随机数:文件名中包含了随机数,增加了文件名的随机性,避免了文件名重复的问题。

另外,下划线_在文件名中也有特殊含义,它可以用来区分临时和正式的图片路径。例如,_avatar表示这是一个临时的头像图片文件路径,而avatar则表示这是一个正式的头像图片文件。有了这个区分,再加上日期,我们就可以配合定时任务,定期清理过期的临时图片文件,保持存储的整洁和效率,降低存储成本。

CDN配置最佳实践

缓存策略

  1. 设置合理的缓存时间

    • 静态图片:7-30天
    • 不常变化的图片:1-7天
    • 可能频繁更新的图片:1小时-1天
  2. 缓存刷新机制

    • 重要图片更新后主动刷新CDN缓存
    • 使用版本号或哈希值作为文件名的一部分,避免缓存问题

域名配置

建议使用专用域名:

1
images.yourcompany.com

图片处理策略

图片格式选择

  • WebP格式:提供更好的压缩率和质量,现代浏览器支持良好
  • AVIF格式:新一代图片格式,压缩率更高,但浏览器支持还在普及中
  • 降级方案:对于不支持WebP的浏览器,提供JPG/PNG格式

图片处理方案

  1. OSS内置处理:大多数OSS提供基础的图片处理功能

    • 阿里云OSS:支持resize、crop、rotate等操作
    • 腾讯云COS:提供imageMogr2等处理能力
  2. 按需处理示例

    1
    2
    3
    4
    5
    6
    7
    8
    # 原图
    https://images.yourcompany.com/avatar/20251022/user1.jpg

    # 缩略图(300x300)
    https://images.yourcompany.com/avatar/20251022/user1.jpg?x-oss-process=image/resize,m_fixed,w_300,h_300

    # WebP格式
    https://images.yourcompany.com/avatar/20251022/user1.jpg?x-oss-process=image/format,webp

成本优化策略

  1. 合理选择存储类型

    • 热数据:标准存储
    • 温数据:低频访问存储
    • 冷数据:归档存储
  2. CDN流量优化

    • 启用Brotli/Gzip压缩
    • 使用WebP等高效格式
    • 设置合理的缓存策略
  3. 按需处理图片:避免预先生成大量不同尺寸的图片

安全性考虑

  1. 访问控制

    • 使用签名URL保护私密图片
    • 设置合理的Referer白名单
  2. 防盗链

    • 配置CDN防盗链
    • 使用时间戳防盗链
  3. 文件类型限制

    • 上传时严格校验文件类型
    • 避免上传可执行文件伪装成图片

实际案例:电商网站图片架构

架构图

1
2
用户上传 → 应用服务器 → 图片处理服务 → OSS存储
用户访问 → CDN → OSS存储

具体实现

  1. 上传流程

    • 前端获取临时上传凭证
    • 直接上传到OSS
    • 上传成功后通知后端
  2. 访问流程

    • 用户请求图片URL
    • CDN节点响应,如果有缓存
    • 如果没有缓存,CDN回源OSS获取
    • OSS返回图片,CDN缓存并返回给用户

总结

基于OSS和CDN的图片服务架构非常适合中小公司,它提供了以下优势:

  1. 低运维成本:无需自己维护存储服务器
  2. 按需付费:根据实际使用量付费,避免资源浪费
  3. 高可用性:利用云服务商的基础设施保障服务稳定
  4. 全球加速:通过CDN提供快速的访问体验
  5. 灵活扩展:随着业务增长可以平滑扩展

这种架构让中小公司能够专注于业务发展,而不必过多担心基础设施的问题。当然,随着业务的发展,可能需要考虑更复杂的架构,但在初期阶段,这是一个非常实用的解决方案。

写在35岁时

迟来的此篇

这篇文章的原定的标题是“写在35岁之前”,我本来是想写在35岁之前写的,但是因为遭遇裁员,身体健康出现问题,家庭危机等各种原因,导致我一直无暇着笔。一直到今天,我正式35岁了,才不得不面对现实,写下此篇文章。

尴尬的35岁

35岁,一个尴尬的年龄。与25岁时的我相比,我并没有成熟多少,却因为社会和职场带来的压力和焦虑,让我比25岁时少了几分信心,相比之下我更像是一个被社会抛弃的中年男子。古人有云“三十而立”,按此推断,我应该立了5年了,可是我感觉自己只是老了5岁,而不是成熟了5年。按照以往的经验,35岁正是年富力强、心智成熟、身体成熟的时候,正是一个人能力达到顶峰,社会认可达到峰值的年龄。但现在的社会,35岁的我已经被社会和职场抛弃了,成为了领导和老板口中的低潜力员工,成为裁员计划中的一员。也成为了25岁的年轻人嘴里的“老家伙”。

计划中的裁员计划

今年2月过完春节回来的一天,领导突然在飞书上叫我去谈话,当时我就知道,我要被裁员了,除了时间比我预计的早之外,一切都有迹可循。按照年前的考核结果,我又一次被打了C(尽管年中考核的时候,我拿到了B,下半年工作也没有出什么问题,都能如期交付,产出质量也都有保障)。
之前在23年底的考核给了我一次C,当时给我说原因是我有两次项目延期。我不去争辩什么,因为第一次延期确实是我的原因,但第二次的项目我却是尽了最大的努力,加了最多的班,干了最多的活,甚至在我车祸住院回来之后,连续加了一个多月的班,最后项目做成了,成果成了别人的,我又一次背了延期的锅。尽管在项目一开始的时候我就非常明确说明了项目排期严重不够,也在争取更多的排期时间,但我的领导、技术部的领导却不愿意给更多的时间。
在吃到了23年的教训之后,我就知道那次只是我侥幸没有轮到而已,有句话讲叫“消灭你,与你无关”。因此,在25年春节前告知我绩效考核又是C的时候,我知道这次并不是我的表现造成的,只是需要有一个人是C而已,只是这次到我了而已。尽管已经对这种公平的不公平见了多次,但是从小接受上进思想教育的我,还是在听到结果后感到无地自容,气愤,羞愧、气恼,心酸,各种说得上来的说不上来的情绪瞬间涌上心头。

因此对于这次谈话,我也不知道说什么,尽管有非常复杂的情绪在里面,我还是平淡的接受了结果,我不太喜欢争辩什么,在我从小接受的教育中,争辩似乎是一种无底气的错误的行为(尽管我知道这是一种错误的观念)。

2月最后一周,技术部领导在微信上叫我去了一间小办公室聊天,我对他没有什么情绪,反而是他有点尴尬的提到“还以为你把我微信删了呢?”尽管我确实想过删掉他的微信,因为留着确实挺尴尬的,本来也没有在微信上聊过几句,但我最终还是没有。我这人向来不太愿意与人起冲突。而且我想了想,删掉不删掉,都改变不了什么,就这么着吧。那次聊天的具体内容我不想赘述什么,大家都默契地避开了裁员这个话题,心照不宣地保持着合格的成熟的职场人应该保持的体面。就像外交场合一样,双方外交官就XX事交互了意见和看法,我们也就以后的打算和计划交流了看法,不咸不淡。

最后一天下午4点,我交接完毕,骑着我的小电驴,离开了工作了4年多的公司。下楼时遇到了一些别的组或者部门的人,打个招呼之后,就此别过,我没有对他们说我要离开,但他们其实早就知道了,因此对我的离开也没有什么惊讶,我也只是简单的说“我要走了,拜拜”。互相道别之后,还是有点感慨,尽管微信上大家都还有联系方式,但以后想再聊聊天,也几近不可能了,毕竟工作是大家最大的交集,离开共同的职场,大家就基本没有什么共同话题了。

骑着车,阳光温柔地洒在我的脸上,那是我4年多第一次在下班后看到太阳,那一刻,我心静如水。

健康与工作

像我这样的普通人,没有天赋,也没有能力,只能靠自己的努力工作,才能成为大家嘴里的合格或优秀的员工。因此不出意外,我也通过努力成功得到了一堆合格职场人都有的健康问题。比如脂肪肝、肺结节、甲状腺结节,超重、尿酸高。这些年随着工作,身体就像得不到保养的机器,慢慢地出现了各种毛病。没办法,普通人就是靠青春换钱,靠健康换钱、甚至靠命换钱。你说有多少人工作一些年后身体还很健康的,其实大家都嘴上说惜命,却又都转头就努力投入到格子间中,继续拉磨转圈,普通人就是这么悲催,面对自己的健康问题,在现实的经济压力面前,主动自我安慰自我欺骗。

我17年时27岁,刚做过近视手术,担心之后继续整天写代码,手术的效果能维持多久。那时候我在想,我努努力,在这一行工作到35岁,挣点钱,之后就不用每天对着屏幕了我那时候真是自信,也对大环境非常乐观,以为会经济会一直蒸蒸日上,至少在我转行之前。现在想来,那是我真乐观。根本没有意识到,下一年,互联网环境、经济、国际关系将会开始发生剧变,也根本不会想到疫情的发生以及后续的次生灾害。

就这么,我到了35岁,工作没有达到预期的效果,健康问题却一个不落的如期出现。我结婚多年,一直没有孩子,除了因为夫妻经常出现争吵之外,我并没有想过工作压力带来的健康问题也是一大因素。直到,迫于父母催生的压力,去年我去医院检查了下。现在看来,不止是工作没成果,健康有问题,连要个宝宝都有问题了,而这也带来了我我失业后的一系列后续问题。

失败的独立开发

我大概是17年知道的独立开发,当时对这个理念深表赞同。从那时起,我算是国内第一批接触到独立开发的人,但也仅限于接触,仅此而已。我高中的时候就对互联网感兴趣,那时候我就想写一些程序让大家用,但受限于家庭条件和个人认知以及没有考上本科,我一直没有能力去实现。好在我经过各种努力,最终侥幸进入了这个行业,从13年开始,正式开始了前端开发的职业生涯。当时前端是我能接触到的门槛最低的开发工作了,其实我更想做的是服务端开发,只是技术水平不够,身边也没有一个人是做这行的,一切都要靠自己摸索。受限于自己技术水平和认知水平,我一直停留在想一个idea,写一个demo,然后放弃的循环中。

17年的时候,当时认知还是停留在钻研技术的阶段,我痴迷于研究各种技术方案的实现,一直在寻找各种NB的技术架构。而钱迹记账APP的作者首富,也是在17年的时候,开始了他的独立开发历程。那个时候,他还在望京SOHO的陌陌上班,业余在写钱迹APP的代码,而我在离他不远的绿地大厦,在一个小的创业团队里呕心沥血,期望和这个我喜欢的团队一起努力走上人生巅峰,直到20年疫情到来,团队解散。我的人生巅峰梦想就此戛然而止。而钱迹已经蒸蒸日上,势不可挡了,此时的首富同学已经成为独立开发的楷模和榜样了。

团队解散后,我还是想继续写代码,继续努力,像继续在这个行业里走下去,在瞎折腾了几个月独立开发无果后,我又继续去上班,也就来到了我上一份工作的公司。再后面的事情就是现在的样子了。
我也终于认识到了自己在尝试独立开发上的问题,就是空想,把精力浪费在空想代码实现的细节上,回避产品的问题。一直隔岸观火,却不付诸行动。
因此我花了一两个月时间,写出来了一个工具小程序,从前到后,每一个细节都是我制造出来的,虽然很简陋,但对我来说,却是我工作这么多年以来,自己的第一个真正意义上的完整作品,我终于可以名正言顺理直气壮地说这是我的作品。尽管上线之后,反响平平,没什么反馈,甚至连负反馈也没有几个。

第一个产品失败后,我又折腾了几个月做了第二个小程序,一个路亚社区小程序,还是从前到后,每一行代码都是我实现的,也写了严肃意义上的产品型后端项目,最近上线了,依然没什么反馈。

虽然独立开发失败了,但我终于迈出了实质性的第一步,尽管把东西写出来只是万里长征的第一步。

家庭矛盾与孩子问题

我结婚多年,一直没有孩子,父母那边给的压力也与日俱增,我经常睡不着觉,压力大的时候,和伴侣经常吵架,感觉到自己人生很昏暗,也很孤独。我一直夹在父母和伴侣中间,伴侣的年龄也很大了,备孕检查也出现了一堆问题,我自己也一堆问题,尽快要一个宝宝是我们目前面临的不能拖延的问题。

哎,算了,不写了,太难过了。

转型服务端开发与求职受挫

经过大半年的蹉跎,以及备孕失败,钱包也空了,我还是要面对找工作吃饭的问题,目前我正在找PHP相关的工作岗位,为什么选PHP这个没落的语言,一方面是我确实挺喜欢这个语言,另一方面是我觉得这个PHP是我目前转型服务端开发最简单的语言,我用PHP写过一些项目,也写过很多代码,所以我觉得PHP是我目前转型的 easiest language。至于为什么要转型服务端开发,一方面是我觉得前端各种层出不穷的框架和工具,很多只是在解决开发的问题,很少在解决现实世界的问题,在我看来,技术是手段,而不是目的。另一方面,我是想延长自己的职业生涯,创造出自己的作品。

但是现在大环境太差了,PHP的岗位也很少,很多公司都在转型到其他语言,比如Java、Go等,再加上我本身只有专科学历,很多职位都是已读不回,我也在考虑是否要转型到其他语言,但是我还是想尝试下PHP的岗位。如果实在不行,我还是做我老本行,前端开发。毕竟吃饭是现实问题,我不能因为工作不满足而选择不工作。

这几日,因为求职受挫,我非常焦虑,也非常难过,经常感觉自己明明很年轻,却在社会上找不到自己的位置。我一直没有和朋友联系,就是怕他们知道我现在连工作都没有搞定,浪费了大半年的时光,无颜见他们,尤其是最开始带我入行后来又内推我进入上家公司的坤哥。我尽量隐藏自己的任何信息,羞于被任何人发现。

有时候,我甚至想,我死了算了,一了百了,也不用面对自己给自己的桎梏,也不用管自己给自己戴上的任何枷锁。

失败的35岁

失败的35岁,有时候想想,死了算了,不用面对父母那边催生的压力,不用面对伴侣的不理解,也不用把糟糕的情绪传染给对方,不用担心房贷,不用担心自己的健康问题,也不用担心承担不了朋友的期望带来的自我谴责和羞愧

总结

本来上午和同样中年失业中年生病的老陈聊天,互相鼓励互相安慰,自己的难过和伤悲稍稍缓解了,本来想写一篇文章,能进一步缓解痛苦,没想到,越写越难过,就这样吧,不写了,我有点想哭