一.egg介绍
Egg.js简称Egg,它是《阿里旗下产品》基于Node.js 和 Koa的一个Nodejs的企业级应用开发框架,它可以帮助开发团队及开发人员降低开发和维护成本。
Egg.js 基于Es6、Es7以及Typescript、Koa2,使得Nodejs具有更规范的开发模式、更低的学习成本、更优雅的代码、更少的开发成本、更少的维护成本,它是为企业级框架而生!真正用过Node.js开发后端服务的朋友看到Egg.js,绝对都是一种惊喜的感觉,它可以把我们曾经踩过的坑和一直想整合到一起的东西给整合到一起。
MVC: service 【model 模型 :主要处理数据(查询数据库,请求接口数据)】 view【视图,模板 页面展示】 controller【 控制器,负责处理一些业务逻辑】
当用户访问我们这个页面的时候,比如用户访问admin,首先走到路由router,路由匹配控制器controller,控制器controller 需要数据 找 model 【service】 ,然后渲染到 模板中view;比如加载router后,我们需要对角色判断,这时我们需要在中间件middleware【middleware 功能是匹配路由之前,或是之后,处理一系列的操作】
extend 扩展【定义些 在模板view中使用的方法,比如日期】
二.插件
官网集成插件 比如:
- 模板渲染:egg-view-nunjucks
- 跨域请求:egg-cors
- Token生成:egg-jwt
- 访问数据库:egg-sequelize 和 mysql2
- MySQL 数据库封装:egg-mysql
- 验证码:svg-captcha
- 文件上传:await-stream-ready 和 stream-wormhole
await-stream-ready:顾名思义,能够使用await进行文件的读写操作。
stream-wormhole:在文件上传出现异常时能够把流消耗掉。
三.特点
约定大于配置【有自己全套的插件库,文件名字都约定好,比如固定routerjs,controller,service,不能随便写】
四.创建项目
安装脚手架egg-init: npm i egg-init -g
1.项目地址 cd eggframe
2. npm init egg --type=simple
3. npm i
4.启动项目: npm run dev
$ open: http://localhost:7001
五.项目结构
// 这是一个 egg 项目的目录结构
├──app
│ ├──controller
│ │ └──home.js
│ ├──service
│ │ └──user.js
│ ├──view
│ │ └──视图(模板),页面展示
│ ├──middleware
│ │ └──xtoken.js 中间件
│ ├──public
│ │ └──css、image、js等静态资源
│ └──router.js
│
├──config
│ ├── config.default.js //配置
│ └── plugin.js //插件
│
├──test
│
└──package.json
如上,由框架约定的目录:
app/router.js
用于配置 URL 路由规则,具体参见 Router。app/controller/**
用于解析用户的输入,处理后返回相应的结果,具体参见 Controller。app/service/**
用于编写业务逻辑层,可选,建议使用,具体参见 Service。app/middleware/**
用于编写中间件,可选,具体参见 Middleware。app/public/**
用于放置静态资源,可选,具体参见内置插件 egg-static。app/extend/**
用于框架的扩展,可选,具体参见框架扩展。config/config.{env}.js
用于编写配置文件,具体参见配置。config/plugin.js
用于配置需要加载的插件,具体参见插件。test/**
用于单元测试,具体参见单元测试。app.js
和agent.js
用于自定义启动时的初始化工作,可选,具体参见启动自定义。关于agent.js
的作用参见Agent机制。
由内置插件约定的目录:
app/public/**
用于放置静态资源,可选,具体参见内置插件 egg-static。app/schedule/**
用于定时任务,可选,具体参见定时任务。
若需自定义自己的目录规范,参见 Loader API
app/view/**
用于放置模板文件,可选,由模板插件约定,具体参见模板渲染。app/model/**
用于放置领域模型,可选,由领域类相关插件约定,如 egg-sequelize。
(1)路由router 常用:
router.redirect('/', '/news', 302); //重定向
router.get('/news', controller.news.list);
router.post("/admin/user/register", controller.admin.register.userRegister)
传参数:
路由:
1. 路由传参
a. router.js
router.get('/newsList/:id', controller.news.newslist);
b. controller/new.js
async newslist() {
const { ctx } = this;
// koa 获取动态路由传值 ctx.params
var params = ctx.params;
console.log("params:", params)
// http://127.0.0.1:7001/newsList/111 输出params: { id: '111' }
ctx.body = "新闻详情内容2"
}
2.get 传值
a. router.js
router.get('/newsDetails', controller.news.details);
b. controller/new.js
async details() {
const { ctx } = this;
// koa 获取get传值 ctx.query
var query = ctx.query;
console.log("query:", query)
//http://127.0.0.1:7001/newsDetails?ids=11 输出query: { ids: '11' }
ctx.body = "新闻详情内容1"
}
(2)模板渲染使用
1.配置
config/plugin.js 配置
然后config.default.js配置
2. router.js 路由
router.get('/news', controller.news.list);
3.Controller 控制器
/**
* 控制器,这边是模块
* 里面的方法是基于 async ,await的开发
* 这个this是指这个模块
*/
const Controller = require('egg').Controller;
class NewsController extends Controller {
async list() {
const { ctx, service } = this;
// 调用服务器里面的方法 注意异步:await
const list = await service.news.getNewsList();
//render 渲染模板
await ctx.render('news/index.html', list);
}
}
module.exports = NewsController;
4.拿取数据 service/news.js
'use strict';
const Service = require('egg').Service;
class NewsService extends Service {
async getNewsList() {
// 新闻的数据
const dataList = {
list: [
{ id: 1, title: '新闻1', url: '/news/1' },
{ id: 2, title: '新闻2', url: '/news/2' }
]
};
return dataList;
}
}
module.exports = NewsService;
5.创建静态资源
6.模板 view/new/index.html
<html>
<head>
<title>new 案例</title>
</head>
<link rel="stylesheet" href="/public/css/common.css" />
<body>
<h1>新闻 案例 </h1>
<ul class="news-view view">
{% for item in list %}
<li class="item">
<a href="{{ item.url }}">{{ item.title }}</a>
</li>
{% endfor %}
</ul>
<img src="/public/images/cat.jpg" />
</body>
</html>
(3).框架扩展 extend
application.js —— this指向:app对象 【对象指的是 Koa 的全局应用对象,全局只有一个,在应用启动时被创建】
调用:this.app
context.js —— this指向:ctx对象【指的是 Koa 的请求上下文,这是 请求级别 的对象】
调用:this.ctx
request.js —— this指向:ctx.request对象【对象和 Koa 的 Request 对象相同,是 请求级别 的对象】
调用:this.ctx.request
response.js —— this指向:ctx.response对象【对象和 Koa 的 Response 对象相同,是 请求级别 的对象】
调用:this.ctx.response
helper.js —— this指向:ctx.helper对象【提供一些实用的 utility 函数,就是工具类】
调用:this.ctx.helper
小案例 :在视图模板中调用extend方法: 时间转换 momentJS
安装moment :npm i --save moment
1.在extend/helper.js
const moment = require('moment');
moment.locale('zh-cn');
module.exports = {
//时间的转换
formatDate(param) {
// this 是 helper 对象,在其中可以调用其他 helper 方法
// this.ctx => context 对象
// this.app => application 对象
return moment(param).format('YYYY-MM-DD');
}
};
2.view/news/index.html
<a>{{helper.formatDate(item.dateTime)}}</a>
注意:也可以在controller,service 里调用
Egg 是基于 Koa 实现的,所以 Egg 的中间件形式和 Koa 的中间件形式是一样的,都是基于洋葱圈模型。
一个js文件就是一个中间件
config.default.js:中间件配置
config.middleware = ['forbidip'];
config.forbidip = {
forbidipList: [
"127.0.0.1"
]
}
中间件编写middleware/ forbidip.js【禁止一些ip地址访问】
module.exports = (option, app) => {
return async function forbidip(ctx, next) {
//获取客服端的ip
const clientIp = ctx.request.ip;
let hasIp = option.forbidipList.some(function (val) {
if (val == clientIp) {
return true;
}
})
if (hasIp) {
//屏蔽
ctx.status = 403;
ctx.body = '您的ip已经被屏蔽';
} else {
await next()
}
}
}
(5)安全机制(post请求)
Web 应用中存在很多安全风险,egg框架针对 Web 端常见的安全风险内置了丰富的解决方案。
比如post 请求,不能直接提交数据,需要认证csrf
view/new/index.html表单
<form action="/add?_csrf={{csrf}}" method="POST">
用户名: <input type="text" name="name" />
<br><br>
密 码:<input type="password" name="password" />
<br><br>
<button type="submit">提交</button>
</form>
const Controller = require('egg').Controller;
class NewsController extends Controller {
async list() {
const { ctx, service } = this;
// 调用服务器里面的方法 注意异步:await
const list = await service.news.getNewsList();
const catInfos = await service.cat.getCatInfos();
console.log('csrf:', this.ctx.csrf)
//render 渲染模板
await ctx.render('news/index.html', {
list: list,
catInfos: catInfos,
csrf: this.ctx.csrf // 获取 csrf 的值
});
}
// 表单post 请求
async add() {
console.log('表单提交的数据:', this.ctx.request.body)
this.ctx.body = this.ctx.request.body //获取表单提交的数据
}
}
module.exports = NewsController;
csrf会自动验证
但是这个很神奇,我测试的时候 不需要csrf 也可以,所以安全机制 csrf 有待讨论,是否我项目配置了
session 也是基于cookie的基础上
(7)数据库
egg-sequelize mysql2
六.编写接口
1.router.js
'use strict';
/*
Router 主要用来描述请求 URL 和具体承担执行动作的 Controller 的对应关系,
*/
/**
* @param {Egg.Application} app - egg application
*/
module.exports = app => {
const { router, controller } = app;
router.get('/home', controller.home.index);
};
2.controller 控制器/home
'use strict';
//控制器
const Controller = require('egg').Controller;
class HomeController extends Controller {
async index() {
const ctx = this.ctx;
const sfzjhm = ctx.query.sfzjhm;
//请求数据接口
const newsList = await ctx.service.home.list(sfzjhm);
ctx.body = newsList;
}
}
module.exports = HomeController;
3.配置接口地址:config/config.default.js
// 增加配置
const userConfig = {
// news 模块的配置
news: {
serverUrl: 'http://114.11****:85/',
}
};
4.service/home.js
egg.js中请求外部接口,this.ctx.curl()
const Service = require('egg').Service;
class NewsService extends Service {
async list(names) {
const { serverUrl } = this.config.news;
let data = {
name: names,
password: "123"
};
// 注意:异步请求,记得加上await
var resultData = await this.ctx.curl(`${serverUrl}sysUserInfo/login`, {
data: data,
method: 'POST',
dataType: 'json',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
}
});
return resultData.data;
}
}
module.exports = NewsService;
5.运行
npm run dev
open: