官方文档:https://koa.bootcss.com/
简介:由 Express 幕后的原班人马打造,没有捆绑任何中间件。KOA的大小完全由需求决定。
依赖:Koa 依赖 node v7.6.0 或 ES2015及更高版本和 async 方法支持.
简单用法:
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
ctx.body = 'Hello World';
});
//错误处理
app.on('error', (err, ctx) => {
log.error('server error', err, ctx)
});
app.listen(3000);
中间件
路由(koa-router)+解析参数(koa-bodyparse)
const bodyParser = require('koa-bodyparser')
const bodyParser = require('koa-bodyparser')
const router = new Router(); // 实例化路由
app.use(router.routes());
app.use(bodyParser())
/**post 用body */
router.post('/api/user/login', async (ctx, next) => {
let postData = await parsePostData(ctx);
const user = configDb.get('user').value();
if (user.name === postData.username && user.pwd === postData.password) {
sendResponse(ctx, { token: user.id })
console.log('登录成功');
} else {
sendResponse(ctx, undefined, -1, '用户名密码错误')
console.log('登录失败');
}
next();
})
/** get 用 query */
router.get('/api/cookies/get', async (ctx, next) => {
sendResponse(ctx, { items: cookies });
next();
})
// 解析 post 字符串为 json
function parsePostData(ctx) {
return new Promise((resolve, reject) => {
try {
let postdata = '';
ctx.req.addListener('data', (data) => {
postdata += data;
});
ctx.req.on("end", function() {
console.log('1111' + postdata);
resolve(JSON.parse(postdata));
})
} catch (error) {
reject(error);
}
});
}
//格式化JSON返回值
function sendResponse(ctx, data={}, code = 0, message = 'SUCCESS') {
const res = JSON.stringify({ data, code, message })
ctx.response.body = res;
}
处理静态资源(koa-static)
const KoaStatic = require('koa-static');
const staticUtil = require('./static');
app.use(KoaStatic('../dist'));//设置静态文件的路径
app.use(async (ctx, next) => {
console.log(ctx.url);
if (ctx.url.startsWith('/api')) {
next();
return;
}
// 获取静态资源内容,有可能是文件内容,目录,或404
let fullStaticPath = path.join(__dirname, '../dist')
console.log(fullStaticPath);
// 获取静态资源内容,有可能是文件内容,目录,或404
let _content = await staticUtil.content(ctx, fullStaticPath)
// 解析请求内容的类型
let _mime = staticUtil.parseMime(ctx.url)
// 如果有对应的文件类型,就配置上下文的类型
if (_mime) {
ctx.type = _mime
}
// 输出静态资源内容
if (_mime && _mime.indexOf('image/') >= 0) {
// 如果是图片,则用node原生res,输出二进制数据
ctx.res.writeHead(200)
ctx.res.write(_content, 'binary')
ctx.res.end()
} else {
// 其他则输出文本
ctx.body = _content
}
});
./static 文件
const path = require('path')
const fs = require('fs')
let mimes = {
'css': 'text/css',
'less': 'text/css',
'gif': 'image/gif',
'html': 'text/html',
'ico': 'image/x-icon',
'jpeg': 'image/jpeg',
'jpg': 'image/jpeg',
'js': 'text/javascript',
'json': 'application/json',
'pdf': 'application/pdf',
'png': 'image/png',
'svg': 'image/svg+xml',
'swf': 'application/x-shockwave-flash',
'tiff': 'image/tiff',
'txt': 'text/plain',
'wav': 'audio/x-wav',
'wma': 'audio/x-ms-wma',
'wmv': 'video/x-ms-wmv',
'xml': 'text/xml'
}
/**
* 遍历读取目录内容(子目录,文件名)
* @param {string} reqPath 请求资源的绝对路径
* @return {array} 目录内容列表
*/
function walk(reqPath) {
let files = fs.readdirSync(reqPath)
let dirList = []
let fileList = []
for (let i = 0, len = files.length; i < len; i++) {
let item = files[i]
let itemArr = item.split('.')
let itemMime = (itemArr.length > 1) ? itemArr[itemArr.length - 1] : 'undefined'
if (typeof mimes[itemMime] === 'undefined') {
dirList.push(files[i])
} else {
fileList.push(files[i])
}
}
let result = dirList.concat(fileList)
return result
}
/**
* 封装目录内容
* @param {string} url 当前请求的上下文中的url,即ctx.url
* @param {string} reqPath 请求静态资源的完整本地路径
* @return {string} 返回目录内容,封装成HTML
*/
function dir(url, reqPath) {
// 遍历读取当前目录下的文件、子目录
let contentList = walk(reqPath)
let html = `<ul>`
for (let [index, item] of contentList.entries()) {
html = `${html}<li data-index="${index}"><a href="${url === '/' ? '' : url}/${item}">${item}</a>`
}
html = `${html}</ul>`
return html
}
/**
* 读取文件方法
* @param {string} 文件本地的绝对路径
* @return {string|binary}
*/
function file(filePath) {
let content = fs.readFileSync(filePath, { encoding: 'utf-8' }, 'binary')
return content
}
/**
* 获取静态资源内容
* @param {object} ctx koa上下文
* @param {string} 静态资源目录在本地的绝对路径
* @return {string} 请求获取到的本地内容
*/
async function content(ctx, fullStaticPath) {
// 封装请求资源的完绝对径
let reqPath = path.join(fullStaticPath, ctx.url)
// 判断请求路径是否为存在目录或者文件
let exist = fs.existsSync(reqPath)
// 返回请求内容, 默认为空
let content = ''
if (!exist) {
// 如果请求路径不存在,返回404
content = '请求路径不存在!'
} else {
// 判断访问地址是文件夹还是文件
let stat = fs.statSync(reqPath)
if (stat.isDirectory()) {
// 如果为目录,则渲读取目录内容
content = dir(ctx.url, reqPath)
} else {
// 如果请求为文件,则读取文件内容
content = await file(reqPath)
}
}
return content
}
// 解析资源类型
function parseMime(url) {
let extName = path.extname(url)
extName = extName ? extName.slice(1) : 'unknown'
return mimes[extName]
}
module.exports = {
parseMime: parseMime,
content: content
}