从零开始的Koa实战(5) 环境配置

在项目开发中,我们希望有多个环境配置,如开发环境、生产环境、测试环境等。不同的环境可能需要不同的配置,如数据库、日志、端口等。此外,不同的开发者也有不同的设置。

经过前面的实战,我们已经有了下面的目录结构:

koa-blog
├── app
│   ├── middleware
│   │   └── logger.js
│   ├── router
│   │   ├── home.js
│   │   └── index.js
│   ├── util
│   │   └── log_format.js
│   └── view
│       ├── 404.html
│       └── index.html
├── app.js
├── config
│   └── config.js
└── package.json

为了让项目支持不同的开发环境配置,我们将使用以下两个包:

  • config - 用来管理不同的运行环境
  • dotenv-safe - 用来定义一些需要保密的环境变量。

安装

$ npm install config dotenv-safe --save

配置运行环境

config 会默认去查看项目根目录的 config 文件夹,所以需要创建一个 config 目录,这个在之前的实战已经做了。

接着,来创建一个默认的配置文件 default.json ,其中包含了我们的数据库设置以及服务的启动设置。以本项目为例,配置如下:

// config/default.json

{
    "App": {
        "ip": "0.0.0.0", // 所有ip可以访问
        "port": 3000 // 端口
    },
    "Router": {
        "apiPrefix": "/api" // 路由前缀
    },
    "Database": {
        "user": "moyufed", // MongoDB用户名
        "password": "123456", // MongoDB密码
        "host": "127.0.0.1",
        "dbName": "koaBlog", // MongoDB数据库名
        "port": 3001
    },
    "Log4js": {
        "appenders": {
            "error": {
                "category": "errorLogger", //logger名称
                "type": "dateFile", //日志类型
                "filename": "logs/error/error", //日志输出位置
                "alwaysIncludePattern": true, //是否总是有后缀名
                "pattern": "yyyy-MM-dd-hh.log" //后缀,每小时创建一个新的日志文件
            },
            "response": {
                "category": "resLogger",
                "type": "dateFile",
                "filename": "logs/response/response",
                "alwaysIncludePattern": true,
                "pattern": "yyyy-MM-dd-hh.log"
            }
        },
        "categories": {
            "error": {
                "appenders": [
                    "error"
                ],
                "level": "error"
            },
            "response": {
                "appenders": [
                    "response"
                ],
                "level": "info"
            },
            "default": {
                "appenders": [
                    "response"
                ],
                "level": "info"
            }
        }
    }
}

代码里面的配置包括了 config/config.js 里面的所有配置信息。

使用运行环境

在前面的代码中配置了应用的设置 App 以及数据库连接配置 Database,在项目的任何地方需要使用这些配置时,只需要引用 config 就可以了,如:

// app.js

const Koa = require('koa');
const config = require('config'); // 引入config
const appConfig = config.get('App'); // 直接使用 config 获取App的配置
const apiPrefix = config.get('Router.apiPrefix'); // 可以通过Router.apiPrefix获取具体的值
console.log(appConfig); // 输出获取的 appConfig

// ...

app.listen(appConfig.port, appConfig.ip, () => {
    console.log(`服务已经启动,访问:http://localhost:${appConfig.port}${apiPrefix}`);
});

router 里面也可以使用config:

// app/router/index.js

const Router = require('koa-router');
const config = require('config'); // 引入config
const apiPrefix = config.get('Router.apiPrefix');
const router = new Router();
router.prefix(apiPrefix); // 设置路由前缀
const home = require('./home');

const index = async (ctx, next) => {
    await ctx.render('index', {title: 'Index', link: 'home'});
};

router.get('/', index);
router.get('/index', index);
router.use('/home', home.routes(), home.allowedMethods()); // 设置home的路由

module.exports = router;

当然,我们可以移除之前创建的 config/config.js 文件,接着来对 log 配置部分进行完善:

// app/util/log_format.js

const log4js = require('log4js');

+ const config = require('config'); // 引入config
+ const log4jsConfig= config.get('Log4js');
+ log4js.configure(log4jsConfig);
- const { LOG_CONFIG } = require('../../config/config'); //加载配置文件
- log4js.configure(LOG_CONFIG);

let logFormat = {};

let errorLogger = log4js.getLogger('error'); // categories的元素
let resLogger = log4js.getLogger('response');

// ...

module.exports = logFormat;

启动服务之后,我们就能看到命令行能够打印出 config.json 里面的 App 配置信息:

{ server: '0.0.0.0', port: 3000 }
服务已经启动,访问:http://localhost:3000/api

配置多个环境

经过上面的介绍,我们已经通过 config 来配置运行环境了,但仅是这样并不能实现多个环境的配置,我们需要再配置一个新的环境。

接下来,配置一个生产环境(production),需要在 config 目录新建一个 production.json 文件:

// config/production.json

{
    "App": {
        "port": 8000
    }
}

这里并没有配置所有的变量,而是希望一些变量保持和默认配置一样,如服务启动的地址、数据库名称等等。

为了验证配置是否生效,需要切换到 production 环境:

'export NODE_ENV=production' // Linux
'set NODE_ENV=production' // Windows

同样,为了方便,可以将该命令添加到 package.json 里面:

{
  "name": "koa-blog",
  "scripts": {
    "start": "node app.js",
    "prod": "set NODE_ENV=production&&npm start"
  },
  // ...
}

