KOA 是什么
后端框架(以 nodejs 作为运行时环境的 js 框架),处理 http 请求并响应的框架,注意,学完 KOA,一定要放弃 Express
学 koa 之前
要掌握 es7 知识,会查 nodejs 的 api,了解 http 是什么
入门
创建项目
mkdir stukoa
cd stukoa
# 初始化node项目
yarn init
# 加入koa依赖
yarn add koa2
Hello world
# 新建一个js文件
touch app.js
写入代码
const Koa = require('Koa')
const app = new Koa()
app.use(async(ctx)=>{
ctx.body = 'hello koa2'
})
app.listen(8080)
console.log('the server is running on 8080')
启动服务
node app.js
服务启动后查看 hello world页面
ctx
const Koa = require('Koa')
const app = new Koa()
app.use(async(ctx)=>{
// 打印一下ctx
// 打上断点
console.dir(ctx);
ctx.body = 'hello koa2'
})
app.listen(8080)
console.log('the server is running on 8080')
看看 ctx 中有什么
我们可以看到 ip,query,request,response,request.header,response.body 等属性
而 koa 的核心就是处理 http 请求并返回数据
ctx = context:应用上下文,里面直接封装部分 request.js 和 response.js 的方法
路由
那我们只有一个 ctx,如何应对各种格式路径的请求呢
// 我们现在访问localhost:8080/userinfo
// 打印出了/userinfo
console.log(ctx.request.url)
现在我们明白如何去区分 url 了
//我们可以手动区分
app.use(async(ctx)=>{
if(ctx.request.url === '/userinfo'){
ctx.body = {name:'王二牛',age:15,sex:0}
}
if(ctx.request.url === '/userinfo/1'){
ctx.body = {name:'王三牛',age:15,set:0}
}
})
一个一个区分太麻烦了代码越写越乱,我们可以引入 koa 的插件,按照插件的配置写就好了
(中间件)插件
为什么叫做中间件,这里我们提出 koa 的思想
最左边是 httpRequest,经过一堆中间件(过滤器?管道?就是这个意思),然后 response 出去
犹豫插件加载在 request 和 response 中间,所以我们叫插件为中间件
koa-router
yarn add koa-router@7
const Koa = require('koa')
const fs = require('fs')
const app = new Koa()
const Router = require('koa-router')
let home = new Router()
// 子路由1
// 我们访问/会获取到html
// 两个链接
// /page/helloworld
// /page/404
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()
// 404 路由,对应的response
// helloworld路由
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())
// 这里为/page装载了 /404 /helloworld
// 拼起来就是 /page/404 /page/helloworld
router.use('/page', page.routes(), page.allowedMethods())
// 加载路由中间件
// 在洋葱中在加一层我们引入的路由中间件
app.use(router.routes()).use(router.allowedMethods())
app.listen(3000, () => {
console.log('[demo] route-use-middleware is starting at port 3000')
})
koa-bodyparser
获取请求参数
get
// get请求?a=b&c=d类似形式
// 从上下文中直接获取
let ctx_query = ctx.query
let ctx_querystring = ctx.querystring
json
// 如果是json格式的直接转换为对象就可以用了
let obj = JSON.parse(ctx.request.body);
post
那既不是 get 也不是 json 的 post 请求呢,我们需要用到转换中间件
yarn add koa-bodyparser@3
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 = `
<h1>koa2 request post demo</h1>
<form method="POST" action="/">
<p>userName</p>
<input name="userName" /><br/>
<p>nickName</p>
<input name="nickName" /><br/>
<p>email</p>
<input name="email" /><br/>
<button type="submit">submit</button>
</form>
`
ctx.body = html
} else if ( ctx.url === '/' && ctx.method === 'POST' ) {
// 当POST请求的时候,中间件koa-bodyparser解析POST表单里的数据,并显示出来
let postData = ctx.request.body
ctx.body = postData
} else {
// 其他请求显示404
ctx.body = '<h1>404!!! o(╯□╰)o</h1>'
}
})
app.listen(3000, () => {
console.log('[demo] request post is starting at port 3000')
})
koa-static
koa-static 为我们提供了静态资源解析的功能
const Koa = require('koa')
const path = require('path')
const static = require('koa-static')
const app = new Koa()
// 静态资源目录对于相对入口文件index.js的路径
const staticPath = './static'
// __dirname为nodejs全局变量
// 将静态资源文件按照文件夹路径解析出来
app.use(static(
path.join( __dirname, staticPath)
))
app.use( async ( ctx ) => {
ctx.body = 'hello world'
})
app.listen(3000)
koa-session
session 保存在服务器上
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: 'abc123',
database: 'koa_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)
koa-cookie
cookie 保存在客户端
const Koa = require('koa')
const app = new Koa()
app.use( async ( ctx ) => {
if ( ctx.url === '/index' ) {
// 写入cookie返回给浏览器
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)
koa-views
通常我们有些路径是获取数据的,但是有些路径是获取页面的,页面如果也写在 ctx.body 中代码会很难看,很难 维护,所以引入 koa-views
├── package.json
├── index.js
└── view
└── index.ejs
const Koa = require('koa')
const views = require('koa-views')
const path = require('path')
const app = new Koa()
// 加载模板引擎
// koa支持许多模版引擎
app.use(views(path.join(__dirname, './view'), {
extension: 'ejs'
}))
app.use( async ( ctx ) => {
let title = 'hello koa2'
// 将title渲染到index.ejs模版中
await ctx.render('index', {title})
})
app.listen(3000)
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<h1><%= title %></h1>
<p>EJS Welcome to <%= title %></p>
</body>
</html>
koa-mysql
待 mysql 结束后再来写
koa-mongodb
待 mogodb 结束后再来写
koa-wechat
这里的项目地址在争渡 github
config :项目的一些配置
controllers :路由
static :静态资源
views :视图
app.js :入口
controller_scan.js :手写路由匹配器
config
// 初始化微信信息
initWechatConfig = () => {
console.log("init wechat:appid,appsecret,tokening");
global.wechat_appid = "wx83157d09978bafed";
global.wechat_appsecret = "aed830780e19a5d6d427eee076558935";
global.wechat_token = "ferried";
global.wechat_access_token = null;
global.wechat_access_token_flush = new Date().getTime();
}
// 获取AccessToken
getAccessToken = (ctx) => {
var request = require('request-promise');
var options = {
url: `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${global.wechat_appid}&secret=${wechat_appsecret}`
}
return request(options).then(function (response) {
if (response) {
if (JSON.parse(response).access_token) {
global.wechat_access_token = JSON.parse(response).access_token;
global.wechat_access_token_flush = new Date().getTime();
}
}
}).catch(function (err) {
throw (err);
})
}
// 初始化AccessToken并每7200秒进行一次刷新
initWechatAccessToken = async (ctx, next) => {
if (!global.wechat_access_token) {
await getAccessToken(ctx);
} else {
var now = new Date().getTime();
var timeout = now - global.wechat_access_token_flush;
console.log("wechat_token: "+global.wechat_access_token,"timeout: "+timeout)
if (timeout >= 7200000) {
await getAccessToken(ctx);
}
}
await next();
}
// 导出
module.exports = {
setWechatConfig: initWechatConfig,
setWechatAccessToken: initWechatAccessToken
};
controller
menu
// 这个函数用来给微信发送请求添加菜单
var menu = async (ctx, next) => {
if (ctx.request.body.button) {
var request = require('request-promise');
var options = {
method: 'POST',
uri: `https://api.weixin.qq.com/cgi-bin/menu/create?access_token=${global.wechat_access_token}`,
body: JSON.stringify(ctx.request.body)
};
await request(options).then((response) => {
ctx.body = response;
}).catch((error) => {
ctx.body = error;
})
} else {
ctx.body = {
'error_code': '-1',
'error_msg': 'menu is null'
}
}
}
module.exports = {
'POST /wechat/addmenu': menu
}
auth 用户认证
// 获取auth页面
var wechat_oauth_page = async (ctx, next) => {
await ctx.render('wechat_oauth');
}
//通过nodejs的request发送请求给微信完成用户认证
var wechat_oauth_token = async (ctx, next) => {
if (ctx.request.body.code) {
var request = require('request-promise');
var code = ctx.request.body.code;
var uri = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${global.wechat_appid}&secret=${global.wechat_appsecret}&code=${code}&grant_type=authorization_code`
var options = {
method: 'GET',
uri: uri
};
await request(options).then((response) => {
var body = JSON.parse(response);
if(body.access_token){
ctx.session.user_access_token = body.access_token;
}
if(body.refresh_token){
ctx.session.user_refresh_token = body.refresh_token;
}
if(body.openid){
ctx.seesion.user_openid = body.openid;
}
if(body.scope){
ctx.session.user_scope = body.scope;
}
}).catch((error) => {
ctx.body = error;
})
} else {
ctx.body = "CODE不存在!";
}
}
// 传入openid 调用其他语言接口
function queryUserByOpenId(openid) {
}
module.exports = {
'GET /wechat/oauth_page': wechat_oauth_page,
'POST /wechat/oauth_token': wechat_oauth_token
}
register:认证链接的时候使用,对参数进行字典排序后比对
const sha = require('sha1');
var register = async (ctx, next) => {
let wechat_register = ctx.query;
// 字典排序
let temp_array = [global.wechat_token, wechat_register.nonce, wechat_register.timestamp].sort();
// sha1加密
let shastr = sha(temp_array.join(''));
// 加密后比对进行开发者注册验证
console.log(`wechat_register:sha:${shastr},signature:${wechat_register.signature}`)
if (shastr == wechat_register.signature) {
ctx.body = wechat_register.echostr;
}
}
module.exports = {
'GET /wechat/register': register,
};
app
// import koa
const Koa = require('koa');
// import koa middlewares
const router = require('koa-router')();
const session = require('koa-session');
const bodyParser = require('koa-bodyparser');
const static = require('koa-static');
const views = require('koa-views');
const logger = require('koa-logger');
// import controller_scan
const controller = require('./controller_scan')
// import wechat config
const wechatConfiger = require('./config/wechat_config');
const path = require('path');
const app = new Koa();
// add logger
app.use(logger());
// add post body
app.use(bodyParser());
// add session
app.keys = ['some secret hurr'];
app.use(session({
key: 'koa:sess',
maxAge: 86400000,
overwrite: true,
httpOnly: true,
signed: true,
rolling: true,
renew: false,
}, app));
// add wechat
wechatConfiger.setWechatConfig();
app.use(wechatConfiger.setWechatAccessToken);
// add static resource
app.use(static(path.join(__dirname,'/static')));
// add views resource
app.use(views(__dirname + '/views', {extension: 'html'}));
// scan controller
app.use(controller());
// 8.listening to port
app.listen(8080);
controller scan
const fs = require('fs')
function addMapping(router, mapping) {
for (var url in mapping) {
if (url.startsWith('GET ')) {
var path = url.substring(4);
router.get(path, mapping[url]);
console.log(`register URL mapping: GET ${path}`);
} else if (url.startsWith('POST ')) {
var path = url.substring(5);
router.post(path, mapping[url]);
console.log(`register URL mapping: POST ${path}`);
} else {
console.log(`invalid URL: ${url}`);
}
}
}
function addControllers(router) {
console.log(__dirname);
var files = fs.readdirSync(__dirname + '/controllers');
var js_files = files.filter((f) => {
return f.endsWith('.js');
});
for (var f of js_files) {
console.log(`process controller: ${f}...`);
let mapping = require(__dirname + '/controllers/' + f);
addMapping(router, mapping);
}
}
module.exports = function (dir) {
let
controllers_dir = dir || '/controllers',
router = require('koa-router')();
addControllers(router, controllers_dir);
return router.routes();
};
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于