2025-06-09 11:52:00
最近同事分享了一篇文章:《该写好代码吗?我也迷茫了。写的代码好被替代,写的代码差到处救火》,引发了我的思考。我问了问他的看法,是写好还是不写好呢。
答案可以说是意料之中,“正常写就好”。他觉得正常写也会有 Bug,代码也不会很糟糕。确实是这样,因为即便你当时认为写好了,这需求变化速度实在太快,后续很有可能就不能满足需求了,就产生了所谓的 Bug。
这让我很自然的联想到我现在在公司做的 Felo 搜索 项目,确切来说它现在的定位已经渐渐不是一个纯粹的搜索网站了,而是各种 AI 工具的混合体,主要竞争对手是 Perplexity、Genspark、天工 等。
前端方面我的评价是基本上已经乱的一锅粥,原先的逻辑很多不能满足现在的需求,就是一个超级缝合怪,既要 Perplexity 的那种搜索总结功能,又要 Genspark 的 Agent 型对话交互模式... 两种完全定位不同的东西被强行融合到了一起。但是换个角度来说,大公司做的东西不也是堆屎山么,不见得有多好,除非推倒重新开发。我们现在 996,就是飞快的加功能,老大天天都想发布新版本,怎么可能愿意干这种事?
最近我们在做的 AI 生成 PPT 功能也差不多是这样子,越演越烈,产品交互方面并不统一,此前 PPT 是一个独立页面,任意入口点击后打开新窗口生成,之后是独立一套交互流程。而现在为了兼容“PPT Agent”模式(单独一个工具页,通过上传文档什么的触发创建一条记录),强行让其他入口套用它的逻辑。作为对比,和它入口旁边的同级功能,思维导图、智能图形,目前都不会单独创建新的记录(虽然它们也各自有一个 Agent 模式)。
原先交互:打开一条搜索记录,找到生成的 PPT,打开独立页面
现在交互:上述入口保留,生成的 PPT 会产生单独一条搜索记录,生成成功后则点击直接打开独立页面,并且作为 PPT 其入口还不在“文档库”里面,很迷
后续需求还说得兼容之前搜索的逻辑,把原先那套模式给搬到新的 UI 上面,主打一个“抄谁不像谁”,代码逻辑的耦合性实在太强,很难想象之后要产生多少 Bug... 只能感慨地说一声,现在它已经彻头彻尾变成了“Felopark”了,这样的架构,代码也不太可能能写得多好了。
所以回到最前面的话题,写代码正常发挥即可。至于可替代性什么的,只要你的能力不差,去哪里都会发光发亮不是么。
2024-12-21 01:29:00
我在公司维护的 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
内容,直接修改了对应的版本号和签名,亲测可用。
2024-10-12 11:28:00
前段时间我购买了几年的 CloudCone 特价机炸了,导致服务中断快一个星期,再加上没有定期做备份,服务无法快速恢复。我开始寻找其他服务商的机器,兜兜转转最终选择了搬瓦工的,因为看它比较老牌,口碑还不错,且国内直连的线路良好,在此发出一个 我的邀请码,你可以通过这个链接购买服务器,为博主提供微薄返利以资助网站的持续运营。
另外小扯一句,你可能会注意到我博客的更新频率有所下降。但其实是因为我以此同时还在维护另外一个名为 保罗的小窝 的网站。个人对于博客的定义是稍微正式的内容,而这个小窝更多的是分享随笔和流水账,各位可以自行选择订阅。
此前一直都在用 OneInstack 脚本来部署环境,尝试用它重新部署,却发现 MariaDB 无法正常安装。国内国外访问同一个包的下载地址居然行为是不一样的(国外出现 404)。我选择构建安装,时间太长太复杂,且选择 Caddy 作为 Web 服务器后,居然虚拟主机都没有创建成功,我怀疑脚本是不是根本就没做好 Caddy 的适配...
OneInstack 此前也爆过供应链挂马的问题,据说是被国内公司收购了之后出现,虽然作者承诺修复,但我仍对其安全性存在质疑。再三抉择后,只能忍痛放弃 OneInstack,打算从零开始学习部署属于自己的 Ubuntu 服务器环境了!
你需要一定的 Linux 使用基础才能更好的阅读本文,实际安装过程可能有些许曲折,本文对部分流程做了次序优化,可能存在欠缺需要自行分析和解决(例如某些软件包需要自行安装),虽然有一部分在编写过程中在虚拟机上重新执行确认过,但还是以实际操作为准吧!
选择自己纯手工配置环境最主要的原因就是 PHP,它除了一个 FPM 服务以外,还需要一个 Web 服务器才能将项目跑起来,它并不能像 NodeJS 那样自己就是一个 HTTP 服务。再加上数据库服务放在相对实际的环境下可以更好的编写脚本实现一键备份等功能。使用 K8S / Docker 部署应用会更方便,但受限于成本、服务器数量和硬件配置等因素,自己纯手工配置环境依旧是首选方案。
PHP 8.3
NodeJS
在使用 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 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
编辑配置文件,修改端口,禁用密码访问,改为使用公私钥形式访问服务器。
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
参考 官网说明 安装即可。
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 已经正常启动。
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!
输入一条命令完成安装:
sudo apt install redis-server -y
在安装之前,我们还需要新增一个 Ubuntu 软件源:
sudo add-apt-repository ppa:ondrej/php
sudo apt update
安装 php
、composer
及其对应的扩展(你可以根据自己的需要来安装):
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 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
首先创建所需的文件夹:
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 的运行信息。
从 官网 上下载最新版本,之后解压在 /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 的问题。
截至本文编写过程在虚拟机里重新执行确认时,并未出现这个错误,就挺奇怪的,但还是提供一下之前的解决方案
使用 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;
再次尝试,应该就能正常登录了。
我注册了 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
程序进行连接。你将会得到以下几个数据:
在编写自动化脚本之前,需要添加对应的存储桶配置,后期可直接使用对应的命令和配置上传文件。
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_id
和 secret_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:存储桶名称
理论上能够正常列出存储桶下的内容,如果不能正常显示,可以从下面几个方向排查:
打包生成备份的过程在这里不作详细介绍,大致就是使用 mysqldump
导出数据库,之后使用 tar
打包压缩站点数据,最后使用 rclone copy
命令将所有文件上传,定期手动或自动化执行都是可以的。
cd ~/backup/2024-10-21/
mysqldump -u root -p home > home.sql
rclone copy . cloudflare:存储桶名称/2024-10-21/
在 官网 上选择对应的环境,即可生成一键安装脚本,我选择了当前最新的 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 项目到 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
配置等。
感谢 @提莫 同学对本文中一些内容的指正,由于本人的运维经验不足,虽然已经花了不少时间重试其中的某些步骤,但仍然可能有所不足和缺漏,有什么问题可以在下方留言,我会尽力为大家解答。
2024-07-05 14:35:00
近期要把公司的新官网项目给收尾了,准备打包部署发布到线上环境,我们主要采用的 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 innode_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
,可以看到构建、运行应用的过程均在里面完成(并非阶段构建),也因此导致最后的镜像略大。
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"]
考虑到我司已经在使用 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 + SSH + PM2 的部署方式,也是一样轻松了不少,以往需要在运行机器上执行极其缓慢的 pnpm build
也将提前在 Jenkins 机器上完成。通过 SCP 的方式传输构建产物,到运行机器上只需替换掉对应的资源,重启 PM2 就能完成,这里就不再具体提供实现过程了,有需要建议自行尝试摸索。
2024-06-27 21:08:00
这 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 使用 Caddy 是最简单的方法,且不会干预到实体系统环境,这里我使用了 Docker Desktop 作为演示。点击顶部搜索栏搜索 caddy
,下载最新版本的镜像,点击 Run 按钮使用它创建启动容器。
在设置里填入对应的端口号,这里我只设置 80 端口的映射,确保端口号没有被占用就行。
设置完成后容器将会自动启动并且持续运行,使用浏览器访问对应映射好的端口号(我这是 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 的问题。
2024-05-22 00:54:00
蓝底白字的 官方通告 已经出了,结果也是在预料之内,看完这篇懂得都懂,这就是语言的艺术。官方的主要出发点是为了平息事件对于公众的影响,但即便如此我相信大多数人的看法都是明智的。
前有假结婚后分财产,后有谈恋爱“同居”大额生活费不算诈骗,版本也在不断快速迭代,但总体而言法律依旧是在偏向女性的,这也就是为什么会出现这么多令人讨厌的 T0 “小仙女”了吧。
关于这份通告不同人也有不同的解读,一方面认为这是“真反转”,认为女方不是诈骗,反倒是胖猫姐姐涉嫌夸大诬陷女方。另一方面则是和我想的一样,女方确实存在诈骗行为了。
我认为从胖猫角度上来说,他可能认为钱给够了女方就会喜欢他。(可能太恋爱脑了,真以为自己满足对方的要求就能得到她了)从女方角度上来说,如果根本就不喜欢他,她应该及时拒绝,而不是让男方一昧的“爆金币”。(都没长期共同居住过都敢要这么多钱了,难道官方鼓励在恋爱中大手大脚的花费吗,显然不可能啊)这就涉及到个人的道德底线问题了。
针对这份通告也是有个知乎回答特别有意思,说是给真正的捞女提供了思路:
- 男方转给女方的钱要适当返还,这样可以被警方认定是正常男女恋爱关系
- 女方要让男方用恋爱记、小荷包等 App 功能共同存钱,并且坚定表示是奔着结婚去的,这样即使钱到手了之后立马分手,警方无法认定自己诈骗
- 要带男方去见见自己的亲友,介绍给家属,之后再以感情不和为理由分手即可
想起来之前也有个段子,说唯一合法的方式就是和别人的老婆在一起,既不会分财产,也不会让男方承担较大风险(虽然还有强奸罪,但最起码财产保住了)。
看完上述例子之后感觉不无道理,只是这样做也确实不道德。
我朋友说“古代男尊女卑 是有他的道理的”,虽然说着有点难听,但前人之鉴的确不能忽视,现实情况的确有不少东西来来去去都是一次轮回罢了。不然你看苹果的设计,以及前端框架的 SSR,本就是一场轮回,玩的都是前面玩剩下的。
女人狠起来连自己人都打,法律偏袒女性的本意是保护女性,但众多事件的发生已经足以证明它已经渐渐变为了某些人的特权,作为广大男同胞的一员,只能对此唏嘘不已。法律的本意是保护,而不是滥用,是为了让你真正遇到危险时能发挥出最大的作用。
T0 “小仙女”们的恶心行为,应该是被众人所鄙夷的,而不是被她们的“逐渐深入”渐渐变成“官方正确”。
在这里我只能呼吁身边的女性朋友们都能够清醒起来,不要变成像她们那样的负面教材吧... 对不合适的人勇敢拒绝,而不是反复纠缠。
男生真的不是不想谈恋爱不想结婚,只是想要遇到一个合适的人确实太困难了。胖猫事件警醒了不少男同胞,告诉他们这并不是少见的个例,这种畸形的价值观已经影响到不少人了。再加上现在各种 App 大数据的推送机制,只会推送用户更喜欢的内容,进一步加大了这种毒鸡汤的扩散。只会让这些人更自以为是,认为男性单方面的付出就是理所应当的,可男生的钱也不是大风刮来的不是么。
我自然还是期望能遇到真正欣赏我、喜欢我的人,尽管可能比较难,但并不代表没有可能。纯爱战士依旧存在,只是概率比较低罢了。(很荣幸自己的一位友链朋友就是这样)
只是从我的角度来看,自由恋爱确实变得越来越困难和不可信,宁愿相信自由恋爱或许还不如相信家里人介绍的,至少家庭背景是相对有保障的,并且通过他们的父母也能一定程度上可以了解他们的子女可能是怎么样的一个人,除非是那种关系并不熟络的朋友。
至于为什么我至今一直都单身,有人说这是上天在保护你,虽然有点自我安慰的感觉,但总比急急忙忙遇到一个要我爆金币的捞女要好吧... 这大环境就是被这样的人搅浑浊了,我结婚的同事都表示已经跟不上版本了。
在真正开始一段恋爱关系之前,我也在认真思考过,自己是否能付出一个合格男友必要的责任。如果不行我自然也不会随意和她们建立关系,我认为这才是对对方的尊重。宁可不爱,也别伤害。因为感情也确实需要一定的磨合,这也无疑是真正的挑战...
我曾经有一段黑历史,在这段黑历史的加持下让我有很长的一段时间惧怕异性,我或许更适合先结识异性朋友,从普通朋友做起,消除恐惧,后面才能更从容的面对真正适合自己的人。