KoaJs框架--实践

12/15/2020 NodeJs

# 使用koaJs框架

  • 安装koa2项目生成器并创建项目: npm install koa-generator -g

  • Sequelize连接MySQL Sequelize具有强大的事务支持,关联关系,读取和复制等功能; 在项目的根目录下创建一个config目录,config目录中创建db.js,mysql连接。

# 创建schema、modules、controllers

  • schema--数据表模型实例 建立与数据表的对应关系,用代码建表。schema目录下的article.js用来创建数据表模型--创建数据表

  • module--实体模型 modules目录下创建article.js文件,为文章表,该文件为文章的实例,存放对数据表在操作的各种方法。

  • controllers--控制器 控制器的主要作用为功能的处理,controller目录下创建article.js,主要是对api的实现

  • 路由 路径,主要是作为请求的url,请求的路径来处理一些请求,返回数据

# 常用插件

  • Sequelize:连接MySQL

  • koa-bodyparser: 处理 post 请求体中的参数

  • log4js: log4j记录日志,必须放在第一个中间件,才能保证所以的请求及操作会先经过logger进行记录再到下一个中间件。

  • response.js:新建response.js中间件对返回前端的响应进行处理,用 ctx.body返回前端,有些东西重复写--进行封装,使得返回的格式一致,暴露出responseHandler和errorHandler

# koa-cors:解决跨域

//  koa-cors.js插件源码
/* 1、如果需要支持 cookies,  Access-Control-Allow-Origin 不能设置为 *,且 Access-Control-Allow-Credentials 需要为 true (注意前端请求需要设置 withCredentials = true)
 * 2、当method=OPTIONS时, 属于预检(复杂请求), 当为预检时可直接返回空响应体, 对应的http状态码为204
 * 3、通过 Access-Control-Max-Age 可以设置预检结果的缓存, 单位(秒)
 * 4、通过 Access-Control-Allow-Headers 设置需要支持的跨域请求头
 * 5、通过 Access-Control-Allow-Methods 设置需要支持的跨域请求方法
 */
// app.js 使用koa2-cors插件
const Koa = require('koa');
const bodyParser = require('koa-bodyparser'); //post数据处理
const router = require('koa-router')(); //路由模块
const cors = require('koa2-cors'); //跨域处理
const app = new Koa();
app.use( cors({
        origin: function(ctx) { //设置允许来自指定域名请求
            if (ctx.url === '/test') { return '*'; }  // 允许来自所有域名请求 
            return 'http://localhost:8080'; //只允许http://localhost:8080这个域名的请求
        },
        maxAge: 5, //指定本次预检请求的有效期,单位为秒。
        credentials: true, //是否允许发送Cookie
        allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], //所允许的HTTP请求方法
        allowHeaders: ['Content-Type', 'Authorization', 'Accept'], //服务器支持的head字段
        exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'] //获取其他自定义字段
    }) );
