koa2+log4js+sequelize搭建的nodejs服務


  • 主要參考http://www.jianshu.com/p/6b816c609669這篇文章
  • npm安裝使用國內taobao鏡像,速度更快些 npm --registry https://registry.npm.taobao.org install --save cross-env;
  • 啟動nodejs服務,如果配置了不同的環境設置,需安裝cross-env ,使用cross-env解決跨平台設置NODE_ENV的問題
  • app.use(async (ctx, next) => {
      await next();
      ctx.body = 'Hello World';
    });
    想要獲取post請求中的參數要使用ctx.request.body

1 構建項目
1.1 安裝koa-generator 這是一個開源框架
npm install -g koa-generator

1.2 使用koa-generator生成koa2項目
進入工作目錄 cd work

然后執行下面命令生成完整項目目錄和文件
koa2 HelloKoa2

創建項目成功后,進入項目目錄

cd HelloKoa2

然后執行下面命令,安裝依賴的插件
npm --registry https://registry.npm.taobao.org install

如果執行失敗,並且提示【Please try running this command again as root/Administrator.】,應該是執行權限不夠。找到CMD的命令提示工具,用管理員權限打開,進入項目根目錄,執行上面命令,

1.3 啟動項目

默認的啟動腳本如下:

  "scripts": {
    "start": "node bin/www",
    "dev": "./node_modules/.bin/nodemon bin/www",
    "prd": "pm2 start bin/www",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

默認端口在 bin/www文件中設置

執行 npm start ,如果不考慮自動重啟功能,其實這句代碼相當於執行了node bin/run 然后即可訪問 網址http://127.0.0.1:3000/ 

也可執行npm run dev,此處配置是使用了nodemon插件,nodemon插件的作用是在你啟動了服務之后,修改文件可以自動重啟服務。

2 項目配置

這里需要根據不同環境配置不同的啟動字符串,例如

    "start": "cross-env NODE_ENV=development ./node_modules/.bin/nodemon bin/www",
    "koa": "./node_modules/.bin/nodemon bin/www",
    "prd": "pm2 start bin/www",
    "test": "cross-env NODE_ENV=test ./node_modules/.bin/nodemon bin/www",
    "pro":"cross-env NODE_ENV=production ./node_modules/.bin/nodemon bin/www"

2.2 npm scripts
我們在scripts對象中添加一段代碼"start_koa": "bin/run",需要npm run start_koa 這樣執行
在npm中,有四個常用的縮寫
    npm start是npm run start
    npm stop是npm run stop的簡寫
    npm test是npm run test的簡寫
    npm restart是npm run stop && npm run restart && npm run start的簡寫
其他的都要使用npm run來執行了。

如果需要執行普通的js文件,只需 node init_db 即可。

2.3 配置環境
關於配置環境常用的有development、test、production、debug。可以使用node提供的process.env.NODE_ENV來設置。

在bin/www 文件中加入下面語句,查看輸出

console.log("process.env.NODE_ENV=" + process.env.NODE_ENV);

在windows下如此 執行Set NODE_ENV=test && npm start  可以看到輸出的環境日志為test。只是賦值,如果加入配置文件后,此方式不管用,需在腳本中配置環境變量。例如:

  "scripts": {
    "start": "cross-env NODE_ENV=development ./node_modules/.bin/nodemon bin/www",
    "test": "cross-env NODE_ENV=test ./node_modules/.bin/nodemon bin/www",
    "pro":"cross-env NODE_ENV=production ./node_modules/.bin/nodemon bin/www"
  },

2.4 配置文件
首先在項目根目錄下添加config目錄,在config目錄下添加index.js、test.js、development.js個文件
development.js 文件中如此定義

module.exports = {
    env: 'development', //環境名稱
    webport: 3001         //服務端口號
}

test.js文件中如此定義

module.exports = {
    env: 'test',        //環境名稱
    webport: 3002         //服務端口號
}

index.js文件中如此定義

var development_env = require('./development');
var test_env = require('./test');
var production_env=require('./production');

//根據不同的NODE_ENV,輸出不同的配置對象,默認輸出development的配置對象
module.exports = {
    development: development_env,
    test: test_env,
    production:production_env
}[process.env.NODE_ENV || 'development']

bin/www添加如下代碼

//引入配置文件
var config = require('../config');
console.log("process.env.NODE_ENV=" + process.env.NODE_ENV);
// 將端口號設置為配置文件的端口號,默認值為3000
var port = normalizePort(config.webport|| '3000');
// 打印輸出端口號
console.log('port = ' + config.webport);

 

3 日志
在app.js文件中,框架已包含一個日志插件

const logger = require('koa-logger');
app.use(convert(logger()));
// logger
app.use(async (ctx, next) => {
  const start = new Date()
  await next()
  const ms = new Date() - start
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})

 3.1 log4js
log4js提供了多個日志等級分類,同時也能替換console.log輸出,另外他還可以按照文件大小或者日期來生成本地日志文件,還可以使用郵件等形式發送日志。
用info和error兩種日志等級分別記錄響應日志和錯誤日志。

3.2 log4js 配置

安裝log4js插件 會提示Please try running this command again as root/Administrator.  錯誤, 先用管理員權限執行cmd窗口,然后進入項目,再執行下面語句
npm --registry https://registry.npm.taobao.org install  log4js --save,如果還是會提示錯誤,就關掉cmd窗口,再重新打開一個,進入項目根目錄,再執行一次。
在config目錄下創建一個log_config.js文件,log4js@1 和log4js@2 兩個版本有差異,現以2.x版本為例

var path = require('path');

//日志根目錄
var baseLogPath = path.resolve(__dirname, '../logs')

//錯誤日志目錄
var errorPath = "/error";
//錯誤日志文件名
var errorFileName = "error";
//錯誤日志輸出完整路徑
var errorLogPath = baseLogPath + errorPath + "/" + errorFileName;
//var errorLogPath = path.resolve(__dirname, "../logs/error/error");

//響應日志目錄
var responsePath = "/response";
//響應日志文件名
var responseFileName = "response";
//響應日志輸出完整路徑
var responseLogPath = baseLogPath + responsePath + "/" + responseFileName;
//var responseLogPath = path.resolve(__dirname, "../logs/response/response");
module.exports = {
  //日志格式等設置
    appenders:
    {
        "rule-console": {"type": "console"},
        "errorLogger": {
            "type": "dateFile",
            "filename": errorLogPath,
            "pattern": "-yyyy-MM-dd-hh.log",
            "alwaysIncludePattern": true,
            "encoding":"utf-8",
            "maxLogSize": 1000,
            "numBackups": 3,
            "path":errorPath
        },
        "resLogger": {
            "type": "dateFile",
            "filename": responseLogPath,
            "pattern": "-yyyy-MM-dd-hh.log",
            "alwaysIncludePattern": true,
            "encoding":"utf-8",
            "maxLogSize": 1000,
            "numBackups": 3,
            "path":responsePath
        },
    },
   //供外部調用的名稱和對應設置定義
    categories: {
        "default": {"appenders": ["rule-console"], "level": "all"},
        "resLogger": {"appenders": ["resLogger"], "level": "info"},
        "errorLogger": {"appenders": ["errorLogger"], "level": "error"},
        "http": {"appenders": ["resLogger"],"level": "info"}
    },
    "baseLogPath": baseLogPath 
}

然后創建一個utils目錄,添加log_util.js文件

var log4js = require('log4js');

var log_config = require('../config/log_config');

//加載配置文件
log4js.configure(log_config);

var logUtil = {};
//調用預先定義的日志名稱
var resLogger = log4js.getLogger("resLogger");
var errorLogger = log4js.getLogger("errorLogger");
var consoleLogger = log4js.getLogger();


//封裝錯誤日志
logUtil.logError = function (ctx, error, resTime) {
    if (ctx && error) {
        errorLogger.error(formatError(ctx, error, resTime));
    }
};

//封裝響應日志
logUtil.logResponse = function (ctx, resTime) {
    if (ctx) {
        resLogger.info(formatRes(ctx, resTime));
    }
};

logUtil.logInfo = function (info) {
    if (info) {
       
        consoleLogger.info( formatInfo(info));
    }
};

var formatInfo = function (info) {
    var logText = new String();
    //響應日志開始
    logText += "\n" + "***************info log start ***************" + "\n";

    //響應內容
    logText += "info detail: " + "\n" + JSON.stringify(info) + "\n";

    //響應日志結束
    logText += "*************** info log end ***************" + "\n";

    return logText;
}

//格式化響應日志
var formatRes = function (ctx, resTime) {
    var logText = new String();
    //響應日志開始
    logText += "\n" + "*************** response log start ***************" + "\n";

    //添加請求日志
    logText += formatReqLog(ctx.request, resTime);

    //響應狀態碼
    logText += "response status: " + ctx.status + "\n";

    //響應內容
    logText += "response body: " + "\n" + JSON.stringify(ctx.body) + "\n";

    //響應日志結束
    logText += "*************** response log end ***************" + "\n";

    return logText;

}

//格式化錯誤日志
var formatError = function (ctx, err, resTime) {
    var logText = new String();

    //錯誤信息開始
    logText += "\n" + "*************** error log start ***************" + "\n";

    //添加請求日志
    logText += formatReqLog(ctx.request, resTime);

    //錯誤名稱
    logText += "err name: " + err.name + "\n";
    //錯誤信息
    logText += "err message: " + err.message + "\n";
    //錯誤詳情
    logText += "err stack: " + err.stack + "\n";

    //錯誤信息結束
    logText += "*************** error log end ***************" + "\n";

    return logText;
};

//格式化請求日志
var formatReqLog = function (req, resTime) {

    var logText = new String();

    var method = req.method;
    //訪問方法
    logText += "request method: " + method + "\n";

    //請求原始地址
    logText += "request originalUrl:  " + req.originalUrl + "\n";

    //客戶端ip
    logText += "request client ip:  " + req.ip + "\n";

    //開始時間
    var startTime;
    //請求參數
    if (method === 'GET') {
        logText += "request query:  " + JSON.stringify(req.query) + "\n";
        // startTime = req.query.requestStartTime;
    } else {
        logText += "request body: " + "\n" + JSON.stringify(req.body) + "\n";
        // startTime = req.body.requestStartTime;
    }
    //服務器響應時間
    logText += "response time: " + resTime + "\n";

    return logText;
}

module.exports = logUtil;

接下來修改app.js 文件中的logger部分,先把原有的日志部分注釋掉,然后加入下列代碼:

//log工具
const logUtil = require('./utils/log_util');


// logger
app.use(async (ctx, next) => {
  //響應開始時間
  const start = new Date();
  //響應間隔時間
  var ms;
  try {
    //開始進入到下一個中間件
    await next();

    ms = new Date() - start;
    //記錄響應日志
    logUtil.logResponse(ctx, ms);

  } catch (error) {

    ms = new Date() - start;
    //記錄異常日志
    logUtil.logError(ctx, error, ms);
  }
});

在這將await next();放到了一個try catch里面,這樣后面的中間件有異常都可以在這集中處理。
比如你會將一些API異常作為正常值返回給客戶端,就可以在這集中進行處理。然后后面的中間件只要throw自定義的API異常就可以

3.3 初始化logs文件目錄
打開bin/www文件 添加

var fs = require('fs');
var logConfig = require('../config/log_config');

/**
 * 確定目錄是否存在,如果不存在則創建目錄
 */
var confirmPath = function(pathStr) {

  if(!fs.existsSync(pathStr)){
      fs.mkdirSync(pathStr);
      console.log('createPath: ' + pathStr);
    }
}

/**
 * 初始化log相關目錄
 */
var initLogPath = function(){
  //創建log的根目錄'logs'
  if(logConfig.baseLogPath){
    confirmPath(logConfig.baseLogPath)
    //根據不同的logType創建不同的文件目錄
    for(var i = 0, len = logConfig.appenders.length; i < len; i++){
      if(logConfig.appenders[i].path){
        confirmPath(logConfig.baseLogPath + logConfig.appenders[i].path);
      }
    }
  }
}

initLogPath();

這樣每次啟動服務的時候,都會去確認一下相關的文件目錄是否存在,如果不存在就創建相關的文件目錄。

3.4 配置規則

 configure方法為配置log4js對象,內部有levels、appenders、categories三個屬性
 levels:
 配置日志的輸出級別,共ALL<TRACE<DEBUG<INFO<WARN<ERROR<FATAL<MARK<OFF八個級別,default level is OFF

只有大於等於日志配置級別的信息才能輸出出來,可以通過category來有效的控制日志輸出級別
appenders:
配置文件的輸出源,一般日志輸出type共有console、file、dateFile三種
console:普通的控制台輸出
file:輸出到文件內,以文件名-文件大小-備份文件個數的形式rolling生成文件
dateFile:輸出到文件內,以pattern屬性的時間格式,以時間的生成文件

replaceConsole:

是否替換控制台輸出,當代碼出現console.log,表示以日志type=console的形式輸出

1、type:console
將日志輸出至控制台,這樣可以方便開發人員在開發時接看到所有日志信息,在其他環境不建議設置
2、alwaysIncludePattern
如果為true,則每個文件都會按pattern命名,否則最新的文件不會按照pattern命名
3、replaceConsole
如果為true,則程序中用console.log輸出到控制台的信息,也會輸出到日志文件中,且格式按照log4js的格式輸出,如果為false,則console.log只會輸出在控制台。與type:console的appender正好相反,如果設置了type:console,則會將log4js.log日志輸出至控制台。
4、category
沒有看到權威的說明,我的理解category就是一個日志名字,如果沒有取應該是默認的。只有當開發人員通過getLogger(category)獲得相對應的日志時,才能輸出到對應的appender中,否則會發送給所有默認的appender
5、logLevelFilter
沒有看到什么文檔說明,但實際的例子還是不少,直觀理解應該就是根據日志級別進行日志過濾。

filename: __dirname + '/logs/test.log',//文件目錄,當目錄文件或文件夾不存在時,會自動創建
maxLogSize : 10,//文件最大存儲空間,當文件內容超過文件存儲空間會自動生成一個文件test.log.1的序列自增長的文件
backups : 3,//default value = 5.當文件內容超過文件存儲空間時,備份文件的數量
//compress : true,//default false.是否以壓縮的形式保存新文件,默認false。如果true,則新增的日志文件會保存在gz的壓縮文件內,並且生成后將不被替換,false會被替換掉
encoding : 'utf-8',//default "utf-8",文件的編碼

 


 3.5 基本使用

var log4js = require('log4js');

var logger = log4js.getLogger();

logger.level = 'debug';

logger.debug("Some debug messages");

或者var resLogger = log4js.getLogger("resLogger");這里取的屬性名稱為categories中的名稱

當前封裝的方法為:

//log工具
const logUtil = require('./utils/log_util');
//記錄響應日志
logUtil.logResponse(ctx, ms);
//記錄異常日志
logUtil.logError(ctx, error, ms);


4 格式化輸出

假設我們現在開發的是一個API服務接口,會有一個統一的響應格式,同時也希望發生API錯誤時統一錯誤格式。

4.1 建立路由接口

框架默認生成的路由接口在routes文件夾下,index.js

const router = require('koa-router')()

router.get('/', async (ctx, next) => {
  await ctx.render('index', {
    title: 'Hello Koa 2!'
  })
})

router.get('/string', async (ctx, next) => {
  ctx.body = 'koa2 string'
})

router.get('/json', async (ctx, next) => {
  ctx.body = {
    title: 'koa2 json'
  }
})

module.exports = router

‘/’為路由接口的根路徑,'/string' 為路由接口名稱

users.js文件

const router = require('koa-router')()

router.prefix('/users')

router.get('/', function (ctx, next) {
  ctx.body = 'this is a users response!'
})

router.get('/bar', function (ctx, next) {
  ctx.body = 'this is a users/bar response'
})

module.exports = router

 

定義完路由文件后,在app.js中注冊一下

// routes
app.use(index.routes(), index.allowedMethods())
app.use(users.routes(), users.allowedMethods())

我們希望服務的地址的組成是這要的
域名 + 端口號 /api/功能類型/具體端口

以上就可以實現我們的需求,運行服務后可以訪問 http://127.0.0.1:3001/users,頁面會顯示  “this is a users response!”

4.2 格式化輸出

如果我們想使服務返回下面的json結果

json格式
{
    "code": 0,
    "message": "成功",
    "data": {
      "username": “王團團",
      "age": 30
    }
}

我們要處理數據應該在發送響應之前和路由得到數據之后添加一個中間件。在項目的根目錄下添加一個middlewares目錄,在該目錄下添加response_formatter.js文件

/**
 * 在app.use(router)之前調用
 */
var response_formatter = async (ctx, next) => {
    //先去執行路由
    await next();

    //如果有返回數據,將返回數據添加到data中
    if (ctx.body) {
        ctx.body = {
            code: 0,
            message: 'success',
            data: ctx.body
        }
    } else {
        ctx.body = {
            code: 0,
            message: 'success'
        }
    }
}

module.exports = response_formatter;

然后在app.js中載入

const response_formatter = require('./middlewares/response_formatter');

在app.js 在添加路由之前調用

app.use(response_formatter);

這樣所有的路由接口都被json格式化。

4.3 對URL進行過濾

修改一下response_formatter.js文件

/** * 在app.use(router)之前調用 */ var response_formatter = (ctx) => { //如果有返回數據,將返回數據添加到data中 if (ctx.body) { ctx.body = { code: 0, message: 'success', data: ctx.body } } else { ctx.body = { code: 0, message: 'success' } } } //指定前綴過濾 var url_filter = function(pattern){ return async function(ctx, next){ var reg = new RegExp(pattern); //先去執行路由  await next(); //通過正則的url進行格式化處理 if(reg.test(ctx.originalUrl)){ response_formatter(ctx); } } } module.exports = url_filter;

//添加格式化處理響應結果的中間件,過濾指定前綴的接口名稱,在添加路由之前調用

app.use(response_formatter('^/users'));

這樣,只有訪問http://127.0.0.1:3001/users接口時才會進行json格式化


4.4 API異常處理
創建一個API異常類,在app目錄下新建一個error目錄,添加ApiError.js文件

/**
 * 自定義Api異常
 */
class ApiError extends Error {

    //構造方法
    constructor(error_name, error_code, error_message) {
        super();
        this.name = error_name;
        this.code = error_code;
        this.message = error_message;
    }
}

module.exports = ApiError;


為了讓自定義Api異常能夠更好的使用,我們創建一個ApiErrorNames.js文件來封裝API異常信息,並可以通過API錯誤名稱獲取異常信息。

/**
 * API錯誤名稱
 */
var ApiErrorNames = {};

ApiErrorNames.UNKNOW_ERROR = "unknowError";
ApiErrorNames.USER_NOT_EXIST = "userNotExist";

/**
 * API錯誤名稱對應的錯誤信息
 */
const error_map = new Map();

error_map.set(ApiErrorNames.UNKNOW_ERROR, { code: -1, message: '未知錯誤' });
error_map.set(ApiErrorNames.USER_NOT_EXIST, { code: 101, message: '用戶不存在' });

//根據錯誤名稱獲取錯誤信息
ApiErrorNames.getErrorInfo = (error_name) => {

    var error_info;

    if (error_name) {
        error_info = error_map.get(error_name);
    }

    //如果沒有對應的錯誤信息,默認'未知錯誤'
    if (!error_info) {
        error_name = UNKNOW_ERROR;
        error_info = error_map.get(error_name);
    }

    return error_info;
}

module.exports = ApiErrorNames;

修改ApiError.js文件,引入ApiErrorNames

const ApiErrorNames = require('./ApiErrorNames');
const ApiErrorNames = require('./ApiErrorNames');

/**
 * 自定義Api異常
 */
class ApiError extends Error{
    //構造方法
    constructor(error_name){
        super();

        var error_info = ApiErrorNames.getErrorInfo(error_name);

        this.name = error_name;
        this.code = error_info.code;
        this.message = error_info.message;
    }
}

module.exports = ApiError;

在response_formatter.js文件中處理API異常
先引入ApiError:

var ApiError = require('../app/error/ApiError');

然后修改url_filter

/**
 * 在app.use(router)之前調用
 */
var ApiError = require('../app/error/ApiError');
 var response_formatter = (ctx) => {
    //如果有返回數據,將返回數據添加到data中
    if (ctx.body) {
        ctx.body = {
            code: 0,
            message: 'success',
            data: ctx.body
        }
    } else {
        ctx.body = {
            code: 0,
            message: 'success'
        }
    }
}

var url_filter = (pattern) => {
    return async (ctx, next) => {
        var reg = new RegExp(pattern);
        try {
            //先去執行路由
            await next();
        } catch (error) {
            //如果異常類型是API異常並且通過正則驗證的url,將錯誤信息添加到響應體中返回。
            if(error instanceof ApiError && reg.test(ctx.originalUrl)){
                ctx.status = 200;
                ctx.body = {
                    code: error.code,
                    message: error.message
                }
            }
            //繼續拋,讓外層中間件處理日志
            throw error;
        }

        //通過正則的url進行格式化處理
        if(reg.test(ctx.originalUrl)){
            response_formatter(ctx);
        }
    }
}

module.exports = url_filter;

 

  1.     使用try catch包裹await next();,這樣后面的中間件拋出的異常都可以在這幾集中處理;
  2.  throw error;是為了讓外層的logger中間件能夠處理日志。

模擬效果,修改routes/users.js 文件

const ApiError = require('../app/error/ApiError');
const ApiErrorNames = require('../app/error/ApiErrorNames');
const router = require('koa-router')()

router.prefix('/users')

router.get('/', function (ctx, next) {
  ctx.body = 'this is a users response!'
})

router.get('/bar', function (ctx, next) {
  ctx.body = 'this is a users/bar response'
})
router.post('/post', function (ctx, next) {
  console.log('post', ctx.request.body);
})
//測試接口
router.get('/getUser', function (ctx, next) {
  //如果id != 1拋出API 異常
  if (ctx.query.id != 1) {
    throw new ApiError(ApiErrorNames.USER_NOT_EXIST);
  }
  ctx.body = {
    username: '王團團',
    age: 1
  }
})

module.exports = router

然后訪問127.0.0.1:3001/users/getUser,127.0.0.1:3001/users/getUser?id=1比對測試結果

5.  引入ORM工具 Sequelize

參考文章為 https://github.com/demopark/sequelize-docs-Zh-CN

5.1 安裝Sequelize

// 使用 NPM
npm --registry https://registry.npm.taobao.org install --save sequelize
//根據需求選擇
npm --registry https://registry.npm.taobao.org install --save pg pg-hstore npm --registry https://registry.npm.taobao.org install --save mysql2 npm --registry https://registry.npm.taobao.org install --save sqlite3 npm --registry https://registry.npm.taobao.org install --save tedious // MSSQL

如果提示Please try running this command again as root/Administrator.  錯誤, 先用管理員權限執行cmd窗口,然后進入項目,再執行上面語句,還提示同樣的錯誤,就關掉現有窗口,重新打開窗口執行。

5.2 安裝Installing CLI 進行數據遷移

npm --registry https://registry.npm.taobao.org install --save sequelize-cli

5.3 遷移框架初始化

初始化命令

sequelize init

  這將默認創建以下文件夾

  • config, 包含配置文件,它告訴CLI如何連接數據庫
  • models,包含您的項目的所有模型
  • migrations, 包含所有遷移文件
  • seeders, 包含所有種子文件

如果要創建自定義的文件目錄,需在項目根目錄下新建 .sequelizerc文件,配置如下:

const path = require('path');

module.exports = {
  'config': path.resolve('config', 'config.js'),
  'models-path': path.resolve('db', 'models'),
  'seeders-path': path.resolve('db', 'seeders'),
  'migrations-path': path.resolve('db', 'migrations')
}

Sequelize CLI可以從“JSON”和“JS”文件中讀取。所以默認的配置文件名字database.json和自定義的config.js都可以。

執行 目錄初始化語句后 修改config/config.js文件內容

module.exports = {
  development: {
    username: 'database_dev',
    password: 'database_dev',
    database: 'database_dev',
    host: '127.0.0.1',
    dialect: 'mysql'
  },
  test: {
    username: process.env.CI_DB_USERNAME,
    password: process.env.CI_DB_PASSWORD,
    database: process.env.CI_DB_NAME,
    host: '127.0.0.1',
    dialect: 'mysql'
  },
  production: {
    username: process.env.PROD_DB_USERNAME,
    password: process.env.PROD_DB_PASSWORD,
    database: process.env.PROD_DB_NAME,
    host: process.env.PROD_DB_HOSTNAME,
    dialect: 'mysql'
  }
}

如果你的數據庫還不存在,你可以調用Sequelize db:create xxx 命令。

如果想實現下面這種形式的配置

test: {
    username: process.env.CI_DB_USERNAME,
    password: process.env.CI_DB_PASSWORD,
    database: process.env.CI_DB_NAME,
    host: '127.0.0.1',
    dialect: 'mysql'
  }

需要先在項目根目錄下新建  .env文件,然后把定義好變量名稱和變量值,例如

LOCAL_DATABASE="testdb"
LOCAL_USERNAME="root"
LOCAL_PASSWORD="!QAZ2wsx"

然后安裝dotenv插件

npm --registry https://registry.npm.taobao.org install --save dotenv

如果出現權限問題,關掉cmd窗口,重新用管理員權限打開窗口再執行上面命令。

然后在config.js 文件中引入

require('dotenv').config();

完整的內容為

require('dotenv').config();
module.exports = {
  development: {
    username: process.env.LOCAL_USERNAME,
    password: process.env.LOCAL_PASSWORD,
    database: process.env.LOCAL_DATABASE,
    host: '127.0.0.1',
    dialect: 'mysql'
  },
  test: {
    username: 'root',
    password: '!QAZ2wsx',
    database: 'testdb1',
    host: '127.0.0.1',
    dialect: 'mysql'
  },
  production: {
    username: 'root',
    password: '!QAZ2wsx',
    database: 'testdb2',
    host: '127.0.0.1',
    dialect: 'mysql'
  }
}

之前配置的development.js,test.js,production.js三個環境變量配置文件可以刪掉,整合到config.js,修改后為

const fs = require('fs');
require('dotenv').config();
module.exports = {
  development: {
    username: process.env.LOCAL_USERNAME,
    password: process.env.LOCAL_PASSWORD,
    database: process.env.LOCAL_DATABASE,
    host: '127.0.0.1',
    dialect: 'mysql',
    port:'3306',
  webport:'3001' }, test: { username: 'root', password: '!QAZ2wsx', database: 'testdb2', host: '127.0.0.1', dialect: 'mysql', port:'3306',
  webport:'3002' }, production: { username: 'root', password: '!QAZ2wsx', database: 'testdb3', host: '127.0.0.1', dialect: 'mysql', port:'3306',
  webport:'3003' } }

config/index.js文件也需要修改

var db = require('./config');

//根據不同的NODE_ENV,輸出不同的配置對象,默認輸出development的配置對象
module.exports = {
    development: db.development,
    test: db.test,
    production:db.production
}[process.env.NODE_ENV || 'development']

 

5.4 創建model

sequelize model:generate --name UserInfo --attributes firstName:string,lastName:string,email:string

在 models 文件夾中創建了一個 UserInfo 模型文件
在 migrations 文件夾中創建了一個名字像 XXXXXXXXXXXXXX-create-userinfo.js 的遷移文件

Sequelize 將只使用模型文件,它是表描述。另一邊,遷移文件是該模型的更改,或更具體的是說 CLI 所使用的表。 處理遷移,如提交或日志,以進行數據庫的某些更改。

系統默認的環境變量是development,在model/index.js中配置

var env       = process.env.NODE_ENV || 'development';
var config    = require(__dirname + '/..\..\config\config.js')[env];

如果想使用test環境或者production的環境變量生成,則執行下面命令

set NODE_ENV=test&&sequelize db:migrate

當沒用運行服務,只是在命令行執行設置當前環境變量的命令是

set NODE_ENV=test

5.5 執行遷移

sequelize db:migrate

此命令將執行這些步驟

  • 將在數據庫中確保一個名為 SequelizeMeta 的表。 此表用於記錄在當前數據庫上運行的遷移
  • 開始尋找尚未運行的任何遷移文件。 這可以通過檢查 SequelizeMeta 表。 在這個例子中,它將運行我們在最后一步中創建的 XXXXXXXXXXXXXX-create-userinfo.js 遷移,。
  • 創建一個名為 UserInfos 的表,其中包含其遷移文件中指定的所有列。

執行配置的數據庫中會生成兩個表sequelizemeta, userinfos

5.6 撤銷遷移

5.6.1 恢復最近的遷移

sequelize db:migrate:undo

5.6.2 撤消所有遷移,可以恢復到初始狀態

Sequelize db:migrate:undo:all

5.6.3 恢復到特定的遷移

sequelize db:migrate:undo:all --to XXXXXXXXXXXXXX-create-posts.js

5.7 創建種子

有些時候需要在表中插入默認值,所以可以創建一個種子文件,它會將一個演示用戶添加到我們的 UserInfos 表中

sequelize seed:generate --name demo-user

這個命令將會在 seeders 文件夾中創建一個種子文件。文件名看起來像是 XXXXXXXXXXXXXX-demo-user.js,它遵循相同的 up/down 語義,如遷移文件。

現在我們應該編輯這個文件,將演示用戶插入User表。

'use strict';

module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.bulkInsert('UserInfos', [{
        firstName: 'John',
        lastName: 'Doe',
        email: 'demo@demo.com'
      }], {});
  },

  down: (queryInterface, Sequelize) => {
    return queryInterface.bulkDelete('UserInfos', null, {});
  }
};

