Egg.js学习心得
Egg.js学习心得
一、Egg.js 基本概念
1.Egg.js 是什么
Egg.js 是基于 Node.js 和 Koa 的企业级 Web 框架,由阿里团队开源,专注于约定优于配置和可扩展性。
Egg.js 的特点:
- 基于Koa,支持 async/await,性能优异
- 强调规范与工程化,框架稳定,测试覆盖率高
- 适合中大型Web应用和API服务
- 渐进式开发
- 提供基于Egg定制上层框架的能力
- 高度可拓展的插件机制
2.Egg.js 的核心思想
Egg.js 采用 MVC 架构,将业务逻辑进行分层管理,提高代码可维护性。
主要分层包括:
- Controller(控制器)
- Service(业务逻辑)
- Model(数据模型)
- Router(路由)
- Middleware(中间件)
二、项目结构
1.安装&快速启动
mkdir egg-example && cd egg-example |
2.基本目录结构
Egg.js 项目初始化后,通常包含如下目录结构:
egg-project |
三、路由(Router)
1.路由的作用
路由用于定义URL与控制器方法之间的映射关系。
2.路由定义方式
在app/router.js 中定义路由:
import { Application } from 'egg'; |
源码router对象中有很多HTTP方法,从egg中解构出Application,这是一个接口,对传入的参数做出约束(启动文件在egg包中,使用过程基本不用动);从传入的app中再解构出controller和router,router就是路由对象
3.HTTP请求方式
Egg.js支持常见HTTP方法:
- GET
- POST
- PUT
- DELETE
其中GET与POST是最常用的请求方式,也是实际开发中最容易遇到参数获取、跨域、安全问题的部分。
(1)GET 请求
① GET 请求的特点
- 参数通过 URL 查询****字符串 传递
- 参数会直接暴露在地址栏
- 请求长度有限制
- 适合用于 查询数据
示例 URL:
http://localhost:7001/user?id=1&name=tom |
② Egg.js 中 GET 参数获取方式
在 Controller 中通过 ctx.query 或 ctx.request.query 获取:
async index() { |
③ GET 请求可能存在的问题
- 参数明文暴露,不适合传递敏感信息
- 容易被浏览器缓存
- 容易被恶意篡改参数
(2)POST 请求
① POST 请求的特点
- 参数放在 请求体(body)中
- 不会显示在 URL 中
- 参数长度限制较小
- 适合用于 新增、修改数据
② Egg.js 中 POST 参数获取方式
需要先确保已开启 bodyParser(Egg.js 默认开启):
async create() { |
前端发送 POST 请求示例(JSON):
fetch('/user', { |
③ POST请求可能存在的问题
- 请求体可能被篡改
- 容易受到CSRF****攻击
- 参数校验不严谨会导致安全漏洞
4.GET 与 POST 对比总结
| 对比项 | GET | POST |
|---|---|---|
| 参数位置 | URL | 请求体 |
| 安全性 | 较低 | 相对较高 |
| 是否缓存 | 会 | 不会 |
| 使用场景 | 查询 | 新增 / 修改 |
四、跨域问题(CORS)
1.什么是跨域
当协议、域名或****端口任意一个不同,浏览器就会认为是跨域请求。
示例:
前端:http://localhost:3000 |
2.Egg.js 解决跨域方式
使用egg-cors插件
安装插件:
npm install egg-cors |
启用插件(config/plugin.js):
exports.cors = { |
配置跨域规则(config/config.default.js):
exports.cors = { |
五、安全问题
1.CSRF攻击
(1)什么是CSRF
攻击者利用用户已登录状态,伪造请求对服务器进行操作,常发生在POST请求中。
(2)CSRF的特点
- 攻击一般发起在第三方网站,而不是被攻击的网站。被攻击的网站无法防止攻击发生。
- 攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作;而不是直接窃取数据。
- 整个过程攻击者并不能获取到受害者的登录凭证,仅仅是“冒用”。
- 跨站请求可以用各种方式:图片URL、超链接、CORS、Form提交等等。部分请求方式可以直接嵌入在第三方论坛、文章中,难以进行追踪。
(3)Egg.js的CSRF防护
Egg.js默认开启 CSRF 防护,POST请求需要携带 token。
// config/config.default.js |
2.XSS攻击
(1)什么是 XSS
攻击者通过注入恶意脚本,窃取用户信息或篡改页面。
(2)防护方式
- 对用户输入进行转义
- 不信任任何前端传来的数据
**3.**SQL注入
(1)产生原因
直接拼接 SQL 语句,导致恶意注入。
(2)防护方式
- 使用 ORM(如 Sequelize)
- 使用参数化查询
六、控制器(Controller)
1.Controller的作用
Controller用于接收请求参数、调用 Service、返回响应结果。
2.Controller基本写法
控制器文件一般放在app/controller目录下:
const { Controller } = require('egg'); |
3.ctx 对象
ctx是请求上下文对象,包含:
- ctx.request:请求信息
- ctx.response:响应信息
- ctx.body:响应内容
- ctx.query:GET 参数
- ctx.request.body:POST 参数
这里注意,egg基于Koa,Koa是ctx.xxx,在egg中ctx被封装在this里面,使用解构赋值解析出来,如果不使用解构就要使用this.ctx.xxx
4.获取传值
get类型
通过ctx.query获取参数,比如
export default class AdminController extends Controller { |
动态路由
从url中获取数据,但不是使用query的形式,而是使用http://127.0.0.1:7001/user/123这种形式
在配置路由时添加参数,例如router.get(‘/user/:id’, controller.admin.user);
然后在Controller中使用ctx.params获取参数,如
public async user() { |
七、Service(业务逻辑)
1.Service的作用
Service用于封装复杂业务逻辑,避免 Controller 代码臃肿。
好处:
- 保持 Controller 中的逻辑更加简洁。
- 保持业务逻辑的独立性,抽象出来的 Service 可以被多个 Controller 重复调用。
- 将逻辑和展现分离,更容易编写测试用例
2.Service示例
Service文件放在 app/service 目录下:
const Service = require('egg').Service; |
Controller 中调用 Service:
const user = await this.ctx.service.user.getUser(); |
每一次用户请求,框架都会实例化对应的Service 实例,由于它继承于egg.Service,故拥有下列属性方便我们进行开发:
- this.ctx:当前请求的上下文Context 对象的实例,通过它我们可以拿到框架封装好的处理当前请求的各种便捷属性和方法。
- this.app:当前应用Application****对象的实例,通过它我们可以拿到框架提供的全局对象和方法。
- this.service:应用定义的Service,通过它我们可以访问到其他业务层,等价于 this.ctx.service。
- this.config:应用运行时的配置项。
- this.logger:logger对象,上面有四个方法(debug、info、warn、error),分别代表打印四个不同级别的日志。使用方法和效果与context logger中介绍的一样,但是通过这个logger对象记录的日志,在日志前面会加上打印日志的文件路径,以便快速定位日志打印位置。
3.使用场景
- 复杂数据的处理,比如要展现的信息需要从数据库获取,还要经过一定的规则计算,才能返回用户显示。或者计算完成后,更新到数据库。
- 第三方服务的调用
4.注意事项
- Service文件必须放在**app/service目录**,可以支持多级目录,访问的时候可以通过目录名级联访问。
app/service/biz/user.js => ctx.service.biz.user |
- 一个Service文件只能包含一个类,这个类需要通过module.exports的方式返回。
- Service需要通过Class的方式定义,父类必须是egg.Service。
- Service不是****单例,是请求级别的对象。
- 框架在每次请求中首次访问ctx.service.xx时延迟实例化,所以Service中可以通过this.ctx获取到当前请求的上下文。
八、中间件(Middleware)
Egg的中间件形式和Koa的中间件形式是一样的,都是基于洋葱圈模型。
一个中间件是一个放置在app/middleware目录下的单独文件,它需要exports一个普通的function,接受两个参数:
- options:中间件的配置项,框架会将app.config[${middlewareName}] 传递进来。
- app:当前应用Application的实例。
1.中间件的作用
中间件用于在请求到达Controller前或响应返回前做统一处理,如:
- 日志
- 权限校验
- 错误处理
2.中间件示例
在app/middleware目录下创建中间件:
这个中间件的作用是每次请求时打印出时间
import { Context } from 'egg'; |
中间件写完之后还需要在config里进行配置,在middleware数组中添加文件名
config.middleware = ['printdate'] |
3.中间件传值
中间件传值在配置文件中bizConfig中增加中间件同名对象,就可以在中间件中通过options来访问变量
const bizConfig = { |
中间件也需要配置参数
import { Context, Application, EggAppConfig } from 'egg'; |
九、Cookie
Cookie是存储在访问者计算机中的变量,可以让同一个浏览器访问同一个域名的时候共享数据
在Egg.js中,Cookie的操作主要通过ctx.cookies完成。
1.设置Cookie
(1)基本设置方式
在Controller或Service中可以通过ctx.cookies.set设置Cookie:
this.ctx.cookies.set('username', 'tom'); |
默认情况下:
- Cookie 为 会话级别
- 浏览器关闭后失效
(2)设置 Cookie 选项
常用配置项如下:
this.ctx.cookies.set('token', 'abc123', { |
常见参数说明:
- maxAge:Cookie 存活时间
- httpOnly:是否禁止前端 JS 读取(推荐开启)
- signed:是否启用签名,防止篡改
- path:Cookie 生效路径
- secure:仅 HTTPS 传输(生产环境推荐)
2.获取 Cookie
(1)获取普通 Cookie
const username = this.ctx.cookies.get('username'); |
(2)获取加签 Cookie
如果 Cookie 设置时使用了 signed: true,获取时也必须指定:
const token = this.ctx.cookies.get('token', { |
3.Cookie 签名机制
(1)什么是签名 Cookie
签名 Cookie 会对值进行加密校验,防止客户端篡改。
(2)配置 keys
要使用签名 Cookie,必须在 config/config.default.js 中配置 keys:
exports.keys = 'your-secret-key'; |
如果 keys 未配置,使用 signed Cookie 会报错。
4.Cookie 的删除
删除 Cookie 本质是设置一个 过期时间:
this.ctx.cookies.set('username', null); |
或者显式设置 maxAge 为 0:
this.ctx.cookies.set('username', null, { |
5.Cookie 使用注意事项(安全)
- 敏感信息不要明文存 Cookie
- 登录态建议:
- Cookie + Session
- Cookie + JWT
- 推荐开启:
- httpOnly: true
- signed: true
- 生产环境建议开启:
- secure: true
6.Cookie 与 Session 的关系(简要)
- Cookie 存在于 客户端
- Session 存在于 服务端
- Egg.js 默认使用 Cookie 存 Session ID
7.Cookie 使用示例(登录场景)
// 登录成功后 |
十、Session
Cookie 在 Web 应用中经常承担标识请求方身份的功能,所以 Web 应用在 Cookie 的基础上封装了 Session 的概念,专门用做用户身份识别
Session保存在服务器上
Egg 中使用 Session
框架内置了 Session 插件,给我们提供了 ctx.session 来访问或者修改当前用户的 Session。
class HomeController extends Controller { |
Session 的使用方法非常直观,直接读取它或者修改它就可以了。
如果要删除它,直接将它赋值为 null:
ctx.session = null; |
可以直接在config中配置session信息
exports.session = { |
十、配置文件(Config)
1.配置文件位置
配置文件位于config目录:
- config.default.js:默认配置
- config.{env}.js:环境配置
2.配置示例
module.exports = { |
十一、日志(logger)
logger有四种级别:
this.logger.debug('this is degug'); |
十二、插件(Plugin)
1.插件的作用
插件用于 引入第三方功能模块,如数据库、Redis 等。
2.插件配置
在 config/plugin.js 中启用插件:
exports.mysql = { |
