1. 问题现象

后台博客信息页面请求以下接口时返回 500:

Text
GET /api/xxxx/xxxx-setting/settings?key=xxxx-blog

返回错误:

JSON
{
  "code": 500,
  "data": null,
  "message": "获取配置信息失败:connect ECONNREFUSED xxx.xxx.x.xxx:xxxx"
}

其他后台接口可以正常使用,因此问题表现为只有博客配置接口异常。

2. 根因分析

2.1 不是前端请求参数问题

管理端使用 GET 请求,并通过 key=xxxx-blog 获取博客配置,接口路径和请求方式正确。

服务端对应路由为:

Text
server/routers/xxxx-setting.js

2.2 原接口在读取数据前执行了表结构迁移

原来的 GET 接口在查询 xxxx_setting 前,会先执行:

  • SHOW COLUMNS FROM xxxx_setting
  • 检查 xxxx_profile_tags
  • 检查 7 个 Hero 配置字段
  • 字段不存在时执行 ALTER TABLE

因此该接口不是单纯读取数据,而是包含了数据库结构检查和修改操作。

2.3 数据库短暂拒绝连接会导致 Promise 永久失败

原代码使用模块级 Promise 缓存初始化结果。如果第一次请求恰好发生在 MySQL 重启期间,数据库连接会返回:

Text
ECONNREFUSED xxx.xxx.x.xxx:xxxx

由于 rejected Promise 被继续缓存,后续请求仍然复用这个失败结果。即使 MySQL 已经恢复,接口也会持续报错,直到 Node 服务重启。

这解释了为什么其他接口正常,而 xxxx-setting 接口持续异常:问题出在该接口独有的初始化逻辑。

2.4 MySQL 日志说明

MySQL 日志显示:

Text
Server hostname (bind-address): '*'; port: xxxx
ready for connections

说明 MySQL 已监听 xxxx,并且已经恢复连接。

以下日志暂时不是本次接口报错的直接原因:

  • TLSv1/TLSv1.1 已废弃:安全协议警告
  • CA certificate self signed:自签名证书警告
  • Failed to start slave threads:未使用主从复制时通常无影响
  • IP address could not be resolved:客户端 IP 反向 DNS 解析警告

3. 已实施的代码修复

3.1 抽离博客配置表结构初始化

新增:

Text
server/modules/xxxx-setting/index.js

表结构初始化从路由文件中抽离,并由服务启动阶段执行。

3.2 GET 接口恢复为纯读取

GET 接口不再执行 SHOW COLUMNSALTER TABLE,只负责读取:

SQL
SELECT * FROM xxxx_setting WHERE xxxx_key = ?

这样可以避免用户访问页面时触发表结构变更,也避免单个配置页面被数据库迁移逻辑影响。

3.3 初始化失败后允许重试

表结构初始化失败时会清空缓存的 Promise:

JavaScript
schemaPromise = undefined;

数据库恢复后,服务启动或后续保存配置时可以重新尝试初始化,不会永久保留失败状态。

同时支持多个服务进程并发启动时的字段重复创建场景,ER_DUP_FIELDNAME 会被视为已完成。

3.4 服务启动时执行初始化

在:

Text
server/app.js

中调用博客配置表结构初始化,并记录失败日志:

Text
[BlogSetting] 初始化失败: ...

3.5 增加接口错误日志

GET 和 POST 失败时会记录配置 key、MySQL 错误码和错误信息。日志不记录数据库密码、Token 等敏感信息。

3.6 增加数据库端口配置支持

在:

Text
server/mysql/db.js

中显式读取:

ENV
DB_PORT=xxxx

未配置时默认使用 xxxx。

4. 宝塔部署步骤

4.1 检查 MySQL

在宝塔终端执行:

Shell
systemctl status mysql
ss -lntp | grep xxxx

确认 MySQL 正常运行并监听 xxxx。

4.2 检查服务端环境变量

如果博客服务和 MySQL 在同一台服务器,建议:

ENV
DB_HOST=127.0.0.1
DB_PORT=xxxx
DB_NAME=xxxx-xxxx

DB_USERDB_PASSWORD 继续使用服务器现有配置,不要提交到 Git 或文档中。

如果 MySQL 在独立服务器,需要确认数据库服务器防火墙和云安全组只允许博客服务服务器的 IP 访问 xxxx。

4.3 更新代码并重启 Node 服务

如果使用 PM2:

Shell
pm2 list
pm2 restart 实际服务名称 --update-env

如果使用宝塔 Node 项目管理,则在对应项目中执行重启。

重启是必要的,因为旧版本可能已经缓存了失败的 Promise。

4.4 验证接口

重新访问:

Text
https://xxxx.xxxx.top/api/xxxx/xxxx-setting/settings?key=xxxx-blog

预期返回 code: 200,实际 data 内容以数据库中的 xxxx-blog 配置为准。

5. 本地验证结果

已完成以下静态检查:

Text
node --check app.js
node --check mysql/db.js
node --check modules/xxxx-setting/index.js
node --check routers/xxxx-setting.js
git diff --check

检查通过。

本地没有直接启动服务连接生产数据库,避免对生产数据库执行结构初始化或修改。

6. 后续建议

当前修复已经解决接口因短暂数据库故障持续失败的问题。后续可以继续将数据库字段变更统一迁移到独立 migration 文件中,由部署流程显式执行,从根本上避免业务接口携带 DDL 操作。

阅读进度 0%