5.8 執行種子文件

sequelize db:seed:all

執行命令后可能會出現下面錯誤提示

ERROR: Field 'createdAt' doesn't have a default value

是因為默認生成的createdAt, updatedAt兩個屬性需要設定默認值,所以修改XXXXXXXXXXXXXX-demo-user.js文件為

'use strict';

module.exports = {
  up: (queryInterface, Sequelize) => {
    var now = new Date();
    return queryInterface.bulkInsert('UserInfos', [{
      firstName: 'John',
      lastName: 'Doe',
      email: 'demo@demo.com',
      createdAt: now,
      updatedAt: now
    }], {});
  },

  down: (queryInterface, Sequelize) => {
    return queryInterface.bulkDelete('UserInfos', null, {});
  }
};

這將執行該種子文件,您將有一個演示用戶插入 UserInfos 表。

5.9 撤銷種子

5.9.1 撤銷最近的種子

sequelize db:seed:undo

5.9.2 撤銷所有種子

sequelize db:seed:undo:all

6 Sequelize使用

6.1 測試數據庫連接

新建db/dbInstance.js 文件,在此初始化數據庫連接

var config = require('../config');
const Sequelize = require('sequelize');

const sequelize = new Sequelize(config.database, config.username, config.password, {
    host: config.host,
    dialect: 'mysql',
  
    pool: {
      max: 5,
      min: 0,
      idle: 10000
    }
  });

  module.exports = sequelize

在路由接口 routes/user.js 中引入dbInstance.js文件

var sequelize = require('../db/dbInstance');

然后在user.js 文件中新建一個接口方法

router.get('/search', function (ctx, next) {
  sequelize
  .authenticate()
  .then(() => {
    console.log('Connection has been established successfully.');
  })
  .catch(err => {
    console.error('Unable to connect to the database:', err);
  });
  ctx.body="this is a test function";
})

測試數據庫連接成功,則命令行log日志輸出

Executing (default): SELECT 1+1 AS result
Connection has been established successfully.

一個簡單的查詢應用

在routes/user.js文件中加入下面代碼

const Sequelize = require('sequelize');

var UserInfo = sequelize.define('UserInfo', {
  firstName: Sequelize.STRING,
  lastName: Sequelize.STRING,
  email: Sequelize.STRING
}
);

router.get('/search', function (ctx, next) {

  (async () => {
    var users = await UserInfo.findAll({
        // where: {
        //     lastName: 'Doe'
        // }
    });
    console.log(`find ${users.length} users:`);
    for (let u of users) {
        console.log(JSON.stringify(u));
    }
})();

  ctx.body = "this is a test function";
})

再次訪問此方法,命令行log日志輸出

GET /users/search - 6ms
  --> GET /users/search 200 17ms 23b
Executing (default): SELECT `id`, `firstName`, `lastName`, `email`, `createdAt`, `updatedAt` FROM `UserInfos` AS `UserInfo`;
find 3 users:
{"id":1,"firstName":"John1","lastName":"Doe1","email":"demo@demo.com","createdAt":"2017-12-14T11:10:24.000Z","updatedAt":"2017-12-14T11:10:24.000Z"}
{"id":2,"firstName":"John2","lastName":"Doe2","email":"demo@demo.com","createdAt":"2017-12-14T11:10:50.000Z","updatedAt":"2017-12-14T11:10:50.000Z"}
{"id":3,"firstName":"John3","lastName":"Doe3","email":"demo@demo.com","createdAt":"2017-12-14T11:10:52.000Z","updatedAt":"2017-12-14T11:10:52.000Z"}

 更加簡潔的post,get方法例子如下

//定義模型
var UserInfo = sequelize.define("UserInfo", {}, {});
router.post(
'/post', function (ctx, next) { console.log(ctx.request.body); let id = ctx.request.body.id || 0; ctx.body = "you post data:" + JSON.stringify({ id: id }); }); router.get('/search', async function (ctx, next) { var userinfos = await UserInfo.findAll(); ctx.body = JSON.stringify(userinfos); })
//post 傳入的參數  
{
"id":"124", "name":"test" }
[{"key":"Content-Type","value":"application/json","description":"","enabled":true}]

 

 其它操作參見https://github.com/demopark/sequelize-docs-Zh-CN文檔介紹。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM