在項(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ā)…