
1. Express 项目里密码明文存储的坑与 bcrypt 加密落地场景如果你正在写一个 Express MongoDB 的注册登录接口大概率会遇到这样一个问题用户表里的 password 字段直接存了123456这种明文。本地跑着玩没事一旦部署上线数据库被人拖走所有用户的密码就全裸了。更麻烦的是很多教程里数据库连接串是写死在app.js里的账号密码跟着代码一起提交到 Git等于把钥匙插在门上。这篇就围绕一个真实的小项目场景展开一个 Express 后端需要做用户注册登录密码要用 bcrypt 做哈希数据库用 mongoose 连接连接账号密码不能写死还要能根据 NODE_ENV 自动区分开发和生产环境。我会把 config 目录结构、bcrypt 加盐哈希代码、mongoose 连接封装、环境变量分支配置全部给出来你照着复制就能跑。先说清楚几个概念方便小白理解。bcrypt 是一种单程哈希算法只能加密不能解密也就是说数据库里存的是哈希值就算泄露了也没法直接还原出原始密码。它和 MD5 最大的区别是自带“盐”salt每次加密同一个密码得到的哈希值都不一样这样攻击者就没法用彩虹表批量比对。mongoose 是 Node.js 里操作 MongoDB 的 ODM 库它把数据库连接、Schema 定义、模型操作都封装好了。NODE_ENV 是 Node.js 的一个约定俗成的环境变量值为development表示开发环境production表示生产环境很多库会根据它切换行为比如日志详细程度、错误堆栈是否返回给客户端。适合谁看如果你已经能跑起一个最简单的 Express 服务知道npm install和app.listen但还没系统处理过密码安全和配置管理那这篇正好补上这块。如果你已经用过 bcrypt 但配置还是写死的也可以看看 config 模块那部分怎么把敏感信息抽离出去。我试过把数据库密码直接写在代码里然后推到 GitHub结果收到邮件提醒说检测到密钥泄露从那以后所有项目都改成环境变量加 config 模块管理。下面按步骤来先装依赖再写加密再连数据库最后做环境判断。2. TaoToken 前置准备模型接入与 API Key 获取在正式写代码之前先解决一个容易被忽略的前置问题如果你这个 Express 项目里还要调用大模型能力比如做智能客服、内容审核或者代码辅助那模型接入的配置同样不能写死。这里我用 TaoToken 来做统一接入它的好处是一个 API Key 可以调多种模型Base URL 固定省得每个模型改一遍代码。TaoToken 是什么简单说它是一个模型接入网关你拿到一个 API Key 之后通过统一的 Base URL 就能请求不同厂商的模型。适合谁适合不想在项目里维护一堆厂商 SDK 和密钥的开发者尤其是做 Agent、Coding 辅助这类需要频繁切换模型的场景。获取 Key 的步骤不复杂。先打开官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册登录然后进控制台。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 在里面找到 API Keys 页面新建一个 Key 并复制保存。API Keys 页面地址是 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。注意 Key 只显示一次复制后存到你的密码管理器或者本地.env文件里别直接写进代码。API 的基础地址是 https://taotoken.net/api 这个地址不带 UTM 参数直接用于代码里的 baseURL。如果你只是想先在网页上试试模型效果可以用模型对话页面 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 。如果你打算长期做编码类项目比如用 Claude Code 或者自己写 Agent可以了解下 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 它针对编码场景有专门的额度方案。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有各语言的调用示例。这里要强调一点TaoToken 的 Key 和你的 MongoDB 密码一样都属于敏感信息绝对不能写死在代码里。下面第 3 节我会把这两类敏感信息统一用 config 模块加环境变量管理这样开发环境和生产环境可以各用各的 Key互不干扰。3. 可复制配置config 目录结构、bcrypt 哈希与 mongoose 连接封装这一节是核心所有代码都可以直接复制。先建目录结构在项目根目录下新建config文件夹里面放四个文件config/ default.json development.json production.json custom-environment-variables.jsondefault.json放所有环境共用的配置development.json放开发环境特有的production.json放生产环境特有的。config 模块会自动根据NODE_ENV去读对应的文件如果某个属性在 development 里没有会回退到 default 里找。先看default.json{ title: Express Demo, db: { host: localhost, port: 27017, name: express_demo } }再看development.json开发环境数据库账号密码用本地的{ db: { user: dev_user, pwd: dev_pass_123 }, morgan: dev }production.json里账号密码留空因为生产环境要从系统环境变量读{ db: { user: , pwd: }, morgan: combined }关键在custom-environment-variables.json它把配置项映射到系统环境变量名{ db: { user: DB_USER, pwd: DB_PWD }, taotoken: { apiKey: TAOTOKEN_API_KEY } }这样项目运行时config 模块会去读DB_USER、DB_PWD、TAOTOKEN_API_KEY这几个系统环境变量把值填进配置里。生产环境部署时只要在服务器上设置这几个环境变量就行代码一行不用改。接下来装依赖npm install bcrypt mongoose config morganbcrypt 在 Windows 上可能需要编译工具如果安装报错先装 node-gyp 和 windows-build-toolsnpm install -g node-gyp npm install --global --production windows-build-toolsmacOS 或 Linux 一般直接装就行。装完后写密码加密工具函数新建utils/password.jsconst bcrypt require(bcrypt); // 生成盐并哈希密码 async function hashPassword(plainPassword) { const salt await bcrypt.genSalt(10); const hashed await bcrypt.hash(plainPassword, salt); return hashed; } // 比对密码 async function verifyPassword(plainPassword, hashedPassword) { return await bcrypt.compare(plainPassword, hashedPassword); } module.exports { hashPassword, verifyPassword };genSalt(10)里的 10 是盐的轮数数字越大越安全但越慢10 是常用平衡值。bcrypt.hash把明文密码和盐一起哈希返回的字符串里已经包含了盐所以数据库只需要存这一个字段。bcrypt.compare会自动从哈希串里提取盐再比对你不需要手动管理盐。然后写 mongoose 连接封装新建db/connect.jsconst mongoose require(mongoose); const config require(config); async function connectDB() { const { user, pwd, host, port, name } config.get(db); const uri mongodb://${user}:${pwd}${host}:${port}/${name}; try { await mongoose.connect(uri, { useUnifiedTopology: true, useNewUrlParser: true }); console.log(数据库连接成功); } catch (err) { console.log(数据库连接失败, err.message); process.exit(1); } } module.exports connectDB;注意这里用的是config.get(db)解构而不是把连接串写死。开发环境会读到development.json里的dev_user和dev_pass_123生产环境会从系统环境变量读。最后在app.js里根据 NODE_ENV 做分支const express require(express); const config require(config); const morgan require(morgan); const connectDB require(./db/connect); const app express(); if (process.env.NODE_ENV development) { console.log(当前是开发环境); app.use(morgan(dev)); } else { console.log(当前是生产环境); app.use(morgan(combined)); } connectDB(); app.listen(3000, () { console.log(服务启动在 3000 端口); });morgan(dev)会把请求信息用彩色简洁格式打到控制台方便开发调试morgan(combined)是标准 Apache 格式适合生产环境日志收集。4. 验证请求本地切换 NODE_ENV 测试连接与加密结果代码写完了得验证它真的按预期工作。分三步验证 bcrypt 加密结果、验证 mongoose 连接、验证 NODE_ENV 切换。先写一个临时脚本test-password.js验证加密const { hashPassword, verifyPassword } require(./utils/password); (async () { const plain mySecret123; const hashed await hashPassword(plain); console.log(哈希结果:, hashed); const ok await verifyPassword(plain, hashed); console.log(正确密码比对:, ok); const bad await verifyPassword(wrongPass, hashed); console.log(错误密码比对:, bad); })();运行node test-password.js你会看到类似$2b$10$...开头的哈希串正确密码返回true错误密码返回false。每次运行哈希结果都不一样但比对都能通过这就是盐在起作用。再验证数据库连接。确保本地 MongoDB 已启动并且创建了dev_user用户。然后设置环境变量并启动# macOS / Linux export NODE_ENVdevelopment node app.js # Windows CMD set NODE_ENVdevelopment node app.js # Windows PowerShell $env:NODE_ENVdevelopment node app.js启动后控制台应该输出当前是开发环境和数据库连接成功。如果连接失败检查 MongoDB 是否运行、用户名密码是否正确、端口是否被占用。再切到生产环境测试export NODE_ENVproduction export DB_USERprod_user export DB_PWDprod_pass_456 node app.js这时控制台输出当前是生产环境config 模块会从系统环境变量读DB_USER和DB_PWD而不是从production.json里读空值。如果你没设置这两个环境变量连接会失败这正好验证了敏感信息确实没有写死在代码里。验证 TaoToken 接入是否正常可以写个小脚本const config require(config); async function testTaoToken() { const apiKey config.get(taotoken.apiKey); const res await fetch(https://taotoken.net/api/v1/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${apiKey} }, body: JSON.stringify({ model: gpt-4o-mini, messages: [{ role: user, content: 你好 }] }) }); const data await res.json(); console.log(data.choices[0].message.content); } testTaoToken();运行前确保TAOTOKEN_API_KEY环境变量已设置。如果返回正常内容说明 Key 和 Base URL 都配对了。5. 本篇常见错排查401、local proxy failed、reading choices 与 OAuth 报错这一节把实际开发中最容易撞到的几个报错列出来对照着排查。401 Unauthorized调用 TaoToken API 时返回 401九成是 API Key 没传对。检查Authorization头是不是Bearer加 Key注意 Bearer 后面有个空格。另外确认 Key 没有多余换行或空格从控制台复制时容易带上。如果 Key 是在custom-environment-variables.json里映射的确认系统环境变量名拼写一致大小写敏感。local proxy failed这个报错通常出现在你本地网络环境有代理设置但 Node.js 请求没走对通道。检查你的终端有没有设置HTTP_PROXY或HTTPS_PROXY环境变量如果有但代理不可用请求就会失败。解决办法是临时取消这些环境变量或者确认代理配置正确。注意这里说的是本地开发环境的网络配置问题不是让你去搞什么特殊网络工具就是检查环境变量而已。Cannot read properties of undefined (reading choices)这个报错说明你拿到的响应里没有choices字段通常是请求失败了但你没检查状态码就直接读data.choices。改成先判断res.ok失败时打印data.errorif (!res.ok) { console.error(请求失败:, res.status, data.error); return; } console.log(data.choices[0].message.content);OAuth 相关报错如果你用 Claude Code 或者某些 CLI 工具接入可能会遇到 OAuth token 过期或未授权的提示。这类工具通常有自己的认证流程检查你的配置文件里 Base URL 和 Key 是否填对。以 Claude Code 为例需要配置ANTHROPIC_BASE_URL和ANTHROPIC_API_KEYBase URL 填https://taotoken.net/apiKey 填你申请的 Key。如果用的是 Codex 的auth.json确认里面的apiKey和baseURL字段都正确。Cline MCP 场景下检查 MCP server 配置里的baseUrl、apiKey、model三件套是否齐全缺一个都会报错。mongoose 连接超时如果数据库连接失败后面跟着buffering timed out说明 mongoose 在等连接但一直没连上。检查 MongoDB 服务是否启动、连接串里的 host 和 port 是否正确、用户名密码是否匹配。开发环境用development.json里的账号生产环境用环境变量别搞混了。bcrypt 安装失败Windows 上如果报node-gyp相关错误按第 3 节装windows-build-tools。如果还是不行可以换bcryptjs它是纯 JS 实现不需要编译API 完全兼容只是性能稍慢。6. 语义一致 CTA把配置管理用到你的下一个项目里到这里bcrypt 加密、mongoose 连接、config 配置管理、NODE_ENV 环境判断这四件事已经串起来了。核心思路就一句话敏感信息不写死环境差异用配置文件隔离代码里只通过config.get读取。如果你在排障过程中遇到 API 接入问题比如 401 或者模型调用失败可以去 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 重新生成 Key或者翻一下接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里的示例。想先验证模型能不能通用模型对话页面 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 发一条消息最快。如果你打算长期在编码项目里用模型能力Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 的额度方案可以了解下。最后给一个实用建议把config目录加入.gitignore里的排除项要谨慎因为default.json和custom-environment-variables.json通常需要提交而development.json和production.json如果包含真实密码就不该提交。我的做法是development.json里放本地测试用的假密码真实密码全部走环境变量这样即使仓库公开也不怕。另外.env文件记得加进.gitignore别问我是怎么知道的。