Node.js Koa2 框架功能盘点 bysking【一】

摘要

该文档是基于Node.js Koa2 框架的内容,本文尽量少地关注业务,着重解析Node.js Koa2的核心架构体系,整理常用的库,和逻辑,帮助快速搭建项目工程

入口

项目基于nodemon来启动,nodemon能监视文件变化并自动重启应用程序,极大地提高了开发效率

实现一个最简单的服务

引入koa, 初始化,监听指定端口

js 复制代码
const Koa = require('koa');
const app = new Koa();

// 基于app拓展服务端逻辑

app.listen(5000, () => {
  console.log('Koa is listening in http://localhost:5000')
})

后续的逻辑都是基于app这个实例进行编码:

解析请求参数:koa-bodyparser

koa-bodyparser 是一个解析 HTTP 请求主体(body)的中间件,它允许你从 POST、PUT 等请求中解析 JSON、URL 编码的数据或 multipart/form-data

举例:ctx.request.body 获取解析后的请求体数据。

js 复制代码
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');

const app = new Koa();
app.use(bodyParser());

app.use(async ctx => {
  if (ctx.request.method === 'POST') {
    console.log(ctx.request.body); // bodyParser解析后的请求体数据
  }
});

app.listen(5000);

处理跨域:@koa/cors

限制来源为 http://example.com,暴露自定义头部能被浏览器js脚本获取(X-My-Custom-Header),只允许 GET 和 POST 请求,并允许携带认证信息

js 复制代码
const Koa = require('koa');
const cors = require('@koa/cors');

const app = new Koa();
app.use(cors({
  origin: function (ctx) {
    // 允许来自特定域名的请求
    return 'http://example.com';
  },
  exposeHeaders: ['X-My-Custom-Header'],
  allowMethods: ['GET', 'POST'],
  allowHeaders: ['Content-Type', 'Authorization'], // 允许携带的header头
  credentials: true, // 允许携带认证信息(如 cookies)
}));

app.listen(5000);

请求频率控制:koa-ratelimit

控制客户端(如浏览器、API 客户端)在特定时间窗口内可以发送的请求次数,以此来防止恶意用户或自动化工具对你的服务进行滥用,保护服务器资源,避免因请求过多导致的服务过载。下面这个配置对象告诉 koa-ratelimit 如何处理请求速率限制,包括限制的类型、时间窗口、错误消息、标识符、响应头、最大请求次数以及黑白名单逻辑

js 复制代码
const Koa = require('koa');
const ratelimit = require('koa-ratelimit');

const app = new Koa();

// 接口调用频率限制(Rate-Limiting)
// Rate limiter middleware for koa.
// https://github.com/koajs/ratelimit
const db = new Map();
app.use(
  ratelimit({
    driver: 'memory', // memory | redis
    db: db,
    duration: 60000, // 设置限制的时间窗口,这里是 60000 毫秒(即 60 秒)
    errorMessage: 'Sometimes You Just Have to Slow Down.',
    id: (ctx) => ctx.ip, // 用于识别请求的标识符
    headers: { // 定义了要在响应头中设置的速率限制相关的自定义字段
      remaining: 'Rate-Limit-Remaining',
      reset: 'Rate-Limit-Reset',
      total: 'Rate-Limit-Total',
    },
    max: 100,// 在时间窗口内允许的最大请求数量
    disableHeader: false, // 是否禁用响应头。如果设置为 `true`,则不会设置 `headers` 中定义的响应头
    whitelist: (ctx) => {
      // 白名单判断逻辑返回boolean
    },
    blacklist: (ctx) => {
     // 黑名单判断逻辑返回boolean
    },
  })
);

app.use(async ctx => {
  ctx.body = 'Hello, World!';
});

app.listen(3000);

设置静态资源目录: koa-static

提供静态文件服务,比如 HTML、CSS、JavaScript 文件、图片和其他资源,让客户端可以直接访问这些文件而无需服务器进行额外的处理

如果 public 目录结构如下

js 复制代码
public/
  └── index.html
  └── css/
      └── style.css
  └── images/
      └── logo.png

public 目录被视为静态文件服务器的根目录。当客户端请求以 /public 开头的 URL 时,koa-static 将尝试从 public 目录中找到对应的文件并返回。例如,如果客户端请求 /public/index.htmlkoa-static 将查找并返回 public/index.html 文件

