水墨中国

使用 Koa + ES6 快速打造后端服务小记

  • 16-05-25
  • back-end
  • node
  • koa

最近为了尝试使用服务端直出基于react-router的单页应用,了解了一下使用 Koa搭建web服务器,在此总结分享给大家。

#项目启动 1、文件夹结构 合理的文件组织方式是一个好的项目开端,更是开发一个高可维护性web App的基本准则。以下是我的目录结构:

2、服务器跑起来 Koa 的helloworld程序这里不再复制,可从koa官网直接看到 demo,

3、使用 ES6 编写 Webpack 配置 要编写 React 应用,Webpack 神器少不了!大家都知道在控制台运行 webpack 命令会自动寻找并加载 webpack.config.js 文件。但是,直接使用 ES6 编写 webpack.config.js 会在运行 webpack 时报错,此时只需将文件名 webpack.config.js改为 webpack.config.babel.js 即可完美支持ES6语法。

4、ES6 全栈

  • 安装 babel-cli 使用 babel-node 命令替代 node 开启服务。
  • 安装 babel-preset-es2015babel-plugin-transform-runtimebabel-preset-react
  • 根目录下创建 .babelrc 文件
    {
      "presets": ["es2015", "react"],
      "plugins": ["transform-runtime"]
    }
    

#项目进行时 ##一、Koa 中间件 所谓“我们不创造代码,我们只是代码的搬用工”,站在巨人的肩膀上可以让我们快速搭建一个系统。在这里可以找到所有官方推荐的中间件

###我的推荐 日志工具: koa-logger Session: koa-session 请求数据解析: koa-body 路由: koa-router 静态文件服务: koa-static favicon: koa-favicon

##二、配置文件 项目基于模块和中间件的,无论是别人的中间件,还是自己编写的中间件,为了提高模块的可复用性,总是会为中间件提供一系列配置参数。在项目中为了方便统一管理中间件配置,我建议在configs文件夹中创建 main.js 存放所有的配置信息,单个复杂配置可单独保存文件在configs文件夹中,例如:

\\ main.js

import path from 'path'

const config = {
    name: "Nemo's blog",
    keys: ['nemo'],
    port: process.env.PORT || 8008,
    bodyparser: { strict: false },
    markdown: {
        root: path.resolve(__dirname, '../docs'),
        baseUrl: '/docs',
        layout: path.resolve(__dirname, '../public/blog.html'),
        cache: false
    },
    static: path.resolve(__dirname, '../public'),
    favicon: path.resolve(__dirname, '../public/favicon.ico')
}

export default config;

##三、数据库管理 1、mongoose 半壁江山 如果使用 mongoDB 作为数据库,那么我强烈建议使用 mongoose 开发,为什么呢?

  • 可以为文档创建一个模式结构
  • 可以对模型中的对象/文档进行验证
  • 应用程序数据可以通过类型强制转换转换成对象模型
  • 可以使用中间件来应用业务逻辑挂钩
  • 比 mongoDB 使用更容易

2、sequelize 一统江湖 如果使用mySQL 作为数据库,那么我强烈“安利” sequelize,sequelize 提供类似于 mongoose 的方式管理数据库,但是更强大百倍,其提供的 BelongsTo(1:1)、HasOne(1:1)、HasMany(1:m)、BelongsToMany(n:m)非常适合关系型数据库的映射。让你在进行数据库操作时编写的代码更加优雅方便,容错性更强。

##四、请求数据校验 其实在第三步中如果使用 sequelize 就自带有数据校验的功能,如果您刚好又不想使用它,那我想“总有一款适合你!”这里推荐 koa-validate 中间件,具有较强的功能和优雅的写法。

##五、路由 好的路由可以让使用者不读说明wiki就可以明白要干什么,并且怎么干。REST(表述性状态传递)的API正式我们所建议的。需要注意的是REST是设计风格而不是标准,其定义了一组体系架构原则,我们可以根据这些原则设计以系统资源为中心的Web服务。

###REST架构风格最重要的架构约束有以下6个:

  • 客户-服务器:通信只能有客户端单方面发起,表现为请求-响应形式
  • 无状态:通信的会话状态应该全部由客户端负责维护
  • 缓存:相应内容可以在通信链的某处被缓存,以改善网络效率
  • 统一接口:通信链的组件之间通过统一的接口相互通信,以提高交互的可见性
  • 分层系统:通过限制组建的行为(即每个组件只能“看到”预期交互的紧邻层),蒋家沟分解为若干等级的层
  • 按需代码:支持通过下载并执行一些代码(如:js)对客户端的功能进行拓展

这里也不敢托大,使用 koa-router 编写的路由规则,大胆使用 patchdelete等请求方法。 栗子:

import router from 'koa-router'
import * as project from '../controllers/project.controller'

const koarouter = router()
koarouter
    .prefix('/cgi-bin/')
    .get('/project', project.view)
    .post('/project'
        , superAdmin
        , project.add)
    .patch('/project/:pid'
        , authenticate
        , project.update)
    .delete('/project/:pid'
        , superAdmin
        , project.remove)
    
export default koarouter

##六、Koa错误处理 在 Koa 中,推荐统一使用 try / catch / throw 的方式来进行错误的触发和捕获。例如可以这样使用:

try {
    yield getDataAsync();
} catch(e) {
    // error handle
}

这里可以找到比较详细的介绍和原因。

#项目部署 ##一、压缩 推荐使用中间件 koa-compress

##二、缓存 对静态文件服务中间件进行配置 maxage 等参数,如 koa-static中间件:

static: {
    path: path.resolve(__dirname, '../public/dist'),
    option: { 
        maxage: 30 * 24 * 60 * 60 * 1000, 
        gzip: true 
    }
},

##三、安全 1、为防止 CSRF 攻击,我们可以安装中间件 koa-csrf,通过它来为每一个表单创建一个 CSRF token 进行安全校验 2、为防止 XSS 攻击,我们可以在页面响应头部添加 Content-Security-Policy 来过滤不符合配置策略的内容 3、由于一般情况下我们会将静态资源(js / css)部署到 CDN 上,因而 CDN 和 html 页面的主域名一般情况下是不同的。因此添加Access-Control-Allow-Origin 头部是非常有必要的,其中的好处之一就是避免跨域脚本报错仅能捕捉到 script error。这里我推荐使用koa-cors中间件配置头部。

更多安全策略可以参见这篇文章

到此为止所用到的中间件都已介绍完毕,这里给出我的 app.js 入口文件(有不同意见请大胆提出,轻拍即可)

##四、稳定 1、使用 babel 全站转码 尽管可以可以将项目直接部署到线上,但是为了让 PM2 更好的支持,我建议还是使用 babel 整站编译到 dist 文件夹下,直接部署 dist 文件夹到服务器。

2、PM2部署 PM2 是一个带有负载均衡功能的Node应用的进程管理器。它可以让你的web服务利用全部的服务器上的所有CPU,并保证进程永远都活着,0秒的重载。 使用方式一: 安装 PM2 到全局:使用 PM2 启动应用,比如:pm2 start app.js -i 0

使用方式二: 安装到局部(推荐),使用配置文件启动: sudo npm install pm2 --save

//pm2-start.js

var pm2 = require('pm2');

var instances = process.env.WEB_CONCURRENCY || -1; // Set by Heroku or -1 to scale to max cpu core -1
var maxMemory = process.env.WEB_MEMORY || 512;    // " " "

pm2.connect(function() {
    pm2.start({
        script    : './dist/app.js',
        name      : 'your-web-app',
        exec_mode : 'cluster',
        instances : instances,
        max_memory_restart : maxMemory + 'M',   // Auto restart if process taking more than XXmo
        log_file : './log/combined.outerr.log',
        error_file: './log/err.log',
        out_file: './log/out.log',
        merge_logs: true,
        env: {
            "NODE_ENV": "development",
            "AWESOME_SERVICE_API_TOKEN": "xxx"
        },
        env_testing: {
            "NODE_ENV": "testing",
        },
        env_production: {
            "NODE_ENV": "production",
        }
    }, function(err) {
        if (err) 
            return console.error('Error while launching applications', err.stack || err);
        
        console.log('PM2 and application has been succesfully started');

        // Display logs in standard output 
        pm2.launchBus(function(err, bus) {
            console.log('[PM2] Log streaming started');
            bus.on('log:out', function(packet) {
                console.log('[App:%s] %s', packet.process.name, packet.data);
            });
            bus.on('log:err', function(packet) {
                console.error('[App:%s][Err] %s', packet.process.name, packet.data);
            });
        });
    });
});

控制台运行: node pm2-start.js 启动pm2

3、运行日志 你可能已经注意到在上方的 pm2 配置文件中已经有这样的配置:

log_file : './log/combined.outerr.log',
error_file: './log/err.log',
out_file: './log/out.log',
merge_logs: true

它表示在根目录下 log 文件夹生成一个合并日志、一个请求流水日志以及一个错误日志

##五、Nginx 部署 Nginx 想必大家并不陌生,这里也正是看中了 Nginx 出色的 HTTP反向代理能力,才把它放置在 Node.js 前端,用来处理我们的各种需求。尤其是当项目后期访问量加大是,一个进程、一台服务器已经不能满足我们的需求,这是 Nginx 就可以发挥自己反向代理的能力——在Nginx 后端添加多个服务器或启动多个进程来分担访问压力。 当然,还有一些不得不说的优点 1、静态文件性能 Node.js 静态文件受制于他的单线程异步I/O,使用 Nginx 处理静态文件的性能基本上是纯 Node.js的2倍以上。 2、反向代理规则 有事我们希望根据 session、IP等一些特殊规则和需求,使用Nginx 配置文件就可以简单实现。 3、扩展性 最典型的就是加入 Lua 语言的拓展。胶水语言Lua赋予了Nginx复杂逻辑判断能力,并保持一贯的高效性。 4、稳定性和转发性能 Nginx 在同样的负载下,相比 Node.js 占用的CPU和内存资源更少。 5、安全性 Nginx 经过一些配置可以有效地抵挡类似 slowloris等的 Dos 攻击。而 Node 的这方面还不够。 6、运维管理 Nginx 的反向代理配置非常方便,轻松的修改一些配置就可以在一台服务器上让多个站点同时占用 80 端口。

comments powered by Disqus