koa list in github : https://github.com/topics/koajavascript
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()
瞭解更多異步編程,能夠戳鯨魚以前的筆記Nodejs學習記錄:異步編程html
如今咱們實現異步編程是用 async/await 加上 Promise, 那麼咱們使用Promise如何兼容之前的回調呢?--> async awarit前端
const fs = require("fs"); const readFilePromise = filename => { new Promise((resolve, reject) => { fs.readFile(filename, (err, data) => { if(err) { reject(err) return } resolve(data) }) }) } async function main() { const txt = await readFilePromise("mock.txt") console.log(txt.toString()) } main()
HTTP 請求都是無狀態的,可是咱們的 Web 應用一般都須要知道發起請求的人是誰。爲了解決這個問題,HTTP 協議設計了一個特殊的請求頭:Cookie。服務端能夠經過響應頭(set-cookie)將少許數據響應給客戶端,瀏覽器會遵循協議將數據保存,並在下次請求同一個服務的時候帶上(瀏覽器也會遵循協議,只在訪問符合 Cookie 指定規則的網站時帶上對應的 Cookie 來保證安全性)。vue
cookie常常用於作登陸信息的儲存,固然咱們在後端常常喜歡用它,在前端的單頁應用通常喜歡用localstoragejava
經過 ctx.cookies
,咱們能夠在 controller 中便捷、安全的設置和讀取 Cookie。node
來個簡單的案例,看看如何寫入cookiemysql
koa提供了從上下文直接讀取、寫入cookie的方法git
讀取上下文請求中的cookie
在上下文中寫入cookie
設置Cookie實際上是經過在HTTP響應中設置set-cookie頭完成,每一個set-cookie都會讓瀏覽器在Cookie中存一個鍵值對。在設置Cookie值同時,協議還支持許多參數來配置這個Cookie的傳輸、儲存和權限github
{Number} maxAge:
設置這個鍵值對在瀏覽器的最長保存時間。是一個從服務器當前時刻開始的毫秒數。{Date} expires:
設置這個鍵值對的失效時間,若是設置了 maxAge,expires 將會被覆蓋。若是 maxAge 和 expires 都沒設置,Cookie 將會在瀏覽器的會話失效(通常是關閉瀏覽器時)的時候失效。{String} path:
設置鍵值對生效的 URL 路徑,默認設置在根路徑上(/),也就是當前域名下的全部 URL 均可以訪問這個 Cookie。 {String} domain:
設置鍵值對生效的域名,默認沒有配置,能夠配置成只在指定域名才能訪問。{Boolean} httpOnly:
設置鍵值對是否能夠被 js 訪問,默認爲 true,不容許被 js 訪問。{Boolean} secure:
設置鍵值對只在 HTTPS 鏈接上傳輸,框架會幫咱們判斷當前是否在 HTTPS 鏈接上自動設置 secure 的值。{Boolean} overwrite
感興趣的能夠看看 cookie的實現源碼ajax
koa2 中操做的cookies是使用了npm的cookies模塊,因此在讀寫cookie的使用參數與該模塊的使用一致。
源碼在:https://github.com/pillarjs/c...
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:10 * 60 * 1000, // cookie有效時長 expires: new Date('2017-02-15'), // cookie失效時間 httpOnly: false, // 是否只用於http請求中獲取 overwrite:false // 是否容許重寫 } ) ctx.body = 'cookie is ok' } else { ctx.body = 'hello world' } }) app.listen(3000, () => { console.log('[demo] cookie is starting at port 3000') })
在設置 Cookie 時咱們須要思考清楚這個 Cookie 的做用,它須要被瀏覽器保存多久?是否能夠被 js 獲取到?是否能夠被前端修改?
訪問http://localhost:3000/index
更改下代碼
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:10 * 60 * 1000, // cookie有效時長 expires: new Date('2017-02-15'), // cookie失效時間 httpOnly: false, // 是否只用於http請求中獲取 overwrite:false // 是否容許重寫 } ); ctx.body = 'cookie is ok'; } else { if(ctx.cookies.get('cid')){ ctx.body= ctx.cookies.get('cid'); }else { ctx.body = 'cookie is none'; } } }) app.listen(3000, () => { console.log('[demo] cookie is starting at port 3000') })
重啓服務器node cookie.js
,瀏覽器分別輸入http://localhost:3000/index/
http://localhost:3000
http://localhost:3000/index/aa
由於咱們配置了
{path:'/index'}
Cookie 在 Web 應用中常常承擔標識請求方身份的功能,
可是cookie信息會被儲存在瀏覽器本地或硬盤中,這樣會有安全問題,若是有人可以訪問你的電腦就能分析出你的敏感信息,用戶名、密碼等等。爲了解決這個隱患,因此 Web 應用在 Cookie 的基礎上封裝了 Session 的概念,專門用作用戶身份識別。
既然服務器渲染又須要用戶登陸功能,那麼用session去記錄用戶登陸態是必要的
koa2原生功能只提供了cookie的操做,可是沒有提供session操做。session就只能本身實現或者經過第三方中間件實現。
可是基於koa的egg.js框架內置了 Session 插件,給咱們提供了 ctx.session 來訪問或者修改當前用戶 Session 。----> cookie & session
在koa2中實現session的方案有:
須要用到中間件:
適用於koa2 的session中間件,提供存儲介質的讀寫接口 。
爲koa-session-minimal中間件提供MySQL數據庫的session數據讀寫操做。
而後,咱們將將sessionId和對應的數據存到數據庫
將數據庫的存儲的sessionId存到頁面的cookie中
根據cookie的sessionId去獲取對於的session信息
在mysql數據庫建立 Koa_session_demo數據庫
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: 'wyc2016', database: 'koa_session_demo', host: '127.0.0.1', }) //存放sessionId的cookie配置 let cookie = { maxAge: '',// cookie有效時長 expires: '',// cookie失效時間 path: '', // 寫cookie所在的路徑 domain: '', // 寫cookie所在的域名 httpOnly: '', // 是否只用於http請求中獲取 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 at port 3000'); })
在express中咱們用的是express-session,那麼在koa2中用的是哪些模塊:
注意:一旦選擇了將 Session 存入到外部存儲中,就意味着系統將強依賴於這個外部存儲,當它掛了的時候,咱們就徹底沒法使用 Session 相關的功能了。所以咱們更推薦你們只將必要的信息存儲在 Session 中,保持 Session 的精簡併使用默認的 Cookie 存儲,用戶級別的緩存不要存儲在 Session 中。
npm install --save busboy
busboy 模塊是用來解析POST請求,node原生req中的文件流。
更多詳細API能夠訪問npm官方文檔 https://www.npmjs.com/package...
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', function(data) { console.log(`File [${fieldname}] got ${data.length} bytes`) }) // 解析文件結束 file.on('end', function() { console.log(`File [${fieldname}] Finished`) }) }) // 監聽請求中的字段 busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated) { console.log(`Field [${fieldname}]: value: ${inspect(val)}`) }) // 監聽結束事件 busboy.on('finish', function() { console.log('Done parsing form!') res.writeHead(303, { Connection: 'close', Location: '/' }) res.end() }) req.pipe(busboy)
#index.js const Koa = require('koa'); const path = require('path'); const app = new Koa(); const {uploadFile} = require('./util/upload') 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', 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'); })
# util/upload.js const inspect = require('util').inspect const path = require('path') 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('文件上傳成功!') }) }) // 解析表單中其餘字段信息 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 }
源碼:https://github.com/JXtreehous...
參考中間件-> aliyun-oss-upload-stream
爲何使用``stream?
stream
的方式上傳文件能夠很大程度上下降服務器內存開銷。Aliyun官方SDK並無對stream進行一個完美的封裝,因此一般上傳文件(Put Object)
的流程是客戶端上傳文件到服務器,服務器把文件數據緩存到內存,等文件所有上傳完畢後,一次性上傳到Aliyun Oss服務。這樣作一旦瞬間上傳文件的請求過多,服務器的內存開銷會直線上升。而使用stream的方式上傳文件的流程是客戶端在上傳文件數據到服務器的過程當中,服務器同時也在把文件數據往Aliyun Oss服務傳送,而不須要在服務器上緩存文件數據。ORM(Object Relational Mapping)框架,提供了了PostgreSQL, MySQL, SQLite and MSSQL 數據庫鏈接池
import Sequelize from "sequelize"; const sequelize = new Sequelize('mock_server', 'root', '123456', { host: 'localhost', dialect: 'mysql', dialectOptions: { charset: "utf8mb4", collate: "utf8mb4_unicode_ci", supportBigNumbers: true, bigNumberStrings: true }, pool: { max: 5, min: 0, idle: 10000 } }); const sequelize = ne
this.dao = sequelize.define('Api', { project_id: Sequelize.INTEGER, url: Sequelize.STRING, request_body: Sequelize.TEXT, response_body: Sequelize.TEXT, user_id: Sequelize.INTEGER, description: Sequelize.TEXT, host: Sequelize.STRING, }); this.dao.sync(); let api = this.dao.findOne({ where: { user_id: user_id, id: api_id, project_id: pid, } });
將查詢結果封裝成Promise
Database Transaction
務,若是遇到⼀一⾏行行,⾃自動回滾全部操做
者執⾏行行rollback操做
npm install --save mysql
mysql模塊是node操做MySQL的引擎,能夠在node.js環境下對MySQL數據庫進行建表,增、刪、改、查等操做。
onst mysql = require('mysql') const connection = mysql.createConnection({ host : '127.0.0.1', // 數據庫地址 user : 'root', // 數據庫用戶 password : '123456' // 數據庫密碼 database : 'my_database' // 選中數據庫 }) // 執行sql腳本對數據庫進行讀寫 connection.query('SELECT * FROM my_table', (error, results, fields) => { if (error) throw error // connected! // 結束會話 connection.release() });
注意:一個事件就有一個從開始到結束的過程,數據庫會話操做執行完後,就須要關閉掉,以避免佔用鏈接資源。
通常狀況下操做數據庫是很複雜的讀寫過程,不僅是一個會話,若是直接用會話操做,就須要每次會話都要配置鏈接參數。因此這時候就須要鏈接池管理會話。
const mysql = require('mysql') // 建立數據池 const pool = mysql.createPool({ host : '127.0.0.1', // 數據庫地址 user : 'root', // 數據庫用戶 password : '123456' // 數據庫密碼 database : 'my_database' // 選中數據庫 }) // 在數據池中進行會話操做 pool.getConnection(function(err, connection) { connection.query('SELECT * FROM my_table', (error, results, fields) => { // 結束會話 connection.release(); // 若是有錯誤就拋出 if (error) throw error; }) })
更多詳細API能夠訪問npm官方文檔 https://www.npmjs.com/package...
因爲mysql模塊的操做都是異步操做,每次操做的結果都是在回調函數中執行,如今有了async/await,就能夠用同步的寫法去操做數據庫
Promise封裝mysql模塊
Promise封裝 ./async-db
const mysql = require('mysql') const pool = mysql.createPool({ host : '127.0.0.1', user : 'root', password : '123456', database : 'my_database' }) let query = function( sql, values ) { return new Promise(( resolve, reject ) => { pool.getConnection(function(err, connection) { if (err) { reject( err ) } else { connection.query(sql, values, ( err, rows) => { if ( err ) { reject( err ) } else { resolve( rows ) } connection.release() }) } }) }) } module.exports = { query }
async/await使用
const { query } = require('./async-db') async function selectAllData( ) { let sql = 'SELECT * FROM my_table' let dataList = await query( sql ) return dataList } async function getData() { let dataList = await selectAllData() console.log( dataList ) } getData()
一般初始化數據庫要創建不少表,特別在項目開發的時候表的格式可能會有些變更,這時候就須要封裝對數據庫建表初始化的方法,保留項目的sql腳本文件,而後每次須要從新建表,則執行建表初始化程序就行
├── index.js # 程序入口文件 ├── node_modules/ ├── package.json ├── sql # sql腳本文件目錄 │ ├── data.sql │ └── user.sql └── util # 工具代碼 ├── db.js # 封裝的mysql模塊方法 ├── get-sql-content-map.js # 獲取sql腳本文件內容 ├── get-sql-map.js # 獲取全部sql腳本文件 └── walk-file.js # 遍歷sql腳本文件
https://chenshenhai.github.io...
https://github.com/ChenShenha...
import redis from "redis"; const client = redis.createClient(({ port: "19002", host: "localhost" }));
在項目複雜的業務場景,有時候須要在前端跨域獲取數據,這時候提供數據的服務就須要提供跨域請求的接口,一般是使用JSONP的方式提供跨域接口。
https://chenshenhai.github.io...
https://github.com/ChenShenha...
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 starting at port 3000') })
前端部分
$.ajax({ url: 'http://localhost:3000/getData.jsonp', type: 'GET', dataType: 'JSONP', success: function(res) { console.log(res) } })
npm install --save koa-jsonp
https://www.npmjs.com/package...
https://github.com/chenshenha...
OAuth 2.0深刻了解:以微信開放平臺統一登陸爲例
微信開放平臺開發——網頁微信掃碼登陸(OAuth2.0)
Koa Static Cache: https://www.npmjs.com/package...
更多中間件
koa-onerror
koa-safe-jsonp
koa-generator
yi-ge/Koa2-API-Scaffold
- Express-style - Support koa 1.x(supported) - Support koa 2.x(koa middleware supported,need Node.js 7.6+ ,babel optional)
uri:
http://localhost:7001/game/62231163?channel=1323
router:
controller.game.detail
const { gameId, channel } = ctx.query; const _gameId = ctx.params.id || gameId; // 一級分類 console.log('-------------------1', ctx.query) console.log('-------------------2', gameId) console.log('-------------------3', ctx.url) console.log('---------------------4', ctx.params) console.log('---------------------5', ctx.querystring) console.log('--------------------6', ctx.origin)
https://www.jianshu.com/p/d0b...
看我如何利用NodeJS SSRF漏洞得到AWS徹底控制權限
Web安全漏洞之SSRF
Web 安全漏洞 SSRF 簡介及解決方案
welefen/ssrf-agent
https://github.com/yolopunk/e...
egg-commerce
koajs/examples
johndatserakis/koa-vue-notes-api
使用koa2+wechaty打造我的微信小祕書
koa
與express
區別
關於區別詳解 戳我這篇文章express中間件 文末
koa原理
Koa.js 設計模式-學習筆記
koa2進階學習筆記
koa2 源碼分析
npm koa-session-minimal
koa2中的session及redis
egg.js Cookie and Session
《HTTP權威指南》
七天學會NodeJS
Koa2 之文件上傳下載
node消息隊列
快速搭建可用於實戰的koa2+mongodb框架
https://chenshenhai.github.io...
KOA2框架原理解析和實現
koa 介紹 ppt