这篇文章上次修改于 706 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

前段时间,我用 MidwayJSReact 重构了 保罗 API 项目,新的叫做 保罗 API Next。在开发机经过测试后发现每次请求都能提升 20ms 左右的速度,相较于使用 cgi 运行的 PHP 程序略有优势。

最关键的是,NodeJS 版我还用了框架,而 PHP 版是原生开发的,从开发效率上来讲,前者也会更好一些。还可以借助进程持久化的特性,更好的扩展程序未来的功能设计,增加更多的练手机会。

你问我为什么不开源?其实我也想过这个问题,主要是此前网易就疯过一轮 GitHub 上的开源项目了,我的 API 项目本质上还是代理了很多现成接口的功能,存在着一定的「版权风险」,如果把这些功能移除再去开源,意义就少了一大半了,加上这个 API 可能实际上还是我的小站调用更多,所以最终我还是选择了闭源维护。

考虑到服务器的实际资源比较小,目前程序并没有采用类似 Jenkins 的自动化部署方案,也没有使用 Docker 封装镜像来隔离运行,还是采用了手动部署的方式。

前期在国内的 Windows 开发机器上轻松的整好过,感觉也差不多可以丢上线玩玩了。于是乎今天就开始折腾了,尽管步骤有些复杂,但数据库存在服务器真实环境上,肯定是比在容器里面要稳妥一些。

服务器环境

OS: Ubuntu 20.04 LTS
Memory: 971MiB

数据库:MariaDB
缓存:Redis
运行环境:NodeJS V16 (NVM) / Nginx

原先服务器已经安装好了 NVM 以及 16 版本的 NodeJS,接下来就需要安装 yarnpm2 以实现更好的包管理和进程管理了。

npm install --global yarn
npm install --global pm2

然后就可以用 git 把代码仓库拉下来,安装依赖、创建+修改数据库配置及部署了。

git clone xxxx .
yarn

新建数据库的步骤就没啥好说了,使用数据库管理软件(phpMyAdmin、Navicat 什么的)创建一个 独立账户及其同名 的数据库,最后用 vim 修改项目配置即可。

初始化 Prisma 数据库

前面我已经修改了 .env 项目配置了,而 Prisma 的 schema 文件可以直接引用它里面的配置项。参考文档

datasource db {
  provider = "mysql"
  url = env("DB_URL")
}

接下来我们就得在新环境上连接并初始化数据库表及结构了。关于 generate 命令的特性,可 参考文档,你在本地开发时会默认执行,但上服务器环境就不一定了。

“以后无论何时对 Prisma 模式进行更改,都需要手动调用 prisma generate 以适应 Prisma Client API 中的更改”
npx prisma generate // 生成类型文件

正常操作来说,我们需要在新的环境下生成项目的类型文件,然后进行发布。此前我发现一直连不上,其实是因为 MariaDB 数据库的密码需要符合一定的规则(如果是随机的密码,出现特殊字符可能会炸,出现如下提示,改成数字或大小写字母就行了)

Error: P1013: The provided database string is invalid. invalid port number in database URL. Please refer to the documentation in https://www.prisma.io/docs/reference/database-reference/connection-urls for constructing a correct connection string. In some cases, certain characters must be escaped. Please check the string for any illegal characters.

如果定义的连接地址是 localhost,可能会无法连接(应该是部分 Linux 下 localhost 不会指向本机),将数据库账号对应的主机名设置成 127.0.0.1,再修改 .env 配置文件,应该就可以了。

生成数据成功,提示如下内容:

✔ Generated Prisma Client (3.12.0 | library) to ./node_modules/@prisma/client in 1.02s
You can now start using Prisma Client in your code. Reference: https://pris.ly/d/client

然后就是执行 pushseed 指令了,我尝试过执行 pull 指令,这个目前应该是用不着的。

npx prisma db pull // 在数据库为空的情况下,Pull 不来(应该是用于同步数据库实际情况给 CLI)
npx prisma db push // 发布到实际数据库环境
npx prisma db seed // 执行播种流程(预设数据库的数据,需要编写 seed.ts 文件)
期间发现我的服务器环境还是比较奇葩,不知道为什么 npx 下执行任何命令都提示不够权限,参考网上的问题,用 which node 定位到了 NodeJS 实际目录,看到不少目录是 www 组,我用 chown -R root:root . 命令全部改成了当前登录用户 root 后依然无效,用 sudo npx 还提示找不到命令,也不知道是什么原因了。

我通过使用 package.json 上的 scripts 进行指令替代,才最终完成了这些操作。就是比较愚蠢,必须把用到的命令都给它预设进去才能用,不够灵活。这种运维方面的问题,真的是难到我了。

// package.json
"scripts": {
  "start": "hooks start",
  "dev": "hooks dev",
  "build": "hooks build",
  "dbgen": "prisma generate",
  "dbpull": "prisma db pull",
  "dbpush": "prisma db push",
  "dbseed": "prisma db seed"
}

最后,执行 yarn build 部署一套生产代码,然后就准备用 pm2 持久化运行了。

使用 PM2 运行进程

参考了 官方文档 相关说明,我用了最纯朴的命令运行了一个进程。正确食用方式,应该是写一个 pm2.json 文件,然后再去填写对应的命令。

pm2 start "yarn start"

项目默认运行在 3000 端口,由于是服务器的第一个 NodeJS 项目,我就没有特地指定成其他端口,如果需要指定端口,就和开发时差不多,给个 --port 5000 这样的参数就行了。

可以使用 wget 工具获取一条接口的返回数据,确保服务运行正常。

wget http://127.0.0.1:3000/api/acgm

修改 Nginx 配置

最后是修改 Nginx 虚拟主机配置文件,把项目彻底发布到外网就完成了。

我采用的是 OneInstack 集成环境,大多数默认的虚拟主机配置都是针对 PHP 实现的,我们需要移除原先的 Nginx 配置项,改用反向代理功能。也就是使用 Nginx 作为跳板,跳到 NodeJS 的服务那去。

注意:不能留有其他 location 配置,否则可能会造成访问故障,毕竟我们已经把全站链接给反向代理到 NodeJS 进程了,如果再使用 Nginx 的规则匹配,就会当做一个无效的路径,产生错误
server {
  ...

  # 不能有其他 location 规则,我们已经把这个站的请求全权交给了 NodeJS 了
  location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host 127.0.0.1;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header REMOTE-HOST $remote_addr;

    add_header Cache-Control no-cache;
    add_header X-Cache $upstream_cache_status;

    proxy_set_header Accept-Encoding "";

    sub_filter_once off;
  }

  # 不能使用,否则就炸了
  #location ~ .*\.(wma|wmv|asf|mp3|mmf|zip|rar|jpg|gif|png|swf|flv|mp4)$ {
    #valid_referers none blocked *.paugram.com api-next.paugram.com;
    #if ($invalid_referer) {
        #return 403;
    #}
  #}
}

使用命令 service nginx restart 重启 Nginx 进程,使用浏览器进行验证,没有其他问题的情况下,服务就成功跑起来啦!

吐槽

说了半天,整体感觉没有太大的技术含量,但我觉得做个这样的记录一下还是蛮有意义的,说不定有人和我一样因为小小的问题卡了很久?而且以后遇到类似的场景就可以更快速的去解决了。

不过这个 npx 不能使用的问题,有没有哪位大佬愿意为我这个小白指教指教的啊==