sequelize 連接sql server服務的簡單封裝

最近的項目,用nodejs做的webapi,連接sql server數據庫用的sequelize框架,現在把我自己簡單封裝的sequelize服務記錄下來,以備參考:

sql.js

const Sequelize = require('sequelize')
const fs = require('fs')
const path = require('path')

const basePath = path.resolve('./')

const db = {
  database: 'TestMall',
  username: 'sa',
  password: 'whb',

  host: '192.168.1.101',
  port: 1433,
  
  dialect: 'mssql',
  // close log
  logging: false,
  // "timestamps: false" fixed Unknown column 'createdAt' in 'field list'
  timestamps: false,
  dialectOptions: {
    multipleStatements: true
  }
}
//const sequelize = new Sequelize('postgres://user:[email protected]:5432/dbname');
const sequelize = new Sequelize(db)

sequelize.sync()

sequelize.authenticate().then(() => {
    console.log('Connection has been established successfully.')
}).catch(err => {
    console.error('Unable to connect to the database:', err)
})

//sequelize.close()

// 統一事件調用方法,避免業務邏輯代碼裏大量重複的then()cathc()代碼
Sequelize.Model.excute = function (res, action, param, cb) {
  this[action](param[0], (param.length > 1 ? param[1] : null)).then((re) => {
    if (cb) {
      cb(re)
    } else {
      res.json({
        code: 200,
        data: re
      })
    }
  }).catch(err => {
    console.log(err)
    // 錯誤日誌
    fs.writeFile(`${basePath}/logs/sqlerr.log`, err, () => {});
    res.json({
      code: 500,
      data: err
    })
  })
}

// 統一存儲過程、批處理調用方法,避免業務邏輯代碼裏大量重複的then()cathc()代碼
Sequelize.Model.exec_proc = function (res, sql, param, cb) {
  sequelize.query(sql, {
    replacements: param
  }).then(re => {
    if (re[0][0].msg) {
      // 錯誤日誌
      fs.writeFile(`${basePath}/logs/sqlerr.log`, re[0][0].msg, () => {});
      res.json({
        code: 500,
        data: re[0][0].msg
      })
    } else {
      if (cb) {
        cb(re[0])
      } else {
        res.json({
          code: 200,
          data: re[0]
        })
      }
    }
  }).catch(err => {
    // 錯誤日誌
    fs.writeFile(`${basePath}/logs/sqlerr.log`, err, () => { });
    res.json({
      code: 500,
      data: err
    })
  })
}
module.exports = sequelize

考慮到新手不容易理解和接受,現在添加幾張數據表映射模型定義和幾個簡單調用例子:

model.js

const Sequelize = require('sequelize')
const sequelize = require('./sql')
const Model = Sequelize.Model

//#region 數據模型申明
class user extends Model {}
class goods extends Model {}
class files extends Model {}
class orders extends Model {}
//#endregion

//#region 數據模型定義
/**
 * 用戶賬號信息表
 */
user.init({
  id: {
    primaryKey: true,
    type: Sequelize.UUID,
    defaultValue: Sequelize.UUIDV1,
    allowNull: false,
    field: 'xGUID'
  },
  account: {
    type: Sequelize.STRING,
    allowNull: false,
    field: 'Account'
  },
  name: {
    type: Sequelize.STRING,
    field: 'Name'
  },
  password: {
    type: Sequelize.BOOLEAN,
    allowNull: false,
    field: 'Password'
  }
}, {
  sequelize,
  timestamps: false,
  tableName: 'Person'
})

/**
 * 商品表
 */
goods.init({
  id: {
    primaryKey: true,
    type: Sequelize.UUID,
    defaultValue: Sequelize.UUIDV1,
    allowNull: false,
    field: 'xGUID'
  },
  /**
   * book's name
   */
  name: {
    type: Sequelize.STRING,
    allowNull: false,
    field: 'Name'
  },
  /**
   * price
   * 定價
   */
  price: {
    type: Sequelize.REAL,
    allowNull: false,
    field: 'DJ'
  },
}, {
  sequelize,
  timestamps: false,
  tableName: 'Goods'
})

/**
 * 訂單表
 */
orders.init({
  id: {
    primaryKey: true,
    type: Sequelize.UUID,
    defaultValue: Sequelize.UUIDV1,
    allowNull: false,
    field: 'xGUID'
  },
  quantity: {
    type: Sequelize.INTEGER,
    allowNull: false,
    field: 'GoodsID'
  },
  amount: {
    type: Sequelize.REAL,
    allowNull: false,
    field: 'Amount'
  },
  /**
   * 外鍵, 關聯商品表主鍵
   */
  goodsID: {
    type: Sequelize.STRING,
    allowNull: false,
    field: 'GoodsID'
  },
  /**
   * 外鍵, 關聯用戶表主鍵
   */
  personID: {
    type: Sequelize.STRING,
    allowNull: false,
    field: 'Person_ID'
  }
}, {
  sequelize,
  timestamps: false,
  tableName: 'Orders'
})

/**
 * 資源文件表
 */
files.init({
  id: {
    primaryKey: true,
    type: Sequelize.UUID,
    defaultValue: Sequelize.UUIDV1,
    allowNull: false,
    field: 'xGUID'
  },
  /**
   * 外鍵, 關聯商品表主鍵
   */
  goodsID: {
    type: Sequelize.STRING,
    allowNull: false,
    field: 'LYBMID'
  },
  path: {
    type: Sequelize.STRING,
    allowNull: false,
    field: 'Path'
  }
}, {  
  sequelize,
  timestamps: false,
  tableName: 'File'
})
//#endregion

//#region 表關聯
goods.hasMany(files, {
  as: 'images',
  foreignKey: 'goodsID',
  through: null
});
orders.belongsTo(user, {
  as: 'buyer',
  foreignKey: 'userID',
  through: null
});
orders.belongsTo(goods, {
  as: 'goods',
  foreignKey: 'goodsID',
  through: null
});
files.belongsTo(orders, {
  as: 'order',
  sourceKey: 'goodsID',
  foreignKey: 'userID',
  through: null
});
//#endregion

module.exports = {
  user,
  goods,
  orders,
  files
}

接下來是業務邏輯層的調用例子:

order.js

const express = require('express')
const Sequelize = require('sequelize')
const Op = Sequelize.Op

const {
  user,
  goods,
  files,
  orders
} = require('../models')


Router.post('/add', (req, res) => {
  payInfo.exec_proc(res,"exec proc_addOrder :personID,:addressID,:amount,:goodsIDs,:nums", {
    personID: req.body.personID,
    goodsID: req.body.goodsID,
    quantity: req.body.quantity,
    amount: req.body.amount
  },(re)=>{
    if(re...){
      //處理庫存鎖定等業務。。。
      //存儲過程其實已經做了, 這裏就是表示下這個位置的用法。。。
    }
  })
})

Router.post('/list', (req, res) => {
  orders.excute(res, 'findAndCountAll', [{
    attributes: ['id',  'goodsID', 'buyerID', quantity, amount],
    where: {
      buyerID: req.body.userID,
      status: {
        [Op.ne]: '已取消'
      }
    },
    include: [{
        as: 'goods',
        model: goods,
        attributes: ['id', 'name', 'price'],
        required: false
      }, {
        as: 'images',
        model: files,
        where: {
          status: 0
        },
        attributes: ['id', 'name', 'path'],
        required: false
    }],
    order: [
      ['createTime', 'DESC'],
    ],
    offset: req.body.pageIndex * req.body.pageSize,
    limit: req.body.pageSize
  }])
})

module.exports = Router

最後是路由設定了:

route.js

const express = require('express')
const bodyParser = require('body-parser')
const cookieParser = require('cookie-parser')
const session = require('express-session')
const fs = require('fs')
const path = require('path')

