18 Dec 2017

koa2脚手架搭建

1. 初始化项目

创建项目安装依赖: mkdir koa2&&cd koa2&&npm init -y&& npm install koa --save

主页 touch app.js

const Koa = require('koa')
const app = new Koa()

app.use(ctx => {
  ctx.body = `您的网址路径为:${ctx.request.url}`
})

app.listen(3000)
console.log(`koa2 已启动 , 端口 : 3000`)

启动: node app.js –>访问 http://localhost:3000/hhhhh

2. es6

依赖 npm install babel-core babel-polyfill babel-register babel-preset-env --save-dev
配置 touch .babelrc

{
  "presets": ["env"]
}

touch server.js

require('babel-register');
require('babel-polyfill');
require('./app.js');

启动:node server.js –>访问 http://localhost:3000/hhhhh

3. nodemon自动重启node服务

依赖 npm install nodemon -g
启动:nodemon server.js –>访问 http://localhost:3000/hhhhh

修改package.json

"scripts": {
    "start": "nodemon server.js"
  }

启动:npm start

4. 忽略文件

touch .gitignore

node_modules/

5. 日志

npm install --save koa-logger koa-convert

const koaLogger = require('koa-logger')
const convert = require('koa-convert')
app.use(convert(koaLogger()))

6. 路由与Controller

项目结构 mkdir controller&&touch controller/user.js controller/hello.js router.js
依赖 npm install koa-router koa-bodyparser --save
改造app.js

const Koa = require('koa')
const Router = require('koa-router')
const bodyParser = require('koa-bodyparser')
const app = new Koa()
const koaLogger = require('koa-logger')
const convert = require('koa-convert')
const router = new Router()
app.use(convert(koaLogger()))

// app.use(ctx => {
//   ctx.body = `网址路径为:${ctx.request.url}`
// })

app.use(bodyParser())
router.get('/', ctx => {
  ctx.body = `这是主页`
})
router.get('/user', ctx => {
  ctx.body = `这是user页`
})

router.get('/post', ctx => {
  ctx.body = ctx.request.body
})

router.get('/async', async ctx => {
  const sleep = async (ms) => {
    return new Promise(resolve => {
      setTimeout(() => {
        resolve(true)
      }, ms)
    })
  }
  await sleep(1000)
  ctx.body = `这是异步处理页`
})

app.use(router.routes())
  .use(router.allowedMethods())

app.listen(3000)
console.log(`koa2 已启动 , 端口 : 3000`)
module.exports = app;

重启应用–>访问 /, /user,/async

6.1 分离路由文件

index.js

const Koa = require('koa')
const app = new Koa()
const router = require('./router')
const bodyParser = require('koa-bodyparser')
app.use(bodyParser())
app.use(router.routes())
  .use(router.allowedMethods())

app.listen(3000, (err) => { if (err) { console.error(err); } else { console.log(`koa2 已启动 , 端口 : 3000`); } });

router.js

const Router = require('koa-router')
const router = new Router()
const user = require('./controller/user')
const hello = require('./controller/hello');
router.post('/user/login', user.login)
router.get('/user/profile', user.profile)
router.use('/hello', hello.routes(), hello.allowedMethods());
module.exports = router

controller/user.js

const sleep = async (ms) => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(true)
    }, ms)
  })
}
module.exports = {
  login (ctx) {
    ctx.body = {
      username: ctx.request.body.username
    }
  },
  async profile (ctx) {
    await sleep(1000)
    ctx.body = {
      username: '相学长',
      sex: 'man',
      age: '999'
    }
  }
}

controller/hello.js

var router = require('koa-router')();
//const router = Router({ prefix: '/hello'})

router.get('/', async function(ctx, next) {
    ctx.state = {
        title: 'abc '
    };    
    ctx.response.body = `<h1>hello world!</h1>`;
})
router.get('/:name', async function(ctx, next) {
    let name = ctx.params.name;
    ctx.state = {
        title: 'abc '
    };
    console.log(ctx.render.path);
    await ctx.render('index', { hello: `abc ${name}` });
})
module.exports = router;

访问:/user/login /user/profile /hello /hello/page

6.2 动态注册路由文件

router.js

const Router = require('koa-router')
const router = new Router()
const user = require('./controller/user')
    // const hello = require('./controller/hello');
router.post('/user/login', user.login)
router.get('/user/profile', user.profile)
    // router.use('/hello', hello.routes(), hello.allowedMethods());
const fs = require('fs');
let addControllers = (router, dir) => {
    dir = dir || 'controller';
    fs.readdirSync(__dirname + '/' + dir).filter((f) => {
        return f.endsWith('.js');
    }).forEach((f) => {
        const model = require(__dirname + '/' + dir + '/' + f);
        if (model.constructor == Router) {
          // constructor 更加精确地指向对象所属的类,而对 instanceof 而言,即使是父类也会返回true
            const modelStr = f.replace(/.js/, '');
            router.use(`/${modelStr}`, model.routes(), model.allowedMethods());
        }
    })
}
addControllers(router);
module.exports = router

6.3 路由使用中间件

编辑中间件 mkdir middleware&&touch middleware/articles.js

module.exports = {
async  edit(ctx, next) {
  const locals = {
    title: '编辑',
    nav: 'article'
  }
  await ctx.render('articles/edit', locals)
},
async  checkLogin(ctx, next) {
  if(!ctx.state.isUserSignIn){
    ctx.status = 302
    ctx.redirect('/')
    return
  }
  await next()
}
}

在路由中使用中间件

const articles = require('./middleware/articles')
router.get('/:id/edit', articles.checkLogin, articles.edit)

7. 模板解析器

依赖 npm i -S ejs koa-views
app.js

