☰
Mongoose strictQuery 弃用警告怎么消?把 Node 项目配置改到 TaoToken 后的一次排查记录
2026/10/15 23:50:57 网站建设 项目流程

1. 从一条启动日志说起:strictQuery 弃用警告到底在提示什么

如果你最近在本地或 CI 里启动 Node 服务,终端大概率会先刷出这么一段:

(node:35806) [MONGOOSE] DeprecationWarning: Mongoose: the `strictQuery` option will be switched back to false by default in Mongoose 7. Use `mongoose.set('strictQuery', false);` if you want to prepare for this change. Or use `mongoose.set('strictQuery', true);` to suppress this warning. (Use `node --trace-deprecation ...` to show where the warning was created) Server running on port 5001

注意,它只是DeprecationWarning,不是Error。服务其实已经起来了,端口也监听了,但这条黄字会一直挂在日志最上面。对本地开发来说,它污染控制台;对 CI 来说,如果你们把 warning 当成失败条件,它直接让流水线红掉。

先把这个选项讲清楚。Mongoose 的strictQuery控制的是:当你查询里带了 Schema 中不存在的字段时,Mongoose 怎么处理。

  • strictQuery: true:查询条件里的未知字段会被静默剥离,相当于帮你过滤掉写错的字段名。
  • strictQuery: false:未知字段会被保留并传给 MongoDB,行为更接近原生驱动。

Mongoose 6 的默认值是true,Mongoose 7 改成了false。这条警告就是在提醒你:默认值要变了,你最好显式声明,别依赖隐式默认。所以消除警告的核心动作只有一个——在连接数据库之前,用mongoose.set('strictQuery', ...)明确表态。

这里有个关键点很多人会忽略:警告里给了两个选项,false和true,选哪个不是随便挑的,它取决于你现有代码的查询行为。选错了,警告是没了,但查询结果可能悄悄变了。这篇就按「定位 → 显式声明 → 验证 → 排障」的顺序,把这次排查完整走一遍,同时把项目配置统一收口到 TaoToken 的接入方式上,方便你在本地和 CI 用同一套环境变量。

适合谁看:正在维护 Express + Mongoose 项目的后端同学、被 CI 警告卡住的同学、以及想把数据库连接配置规范化的人。读完你能拿到一份可直接复制的连接片段,并且知道怎么用启动日志前后对比来确认警告真的清零了。

2. 定位警告来源与 TaoToken 接入前置准备

2.1 先确认警告是谁打印的

(node:35806)里的数字是进程 PID,每次启动都不一样,别拿它当线索。真正有用的是--trace-deprecation。在启动命令前加上它:

node --trace-deprecation server.js

这样终端会多打印一段调用栈,直接告诉你strictQuery的默认值是在哪一行被读取的。多数情况下栈顶会指向mongoose.connect()或mongoose.createConnection()那一行,也就是你的config/db.js。

我试过在一个老项目里跑这个命令,栈直接指到了config/db.js:8,就是await mongoose.connect(...)那行。定位成本几乎为零,比一行行翻代码快得多。

2.2 顺手排查另一个隐藏报错

原始日志里其实还藏着第二条信息:

The uri parameter to openUri() must be a string, got "undefined".

这条比strictQuery更严重,它意味着process.env.MONGO_URI是undefined。常见原因有三个:

第一,require('dotenv').config()没有放在文件最顶部,导致读取环境变量时.env还没加载。第二,.env文件不在进程的工作目录下,dotenv默认只找当前目录。第三,.env里变量名拼错了,比如写成了MONGO_URL。

处理顺序建议是:先修undefined,再消strictQuery警告。因为连接字符串都拿不到,后面的查询行为根本无从谈起。

2.3 为什么把配置收口到 TaoToken

本地开发一个.env、CI 里再配一套、换台机器又改一遍,是这类报错反复出现的根源。把模型调用和密钥管理统一走 TaoToken,好处是本地和 CI 用同一套 Base URL 和 Key 的读取逻辑,环境变量只区分值、不区分结构。

TaoToken 的接入信息如下,后面配置片段里会直接用到:

  • 官网入口:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
  • API 地址:https://taotoken.net/api
  • 模型对话页:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite
  • Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite
  • 控制台:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite
  • API Keys 管理:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite
  • 接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite

注意:TaoToken 负责的是模型调用侧的密钥与地址统一,MongoDB 连接串仍然由你自己的.env管理。两者是并列关系,不要混在同一个变量里。

2.4 目录结构先对齐

在动手改之前,确认你的目录长这样,后面所有路径都以此为准:

backend/ ├── .env ├── server.js ├── config/ │ └── db.js └── routes/ ├── authRoutes.js ├── bookRoutes.js └── reviewRoutes.js

.env放在backend/下,启动命令在backend/目录里执行。如果你在项目根目录启动,dotenv就找不到它,undefined报错会立刻复现。

3. 可复制配置:strictQuery 显式声明与连接片段

3.1 改造 config/db.js

这是本次排查的核心文件。把strictQuery的声明放在connect之前,并且显式传入连接参数:

// config/db.js const mongoose = require('mongoose'); // 关键:在 connect 之前显式声明,消除 DeprecationWarning // 选 false 还是 true 见 3.2 的判断 mongoose.set('strictQuery', false); const connectDB = async () => { const uri = process.env.MONGO_URI; // 提前拦截 undefined,避免 openUri() 报错 if (typeof uri !== 'string' || uri.length === 0) { console.error('[db] MONGO_URI is not a string, got:', typeof uri); process.exit(1); } try { await mongoose.connect(uri, { useNewUrlParser: true, useUnifiedTopology: true, serverSelectionTimeoutMS: 5000, }); console.log('MongoDB Connected...'); } catch (err) { console.error('[db] connect failed:', err.message); process.exit(1); } }; module.exports = connectDB;

三个改动点值得单独说:

第一,mongoose.set('strictQuery', false)放在模块顶层,而不是connectDB函数内部。因为set是全局配置,放函数里每次调用都执行一遍没必要,放顶层更清晰。

第二,typeof uri !== 'string'这个判断直接对应原始日志里的openUri()报错。与其等 Mongoose 抛错,不如自己先拦下来,报错信息还更明确。

第三,serverSelectionTimeoutMS: 5000让连接超时从默认的 30 秒缩到 5 秒。本地连不上时不用干等半分钟,CI 里也能更快失败。

3.2 strictQuery 到底选 true 还是 false

这是最容易踩坑的地方。警告文案说「用 false 准备迎接 Mongoose 7,或用 true 抑制警告」,但没告诉你选哪个取决于业务。

判断方法:去你的路由和 controller 里搜一下查询条件,看有没有「查询字段可能不在 Schema 里」的情况。

// 如果你的代码里有这种动态拼查询 const query = {}; if (req.query.author) query.author = req.query.author; if (req.query.tag) query.tag = req.query.tag; // tag 不在 Schema 里 await Book.find(query);

如果tag不在 Book Schema 里,strictQuery: true会把它剥掉,查询变成「只按 author 过滤」,结果比你预期的多;strictQuery: false会保留它,MongoDB 按tag过滤,结果为空。两种行为差异很大。

我的建议是:新项目直接跟 Mongoose 7 的默认值走,用false;老项目先跑一遍测试用例,确认查询结果不变再改。如果你不确定,就先用true抑制警告,把「迁移到 false」当成一个独立任务排期,别和消警告混在一起做。

3.3 .env 与 TaoToken 变量对照

.env里把两类配置分开写,避免混淆:

# backend/.env # --- 数据库 --- MONGO_URI=mongodb://localhost:27017/mydatabase PORT=5001 JWT_SECRET=your_secret_key # --- 模型调用(TaoToken)--- TAOTOKEN_BASE_URL=https://taotoken.net/api TAOTOKEN_API_KEY=sk-你的key TAOTOKEN_MODEL_ID=你的模型ID

用表格对照一下每个变量的作用,方便你排查时逐个核对:

变量名作用缺失后果
MONGO_URIMongoDB 连接串openUri() 报 undefined
PORT服务监听端口回退到 5001
TAOTOKEN_BASE_URL模型 API 根地址模型调用 404
TAOTOKEN_API_KEY调用凭证401 未授权
TAOTOKEN_MODEL_ID指定模型请求体缺 model 字段

提示:TAOTOKEN_API_KEY不要提交到 Git。在.gitignore里加上.env,CI 里用平台的 Secret 注入。

3.4 server.js 的加载顺序

