koa連線mysql專案例子
阿新 • • 發佈:2018-11-17
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,