Egg.js:https://issummer.cn/2021/06/20/egg/

一、项目环境

egg.js对node版本是有要求的,node> 8.x npm >= 6.1.0

目前电脑环境:node:12.6.0 npm 6.1.0

egg提供脚手架生成指令

1
2
3
mkdir egg-example && cd egg-example
npm init egg --type=simple
npm i

启动项目会出现报错:Cannot find module ‘fs/promise’

主要是由于node版本较低造成的,使用n 升级node版本到16.13.0 ,重新npm install,启动项目,报错消失。

二、egg项目目录

Egg 作为一套解决方案,它内部高度集成了封装好的项目目录结构,现代开发俗称“约定式开发”。正常情况下,你从 0 开始搭建一个 Node 服务端代码,需要结合很多工具插件来辅助完成项目的搭建,而 Egg 则提前为你提供好了这些繁琐的初始工作,让你能专心与业务层面的开发。

当然,“约定式开发”也有不好的地方,很多配置项都是内部约定好的,在你想要用到某一个功能时,你可能需要去查阅 Egg 的官方文档是如何配置的,这就会消耗一点时间,但是相比之下,使用它的利大于弊。

1、app/router.js

用于配置 URL 路由规则,比如 get 请求,npm run dev 启动项目之后,直接在浏览器中访问启动的端口 + 路径,默认是 http://localhost:7001/,将会拿到 app/controller 文件夹下,home.js 脚本中 index 方法返回的内容。

这就是路由配置的作用,当然,抛出的形式有多种,如router.getrouter.postrouter.deleterouter.put 等,都是支持的,更加具体的内容 Router 配置

2、app/controller/xx

用于解析用户的输入,处理后返回相应的结果。通过请求路径将用户的请求基于 methodURL 分发到对应的 Controller 上,而 Controller 要做的事情就是响应用户的诉求。举个例子,我想拿到 A 用户的个人信息,于是我们要在控制器(Controller)里,通过请求携带的 A 用户的 id 参数,从数据库里获取指定用户的个人信息。

控制器需要做的就是处理数据和响应请求返回数据。更加详细的描述 Controller 文档

3、app/service/xx

简单来说,Service 就是在复杂业务场景下用于做业务逻辑封装的一个抽象层。初始化项目中未声明 service 文件夹,它是可选项,但是官方建议我们操作业务逻辑最好做一层封装。我们换一种理解方式,Service 层就是用于数据库的查询,我们尽量将粒度细化,这样以便多个 Controller 共同调用同一个 Service。更加详细的描述 Service 文档

4、app/middleware/xx

用于编写中间件,中间件的概念就是在路由配置里设置了中间件的路由,每次请求命中后,都要过一层中间件。在后续的开发中,也会利用到这个中间件的原理做用户鉴权。当用户未登录的情况下,是不能调用某些接口的。

每次都在 Controller 判断,当前请求是否携带有效的用户认证信息。接口一多,到处都是这样的判断,逻辑重复。所以,中间件在某种程度上,也算是优化代码结构的一种方式。更加详细的描述 Middleware 文档

5、app/public/xx

用于放置静态资源。会有一个上传静态资源的接口,包括图片、文本文档、excel、word等,都可以通过服务端读取文件之后,将其写入 app/public 文件夹中。在目前没有 OSS 服务的情况下,姑且先用这种方式存储静态资源,会消耗一点服务器的内存。

6、config/config.{env}.js

用于编写配置文件。 config/config.default.js 文件,这个是 Egg 框架约定好的,在内部设置一些全局的配置常量,在任何地方都可以通过 app.config 获取到 config.default.js 文件内的配置项。

7、config/plugin.js

用于配置需要加载的插件。比如 egg-mysqlegg-corsegg-jwt 等官方提供的插件

三、编写 GET 和 POST 接口

GET 请求参数获取

浏览器输入URL:http:*//localhost:7001/?id=12

打开 app/controller/home.js,通过如下形式获取到浏览器查询参数

1
2
3
4
5
6
7
8
9
10
11
const Controller = require('egg').Controller;

class HomeController extends Controller {
async index() {
const { ctx } = this;
const { id } = ctx.query;
ctx.body = id;
}
}

module.exports = HomeController;

还有另一种获取申明参数,比如我希望通过这样一个地址获取用户参数 /user/5,想获取用户 id5 的用户信息。
我们可以这样操作,首先添加路由,打开 app/router.js 添加一个路由:

