koa笔记 koa2 笔记
之前一直使用express觉得还可以,不久express团队又出了一个koa,也出现了1,2版本,目前学习最好使用koa2,使用了es6语法。直接上干货,使用过express上手搭建一个项目其实很简单。
首先:
npm install koa-generator -g //全局安装这个就可以使用koa命令了
在你需要创建koa项目的目录下输入以下命令
koa2 -e koa2 //此时就会出现一个koa2的目录,并且里面会有一些初始化的文件 如果想使用koa1的话就 koa -e 目录名称 //生成的是koa1的版本 koa2 -e 目录名称 //生成的时koa2的版本
进入到koa2目录下,接着安装需要的依赖模块
npm install
启动koa
node ./bin/www
最后打开浏览器输入:http://localhost:3000/ ,就可以看到我们网站建立了。
提示:如果简单使用node命令启动koa 2报错可以尝试使用
node --harmony xx.js
来启动我们的koa项目,因为koa有时候会需要es5的一些属性,带有–harmony参数才能够启用部分es5的功能。
要么升级node.js环境到7.6以上
————————————–
————————————–
简单的启动web服务,访问http://localhost:3000/
var Koa =require('koa');
var app=new Koa();
app.use(async function (ctx,next) {
await next();
ctx.response.type='text/html';
ctx.response.body='<div style="background: red">你好</div>';
});
app.listen(3000);
// koa2中间件执行顺序 (洋葱模式)
//最先的中间件会被执行,直到执行到最后一个中间件后又会再次返回来,
//中间件必须await next();不然就不会执行到下一个中间件了。
//koa2启动运行其实就是在按着洋葱模式执行着一个个中间件
//如果请求一个css文件和一个js文件,相当于执行了某个中间件两次
var Koa =require('koa');
var app=new Koa();
app.use(async function (ctx,next) {
console.log(1); //<--最先被执行
await next();
console.log(2); //<--最后被执行
});
app.use(async function (ctx,next) {
console.log(3);
console.log(4);
await next();
});
app.use(async function (ctx,next) {
await next();
console.log(5);
console.log(6);
});
app.use(async function (ctx,next) {
console.log(7);
await next();
console.log(8);
});
app.listen(3000);
// 结果
// 1
// 3
// 4
// 7
// 8
// 5
// 6
// 2
//封装一个中间件
//test.js中间件
function log( ctx ) {
console.log( '执行自定义中间件' )
}
module.exports = function () {
return async function ( ctx, next ) {
log(ctx);
await next()
}
}
//加载,并调用
var test=require('./test');
app.use(test());
*koa@2中间件只支持 async/await 封装的,如果要使用koa@1基于generator中间件,需要通过中间件koa-convert封装一下才能使用
中间件之间传递数据
app.use(async function (ctx,next) {
ctx.haha='haha';
await next();
});
app.use(async function (ctx,next) {
await next();
console.log(ctx.haha); //我能获取到上一个中间件赋值的变量
});
ctx上下文
ctx.response.type 可以缩写为 ctx.type,返回给浏览器的参数
有等号,表示还可以修改
ctx.body
ctx.body=
ctx.status
ctx.status=
ctx.length=
ctx.type
ctx.type= // ctx.type = ‘text/plain; charset=utf-8’;
ctx.headerSent
ctx.redirect()
ctx.attachment()
ctx.set() // ctx.set(‘Content-Type’, ‘text/html’)
ctx.remove()
ctx.lastModified=
ctx.etag=
—————————
ctx.request //发送给服务器端
ctx.header //ctx.request.header = {‘authorization’: “Bearer ” + ctx.cookies.get(‘state’)}
ctx.method
ctx.method=
ctx.url
ctx.url=
ctx.path
ctx.path=
ctx.query
ctx.query=
ctx.querystring
ctx.querystring=
ctx.length
ctx.host
ctx.fresh
ctx.stale
ctx.socket
ctx.protocol
ctx.secure
ctx.ip
ctx.ips
ctx.subdomains
ctx.is()
ctx.accepts()
ctx.acceptsEncodings()
ctx.acceptsCharsets()
ctx.acceptsLanguages()
ctx.get()
支持Es6的import导入
package.json 的根节点新增 “type”: “module” //注意要 v14.15.1以上, 不推荐这种写法,这样就不支持commonjs语法了
或者把要导入的模块改成后缀为mjs , import a from ‘./a.mjs’;
从 Node.js v13.2 版本开始
//区分测试环境、生产环境
可能需要在不同环境下跑不同的逻辑,这时候就需要到
安装:npm i cross-env -g //为win和linux做好了兼容
启动执行:cross-env NODE_ENV=production pm2 start ./1.js //以生产方式启动
获取判断方法:process.env.NODE_ENV //输出 production
cross-env NODE_ENV=production PORT=3001 node ./1.js
获取端口process.env.PORT
另一种方法:
"dev": "./node_modules/.bin/nodemon bin/www token=true",
//获取 bin/www xxx1=xxx2 后面的参数 打印出 { xxx1:xxx2 }
function getOption() {
const arr = process.argv.slice(2); // 获取命令行参数数组
const r = arr.reduce((pre, item) => { // 使用reduce方法对参数数组进行处理
if (item.indexOf("=") !== -1) { // 判断参数是否有等号
return [...pre, item.split("=")]; // 将带有等号的参数进行分割并添加到结果数组中
}
return pre; // 否则返回原结果数组
}, []);
const params = Object.fromEntries(r); // 将结果数组转化为参数对象
return params; // 返回参数对象
}
调用:getOption().token //获取都token的字段值
// 数据跟踪监听
var Koa =require('koa');
var app=new Koa();
const logger = require('koa-logger');
app.use(logger());
app.use(async function (ctx,next) {
await next();
ctx.response.type='text/html';
ctx.response.body='<div style="background: red">你好</div>';
});
app.listen(3000);

