Files
app-261004/scripts/db-diagnose.mjs
T
2026-10-08 11:09:37 +08:00

121 lines
4.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 诊断 DATABASE_URL 与驱动/SSL 配置是否匹配。
*
* 覆盖两类常见问题:
* 1. **驱动不匹配**:@neondatabase/serverless 的 neon-http 走 Neon 的 HTTP 代理,
* 无法连接普通 Postgres(需改用 pg)。
* 2. **SSL 不匹配**:本地 Postgres 默认不开 SSL,若连接串带 sslmode=require
* 会报 "The server does not support SSL connections"。
*
* 用法:node scripts/db-diagnose.mjs
*/
import { config } from "dotenv";
import net from "node:net";
import pg from "pg";
config({ path: ".env.local" });
const url = process.env.DATABASE_URL;
if (!url) {
console.error("缺少 DATABASE_URL");
process.exit(1);
}
let parsed;
try {
parsed = new URL(url);
} catch {
console.error(`DATABASE_URL 不是合法 URL: ${url}`);
process.exit(1);
}
const host = parsed.hostname;
const port = Number(parsed.port || 5432);
const database = parsed.pathname.replace(/^\//, "");
const sslmode = parsed.searchParams.get("sslmode");
const isNeonHost = /\.neon\.tech$/i.test(host);
const isLocal = /^(localhost|127\.0\.0\.1|::1)$/.test(host);
console.log("── DATABASE_URL 解析 ─────────────────");
console.log(` host: ${host}`);
console.log(` port: ${port}`);
console.log(` database: ${database}`);
console.log(` sslmode: ${sslmode ?? "(未指定)"}`);
console.log(` Neon 托管域名: ${isNeonHost ? "是" : "否"}`);
console.log(` 本地地址: ${isLocal ? "是" : "否"}\n`);
/** 探测 TCP 端口是否可连接 */
function probeTcp(hostname, tcpPort, timeoutMs = 3000) {
return new Promise((resolve) => {
const socket = net.createConnection({ host: hostname, port: tcpPort });
const done = (result) => {
socket.destroy();
resolve(result);
};
socket.setTimeout(timeoutMs);
socket.once("connect", () => done(true));
socket.once("timeout", () => done(false));
socket.once("error", () => done(false));
});
}
const tcpReachable = await probeTcp(host, port);
console.log("── 网络连通性 ────────────────────────");
console.log(` TCP ${host}:${port} 可达: ${tcpReachable ? "是" : "否"}\n`);
// 依次尝试「按 sslmode」与「不启用 SSL」,判断是否为 SSL 配置问题
async function tryConnect(useSsl) {
const client = new pg.Client({
connectionString: url,
ssl: useSsl ? { rejectUnauthorized: false } : undefined,
connectionTimeoutMillis: 5000,
});
try {
await client.connect();
await client.query("select 1");
await client.end();
return { ok: true };
} catch (error) {
await client.end().catch(() => {});
return { ok: false, error: error.message };
}
}
console.log("── 实际连接测试 ──────────────────────");
const withConfiguredSsl = await tryConnect(
sslmode === "disable" ? false : sslmode ? true : !isLocal,
);
console.log(
` 按当前 sslmode 连接: ${withConfiguredSsl.ok ? "✓ 成功" : `✗ ${withConfiguredSsl.error}`}`,
);
let oppositeResult = null;
if (!withConfiguredSsl.ok) {
oppositeResult = await tryConnect(!(sslmode === "disable"));
console.log(
` 改用相反 SSL 设置: ${oppositeResult.ok ? "✓ 成功" : `✗ ${oppositeResult.error}`}`,
);
}
console.log("");
console.log("── 结论 ──────────────────────────────");
if (withConfiguredSsl.ok) {
console.log(" ✓ 配置正确,数据库可用。");
} else if (oppositeResult?.ok) {
console.log(" ✗ SSL 配置与服务器不匹配。");
console.log(
isLocal
? " 本地 Postgres 默认不启用 SSL,请把连接串改为 ?sslmode=disable"
: " 该服务器要求 SSL,请在连接串中加上 ?sslmode=require",
);
} else if (!tcpReachable) {
console.log(" ✗ 端口不可达:请确认数据库已启动、主机与端口正确。");
} else {
console.log(" ✗ 连接失败,且与 SSL 无关,请检查用户名/密码/数据库名。");
console.log(" → 若使用 neon-http 驱动连接普通 Postgres,需改用 pg 驱动。");
}