1
2
3
4
5
6
7
8
9
10
'use strict';

/**
* @param {Egg.Application} app - egg application
*/
module.exports = app => {
const { router, controller } = app;
router.get('/', controller.home.index);
router.get('/user/:id', controller.home.user);
};

其次在 app/controller/home.js 下添加一个 user 方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
'use strict';

const Controller = require('egg').Controller;

class HomeController extends Controller {
async index() {
const { ctx } = this;
const { id } = ctx.query;
ctx.body = id;
}
// 获取用户信息
async user() {
const { ctx } = this;
const { id } = ctx.params; // 通过 params 获取申明参数
ctx.body = id;
}
}
module.exports = HomeController;

浏览器输入URL:localhost:7001/user/123

网页会打印出:123

post请求参数获取

POST 接口需要借助 Postman 工具进行请求,因为通过浏览器无法手动发起 POST 请求,只能通过浏览器地址栏发起 GET 请求。

1
2
3
// app/router.js
// ...
router.post('/add', controller.home.add);
1
2
3
4
5
6
7
8
9
10
// app/controller/home.js
// post 请求方法
async add() {
const { ctx } = this;
const { title } = ctx.request.body;
// Egg 框架内置了 bodyParser 中间件来对 POST 请求 body 解析成 object 挂载到 ctx.request.body 上
ctx.body = {
title
};
}

Postman发送post请求会出现报错,触发网络请求的安防策略

config/config.default.js 做好白名单配置,全部允许请求:

1
2
3
4
5
6
7
config.security = {
csrf: {
enable: false,
ignoreJSON: true
},
domainWhiteList: [ '*' ], // 配置白名单
};

配置完成后,再次发送请求,可以看到请求成功,成功拿到服务端参数。

从 Service 内获取数据

模拟一下在 Service 中获取数据库的数据,在 app 目录下新建 service,并且创建一个 home.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'use strict'

const Service = require('egg').Service

class HomeService extends Service {
async user(){
// 假设从数据库获取的用户信息
return {
name:'yu',
msg:'i love you egg.js'
}
}
}

module.exports = HomeService

Controller 内拿到上述方法

1
2
3
4
5
6
7
8
9
10
//  app/controller/home.js
// 获取用户信息
async user() {
const { ctx } = this;
const { name, msg } = await ctx.service.home.user();
ctx.body = {
name,
msg
}
}

Postman打开http://127.0.0.1:7001/user/yu 可以看到网页显示:{“name”:”yu”,”msg”:”i love you egg.js”}

四、Egg.js 中使用前端模板

开发一些简单的网页,想快速部署到云服务器上,就可以使用前端模板的开发形式。

1、首先安装插件 egg-view-ejs

1
npm install egg-view-ejs -save

2、然后在 config/plugin.js 里面声明需要用到的插件:

1
2
3
4
5
6
module.exports = {
ejs: {
enable: true,
package: 'egg-view-ejs'
}
};

3、去 config/config.default.js 里配置 ejs ,这一步我们会将 .ejs 的后缀改成 .html 的后缀。

1
2
3
config.view = {
mapping: {'.html': 'ejs'} //左边写成.html后缀,会自动渲染.html文件
};

上述的配置,指的是将 view 文件夹下的 .html 后缀的文件,识别为 .ejs

接着,在 app 目录下创建 view 文件夹,并且新建一个 index.html 文件,作为前端模板

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><%-title%></title>
</head>
<body>
<!-- 使用模板数据 -->
<h1><%-title%></h1>
</body>
</html>

Controller 内把变量注入到 index.html 文件,模板通过 <%-xx%>关键字获取到传入的变量。

4、修改 controller/home.js 下的 index 方法

1
2
3
4
5
6
7
async index() {
const { ctx } = this;
// ctx.render 默认会去 view 文件夹寻找 index.html,这是 Egg 约定好的。
await ctx.render('index.html', {
title: '于齐', // 将 title 传入 index.html
});
}

浏览器打开:localhost:7001 就会显示:于齐

上述开发模式便是前后端不分离的模式,在页面不复杂的情况下,整个项目不采用如 ReactVue 这些前端框架,也是可以的。在上述代码 ctx.render 之前,我们可以从数据库获取想要的信息,作为参数塞入模板中,模板拿到数据,构建 HTML。如果是提交事件,可以通过原生 ajax 或者是引入一把梭专家 jQuery,提交数据。