☰
eggjs+egg-mongoose操作mongodb数据库:TaoToken统一Key接入与本地联调配置
2026/10/3 6:37:55 网站建设 项目流程

1. eggjs 项目里 egg-mongoose 连不上 MongoDB 的真实场景

如果你正在写一个 eggjs 的接口服务,数据库选了 MongoDB,插件用的是 egg-mongoose,那么大概率会遇到这么几个瞬间:npm run dev起来了,但一调接口就报MongooseError: Operation buffering timed out,或者connect ECONNREFUSED 127.0.0.1:27017,再或者数据明明写进去了,find()却返回空数组。这些问题的根子往往不在 egg-mongoose 本身,而在配置的加载顺序、连接串写法、以及 model 注册时机上。

这篇内容聚焦的就是这个场景:一个 eggjs 项目,通过 egg-mongoose 操作 MongoDB,完成本地开发环境下的增删改查联调。同时我会把 TaoToken 的统一 Key 接入方式一起讲清楚——因为现在很多团队在做接口联调时,模型层(比如调用大模型做内容生成、字段补全)和数据库层是并行开发的,如果模型调用这块的 Key 管理混乱,联调阶段会非常痛苦。TaoToken 在这里扮演的角色是:给你一个统一的 API 通道和 Key,让 eggjs 服务在调用模型能力时不用到处散落不同厂商的密钥。

egg-mongoose 是什么?它是 eggjs 官方生态里的 MongoDB 插件,把 mongoose 的 Schema、Model 能力挂到 egg 的app.mongoose和ctx.model上,让你在 controller 和 service 里直接写ctx.model.XxxModel.find()。适合谁?适合已经用 eggjs 做后端、需要快速接入 MongoDB 的开发者,尤其是那种「接口要跑通、数据要落库、模型调用也要接上」的联调阶段。

我试过在一个 simple 模板的 egg 项目里从零配一遍,踩过的坑主要集中在三处:plugin.js 没开插件、config.default.js 里 mongoose 配置写成了对象而不是 client 结构、以及 model 文件名和mongoose.model()的第三个参数对不上导致查错集合。下面按可复制的顺序走一遍。

2. TaoToken 统一 Key 前置准备与 eggjs 环境说明

在动手写数据库配置之前,先把 TaoToken 这块的前置准备好。原因很简单:你的 eggjs 服务在联调阶段,很可能某个 service 里要调模型接口(比如给 collectdbs 里的记录自动生成摘要、或者做字段清洗),这时候如果 Key 是硬编码在代码里的,换环境就得改代码。TaoToken 提供的是统一 Key + 统一 API 通道,你只需要在配置里放一个 Key,指向统一的 Base URL,模型切换在控制台完成,代码不用动。

具体要准备的东西:

第一,一个 TaoToken 的 API Key。去控制台的 API Keys 页面创建,地址是https://taotoken.net/api-keys(带 utm 的完整链接是https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite)。创建后复制那串 Key,形如sk-开头的一长串,先存到本地环境变量里,别直接写进 git。

第二,确认你的调用入口。TaoToken 的 API 基础地址是https://taotoken.net/api,注意这个地址不加 UTM 参数,直接用于代码里的baseURL。模型对话的调试页面在https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite,你可以先在页面上选一个模型发一条消息,确认 Key 是通的,再去写代码。

第三,eggjs 项目环境。我用的是 egg 的 simple 脚手架,Node 版本建议 16 以上。项目结构里你会用到这几个文件:config/plugin.js、config/config.default.js、app/model/、app/service/、app/controller/、app/router.js。MongoDB 本地跑在127.0.0.1:27017,可视化工具用 MongoDB Compass 看数据,接口测试用 Postman 或 curl 都行。

这里要强调一个点:TaoToken 不是用来替代你的数据库的,它解决的是模型调用层的 Key 统一问题。数据库还是你自己的 MongoDB,egg-mongoose 还是照常连本地或你的 MongoDB 实例。两者是并行的两条线,只是在同一个 eggjs 服务里共存。很多同学一开始会混淆,以为接了 TaoToken 就不用配 MongoDB 了,不是的。