router.post('/', async function (ctx) { ctx.body = '请求成功了' });
app.listen(3000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# JWT

用于 jwt 权限认证,依赖于json-web-token;用来在身份提供者和服务提供者间传递被认证的用户身份信息

整体流程: 用户使用用户名密码来请求服务器 服务器进行验证用户的信息,通过验证发送给用户一个token 客户端存储token,并在每次请求时附送上这个token值 服务端验证token值,并返回数据

    • koa-jwt:对token比对校检鉴权
const Koa = require('koa');
const jwt = require('koa-jwt');
const app = new Koa();
app.use(async (ctx, next) => {     // 鉴权失败,错误处理
  return next().catch((err) => {
    if (401 == err.status) {ctx.body = { code: 50001, message: '用户鉴权失败' }; } 
    else { throw err; }
  });
});
app.use(jwt({ secret: JWT_SECRET }).unless({ path: [/^\/public/, /\/login/] }));
1
2
3
4
5
6
7
8
9
10
  • JWT_SECRET是一个加密因子,安全性,可设置成123456,可用密钥生成器生成密钥。

  • 后面的path路径是设置匹配不需要鉴权的路由或目录,比如所有的public开头的、登录xxxx/login的请求都不需要鉴权。

    • jsonwebtoken 库生成token。
// controller/UserController.js
const jwt = require('jsonwebtoken');
async login(ctx, next) {
  // 在登录成功后
  const token = jwt.sign({ uid: user._id }, JWT_SECRET, { expiresIn: '15d' });
  ctx.body = { code: 200, entry: { token: token}, };
}
1
2
3
4
5
6
7

{ uid: user._id } payload 数据载体,放些参数在 token 中,比如用户id。 JWT_SECRET 加密因子,要跟 koa-jwt 设置的保持一致。 expiresIn 设置 token 的过期时间。

# 具体实现

# Sequelize连接MySQL

在项目的根目录下创建一个config目录,config目录中创建db.js,该文件主要用来创建mysql的数据库链接的。

const Sequelize = require('sequelize');
const sequelize = new Sequelize('dbname','dbusername','password',{
    host:'localhost',
    dialect:'mysql',
    operatorsAliases:false,
    dialectOptions:{  //字符集 
        charset:'utf8mb4', collate:'utf8mb4_unicode_ci',
        supportBigNumbers: true, bigNumberStrings: true
    },
    pool:{ max: 5, min: 0, acquire: 30000, idle: 10000 },
    timezone: '+08:00'  //东八时区
});
module.exports = { sequelize };
1
2
3
4
5
6
7
8
9
10
11
12
13

创建schema、modules、controllers

# schema--数据表模型实例

建立与数据表的对应关系,用代码建表。

在schema目录下的article.js用来创建数据表模型的,也可以理解为创建一张数据表

module.exports = function(sequelize,DataTypes){
    return sequelize.define('article',{
        id:{ type: DataTypes.INTEGER, primaryKey: true,
            allowNull: true, autoIncrement: true },
        //文章标题
        title:{ type: DataTypes.STRING, allowNull: false, field: 'title' },
        //作者
        author:{ type: DataTypes.STRING, allowNull: false, field: 'author' },
        //内容
        content:{ type: DataTypes.STRING, allowNull: false, field:'content' },
        //文章分类
        category:{ type: DataTypes.STRING, allowNull: false, field: 'category' },
        // 创建时间
        createdAt:{ type: DataTypes.DATE },
        // 更新时间
        updatedAt:{ type: DataTypes.DATE }
    },{ freezeTableName: true });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

使用mysql工具建表方式

DROP TABLE IF EXISTS `article`;
CREATE TABLE `article` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL,
  `author` varchar(255) NOT NULL,
  `content` varchar(255) NOT NULL,
  `category` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
SET FOREIGN_KEY_CHECKS = 1;
1
2
3
4
5
6
7
8
9
10

# module--实体模型

在项目中modules目录下创建article.js文件,为文章表,该文件为文章的实例

// 引入mysql的配置文件
const db = require('../config/db');
const Sequelize = require("sequelize");
// 引入sequelize对象
const sequelize = db.sequelize;
// 引入数据表模型
const Article = require('../schema/article')(sequelize, Sequelize.DataTypes);
Article.sync({force: false}); //自动创建表

class ArticleModel {
    static async createArticle(data){
        return await Article.create({
            title: data.title, author: data.author,
            content: data.content, category: data.category
        });
    }
    static async getArticleDetail(id){
        return await Article.findOne({ where:{ id } });
    }
}
module.exports = ArticleModel;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# controllers--控制器

控制器的主要作用为功能的处理,项目中controller目录下创建article.js

const ArticleModel = require("../modules/article");

class articleController {
    static async create(ctx){
        let req = ctx.request.body;  //接收客服端
        if(req.title && req.author && req.content && req.category){
            try{ //创建文章模型
                const ret = await ArticleModel.createArticle(req);
                //使用刚刚创建的文章ID查询文章详情,且返回文章详情信息
                const data = await ArticleModel.getArticleDetail(ret.id);
                ctx.response.status = 200;
                ctx.body = { code: 200, msg: '创建文章成功', data }
            }catch(err){
                ctx.response.status = 412;
                ctx.body = { code: 412, msg: '创建文章失败', data: err }
            }
        }else {
            ctx.response.status = 416;
            ctx.body = { code: 200, msg: '参数不齐全' }
        }
    }
    static async detail(ctx){
        let id = ctx.params.id;
        if(id){
            try{   // 查询文章详情模型
                let data = await ArticleModel.getArticleDetail(id);
                ctx.response.status = 200;
                ctx.body = { code: 200,  msg: '查询成功', data }
            }catch(err){
                ctx.response.status = 412;
                ctx.body = { code: 412, msg: '查询失败', data }
            }
        }else {
            ctx.response.status = 416;
            ctx.body = { code: 416, msg: '文章ID必须传' }
        }
    }
}
module.exports = articleController;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 路由

路径,主要是作为请求的url,请求的路径来处理一些请求,返回数据

const Router = require('koa-router');
const ArtileController = require('../controllers/article');

const router = new Router({ prefix: '/api/v1' });
//创建文章
router.post('/article/create',ArtileController.create);
//获取文章详情
router.get('/article/:id',ArtileController.detail)

module.exports = router
1
2
3
4
5
6
7
8
9
10