async / await

特点

  • 让异步逻辑用同步写法实现
  • 最底层的await返回需要是Promise对象
  • 可以通过多层 async function 的同步写法代替传统的callback嵌套
function getSyncTime() {return new Promise((resolve, reject) => {try {let startTime = new Date().getTime()setTimeout(() => {let endTime = new Date().getTime()let data = endTime - startTimeresolve( 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学习记录:异步编程

cookie

HTTP 请求都是无状态的,但是我们的 Web 应用通常都需要知道发起请求的人是谁。为了解决这个问题,HTTP 协议设计了一个特殊的请求头:Cookie。服务端可以通过响应头(set-cookie)将少量数据响应给客户端,浏览器会遵循协议将数据保存,并在下次请求同一个服务的时候带上(浏览器也会遵循协议,只在访问符合 Cookie 指定规则的网站时带上对应的 Cookie 来保证安全性)。

cookie经常用于做登录信息的储存,当然我们在后端经常喜欢用它,在前端的单页应用一般喜欢用localstorage

通过 ctx.cookies,我们可以在 controller 中便捷、安全的设置和读取 Cookie。

来个简单的案例,看看如何写入cookie

koa提供了从上下文直接读取、写入cookie的方法

  • ctx.cookies.get(name, [options]) 读取上下文请求中的cookie
  • ctx.cookies.set(name, value, [options]) 在上下文中写入cookie

设置Cookie其实是通过在HTTP响应中设置set-cookie头完成,每个set-cookie都会让浏览器在Cookie中存一个键值对。在设置Cookie值同时,协议还支持许多参数来配置这个Cookie的传输、储存和权限

  • {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的实现源码

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

  • 可以在控制台的cookie列表中中看到写在页面上的cookie
  • 在控制台的console中使用document.cookie可以打印出在页面的所有cookie(需要是httpOnly设置false才能显示)


更改下代码

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'}

session

Cookie 在 Web 应用中经常承担标识请求方身份的功能,
但是cookie信息会被储存在浏览器本地或硬盘中,这样会有安全问题,如果有人能够访问你的电脑就能分析出你的敏感信息,用户名、密码等等。为了解决这个隐患,所以 Web 应用在 Cookie 的基础上封装了 Session 的概念,专门用做用户身份识别。

既然服务器渲染又需要用户登录功能,那么用session去记录用户登录态是必要的

koa2原生功能只提供了cookie的操作,但是没有提供session操作。session就只能自己实现或者通过第三方中间件实现。
但是基于koa的egg.js框架内置了 Session 插件,给我们提供了 ctx.session 来访问或者修改当前用户 Session 。----> cookie & session
在koa2中实现session的方案有:

  • 如果session数据量很小,可以直接存在内存中
  • 如果session数据量很大,则需要存储介质存放session数据

 数据库存储方案

 储存在MySQL

需要用到中间件:

  • koa-session-minimal 适用于koa2 的session中间件,提供存储介质的读写接口 。
  • koa-mysql-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信息的mysqllet 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) => {// 设置sessionif(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 + 1ctx.body = ctx.session}
})app.listen(3000, ()=> {console.log('[demo] session is starting at port 3000');
})



 储存在redis

在express中我们用的是express-session,那么在koa2中用的是哪些模块:

  • koa2-cookie-session
  • koa-session-redis
  • ioredis

注意:一旦选择了将 Session 存入到外部存储中,就意味着系统将强依赖于这个外部存储,当它挂了的时候,我们就完全无法使用 Session 相关的功能了。因此我们更推荐大家只将必要的信息存储在 Session 中,保持 Session 的精简并使用默认的 Cookie 存储,用户级别的缓存不要存储在 Session 中。

模板引擎

文件上传(upload)

busboy模块

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 {// 其他请求显示404ctx.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.reqlet res = ctx.reslet 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 = trueresult.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...

数据库

mysql

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...

async/await封装使用mysql

由于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...




JSONP


原生koa2实现jsonp

在项目复杂的业务场景,有时候需要在前端跨域获取数据,这时候提供数据的服务就需要提供跨域请求的接口,通常是使用JSONP的方式提供跨域接口。
https://chenshenhai.github.io...
https://github.com/ChenShenha...

const Koa = require('koa')
const app = new Koa()app.use(async (ctx) => {// 如果jsonp 的请求为GETif(ctx.method === 'GET' && ctx.url.split('?')[0] ==='/getData.jsonp'){// 获取jsonp的callbacklet 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)}
})

koa-jsonp中间件

npm install --save koa-jsonp

https://www.npmjs.com/package...
https://github.com/chenshenha...

OAuth 2.0

OAuth 2.0深入了解:以微信开放平台统一登录为例
微信开放平台开发——网页微信扫码登录(OAuth2.0)

常用中间件


Koa Static Cache: https://www.npmjs.com/package...

更多中间件

常用脚手架

hello-koa2-mongodb

koa-generator

- Express-style
- Support koa 1.x(supported)
- Support koa 2.x(koa middleware supported,need Node.js 7.6+ ,babel optional)

参考项目

egg-commerce
koajs/examples
johndatserakis/koa-vue-notes-api

参考

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框架原理解析和实现

Nodejs学习记录: koa2相关推荐

  1. NodeJS 学习记录

    一.环境 1. 操作系统:win7, 32位 2. nodejs版本:v0.12.0 3. npm版本:2.5.1 二.问题 1. npm安装镜像源问题 国外镜像源很慢,国内出名且公开的有淘宝,也有内 ...

  2. NodeJS学习笔记: RESTful —— 为本系列做个小结

    前言 本人不是技术专家,该笔记只是从使用语言进行开发的层面上记录一些体会,不包含也不想尝试从源码或者更深的层次去讨论语言本身的优劣.文章内容是笔者的个人感悟,既不保证正确性,也不保证别人能看懂. 这是 ...

  3. Nodejs学习笔记(七)——接口API

    [目录] Nodejs学习笔记(一)--基础之全局对象.包和npm Nodejs学习笔记(二)--模块 Nodejs学习笔记(三)--同步和与异步之文件系统模块 Nodejs学习笔记(四)--http ...

  4. Nodejs学习笔记(一)——基础之全局对象、包和npm

    [目录] Nodejs学习笔记(二)--模块 Nodejs学习笔记(三)--同步和与异步之文件系统模块 Nodejs学习笔记(四)--http协议与服务器 Nodejs学习笔记(五)--express ...

  5. Nodejs学习路线图

    Nodejs学习路线图 从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发.Nodejs框架是基于V8的引擎,是目前速度最快的Javascri ...

  6. Nodejs 学习系列

    Nodejs学习计划(后续学习过程中可能会有变化,会实时更新-) 计划如下: Nodejs 整体介绍 Nodejs http模块.url模块.path模块.supervisor工具使用 Nodejs ...

  7. NodeJS学习笔记

    今天开始学习NodeJS,在这里做个笔记,记录一下我的学习历程,也方便以后参考. 1.什么是NodeJS Node.js® is a JavaScript runtime built on Chrom ...

  8. Pytorch学习记录-torchtext和Pytorch的实例( 使用神经网络训练Seq2Seq代码)

    Pytorch学习记录-torchtext和Pytorch的实例1 0. PyTorch Seq2Seq项目介绍 1. 使用神经网络训练Seq2Seq 1.1 简介,对论文中公式的解读 1.2 数据预 ...

  9. HTML5与CSS3权威指南之CSS3学习记录

    title: HTML5与CSS3权威指南之CSS3学习记录 toc: true date: 2018-10-14 00:06:09 学习资料--<HTML5与CSS3权威指南>(第3版) ...

  10. springboot @cacheable不起作用_Springboot学习记录13 使用缓存:整合redis

    本学习记录的代码,部分参考自gitee码云的如下工程.这个工程有详尽的Spingboot1.x教程.鸣谢! https://gitee.com/didispace/SpringBoot-Learnin ...

最新文章

  1. 学生卡变成普通卡_刚接触流量卡的小白看这一篇就够了!!!
  2. 怎么把python结果全部显示-python – 如何展开输出显示以查看更多列?
  3. shiro的简单入门使用
  4. PowerBI随笔(7)-lookupvalue、divide
  5. Linux Centos7网络属性配置
  6. 哪种HTML列表会自动编号,HTML列表的种类
  7. #6682. 梦中的数论(Min25筛)
  8. 请使用复选框选择_使用可选是可选的
  9. 海云健康:上云为10万家药店带去了什么价值?
  10. TaskTracker执行map或reduce任务的过程(二)
  11. Jsp Servlet Mysql实现的Java Web在线商城项目源码
  12. 用docker swarm 实现集群
  13. 基于SpringBoot+Vue的音乐网站项目-附源码+报告
  14. 如何在数字化领域脱颖而出?TOGAF®认证为你的职业成长赋能
  15. 毕业设计 stm32便携式老年人智能药箱系统 - 物联网 单片机 嵌入式
  16. prolog寻找三角形个数
  17. hello world你知多少------300种编程语言中的hello world程序汇
  18. WEB前端代码:边框阴影、边框图片、背景样式、文本样式、字体样式
  19. 用V实现电脑给手机开热点快乐上网
  20. 如何增加百度收录量和友好度

热门文章

  1. Linux user 的密码策略设定 /etc/shadow
  2. for(atuo x : s)
  3. Springboot JPA注解@Enumerated
  4. emcy协议_商铺三方租赁协议合同范本
  5. 微信公众号前端40163解决办法
  6. tablau desktop注册码本地停用虚拟机激活
  7. angular 子父页面传值以及调用方法
  8. 算法打卡Ques20201009
  9. 操作系统 内存分配-分区
  10. 无向图的邻接表表示法