环境变量建议这样管理:在项目根目录建一个.env(记得加进.gitignore),里面放TAOTOKEN_API_KEY=sk-xxxx,然后在config.default.js里通过process.env.TAOTOKEN_API_KEY读取。这样本地开发、测试、生产可以用不同的 Key,代码零改动。

3. 可复制的 egg-mongoose 配置与 TaoToken 接入片段

这一节是核心,所有片段都可以直接复制。先装依赖:

npm i egg-mongoose mongoose lodash dayjs

注意 egg-mongoose 本身依赖 mongoose,但显式装一下版本更可控。装完后按顺序改配置。

3.1 config/plugin.js 开启插件

/** @type Egg.EggPlugin */ module.exports = { mongoose: { enable: true, package: 'egg-mongoose', }, };

这一步如果漏了,启动时app.mongoose是 undefined,后面所有 model 都会报错。这是最常见的第一个坑。

3.2 config/config.default.js 数据库与安全配置

const code = require('./code.js'); module.exports = appInfo => { const config = exports = {}; config.keys = appInfo.name + '_1700000000000_0000'; // egg-mongoose 连接配置,注意是 client 结构 config.mongoose = { client: { url: 'mongodb://127.0.0.1:27017/codemodel', options: { useUnifiedTopology: true, useNewUrlParser: true, }, }, }; // 关闭 csrf,方便 Postman/curl 直接调 config.security = { csrf: { enable: false, }, }; // 跨域配置 config.cors = { origin: '*', allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS', credentials: true, }; // 业务返回码 config.CODE = code; // TaoToken 统一 Key 配置 config.taotoken = { baseURL: 'https://taotoken.net/api', apiKey: process.env.TAOTOKEN_API_KEY || '', defaultModel: 'claude-3-5-sonnet', }; return config; };

这里config.mongoose.client.url的写法是关键。如果你写成config.mongoose = { url: '...' },egg-mongoose 在部分版本下不会报错但也不会连上,表现就是查询一直 buffering。client这一层不能省。

3.3 config/code.js 返回码

module.exports = { DEFAULT: { DEMO: 100001, }, };

3.4 app/extend/context.js 扩展方法

module.exports = { success(data) { this.body = { code: 0, message: 'success', data, }; }, error(code, message) { this.body = { code, message, }; }, };

3.5 app/model/collectdbs.js 定义 Model

module.exports = app => { const mongoose = app.mongoose; const Schema = mongoose.Schema; const CollectSchema = new Schema({ filePath: { type: String }, fileName: { type: String }, fileCreateAt: { type: String }, fileUrl: { type: String }, account: { type: String }, collectName: { type: String }, fileUpdateAt: { type: String }, }); return mongoose.model('Collectdbs', CollectSchema, 'collectdbs'); };

第三个参数'collectdbs'是集合名,必须和 MongoDB 里实际的集合名一致。如果你只写前两个参数,mongoose 会把Collectdbs自动转成collectdbs(复数小写),大多数情况没问题,但一旦你手动建了集合名不一致,就会查空。

3.6 app/service/collectdbs.js

const { Service } = require('egg'); const _ = require('lodash'); class CodeModelService extends Service { async list() { const { ctx } = this; return ctx.model.Collectdbs.find(); } async update(params) { const _params = _(params).omitBy(_.isUndefined).omitBy(_.isNull).value(); return this.ctx.model.Collectdbs.updateOne( { _id: params._id }, _params ); } async delete(params) { return this.ctx.model.Collectdbs.deleteOne(params); } async create(params) { return this.ctx.model.Collectdbs.insertMany(params); } } module.exports = CodeModelService;

3.7 app/controller/collectdbs.js

const { Controller } = require('egg'); const dayjs = require('dayjs'); class CollectdbsModel extends Controller { async list() { const { ctx, service, config } = this; try { const result = await service.collectdbs.list(); ctx.success(result); } catch (error) { ctx.error(config.CODE.DEFAULT.DEMO, error.message); } } async update() { const { ctx, service, config } = this; const _id = ctx.request.body?._id; const collectName = ctx.request.body?.collectName; const updateTime = dayjs().format('YYYY-MM-DD HH:mm:ss'); const params = { _id, collectName, fileUpdateAt: updateTime }; try { const result = await service.collectdbs.update(params); ctx.success(result); } catch (error) { ctx.error(config.CODE.DEFAULT.DEMO, error.message); } } async delete() { const { ctx, service, config } = this; const _id = ctx.request.body?._id; try { await service.collectdbs.delete({ _id }); ctx.success({}); } catch (error) { ctx.error(config.CODE.DEFAULT.DEMO, error.message); } } async create() { const { ctx, service, config } = this; const { collectName, fileName, account, fileUrl, filePath } = ctx.request.body; const updateTime = dayjs().format('YYYY-MM-DD HH:mm:ss'); const params = { collectName, fileName, account, fileUrl, filePath, fileCreateAt: updateTime, fileUpdateAt: updateTime, }; try { const result = await service.collectdbs.create(params); ctx.success(result); } catch (error) { ctx.error(config.CODE.DEFAULT.DEMO, error.message); } } } module.exports = CollectdbsModel;

3.8 app/router.js

module.exports = app => { const { router, controller } = app; router.get('/collectdbs/list', controller.collectdbs.list); router.post('/collectdbs/update', controller.collectdbs.update); router.post('/collectdbs/delete', controller.collectdbs.delete); router.post('/collectdbs/create', controller.collectdbs.create); };

3.9 TaoToken 调用封装(可选,用于模型层联调)

如果你要在 service 里调模型,建议单独封一个app/service/taotoken.js:

const { Service } = require('egg'); class TaotokenService extends Service { async chat(messages, model) { const { config } = this; const res = await this.ctx.curl(`${config.taotoken.baseURL}/v1/chat/completions`, { method: 'POST', contentType: 'json', dataType: 'json', headers: { Authorization: `Bearer ${config.taotoken.apiKey}`, }, data: { model: model || config.taotoken.defaultModel, messages, }, timeout: 60000, }); return res.data; } } module.exports = TaotokenService;

这样你的 eggjs 服务里,数据库走 egg-mongoose,模型走 TaoToken 统一通道,两条线互不干扰。

4. 启动项目与 curl 验证读写成功结果

配置写完后,启动:

npm run dev

看到egg started on http://127.0.0.1:7001就说明起来了。如果启动时报Cannot find module 'egg-mongoose',回去检查 plugin.js 和依赖安装。

先验证写入。用 curl 发一条 create:

curl -X POST http://127.0.0.1:7001/collectdbs/create \ -H "Content-Type: application/json" \ -d '{ "collectName": "测试收藏", "fileName": "demo.md", "account": "tester", "fileUrl": "https://example.com/demo.md", "filePath": "/data/demo.md" }'

预期返回:

{ "code": 0, "message": "success", "data": [ { "_id": "65efc1b63ecbf05aacf9378b", "collectName": "测试收藏", "fileName": "demo.md", "account": "tester", "fileUrl": "https://example.com/demo.md", "filePath": "/data/demo.md", "fileCreateAt": "2024-03-11 10:00:00", "fileUpdateAt": "2024-03-11 10:00:00", "__v": 0 } ] }

拿到_id后验证查询:

curl http://127.0.0.1:7001/collectdbs/list

应该返回包含刚才那条记录的数组。如果返回空数组,先去 MongoDB Compass 里看codemodel库的collectdbs集合里有没有数据。有数据但接口返回空,八成是 model 的集合名对不上。

验证更新:

curl -X POST http://127.0.0.1:7001/collectdbs/update \ -H "Content-Type: application/json" \ -d '{ "_id": "65efc1b63ecbf05aacf9378b", "collectName": "测试收藏-已改" }'

预期返回modifiedCount: 1。再去 list 一次,collectName应该变了,fileUpdateAt也更新成了当前时间。

验证删除:

curl -X POST http://127.0.0.1:7001/collectdbs/delete \ -H "Content-Type: application/json" \ -d '{"_id": "65efc1b63ecbf05aacf9378b"}'

预期返回deletedCount: 1。再 list 就查不到了。

如果你同时想验证 TaoToken 通道,可以在 Postman 里直接打https://taotoken.net/api/v1/chat/completions,Header 带Authorization: Bearer sk-你的Key,body 里放model和messages,返回正常就说明 Key 和通道没问题。这一步和数据库无关,但联调阶段两条线都要通。

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

联调阶段报错是常态,这里列几个真实会遇到的,对照着排。

401 Unauthorized。如果你在调 TaoToken 接口时看到 401,先检查 Header 里的Authorization是不是Bearer sk-xxx格式,中间有没有多余空格。再检查 Key 是不是从控制台复制完整了,有没有把前后引号也复制进去。还有一种情况是环境变量没生效,process.env.TAOTOKEN_API_KEY是 undefined,这时候请求头会变成Bearer undefined,也是 401。解决方式:在config.default.js里打印一下config.taotoken.apiKey的长度,确认不是 0。

local proxy failed。这个报错通常出现在你本地网络环境有代理设置,或者 egg 的 curl 请求走了系统代理。表现是请求发不出去,日志里出现local proxy failed或ECONNREFUSED。排查方向:检查你的 shell 里有没有http_proxy/https_proxy环境变量,有的话在启动 egg 前 unset 掉。另外 egg 的ctx.curl默认不走代理,但如果你在config.default.js里配了config.httpclient相关代理,要去掉。

reading choices。这个报错一般出现在你解析模型返回时,代码里写了res.data.choices[0],但实际返回结构不是这样,或者返回体是错误信息没有choices字段。排查:先把完整的res.data打印出来,看结构。如果是 TaoToken 返回的错误,通常会有error字段,先处理错误分支再取choices。另外注意有些模型返回的是流式,非流式请求才有完整choices。

OAuth 相关报错。如果你在 Claude Code 或某些 CLI 工具里配置 TaoToken 时看到 OAuth 报错,通常是因为工具默认走了 Anthropic 的 OAuth 流程,而你需要改成 API Key 模式。以 Claude Code 为例,需要设置ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY两个环境变量,Base URL 指向https://taotoken.net/api,Key 用你的 TaoToken Key。如果工具里同时存在 OAuth token 和 API Key,优先用 API Key,把 OAuth 相关的配置清掉。

MongooseError: Operation buffering timed out。这个和 TaoToken 无关,是 egg-mongoose 没连上 MongoDB。检查config.mongoose.client.url里的地址端口对不对,MongoDB 服务有没有起,以及client这一层有没有写。如果 MongoDB 在 Docker 里,注意127.0.0.1在容器内指向的是容器本身,要用宿主 IP 或容器网络别名。

查询返回空但 Compass 里有数据。九成是集合名不一致。mongoose.model('Collectdbs', schema, 'collectdbs')第三个参数必须和实际集合名完全一致,大小写敏感。去 Compass 里确认集合名,然后改 model 定义。

csrf 报错 403。如果你没关 csrf,Postman 发 POST 会返回 403。确认config.security.csrf.enable = false已经配上,并且重启了服务。

6. 后续联调与 TaoToken 接入入口

数据库这条线跑通之后,你的 eggjs 服务已经能正常增删改查了。接下来如果要在业务里接模型能力,比如给 collectdbs 的记录做自动分类、生成摘要、或者做字段补全,就可以用前面封装的taotokenservice。Key 的管理统一在 TaoToken 控制台,换模型不用改代码,只改config.taotoken.defaultModel或者调用时传参。

如果你还在选模型阶段,想先对比不同模型对同一段 prompt 的输出效果,可以直接用模型对话页面试:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite。选好模型后,把模型 ID 填到配置里就行。

如果你的项目进入长期编码阶段,或者要跑 Agent 类的任务,建议看一下 Coding Plan,地址是https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite,它更适合持续性的编码调用场景,比按次调用更划算。

接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite,里面有各语言和各工具的接入示例,包括 Claude Code 的配置方式。API Key 管理在https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite,控制台在https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite。

最后说一个实操细节:egg-mongoose 的 model 文件是懒加载的,app/model/下的文件在第一次ctx.model.Xxx访问时才注册。所以如果你在启动阶段就想用 model,得用app.model.Xxx而不是ctx.model。这个在写定时任务或者启动脚本时容易踩。另外insertMany返回的是数组,updateOne返回的是{ modifiedCount, matchedCount },前端拿数据时注意结构差异,别直接当对象用。

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

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

立即咨询