const views = require('koa-views');
app.use(views(__dirname + '/views', {  extension: 'html',map: { html: 'ejs' }}));

mkdir views&&touch views/index.html

<h1><%= hello %></h1>

访问 /hello/美女

8. 静态资源

npm install koa-static --save-dev

const koaStatic = require('koa-static')
const path= require("path")
app.use(koaStatic(
  path.join( __dirname,  './static')
))

9. cookie、session

router.get('/cookie', async function(ctx, next) { 
 ctx.cookies.set("demo", "demoValue", {
      name: 'abc',
      age: '20',
      token: 'xyz'
    }) 
 console.log(ctx.cookies.get("demo"));
 ctx.response.body = `cookie`;
})

9.2. session

npm install koa-session-minimal koa-mysql-session --save-dev
app.js

const session = require('koa-session-minimal')
const MysqlSession = require('koa-mysql-session')
// 配置存储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
}))

hello.js

router.get('/session', async function(ctx, next) { 
     // 读取session信息
    ctx.session.count = ctx.session.count + 1
    ctx.response.body = ctx.session;
})
router.get('/session/set', async function(ctx, next) { 
 ctx.session = {
      user_id: Math.random().toString(36).substr(2),
      count: 0
    }
    ctx.response.body = ctx.session;
})

10. mysql

npm install --save mysql
mkdir db&&touch db/mysqlUtil.js db/mysqlUser.js
mysqlUtil.js

const mysql = require('mysql')
const pool = mysql.createPool({
    user: 'root',
    password: 'root',
    database: 'demo2',
    host: '127.0.0.1'
})
let query = function(sql, values) {
    return new Promise((resolve, reject) => {
        pool.getConnection(function(err, connection) {
            if (err) {
                reject(err)
            } else {
                connection.query(sql, values, (err, rows) => {
                    if (err) {
                        reject(err)
                    } else {
                        resolve(rows)
                    }
                    connection.release()
                })
            }
        })
    })
}
module.exports = { query }

mysqlUser.js

const { query } = require('./mysqlUtil')
async function getUsers( ) {
  let sql = 'SELECT * FROM user'
  return await query( sql )
}
async function getUser( userId) {
 let sql = 'select * from user where id = ?'
  return await query( sql,userId )
}
module.exports = { getUsers,getUser }

controller.js

const { getUsers,getUser } = require('../db/mysqlUser')
router.get('/users', async function(ctx, next) { 
  return ctx.response.body = await getUsers();
})
router.get('/user/:id', async function(ctx, next) { 
    var id = ctx.params.id;
  return ctx.response.body = await getUser(id);
})

11. mongoose

npm install --save mongoose
touch db/mongoUtil.js

var mongoose = require('mongoose');
mongoose.Promise = global.Promise;
mongoose.connect('mongodb://localhost:27017/demo2');
mongoose.connection.on('error', function(error) {
    console.log('数据库连接失败:' + error);
});
mongoose.connection.on('open', function() {
    console.log('——数据库连接成功!——');
});
exports.mongoose = mongoose;

touch db/mongoEmp.js

const mongodb = require('./mongoUtil');
const Schema = mongodb.mongoose.Schema;
//  schema 数据库模型骨架,不具备操作数据库能力
var dept = new Schema({
    id: String, //部门编号
    name: String //名称
});
var emp = new Schema({
    id: Number, //工号
    name: String, //姓名
    age: { type: Number, default: 0 }, //年龄
    // dep_id: String //部门
    dep: {
        type: Schema.Types.ObjectId,
        ref: 'dept'
    }
});

// 添加实例方法
emp.methods.printInfo = function() {
        let greeting = this.name;
        console.log("Testing methods defined in schema:" + greeting);
    }
    // 添加静态方法,在Model层就能使用
emp.statics.findbyId = function(id, callback) {
    return this.model('employee').find({ id: id }, callback);
}

// model 由Schema构造生成的模型,类似于管理数据库属性、行为的类。
var employee = mongodb.mongoose.model("employee", emp);
var department = mongodb.mongoose.model('department', dept);


//Entity —— 由Model创建的实体,能影响数据库操作
var empEntity = new employee({
    id: 1,
    name: '姓名'
});
console.log(empEntity.name, empEntity.id);

empEntity.save(function(err, doc) {
    if (err) {
        console.log("error :" + err);
    } else {
        console.log(doc);
    }
})
var empDAO = function() {};
empDAO.prototype.findAll = async function(callback) {

    console.log(await employee.find({}));
    return await employee.find({}, callback)
        // .populate({ path: 'dep', select: { id: 1 } }).exec();    
}
module.exports = new empDAO();

controller.js

const  empDAO = require('../db/mongoEmp')
router.get('/emp', async function(ctx, next) {
 let result = await empDAO.findAll();
    await ctx.render('list', {
        empList: result
    })
})

touch views/list.html 因模板语法冲突请把:{\% <%\} 的斜线去掉

<p>雇员列表</p>
<ul>
    <% empList.forEach(function(item){\%>
        <li>
            <%=item.id %>
                <%=item.name %>
        </li>
        <%\}) %>
</ul>

12. 开发中间件

12.1. generator 中间件

generator 中间件为koa1 直接使用的中间件 在koa2 使用:

const convert = require('koa-convert')
const loggerGenerator  = require('./middleware/logger-generator')
app.use(convert(loggerGenerator()))

12.2. async中间件开发

async 中间件开发

function log( ctx ) {
    console.log( ctx.method, ctx.header.host + ctx.url )
}
module.exports = function () {
  return async function ( ctx, next ) {
    log(ctx);
    await next()
  }
}

async 中间件只能在 koa v2中使用

const loggerAsync  = require('./middleware/logger-async')
app.use(loggerAsync())

Tags: