Egg
是 Node.js
社区广泛使用的框架,简洁且扩展性强,按照固定约定进行开发,低协作成本。
node.js
是一个异步的世界
egg.js核心的是Middleware、Koa中间件选择的是洋葱模型;
在Egg.js
框架中,ctx
是一个非常核心且常用的对象,全称为 Context
,它代表了当前 HTTP
请求的上下文。ctx
对象封装了关于当前请求的所有相关信息,并提供了丰富的操作方法,使得开发者能够便捷地处理请求、响应、中间件逻辑、数据校验、模板渲染等与请求生命周期相关的任务。
ctx.request ctx.response
ctx.path: 当前请求的路径。
ctx.routerPath: 不包含查询参数的请求路径。
ctx.method: 请求的HTTP方法(GET、POST、PUT、DELETE等)。
ctx.app: 访问到Egg应用实例,可用于操作全局应用级别的资源或配置。
ctx.cookies: 操作cookies,如读取、设置、删除。
ctx.session: 用于会话管理,支持读取、设置、销毁会话数据。
ctx.logger: 提供日志记录功能,可以根据需要记录不同级别的日志信息(debug、info、warn、error等)。
ctx.onerror(err): 用于捕获并处理在当前请求生命周期内发生的未被捕获的错误。 服务与依赖注入:
ctx.service: 访问服务层(Service)的快捷方式,用于调用业务逻辑相关的函数。
ctx.helper: 访问框架提供的辅助工具或自定义的帮助函数。
ctx.locals: 用于在中间件或控制器之间共享数据,生命周期仅限于单个请求。
在Egg.js的应用中,ctx 通常作为控制器(Controller)方法的参数传递,使得开发者可以直接在控制器中操作和响应当前请求。例如:
module.exports = {
async list(ctx) {
const userId = ctx.query.userId;
const users = await ctx.service.user.list(userId);
ctx.body = { users };
},
};
在这个例子中,ctx
被用来获取请求查询参数(ctx.query.userId
),调用服务层方法(ctx.service.user.list()
),以及设置响应体(ctx.body
)。这种设计使得代码逻辑清晰、模块化,并且易于维护和测试。
总之,ctx 在 Egg.js 中是处理 HTTP 请求的核心上下文对象,它集中了所有与当前请求相关的信息和操作,极大地简化了Web应用的开发过程。
参考文章 https://www.zhihu.com/column/c_1613568973672493056
八大模块:Controller、路由、Service、MiddleWare、数据库、View层使用EJS模版渲染、cookie、session、
1 简单Controller
控制器Controller
,解析用户输入,处理后返回结果;
在代理服务器中,controller将用户请求转发到其他服务器上,并将服务结果返回给用户;
app下文件夹有controller文件夹和router.js文件
controller文件夹下有home.js文件
home.js文件
const Controller = require('egg').Controller;
class HomeController extends Controller {
async index() {
this.ctx.body = 'Hello World';
}
}
module.exports = HomeController;
router.js文件
module.export = (app) => {
// app指的是Egg.js的应用实例
const { router, controller } = app;
router.get('/', controller.home.index);
}
2 增加路由参数
2.1 query参数
'use strict';
const Controller = require('egg').Controller;
class GetController extends Controller {
async getInfo() {
const { ctx } = this;
const { name, age } = ctx.query;
ctx.body = {
name, age,
};
}
}
module.exports = GetController;
router.get('/getInfo'. controller.get.getInfo);
2.2 params参数
async getDetail() {
const { ctx } = this;
const { name, age } = ctx.params;
ctx.body = {
name, age,
};
}
router.get('/getDetail/:name/:age'. controller.get.getDetail);
2.3 post 请求
const Controller = require('egg').Controller;
class PostController extends Controller {
async postParmas() {
const { ctx } = this;
const { name, age } = ctx.request.body;
ctx.response.body = {
name, age,
};
}
}
module.exports = PostController;
router.post('/postParams'. controller.post.postParmas);
限制body体内容大小
app文件夹下的config文件夹下的config.default.js文件
const config = export = {
bodyParser: {
jsonLimit: '1mb',
formLimit: '1mb',
},
};
3 service
service在复杂业务中用来做业务封装的抽象层;
service 在app文件夹下;
app文件夹下建立一个service文件夹下;
service文件夹下新建一个new.js 文件;
const Service = require('egg').Service;
class NewService extends Service {
async getNewInfo(name, age) {
return {
name,
age,
id: 1,
arr: ['1', '2', '3'],
};
}
}
module.exports = NewService;
const Controller = require('egg').Controller;
class NewController extends Controller {
async newIndex() {
const { ctx } = this;
ctx.body = '这是一个新的controller';
}
async getNewInfo() {
const { ctx } = this;
const { name, age } = ctx.query;
const data = await ctx.service.new.getNewInfo(name, age);
ctx.body = data;
}
}
module.exports = NewController;
router.get('/newInfo'. controller.new.getNewInfo);
4 View层使用EJS模版渲染
app文件夹下的config文件夹下的plugin.js文件
module.export = {
ejs: {
enable: true,
package: 'egg-view-ejs',
},
}
app文件夹下的config文件夹下的config.default.js文件
config.view = {
mapping: {
'.html': 'ejs',
}
}
controller文件
'use strict';
const Controller = require('egg').Controller;
class EjsController extends Controller {
async index() {
const { ctx } = this;
const { name, age } = ctx.query;
const data = await ctx.service.new.getNewInfo(name, age);
await ctx.render('index.html', data);
}
}
module.export = EjsController;
router.get('/index'. controller.ejs.index);
4.1 引入html文件
app文件夹下新建一个view文件夹
view文件夹下新建index.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA_Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 引入其他html文件 -->
<% - include ("header.html") %>
<h1>这是ejs ---首页</h1>
<h1><%=id %></h1>
<h1><%=name %></h1>
<h1><%=age %></h1>
<ul>
<% for(var i=0;i<arr.length;i++){ %>
<li><%=arr[i] %></li>
<% } %>
</ul>
</body>
</html>
4.2 引入css文件
app文件夹下的public文件夹下新建common.css文件
<title>Document</title>
<link rel="stylesheet" href="public/common.css">
5 cookie的增删改查
通过路由,找到对应的cookie.html,渲染cookie.html
cookies.html的script中写入函数
function addCookies() {
fetch('/addCookies', {
method: 'post',
headers: {
'Content-type': 'application/json'
}
});
}
配置路由
// cookie路由
router.post('/addCookies', controller.cookies.addCookies);
router.post('/delCookies', controller.cookies.delCookies);
router.post('/updateCookies', controller.cookies.updateCookies);
router.post('/getCookies', controller.cookies.getCookies);
this.ctx.request.body可以获取request body;
this.ctx.body 可以设置response body;
async addCookies() {
const { ctx } = this;
ctx.cookies.set('user', 'zhangsan', {
maxAge: 1000 * 3, // 设置cookie时效性:3秒
});
ctx.cookies.set('user', '张三', {
maxAge: 1000 * 3, // 设置cookie时效性:3秒
encrypt: true, // cookie可以设置为中文
});
// Reponse Headers 内容 set-cookie
ctx.body = {
status: 200,
data: '添加成功',
};
}
async updateCookies() {
const { ctx } = this;
ctx.cookies.set('user', 'lisi');
ctx.body = {
status: 200,
data: '更新成功',
};
}
async getCookies() {
const { ctx } = this;
const user = ctx.cookies.get('user', {
encrypt: true,
});
ctx.body = {
user,
status: 200,
data: '查询成功',
};
}
async delCookies() {
const { ctx } = this;
ctx.cookies.set('user', null);
ctx.body = {
status: 200,
data: '删除成功',
};
}
这里要注意:
在 Egg.js 中,可以通过 app.get() 方法来定义处理 GET 请求的路由,也可以处理网页路径;
通常对于接口请求,可以在路由配置上加一个/api;
- /api/users 通常暗示这是一个API端点,用于数据交换;
- /users 更倾向于一个网页路径,可能用于直接展示用户列表页面给用户看。
API路径(如 /api/users)返回数据格式(如JSON),而页面路径(如 /users)通过渲染模板来返回HTML页面给浏览器。因此,即便调用了相同的控制器方法,该方法内部也应根据请求的上下文(ctx)来判断响应类型。
6 Session
新建一个Controller
async addSession() {
const { ctx } = this;
//添加session
ctx.session.username = 'zhangsan';
ctx.body = {
status: 200,
};
}
//访问session
async sessionIndex() {
const { ctx } = this;
// 获取Session
const username = ctx.session.username;
await ctx.render(
'session.html', {
id: 1,
name: '张三',
age: 18,
// 赋值给模板
username,
});
}
在view中新建session.html
<body>
<button onclick="addSession()">添加session</button>
<h3><%=username%></h3>
</body>
<script>
function addSession(){
fetch("/addSession",{
method:"post",
headers:{
"Content-type":"application/json"
}
})
}
</script>
路由配置
// post接口
router.post('/addSession', controller.session.addSession);
// 获取页面
router.get('/session', controller.session.sessionIndex);
session 配置是在config.default.js
文件中
config.session = {
key :"EGG_SESS", // 设置Key的默认值
httpOnly:true, // 设置服务端操作
maxAge:1000*60 , // 设置最大有效时间
renew: true, // 页面有访问动作自动刷新session
}
7 中间件
在app/middleware目录下的单独文件 error_handler.js
module.exports = () => {
return async function errorHandler(ctx, next) {
try {
await next();
if (ctx.status === 404 && !ctx.body) {
ctx.body = {
msg: `请求地址${ctx.request.url}不存在`,
status: 201,
data: null,
};
}
} catch (err) {
// 所有的异常都在 app 上触发一个 error 事件,框架会记录一条错误日志
ctx.app.emit('error', err, ctx);
const status = err.status || 500;
// 生产环境时 500 错误的详细错误内容不返回给客户端,因为可能包含敏感信息
const error = status === 500 && ctx.app.config.env === 'prod'
? 'Internal Server Error'
: err.message;
// 从 error 对象上读出各个属性,设置到响应中
ctx.body = { status, msg: error };
if (status === 422) {
ctx.body = { status, msg: err.errors };
}
ctx.status = status;
}
};
};
在应用中使用中间件,所有的页面都能够使用到该中间件;
config.default.js
文件中加入下面的配置就完成了中间件的开启和配置:
module.exports = {
// 配置需要的中间件,数组顺序即为中间件的加载顺序
middleware: ['error_handler'],
};
要想在某个路由中使用中间件,不必让所有的页面都能够使用到该中间件;
在handler的controller使用中间件;
module.exports = (app) => {
const error_handler = app.middleware.error_handler();
app.router.get('/testMiddeware', error_handler, app.controller.handler);
};
8 MySql数据库
8.1 安装 egg-mysql插件
npm i --save egg-mysql
8.2 开启插件
在config/plugin.js
中开启
module.exports = {
ejs: {
enable: true,
package: 'egg-view-ejs',
},
mysql: {
enable: true,
package: 'egg-mysql',
},
};
config.default.js
中 配置连接数据库
module.exports = appInfo => {
// 数据库连接
exports.mysql = {
// 单数据库信息配置
client: {
// host
host: '127.0.0.1',
// 端口号
port: '3306',
// 用户名
user: 'web',
// 密码
password: '123456',
// 数据库名
database: 'web',
},
// 是否加载到 app 上,默认开启
app: true,
// 是否加载到 agent 上,默认关闭
agent: false,
};
};
8.3 Egg.js 操作 MySql 数据库
- 新建一个 Controller
stu.js
const Controller = require('egg').Controller;
class StuController extends Controller {
// 添加学生信息
async addStu() {
const { ctx } = this;
const { name, age } = ctx.request.body;
const result = await ctx.service.stu.addStu(name, age);
if (result) {
ctx.body = {
status: 200,
msg: '添加成功',
data: {},
};
} else {
ctx.body = {
status: 201,
msg: '添加失败',
data: {},
};
}
}
}
- 新建一个
stu.js
service
this.app.mysql.insert(‘stu’, params);
const Service = require('egg').Service;
class StuService extends Service {
// 添加学生信息
async addStu(name, age) {
try {
const params = {
name,
age,
};
const result = await this.app.mysql.insert('stu', params);
const insertSuccess = result.affectedRows === 1;
if (insertSuccess) {
return result;
}
return null;
} catch (error) {
console.log(error);
}
}
}
module.exports = StuService;
- 添加路由
router.post('/addStu', controller.stu.addStu);
8.4 总结
- this.app.mysql.insert(‘stu’, params); // 增
- this.app.mysql.get(‘stu’, params); // 查
- this.app.mysql.update(‘stu’, params); // 改
- this.app.mysql.delete(‘stu’, { id }); // 删