從零開始的Koa實(shí)戰(zhàn)(5) 環(huán)境配置

在項(xiàng)目開發(fā)中语盈,我們希望有多個(gè)環(huán)境配置,如開發(fā)環(huán)境缰泡、生產(chǎn)環(huán)境刀荒、測(cè)試環(huán)境等。不同的環(huán)境可能需要不同的配置棘钞,如數(shù)據(jù)庫缠借、日志、端口等宜猜。此外泼返,不同的開發(fā)者也有不同的設(shè)置。

經(jīng)過前面的實(shí)戰(zhàn)姨拥,我們已經(jīng)有了下面的目錄結(jié)構(gòu):

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

為了讓項(xiàng)目支持不同的開發(fā)環(huán)境配置绅喉,我們將使用以下兩個(gè)包:

  • config - 用來管理不同的運(yùn)行環(huán)境
  • dotenv-safe - 用來定義一些需要保密的環(huán)境變量。

安裝

$ npm install config dotenv-safe --save

配置運(yùn)行環(huán)境

config 會(huì)默認(rèn)去查看項(xiàng)目根目錄的 config 文件夾叫乌,所以需要?jiǎng)?chuàng)建一個(gè) config 目錄柴罐,這個(gè)在之前的實(shí)戰(zhàn)已經(jīng)做了。

接著憨奸,來創(chuàng)建一個(gè)默認(rèn)的配置文件 default.json 革屠,其中包含了我們的數(shù)據(jù)庫設(shè)置以及服務(wù)的啟動(dòng)設(shè)置。以本項(xiàng)目為例,配置如下:

