Node.js的特点和优势
1、基于chrome V8引擎进行代码解析的JavaScript运行环境。
2、事件驱动,
3、非阻塞IO,IO即输入输出操作,阻塞IO可以理解为被阻塞了的输入输出操作,在服务器端有很多会涉及阻塞IO的操作,例如在读取文件过程中,需要等待文件读取完毕后才能继续执行后面的操作,Node.js中使用事件回调的方式来解决这种阻塞IO情况,避免了阻塞IO所需的等待,所以说Node.js具有非阻塞IO的特点。
4、单进程、单线程,进程可以理解为一个引用程序运行,它是一个动态的概念;而线程是进程中的一部分,进程包含多个线程在运行。单线程就是进程中只有一个线程,阻塞IO模式下一个线程只能处理一个任务,而非阻塞IO模式下,一个线程永远在处理任务,这样cpu的利用率是100%。Node.js采用单线程,利用事件驱动的异步编程模式实现了非阻塞IO。
5、轻量、可伸缩,适用于实时数据交互应用,在Node.js中,Socket可以实现双向通信,例如聊天室就是实时的数据交互应用。
koa2
静态资源服务
使用koa-static中间件
npm install koa-static --save
const static = require('koa-static');
//静态文件目录是根目录下的public文件件
app.use(static(__dirname+'/public'));
ejs模板引擎
使用koa-views中间件
npm install koa-views ejs --save
const views = require('koa-views');
//使用模板引擎,ejs文件地址在根目录下的views文件夹
app.use(views(__dirname+'/views',{extension: 'ejs'}));
ejs基本语法
<% for(var i=0; i<articles.length; i++){ %>
<li class="article">
<a href="/article/<%= articles[i].id %>">
<div class="article-cover" style="background-image: url(<%=articles[i].cover%>)"></div>
<div class="article-text">
<h2 class="article-title"><%= articles[i].title %></h2>
<p class="article-intro"><%= articles[i].intro %>...</p>
</div>
</a>
</li>
<% } %>
<%- include('side-nav')%>:内嵌公共ejs文件。
用<%= %>输出变量,会发生转义
var ejs = require('ejs');
var result = ejs.render('<%=a%>',{a:'<div>123</div>'});
console.log(result);//<div>123</div>
如果不希望内容被转义,可以使用<%- %>
var ejs = require('ejs');
var result = ejs.render('<%-a%>',{a:'<div>123</div>'});
console.log(result);//<div>123</div>
CORS跨域资源共享
使用koa2-cors中间件
npm install koa2-cors --save
我在路由目录下新建了一个cors.js文件
const cors = require('koa2-cors');
let allowUrl = ['/upload']; //允许跨域的地址
//将cors()暴露出去给app.js使用
module.exports = function(){
return cors({
origin: function (ctx) {
let url = ctx.url.indexOf('?') >= 0 ? ctx.url.slice(0, ctx.url.indexOf('?')) : ctx.url;
if (allowUrl.indexOf(url) >= 0) {
return 'http://localhost:8080'; // 允许请求的域
}
return false;
},
exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
maxAge: 5,
credentials: true,
allowMethods: ['GET', 'POST'],
allowHeaders: ['Content-Type', 'Authorization', 'Accept'],
})
}
程序入口文件app.js中
const cors = require('./router/cors');
app.use(cors());
koa-body文件上传
npm install koa-body --save
app.js
const koaBody = require('koa-body');
app.use(koaBody({
multipart: true,
formidable: {
maxFileSize: 200*1024*1024 // 设置上传文件大小最大限制,默认2M
}
}));
新版本的koa-body通过ctx.request.files获取上传的文件,而旧版本的koa-body通过ctx.request.body.files获取上传的文件。
koa2路由
使用koa-router中间件
npm install koa-router --save
假设router/home.js路由文件
const Router = require('koa-router');
const router = new Router();
router.get('/xxx', async ctx => {})
app.js
const Router = require('koa-router');
const router = new Router();
//引入路由文件
const home = require('./router/home');
//装载子路由
router.use('', home.routes(), home.allowedMethods());
//启用路由中间件
app.use(router.routes()).use(router.allowedMethods());
这里要注意的是,app.js中的router.use()第一个参数与home.js中的路由地址是字符串连接的,如果app.js中写'/',那么最终的路由地址是'//xxx'
Cookie
跟原生node.js一样cookie不需要中间件,直接使用。
设置cookie
ctx.cookies.set(name, value, [options])
options一些配置说明
maxAge 一个数字表示从 Date.now() 得到的毫秒数
expires cookie 过期的 Date
path cookie 路径, 默认是'/'
domain cookie 域名
secure 安全 cookie 默认false,设置成true表示只有 https可以访问
httpOnly 是否只是服务器可访问 cookie, 默认是 true
overwrite 一个布尔值,表示是否覆盖以前设置的同名的 cookie (默认是 false). 如果是 true, 在同一个请求中设置相同名称的所有 Cookie(不管路径或域)是否在设置此Cookie 时从 Set-Cookie 标头中过滤掉。
基本用法
ctx.cookies.set('user', username, {
// domain: 'localhost', // 写cookie所在的域名
// path: '/index', // 写cookie所在的路径
maxAge: 60 * 1000, // cookie有效时长
// expires: new Date('2017-02-15'), // cookie失效时间
httpOnly: true, // 是否只用于服务器端
overwrite: false // 是否允许重写
})
取值
ctx.cookies.get('name')
koa中设置中文cookie可能会报错,可以用Buffer将中文转成base64编码存到cookie中
new Buffer('你好世界').toString('base64')// 转换成base64字符串:5L2g5aW95LiW55WM
new Buffer('5L2g5aW95LiW55WM', 'base64').toString()// 还原base64字符串:你好世界
Session
使用koa-session中间件
npm install koa-session --save
官方文档使用
const session = require('koa-session');
app.keys = ['some secret hurr'];
const CONFIG = {
key: 'koa:sess', //cookie key (default is koa:sess)
maxAge: 86400000, // cookie的过期时间 maxAge in ms (default is 1 days)
overwrite: true, //是否可以overwrite (默认default true)
httpOnly: true, //cookie是否只有服务器端可以访问 httpOnly or not (default true)
signed: true, //签名默认true
rolling: false, //在每次请求时强行设置cookie,这将重置cookie过期时间(默认:false)
renew: false, //(boolean) renew session when session is nearly expired,
};
app.use(session(CONFIG, app));
使用
设置 ctx.session.username = "xxx";
获取 ctx.session.username
文件下载
其实文件下载的思路并不难,就是设置个响应头,主要设置两个
res.setHeader('Content-Disposition', `attachment; filename=${filename}`);
res.setHeader('Content-Type', 'application/x-download; charset=utf-8');
看看简单的原生代码
let {pathname} = url.parse(req.url);
if(pathname.startsWith('/download/')){
let filename = pathname.slice(10);
let filepath = `logs/${filename}`;
let reader = fs.createReadStream(filepath);
res.setHeader('Content-Disposition', `attachment; filename=${filename}`);
reader.pipe(res);
}
使用koa-send中间件
npm install koa-send --save
基本使用
const send = require('koa-send');
router.get('/download/:filename', async ctx => {
let filepath = `logs/${ctx.params.filename}`;
ctx.attachment(filepath);
await send(ctx, filepath);
})
前端
<button class="btn">点我下载文件</button>
<script>
document.querySelector('.btn').onclick = function(){
window.open('/download/2018-11-07.log', '_blank');
}
</script>
axios实现文件下载
首先后台响应文件流,这里我还是不用koa-send中间件
router.get('/download', async ctx => {
//文件路径
let filepath = path.join(__dirname, `../logs/${ctx.query.filename}`);
//创建文件读取流
let reader = fs.createReadStream(filepath);
//设置响应头
ctx.set('Content-Disposition', `attachment; filename=${ctx.query.filename}`);
ctx.set('Content-Type', 'application/x-download; charset=utf-8');
//响应文件流
ctx.body = reader;
})
前端用blob对象接收流,然后创建a标签实现下载
axios.get('xxx').then(res => {
let blob = new Blob([res.data],{
type: 'text/plain' //文件MIME类型
});
let url = window.URL.createObjectURL(blob);
// window.location.href = url;
let a = document.createElement('a'); //用a标签指定下载文件名
a.href = url;
a.download = filename;
a.click();
})
koa-jwt
主要用来进行token验证的,一般还要配合jsonwebtoken使用。
安装
npm install jsonwebtoken koa-jwt --save
登录成功服务端给客户端下发token
const jwt = require('jsonwebtoken');
const jwtSecret = 'xxx';
router.post('/login', async ctx => {
let user = ctx.request.body;
let count = await User.count(user);
// 如果用户存在
if(count > 0){
// 生成token
let token = jwt.sign({
name: 'xxx'
}, jwtSecret, { //秘钥,自定义
expiresIn: '1h' //过期时间
})
return ctx.body = {code: 1, token}
}else{
return ctx.body = {code: 0}
}
})
前端接收到token后将它储存起来,可以用localStorage或sessionStorage,然后在每次请求时将token放入请求头中,这里举例在vue中用法
// 给每个请求头加token
axios.interceptors.request.use(config => {
const token = sessionStorage.getItem('token'); //我将token储存在sessionStorage
config.headers.common['Authorization'] = 'Bearer ' + token;
return config;
})
最后后台用koa-jwt中间件验证token
const koaJwt = require('koa-jwt');
const jwtSecret = 'xxx';
// token验证
router.use(koaJwt({
secret: jwtSecret
}));
jsonwebtoken验证token
let token = rq.body.token || rq.query.token || rq.headers["x-access-token"]; // 从body或query或者header中获取token
jwt.verify(token, secretOrPrivateKey, function (err, decode) {
if (err) { // 时间失效的时候/ 伪造的token
rs.json({err:err})
} else {
rq.decode = decode;
console.log(decode.msg); // token文本内容
next();
}
})
日志管理
我写了一个基于log4js的日志中间件koa-sam-log,我的文章
错误处理
利用koa2的洋葱模型在第一个中间件中try catch就可以。
app.use(async (ctx, next) => {
try{
await next();
if(ctx.status == 404) ctx.throw(404);//如果后台没响应,就报404
console.log(ctx.status, '没报错')
}catch(err){
console.log(ctx.status, err.statusCode, err.statusm, '报错了');
ctx.status = err.statusCode || err.status || ctx.status;
ctx.log.error(JSON.stringify({errorCode: ctx.status, errorMsg: err.message}));
ctx.body = err.message;
}
})