dotenv必须在所有读取环境变量的模块之前加载。把require('dotenv').config()放在第一行:

// server.js require('dotenv').config(); // 必须第一行 const express = require('express'); const cors = require('cors'); const connectDB = require('./config/db'); const authRoutes = require('./routes/authRoutes'); const bookRoutes = require('./routes/bookRoutes'); const reviewRoutes = require('./routes/reviewRoutes'); const app = express(); connectDB(); app.use(cors()); app.use(express.json()); app.use('/api/auth', authRoutes); app.use('/api/books', bookRoutes); app.use('/api/reviews', reviewRoutes); const PORT = process.env.PORT || 5001; app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

顺序错了会怎样?如果connectDB的require在dotenv之前,config/db.js顶层虽然没读环境变量,但connectDB()执行时读的是process.env.MONGO_URI,此时dotenv已经加载完,所以这个例子里其实不会出错。真正会出错的是「在模块顶层直接读环境变量」的写法,比如const uri = process.env.MONGO_URI写在db.js顶层。养成dotenv第一行的习惯,能规避一整类顺序问题。

4. 验证请求:启动日志前后对比与查询行为确认

4.1 改之前的日志

先留一份基线,方便对比。改之前启动:

cd backend node server.js

输出:

(node:35806) [MONGOOSE] DeprecationWarning: Mongoose: the `strictQuery` option will be switched back to false by default in Mongoose 7... The uri parameter to openUri() must be a string, got "undefined". Server running on port 5001

两条问题都在:警告 + undefined。

4.2 改之后的日志

按第 3 节改完,重新启动:

node server.js

期望输出:

MongoDB Connected... Server running on port 5001

DeprecationWarning消失,openUri()报错消失,只剩两行正常日志。这就是「警告清零」的验收标准。

4.3 用 --trace-deprecation 二次确认

如果警告还在,别猜,直接上追踪:

node --trace-deprecation server.js 2>&1 | head -30

如果输出里完全没有[MONGOOSE]字样,说明警告确实清了。如果还有,栈会告诉你哪一行触发的,大概率是你有多个mongoose.connect调用点,只改了其中一个。

4.4 验证查询行为没变

警告清零只是第一步,还要确认strictQuery的取值没改变查询结果。写一个最小验证脚本:

// verify-strict.js require('dotenv').config(); const mongoose = require('mongoose'); mongoose.set('strictQuery', false); const Book = mongoose.model('Book', new mongoose.Schema({ title: String, author: String, })); (async () => { await mongoose.connect(process.env.MONGO_URI); // tag 不在 Schema 里,观察它是否被保留 const q = { author: '张三', tag: 'novel' }; const raw = Book.find(q).getFilter(); console.log('final filter:', JSON.stringify(raw)); await mongoose.disconnect(); })();

运行:

node verify-strict.js

strictQuery: false时输出{"author":"张三","tag":"novel"};改成true再跑,输出{"author":"张三"}。看到这个差异,你就知道自己选的取值到底做了什么。

4.5 CI 里怎么固化

在 CI 的启动步骤里加一个警告检查,防止以后有人改回去:

node server.js 2>&1 | tee /tmp/start.log & sleep 3 if grep -q "DeprecationWarning" /tmp/start.log; then echo "发现弃用警告,构建失败" exit 1 fi

这样警告一旦回归,流水线立刻红,比人工 review 靠谱。

5. 本篇常见错排查:401、local proxy failed 与 reading choices

5.1 警告还在,但代码明明改了

最常见的原因是存在多个连接入口。比如server.js里连了一次,某个脚本里又mongoose.connect了一次,后者没设strictQuery。用grep全局搜:

grep -rn "mongoose.connect\|createConnection" --include="*.js" .

把所有调用点列出来,逐个确认前面都有mongoose.set('strictQuery', ...)。注意createConnection创建的是独立连接,全局set不一定对它生效,需要单独处理。

5.2 401 未授权

如果你在接入 TaoToken 后遇到 401,先检查 Key 的读取链路:

node -e "require('dotenv').config(); console.log(process.env.TAOTOKEN_API_KEY ? 'key loaded' : 'key missing')"

输出key missing说明.env没加载或变量名拼错。输出key loaded但请求仍 401,检查请求头是不是Authorization: Bearer <key>,以及 Key 是否在控制台被禁用。API Keys 管理页在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite ,可以对照确认。

5.3 local proxy failed

这个报错通常出现在你本地配了代理、但代理进程没起来的时候。表现是请求直接失败,日志里带local proxy failed或连接被拒。处理方式:检查你的 HTTP_PROXY / HTTPS_PROXY 环境变量是否指向了一个不存在的端口,把它清掉再试:

unset HTTP_PROXY HTTPS_PROXY node server.js

如果你根本没配代理却看到这个错,检查是不是某个 SDK 默认读了系统代理设置。

5.4 reading 'choices' 报错

Cannot read properties of undefined (reading 'choices')是模型调用里非常典型的错误,意思是响应体结构和预期不符,choices字段不存在。原因通常是:

第一,Base URL 写错了,请求打到了非预期地址,返回的是 HTML 或错误 JSON。确认TAOTOKEN_BASE_URL是https://taotoken.net/api,不要多写或少写路径段。第二,请求体里缺model字段,服务端返回错误对象。第三,把流式响应当非流式解析了。

排查时先把原始响应打出来:

const res = await fetch(`${process.env.TAOTOKEN_BASE_URL}/v1/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.TAOTOKEN_API_KEY}`, }, body: JSON.stringify({ model: process.env.TAOTOKEN_MODEL_ID, messages: [{ role: 'user', content: 'ping' }], }), }); const text = await res.text(); console.log('status:', res.status); console.log('raw:', text.slice(0, 300));

看到原始返回,问题基本一目了然。

5.5 OAuth 相关报错

如果你用的是带 OAuth 流程的客户端(比如某些 CLI 工具),报错里出现OAuth字样,通常是 token 过期或回调地址不匹配。这类问题优先看工具的配置文件,确认 Base URL、Key、Model ID 三件套是否齐全。以 Codex 的auth.json为例,三个字段缺一不可:

{ "base_url": "https://taotoken.net/api", "api_key": "sk-你的key", "model": "你的模型ID" }

Cline 的 MCP 配置同理,baseUrl、apiKey、model三个键都要有。CC Switch 这类切换工具也是同一套逻辑。少任何一个,都会在启动或首次请求时报错。

5.6 报错对照速查

报错关键词大概率原因处理动作
DeprecationWarning strictQuery未显式 set连接前加 mongoose.set
openUri() got undefinedMONGO_URI 未加载dotenv 放首行、查 .env 路径
401Key 缺失或错误检查 Authorization 头
local proxy failed代理变量指向无效端口unset 代理变量
reading 'choices'Base URL 或 model 错误打印原始响应核对
OAuthtoken 过期/配置不全补全三件套

6. 把配置固定下来:本地与 CI 一套逻辑

排查完别急着关终端,把这次的结论固化到项目里,下次换机器或新人入职就不用再踩一遍。

第一步,在config/db.js顶部保留mongoose.set('strictQuery', false),并加一行注释说明为什么选 false。注释比代码更重要,半年后你自己也会忘。

第二步,把.env.example提交到仓库,里面只放变量名和占位值,不放真实 Key:

MONGO_URI=mongodb://localhost:27017/yourdb PORT=5001 JWT_SECRET=change_me TAOTOKEN_BASE_URL=https://taotoken.net/api TAOTOKEN_API_KEY=sk-replace_me TAOTOKEN_MODEL_ID=replace_me

第三步,在 CI 的 Secret 里配置TAOTOKEN_API_KEY,启动脚本里加上第 4.5 节的警告检查。这样本地和 CI 走的是同一套变量名、同一套加载顺序,undefined类报错基本绝迹。

第四步,如果你还在用useNewUrlParser和useUnifiedTopology,注意这两个选项在新版驱动里已经默认开启,传了不报错但也没作用。可以保留,也可以删掉,不影响本次警告的消除。

最后留一个实用技巧:把启动命令写进package.json的 scripts,加上--trace-deprecation作为可选的调试入口:

{ "scripts": { "start": "node server.js", "start:trace": "node --trace-deprecation server.js" } }

平时用npm start,遇到警告排查用npm run start:trace。这样既不影响正常启动日志的干净度,又保留了随时深挖的能力。整套流程走下来,strictQuery警告清零、openUri报错消失、查询行为可验证,本地和 CI 的配置也统一了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询