Koa

本贴最后更新于 2036 天前,其中的信息可能已经沧海桑田

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)

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(); };
  • Koa
    3 引用 • 9 回帖
  • JavaScript

    JavaScript 一种动态类型、弱类型、基于原型的直译式脚本语言,内置支持类型。它的解释器被称为 JavaScript 引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在 HTML 网页上使用,用来给 HTML 网页增加动态功能。

    729 引用 • 1278 回帖 • 1 关注
  • Node.js

    Node.js 是一个基于 Chrome JavaScript 运行时建立的平台, 用于方便地搭建响应速度快、易于扩展的网络应用。Node.js 使用事件驱动, 非阻塞 I/O 模型而得以轻量和高效。

    139 引用 • 269 回帖 • 1 关注

相关帖子

5 回帖
Koa

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • ap via macOS

    node 后台快速开发框架了解下 https://cool-admin.com

    1 回复
  • 其他回帖
  • csfwff 1

    doge 图全挂了

  • someone27889 via macOS

    凑合理解吧,我把我 sf.gg 的直接转过来了。

  • someone27889 via macOS

    简单的扫了一眼,用户,角色,权限(五张表,三张资源表,两张关联表) 菜单(一张 资源表 一张 关联表...... 模式熟悉,这种用户模块 是最灵活的 给个 大大的 👍~
    页面看起来很舒服,顺眼,
    框架有机会搞一份,研究研究
    ps:为啥不搞成开源的嘞

  • 查看全部回帖