//導入接口文件
const order = require('./controller/order')

// 默認頁,首頁
const pagePath = path.resolve(__dirname, 'public/index.html')
const page = fs.readFileSync(pagePath, 'utf-8', (err, data) => {
  if (err) {
    return err
  }
  return data
})

//創建express應用實例
const app = express()

//啓用session、cookie等
app.use(cookieParser())
app.use(
  session({
    secret: 'keyboard cat',
    // cookie: {
    //   domain: '192.168.2.7',
    //   maxAge: 60 * 60 * 1000,
    //   secure: false
    // },
    resave: false,
    saveUninitialized: true
  })
)

app.use(bodyParser.json())
app.use(
  bodyParser.urlencoded({
    extended: false
  })
)

//設置跨域等頭部參數
app.all('*', function (req, res, next) {
  res.header('Access-Control-Allow-Origin', '*')
  //res.header('Access-Control-Allow-Origin', 'http://www.uni-engine.cn:8060')
  res.header('Access-Control-Allow-Headers', 'Content-Type')
  res.header('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS')
  res.header('Access-Control-Allow-Credentials', true);
  res.header('X-Powered-By', ' 3.2.1')
  res.header('Content-Type', 'application/json;charset=utf-8')
  res.cookie('test', 'sadasdsad')
  next()
})

//指定路由
app.use('/api/v1/order', order)

//允許訪問靜態頁
app.use('/apidoc', (req, res) => {
  res.set('Content-Type', 'text/html')
  res.send(page)
})

//允許訪問指定的靜態資源
app.use('/public', express.static(path.join(__dirname, 'public')))

//端口監聽
app.listen('9000', () => {
  console.log('open Browser on http://127.0.0.1:9000')
})

這樣,一個相對完整的webapi程序的代碼就全有了。。。

下面再貼幾個常用寫法:

關聯查詢加分頁:

Router.post('/list', (req, res) => {
  orders.findAndCountAll({
    attributes: ['id', 'code', 'amount', 'status', 'createTime'],
    where: {
      personID: req.body.personID,
    },
    distinct: true, //避免因連接查詢導致數據重複,統計條數錯誤
    include: [{
      as: 'address',
      model: receiveAddress,
      attributes: ['id', 'area', 'address'],
      required: false
    }, {
      as: 'details',
      model: orderDetail,
      attributes: ['id', 'status', 'quantity'],
      include: [{
        as: 'goods',
        model: goods,
        raw: true, //合併字段
        attributes: ['desc', 'name', 'price', 'salePrice', 'stock'],
        required: false //避免關聯表數據爲空是不返回主表數據
      }, {
        as: 'images',
        model: files,
        where: {
          scenes: '圖書封面'
        },
        //through: false //如果多對多關係,中間有關聯表,這個屬性表示是否留存關聯表結構
        raw: true, //合併字段
        attributes: ['path'],
        required: false //避免關聯表數據爲空是不返回主表數據
      }]
    }],
    order: [
      ['createTime', 'DESC'],
    ],
    offset: req.body.pageIndex * req.body.pageSize,
    limit: req.body.pageSize
  }).then((re)={
    res.Json({
      code: '200',
      data: re
    })
  }).catch((err)=>{
    res.Json({
      code: '500',
      data: err
    })
  })
})

通過調用存儲過程添加訂單:

Router.post('/add', (req, res) => {
  payInfo.sequelize.query("exec proc_addOrder :personID,:addressID,:amount,:goodsIDs,:nums", {
    personID: req.body.personID,
    addressID: req.body.addressID,
    amount: req.body.amount,
    goodsIDs: req.body.goodsID,
    nums: req.body.quantity
  }).then((re) => {
    res.json({
      code: 200,
      data: re[0]
    })
  }).catch(err => {
    res.json({
      code: 500,
      data: err
    })
  })
})

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章