// 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數(shù)據(jù)庫名
        "port": 3001
    },
    "Log4js": {
        "appenders": {
            "error": {
                "category": "errorLogger", //logger名稱
                "type": "dateFile", //日志類型
                "filename": "logs/error/error", //日志輸出位置
                "alwaysIncludePattern": true, //是否總是有后綴名
                "pattern": "yyyy-MM-dd-hh.log" //后綴屠阻,每小時(shí)創(chuàng)建一個(gè)新的日志文件
            },
            "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 里面的所有配置信息。

使用運(yùn)行環(huán)境

在前面的代碼中配置了應(yīng)用的設(shè)置 App 以及數(shù)據(jù)庫連接配置 Database额各,在項(xiàng)目的任何地方需要使用這些配置時(shí)国觉,只需要引用 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(`服務(wù)已經(jīng)啟動(dòng)虾啦,訪問: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); // 設(shè)置路由前綴
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()); // 設(shè)置home的路由

module.exports = router;

當(dāng)然麻诀,我們可以移除之前創(chuàng)建的 config/config.js 文件,接著來對(duì) log 配置部分進(jìn)行完善:

// 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;

啟動(dòng)服務(wù)之后傲醉,我們就能看到命令行能夠打印出 config.json 里面的 App 配置信息:

{ server: '0.0.0.0', port: 3000 }
服務(wù)已經(jīng)啟動(dòng)蝇闭,訪問:http://localhost:3000/api

配置多個(gè)環(huán)境

經(jīng)過上面的介紹,我們已經(jīng)通過 config 來配置運(yùn)行環(huán)境了硬毕,但僅是這樣并不能實(shí)現(xiàn)多個(gè)環(huán)境的配置呻引,我們需要再配置一個(gè)新的環(huán)境。

接下來吐咳,配置一個(gè)生產(chǎn)環(huán)境(production)逻悠,需要在 config 目錄新建一個(gè) production.json 文件:

// config/production.json

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

這里并沒有配置所有的變量,而是希望一些變量保持和默認(rèn)配置一樣韭脊,如服務(wù)啟動(dòng)的地址童谒、數(shù)據(jù)庫名稱等等。

為了驗(yàn)證配置是否生效沪羔,需要切換到 production 環(huán)境:

'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"
  },
  // ...
}

接下來執(zhí)行命令 npm run prod 啟動(dòng)服務(wù)就能夠看到輸出的環(huán)境配置已經(jīng)改變蔫饰,端口變成了 8000 琅豆。訪問 http://localhost:8000/api ,瀏覽器正常顯示頁面死嗦。

$ npm run prod

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

{ ip: '0.0.0.0', port: 8000 }
服務(wù)已經(jīng)啟動(dòng)趋距,訪問:http://localhost:8000/api

事實(shí)上,當(dāng)調(diào)用 config.get('App') 時(shí)越除,會(huì)從對(duì)應(yīng)環(huán)境的 json 文件去取值替換 default.json 對(duì)應(yīng)的值节腐。若需要支持更多的運(yùn)行環(huán)境,我們只需要新增其它的文件就行摘盆,如 staging.json 翼雀、 qa.json 等。

配置環(huán)境變量

大家已經(jīng)注意到孩擂,在前面的配置中狼渊,數(shù)據(jù)庫密碼是寫在 config 里面的,為了安全起見,我們希望把密碼配置在本地而不是提交到代碼庫或者倉庫狈邑。因此城须,我們需要用到 dotenv-safe

dotenv-safe 可以定義私有的變量米苹,這是 node 進(jìn)程運(yùn)行時(shí)的變量而不是前面配置的環(huán)境變量糕伐。dotenv-safe 默認(rèn)會(huì)從項(xiàng)目根目錄的 .env 文件中加載配置,下面來看看具體操作蘸嘶。

在根目錄新建一個(gè) .env 文件良瞧,內(nèi)容如下:

DB_PASSWORD=123456

上面代碼把數(shù)據(jù)庫密碼抽離了出來,并且我們會(huì)在 .gitignore 文件中忽略掉這個(gè)文件:

node_modules/
.idea/
logs/
+ .env

這樣就不會(huì)提交到倉庫了训唱。

接下來我們新建一個(gè) .env.example 文件用來提交到代碼庫褥蚯,這個(gè)文件沒有對(duì)變量進(jìn)行賦值,但是能夠表明項(xiàng)目使用的配置况增,注意一來赞庶,其他開發(fā)者可以根據(jù)這里面的內(nèi)容設(shè)置自己的項(xiàng)目環(huán)境。如果這個(gè)文件里面定義了 .env 沒有的值澳骤,程序?qū)⑼V箞?zhí)行尘执。 .env.example 的內(nèi)容:

DB_PASSWORD=

然后在 app.js 里面優(yōu)先引入來進(jìn)行使用:

+ 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

// ...

啟動(dòng)服務(wù)查看輸出:

123456
{ ip: '0.0.0.0', port: 8000 }
服務(wù)已經(jīng)啟動(dòng),訪問:http://localhost:8000/api

使用.env環(huán)境變量

接下來宴凉,我們將使用定義好的變量來替換 config 里面的配置誊锭。我們?cè)?config 目錄新增一個(gè)文件 custom-environment-variables.json

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

這個(gè) json 文件里面我們對(duì)數(shù)據(jù)庫的密碼進(jìn)行了定義,當(dāng)執(zhí)行 config.get('Database.password') 時(shí)弥锄, config 將去查詢一個(gè)叫 “DB_PASSWORD” 的環(huán)境變量丧靡。如果查詢不到就會(huì)使用匹配當(dāng)前 node 環(huán)境的 json 文件的值,如果當(dāng)前 node 環(huán)境的值任然沒有設(shè)置籽暇,就會(huì)去查詢 default.json 里面設(shè)置的默認(rèn)值 温治。

驗(yàn)證 app.js 驗(yàn)證是否有效:

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 里面的值來啟動(dòng)服務(wù)查看是否生效:

DB_PASSWORD=12345678

結(jié)果:

{ user: 'moyufed',
  password: '12345678',
  host: '127.0.0.1',
  dbName: 'koaBlog',
  port: 3001 }
12345678
{ ip: '0.0.0.0', port: 8000 }
服務(wù)已經(jīng)啟動(dòng),訪問:http://localhost:8000/api

我們可以看到戒悠,數(shù)據(jù)庫的連接密碼已經(jīng)被 .env 修改為 12345678 熬荆。通過這種方式,可以將服務(wù)器的一些配置抽離到 .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數(shù)據(jù)庫名
        "port": "DB_PORT"
    }
}

移除引入的舊的config設(shè)置

參考資料:Maintain Multiple Environment Configurations and Secrets in Node.js Apps

經(jīng)過本節(jié)實(shí)戰(zhàn)绸狐,我們已經(jīng)完成了項(xiàng)目的環(huán)境配置卤恳,我們的項(xiàng)目目錄如下:

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 插入一條數(shù)據(jù)寒矿,并且使用MVC(Model View Controller)規(guī)范進(jìn)行開發(fā)…

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末突琳,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子符相,更是在濱河造成了極大的恐慌拆融,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異镜豹,居然都是意外死亡傲须,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門趟脂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來躏碳,“玉大人,你說我怎么就攤上這事散怖『鹗唬” “怎么了涩澡?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵这嚣,是天一觀的道長(zhǎng)搞监。 經(jīng)常有香客問我劲妙,道長(zhǎng)请敦,這世上最難降的妖魔是什么贸营? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任贾漏,我火速辦了婚禮惑申,結(jié)果婚禮上具伍,老公的妹妹穿的比我還像新娘。我一直安慰自己圈驼,他們只是感情好人芽,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著绩脆,像睡著了一般萤厅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上靴迫,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天惕味,我揣著相機(jī)與錄音,去河邊找鬼玉锌。 笑死名挥,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的主守。 我是一名探鬼主播禀倔,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼参淫!你這毒婦竟也來了蹋艺?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤黄刚,失蹤者是張志新(化名)和其女友劉穎捎谨,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡涛救,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年畏邢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片检吆。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡舒萎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蹭沛,到底是詐尸還是另有隱情臂寝,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布摊灭,位于F島的核電站咆贬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏帚呼。R本人自食惡果不足惜掏缎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望煤杀。 院中可真熱鬧眷蜈,春花似錦、人聲如沸沈自。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽枯途。三九已至今豆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間柔袁,已是汗流浹背呆躲。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留捶索,地道東北人插掂。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像腥例,于是被迫代替她去往敵國和親辅甥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344