创建一个页面
创建首页http://127.0.0.1/
router.get('/', function (ctx, next) { //首页
ctx.body = 'Hello World!';
})
推荐此方法创建页面:
创建内页并且带2个参数 http://127.0.0.1/www/27/28
router.get('www/:id/:age', async function (ctx, next) {
ctx.state = {
title:'ffff'+ctx.params.id
};
await ctx.render('index', {
});
})
get传参
//page1页面传参
router.get('page1/:id/:age', async function (ctx, next) {
ctx.state = {
title:'ffff'+ctx.params.id
};
await ctx.render('index', {
});
})
地址:http://xxxx.com/page1/参数1/参数2
//首页传参
router.get(':id/:age', async function (ctx, next) {
ctx.state = {
title:'ffff'+ctx.params.id
};
await ctx.render('index', {
});
})
地址:http://xxxx.com/参数1/参数2
连缀
router //这里是4个路由
.get('/', function (ctx, next) {
ctx.body = 'Hello World!';
})
.post('/users', function (ctx, next) {
// ...
})
.put('/users/:id', function (ctx, next) {
// ...
})
.del('/users/:id', function (ctx, next) {
// ...
});
路由回调
为某个路由访问成功添加回调
router.get('/',async (ctx, next) => {
console.log('1');
await next(); //要加这个才能执行到all回调
console.log('2');
}).all('/',async function (ctx, next) {
console.log('3');
});
结果:
1
3
2
跟中间件的洋葱模式一样
路由全局 (很有用) 拦截器
效果就是访问任意一个url路由之前时,都要执行某段业务逻辑。
不管先访问首页还是haha,都要先执行all路由。
router.all('*',async function (ctx, next) {
ctx.state={ //全局变量,在ejs模板中可以直接显示出,如<%= web %>
web:'http://localhost:3000',
title:'111'
}
await next(); //必须加这个
});
router.get('/',async (ctx, next) => {
//ctx.state,可以获取到初始变量
ctx.type='json';
ctx.body={xx:'123'};
});
router.get('haha',async (ctx, next) => {
ctx.body='haha';
});
或者
router.use(async (ctx,next)=>{
//全局的G变量
ctx.state.G={
url:'http://www.itying.com',
userinfo:111,
prevPage:222 /*上一页的地址*/
}
await next();
})
router.use('/',index.routes());
router.use('/admin',admin.routes());
前缀
const router = new Router({
prefix: '/users'
});
router.get('/', ...); //实际的响应路径是:/users
router.get('/:id', ...); //实际的响应路径是:/users/:id
为一个路由命名
router.get('home',':haha/:vv', function (ctx, next) { //为首页的路由命名为home
ctx.body = 'Hello World!';
router.url('home', 3,'age') //第一次参数填写路由名称,第二个参数开始填写自定义参数,输出:/3/age
})
路由使用中间件
router.use(session(), authorize()); //所有路由都使用这两个中间件
router.use('/users', userAuth()); //只有跳转到/users页面时使用 这个中间件
router.use(['/users', '/admin'], userAuth()); //只有这两个页面可以使用
app.use(router.routes()); //上面是设置,此步骤是启动
单个路由支持多个中间件执行
//child.js
function log( ctx ) {
console.log( '执行自定义中间件' )
}
module.exports = function () {
return async function ( ctx, next ) {
log(ctx);
await next()
}
};
var child=require('./child');
router.get('/',
child(), //一个中间件
async (ctx,next)=>{ //一个中间件
console.log(11111);
await next();
},
async (ctx, next) => {
await ctx.render('index',{
title:'xxxxx'
})
});
重定向、页面跳转
ctx.redirect('http://localhost:3000/www/123/456')
app.use(async function (ctx,next) { //访问到首页就自动跳转到/wucg目录。像apache、nginx也能实现
if(ctx.originalUrl=='/'){
ctx.response.redirect('/wucg');
}
await next();
});
router.use('/wucg',index.routes(),index2.routes());
router.use('/admin',admin.routes());
app.use(router.routes(), router.allowedMethods());
//让页面报错 抛错
//全局,在一个路由上设置,会让所有接口都报400
ctx.throw([msg],[status],[properties])
ctx.throw(400, 'name required');
//局部,单个接口报错
router.post('/hnsy-app/aed/pageAedInfo',async (ctx, next)=>{
await new Promise((a,b)=>{
setTimeout(()=>{
ctx.status=500
a()
},8000)
})
})
SSL、https
路由参数验证
要访问该地址之前http://127.0.0.1/www/参数1/参数2,先验证参数1,2,在决定是否在跳转
router
.param('ff', function (ff, ctx, next) { //参数1的验证逻辑
console.log(ff+'<----------------');
return next();
})
.param('vv', function (vv, ctx, next) { //参数2的验证逻辑
console.log(vv+'<----------------');
if (!vv) return ctx.status = 404;
return next();
})
.get('www/:ff/:vv', async function (ctx, next) {
// ctx.state = {
// title:'shouye1111'
// };
await ctx.render('index', {
title:'shouye1111'+ctx.params.ff
});
})
一个路由业务逻辑分离成多个路由
假如我们在写后台的业务逻辑时,往往需要很多判断,我们不可能把所有代码都写进一个路由中,时间长了代码不容易维护,这时候我们需要把后台的业务逻辑同时分离归类成多个文件,编辑文件app.js
const admin = require('./routes/admin/admin_login'); //后台登陆页面
const admin_default = require('./routes/admin/admin_default'); //后台登陆页面后的业务逻辑
.
.
.
router.use('/admin', admin.routes(), admin.allowedMethods());
router.use('/admin', admin_default.routes(), admin_default.allowedMethods());
或者
const admin = require('./routes/admin/admin_login');
router.use(admin.routes(), admin.allowedMethods());
编辑:./routes/admin/admin_login.js,
const router = require('koa-router')()
router.prefix('/admin') //新增
————以上是路由的使用——————–
post和get
后台获取客户端get发来的数据
ctx.params //获取到的是以路由 router.get(‘/:name’) 形式 ,http://xxx/123
ctx.query //{wo:’chen’,age:’123′} ,获取到的是url这样传递的数据,http://xxx/1.php?wo=’chen’&age=’123′
后台获取客户端post发来的数据
ctx.request.body
cookies的设置和获取
ctx.cookies.set(‘name’, ‘value’, {httpOnly:false}) //设置 {httpOnly:false},表示可以通过客户端获取cookies的值
signed: 是否要做签名
expires: cookie 有效期时间
path: cookie 的路径,默认为 /’
domain: cookie 的域
secure: false 表示 cookie 通过 HTTP 协议发送,true 表示 cookie 通过 HTTPS 发送。
httpOnly: true 表示 cookie 只能通过 HTTP 协议发送
例如:
ctx.cookies.set(
'state',
'hello world',
{
domain: 'localhost', // 写cookie所在的域名
path: '/', // 写cookie所在的路径
maxAge:7200000, // 2小时有效
expires: new Date('2017-07-15'), // cookie失效时间
httpOnly: false, // 是否只用于http请求中获取
overwrite: false // 是否允许重写
}
)
ctx.cookies.set('token', response2.data.access_token,{maxAge:7200000}) //2小时有效
ctx.cookies.get(name, [options]) //获取
session使用
方法一:
npm install koa-session --save //安装
//编辑main.js,加入以下内容
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));
方法二:
npm install koa2-cookie-session --save
import session from "koa2-cookie-session"; //一定要用这种方式加载
var router = require('koa-router')();
router.use(session({
key: "SESSIONID", //default "koa:sid"
expires:3, //default 7
path:"/" //default "/"
}));
router.get('home',':haha/:vv',async function (ctx, next) {
ctx.session.user = {name: "myname更新"}; //设置
});
ctx.session.user //获取
api 接口
router.get('/json', async (ctx, next) => {
ctx.body = {
code:200,
title: 'koa2 json',
data:{
xx:1,
xx:2
}
}
})
生成log日志文件
下载:log
1,安装:npm install –save log4js //4.5.1
2,解压zip把utils和config目录放到根目录下
// logger
const logsUtil = require('./utils/logs.js');
app.use(async (ctx, next) => {
const start = new Date(); // 响应开始时间
let intervals; // 响应间隔时间
try {
await next();
intervals = new Date() - start;
logsUtil.logResponse(ctx, intervals); //记录响应日志
} catch (error) {
intervals = new Date() - start;
logsUtil.logError(ctx, error, intervals);//记录异常日志
}
})
router.use('/',index.routes(),index2.routes());
router.use('/admin',admin.routes());
启动项目后,访问下网站就会在根目录下生成logs目录,里面就记录这log日志文件了
记录指定路由日志
例如routes/index
const logsHandle = require("../utils/logs.js").logHandle;
const logsInfo = require("../utils/logs.js").logInfo;
.
.
router.get('json', async (ctx, next) => {
logsHandle("请求了json接口") // 假设此处需要记录敏感操作的日志
logsInfo("请求了json接口") // 假设此处需要在控制台打印日志
ctx.body = {
title: 'koa2 json'
}
})
日志打印出

