首页 > 其他分享 >详解 dotenv 的使用与实现

详解 dotenv 的使用与实现

时间:2024-08-27 19:26:03浏览次数:7  
标签:文件 const 实现 详解 env dotenv 加密 options

每当涉及到保护API密钥或我们不想因为开源项目而向公众展示的东西时,我们总是倾向于.env文件,而它的解析依赖到dotenv包,一个每周都有31k+开发人员下载的软件包。其设计的理念是Twelve-Factor App的第三点。配置与代码分离。

关于Twelve-Factor App大家可以前往这里查看:https://12factor.net/

为什么文件名只有.env?

文件名只能以.env开头,是一个误解。使用任何名字,它仍然可以在node.js上正常工作。

那为什么要用点开头?

当涉及到环境文件时,在文件名前面使用一个点(.)被认为是好的,因为在任何文件名前面添加一个点都会使其成为一个隐藏的文件或文件夹。

这就是为什么您的操作系统中有多个文件夹,这些文件夹是隐藏的,只能通过CLI访问,例如.ssh、.github、.vscode等。

使用介绍

首先,在你的 Node.js 项目中安装 dotenv

npm install dotenv

在项目的根目录中创建一个名为 .env 的文件。这个文件中每一行定义一个环境变量,格式为 KEY=VALUE。例如:

# .env 文件
PORT=3000
DB_HOST=localhost
DB_USER=root
DB_PASS=s1mpl3
SECRET_KEY=mysecretkey

注意:.env 文件通常不会提交到版本控制系统(如 Git),因此你可以在 .gitignore 文件中添加一行 /.env 来忽略它。

在你的应用程序入口文件中(通常是 app.jsindex.js),加载并配置 dotenv。这通常是你在应用程序中最早执行的操作之一:

require('dotenv').config();

// 现在你可以通过 process.env 访问环境变量
console.log(process.env.PORT); // 输出: 3000
console.log(process.env.DB_HOST); // 输出: localhost

在一些情况下,你可能需要为不同的环境(如开发、测试、生产)使用不同的 .env 文件。你可以通过 dotenv 的配置选项来指定加载的文件:

require('dotenv').config({ path: './config/.env.dev' });

如果想让配置支持使用变量替换,你可以使用 dotenv-expand 来实现:

# .env 文件
HOST=localhost
PORT=3000
FULL_URL=http://${HOST}:${PORT}
const dotenv = require('dotenv');
const dotenvExpand = require('dotenv-expand');

const myEnv = dotenv.config();
dotenvExpand.expand(myEnv);

console.log(process.env.FULL_URL); // 输出: http://localhost:3000

源码实现

dotenv的源码很简单,只有1个主要文件:https://github.com/motdotla/dotenv/blob/master/lib/main.js

键值解析

核心原理是将 .env 文件解析为键值对,并加载到 process.env 中。在实现上主要是通过使用正则表达式,并处理了字符串、引号、换行符等特殊情况。

这个正则,表示看着头疼。。。

const LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg

加解密

在16.1.x版本之后,还增加了解密的功能。可以用于一些安全要求较高的项目中。有了这个可以放心地把.env文件提交到生产了。

function decrypt (encrypted, keyStr) {
  const key = Buffer.from(keyStr.slice(-64), 'hex')
  let ciphertext = Buffer.from(encrypted, 'base64')

  const nonce = ciphertext.subarray(0, 12)
  const authTag = ciphertext.subarray(-16)
  ciphertext = ciphertext.subarray(12, -16)

  try {
    const aesgcm = crypto.createDecipheriv('aes-256-gcm', key, nonce)
    aesgcm.setAuthTag(authTag)
    return `${aesgcm.update(ciphertext)}${aesgcm.final()}`
  } catch (error) {
    // 处理解密错误
    throw error
  }
}

该函数使用 AES-256-GCM 算法进行解密操作。AES-256 表示它使用 256 位的密钥进行加密,GCM 是一种加密模式,除了加密数据,还可以验证数据的完整性。

  • 加密: 先用 256 位的密钥加密信息,然后生成一个“标签”,这个标签是用来验证数据有没有被改动的。
  • 解密: 先检查标签,如果标签正确,才会用密钥解密数据。如果标签不对,就说明数据可能被篡改了,解密就会失败。

你会不会有疑问,这里是解密,那加密呢?dotenv自身不提供加密的功能,加密依赖于一个工具,dotenvx。https://dotenvx.com/docs/。

dotenvx 是 dotenv 的扩展或增强版,通常基于 dotenv 的功能进行构建,使用时也会依赖于 dotenv 的基础设施。dotenvx 提供了更加专业和复杂的功能,适用于更高要求的应用场景。

另外也可以通过dotenvx ext genexample命令生成一个env的配置例子文件。

灵活的配置入口

dotenv 支持灵活设置配置文件地址,这主要依赖于configDotenv 函数。同时,还内置了调试功能,通过 _debug 函数输出详细的调试信息,帮助开发者快速定位问题。

