标签归档:node

JS 学习之 Node (三)

Web 开发

koa

koa 入门

创建 hello-koa 工程,工程下创建 app.js 文件。

// 导入koa,和koa 1.x不同,在koa2中,我们导入的是一个class,因此用大写的Koa表示:
const Koa = require('koa');


// 创建一个Koa对象表示web app本身:
const app = new Koa();


// 对于任何请求,app将调用该异步函数处理请求:
app.use(async (ctx, next) => {
    await next();
    ctx.response.type = 'text/html';
    ctx.response.body = '<h1>Hello, koa2!</h1>';
});


// 在端口3000监听:
app.listen(3000);
console.log('app started at port 3000...'

安装 koa2

hello-koa 工程下创建 package.json

{
    "name": "hello-koa2",
    "version": "1.0.0",
    "description": "Hello Koa 2 example with async",
    "main": "app.js",
    "scripts": {
        "start": "node app.js"
    },
    "keywords": [
        "koa",
        "async"
    ],
    "author": "Michael",
    "licence": "Apache-2.0",
    "repository": {
        "type": "git",
        "url": "https://github.com/michaelliao/learn-javascript.git"
    },
    // 项目的依赖
    "dependencies": {
        "koa": "2.0.0"
    }
}
  1. scripts 代表项目中的脚本,这么使用 node start
  2. dependencies 是项目的依赖,npm install 的时候自动安装

koa middleware

koa把很多async函数组成一个处理链,每个 async 函数都可以做一些自己的事情,然后用await next()来调用下一个async函数。我们把每个 async 函数称为 middleware,这些middleware 可以组合起来,完成很多有用的功能。

app.use(async (ctx, next) => {
    console.log(`${ctx.request.method} ${ctx.request.url}`); // 打印URL
    await next(); // 调用下一个middleware
});


app.use(async (ctx, next) => {
    const start = new Date().getTime(); // 当前时间
    await next(); // 调用下一个middleware
    const ms = new Date().getTime() - start; // 耗费时间
    console.log(`Time: ${ms}ms`); // 打印耗费时间
});


app.use(async (ctx, next) => {
    await next();
    ctx.response.type = 'text/html';
    ctx.response.body = '<h1>Hello, koa2!</h1>';
})

middleware的顺序很重要,也就是调用app.use()的顺序决定了middleware 的顺序。

此外,如果一个 middleware 没有调用await next(),会怎么办?答案是后续的middleware将不再执行了。

处理 url

安装 koa-router

  1. 在 package.json 中的 dependencies 添加 “koa-router”: “7.0.0” 配置
  2. 执行 npm install

使用 koa-router

// ...
var router = require('koa-router')();
router.get('/hello/:name', async (ctx, next) => {
    var name = ctx.params.name;
    ctx.response.body = `<h1>Hello,${name}</h1>`;
})
app.use(router.routes());// 注意方法不是 routers
// ...

koa-router 是一个函数,所以引入的时候加了 () ,get 方法接收一个路由地址,地址中的参数可以通过 ctx.params.paramName 获取

处理 post 请求

Node.js 的 request 与 koa 的 request 对象,都不能处理请求的 body ,需要安装 koa-bodyparser

  1. 在 package.json 中的 dependencies 添加 “koa-bodyparser”: “7.0.0” 配置
  2. 执行 npm install

bodyparser 的初始化应该在注册路由之前

const bodyParser = require('koa-bodyparser');
app.use(bodyParser());
app.use(router.routes());

重构

创建 controllers 文件夹,一个 js 文件对应一个控制器。新建 controller.js 文件,处理路由。

// controller.js
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) {
    var files = fs.readdirSync(__dirname + '/controllers');
    var js_files = files.filter((f) => {
        return f.endsWith('.js');
    })
    js_files.forEach(function (value, index) {
        console.log(`process controller:${value}...`);
        let mapping = require(__dirname + '/controllers/' + value);
        addMapping(router, mapping);
    })
}
module.exports = function () {
    var router = require('koa-router')();
    addControllers(router);
    return router.routes();
}
// app.js
const controller = require('./controller');
app.use(controller());

Nunjucks

nunjucks 是一个模板引擎,安装:

  1. package.json 添加依赖:”nunjuncks”:”2.4.2″
  2. 执行 npm install

使用

const nunjucks = require('nunjucks');
// views 是相对于当前工作目录的路径,autoscape 表示是否开启模板转义
nunjucks.configure('views', {autoescape: true, noCache:false});
// 第一个参数是模板名称,第二个参数是绑定到模板中的变量
var str = nunjucks.render('index.html', {fruits: ['苹果', '葡萄', '栗子']});

语法

// 使用变量
{{ variable }}
// 使用循环
{% for var in Array %}
{{ var }}
{% endfor %}
// 使用块布局
base.html: 
  {% block blockName %} something... {% endblock %}
extend.html: 
  {% extends 'base.html' %}
  {% block blockName %} something... {% endblock %}

性能

Nunjucks 默认使用同步 IO 读取文件,速度比较慢。不过 Nunjucks 提供了模板缓存的的功能,也就是说,每个模板最多读取一次。在配置中,我们设置了 noCache:false ,将缓存关闭了,这样方便开发调试,在生成环境中,一定要打开。

MVC

工程目录

