1. 从一次「Key 到处贴」的翻车说起
Node.js 基础第 7 篇,我们做的是连接 mongo 数据库并上传文件:用 express-generator 起项目,mongoose 连库,formidable 收文件,bcryptjs 加密密码,最后把用户信息和头像地址写进 users 集合。这套流程本身不复杂,真正让人头疼的是另一件事——当你在同一个项目里同时用 Cline、Claude Code、CC Switch 这类工具时,每个工具都要填一遍 API Key、Base URL、模型名,改一次配置要翻四五个文件,改漏一个就报 401。
我试过把 Key 写死在.env里,结果 Cline 读的是 VS Code 的 settings.json,Claude Code 读的是~/.claude/settings.json,CC Switch 又有自己的 config.toml,三份配置各说各话。后来我把它们统一收敛到 TaoToken 的同一个 Key 和同一个 API 通道上,配置只维护一份,工具之间切换不再互相打架。这篇就把 mongo 连接、文件上传的完整代码,和这套统一 Key 的配置骨架一起交付给你,适合正在学 Node.js 全栈、同时又在用 AI 编码工具的同学。
核心检索词先摆出来:Node.js 连接 mongo 数据库、上传文件、TaoToken 统一 Key 配置。读完你能拿到可复制的 settings.json 与 config.toml 骨架、CC Switch 和 Cline 的接入步骤,以及连接 mongo 与文件上传的验证动作。
2. TaoToken 前置:一个 Key 管住所有工具
TaoToken 在这里扮演的角色是「统一入口」:你只在它这里拿一次 API Key,之后 Cline、Claude Code、CC Switch 都指向同一个 Base URL 和同一个 Key。官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址固定为 https://taotoken.net/api ,注意 API 地址后面不加任何 UTM 参数,填错会导致请求 404。
为什么要在写 mongo 项目时先配这个?因为你在写routes/users.js的时候,大概率会让 AI 帮你补全 formidable 的form.parse回调、bcrypt 的加密强度、mongoose 的findById写法。如果工具 Key 散落,你会在「代码写一半 → 工具报鉴权失败 → 去翻配置」之间反复横跳。统一 Key 之后,工具层稳定,你才能专注在业务代码上。
拿 Key 的路径:进入控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,在 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 创建一个 Key,复制出来先存到密码管理器。这个 Key 后面会同时出现在 settings.json 和 config.toml 里,所以命名建议带项目前缀,比如node-mongo-demo,方便你日后区分。
注意:Key 只创建一次就够,不要每个工具建一个。多 Key 反而会让「哪个工具用了哪个额度」变得不可追踪。
3. 可复制配置:settings.json 与 config.toml 骨架
先给 Cline(VS Code 插件)用的 settings.json 骨架。路径通常在 VS Code 的用户设置里,或者项目级.vscode/settings.json。关键是apiProvider选 openai 兼容模式,baseUrl指向 TaoToken 的 API 地址,apiKey填你刚创建的那把。
{ "cline.apiProvider": "openai", "cline.openAiBaseUrl": "https://taotoken.net/api", "cline.openAiApiKey": "sk-你的TaoTokenKey", "cline.openAiModelId": "claude-sonnet-4-5", "cline.openAiModelInfo": { "maxTokens": 8192, "contextWindow": 200000 } }再给 Claude Code / CC Switch 用的 config.toml 骨架。CC Switch 的作用是帮你在多个 API 通道之间切换,配置里同样只保留一份 TaoToken 通道:
[[providers]] name = "taotoken" base_url = "https://taotoken.net/api" api_key = "sk-你的TaoTokenKey" model = "claude-sonnet-4-5" [settings] default_provider = "taotoken" timeout_seconds = 120如果你用的是 Claude Code 原生命令行,环境变量方式更直接:
export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_API_KEY="sk-你的TaoTokenKey"这三份配置的共同点是:Base URL 完全一致,Key 完全一致,只有「工具怎么读」不同。改 Key 的时候三处一起改,或者干脆用环境变量注入,避免硬编码。CC Switch 的接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,里面有各工具的字段对照表,填之前对一眼能省很多试错。
4. 项目骨架:express-generator + mongoose + formidable
配置搞定,回到 Node.js 本体。先建项目:
mkdir mondo-express && cd mondo-express npx express-generator npm i npm i mongoose formidable bcryptjs npm i -D nodemon然后把package.json的启动脚本改成 nodemon,省得每次手动重启:
"scripts": { "start": "nodemon ./bin/www" }4.1 model/index.js 连接 mongo
在model目录下建index.js,定义 Schema 并连接数据库。注意 mongoose 7 之后connect返回 Promise,建议加.catch兜底:
const mongoose = require("mongoose"); mongoose .connect("mongodb://127.0.0.1:27017/runoob") .then(() => console.log("mongo connected")) .catch((err) => console.error("mongo error:", err.message)); const userSchema = new mongoose.Schema({ username: String, password: String, gender: String, tel: String, avatarUrl: String, }); module.exports = mongoose.model("runoob", userSchema, "users");这里用127.0.0.1而不是localhost,是因为部分 Node 版本下 localhost 会先解析 IPv6 的::1,而 mongo 默认只监听 IPv4,导致连接超时。这个坑我在本地环境踩过,换成 127.0.0.1 立刻通。
4.2 app.js 挂载连接与路由
在app.js顶部引入 model(引入即触发连接),并挂载 users 路由:
const createError = require("http-errors"); const express = require("express"); const path = require("path"); const cookieParser = require("cookie-parser"); const logger = require("morgan"); require("./model/index"); // 引入即连接 mongo const indexRouter = require("./routes/index"); const usersRouter = require("./routes/users"); const app = express(); app.set("views", path.join(__dirname, "views")); app.set("view engine", "jade"); app.use(logger("dev")); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, "public"))); app.use("/static", express.static(path.join(__dirname, "static"))); app.use("/", indexRouter); app.use("/users", usersRouter); app.use(function (req, res, next) { next(createError(404)); }); app.use(function (err, req, res, next) { res.locals.message = err.message; res.locals.error = req.app.get("env") === "development" ? err : {}; res.status(err.status || 500); res.render("error"); }); module.exports = app;多了一行app.use("/static", ...),是为了让上传后的头像能通过/static/upload/xxx.png访问到,否则前端拿到 avatarUrl 也显示不出来。
4.3 routes/users.js 增删改查与文件上传
这是核心文件,遵循 REST 风格:GET 查、POST 增、PUT 改、DELETE 删。上传用 formidable,密码用 bcryptjs 加密。
const express = require("express"); const path = require("path"); const formidable = require("formidable"); const bcrypt = require("bcryptjs"); const db = require("../model/index"); const router = express.Router(); // 查找:有 _id 查单条,没有查全部 router.get("/:_id?", (req, res) => { const { _id } = req.params; if (_id) { db.findById(_id).then((data) => { res.json({ code: 200, status: 1, data }); }); } else { db.find().then((data) => { res.json({ code: 200, status: 0, data }); }); } }); // 增加:上传头像 + 密码加密 router.post("/api/upload", (req, res, next) => { const form = formidable({ multiples: true, uploadDir: path.join(__dirname, "../static/upload"), keepExtensions: true, }); form.parse(req, (err, fields, files) => { if (err) { next(err); return; } const { name, password, gender, tel } = fields; const avatar = Array.isArray(files.avatar) ? files.avatar[0] : files.avatar; const avatarUrl = "/upload/" + avatar.newFilename; if (!name || !password) { res.json({ code: 200, status: 1, message: "用户名或密码不能为空" }); return; } db.findOne({ username: name }).then((exist) => { if (exist) { res.json({ code: 200, status: 1, message: "用户名已存在" }); return; } const hash = bcrypt.hashSync(password, 10); new db({ username: name, password: hash, gender, tel, avatarUrl }) .save() .then((data) => { res.json({ code: 200, status: 0, message: data }); }); }); }); }); // 修改 router.put("/:_id", (req, res) => { const { _id } = req.params; const { username, password, gender, tel, avatarUrl } = req.body; db.updateOne({ _id }, { username, password, gender, tel, avatarUrl }).then( () => { res.json({ code: 200, status: 0, message: "修改成功" }); } ); }); // 删除 router.delete("/:_id", (req, res) => { const { _id } = req.params; db.deleteOne({ _id }).then(() => { res.send({ code: 200, status: 0, message: "删除成功" }); }); }); module.exports = router;formidable 3.x 里files.avatar可能是数组也可能是对象,取决于multiples配置,所以上面加了一行兼容判断。另外newFilename是 formidable 3 的新字段,旧教程里的path已经废弃,照抄老代码会拿到 undefined。
4.4 上传目录与前端页面
建目录:
mkdir -p static/upload在public/index.html放一个表单,注意enctype必须是 multipart:
<form action="/users/api/upload" method="post" enctype="multipart/form-data"> <input type="text" name="name" placeholder="用户名" /> <input type="password" name="password" placeholder="密码" /> <input type="text" name="gender" placeholder="性别" /> <input type="text" name="tel" placeholder="电话" /> <input type="file" name="avatar" /> <button type="submit">提交</button> </form>5. 验证请求:从 curl 到浏览器
启动服务:
npm start看到mongo connected说明数据库连上了。先验证查询接口:
curl http://127.0.0.1:3000/users返回{"code":200,"status":0,"data":[]}表示路由和数据库都通。再验证上传:
curl -X POST http://127.0.0.1:3000/users/api/upload \ -F "name=testuser" \ -F "password=123456" \ -F "gender=male" \ -F "tel=13800000000" \ -F "avatar=@./test.png"成功会返回带_id的文档,avatarUrl形如/upload/xxxx.png。浏览器打开http://127.0.0.1:3000/static/upload/xxxx.png能显示图片,说明静态目录挂载正确。最后再查一次/users,能看到新用户且 password 是 bcrypt 哈希串,就全链路通了。
如果你在验证阶段让 AI 工具帮你读报错,记得工具已经指向 TaoToken 统一通道,直接问就行,不用再切配置。模型对话入口在 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,把 curl 的报错贴进去让它分析,比来回翻文档快。
6. 本篇常见错排查
报错一:MongooseServerSelectionError: connect ECONNREFUSED ::1:27017原因就是前面说的 IPv6 解析问题。把连接串里的localhost换成127.0.0.1,或者启动 mongo 时显式绑定 IPv4。
报错二:formidable拿到的files.avatar是 undefined检查前端表单有没有写enctype="multipart/form-data",以及name属性是不是avatar。formidable 靠字段名匹配,名字对不上就拿不到。
报错三:上传成功但图片 404uploadDir指向static/upload,但app.js里没挂/static静态目录,或者 avatarUrl 拼的是/upload/而实际访问路径是/static/upload/。两处路径要对齐。
报错四:AI 工具报 401 或 404401 通常是 Key 填错或带了多余空格;404 多半是 Base URL 写成了https://taotoken.net/api/带尾斜杠,或者误加了 UTM 参数。API 地址就是https://taotoken.net/api,干净利落。接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 里有各工具的字段校验清单,对不上时逐项核。
报错五:bcrypt.hashSync卡住或报错bcryptjs 是纯 JS 实现,不需要编译,但如果误装了bcrypt(原生模块)在部分环境会编译失败。确认package.json里是bcryptjs。
7. 配置收敛之后,把精力留给业务
mongo 连接和文件上传这套代码,写一遍就熟了,真正消耗时间的是环境配置的反复折腾。把 Cline、Claude Code、CC Switch 全部指向 TaoToken 的同一个 Key 和同一个 API 通道之后,你改一次配置就能全局生效,写routes/users.js时让工具补全 formidable 回调、bcrypt 强度、mongoose 查询链,都不会再被鉴权打断。
如果你后面要长期在这个项目上做编码和 Agent 任务,可以看下 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,它更适合持续性的代码生成场景;只是偶尔验证模型效果,用模型对话页就够了。配置骨架已经给你了,接下来把static/upload目录建好,跑一遍 curl,看到那条带_id的返回,这篇的任务就完成了。