banner
NEWS LETTER

Node.js 【Koa】

Scroll down

Koa 是 Node.js 的框架之一,使用洋葱模型,是一个轻量级的服务端应用。

KOA 一些 API 的使用

设置响应头

ctx.set('Allow', 'GET,POST')

批量注册路由 routes

// 新建一个 app 目录,下建 routes 目录 下建index.js
// 路由批量处理脚本
const fs = require('fs')
// 批量化读取目录
module.exports = (app) => {
  // 同步读取目录
  fs.readdirSync(__dirname).forEach(file => {
    // 过滤掉目录下的index.js
    if (file === 'index.js') { return }
    // 绕过 index.js 就开始注册路由
    const route = require(`./${file}`)
    app.use(route.routes()).use(route.allowedMethods())
  })
}
----------- 分割线 ------------
// 在app目录下的 index.js中
const Koa = require('koa'// 引入 Koa 依赖
const body = require('koa-body')
const app = new Koa()  // 实例化 Koa
const routing = require('./routes'// 导入路由批量注册
routing(app) // 调用这个函数
app.use(body({
  multiparttrue,  // 启用文件
  formidable: {
    uploadDir: path.join(__dirname, '/public/uploads'), // 上传目录
    keepExtensionstrue  // 保证拓展名
  }
})) // 使用body中间件
// 开启端口服务
app.listen(3000() => console.log('程序启动成功在3000端口'))

控制器的处理封装 controllers

// 在 app目录下新建 controllers 目录
// 目录中的文件名对应的是路由中的文件名
// 控制器的本质是中间件 中间件的本质就是函数, 用类+类方法的方式
class HaomeCtl {
index(ctx) {
ctx.body = '这是首页'
}
}
module.exports = new HaomeCtl()
// 然后在 route 目录中的 home.js 中如下使用
const Router = require('koa-router')
const router = new Router()
// 使用控制器
const { index } = require('../controllers/home')
router.get('/', index)
module.exports = router

错误处理中间件 koa-json-error

npm install koa-json-error --save

// 引用
const error = require('koa-json-error')
app.use(
error({
// 定制返回格式 process.env.NODE_ENV 获取环境变量
postFormat: (e, { stack, ...rest }) =>
process.env.NODE_ENV === 'production' ? rest : { stack, ...rest },
})
)

windows 系统跨平台设置环境变量

npm install cross-env --save-dev 开发环境使用

// package.json 就可以这么设置
  "scripts": {
// 注意 NODE_ENV=production =号左右不能有间隙
    "start""cross-env NODE_ENV=production node app", // 生产
    "dev""nodemon app" // 开发环境
  },

koa-jwt 中间件

npm i koa-jwt --save

const jwt = require('koa-jwt')
// 使用 koa-jwt 认证 secret 是自己设置的密码
const auth = jwt({ secret })

上传文件 使用 koa-body

app.use(
body({
multipart: true, // 启用文件
formidable: {
uploadDir: path.join(__dirname, '/public/uploads'), // 上传目录
keepExtensions: true, // 保证拓展名
},
})
)
// 使用body中间件
// 控制器中
class HaomeCtl {
index(ctx) {
ctx.body = '这是首页'
}
upload(ctx) {
// 注意这里
const file = ctx.request.files.file
ctx.body = { path: file.path }
}
}

module.exports = new HaomeCtl()
// 在路由中使用
const Router = require('koa-router')
const router = new Router()
// 使用控制器
const { index, upload } = require('../controllers/home')

router.get('/', index).get('/upload', upload)

module.exports = router

使用 koa-static 中间件生成图片链接

npm i koa-static --save

const koaStatic = require('koa-static')
app.use(
koaStatic(
// 放最前面,静态文件 use 都写在前面
path.join(__dirname, 'public')
)
)
// ---->
const path = require('path')

class HaomeCtl {
index(ctx) {
ctx.body = '这是首页'
}
upload(ctx) {
const file = ctx.request.files.file
const basename = path.basename(file.path)
ctx.body = { url: `${ctx.origin}/uploads${basename}` }
}
}

module.exports = new HaomeCtl()

Koa 的使用

初用 koa

npm install koa --save

// 引入 koa 模块
const Koa = require('koa')

// 创建 koa 实例
const app = new Koa()

app.use(async (ctx) => {
ctx.body = '哈哈 你好 koa2 我来学习你了'
})

// 启动服务
app.listen(3001)

koa-router

.prefix(prefix) => Router // 加前缀
const usersRouter = new Router().prefix('/users'// 配置前缀

npm install koa-router --save

// 引入
const Router = require('koa-router')
// 也可以引入并实例化
const router = require('koa-router')()
===> 上面更精简
或者 const Router = require('koa-router')
const router = new Router()
// 启动路由
app.use(router.routes()) // 记得引入koa 并创建实例后使用这个中间件
app.use(router.allowedMethods) // 响应options方法,告知所支付的方法
// 而且会返回405 及 501状态码 请求的方法还没写及不支持该方法
// 使用
router.get('/',(ctx,next)=>{ // next 是下一个中间件
cty.body = 'hello koa'
}) // ...

栗:

// 引入 koa 模块
const Koa = require('koa')
// 引入 koa 路由模块
var router = require('koa-router')()

// 创建 koa 实例
const app = new Koa()
// 创建一个 router 实例
// const router = new Router()

// 使用路由
app.use(router.routes()).use(router.allowedMethods) // 帮你自动添加响应头

// 配置路由
router
.get('/', async (ctx) => {
ctx.body = '首页' // 返回数据 原生里面res.end/write
})
.get('/news', async (ctx) => {
ctx.body = '新闻页面'
})
.get('/newscontent', async (ctx) => {
ctx.body = '新闻详情'
})

// 启动服务
app.listen(3001)

koa 中间件

  • 应用级中间件,比如 app.use()
app.use(async (ctx, next) => {
// 中间件 通过 next () 实现
await next()
})
  • 路由级中间件:
  .get('/newscontent', async (ctx,next) => {
console.log('新闻详情')
await next()
})
.get('/newscontent',async(ctx, next)=>{
ctx.body = '新闻详情'
})
  • 错误处理中间件
// 注意要写在上面 执行顺序 上下文
app.use(async (ctx, next) => {
console.log('这是一个中间件')
next()
if (ctx.status == 404) {
ctx.status = 404
ctx.body = '这是一个404页面'
} else {
console.log(ctx.url)
}
})
  • 第三方中间件
比如静态服务中间件
post 中间件等等
  • 中间件配置公共信息
ctx.state = {
// 配置公共信息
}
----- 实例 -----
// 写一个中间件来配置公共的信息
app.use(async (ctx) => {
// 这样配置了 任何一个路由都可以使用这个数据
ctx.state.useinfo = '张三'
next() // 继续向下匹配路由
})

koa 中的 ejs 模板引擎

npm install koa-views --save && npm install ejs --save

// 引入
const views = require('koa-views')
// 使用
// 这样配置后缀是 html
app.use(views('views', { map: { html: 'ejs' } }))
// 这样配置后缀是 ejs
app.use(views('views', { extension: 'ejs' }))
// 通过 await ctx.render() 渲染 ejs 模板引擎
await ctx.render('index')

栗:

router
.get('/', async (ctx) => {

let title = '我是要传入 ejs 文件中的数据'
let arr = [111, 222, 333]
await ctx.render('index', { // 绑定数据 真实数据要从数据库拿
title,
arr
})
})
// ejs 文件中
<body>
<h2>这是一个 ejs 的模板引擎</h2>
<h3><%=title%></h3>
<ul>
<%for (var i = 0; i<arr.length; i++){%> // 循环 ejs 数据
<li><%=arr[i]%></li>
<%}%>
</ul>
</body>

// 引入某个 ejs 模块的内容
<% include public/header.ejs%> // 在index.ejs 引入 public/header.ejs
// 解析 html 标签
<%-content%>

koa 中 处理 post 的 koa-bodyparser 中间件

npm install koa-bodyparser --save

// 引入配置中间件
const bodyParser = require('koa-bodyparser')
// 使用中间件
app.use(bodyParser())
// 获取表单提交的 post 数据
router.post('/doAdd', async (ctx) => {
// 获取表单提交的数据
ctx.body = ctx.request.body // 获取
})

koa-static 静态资源中间件

npm install koa-static –save

// 使用
const static = require('koa-static')
// 使用 配置静态 web 服务的中间件
// 去static目录找 找到返回 找不到继续next
app.use(static(__dirname+'/public'))
// 静态资源中间件可以配置多个
------------ 分割线 ------------
// 配置中间件 获取url的地址
router.use(async (ctx) => {
// 模板引擎配置全局变量
ctx.state.__HOST__ = 'http://' + ctx.request.header.host
})
// 使用配置的HOST变量
<link rel="stylesheet" href="{{__HOST__}}/admin/css/font-aw.min.css" />

art-template 模板引擎

npm install art-template --save && npm install koa-art-template --save

// 引入
const render = require('koa-art-template')
// 配置 koa-art-template 模板引擎
render(app, {
root: path.join(__dirname, 'views'), // 视图的位置
extname: '.html', // 后缀名
debug: process.env.NODE_ENV !== 'production', // 是否开启调试
})
// 使用
await ctx.render('user')

栗:

{{include 'public/footer.html'}} // 引入公共文件
{{obj.name}} // 获取值
{{@obj.h}} // 获取标签元素
{{if num >20}} 大于 20 {{else}} 小于等于 20 {{/if}} // 条件判断
<ul>
// each 循环
{{each obj.arr}}
<li>{{$index}} ---- {{$value}}</li>
{{/each}}
</ul>
// NODE JS 文件中
router
.get('/', async (ctx) => {
let obj = { // 这些数据真实的应该从数据库拿 这里模拟
name: '张三',
h: '<h1>我是一个h1</h1>',
num: 20,
arr: [111,222,333]
}
await ctx.render('index', {
obj
})
  • 保存用户信息
  • 浏览器历史记录
  • 猜你喜欢的功能
  • 10 天免登陆
  • 多个页面之间的数据传递

cookie 参数:

maxAage // 设置过期时间
expires // cookie 过期的 Date 这里设置具体的时间
path // coolie 路径 默认是'/' 配置可以拿到cookie的路由
domain // cookie 域名
secure // 安全 cookie 默认 false 设置成 true 只能 https 访问
httpOnly // 是否只有服务器可以访问 cookie 默认是 true
overwrite // 默认 false 如果是 true 会覆盖过滤

koa 中设置 和获取 cookie 的值:

// 设置 cookie 的值
ctx.cookies.set(name.value,[options])
// 获取 cookie 的值
ctx.cookies.get('name')
// 中文问题
设置的时候先把先将它转成 ‘ base64 ’ 编码来存储  
new Buffer(value).toString('base64')
 
使用的时候再转换回来
new Buffer(value,'base64').toString()

koa 中的 session

session 的工作流程:

当浏览器访问服务器并发送第一个请求时,服务器会创建一个 session 对象,生成一个类似于 key,value 的键值对,然后将 key(cookie)返回到浏览器(客户端),浏览器下次再访问时,携带 key(cookie),找到对应的 session(value),客户的信息保存在 session 中。

koa-session 的使用:

npm install koa-session --sava

// 引入
const session = require('koa-session')
// 配置 session 中间件
app.keys = ['some secret hurr'] // 这个是配合signed属性的签名ke
// 配置信息
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 = '张三'
// 获取
console.log(ctx.session.userinfo)

栗:

🔔 注意:使用路由一定把启动路由放在下面 否则拿不到 session 的数据。

// 配置路由
router
.get('/', async (ctx) => {
console.log(ctx.session.userinfo) // 获取 session
ctx.body = '首页' + ctx.session.userinfo
})
.get('/news', async (ctx) => {
console.log(ctx.session.userinfo) // 获取 session
ctx.body = `登录成功`
})
.get('/login', async (ctx) => {
ctx.session.userinfo = '张三' // 设置 session
ctx.body = `登录成功`
})

// 接收 post 提交的数据
router.post('/doAdd', async (ctx) => {
// 获取表单提交的数据
ctx.body = ctx.request.body
})

// 使用路由 要放在下面位置 不然巨坑 拿不到session的数据
app.use(router.routes()).use(router.allowedMethods)

MongoDB 的封装及使用

ES6 的简单封装单例模式。

class Db {
// 静态方法实现单例
static getInstance() {
if (!Db.instance) {
Db.instance = new Db()
}
return Db.instance
}

constructor() {
console.log('实例化触发构造函数')
this.connect()
}

connect() {
console.log('链接数据库')
}

find() {
console.log('查询数据库')
}
}

let myDb = Db.getInstance()

安装以及简单使用:

npm install mongodb --save-dev

// 引入
const MongoClient = require('mongodb').MongoClient
// 定义地址
const dbUrl = 'mongodb://localhost:27017/'
const dbName = 'demo' // 要链接的数据库名字
// 链接数据库
console.time('start') // 测试事件
MongoClient.connect(dbUrl, { useNewUrlParser: true }, (err, client) => {
if (err) {
console.log(err)
return
}
// 链接上 db 数据库
let db = client.db(dbName)
// 增加数据
db.collection('user').insertOne(
{
username: '王武',
age: 32,
sex: '男',
status: '1',
},
(err, result) => {
if (!err) {
console.log('增加数据成功')
// 关闭数据库
client.close()
console.timeEnd('start') // 测试事件
}
}
)
})
// 查询数据
// 拿到所有该数据库下某个集合的所有数据
let result = db.collection('user').find({})
result.toArray((err, docs) => {
console.log(docs)
})

提高性能,单例封装:

// config.js
const app = {
// 数据库的 url
dbUrl: 'mongodb://localhost:27017',
// 链接的数据库名称
dbName: 'demo'
}

// 暴露配置信息出去
module.exports = app;
------------ 分割 ---------
// 引入
const MongoClient = require('mongodb').MongoClient;
const ObjectID = require('mongodb').ObjectID;
const Config = require('./config.js');

// 封装类库
class Db {

// 单例模式
static getInstance() {
if (!Db.instance) {
Db.instance = new Db();
}
return Db.instance;
}

constructor() {
this.dbClient = ''; // 属性 放db对象 为了只链接一次
// 初始化的时候链接数据库
this.connect()

}
// 链接数据库
connect() {
return new Promise((resolve, reject) => {
if (!this.dbClient) {
MongoClient.connect(Config.dbUrl, { useNewUrlParser: true }, (err, client) => {
if (err) {
reject('链接数据库失败');
} else {
let db = client.db(Config.dbName);
this.dbClient = db;
resolve(db);
}
})
} else {
resolve(this.dbClient)
}
})
}
// 查询数据
find(collectionName, json) {
return new Promise((resolve, reject) => {
this.connect().then((db) => {
let result = db.collection(collectionName).find(json);
result.toArray((err, docs) => {
if (err) {
reject('数据找不到对应的集合(表),请检查')
}
resolve(docs)
})

})
})
}
// 更新数据
update(collectionName, json1, json2) {
return new Promise((resolve, reject) => {
this.connect().then((db) => {
db.collection(collectionName).updateOne(json1, {
$set: json2
}, (err, result) => {
if (err) {
reject('更新数据失败');
} else {
resolve(result)
}
})
})
})
}
// 增加数据
insert(collectionName, json) {
return new Promise((resolve, reject) => {
this.connect().then((db) => {
db.collection(collectionName).insertOne(json, (err, result) => {
if (err) {
reject('添加数据失败')
} else {
resolve(result)
}
})
})
})
}
// 删除数据
remove(collectionName, json) {
return new Promise((resolve, reject) => {
this.connect().then((db) => {
db.collection(collectionName).removeOne(json, (err, result) => {
if (err) {
reject('删除数据失败')
} else {
resolve(result)
}
})
})
})
}
// 封装获取id
// mongodb 里面查询_id 吧字符串转换成对象
getObjectId(id){
return new ObjectID(id)
}
}
// let myDb = Db.getInstance()

// myDb.find('user', {}).then((data) => {
// console.log(data);
// })

module.exports = Db.getInstance();
// setTimeout(() => {
// console.time('start1');
// myDb.find('user', {}).then((data) => {
// // console.log(data);
// console.timeEnd('start1');

// })
// }, 1000);


// setTimeout(() => {
// console.time('start2');

// myDb.find('user', {}).then((data) => {
// // console.log(data);
// console.timeEnd('start2');

// })
// }, 3000);

// setTimeout(() => {
// console.time('start3');

// myDb.find('user', {}).then((data) => {
// // console.log(data);
// console.timeEnd('start3');

// })
// }, 5000);

简单的使用:

.get('/add', async (ctx) => {
let data = await DB.insert('user', {
username: '赵六',
age: 26,
sex: '女',
status: '1'
})
console.log(data.result);
})
.get('/edit', async (ctx) => {
let data = await DB.update('user',{
username: '张三的小弟'
},{
username: '我是祁连山,不是张三他小弟'
})
console.log(data.result);
})
.get('/delete', async (ctx) => {
let data = await DB.remove('user',{
username: '张三'
})
console.log(data.result);
})

小技巧:

// 重定向 执行完某些操作让跳转到哪里
ctx.redirect('/');
// 跳转的时候绑定唯一的 id,下面用的 art-template 模板引擎语法
<td><a href="/edit?id={{@$value._id}}">编辑</a>
// 查询 id
const ObjectID = require('mongodb').ObjectID;

验证码模块 svgCaptcha

npm install --save svg-captcha

// 使用
const svgCaptcha = require('svg-captcha');

const captcha = svgCaptcha.create(
{
size: 4,
fontSize: 50,
width: 120,
height: 34,
background: "#cc9966"
});
// captcha.text 是4位数的验证码
//console.log(captcha.text);

ctx.session.code = captcha.text;
// 设置它的响应头
ctx.response.type = 'image/svg+xml';
ctx.body = captcha.data;

// mate 五秒跳转
<meta http-equiv="refresh" content="5; url='http://www.qq.com/'">

利用 koa-jsonp 写 api 接口

npm install koa-jsonp --save

// 引入
const jsonp = require('koa-jsonp')
// 配置
app.use(jsonp())

我很可爱,请给我钱

其他文章