Node.js Koa2 框架功能盘点 bysking【一】
摘要
该文档是基于Node.js Koa2 框架的内容,本文尽量少地关注业务,着重解析Node.js Koa2的核心架构体系,整理常用的库,和逻辑,帮助快速搭建项目工程
入口
项目基于nodemon来启动,nodemon能监视文件变化并自动重启应用程序,极大地提高了开发效率
实现一个最简单的服务
引入koa, 初始化,监听指定端口
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
获取解析后的请求体数据。
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 请求,并允许携带认证信息
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
如何处理请求速率限制,包括限制的类型、时间窗口、错误消息、标识符、响应头、最大请求次数以及黑白名单逻辑
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
目录结构如下
public/
└── index.html
└── css/
└── style.css
└── images/
└── logo.png
public
目录被视为静态文件服务器的根目录。当客户端请求以 /public
开头的 URL 时,koa-static
将尝试从 public
目录中找到对应的文件并返回。例如,如果客户端请求 /public/index.html
,koa-static
将查找并返回 public/index.html
文件
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
文件中定义别名,例如:
{
"name": "my-app",
"version": "1.0.0",
"main": "index.js",
"_moduleAliases": {
"@src": "./src",
"@lib": "./lib"
}
}
然后,在项目中,你可以使用这些别名来导入模块,而不是写完整的路径,比如:
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 将作为响应体返回给客户端。
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整体捕获异常,分环境处理方式也不同
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;
错误处理模块中间件使用
// 导入错误处理模块中间件
const catchError = require('./middlewares/exception');
// 注册中间件
app.use(catchError);
业务框架初始化
我们一般单独提供一个初始化的模块,统一对koa实例app进行初始化
const InitManager = require('./core/init');
// ...省略其他代码
InitManager.initCore(app); // app 是koa的实例,const app = new Koa();
InitManager的内容如下:
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路由
const Router = require('koa-router')
const router = new Router()
router.get('/', async ctx => {
await ctx.render('home')
})
module.exports = router
- config/config.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的内容
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
转载自:https://juejin.cn/post/7366795773756325925