koa@2學習筆記
前言:站在巨人的肩膀上,感謝前輩們的付出與貢獻
安裝 koa 模組
koa 需要 node v7.6.0 及以上版本,提供 ES6 和 async 函式支援
$ npm install koa
新建 hello.js
const Koa = require('koa');
const app = new Koa();
// response
app.use(ctx => {
ctx.body = 'Hello Koa';
});
app.listen(3000);
中介軟體
普通函式
app.use((ctx, next) => {
const start = Date.now();
return next().then(() => {
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
});
async 函式(node v7.6.0+)
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
中介軟體開發
/* ./middleware/logger-async.js */
function log( ctx ) {
console.log( ctx.method, ctx.header.host + ctx.url )
}
module.exports = function () {
return async function ( ctx, next ) {
log(ctx);
await next()
}
}
/* index.js */
const Koa = require('koa')
const loggerAsync = require('./middleware/logger-async')
var app = new Koa()
app.use(loggerAsync())
app.use((ctx) => {
ctx.body = 'hello world'
})
app.listen(3000, 'localhost', () => {
console.log('starting on port: ', 3000)
})
//控制檯
PS D:\workspace\koa2demo> node .\index.js
starting on port: 3000
GET localhost:3000/
理解 async/await
function getSyncTime() {
return new Promise((resolve, reject) => {
try {
let startTime = new Date().getTime()
setTimeout(() => {
let endTime = new Date().getTime()
let data = endTime - startTime
resolve(data)
}, 500)
} catch (err) {
reject(err)
}
})
}
async function getSyncData() {
let time = await getSyncTime()
let data = `endTime - startTime = ${time}`
return data
} async function getData() {
let data = await getSyncData()
console.log(data)
}
getData()
koa2特性
- 利用ES7的async/await的來處理傳統回撥巢狀問題和代替koa@1的generator
- 中介軟體只支援 async/await 封裝,如果要使用koa@1基於generator中介軟體,需要通過中介軟體koa-convert封裝一下才能使用
路由中介軟體 koa-router
npm install koa-router --save
/* index.js */
const Koa = require('koa')
const fs = require('fs')
const app = new Koa()
const Router = require('koa-router')
//子路由1
let home = new Router()
home.get('/', async (ctx) => {
let html = `
<ul>
<li><a href="/page/helloworld">/page/helloworld</a></li>
<li><a href="/page/404">/page/404</a></li>
</ul>
`
ctx.body = html
})
//子路由2
let page = new Router()
page
.get('/404', async (ctx) => {
ctx.body = '404 page'
})
.get('/helloworld', async (ctx) => {
ctx.body = 'helloworld page'
})
//裝載所有子路由
let router = new Router()
router.use('/', home.routes(), home.allowedMethods())
router.use('/page', page.routes(), page.allowedMethods())
//載入路由中介軟體
app.use(router.routes()).use(router.allowedMethods())
app.listen(3000, () => {
console.log('[demo] koa-router is starting on port: 3000')
})
//console
PS D:\workspace\koa2demo> node .\index.js
[demo] koa-router is starting on port: 3000
請求獲取資料
GET請求
const Koa = require('koa')
const app = new Koa()
app.use(async (ctx)=>{
let url = ctx.url
//從上下文的request物件中獲取
let request = ctx.request
let req_query = request.query
let req_querystring = request.querystring
//從上下文直接獲取
let ctx_query = ctx.query
let ctx_querystring = ctx.querystring
ctx.body = {
url,
req_query,
req_querystring,
ctx_query,
ctx_querystring
}
})
app.listen(3000, () => {
console.log('[demo] get request is starting on port: 3000')
})
POST請求獲取資料
const Koa = require('koa')
const app = new Koa()
app.use(async (ctx) => {
if (ctx.url === '/' && ctx.method === 'GET') {
//get請求時返回表單
let html = `
<h2>koa@2 post request</h2>
<form action="/" method="POST">
<p>userName</p>
<input name="username" type="text"><br>
<p>userPwd</p>
<input name="userPwd" type="password"><br>
<button type="submit">submit</button>
</form>
`
ctx.body = html
} else if (ctx.url === '/' && ctx.method === 'POST') {
//post請求時,解析表單裡資料,並顯示
let postData = await parsePostData(ctx)
ctx.body = postData
} else {
//其他請求顯示404
ctx.body = '<h1>404 page</h1>'
}
})
//解析上下文裡node原生請求的post引數
function parsePostData(ctx) {
return new Promise((resolve, reject) => {
try {
let postData = '';
ctx.req.addListener('data', (data) => {
postData += data
})
ctx.req.addListener('end', () => {
let parseData = parseQueryStr(postData)
resolve(parseData)
})
} catch (err) {
reject(err)
}
})
}
//將post請求引數字串解析成JSON
function parseQueryStr(queryStr) {
let queryData = {}
let queryStrList = queryStr.split('&')
console.log(queryStrList)
for (let [index, queryStr] of queryStrList.entries()) {
let itemList = queryStr.split('=')
queryData[itemList[0]] = decodeURIComponent(itemList[1])
}
return queryData
}
app.listen(3000, () => {
console.log('[demo] post request is starting on port: 3000')
})
POST表單請求 | 請求響應結果 |
---|---|
koa-bodyparser 中介軟體
const Koa = require('koa')
const app = new Koa()
const bodyParser = require('koa-bodyparser')
//使用ctx.body解析中介軟體
app.use(bodyParser())
app.use(async (ctx) => {
if (ctx.url === '/' && ctx.method === 'GET') {
//get請求時返回表單
let html = `
<h2>koa@2 post request</h2>
<form action="/" method="POST">
<p>userName</p>
<input name="username" type="text"><br>
<p>userPwd</p>
<input name="userPwd" type="password"><br>
<button type="submit">submit</button>
</form>
`
ctx.body = html
} else if (ctx.url === '/' && ctx.method === 'POST') {
//post請求時,解析表單裡資料,並顯示
let postData = ctx.request.body
ctx.body = postData
} else {
//其他請求顯示404
ctx.body = '<h1>404 page</h1>'
}
})
app.listen(3000, () => {
console.log('[demo] koa-bodyparser is starting on port: 3000')
})
靜態資源載入
koa-static中介軟體
const Koa = require('koa')
const path = require('path')
const static = require('koa-static')
const app = new Koa()
//靜態資源相對路徑
const staticPath = './public'
app.use(static(path.join(__dirname, staticPath)))
app.use(async (ctx) => {
ctx.body = 'hello koa@2'
})
app.listen(3000, () => {
console.log('[demo] koa-static middleware is starting on port: 3000')
})
koa2使用cookie
const Koa = require('koa')
const app = new Koa()
app.use(async (ctx) => {
if (ctx.url === '/index') {
ctx.cookies.set('cid', 'hello world', {
domain: 'localhost',//cookie所在的域名
path: '/index',//cookie所在的路徑
maxAge: 20 * 60 * 1000,//cookie有效時長
expires: new Date('2018-10-24'),//cookie失效時間
httpOnly: false,//是否只用於http請求中獲取
overwrite: false//是否允許重寫
})
ctx.body = 'cookie is ok'
} else {
ctx.body = 'hello koa@2'
}
})
app.use(async (ctx) => {
ctx.body = 'hello koa@2'
})
app.listen(3000, () => {
console.log('[demo] cookie is starting on port: 3000')
})
koa2實現session
存放mysql中
//建立mysql資料庫名為koademo
CREATE DATABASE IF NOT EXISTS koademo DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
const Koa = require('koa')
const session = require('koa-session-minimal')
const MysqlSession = require('koa-mysql-session')
const app = new Koa()
//配置儲存session資訊的mysql
let store = new MysqlSession({
user: 'root',
password: 'root',
database: 'koademo',
host: '127.0.0.1'
})
//存放sessionId的cookie配置
let cookie = {
maxAge: '',
expires: '',
path: '',
domain: '',
httpOnly: '',
overwrite: '',
secure: '',
sameSite: '',
signed: ''
}
//使用session中介軟體
app.use(session({
key: 'SESSION_ID',
store: store,
cookie: cookie
}))
app.use(async (ctx) => {
//設定session
if (ctx.url === '/set') {
ctx.session = {
user_id: Math.random().toString(36).substr(2),
count: 0
}
ctx.body = ctx.session
} else if (ctx.url === '/') {
//讀取session資訊
ctx.session.count = ctx.session.count + 1
ctx.body = ctx.session
}
})
app.listen(3000, () => {
console.log('[demo] session is starting on port: 3000')
})
載入模板引擎
koa-views中介軟體
const Koa = require('koa')
const views = require('koa-views')
const path = require('path')
const app = new Koa()
//載入模板引擎
app.use(views(path.join(__dirname, './views'), {
extension: 'ejs'
}))
app.use(async (ctx) => {
let title = 'hello koa@2'
await ctx.render('index', {
title
})
})
app.listen(3000, () => {
console.log('[demo] koa-views ejs is starting on port: 3000')
})
檔案上傳
busboy模組
busboy模組是用來解析post請求,node原生req中的檔案流
const inspect = require('util').inspect
const path = require('path')
const fs = require('fs')
const Busboy = require('busboy')
//req為node原生請求
const busboy = new Busboy({ headers: req.headers })
//監聽檔案解析事件
busboy.on('file', function (fieldname, file, filename, encoding, mimetype) {
console.log(`File [${fieldname}]: filename: ${filename}`)
//檔案儲存特定路徑
file.pipe(fs.createWriteStream('./upload'))
//開始解析檔案流
file.on('data', (data) => {
console.log(`File [${fieldname}] got ${data.length} bytes`)
})
//解析檔案結束
file.on('end', () => {
console.log(`File [${fieldname}] finished`)
})
})
//監聽請求中的欄位
busboy.on('field', (fieldname, val, fieldnameTruncated, valTruncated) => {
console.log(`Field [${fieldname}]: value: ${inspect(val)}`)
})
//監聽結束事件
busboy.on('finish', () => {
console.log('Done parsing form!')
res.writeHead(303, { Connection: 'close', Location: '/' })
res.end()
})
req.pipe(busboy)
上傳檔案簡單實現
封裝上傳檔案到寫入服務方法
const inspect = require('util').inspect
const path = require('path')
const os = require('os')
const fs = require('fs')
const Busboy = require('busboy')
/**
* 同步建立檔案目錄
* @param {string} dirname 目錄絕對地址
* @return {boolean} 建立目錄結果
*/ function mkdirsSync(dirname) {
if (fs.existsSync(dirname)) {
return true
} else {
if (mkdirsSync(path.dirname(dirname))) {
fs.mkdirSync(dirname)
return true
}
}
}
/**
* 獲取上傳檔案的字尾名
* @param {string} fileName 獲取上傳檔案的字尾名
* @return {string} 檔案字尾名
*/
function getSuffixName(fileName) {
let nameList = fileName.split('.')
return nameList[nameList.length - 1]
}
/**
* 上傳檔案
* @param {object} ctx koa上下文
* @param {object} options 檔案上傳引數 fileType檔案型別, path檔案存放路徑
* @return {promise}
*/ function uploadFile(ctx, options) {
let req = ctx.req
let res = ctx.res
let busboy = new Busboy({ headers: req.headers }) // 獲取型別
let fileType = options.fileType || 'common'
let filePath = path.join(options.path, fileType)
let mkdirResult = mkdirsSync(filePath)
return new Promise((resolve, reject) => {
console.log('檔案上傳中...')
let result = { success: false, formData: {}, } // 解析請求檔案事件
busboy.on('file', function (fieldname, file, filename, encoding, mimetype) {
let fileName = Math.random().toString(16).substr(2) + '.' + getSuffixName(filename)
let _uploadFilePath = path.join(filePath, fileName)
let saveTo = path.join(_uploadFilePath) // 檔案儲存到制定路徑
file.pipe(fs.createWriteStream(saveTo)) // 檔案寫入事件結束
file.on('end', function () {
result.success = true
result.message = '檔案上傳成功'
console.log('檔案上傳成功!')
resolve(result)
})
}) // 解析表單中其他欄位資訊
busboy.on('field', function (fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) {
console.log('表單欄位資料 [' + fieldname + ']: value: ' + inspect(val));
result.formData[fieldname] = inspect(val);
}); // 解析結束事件
busboy.on('finish', function () {
console.log('檔案上結束')
resolve(result)
}) // 解析錯誤事件
busboy.on('error', function (err) {
console.log('檔案上出錯')
reject(result)
})
req.pipe(busboy)
})
}
module.exports = { uploadFile }
入口檔案
const Koa = require('koa')
const path = require('path')
const app = new Koa()
// const bodyParser = require('koa-bodyparser')
const { uploadFile } = require('./util/upload')
// app.use(bodyParser())
app.use(async (ctx) => {
if (ctx.url === '/' && ctx.method === 'GET') {
// 當GET請求時候返回表單頁面
let html = `
<h1>koa2 upload demo</h1>
<form method="POST" action="/upload.json" enctype="multipart/form-data">
<p>file upload</p>
<span>picName:</span><input name="picName" type="text" /><br/>
<input name="file" type="file" /><br/><br/>
<button type="submit">submit</button>
</form>
`
ctx.body = html
} else if (ctx.url === '/upload.json' && ctx.method === 'POST') {
// 上傳檔案請求處理
let result = {
success: false
}
let serverFilePath = path.join(__dirname, 'upload-files')
// 上傳檔案事件
result = await uploadFile(ctx, {
fileType: 'album', // common or album
path: serverFilePath
})
ctx.body = result
} else {
// 其他請求顯示404
ctx.body = '<h1>404!!! o(╯□╰)o</h1>'
}
})
app.listen(3000, () => {
console.log('[demo] upload-simple is starting at port 3000')
})
非同步上傳圖片
入口檔案
const Koa = require('koa')
const views = require('koa-views')
const path = require('path')
const convert = require('koa-convert')
const static = require('koa-static')
const { uploadFile } = require('./util/upload')
const app = new Koa()
/**
* 使用第三方中介軟體 start
*/
app.use(views(path.join(__dirname, './views'), {
extension: 'ejs'
}))
// 靜態資源目錄對於相對入口檔案index.js的路徑
const staticPath = './public'
// 由於koa-static目前不支援koa2
// 所以只能用koa-convert封裝一下
app.use(convert(static(path.join(__dirname, staticPath))))
/**
* 使用第三方中介軟體 end
*/
app.use(async (ctx) => {
if (ctx.method === 'GET') {
let title = 'upload pic async'
await ctx.render('index', {
title,
})
}
else if (ctx.url === '/api/picture/upload.json' && ctx.method === 'POST') {
// 上傳檔案請求處理
let result = { success: false }
let serverFilePath = path.join(__dirname, 'public/image')
// 上傳檔案事件
result = await uploadFile(ctx, {
fileType: 'album',
path: serverFilePath
})
ctx.body = result
} else {
// 其他請求顯示404
ctx.body = '<h1>404!!! o(╯□╰)o</h1>'
}
})
app.listen(3000, () => {
console.log('[demo] upload-async is starting at port 3000')
})
上傳圖片流寫操作
const inspect = require('util').inspect
const path = require('path')
const os = require('os')
const fs = require('fs')
const Busboy = require('busboy')
/**
* 同步建立檔案目錄
* @param {string} dirname 目錄絕對地址
* @return {boolean} 建立目錄結果
*/
function mkdirsSync(dirname) {
if (fs.existsSync(dirname)) {
return true
} else {
if (mkdirsSync(path.dirname(dirname))) {
fs.mkdirSync(dirname)
return true
}
}
}
/**
* 獲取上傳檔案的字尾名
* @param {string} fileName 獲取上傳檔案的字尾名
* @return {string} 檔案字尾名
*/ function getSuffixName(fileName) {
let nameList = fileName.split('.')
return nameList[nameList.length - 1]
}
/**
* 上傳檔案
* @param {object} ctx koa上下文
* @param {object} options 檔案上傳引數 fileType檔案型別, path檔案存放路徑
* @return {promise}
*/ function uploadFile(ctx, options) {
let req = ctx.req
let res = ctx.res
let busboy = new Busboy({ headers: req.headers })
// 獲取型別
let fileType = options.fileType || 'common'
let filePath = path.join(options.path, fileType)
let mkdirResult = mkdirsSync(filePath)
return new Promise((resolve, reject) => {
console.log('檔案上傳中...')
let result = { success: false, message: '', data: null }
// 解析請求檔案事件
busboy.on('file', function (fieldname, file, filename, encoding, mimetype) {
let fileName = Math.random().toString(16).substr(2) + '.' + getSuffixName(filename)
let _uploadFilePath = path.join(filePath, fileName)
let saveTo = path.join(_uploadFilePath)
// 檔案儲存到制定路徑
file.pipe(fs.createWriteStream(saveTo))
// 檔案寫入事件結束
file.on('end', function () {
result.success = true
result.message = '檔案上傳成功'
result.data = {
pictureUrl: `//${ctx.host}/image/${fileType}/${fileName}`
}
console.log('檔案上傳成功!')
resolve(result)
})
})
// 解析結束事件
busboy.on('finish', function () {
console.log('檔案上結束')
resolve(result)
})
// 解析錯誤事件
busboy.on('error', function (err) {
console.log('檔案上出錯')
reject(result)
})
req.pipe(busboy)
})
}
module.exports = { uploadFile }
前端程式碼
<!DOCTYPE html>
<html lang="en">
<head>
<title><%= title%></title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<button class="btn" id="J_UploadPictureBtn">上傳圖片</button>
<hr/>
<p>上傳進度<span id="J_UploadProgress">0</span>%</p>
<p>上傳結果圖片</p>
<div id="J_PicturePreview" class="preview-picture"> </div>
<script src="js/index.js"></script>
</body>
</html>
上傳操作程式碼
(function () {
let btn = document.getElementById('J_UploadPictureBtn')
let progressElem = document.getElementById('J_UploadProgress')
let previewElem = document.getElementById('J_PicturePreview')
btn.addEventListener('click', function () {
uploadAction({
success: function (result) {
console.log(result)
if (result && result.success && result.data && result.data.pictureUrl) {
previewElem.innerHTML = '![](' + result.data.pictureUrl + ')'
}
},
progress: function (data) {
if (data && data * 1 > 0) {
progressElem.innerText = data
}
}
})
})
/**
* 型別判斷
* @type {Object}
*/
let UtilType = {
isPrototype: function (data) {
return Object.prototype.toString.call(data).toLowerCase();
}, isJSON: function (data) {
return this.isPrototype(data) === '[object object]';
}, isFunction: function (data) {
return this.isPrototype(data) === '[object function]';
}
}
/**
* form表單上傳請求事件
* @param {object} options 請求引數
*/
function requestEvent(options) {
try {
let formData = options.formData
let xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
options.success(JSON.parse(xhr.responseText))
}
}
xhr.upload.onprogress = function (evt) {
let loaded = evt.loaded
let tot = evt.total
let per = Math.floor(100 * loaded / tot)
options.progress(per)
}
xhr.open('post', '/api/picture/upload.json')
xhr.send(formData)
} catch (err) { options.fail(err) }
}
/**
* 上傳事件
* @param {object} options 上傳引數
*/ function uploadEvent(options) {
let file
let formData = new FormData()
let input = document.createElement('input')
input.setAttribute('type', 'file')
input.setAttribute('name', 'files')
input.click()
input.onchange = function () {
file = input.files[0]
formData.append('files', file)
requestEvent({ formData, success: options.success, fail: options.fail, progress: options.progress })
}
}
/**
* 上傳操作
* @param {object} options 上傳引數
*/
function uploadAction(options) {
if (!UtilType.isJSON(options)) {
console.log('upload options is null')
return
}
let _options = {}
_options.success = UtilType.isFunction(options.success) ? options.success : function () { }
_options.fail = UtilType.isFunction(options.fail) ? options.fail : function () { }
_options.progress = UtilType.isFunction(options.progress) ? options.progress : function () { }
uploadEvent(_options)
}
})()
建立mysql資料庫連線池
const mysql = require('mysql')
//建立資料連線池
const pool = mysql.createPool({
host: '127.0.0.1',
user: 'root',
password: 'root',
database: 'koademo'
})
//在資料池中進行會話操作
pool.getConnection((err, conn) => {
conn.query('SELECT * FROM test', (err, rs, fields) => {
//結束會話
conn.release()
if (err) throw err
})
})
async/await封裝使用mysql
/* ./async-db.js */
const msyql = require('mysql')
const pool = msyql.createPool({
host: '127.0.0.1',
user: 'root',
password: 'root',
database: 'koademo'
})
let query = (sql, values) => {
return new Promise((resolve, reject) => {
pool.getConnection((err, conn) => {
if (err) {
reject(err)
} else {
conn.query(sql, values, (err, rows) => {
if (err) {
reject(err)
} else {
resolve(rows)
}
conn.release()
})
}
})
})
}
module.exports = {
query
}
/* index.js */
const { query } = require('./async-db')
async function selectAllData() {
let sql = 'SELECT * FROM test'
let dataList = await query(sql)
return dataList
}
async function getData() {
let dataList = await selectAllData()
console.log(dataList)
}
getData()
jsonp
const Koa = require('koa')
const app = new Koa()
app.use(async (ctx) => {
//如果JSONP的請求為GET
if (ctx.method === 'GET' && ctx.url.split('?')[0] === '/getData.jsonp') {
//獲取JSONP的callback
let callbackName = ctx.query.callback || 'callback'
let returnData = {
success: true,
data: {
text: 'this is a jsonp api',
time: new Date().getTime()
}
}
//JSONP的script字串
let jsonpStr = `;${callbackName}(${JSON.stringify(returnData)})`
//用text/javascript,讓請求支援跨域請求
ctx.type = 'text/javascript'
//輸出jsonp字串
ctx.body = jsonpStr
} else {
ctx.body = 'hello jsonp'
}
})
app.listen(3000, () => {
console.log('[demo] jsonp is tarting on port 3000')
})
koa-jsonp中介軟體
const Koa = require('koa')
const jsonp = require('koa-jsonp')
const app = new Koa()
//使用中介軟體
app.use(jsonp())
app.use(async (ctx) => {
let returnData = {
success: true,
data: {
text: 'this is a jsonp api',
time: new Date().getTime()
}
}
//直接輸出json
ctx.body = returnData
})
app.listen(3000, () => {
console.log('[demo] koa-jsonp is tarting on port 3000')
})
各章節程式碼存放在對應的分支中:所有原始碼
相關文章
- numpy的學習筆記\pandas學習筆記筆記
- 學習筆記筆記
- 【學習筆記】數學筆記
- 《JAVA學習指南》學習筆記Java筆記
- 機器學習學習筆記機器學習筆記
- 學習筆記-粉筆980筆記
- 學習筆記(3.29)筆記
- 學習筆記(4.1)筆記
- 學習筆記(3.25)筆記
- 學習筆記(3.26)筆記
- JavaWeb 學習筆記JavaWeb筆記
- golang 學習筆記Golang筆記
- Nginx 學習筆記Nginx筆記
- spring學習筆記Spring筆記
- gPRC學習筆記筆記
- GDB學習筆記筆記
- 學習筆記(4.2)筆記
- 學習筆記(4.3)筆記
- 學習筆記(4.4)筆記
- Servlet學習筆記Servlet筆記
- 學習筆記(3.27)筆記
- jest 學習筆記筆記
- NodeJS學習筆記NodeJS筆記
- WebSocket 學習筆記Web筆記
- mount 學習筆記筆記
- mapGetters學習筆記筆記
- jQuery學習筆記jQuery筆記
- 學習筆記:DDPG筆記
- flex學習筆記Flex筆記
- react 學習筆記React筆記
- Promise學習筆記Promise筆記
- vim學習筆記筆記
- Ansible 學習筆記筆記
- Taro 學習筆記筆記
- MongoDB學習筆記MongoDB筆記
- hbase學習筆記筆記
- git學習筆記Git筆記
- ComfyUi學習筆記UI筆記
- 2024.3.6學習筆記筆記