view-koa/
|
+- .vscode/
|  |
|  +- launch.json <-- VSCode 配置文件
|
+- controllers/ <-- Controller
|
+- views/ <-- html模板文件
|
+- static/ <-- 静态资源文件
|
+- controller.js <-- 扫描注册Controller
|
+- app.js <-- 使用koa的js
|
+- package.json <-- 项目描述文件
|
+- node_modules/ <-- npm安装的所有依赖包

处理

// 处理静态文件
app.use(staticFiles('/static/', __dirname + '/static'));
// 处理模板引擎
app.use(templating());
// 处理post请求
app.use(bodyParser());
// 处理路由
app.use(controller());

所用包

"koa": "2.0.0",
"koa-bodyparser": "3.2.0",
"koa-router": "7.0.0",
"nunjucks": "2.4.2",
"mime": "1.3.4",
"mz": "2.4.0"

处理静态资源

const path = require('path');
const mime = require('mime');
const fs = require('mz/fs');


// url: 类似 '/static/'
// dir: 类似 __dirname + '/static'
function staticFiles(url, dir) {
    return async (ctx, next) => {
        let rpath = ctx.request.path;
        // 判断是否以指定的url开头:
        if (rpath.startsWith(url)) {
            // 获取文件完整路径:
            let fp = path.join(dir, rpath.substring(url.length));
            // 判断文件是否存在:
            if (await fs.exists(fp)) {
                // 查找文件的mime:
                ctx.response.type = mime.lookup(rpath);
                // 读取文件内容并赋值给response.body:
                ctx.response.body = await fs.readFile(fp);
            } else {
                // 文件不存在:
                ctx.response.status = 404;
            }
        } else {
            // 不是指定前缀的URL,继续处理下一个middleware:
            await next();
        }
    };
}
module.exports = staticFiles;

集成Nunjucks

const nunjucks = require('nunjucks');
// 这个配置可以根据环境进行不同的配置
nunjucks.configure('views', {autoescape: true, noCache: true});


function templating() {
    return async (ctx, next) => {
        ctx.render = function (view, model) {
            ctx.response.body = nunjucks.render(view, Object.assign({}, ctx.state || {}, model || {}));
            ctx.response.type = 'text/html';
        }
        await next();
    }
}


module.exports = templating

mysql

使用 Sequelize

安装:

package.json 添加依赖

"sequelize": "3.24.1",
"mysql": "2.11.1"

连接数据库

const Sequelize = require('sequelize');
const config = require('./config');
var sequelize = new Sequelize(config.database, config.username, config.password, {
    host: config.host,
    dialect: 'mysql',
    pool: {
        max: 5,
        min: 0,
        idle: 30000
    }
})

创建 ORM 模型

var Pet = sequelize.define('pet', {
    id: {
        type: Sequelize.STRING(50),
        primaryKey: true
    },
    name: Sequelize.STRING(100),
    gender: Sequelize.BOOLEAN,
    birth: Sequelize.STRING(10),
    createdAt: Sequelize.BIGINT,
    updatedAt: Sequelize.BIGINT,
    version: Sequelize.BIGINT
}, {
    timestamps: false
});

CRUD

// 添加 create
var now = Date.now();
(async () => {
    var dog = await Pet.create({
        id: 'd-' + now,
        name: 'Odie',
        gender: false,
        birth: '2008-08-08',
        createdAt: now,
        updatedAt: now,
        version: 0
    });
    console.log('created: ' + JSON.stringify(dog));
})();
// 查找 findAll
(async () => {
    var pets = await Pet.findAll({
        where: {
            name: 'Gaffey'
        }
    });
    console.log(`find ${pets.length} pets:`);
    for (let p of pets) {
        console.log(JSON.stringify(p));
    }
})();
// 更新,save
(async () => {
    var p = await queryFromSomewhere();
    p.gender = true;
    p.updatedAt = Date.now();
    p.version ++;
    await p.save();
})();
// 删除,destroy
(async () => {
    var p = await queryFromSomewhere();
    await p.destroy();
})();

WebSocket

WebSocket 是 HTML5 新增的协议,它的目的是在浏览器和服务器之间建立一个不受限的双向通信的通道,比如说,服务器可以在任意时刻发送消息给浏览器。

使用 ws

要使用WebSocket,关键在于服务器端支持,这样,我们才有可能用支持WebSocket的浏览器使用WebSocket。

安装 ws 模块:npm install ws

创建 websocket 连接

// app.js
const WebSocket = require('ws');
const WebSocketServer = WebSocket.Server;
const wss = new WebSocketServer({
    port: 3000
});
wss.on('connection', function (ws) {
    console.log(`[Server] connection()`);
    
    ws.on('message', function (message) {
        console.log(`[Server] Received:${message}`);
        ws.send(`ECHO:${message}`, (err) => {
            if (err) {
                console.log(`[SERVER] error: ${err}`);
            }
        })
    });
});


// 客户端的写法
let ws = new WebSocket('ws://localhost:3000/test');
// 打开WebSocket连接后立刻发送一条消息:
ws.on('open', function () {
    console.log(`[CLIENT] open()`);
    ws.send('Hello!');
});
// 响应收到的消息:
ws.on('message', function (message) {
    console.log(`[CLIENT] Received: ${message}`);
}