在Sequelize中使用遷移

qbylucky發表於2019-05-10

Sequelize是Nodejs生態中一個比較出名的ORM框架。通過ORM框架,可以使用物件來運算元據庫表資料,提高了開發效率和程式碼可讀性,也方便後期維護。

今天主要介紹通過遷移[Migration]來建立資料庫,表。

遷移的好處,可以類比git。通過每次建立遷移檔案,來支援更新,回滾資料庫表結構,也方便協同開發,也避免人工手動去直接修改資料庫,用程式碼自動管理。換個電腦,也不用去拷貝資料庫,直接執行遷移就可以完全恢復開發環境,極大減輕了心智負擔。

1. 建立專案, 安裝node package依賴

mkdir node_work

cd node_work

mkdir app

npm init -y

npm i sequelize-cli sequelize mysql2 koa
複製程式碼

2. 初始化Sequelize

npx sequelize init
複製程式碼

執行之後,會產生四個目錄:

config, migrations, models, seeders


config:

{
  "development": {
    "username": "root",
    "password": "root",
    "database": "app_development",
    "host": "127.0.0.1",
    "port": 8889,
    "dialect": "mysql",
    "timezone": "+08:00"
  },
  "test": {
    "username": "root",
    "password": null,
    "database": "app_test",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "production": {
    "username": "root",
    "password": null,
    "database": "app_production",
    "host": "127.0.0.1",
    "dialect": "mysql"
  }
}
複製程式碼

環境env => {配置}

不同環境,對應不同的配置,也可以自定義環境,比如home

env指的是process.env.NODE_ENV

可以通過設定環境變數來改變,比如export NODE_ENV=production

遷移時候,也可以指定環境:npx sequelize db:migrate --env production,來連線production對應配置的資料庫

建立資料庫:

npx sequelize db:create
複製程式碼

說明npx是npm5.2之後,自帶的一個命令。可以不用全域性安裝sequelize,使用時候,如果本地沒有,就去npm倉庫下載;下載完後或者本地已經下載過,就執行指令碼命令。這樣可以避免本地全域性包過期,環境問題,每次都使用最新版本


migrations: 遷移檔案

npx sequelize model:generate --name User --attributes username:string
複製程式碼

執行後,會生成20180918055558-create-user.js遷移檔案,和models/user.js模型檔案

其他欄位可以在遷移檔案中補全,最後再執行npx sequelize db:migrate,就可以在資料庫中看到生成了users表

'use strict';
  module.exports = {
    up: (queryInterface, Sequelize) => {
      return queryInterface.createTable('Users', {
        id: {
          allowNull: false,
          autoIncrement: true,
          primaryKey: true,
          type: Sequelize.INTEGER
        },
        username: {
          type: Sequelize.STRING(20),
          allowNull: false
        },
        password: {
          type: Sequelize.CHAR(32),
          allowNull: false
        },
        createdAt: {
          allowNull: false,
          type: Sequelize.DATE
        },
        updatedAt: {
          allowNull: false,
          type: Sequelize.DATE
        }
      }, {
        tableName: 'users',
        charset: 'utf8mb4',
        collate: 'utf8mb4_bin',
        define: {
          timestamps: true
        }
      }).then(() => {
        // 新增索引
        return queryInterface.addIndex('users', {
          name: 'username',
          unique: true,
          fields: ['username']
        });
      });
    },
    
    // 回退時執行,刪除表
    down: (queryInterface, Sequelize) => {
      return queryInterface.dropTable('Users');
    }
  };
複製程式碼

執行遷移:

npx sequelize db:migrate

npx sequelize db:migrate:all
複製程式碼

撤銷遷移:

npx sequelize db:migrate:undo 最近一次的

npx sequelize db:migrate:undo:all

npx sequelize db:migrate:undo:all --to XXXXXXXXXXXXXX-create-posts.js
複製程式碼

--from, --to 引數,可以指定遷移檔案


models: 模型檔案

model:generate生成的model都在這個目錄中

'use strict';
module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.createTable('Users', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      username: {
        type: Sequelize.STRING(20),
        allowNull: false
      },
      password: {
        type: Sequelize.CHAR(32),
        allowNull: false
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    }, 
    {
      tableName: 'users',
      charset: 'utf8mb4',
      collate: 'utf8mb4_bin',
    }).then(() => {
      return queryInterface.addIndex('users', {
        name: 'username',
        unique: true,
        fields: ['username']
      });
    });
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.dropTable('Users');
  }
};
複製程式碼

模型物件建立,預設會自動賦值,更新createdAt, updatedAt兩個timestamps欄位。下邊會給出完整示例。


seeders: 填充資料檔案

建立seed檔案:

npx sequelize seed:generate --name demo-user
複製程式碼

執行之後,會得到20180918090545-demo-user.js

'use strict';

const md5 = require('md5')

module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.bulkInsert('Users', [
      {
        username: 'Kimoo',
        password: md5('123456'),
        createdAt: new Date(),
        updatedAt: new Date(),
      },
      {
        username: 'Reci',
        password: md5('123321'),
        createdAt: new Date(),
        updatedAt: new Date(),
      }
    ], {});
  },

  down: (queryInterface, Sequelize) => {
    /*
      Add reverting commands here.
      Return a promise to correctly handle asynchronicity.

      Example:
      return queryInterface.bulkDelete('Person', null, {});
    */
    return queryInterface.bulkDelete('Users', null, {});
  }
};

複製程式碼

填充資料:

npx sequelize db:seed:all

複製程式碼

撤銷資料:

npx sequelize db:seed:undo 最近一次的

npx sequelize db:seed:undo --seed name-of-seed-as-in-data 具體某個

npx sequelize db:seed:undo:all

複製程式碼

3. 具體實踐

app.js

(async function() {

    const Koa = require('koa');
    const KoaStaticCache = require('koa-static-cache');
    const KoaBodyParser = require('koa-bodyparser');
    const router = require('./routers/main');
    const Session = require('koa-session');

    const app = new Koa();

    // app.keys = new KeyGrip(['im a newer secret', 'i like turtle'], 'sha256');

    app.keys = ['app'];

    app.use( Session({
        key: 'koa:sess',
        maxAge: 86400000,
        autoCommit: true,
        overwrite: true,
        httpOnly: true,
        signed: true,
        rolling: false,
        renew: false
    }, app) );

    // app.use( async (ctx, next) => {
    //     ctx.set('Access-Control-Allow-Origin','*');
    //     await next();
    // } );

    app.use( KoaStaticCache('./public', {
        prefix: 'public',
        gzip: true
    }) );

    app.use( KoaBodyParser() );

    app.use( router.routes() );

    app.listen(8088);

})();
複製程式碼

models/index.js

'use strict';

const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + '/../config/config.json')[env];
const db = {};

let sequelize;
if (config.use_env_variable) {
  sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
  sequelize = new Sequelize(config.database, config.username, config.password, config);
}

// 自動匯入 models 資料夾下的所有檔案,比如user.js這個模型檔案

// 自動載入模型並執行
// let users = require('./users');
// let UsersModel = users(sequelize, Sequelize);
// db[UsersModel.name] = UsersModel; // db['Users'] = UsersModel;


// 下面通過fs自動載入所有的檔案,並執行,同時生成的模型物件掛載到db物件下面,最後返回出去
fs
  .readdirSync(__dirname)
  .filter(file => {
    return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
  })
  .forEach(file => {
    const model = sequelize['import'](path.join(__dirname, file));
    db[model.name] = model;
  });

Object.keys(db).forEach(modelName => {
  if (db[modelName].associate) {
    db[modelName].associate(db);
  }
});

db.sequelize = sequelize;
db.Sequelize = Sequelize;

module.exports = db;
複製程式碼

routers/main.js

const KoaRouter = require('koa-router');
const md5 = require('md5');
const Models = require('../models');
const Sequelize = require('sequelize');

const router = new KoaRouter();

router.post('/register', async ctx => {

    // console.log(ctx.request.body);
    let username = ctx.request.body.username.trim();
    let password = ctx.request.body.password.trim();
    let repassword = ctx.request.body.repassword.trim();

    if (username=='' || password == '' || repassword == '') {
        return ctx.body = {
            code: 1,
            data: '使用者名稱或密碼不能為空'
        }
    }
    if (password != repassword) {
        return ctx.body = {
            code: 2,
            data: '兩次輸入的密碼不一致'
        }
    }

    let user = await Models.Users.findOne({
        where: {
            username
        }
    });

    if (user !== null) {
        return ctx.body = {
            code: 3,
            data: '當前使用者已經被註冊了'
        }
    }

    let newUser = await Models.Users.build({
        username,
        password: md5(password)
    }).save();

    ctx.body = {
        code: 0,
        data: {
            id: newUser.get('id'),
            username: newUser.get('username')
        }
    }

  });

  router.post('/login', async ctx => {
      let username = ctx.request.body.username;
      let password = ctx.request.body.password;

      let user = await Models.Users.findOne({
          where: {
              username
          }
      });

      if (user === null) {
          return ctx.body = {
              code: 1,
              data: '不存在該使用者'
          }
      }

      if (user.get('password') !== md5(password)) {
          return ctx.body = {
              code: 1,
              data: '密碼錯誤'
          }
      }

      
      // ctx.cookies.set('uid', user.get('id'), {
      //     httpOnly: false
      // });

      // 服務端傳送一個約定好的cookie,來表示當前是登入
      // ctx.cookies.set('uid', user.get('id'), {
      //     // httpOnly,表示當前的cookie是否允許客戶端進行操作(js),如果為true,那麼就表示這個cookie是能使用者http協議的資料傳輸
      //     httpOnly: true,
      //     signed: true
      // });
      ctx.cookies.set('username', user.get('username'), {
          httpOnly: false
      });

      ctx.session.uid = 1;

      ctx.body = {
          code: 0,
          data: {
              id: user.get('id'),
              username: user.get('username')
          }
      }
  });

})

module.exports = router;
複製程式碼

4. 測試介面,註冊使用者,新增資料

可以在postman中測試介面,地址http://localhost:8088/register,註冊使用者

node app.js
複製程式碼

在Sequelize中使用遷移

相關文章