egg框架概述
之前自学egg的时候记录的笔记,看的是晓舟报告系列视频
egg项目初始化
npm init egg --type=simple
项目目录需要手动创建,创建项目时间较长,需要耐心等待
比如创建一个空的文件egg_project,再输入上面的命令
执行cnpm install安装依赖
执行npm run dev启动
会提示通过http://127.0.0.1:7001访问首页
路由与控制器
mvc概述:
mvc是一种软件设计规范,主要作用是逻辑拆分
可以将要实现的系统拆分成三层
模型model 处理数据
视图view 给用户显示数据
控制器controller 处理用户输入
egg中的控制器 controller:
1 直接相应数据或渲染模板
2 接受用户的输入
3 与路由建立对应关系
this.ctx可以获取到当前请求的上下文对象,通过此对象可以便捷获取到请求与响应的属性与方法
在controller目录下新建文件,这个文件就是控制器了
比如在controler目录下建立fruits.js文件,这就是fruits控制器
他自带的home.js里代码的意义如下:
'use strict'; //严格模式
//并不是egg专有的,是js的语法
//controller类,egg提供的
//拿到controller类
const Controller = require('egg').Controller;
//定义我们自己的controller来继承egg提供的类
class HomeController extends Controller {
async index() {
const { ctx } = this;
ctx.body = `
<h1>hello kayo</h1>
`;
}
}
//暴露接口
module.exports = HomeController;
我们按照这个思路,写我们自己的fruits的文件:
fruits.js
const Controller = require("egg").Controller;
class FruitsController extends Controller{
}
module.exports = FruitsController;
先写出框架,再添加内容
const Controller = require("egg").Controller;
class FruitsController extends Controller{
async index(){
this.ctx.body = "我是一个水果列表页面" //上下文对象
}
}
module.exports = FruitsController;
先简单设置一下,如果想显示这个页面,就要给他设置路由
通过在router.js里设置路由,就可以在访问https://127.0.0.1:7001/fruits的时候显示刚刚写的内容了
'use strict';
/**
* @param {Egg.Application} app - egg application
*/
module.exports = app => {
const { router, controller } = app;
router.get('/', controller.home.index);
router.get('/fruits',controller.fruit.index);
};
路由
通过路由传递参数
1 获取query参数
2 获取params参数
GET请求:
第一种语法
在fruit.js里先修改相应的部分
const Controller = require("egg").Controller;
class FruitsController extends Controller{
async index(){
// this.ctx.request 可以拿到请求的对象
// this.ctx.request.query 可以拿到get请求中url的参数
//比如说想拿到一个索引的index
let query = this.ctx.request.query;
this.ctx.body = "我是一个水果列表页面" //上下文对象
}
}
module.exports = FruitsController;
在浏览器里输入连接,http://127.0.0.1:7001/fruits?index=100,先传递一个参数index=100
let query = this.ctx.request.query;
console.log(query);
刷新页面,看到服务器可以输出,说明参数传递成功了
另一个GET的语法:
在router.js里添加一条新的路由
'use strict'; //严格模式
/**
* @param {Egg.Application} app - egg application
*/
//暴露了一个函数,通过解构赋值,拿到每一个路径
module.exports = app => {
const { router, controller } = app;
router.get('/', controller.home.index);
router.get('/fruits',controller.fruit.index);
router.get('/fruits/:id',controller.fruit.getId);
};
router.get('/fruits/:id',controller.fruit.getId);
在fruit.js里写对应的函数getId
const Controller = require("egg").Controller;
class FruitsController extends Controller{
async index(){
// this.ctx.request 可以拿到请求的对象
// this.ctx.request.query 可以拿到get请求中url的参数
//比如说想拿到一个索引的index
let query = this.ctx.request.query;
console.log(query);
this.ctx.body = "我是一个水果列表页面" //上下文对象
}
async getId(){
this.ctx.body = "传递的id是"
}
}
module.exports = FruitsController;
在浏览器中填写url访问:
http://127.0.0.1:7001/fruits/123
如何获得传递的123:
const Controller = require("egg").Controller;
class FruitsController extends Controller{
async index(){
// this.ctx.request 可以拿到请求的对象
// this.ctx.request.query 可以拿到get请求中url的参数
//比如说想拿到一个索引的index
let query = this.ctx.request.query;
console.log(query);
this.ctx.body = "我是一个水果列表页面" //上下文对象
}
async getId(){
//通过this.ctx.params进行获取,后面跟要获取参数名
//这里参数名是id
let id = this.ctx.params.id;
this.ctx.body = `传递的id是${id}`
}
}
module.exports = FruitsController;
在写博客系统的时候,可以通过这种方式获取文章id,显示文章
POST请求
提交表单
获取post请求的参数: this.ctx.request.body
在controller中可以处理表单提交的数据
CSRF指跨站请求伪造,Egg对post请求做了一些安全验证(egg默认有一个安全验证,比如说直接提交post请求的话,验证会禁止提交这个请求),可以在config.default.js文件中,通过下面的设置验证。
config.security = {
csrf : {
enable:false,
}
}
先不管这些内容,先写一个表单提交
修改router.js
'use strict'; //严格模式
/**
* @param {Egg.Application} app - egg application
*/
//暴露了一个函数,通过解构赋值,拿到每一个路径
module.exports = app => {
const { router, controller } = app;
router.get('/', controller.home.index);
router.get('/fruits',controller.fruit.index);
router.get('/fruits/:id',controller.fruit.getId);
router.get('/createfruit',controller.fruit.createPage)
};
修改fruit.js
const Controller = require("egg").Controller;
let fruitList = [
"香蕉","苹果","鸭梨"
]
class FruitsController extends Controller{
async index(){
this.ctx.body = fruitList //上下文对象
}
async getId(){
//通过this.ctx.params进行获取,后面跟要获取参数名
//这里参数名是id
let id = this.ctx.params.id;
this.ctx.body = `传递的id是${id}`
}
//显示添加的页面
async createPage(){
this.ctx.body = `
<form>
<input name='fruitname'>
<button>添加水果</button>
</form>
`;
}
//处理添加功能
async createFruit(){
}
}
module.exports = FruitsController;
在浏览器http://127.0.0.1:7001/createfruit浏览器页面下添加内容,点击添加,连接会变成这样:
http://127.0.0.1:7001/createfruit?fruitname=%E8%8A%92%E6%9E%9C
填写提交的地方:
const Controller = require("egg").Controller;
let fruitList = [
"香蕉","苹果","鸭梨"
]
class FruitsController extends Controller{
async index(){
this.ctx.body = fruitList //上下文对象
}
async getId(){
//通过this.ctx.params进行获取,后面跟要获取参数名
//这里参数名是id
let id = this.ctx.params.id;
this.ctx.body = `传递的id是${id}`
}
//显示添加的页面
async createPage(){
this.ctx.body = `
<form method='post' action='/createfruit'>
<input name='fruitname'>
<button>添加水果</button>
</form>
`;
}
//处理添加功能
async createFruit(){
}
}
module.exports = FruitsController;
修改router
'use strict'; //严格模式
/**
* @param {Egg.Application} app - egg application
*/
//暴露了一个函数,通过解构赋值,拿到每一个路径
module.exports = app => {
const { router, controller } = app;
router.get('/', controller.home.index);
router.get('/fruits',controller.fruit.index);
router.get('/fruits/:id',controller.fruit.getId);
router.get('/createfruit',controller.fruit.createPage)
router.post('/createfruit',controller.fruit.createFruit);
};
修改好处理post请求的函数createFruit
const Controller = require("egg").Controller;
let fruitList = [
"香蕉","苹果","鸭梨"
]
class FruitsController extends Controller{
async index(){
this.ctx.body = fruitList //上下文对象
}
async getId(){
//通过this.ctx.params进行获取,后面跟要获取参数名
//这里参数名是id
let id = this.ctx.params.id;
this.ctx.body = `传递的id是${id}`
}
//显示添加的页面
async createPage(){
this.ctx.body = `
<form method='post' action='/createfruit'>
<input name='fruitname'>
<button>添加水果</button>
</form>
`;
}
//处理添加功能
async createFruit(){
let fruit = this.ctx.request.body;
this.ctx.body = fruit;
}
}
module.exports = FruitsController;
测试一下传递的参数能不能正常显示,显示的页面是这样的:
这是因为数据提交完成之后,因为csrf的验证,会有一个拦截
在 目录下粘贴刚才的代码,可以解决这个问题
const userConfig = {
// myAppName: 'egg',
};
config.security = {
csrf : {
enable:false,
},
};
return {
...config,
...userConfig,
};
};
然后就可以写好添加的语句了,就能实现添加功能:
const Controller = require("egg").Controller;
let fruitList = [
"香蕉","苹果","鸭梨"
]
class FruitsController extends Controller{
async index(){
this.ctx.body = fruitList //上下文对象
}
async getId(){
//通过this.ctx.params进行获取,后面跟要获取参数名
//这里参数名是id
let id = this.ctx.params.id;
this.ctx.body = `传递的id是${id}`
}
//显示添加的页面
async createPage(){
this.ctx.body = `
<form method='post' action='/createfruit'>
<input name='fruitname'>
<button>添加水果</button>
</form>
`;
}
//处理添加功能
async createFruit(){
let fruit = this.ctx.request.body;
//把获得的数据插入到fruits列表里
fruitList.push(fruit.fruitname);
this.ctx.body = "添加成功"
}
}
module.exports = FruitsController;
这样就能通过post请求实现添加水果的功能了
但是这样写,只是一个简单的功能,就添加了这么多router,egg提供了一种功能,可以简化这一步:
RESTful风格的URL定义
RESTful风格的URL可以简化路由文件:
router.resources(‘post’,’/api/posts’,controller.posts); 一个方法同时定义增删改查
把刚才的功能重写:
Fruit.js
const Controller = require("egg").Controller;
let fruitList = [
"香蕉","苹果","鸭梨"
]
class FruitsController extends Controller{
async index(){
this.ctx.body = fruitList //上下文对象
}
//new对应添加水果列表的功能
async new(){
this.ctx.body = `
<form method='post' action='/fruits'>
<input name='fruitname'>
<button>添加水果</button>
</form>
`;
}
//create对应添加数据
async create(){
let fruit = this.ctx.request.body;
//把获得的数据插入到fruits列表里
fruitList.push(fruit.fruitname);
this.ctx.body = "添加成功"
}
}
module.exports = FruitsController;
这样访问/fruits/new就能添加了,访问/fruits就能看添加之后的内容了
要注意在post页面刷新会请求两遍
Router.js
'use strict'; //严格模式
/**
* @param {Egg.Application} app - egg application
*/
//暴露了一个函数,通过解构赋值,拿到每一个路径
module.exports = app => {
const { router, controller } = app;
router.get('/', controller.home.index);
//router.get('/fruits',controller.fruit.index);
//router.get('/fruits/:id',controller.fruit.getId);
//router.get('/createfruit',controller.fruit.createPage)
//router.post('/createfruit',controller.fruit.createFruit);
//实现一句话定义增删改查
//router.resources(接口名,url,控制器)
router.resources('fruit','/fruits',controller.fruit)
};
三个参数:
router-name 给路由设定一个别名,可以通过 Helper 提供的辅助函数 pathFor 和 urlFor 来生成 URL。(可选)
path-match - 路由 URL 路径。
middleware1 - 在 Router 里面可以配置多个 Middleware。(可选)
controller - 指定路由映射到的具体的 controller 上,controller 可以有两种写法:
app.controller.user.fetch - 直接指定一个具体的 controller
'user.fetch' - 可以简写为字符串形式
进行一个刷新的修改:
const Controller = require("egg").Controller;
let fruitList = [
"香蕉","苹果","鸭梨"
]
class FruitsController extends Controller{
async index(){
this.ctx.body = fruitList //上下文对象
}
//new对应添加水果列表的功能
async new(){
this.ctx.body = `
<form method='post' action='/fruits'>
<input name='fruitname'>
<button>添加水果</button>
</form>
`;
}
//create对应添加数据
async create(){
let fruit = this.ctx.request.body;
//把获得的数据插入到fruits列表里
fruitList.push(fruit.fruitname);
this.ctx.body = "添加成功";
//跳转到/fruits 并且通过get请求
//重定向
this.ctx.redirect('/fruits');
}
}
module.exports = FruitsController;
这里/fruits页面显示的其实就是/路由下index()函数的内容
插件
Egg通过插件来实现功能的扩展
1 egg-view-nunjucks nunjucks模板插件
2 egg-cors 跨域请求配置插件
创建一个新的egg项目
进入项目路径
安装引擎模板:npm install –save egg-view-nunjucks
在plugin.js文件中引入插件,在config.default.js中配置插件
在view目录中创建模板文件,并在控制器中使用render方法渲染模板
修改一下配置:
在plugin.js中增加:
nunjucks:{
enable:true,
package:’egg-view-nunjucks’,
}
'use strict';
/** @type Egg.EggPlugin */
module.exports = {
// had enabled by egg
// static: {
// enable: true,
// }
nunjucks:{
enable:true,
package:'egg-view-nunjucks',
}
};
在config.default.js中增加:
config.view = {
defaultViewEngine : ‘nunjucks’
}
// add your user config here
const userConfig = {
// myAppName: 'egg',
};
config.view = {
defaultViewEngine : 'nunjucks'
}
return {
...config,
...userConfig,
};
};
这样就能使用nunjucks了
启动服务器,开始接下来的学习吧
要使用模板的话,需要在app路径下创建view目录,把模板都写在view目录下
通过ctx.render显示模板
'use strict';
const Controller = require('egg').Controller;
class HomeController extends Controller {
async index() {
const { ctx } = this;
//使用ctx.render(模板名)
await ctx.render("index")
}
}
module.exports = HomeController;
一个小练习,显示水果列表
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=, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>水果列表</h1>
<ul>
{% for item in fruits %}
<li>{{item}}</li>
{% endfor %}
</ul>
</body>
</html>
Home.js
'use strict';
const Controller = require('egg').Controller;
class HomeController extends Controller {
async index() {
const { ctx } = this;
//使用ctx.render(模板名,数据)
await ctx.render("index",{fruits:["香蕉","苹果","鸭梨"]})
}
}
module.exports = HomeController;
egg-cors (前后端分离的时候允许跨域)
使用egg-cors
1 npm install –save egg-cors
2 在plugin.js文件中引入插件
3 在config.default.js文件中配置egg-cors插件
先创建一个vue项目,这里创建的路径是D:\egg\demo2_vue\demo2
Egg项目的路径是D:\egg\demo2
启动两个服务器,现在我们要实现前端向后台请求数据
牵扯到跨域的问题
前端:
App.vue
<template>
<div id="app">
<h1> {{message}}</h1>
</div>
</template>
<script>
import axios from 'axios';
export default {
data(){
return{
message:"",
}
},
created(){
axios.get("http://127.0.0.1:7001/data").then(
(res)=>{
this.message = res.data;
}
)
}
}
</script>
<style>
</style>
后台
先让后台允许跨域访问:
Plugin.js
'use strict';
/** @type Egg.EggPlugin */
module.exports = {
// had enabled by egg
// static: {
// enable: true,
// }
nunjucks:{
enable:true,
package:'egg-view-nunjucks',
},
cors:{
enable:true,
package:'egg-cors'
}
};
Config-default.js
config.view = {
defaultViewEngine : 'nunjucks'
};
config.cors = {
origin:"*",
allowMethods:'GET,HEAD,PUT,POST,DELETE,PATCH'
};
return {
...config,
...userConfig,
};
};
Home.js
'use strict';
const Controller = require('egg').Controller;
class HomeController extends Controller {
async index() {
const { ctx } = this;
//使用ctx.render(模板名,数据)
await ctx.render("index",{fruits:["香蕉","苹果","鸭梨"]})
}
async getData(){
this.ctx.body = "hello egg"
}
}
module.exports = HomeController;
Router.js
'use strict';
/**
* @param {Egg.Application} app - egg application
*/
module.exports = app => {
const { router, controller } = app;
router.get('/', controller.home.index);
router.get("/data",controller.home.getData);
};
这样就能在8080的页面上显示后台的数据hello egg了
练习:通过vue和egg实现水果列表的查看添加和删除效果
定义restful接口,实现数据交互
前端
App.vue
<template>
<div id="app">
<form @submit.prevent="postData">
<input type="text" v-model="fruit">
<button>提交</button>
</form>
<ul>
<li v-for="item,index of fruitList" :key="index" >
{{item}}
<button @click="deleteData(index)">删除</button>
</li>
</ul>
</div>
</template>
<script>
import axios from 'axios';
export default {
data(){
return{
fruitList:[],
fruit:""
}
},
created(){
this.getData();
},
methods:{
getData(){
axios.get("http://127.0.0.1:7001/fruits").then(
(res)=>{
this.fruitList = res.data;
}
);
},
postData(){
axios.post("http://127.0.0.1:7001/fruits",{fruit:this.fruit}).then(
()=>{
this.getData();
}
)
},
deleteData(id){
axios.delete(`http://127.0.0.1:7001/fruits/${id}`).then(
()=>{
this.getData();
}
)
}
}
}
</script>
<style>
</style>
后台
Router.js
'use strict';
/**
* @param {Egg.Application} app - egg application
*/
module.exports = app => {
const { router, controller } = app;
router.get('/', controller.home.index);
router.get("/data",controller.home.getData);
router.resources("fruits","/fruits",controller.fruits);
};
Fruit.js
'use strict';
const Controller = require('egg').Controller;
let fruitsList = ["香蕉","苹果","鸭梨"];
class FruitsController extends Controller {
//对应get请求
//对应的url是/fruits
async index(){
this.ctx.body = fruitsList;
}
//对应post请求
async create(){
//fruit是前端传过来的
const fruit = this.ctx.request.body.fruit;
console.log(fruit);
fruitsList.push(fruit);
//正式项目里都要有响应,添加成功或失败再做相应的反应
this.ctx.body = "添加成功";
}
//对应delete请求
//对应的url /fruits/:id
async destroy(){
let id = this.ctx.params.id;
fruitsList.splice(id,1);
this.ctx.body = "删除成功";
}
}
module.exports = FruitsController;
记得设置允许跨域