接下来执行命令 npm run prod 启动服务就能够看到输出的环境配置已经改变,端口变成了 8000 。访问 http://localhost:8000/api ,浏览器正常显示页面。

$ npm run prod

> set NODE_ENV=production&&npm start
> node app.js

{ ip: '0.0.0.0', port: 8000 }
服务已经启动,访问:http://localhost:8000/api

事实上,当调用 config.get('App') 时,会从对应环境的 json 文件去取值替换 default.json 对应的值。若需要支持更多的运行环境,我们只需要新增其它的文件就行,如 staging.jsonqa.json 等。

配置环境变量

大家已经注意到,在前面的配置中,数据库密码是写在 config 里面的,为了安全起见,我们希望把密码配置在本地而不是提交到代码库或者仓库。因此,我们需要用到 dotenv-safe

dotenv-safe 可以定义私有的变量,这是 node 进程运行时的变量而不是前面配置的环境变量。dotenv-safe 默认会从项目根目录的 .env 文件中加载配置,下面来看看具体操作。

在根目录新建一个 .env 文件,内容如下:

DB_PASSWORD=123456

上面代码把数据库密码抽离了出来,并且我们会在 .gitignore 文件中忽略掉这个文件:

node_modules/
.idea/
logs/
+ .env

这样就不会提交到仓库了。

接下来我们新建一个 .env.example 文件用来提交到代码库,这个文件没有对变量进行赋值,但是能够表明项目使用的配置,注意一来,其他开发者可以根据这里面的内容设置自己的项目环境。如果这个文件里面定义了 .env 没有的值,程序将停止执行。 .env.example 的内容:

DB_PASSWORD=

然后在 app.js 里面优先引入来进行使用:

+ require('dotenv-safe').config(); // 只需要引入一次
const Koa = require('koa');
const config = require('config'); // 引入config
const appConfig = config.get('App'); // 直接使用 config 获取App的配置
const apiPrefix = config.get('Router.apiPrefix'); // 可以通过Router.apiPrefix获取具体的值
+ console.log(process.env.DB_PASSWORD); // 123456
console.log(appConfig); // 输出获取的 appConfig

// ...

启动服务查看输出:

123456
{ ip: '0.0.0.0', port: 8000 }
服务已经启动,访问:http://localhost:8000/api

使用.env环境变量

接下来,我们将使用定义好的变量来替换 config 里面的配置。我们在 config 目录新增一个文件 custom-environment-variables.json

{
    "Database": {
        "password": "DB_PASSWORD"
    }
}

这个 json 文件里面我们对数据库的密码进行了定义,当执行 config.get('Database.password') 时, config 将去查询一个叫 “DB_PASSWORD” 的环境变量。如果查询不到就会使用匹配当前 node 环境的 json 文件的值,如果当前 node 环境的值任然没有设置,就会去查询 default.json 里面设置的默认值 。

验证 app.js 验证是否有效:

require('dotenv-safe').config(); // 只需要引入一次
const Koa = require('koa');
const config = require('config'); // 引入config
const appConfig = config.get('App'); // 直接使用 config 获取App的配置
const apiPrefix = config.get('Router.apiPrefix'); // 可以通过Router.apiPrefix获取具体的值
+ const dbConfig = config.get('Database');
+ console.log(dbConfig);
console.log(process.env.DB_PASSWORD); // 123456
console.log(appConfig); // 输出获取的 appConfig

// ...

修改 .env 里面的值来启动服务查看是否生效:

DB_PASSWORD=12345678

结果:

{ user: 'moyufed',
  password: '12345678',
  host: '127.0.0.1',
  dbName: 'koaBlog',
  port: 3001 }
12345678
{ ip: '0.0.0.0', port: 8000 }
服务已经启动,访问:http://localhost:8000/api

我们可以看到,数据库的连接密码已经被 .env 修改为 12345678 。通过这种方式,可以将服务器的一些配置抽离到 .env 文件:

// .env
APP_IP=0.0.0.0
APP_PORT=3000
DB_PASSWORD=123456
DB_HOST=127.0.0.1
DB_PORT=3001
DB_USER=moyufed
DB_NAME=koaBlog

// .env.example
APP_IP=
APP_PORT=
DB_PASSWORD=
DB_HOST=
DB_PORT=
DB_USER=
DB_NAME=

// config/custom-environment-variables.json
{
    "App": {
        "ip": "APP_IP", // 所有ip可以访问
        "port": "APP_PORT" // 端口
    },
    "Database": {
        "user": "DB_USER", // MongoDB用户名
        "password": "DB_PASSWORD", // MongoDB密码
        "host": "DB_HOST",
        "dbName": "DB_NAME", // MongoDB数据库名
        "port": "DB_PORT"
    }
}

移除引入的旧的config设置

参考资料:Maintain Multiple Environment Configurations and Secrets in Node.js Apps

经过本节实战,我们已经完成了项目的环境配置,我们的项目目录如下:

koa-blog
├── .env.example
├── .env
├── .gitignore
├── app
│   ├── middleware
│   │   └── logger.js
│   ├── router
│   │   ├── home.js
│   │   └── index.js
│   ├── util
│   │   └── log_format.js
│   └── view
│       ├── 404.html
│       └── index.html
├── app.js
├── config
│   ├── custom-environment-variables.json
│   ├── default.json
│   └── production.json
├── package.json
└── README.md

下一步,我们来使用 mongoose 操作 MongoDB 插入一条数据,并且使用MVC(Model View Controller)规范进行开发…

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349