NodeJS 连 MongoDB 一直 pending,把 Codex 的 Base URL 改到 TaoToken 后让 Codex 查 mongoose 的 connect 参数
NodeJS mongoose 连 MongoDB 卡住时TaoTokenhttps://taotoken.net/?utm_sourcetaotoken_aicg_blog_end可以充当 Codex 的兼容 API 通道在官网创建一把 API Key把 Codex 的 Base URL 填成 https://taotoken.net/api剩下的分析工作交给 Codex。这篇要拆的场景很具体一个npm init -y出来的空项目根目录放了index.js里面用mongoose.connect(mongodb://localhost/mongoose_test)发起连接紧跟着mongoose.connection.once(open, () console.log(数据库连接成功))。跑起来之后控制台既没有「数据库连接成功」也没有任何错误堆栈进程就那么挂着。原文第 4 步还留了一行mongoose.disconnect()这行本身就是嫌疑之一。这类问题最难受的地方在于「没有报错」。MongoDB 驱动在选服务器阶段会反复重试默认serverSelectionTimeoutMS是 30000 毫秒这 30 秒里它一声不吭超时才抛错。你盯着终端发呆的每一秒驱动都在原地打转。下面把整条链路拆开先让 Codex 读懂你的连接代码再逐条比连接串、端口、服务状态最后确认open事件到底有没有机会被触发。整个过程里 Codex 只负责读代码、给检查命令、解释 mongoose 的语义真正执行命令和改代码的人是你。1. open 事件不触发先分清「在连」和「连不上」1.1 mongoose.connect 只是发起动作open 是结果通知mongoose.connect()内部干的事是这样的解析连接串 → 让 MongoDB 驱动建立连接池 → 向目标地址发握手hello 命令→ 完成认证 → 拓扑进入 connected 状态 → mongoose 把这个状态映射成connection上的open事件。前面任何一步没走完open就不会 emit。所以「一直 pending」的准确含义是握手阶段没有拿到返回而不是「代码写错了」。connection本身是个 EventEmitter同族事件还有connecting、connected、disconnected、close、error。原文只监听了open等于只等最后一声哨响中间走了几步完全看不见。临时加几个监听链路走到哪里一眼就清楚const mongoose require(mongoose); mongoose.connection.on(connecting, () console.log([state] connecting)); mongoose.connection.on(connected, () console.log([state] connected)); mongoose.connection.on(error, (err) console.log([state] error:, err.message)); mongoose.connection.on(disconnected, () console.log([state] disconnected));加上之后重新跑一次。如果连connecting都没打印说明问题在更外层如果打印了connecting却始终没有connected那就是网络层或服务层没通。1.2 原文那行 mongoose.disconnect() 是头号嫌疑原文注释写得没错——「断开数据库mongoose.disconnect() 一般不需要」。但示例代码里这行是真的执行了而且紧跟在connect后面没有await也没有塞进任何回调。Node 的事件循环里connect()只是把连接任务丢给驱动TCP 握手和后续命令都在异步阶段完成disconnect()却在同一个 tick 里立刻去关连接池。连接还没走到open就被自己掐断了事件自然永远等不到。处理办法有两个都很直接。第一种是先把这行删掉确认连接成功之后再决定要不要主动断开第二种是把它挪进open回调或者改用await的写法const mongoose require(mongoose); async function main() { await mongoose.connect(mongodb://127.0.0.1:27017/mongoose_test); console.log(数据库连接成功); // 需要主动断开时再调用 // await mongoose.disconnect(); } main().catch((err) console.error(连接失败, err.name, err.message));1.3 「没报错」和「有报错」的排查方向完全不同有报错的时候栈里直接写着ECONNREFUSED、Authentication failed或者MongooseServerSelectionError照着名字查就行。没有报错的时候你只能从「哪一层没返回」倒推。一个习惯值得养成把connect返回的 Promise 接住。mongoose.connect()返回的是 Promise不接住的话超时错误会以未处理拒绝的形式飘走看起来就像什么都没发生。mongoose.connect(uri, { serverSelectionTimeoutMS: 5000 }) .catch((err) console.error(connect 失败, err.name, err.message));把serverSelectionTimeoutMS从默认的 30 秒压到 5 秒是为了让排障节奏快一点。调试阶段没必要等半分钟才知道结果。注意这只是缩短等待不会让连接变得能通。2. 把 Codex 的 Base URL 指到 TaoToken 的兼容通道2.1 拿 Key 这一步在官网完成打开 TaoToken 官网 注册账号进控制台创建一把 API Key复制出来先放好。文中所有配置里它都写成占位符YOUR_API_KEY别把真实 Key 贴进任何代码仓库。Key 是给 Codex 用的不是给 mongoose 用的。mongoose 连的是本地 MongoDB两件事互不相干——这一点在排障时很重要否则很容易误以为是 Key 配错导致连接卡住。模型 ID 不要凭记忆写。打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 的模型广场看当时列表里有哪些可用项挑一个再填进配置。2.2 ~/.codex/config.toml 里写 model_provider 与 base_urlCodex 的配置文件在用户目录下的.codex/config.toml。要做的只有一件事加一个自定义 provider把base_url指向兼容通道把 Key 放在环境变量里。# ~/.codex/config.toml model YOUR_MODEL_ID model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEYbase_url末尾不要带/v1。写https://taotoken.net/api就够了多写一层会在拼接后变成重复路径。也不要往base_url后面挂任何查询参数UTM 只属于给人点的落地页不属于接口地址。环境变量按系统来设置# macOS / Linux export TAOTOKEN_API_KEYYOUR_API_KEY# Windows PowerShell $env:TAOTOKEN_API_KEYYOUR_API_KEY设完重开一个终端让变量生效再启动 Codex。如果这一步看到 401先回头确认环境变量名和env_key里写的名字是否完全一致大小写都算。2.3 让 Codex 先复述再动手排障场景下最容易出问题的不是 Codex 的能力而是它的热情。你不说清楚它可能顺手把index.js重写一遍把连接逻辑换成别的写法结果你连原来的问题都看不见了。第一条指令建议写成这样把index.js全文和package.json一起贴过去明确要求它只做分析不改业务逻辑。提示请先复述这段代码的执行顺序然后指出 mongoose.connect 到 connection.once(open) 之间可能被中断的位置。不要改写代码只列疑点和需要我在本地执行的检查命令。这句约束能让 Codex 把注意力放在连接时序上而不是急着给你一个新版本。它给出的每一条命令都由你在本机终端执行再把输出贴回对话。3. 让 Codex 逐项核对 mongoose.connect 的参数3.1 localhost 解析成 ::1 的坑连接串里写localhost很自然但在 Node 17 之后DNS 结果不再做 IPv4 优先的重排localhost有可能先解析到::1。而 MongoDB 服务端默认的bindIp是127.0.0.1也就是只听 IPv4。两边对不上就会出现连不上。如果本机对::1的入站是直接丢弃而不是明确拒绝表现就是「一直等」连ECONNREFUSED都看不到。这也是「pending 而不报错」最常见的成因之一。把连接串里的localhost换成127.0.0.1是最省事的一步。想更稳一点可以在选项里显式指定family: 4。3.2 把隐式默认值显式写出来原文连接串省掉了端口因为 27017 是默认值。省掉没问题但排障阶段建议把端口、库名、关键选项都写全让 Codex 一眼能看出你到底在连什么。const mongoose require(mongoose); const uri mongodb://127.0.0.1:27017/mongoose_test; mongoose.connect(uri, { family: 4, // 强制 IPv4绕开 ::1 解析 serverSelectionTimeoutMS: 5000, // 选服务器超时默认 30 秒 connectTimeoutMS: 5000, // 单次连接超时 directConnection: true, // 单机 mongod 场景 });这几个参数的含义可以整段丢给 Codex 让它用中文解释一遍serverSelectionTimeoutMS管的是「多久找不到可用节点就放弃」connectTimeoutMS管的是「单次 TCP 连接多久算超时」family管的是 DNS 解析出来的地址族。它解释完你再对照自己的实际部署情况判断哪些该留、哪些该删。3.3 事件注册顺序与 disconnect 的位置once(open, ...)写在connect()之后是没问题的因为握手是异步的注册监听的动作会先完成。真正容易出问题的是两处一是把监听写在await mongoose.connect()之后那时open早就发过了你等的是一个不会再来一次的事件二是前面说过的disconnect()抢跑。把这两点合并成一条指令给 Codex请检查我的监听注册位置是否晚于 open 事件触发以及有没有在当前 tick 内关闭连接池的调用。3.4 版本差异交给 Codex 对照mongoose 的版本跨度会带来行为差异。老教程里常见的useNewUrlParser、useUnifiedTopology在新版本里已经无效strictQuery在不同大版本里的默认值变过回调风格的 API 也被逐步移除。与其靠记忆不如把package.json里的 mongoose 版本号贴给 Codex让它在文档语义层面告诉你当前版本该怎么写。注意Codex 只能读你的代码和依赖版本它没有办法替你去连 MongoDB也没有办法替你看本机进程。它给出的判断需要你用本地命令去验证。4. 27017 端口与本地 MongoDB 服务命令 Codex 给执行在你机器上4.1 先看有没有进程在听 27017这一步判断的是「服务端到底有没有在那个端口上等客」。三套系统命令不同选自己那套。# Windows PowerShell Test-NetConnection -ComputerName 127.0.0.1 -Port 27017 netstat -ano | findstr :27017# macOS / Linux nc -vz 127.0.0.1 27017 lsof -iTCP:27017 -sTCP:LISTEN ss -lntp | grep 27017nc -vz成功时会显示 succeeded拒绝时显示 refused卡住不动则说明包被丢了。netstat和lsof看的是有没有进程处于 LISTEN 状态。三者结合起来端口这一层基本就定性了。4.2 再确认 mongod 服务本身起没起端口没人听通常就是服务没起。按安装方式选命令# macOSHomebrew 安装 brew services list | grep mongodb# Linuxsystemd systemctl status mongod# Windows 服务 Get-Service -Name MongoDB如果是容器跑的换一套docker ps --filter publish27017 docker port 容器名或ID容器场景有个常见疏漏docker run时没写-p 27017:27017容器内部服务起着宿主机上却没有任何端口在听Node 侧注定连不上。4.3 用 mongosh 做一次独立验证在怀疑 mongoose 之前先用官方客户端确认数据库本身可用mongosh mongodb://127.0.0.1:27017/mongoose_test --eval db.runCommand({ping:1})返回{ ok: 1 }就说明服务、端口、库名这条路是通的问题就落回 Node 侧的写法上如果 mongosh 也连不上就别在 mongoose 代码里绕了先把服务修好。把这几条命令的输出原样贴回 Codex 的对话里让它结合index.js给出下一步。它看到ECONNREFUSED会指向服务未启动看到超时会指向地址族或防火墙看到Authentication failed会转向凭据检查——这些判断都需要真实输出作为依据。5. 改完之后验证 open 到底来没来5.1 一份最小可运行的验证脚本把上面散落的改动合到一个文件里先跑通再谈业务const mongoose require(mongoose); const uri mongodb://127.0.0.1:27017/mongoose_test; async function main() { mongoose.connection.on(connecting, () console.log([state] connecting)); mongoose.connection.on(error, (err) console.error([state] error:, err.message)); try { await mongoose.connect(uri, { family: 4, serverSelectionTimeoutMS: 5000, connectTimeoutMS: 5000, directConnection: true, }); console.log(数据库连接成功readyState , mongoose.connection.readyState); const ping await mongoose.connection.db.admin().ping(); console.log(ping 结果, ping); } catch (err) { console.error(连接失败, err.name, err.message); } finally { await mongoose.disconnect(); } } main();跑之前确认一句这是在本机终端执行不是让 Codex 去连你的数据库。Codex 的职责到「生成这段脚本、解释每个参数」为止运行和看输出都在你这边。5.2 readyState 的 0/1/2/3 各自意味着什么mongoose.connection.readyState是个数字比你翻日志快得多值含义排障含义0disconnected从未开始连或已被断开1connected握手完成可以发命令2connecting正在握手pending 就是卡在这里3disconnecting正在关闭连接池打印出 1说明连接成功open事件也一定发过了。打印出 2 并且长时间不变说明卡在选服务器阶段回到第 4 节的端口和服务检查。打印出 0 且脚本立刻结束多半是disconnect()抢跑。5.3 还在 pending 时的对照表现象常见成因先查什么静默十几秒后报超时mongod 未启动或端口未监听进程与服务状态立刻报ECONNREFUSED ::1:27017localhost 解析到 IPv6连接串改 127.0.0.1加 family: 4报Authentication failed连接串缺账号或库级权限不足用 mongosh 验证凭据connected 出现过但 open 没触发监听注册太晚调整监听位置从来没有输出进程直接退出disconnect 提前关闭连接池删掉或后移 disconnect报MongooseServerSelectionError直连与副本集模式不匹配directConnection、replicaSet这张表可以整段交给 Codex让它对照你的实际报错挑一行出来解释。有真实报错时排查效率比空谈高得多。6. 这次排障的几次调用回到控制台对一下6.1 在用量里确认 Codex 确实走了这把 Key配通之后Codex 每读一次你的index.js、每解释一次参数都是一次真实的模型调用。回到 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 的用量页面看看这几轮对话有没有被记录下来。如果调用记录是空的说明 Codex 根本没走到这条通道回头检查config.toml里的base_url是不是被写成了带/v1的地址或者环境变量没生效。这类问题和 mongoose 的连接问题是两回事别混在一起排查。6.2 下一步这套流程跑顺之后可以把它变成固定动作本机服务状态用命令行确认代码层面的疑点交给 Codex 分析两边互不越界。想先试一下通道是否可用去 模型对话 用同一把 Key 发条消息长期要靠它读代码、查参数可以看 Coding Plan 的额度是否够用Key 本身在 控制台 API Keys 里创建和管理。回到index.js本身最该记住的还是那两件事连接串里写127.0.0.1而不是localhost以及别在同一个 tick 里disconnect。搞定这两点数据库连接成功那行字就会如约出现在终端里。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →