1. 程式人生 > >koa連線mysql專案例子

koa連線mysql專案例子

koa路由層封裝

最近做了一個koa的例子,封裝了一些controller層和services層

技術棧採用的是koa2 + koa-router + mysql

  • controller : 負責直接和資料庫進行連線(寫sql, 對引數進行處理)

  • services: 負責傳輸資料

  • route: 負責定義路面的路由

簡單看一下專案的目錄結構

這便是其中最重要的三層結構

入口檔案是index.js,我們簡單來看一下原始碼

var Koa=require('koa');
var path=require('path')
var bodyParser = require('koa-bodyparser');
var session = require('koa-session-minimal');
var MysqlStore = require('koa-mysql-session');
var config = require('./config/default.js');
var koaStatic = require('koa-static')
var app=new Koa()
const routers = require('./routes/index')
​
// session儲存配置
const sessionMysqlConfig= {
  user: config.database.USERNAME,
  password: config.database.PASSWORD,
  database: config.database.DATABASE,
  host: config.database.HOST,
}
​
// 配置session中介軟體
app.use(session({
  key: 'USER_SID',
  store: new MysqlStore(sessionMysqlConfig)
}))
​
// 配置跨域
app.use(async (ctx, next) => {
  ctx.set('Access-Control-Allow-Headers', 'Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With')
  ctx.set('Access-Control-Allow-Origin', 'http://localhost:9000');
  ctx.set('Access-Control-Allow-Methods', 'PUT,DELETE,POST,GET');
  ctx.set('Access-Control-Allow-Credentials', true);
  ctx.set('Access-Control-Max-Age', 3600 * 24);
  await next();
});
// 配置靜態資源載入中介軟體
app.use(koaStatic(
  path.join(__dirname , './public')
))
​
// 使用表單解析中介軟體
app.use(bodyParser())
​
// 使用新建的路由檔案
app.use(routers.routes()).use(routers.allowedMethods())
​
// 監聽在1200
app.listen(config.port)
​
console.log(`listening on port ${config.port}`)
​

中間讀取了資料庫配置和進行跨域的設定以及表單解析,其中路由定義是在引用router

我們來看一下router的定義

​
​
const router = require('koa-router')()
// 配置所有的routes檔案
const routes = (config => {
    return config.reduce((copy, name) => {
    // 這裡是請求對應的路由模組,獲得對應的物件
    const obj = require(`./${name}`)
    const newArr = Object.keys(obj).reduce((total, each) => {
      let item = { path: `/api/${name}/${each}`, method: obj[each].method, action: each, service: name }
      total.push(item)
      return total
    }, [])
    copy = copy.concat(newArr)
      return copy
    }, [])
})([
  'admin',
  'user',
  'guider',
  'order',
])
/**
 * 整合所有子路由
 */
// 配置最終的路由,形式為
// router.get(url, service.action)
routes.forEach(item => {
  const service = require(`../services/${item.service}`)
  router[item.method](item.path, service[item.action])
})
module.exports = router
​
​

 

這是對應的routes的輸出

[ { path: '/api/admin/list',
    method: 'get',
    action: 'list',
    service: 'admin' },
  { path: '/api/admin/add',
    method: 'post',
    action: 'add',
    service: 'admin' },
  { path: '/api/admin/update',
    method: 'post',
    action: 'update',
    service: 'admin' },
  { path: '/api/admin/del',
    method: 'post',
    action: 'del',
    service: 'admin' },
  { path: '/api/admin/login',
    method: 'post',
    action: 'login',
    service: 'admin' },
  { path: '/api/admin/single',
    method: 'post',
    action: 'single',
    service: 'admin' },
  { path: '/api/user/list',
    method: 'get',
    action: 'list',
    service: 'user' },
  { path: '/api/user/add',
    method: 'post',
    action: 'add',
    service: 'user' },
  { path: '/api/user/update',
    method: 'post',
    action: 'update',
    service: 'user' },
  { path: '/api/user/del',
    method: 'post',
    action: 'del',
    service: 'user' },
  { path: '/api/user/single',
    method: 'post',
    action: 'single',
    service: 'user' },
  { path: '/api/guider/list',
    method: 'get',
    action: 'list',
    service: 'guider' },
  { path: '/api/guider/add',
    method: 'post',
    action: 'add',
    service: 'guider' },
  { path: '/api/guider/update',
    method: 'post',
    action: 'update',
    service: 'guider' },
  { path: '/api/guider/del',
    method: 'post',
    action: 'del',
    service: 'guider' },
  { path: '/api/guider/single',
    method: 'post',
    action: 'single',
    service: 'guider' },
  { path: '/api/order/list',
    method: 'get',
    action: 'list',
    service: 'order' },
  { path: '/api/order/add',
    method: 'post',
    action: 'add',
    service: 'order' },
  { path: '/api/order/update',
    method: 'post',
    action: 'update',
    service: 'order' },
  { path: '/api/order/del',
    method: 'post',
    action: 'del',
    service: 'order' },
  { path: '/api/order/listu',
    method: 'post',
    action: 'listu',
    service: 'order' },
  { path: '/api/order/listb',
    method: 'post',
    action: 'listb',
    service: 'order' },
  { path: '/api/order/single',
    method: 'post',
    action: 'single',
    service: 'order' } ]

接下來我們看一下單個route模組的定義

admin為例:

// 封裝好的模版物件,裡面有一些最基本的方法定義
// add,list,delete,update
const model = require('../model')
// 方法物件
const methods = require('../methods')
module.exports = {
  ...model,
  'login': { method: methods.post },
  'single': { method: methods.post },
}

model物件如下:

const methods = require('../methods')
​
module.exports = {
  'list': { method: methods.get },
  'add': { method: methods.post },
  'update': { method: methods.post },
  'del': { method: methods.post },
}
​

methods物件如下:

module.exports = {
  get: 'get',
  post: 'post',
}

到這裡,我們的route封裝已經完成,其實就是把一堆路由的定義化整為零變成了一個個資料夾下的js檔案,變成了一個個物件

然後通過在route/index.js裡面的定義對相應的service層進行呼叫對應的方法,其中名字的命名需要一致

我們來看一下services下的各個模組

以admin為例:

services的執行邏輯為

  • 拿到引數

  • 呼叫controller對應的方法

  • 處理結果

  • 返回結果

// 請求對應的controller模組
const controller = require('../../controller/admin')
// 請求對應的模版物件
const model = require('../model')
// 封裝的pojo訊息集,對返回資料的處理
const pojo = require('../../helper/pojo')
// success: 成功返回
// failed: 失敗返回
// filterUnderLine: 處理駝峰和下劃線的區別
const { success, failed, filterUnderLine }  = pojo
const m  = model([
  'list',
  'add',
  'update',
  'del',
], 'admin')
​
// 查詢單個物件
const single = async ctx => {
  let res;
  try {
    // 獲取的到引數 
    const val = ctx.request.body
    // 呼叫對應的controller層
    await controller.single(val).then(result => {
      // 對返回結果進行處理
      if(result.length === 0 || result === null || result === undefined)  
        // 等於0或者null | undefined 返回失敗的物件
        res = failed('操作失敗')
      else 
        // 其他返回成功的物件,處理一下從資料庫拿到的資料
        res = success(filterUnderLine(result[0]))
    })
  } catch(err) {
    // 出錯返回失敗的物件
    res = failed(err)
  }
  // 資料返回給前臺
  ctx.body = res
}
const login = async ctx => {
  let res;
  try {
    const val = ctx.request.body
    await controller.login(val).then(result => {
      if(result.length === 0 || result === null || result === undefined)  
        res = failed('使用者名稱或密碼不對')
      else 
        res = success(filterUnderLine(result[0]))
    })
  } catch(err) {
    res = failed(err)
  }
  ctx.body = res
}
// 模組的對應匯出
module.exports = {
  ...m,
  login,
  single,
}

 

services的model物件

const pojo = require('../../helper/pojo')
const { success, failed, successWithCode, filterUnderLine } = pojo
const list = [
  'del',
  'add',
  'update',
]
/**
 * 
 * @param {*} config  對應的方法,要定義的哪幾個方法模組,單個services層傳入
 * @param {*} file 對應的controller檔名稱
 * @return 返回一個對應好的物件
 */