https://www.cnblogs.com/HoChine/p/10717831.html
自定义404页面
(是当用户乱输入url时,没有相应的路由匹配的时候才跳转404页面)
404页面也可以用nginx、apache来处理
编辑文件:app.js
//编辑以下代码
app.use(async (ctx, next) => {
if (ctx.status === 404){
ctx.state={
title:'404页面'
};
await ctx.render('error', { //error为你的错误页面
});
}
});
module.exports = app;
接着在views/error.ejs,创建错误页面,即可。
自定义接口错误信息,编辑app.js
// 身份认证错误中间件
app.use(async (ctx, next) => {
return next().catch(err => {
if (err.status === 401) {
// 自定义返回结果
ctx.status = 401;
console.log(err.name);
ctx.body = {
code: 401,
msg: err.message
};
} else {
throw err;
}
});
});
效果:

时间戳
这里推荐使用一款模块插件
安装:
npm install moment
加载调用:
var moment = require('moment');
moment().format();
案例:
moment().format('YYYY-M-DD kk:mm:ss'); //2017-01-01 01:01:01
文档:http://momentjs.com/docs/#/displaying/
上传
安装:
npm install koa-body@2 --save
前端:创建页面views/upload.ejs
<!doctype html> <html> <body> <form action="/upload" enctype="multipart/form-data" method="post"> <input type="text" name="username" placeholder="username"><br> <input type="text" name="title" placeholder="title of file"><br> <input type="file" name="uploads" multiple="multiple"><br> <button type="submit">Upload</button> </form> </body> </html>
后台:routes/index.js
router.get('/haha',async function (ctx,next) {
await ctx.render('upload', {});
});
router.post('upload',
koaBody({
multipart: true,
formidable: {
uploadDir:process.cwd() + '/public/upload',
keepExtensions:true,
onFileBegin: (name, file) => { // 文件上传前的设置
const fp = process.cwd() + '/public/upload';
if (!fs.existsSync(fp)) { // 检查是否有“public/upload/”文件夹
fs.mkdirSync(fp); // 没有就创建
}
}
}
}),
async function (ctx,next) {
await new Promise((a,b)=>{
if((ctx.request.body.files["file"]).size==0){
fs.unlink((ctx.request.body.files["file"]).path,function (err) {
if(err) throw err;
console.log('成功')
});
}
a();
});
ctx.body =ctx.request.body;
}
)
要注意提交action的路径和图片上传到服务器的路径
访问http://localhost:3000/haha
koa-body参数:
| 参数名 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| patchNode | 将补丁写入ctx.req节点中 | Boolean | false |
| patchKoa | 将补丁写入ctx.request节点中 | Boolean | true |
| jsonLimit | JSON 数据体的大小限制 | String / Integer | 1mb |
| formLimit | 限制表单请求体的大小 | String / Integer | 56kb |
| textLimit | 限制 text body 的大小 | String / Integer | 56kb |
| encoding | 表单的默认编码 | String | utf-8 |
| multipart | 是否支持 multipart-formdate 的表单 | Boolean | false |
| urlencoded | 是否支持 urlencoded 的表单 | Boolean | true |
| text | 是否解析 text/plain 的表单 | Boolean | true |
| json | 是否解析 json 请求体 | Boolean | true |
| jsonStrict | 是否使用 json 严格模式,true 会只处理数组和对象 | Boolean | true |
| formidable | 配置更多的关于 multipart 的选项 | Object | |
| onError | 错误处理 | Function | |
| stict | 严格模式,启用后不会解析 GET, HEAD, DELETE 请求 | Boolean | true |
其中formidable
| 参数名 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| maxFields | 限制字段的数量 | Integer | 1000 |
| maxFieldsSize | 限制字段的最大值 | Integer | 2mb (2 * 1024 * 1024) |
| uploadDir | 设置文件上传的文件夹 | String | os.tmpDir() |
| keepExtensions | 保留原来的文件后缀 | Boolean | false |
| hash | 如果希望计算传入文件的校验,请将其设置为“sha1”或“md5” | String | false |
| multipart | 是否支持多文件上传 | Boolean | true |
| onFileBegin | 开始时对文件进行特殊回调。它可以用于在将文件保存到磁盘之前重命名文件等 | Function |
案例:
推荐把bodyparse加载到app.js里面的全局中,不然每个router都要加载才能获取post传来的数据不现实
app.use(koaBody({
multipart: true,
formidable: {
uploadDir:process.cwd() + '/public/upload',
keepExtensions:true,
onFileBegin: (name, file) => { // 文件上传前的设置
const fp = process.cwd() + '/public/upload';
if (!fs.existsSync(fp)) { // 检查是否有“public/upload/”文件夹
fs.mkdirSync(fp); // 没有就创建
}
// console.log(`bodyparse: name:${name}; file:${util.inspect(file)}`);
}
}
}));
https://blog.csdn.net/weixin_40415614/article/details/84783723
上传base64图片
base64的形式为“data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0。。。。”;
当接收到上边的内容后,需要将data:image/png;base64,这段内容过滤掉,过滤成:“iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0。。。”;传给后台
前端:
<form action="/uploadbase64" enctype="multipart/form-data" method="post"> <input value="" name="baseimg" hidden id="baseimg"> <input id="fielinput" type="file" multiple="multiple" accept="image/*" onchange="showPreview(this)"><br> <img id="fielImg" src="" /> <button>Upload</button> </form>
<script>
function showPreview(source) {
var file = source.files[0];
//判断文件类型
var extfile = file.name;
var AllImgExt=".jpg|.jpeg|.gif|.bmp|.png|";
var extName = extfile.substring(extfile.lastIndexOf(".")).toLowerCase();//(把路径中的所有字母全部转换为小写)
if(AllImgExt.indexOf(extName+"|")==-1)
{
var ErrMsg="该文件类型不允许上传。请上传 "+AllImgExt+" 类型的文件,当前文件类型为"+extName;
alert(ErrMsg);
return false;
}
if(window.FileReader) {
var fr = new FileReader();
fr.readAsDataURL(file);
fr.onloadend = function(e) {
document.getElementById("fielImg").src = e.target.result;
document.getElementById("baseimg").value = e.target.result;
};
}else{
alert('浏览器不支持预览图片');
}
};
</script>
后台:
router.post('uploadbase64',async (ctx, next) => {
var imgData=ctx.request.body.fields.baseimg;
var reg=/^data:image\/(\w+);base64,/; //传给后台的base64,前面这部分的不要,前端显示才需要
var base64Data = imgData.replace(reg, "");
var imgtype=RegExp.lastParen; //获取到括号的字符串
// var dataBuffer = new Buffer(base64Data, 'base64'); //也可以用,但是会提示有安全问题
var dataBuffer =Buffer.from(base64Data, 'base64');
fs.writeFile(process.cwd() + '/public/upload/image.'+imgtype, dataBuffer, function(err) {
// if(err){
// res.send(err);
// }else{
// res.send("保存成功!");
// }
});
ctx.body =ctx.request.body;
});
下载
接口模拟延迟
router.post('/syldmh-web/service/wdzhq/getTopicData',async (ctx,next) =>{
async function delay(time) {
return new Promise(function(resolve, reject) {
setTimeout(function(){
resolve();
}, time);
});
};
await delay(4000);
ctx.body = {}
})
图片压缩
使用第三方:https://tinypng.com/developers/reference/nodejs
安装:npm install –save tinify
在 app.js
const tinify = require("tinify");
tinify.key = "API Key";
上传接口:
const tinify = require("tinify");
.
.
router.post('upload',koaBody({
multipart: true,
formidable: {
uploadDir:process.cwd() + '/public/upload',
keepExtensions:true,
onFileBegin: (name, file) => { // 文件上传前的设置
const fp = process.cwd() + '/public/upload';
if (!fs.existsSync(fp)) { // 检查是否有“public/upload/”文件夹
fs.mkdirSync(fp); // 没有就创建
}
}
}
}),async (ctx, next) => {
if( !!ctx.request.body.files["file"].length ){ //多图片
for(let i=0;i<ctx.request.body.files["file"].length;i++){
fs.readFile(ctx.request.body.files['file'][i].path, function(err, sourceData) {
if (err) throw err;
tinify.fromBuffer(sourceData).toBuffer(function(err, resultData) {
if (err) throw err;
console.log(resultData);
fs.writeFile(ctx.request.body.files['file'][i].path, resultData, function(err) { //压缩后覆盖原图
console.log('覆盖成功');
});
});
});
}
}else{ //单图片
fs.readFile(ctx.request.body.files['file'].path, function(err, sourceData) {
if (err) throw err;
tinify.fromBuffer(sourceData).toBuffer(function(err, resultData) {
if (err) throw err;
console.log(resultData);
fs.writeFile(ctx.request.body.files['file'].path, resultData, function(err) { //压缩后覆盖原图
console.log('覆盖成功');
});
});
});
}
ctx.body =ctx.request.body;
});
启动服务器仅执行一次
console.log('只会执行一次');
app.use(async function (ctx,next) {
await next();
});
只要不把代码写在中间件里面,只有koa启动时执行一次
文本编辑器wangeditor2
http://www.kancloud.cn/wangfupeng/wangeditor2/113964
https://github.com/wangfupeng1988/wangEditor
加载必要插件:
https://github.com/wangfupeng1988/wangEditor/tree/master/test/plupload/lib/plupload
<!--引入wangEditor.css-->
<link rel="stylesheet" type="text/css" href="../dist/css/wangEditor.min.css">
<div id="div1">
<p>请输入内容...</p>
</div>
<!--引入jquery和wangEditor.js--> <!--注意:javascript必须放在body最后,否则可能会出现问题-->
<script type="text/javascript" src="../dist/js/lib/jquery-1.10.2.min.js"></script>
<script type="text/javascript" src="lib/plupload/plupload.full.min.js"></script>
<script type="text/javascript" src="../dist/js/wangEditor.min.js"></script>
<script type="text/javascript">
function printLog(title, info) {
window.console && console.log(title, info);
}
// ------- 配置上传的初始化事件 -------
function uploadInit () {
// this 即 editor 对象
var editor = this;
// 编辑器中,触发选择图片的按钮的id
var btnId = editor.customUploadBtnId;
// 编辑器中,触发选择图片的按钮的父元素的id
var containerId = editor.customUploadContainerId;
//实例化一个上传对象
var uploader = new plupload.Uploader({
browse_button: btnId, // 选择文件的按钮的id
url: '/admin/upload', // 服务器端的上传地址
flash_swf_url: '/plupload/lib/plupload/Moxie.swf',
sliverlight_xap_url: '/plupload/lib/plupload/Moxie.xap',
filters: {
mime_types: [
//只允许上传图片文件 (注意,extensions中,逗号后面不要加空格)
{ title: "图片文件", extensions: "jpg,gif,png,bmp" }
]
}
});
//存储所有图片的url地址
var urls = [];
//初始化
uploader.init();
//绑定文件添加到队列的事件
uploader.bind('FilesAdded', function (uploader, files) {
//显示添加进来的文件名
$.each(files, function(key, value){
printLog('添加文件' + value.id);
});
// 文件添加之后,开始执行上传
uploader.start();
});
//单个文件上传之后
uploader.bind('FileUploaded', function (uploader, file, responseObject) {
//注意,要从服务器返回图片的url地址,否则上传的图片无法显示在编辑器中
var url = responseObject.response;
//先将url地址存储来,待所有图片都上传完了,再统一处理
urls.push(url);
printLog('一个图片上传完成,返回的url是' + url);
});
//全部文件上传时候
uploader.bind('UploadComplete', function (uploader, files) {
printLog('所有图片上传完成');
// 用 try catch 兼容IE低版本的异常情况
try {
//打印出所有图片的url地址
console.log(urls)
$.each(urls, function (key, value) {
printLog('即将插入图片' + value);
// 插入到编辑器中
//editor.command(null, 'insertHtml', '<img src="' + value + '" style="max-width:100%;"/>');
editor.$txt.append('<img src="' + value + '" style="max-width:100%;"/>');
});
} catch (ex) {
// 此处可不写代码
} finally {
//清空url数组
urls = [];
// 隐藏进度条
editor.hideUploadProgress();
}
});
// 上传进度条
uploader.bind('UploadProgress', function (uploader, file) {
// 显示进度条
editor.showUploadProgress(file.percent);
});
}
// ------- 创建编辑器 -------
var editor = new wangEditor('div1');
editor.config.customUpload = true; // 配置自定义上传的开关
editor.config.customUploadInit = uploadInit; // 配置上传事件,uploadInit方法已经在上面定义了
editor.create();
</script>
admin/upload页面
//编辑器中的上传,支持单、多文件上传
router.post('/upload',
koaBody({
multipart: true,
formidable: {
uploadDir:path.join(process.cwd(), '/public/images')
}
}),
async function (ctx,next) {
//console.log(ctx.request.body.fields);
// => {username: ""} - if empty
//console.log( ((((ctx.request.body.files["file"])).name).split('.'))[(((((ctx.request.body.files["file"])).name).split('.')).length-1)] );
//console.log( (((ctx.request.body.files)["uploads"])).path );
// console.log( (((((ctx.request.body.files)["uploads"])).name).split('.'))[(((((ctx.request.body.files)["uploads"])).name).split('.')).length-1] );
/* => {uploads: [
{
"size": 748831,
"path": "/tmp/f7777b4269bf6e64518f96248537c0ab.png",
"name": "some-image.png",
"type": "image/png",
"mtime": "2014-06-17T11:08:52.816Z"
},
{
"size": 379749,
"path": "/tmp/83b8cf0524529482d2f8b5d0852f49bf.jpeg",
"name": "nodejs_rulz.jpeg",
"type": "image/jpeg",
"mtime": "2014-06-17T11:08:52.830Z"
}
]}
*/
// ctx.body = JSON.stringify(ctx.request.body, null, 2)
await new Promise((a,b)=>{
cox.body='http://www.xxx.com/1.jpg'
})
}
)
验证码
加密
var crypto = require('crypto'); //node.js内置
module.exports = function(pwd) {
var hash = crypto.createHash('sha256').update(pwd).digest('base64');
return hash;
};
验证token
手机页面
使用pc端或者手机端访问可以智能判断切换页面,实现方法有2种:
1,在客户端顶部添加js来判断pc、手机端来实现域名跳转,来访问不同页面。好处就是让服务器减轻负担,坏处就是域名发生跳转。
2,在后台判断来实现加载pc、手机端模版,好处是域名始终不变。坏处就是,稍微给服务器添加一些负担。
npm install koa2-useragent --save
//编辑app.js
const Koa = require('koa');
.
.
.
import userAgent from 'koa2-useragent';
app.use(userAgent());
//路由
router.get('/', async function (ctx, next) {
console.log(ctx.userAgent);
.
.
.
if(ctx.userAgent.isMobile){ //客户端不同,应用不同模板
await ctx.render('mobile_index.ejs', {
});
}else{
await ctx.render('home_index', {
});
}
})
https://www.npmjs.com/package/koa2-useragent
子进程
一些繁重的业务逻辑给子线程去做然后传回给主线程,防止主线程挂壁
//父亲
var childProcess = require('child_process');
.
.
router.get('/',async (ctx, next) => {
await new Promise((a,b)=>{
childProcess.fork(process.cwd()+'/routes/child.js',[]).on('message', function(m) {
console.log('儿子发来的信息: ', m); //此处是,老爸接受到儿子发来的信息
a();
}).send(ctx); //把ctx传给儿子
});
await ctx.render('index', Object.assign({
title:'55'
},pubObj));
});
//child.js 儿子 (子进程)
;(async function () {
process.on('message', function(ctx) {
ctx.xxx=123; // ctx 是老爸传来的数据,也可以自己添加额外数据传回去
process.send({ Hello: '老爸我是儿子' }); //向老爸发送信息
process.exit(0); //儿子发数据给老爸后,任务完成退出。
});
})();
数据缓存
npm install node-cache --save
准备:
加载并实例化
const NodeCache = require( "node-cache" );
const myCache = new NodeCache(); //参数默认
const myCache = new NodeCache( { stdTTL: 0, checkperiod: 600 } );
//单值
myCache.set( "myKey", "aa");//设置
myCache.get( "myKey" ) //取值
myCache.has("myKey") //判断值是否存在true存在,false不存在
myCache.del( "myKey" ) //删除
value = myCache.take( "myKey" ) //赋值给value变量并删除"myKey"
//多值
myCache.mset([
{key: "myKey", val: obj, ttl: 10000},
{key: "myKey2", val: obj2},
])
myCache.mget( [ "myKeyA", "myKeyB" ] );
myCache.del( [ "B", "C" ] )
myCache.keys(); //获取当前所有变量名 [ "all", "my", "keys", "foo", "bar" ]
//回调
myCache.on( "set", function( key, value ){
// ... do something ...
});
myCache.on( "del", function( key, value ){
// 手动删除,或者值过期
});
myCache.on( "expired", function( key, value ){
//值过期
});
https://www.npmjs.com/package/node-cache
代理
请求第三方接口,把数据渲染到前端
安装所需模块:
npm i request -S //用来请求接口类似ajax npm install --save-dev koa2-proxy-middleware //设置代理
编辑app.js
const proxy = require('koa2-proxy-middleware'); //加载
const bodyparser = require('koa-bodyparser')
.
.
.
//参数设置
const options = {
targets: {
'/api/(.*)': {
target: 'http://testh3.xxx.com/',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
},
}
}
app.use(proxy(options)); //注册到koa中
// bodyparser一定要放在代理后面不然会有问题
app.use(bodyparser({ //解析post数据
enableTypes:['json', 'form', 'text']
}))
路由案例:
const router = require('koa-router')()
const request = require('request');
//注意/api/就相当于前缀,其实真是的接口没有,本地代理需要加上而已
request.post('http://localhost:3000/api/wucg/cms/provinceCity/list', function optionalCallback(err, httpResponse, body) {
router.get('/', async (ctx, next) => {
await ctx.render('index', {
title: body
})
})
})
– – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
纯koa2模块介绍
– – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
以上的教程是基于koa-generator脚手架进行开发,安装的同时也大量安装了一些附属依赖模块让开发的更简单。koa-generator其实也就是以koa2为基础,依赖大量的模块进行组装,如果单纯的使用koa2来进行一步步开发又是如何的呢?
首先安装
npm install koa@next //保证安装koa是2.x版本
– – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
搭建http
//1.js
const Koa = require('koa');
const app = new Koa();
app.listen(3000);
执行node 1.js
打开http://localhost:3000/ ,会看到”Not Found”,那是因为没有像客户端发送任何数据。
– – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
向客户端发送数据
const Koa = require('koa');
const app = new Koa();
app.use((ctx)=>{
ctx.response.body = '你好';
});
app.listen(3000);
打开页面 http://localhost:3000/ ,可以看到显示内容“你好”,如果没有指定发送给客户端的数据类型默认是:text/plain

– – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
指定发送客户端的数据类型
const Koa = require('koa');
const app = new Koa();
app.use((ctx)=>{
ctx.response.type = 'html';
ctx.response.body = '<html><head><title>你好</title></head><body><p>这里</p></body></html>';
});
app.listen(3000);
其他常见类型有:xml、json、text
– – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
koa2原生路由
app.use((ctx)=>{
if(ctx.request.path == '/'){ //首页
ctx.response.type = 'html';
ctx.response.body = '<html><head><title>你好</title></head><body><p>首页</p></body></html>';
}
if(ctx.request.path == '/about'){ // http://localhost:3000/about
ctx.response.type = 'html';
ctx.response.body = '<html><head><title>你好</title></head><body><p>关于我们</p></body></html>';
}
});
– – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
静态资源
//在二级目录public里面存放着css.css
const Koa = require('koa');
const app = new Koa();
app.use(require('koa-static')(__dirname + '/public'));
app.listen(3000);
html:
目录:public\js\jquery.min.js <script src="/js/jquery.min.js"></script> //引入
访问http://127.0.0.1:3000/css.css ,可以直接访问到内容
– – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
为静态资源加版本号
<script src="<%- '/js/jquery.min.js?='+ver %>"></script>
var pubObj={ver:'22.2'}; //公共对象,推荐放在app.js中
router.get('/',async (ctx, next) => {
await ctx.render('index', Object.assign({
title:'首页'
},pubObj));
});
推荐使用gulp、webpack类似工具添加版本号
– – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
302自动跳转、重定向
const Koa = require('koa');
const app = new Koa();
app.use((ctx)=>{
if(ctx.request.path == '/'){
ctx.response.redirect('/about') //访问首页时,会自动跳转到/about页面
ctx.response.type = 'html';
ctx.response.body = '<html><head><title>你好</title></head><body><p>首页</p></body></html>';
}
if(ctx.request.path == '/about'){
ctx.response.type = 'html';
ctx.response.body = '<html><head><title>你好</title></head><body><p>关于我们</p><form method="get" action="/ser"><input type="text" name="xxx1"><input type="submit" value="提交"></form></body></html>';
}
});
app.listen(3000);
– – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
中间件,在客户端中将数据传递给后台接收时,的中间过程都会经历一层层的中间处理后才能将数据传递到后台。
app.use() //就是专门加载中间件的,new require(‘koa’)().use
const Koa = require('koa');
const app = new Koa();
// 以下执行顺序是同步执行
const one = (ctx, next) => {
console.log('>> one');
next();
console.log('<< one');
}
const two = (ctx, next) => {
console.log('>> two');
next();
console.log('<< two');
}
const three = (ctx, next) => {
console.log('>> three');
next();
console.log('<< three');
}
app.use(one);
app.use(two);
app.use(three);
app.listen(3000);
-----------------
结果:
>> one
>> two
>> three
<< three
<< two
<< one
const Koa = require('koa');
const app = new Koa();
const one = (ctx, next) => {
console.log('>> one');
next();
console.log('<< one');
}
const two = (ctx, next) => {
console.log('>> two');
//next() 删除
console.log('<< two');
}
const three = (ctx, next) => {
console.log('>> three');
next();
console.log('<< three');
}
app.use(one);
app.use(two);
app.use(three);
app.listen(3000);
-----------------
结果:
>> one
>> two
<< two
<< one
所以说,要保证所有的中间件能运行的到,就要使用next()进行串联
中间件,异步执行
const one =async (ctx, next) => {
await console.log('>> one1');
next();
await console.log('<< one2');
}
const two =async (ctx, next) => {
await console.log('>> two3');
next();
await console.log('<< two4');
}
const three =async (ctx, next) => {
await console.log('>> three5');
next();
await console.log('<< three6');
}
执行结果完全打乱
– – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
合并中间件,如果觉得app.use()使用的频率过高,可以使用koa-compose
npm install koa-compose
案例:
const middlewares = compose([one, two,three]); app.use(middlewares);
– – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
500 错误
const main = ctx => {
ctx.throw(500);
};
404错误
const main = ctx => {
ctx.response.status = 404; //就相当于ctx.throw(404)
ctx.response.body = 'Page Not Found';
};
– – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
错误的统一处理
为了方便处理错误,最好使用try…catch将其捕获。但是,为每个中间件都写try…catch太麻烦,我们可以让最外层的中间件,负责所有中间件的错误处理。
const handler = async (ctx, next) => {
try {
await next();
} catch (err) { //报错执行此处
ctx.response.status = err.statusCode || err.status || 500;
ctx.response.body = {
message: err.message
};
}
};
const main = ctx => {
ctx.throw(500);
};
app.use(handler);
app.use(main);
类似效果,对错误后的处理
const main = ctx => {
ctx.throw(500);
};
app.on('error', (err, ctx) =>
console.error('server error', err);
);
注意:同时使用try…catch 和 app.on(‘error’,fn),监听error事件不会被触发,除非释放错误事件,例如:
const handler = async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.response.status = err.statusCode || err.status || 500;
ctx.response.type = 'html';
ctx.response.body = '<p>Something wrong, please contact administrator.</p>';
ctx.app.emit('error', err, ctx); //释放错误app.on('error',fn)才能被执行
}
};
const main = ctx => {
ctx.throw(500);
};
app.on('error', function(err) {
console.log('logging error ', err.message);
console.log(err);
});
– – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
cookies
const main = function(ctx) {
ctx.cookies.set('view', '187'); //设置
ctx.response.body = ctx.cookies.get('view'); //取值
}