function configDotenv (options) {
  const dotenvPath = path.resolve(process.cwd(), '.env')
  let encoding = 'utf8'
  const debug = Boolean(options && options.debug)

  if (options && options.encoding) {
    encoding = options.encoding
  }

  // 加载并解析 .env 文件
  let optionPaths = [dotenvPath]
  if (options && options.path) {
    // 自定义路径处理逻辑
  }

  // 解析并填充环境变量
  let lastError
  const parsedAll = {}
  for (const path of optionPaths) {
    try {
      const parsed = DotenvModule.parse(fs.readFileSync(path, { encoding }))
      DotenvModule.populate(parsedAll, parsed, options)
    } catch (e) {
      if (debug) {
        _debug(`Failed to load ${path} ${e.message}`)
      }
      lastError = e
    }
  }

  // 填充到 process.env
  DotenvModule.populate(processEnv, parsedAll, options)

  return { parsed: parsedAll, error: lastError }
}

总结

关于dotenvx,这里说多一点,真是一个好工具。除了上面介绍的用来加密,也可以用来生成配置用例

dotenvx ext genexample

也可以用来设置环境文件,不用在项目里自己调用dotenv也可以

dotenvx run -f .env.production -- node index.js

最后,提一下注意事项:

  • 非加密的env配置文件,不要提交到代码仓库。除非你确信其中不包含任何敏感信息。
  • 分环境管理:为不同环境创建 .env 文件,例如 .env.development, .env.production
  • 确保 .env 文件的权限设置是适当的,以防止未经授权的访问。

本文由mdnice多平台发布

标签:文件,const,实现,详解,env,dotenv,加密,options
From: https://www.cnblogs.com/miniwa/p/18383359

相关文章

  • 使用 Tampermonkey5.1.1_0加自定义编写的js脚本实现自动填充表单
    最近有碰到要使用单点登录的需求,最开始是按照固定流程使用OAuth2.0或者jwt等技术通过父子系统交互的方式实现单点登录。缺点:代码繁琐,而且需要子系统配合提供单点登录接口,并且跳转时子系统需要携带其token等参数优点:安全,通过系统交互的方式鉴权访问接口。由于要集成的子系统很多,而......
  • A147-基于SSM实现的超市订单管理系统
    介绍基于JavaWeb超市订单管理系统,采用的是Java开发,项目简洁,采用的技术是非常经典SSM,适合Java项目入门学习以及企业级Java开发熟悉...软件架构后端技术:spring+springmvc+mybatis前端技术:jsp环境要求:jdk1.8|maven|mysql功能介绍【代码结构与数据库截图】【功......
  • ES6的Map函数详解
    一、Map介绍Map对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者基本类型)都可以作为一个键或一个值Map对象是键值对的集合。Map中的一个键只能出现一次;它在Map的集合中是独一无二的。Map对象在for…of循环在每次迭代后会返回一个形式为[key,value]的数组......
  • Fins TCP协议理解及C Sharp实现思路
     假设本文中使用到设备的ip地址,用于后续内容的理解:客户端(本机电脑windows系统)IP:192.168.1.101服务端(PLComronCJ2M系列)IP和端口号:192.168.1.10:9600 注意:①本文中的FINSTCP报文都是以16进制(Hex)发送出去的,所以对应的转换也都会转成16进制的形式。②16进制He......
  • rabbitmq实现用户关系绑定信息推送
    1.MQ三大要点交换机队列Key2.交换机  交换机是消息队列系统中的一个核心组件,主要用于将消息路由到一个或多个队列中。交换机通过不同的路由规则来决定消息的去向。根据不同的类型,交换机可以有不同的路由策略:直连交换机(DirectExchange):根据消息的路由键(RoutingKey......
  • Springboot计算机毕业设计面向社区的洗衣店智能服务系统设计与实现808ub
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表用户,设备信息,洗衣服务,用户下单,上门预约,送衣信息,上门取件,洗衣常识,优惠活动,洗衣分配,会员卡,会员等级,会员卡购买开题报告内容计算机毕业设计面向社区的......
  • 【Nginx】windows如何实现模拟微服务负载
    背景:上篇讲到本地的【微服务多开】,在前后端分离项目中,可能还需要配合nginx配置,才能实现真实负载运行场景,本文讲述输入如何模拟微服务负载一、本地下载windows版本Nginx并解压 二、在conf/nginx.conf中添加一下配置http{#定义upstream,这里使用轮询策略upstre......
  • springboot+vue基于青少年篮球俱乐部管理系统设计与实现【程序+论文+开题】-计算机毕
    系统程序文件列表开题报告内容研究背景随着青少年体育运动的蓬勃发展,篮球作为一项广受欢迎的体育项目,其俱乐部数量在全国范围内迅速增长。然而,传统的管理方式往往依赖于人工记录和纸质文档,不仅效率低下,还容易出错,难以满足俱乐部日常运营中运动员信息管理、比赛组织、报名流......
  • 遥控链路应用行业行业详解!!!
    遥控链路,即遥控器和接收机之间的信号传输链路,其应用行业广泛且多样。1.家电行业传统家电控制:如电视、空调、音响等设备,通过遥控器实现远程控制,极大地方便了用户的日常生活。这些遥控器通过红外线信号或无线电波将控制指令传输给设备,设备内部的接收器接收并解码信号,从而执行......
  • js 封装日志上传模块,实现异常日志的上报
    封装定义日志上传模块,实现异常日志的上报,包含触发方式:1、主动调取方法上报2、覆盖原生console.error实现,收集所有console.error打印的日志3、window注册绑定error事件,触发 window.addEventListener('error',/***客户端日志上传模块,实现异常日志的上报*使用时在HTML......