js 复制代码
const Koa = require('koa');
const koaStatic = require('koa-static');
const path = require('path');

const app = new Koa();

// 指定静态文件目录,例如 'public'
const staticDir = path.join(__dirname, 'public');

// 使用 koa-static 中间件
app.use(koaStatic(staticDir));

app.listen(3000);

自定义路径解析:module-alias/register

当你在项目中使用 module-alias,通常会在项目根目录下的 package.json 文件中定义别名,例如:

js 复制代码
{
  "name": "my-app",
  "version": "1.0.0",
  "main": "index.js",
  "_moduleAliases": {
    "@src": "./src",
    "@lib": "./lib"
  }
}

然后,在项目中,你可以使用这些别名来导入模块,而不是写完整的路径,比如:

js 复制代码
import MyComponent from '@/src/components/MyComponent'; 
import util from '@lib/utils';

在运行你的应用之前,你需要调用 require('module-alias/register'),这会告诉 Node.js 使用 module-alias 中定义的别名解析模块。这样,当你在代码中使用 @src@lib 时,Node.js 会自动找到对应的实际路径。

总结一下,require('module-alias/register') 是为了启用 module-alias 功能,使得你可以使用在 package.json_moduleAliases 字段中定义的路径别名,简化模块导入的路径,提高代码可读性和可维护性。

模版引擎:koa-view

koa-views 是一个 Koa 框架的中间件,它允许你在 Koa 应用中集成视图模板引擎,以便动态渲染 HTML 页面, ejs参考,下面这个例子中,koa-view 中间件配置了 views 目录作为模板文件的根目录,并指定了 EJS 作为模板引擎。在处理请求时,ctx.render 方法用于渲染模板文件(例如 views/index.ejs),并将数据(title,message )传递给模板。最终,渲染后的 HTML 将作为响应体返回给客户端。

js 复制代码
const Koa = require('koa');
const views = require('koa-views');
const path = require('path');

const app = new Koa();

// 指定模板引擎和模板文件的根目录
app.use(views(path.join(__dirname, 'views'), {
  extension: 'ejs', // 指定模板引擎的扩展名,例如 EJS
}));

app.use(async ctx => {
  // 渲染模板并设置响应体, index.ejs这个模版文件中的title,message会被替换后返回
  ctx.body = await ctx.render('index', {
    title: 'My App',
    message: 'Hello, World!',
  });
});

app.listen(3000);

统一错误处理层

统一维护异常类,业务代码处理接口请求时候,可以抛出这些定义好的错误类的实例(统一的状态码,错误消息)

  • ./middlewares/exception

try catch整体捕获异常,分环境处理方式也不同

js 复制代码
const { HttpException } = require('@core/http-exception');

const catchError = async (ctx, next) => {
  try {
    // try catch整体捕获异常
    await next();
  } catch (error) {
  
    // 判断是否自定义的http异常
    const isHttpException = error instanceof HttpException;

    // 开发环境
    const isDev = global.config.environment === 'dev';
    if (isDev && !isHttpException) {
      throw error;
    }

    // 生产环境
    if (isHttpException) {
      ctx.body = {
        msg: error.msg,
        error_code: error.errorCode,
        request: `${ctx.method} ${ctx.path}`,
      };
      ctx.response.status = error.code;
    } else {
      ctx.body = {
        msg: '未知错误!',
        error_code: 9999,
        request: `${ctx.method} ${ctx.path}`,
      };
      ctx.response.status = 500;
    }
  }
};

module.exports = catchError;

错误处理模块中间件使用

js 复制代码
// 导入错误处理模块中间件
const catchError = require('./middlewares/exception');

// 注册中间件
app.use(catchError);

业务框架初始化

我们一般单独提供一个初始化的模块,统一对koa实例app进行初始化

js 复制代码
const InitManager = require('./core/init');

// ...省略其他代码
InitManager.initCore(app); // app 是koa的实例,const app = new Koa();

InitManager的内容如下:

js 复制代码
const Router = require('koa-router');
const requireDirectory = require('require-directory'); // 支持批量处理目录