module.exports = (config, file) => {
  const controller = require(`../../controller/${file}`)
    return config.reduce((copy, name) => {
    copy[name] = async ctx => {
      let res;
      try {
        const val = ctx.request.body
        await controller[name](val).then(result => {
          // 沒有資料返回的介面直接返回msg和code
          if (list.indexOf(name) !== -1) {
            res = successWithCode('操作成功')
            return
          }
          // 其他模組方法直接過濾資料下劃線
          const arr = result.map(item => filterUnderLine(item))
          res = success(arr)
        })
      } catch(err) {
        res = failed(err)
      }
      ctx.body = res
    }
      return copy
    }, {})
}
​

 

到了這裡,就剩下對controller層的呼叫,也就是對資料庫的真正操作

以admin為例:

一個controller的方法邏輯為

  • 接受引數

  • 編寫sql

  • 呼叫資料庫連線池執行

  • 返回結果

const pool = require('../../lib/mysql')
const { NtNUpdate } = require('../../helper')
const { STATUS } = require('../../enum')
const { query } = pool
// 新添管理員
const add = (val) => {
  const { account, phone, password, name, creator, type } = val
  // 寫好的sql語句
  const _sql = 'insert into tour_admin(account,phone,password,create_time,creator,name,type,status) values(?,?,?,now(),?,?,?,?);'
  // 執行資料庫並且返回結果
  return query( _sql, [ account, phone, password, creator, name, type, STATUS.NORMAL,])
}
​
const login = (val) => {
  const { account, password } = val
  const _sql = 'select * from tour_admin where account = ? and password = ? and status = ?'
  return query( _sql, [ account, password, STATUS.NORMAL ] )
}
​
// 更改管理員
const update = (val) => {
  const { account, phone, password, name, type, id } = val
  let _sql = 'update tour_admin set '
  const { sql, args } = NtNUpdate({ account, phone, password, name, type }, _sql)
  _sql = sql + 'where id = ?'
  const arr = [ ...args, id]
  return query( _sql, arr )
}
​
// 查詢管理員
const list = val => {
  const sql = 'select * from tour_admin where status != ?'
  return query(sql, [ STATUS.DELED ])
}
​
// 查詢單個管理員byId
const single = val => {
  const { id } = val
  const sql = 'select * from tour_admin where status != ? and id = ?'
  return query(sql, [ STATUS.DELED, id ])
}
​
// 刪除管理員
const del = val => {
  const { id } = val
  const sql = 'update tour_admin set status = ? where id = ?'
  return query(sql, [ STATUS.DELED, id ])
}
​
module.exports = {
  add,
  list,
  update,
  del,
  login,
  single,
}

helper如下

/**
 * 
 * @param {*} params  引數物件
 * @param {*} sql sql語句
 * @description 根據引數物件去改變sql語句,最後返回對應的sql語句
 * @return 返回處理後的sql語句
 */
const update = (params, sql) =>  {
  let keys = Object.keys(params)
  let arr = []
  keys.forEach((key) => {
    if (key) {
      sql = sql + `${key} = ? ,`
      arr.push(params[key])
    }
  })
  sql = sql.substring(0, sql.length - 1)
  return {
    args: arr,
    sql,
  }
}
​
module.exports = {
  NtNUpdate: update,
}
​

enum如下

const status = require('./status')
const type = require('./status')
​
module.exports = {
  STATUS: status,
  TYPES: type,
}
​

Status如下

module.exports = {
  DELED: 404,
  ABA: 111,
  NORMAL: 0,
}

lib/mysql如下

const mysql = require('mysql');
const config = require('../config/default.js')
​
const pool  = mysql.createPool({
  host     : config.database.HOST,
  user     : config.database.USERNAME,
  password : config.database.PASSWORD,
  database : config.database.DATABASE
});
​
/**
 * 
 * @param sql 接收的sql語句
 * @param values 接受的引數: 為陣列
 */
const query = function( sql, values ) {
  return new Promise(( resolve, reject ) => {
    pool.getConnection(function(err, connection) {
      if (err) {
        console.log(err)
        resolve( err )
      } else {
        connection.query(sql, values, ( err, rows) => {
          if ( err ) {
            reject( err )
          } else {
            resolve( rows )
          }
          connection.release()
        })
      }
    })
  })
}
module.exports={
  query,
}

到這裡,我們就實現了koa的相關操作了。

 

最後,這是專案地址,歡迎star,