class InitManager {
  static initCore(app) {
    // 入口方法
    InitManager.app = app; // 类上存一下app实例,下方会使用
    InitManager.initLoadRouters();
    InitManager.loadHttpException();
    InitManager.loadConfig();
  }

  // 加载app/api目录下的全部路由
  static initLoadRouters() {
    const apiDirectory = `${process.cwd()}/app/api`; // 绝对路径
    // 访问器
    function whenLoadModule(obj) {
      if (obj instanceof Router) {
        // 判断 requireDirectory 加载的模块是否为路由,是的话,就注册
        InitManager.app.use(obj.routes());
      }
    }

    //
    requireDirectory(module, apiDirectory, {
      visit: whenLoadModule,
    });
  }

  // 初始化配置数据,挂到全局config
  static loadConfig(path = '') {
    const configPath = path || process.cwd() + '/config/config.js';
    const config = require(configPath);
    global.config = config;
  }

  // 初始化异常类,挂到全局errs
  static loadHttpException() {
    const errors = require('./http-exception');
    global.errs = errors;
  }
}

module.exports = InitManager;
  • app/api目录

我们选一个看一看具体内容: 就是导出api路由

js 复制代码
const Router = require('koa-router')
const router = new Router()

router.get('/', async ctx => {
    await ctx.render('home')
})

module.exports = router
  • config/config.js的内容
js 复制代码
module.exports = {
  environment: 'dev',
  database: {
    dbName: 'bysking',
    host: 'localhost',
    port: 3306,
    user: 'root',
    password: '123456',
  },
  security: {
    secretKey: 'secretKey',
    // 过期时间 1小时
    expiresIn: 60 * 60,
  },
};
  • ./http-exception的内容
js 复制代码
class HttpException extends Error {
  constructor(msg = '服务器异常', errorCode = 10000, code = 400) {
    super()
    this.errorCode = errorCode
    this.code = code
    this.msg = msg
  }
}

class ParameterException extends HttpException {
  constructor(msg, errorCode) {
    super()
    this.code = 400
    this.msg = msg || '参数错误'
    this.errorCode = errorCode || 10000
  }
}

class AuthFailed extends HttpException {
  constructor(msg, errorCode) {
    super()
    this.code = 401
    this.msg = msg || '授权失败'
    this.errorCode = errorCode || 10004
  }
}

class NotFound extends HttpException {
  constructor(msg, errorCode) {
    super()
    this.code = 404
    this.msg = msg || '404找不到'
    this.errorCode = errorCode || 10005
  }
}

class Forbidden extends HttpException {
  constructor(msg, errorCode) {
    super()
    this.code = 403
    this.msg = msg || '禁止访问'
    this.errorCode = errorCode || 10006
  }
}

class Existing extends HttpException {
  constructor(msg, errorCode) {
    super()
    this.code = 412
    this.msg = msg || '已存在'
    this.errorCode = errorCode || 10006
  }
}

module.exports = {
  HttpException,
  ParameterException,
  AuthFailed,
  NotFound,
  Forbidden,
  Existing
}

更多内容正在整理中,敬请期待,包括:mysql集成,用户登录注册,加密解密,jsonwebtoken,单元测试 ...等

参考

开源项目nodejs-koa-blog

相关推荐
谢尔登3 小时前
【Node.js】worker_threads 多线程
node.js
osnet8 小时前
showdoc二次开发
node.js·vue
泯泷8 小时前
「生产必看」在企业环境中正确使用 Node.js 的九大原则
前端·后端·node.js
太阳火神的美丽人生9 小时前
Vant WeApp 开启 NPM 遇到的问题总结
前端·npm·node.js
fishmemory7sec1 天前
Koa2+mongodb项目实战1(项目搭建)
数据库·mongodb·koa
qingshun1 天前
Node 系列之预热知识(1)
node.js
余生H1 天前
前端的全栈混合之路Meteor篇:RPC方法注册及调用
前端·rpc·node.js·全栈
前端 贾公子1 天前
Node.js env 环境变量多种配置方式
node.js
sooRiverling1 天前
VUE 开发——Node.js学习(一)
vue.js·学习·node.js
_清豆°1 天前
NodeJS下载、安装及环境配置教程,内容详实
javascript·node.js