egg.js是阿里旗下基于node.js和koa是一个node企业级应用开发框架,能够帮助开发团队,和开发人员减小成本。
基于koa二、es六、es7使得node具备更有规范的开发模式,更低的学习成本、更优雅的代码、更少的维护成本。
javascript
一、要求nodejs版本必须大于8.0而且要用LTS 版本
二、建立egg的环境 npm i egg-init -g / cnpm i egg-init -g (只须要安装一次)
三、建立项目
cd 到目录里面 (注意目录不要用中文 不要有空格)css
$ npm i egg-init -g
$ egg-init egg-example --type=simple //例如:egg-init 项目名称 --type=simple
$ cd egg-example
$ npm i
复制代码
npm run dev
open localhost:7001 //通常性来讲默认端口是7001
复制代码
egg-project
├── package.json
├── app.js (可选)
├── agent.js (可选)
├── app(项目开发目录)
| ├── router.js (用于配置 URL 路由规则)
│ ├── controller (用于解析用户的输入,处理后返回相应的结果)
│ | └── home.js
│ ├── service (用于编写业务逻辑层)
│ | └── user.js
│ ├── middleware (用于编写中间件)
│ | └── response_time.js
│ ├── schedule (可选)
│ | └── my_task.js
│ ├── public (用于放置静态资源)
│ | └── reset.css
│ ├── view (可选)
│ | └── home.tpl
│ └── extend (用于框架的扩展)
│ ├── helper.js (可选)
│ ├── request.js (可选)
│ ├── response.js (可选)
│ ├── context.js (可选)
│ ├── application.js (可选)
│ └── agent.js (可选)
├── config (用于编写配置文件)
| ├── plugin.js(用于配置须要加载的插件)
| ├── config.default.js
│ ├── config.prod.js
| ├── config.test.js (可选)
| ├── config.local.js (可选)
| └── config.unittest.js (可选)
└── test (用于单元测试)
├── middleware
| └── response_time.test.js
└── controller
└── home.test.js
复制代码
egg在设计彻底符合比较好的mvc的设计模式。
html
全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范。前端
在egg中视图 (view)、控制器(controller) 和数据模型 Model(Service) 和配置文件(config)java
app/controller
目录下面实现 Controller// app/controller/home.js
const Controller = require('egg').Controller;
class HomeController extends Controller {
async index() {
const { ctx } = this;
ctx.body = 'hi, world';
}
}
module.exports = HomeController;
复制代码
输入 npm run dev 查看 http://127.0.0.1:7001 输出 hi, worldnode
我认为控制器就是一个接口,他管理输入和输出mysql
*一样你能够在app/controller 目录下 写不少个这样个js的,来表明接口es6
主要用来描述请求 URL 和具体承担执行动做的 Controller 的对应关系, 框架约定了 app/router.js
文件用于统一全部路由规则。sql
如今不少单页面,都是存在相对于的路由,你写个js,一样就要写一个路由数据库
// app/controller/user.js
class UserController extends Controller {
async info() {
const { ctx } = this;
ctx.body = {
name: `hello ${ctx.params.id}`,
};
}
}
复制代码
// app/router.js
module.exports = app => {
const { router, controller } = app;
router.get('/user/:id', controller.user.info);
};
复制代码
简单来讲,Service 就是在复杂业务场景下用于作业务逻辑封装的一个抽象层,提供这个抽象有如下几个好处:
// app/service/user.js
const Service = require('egg').Service;
class UserService extends Service {
async addName(name) {
const user = `你好,${name}`;
return user;
}
}
module.exports = UserService;
复制代码
// app/controller/user.js
class UserController extends Controller {
async info() {
const { ctx } = this;
const userInfo = await ctx.service.user.addName('wjw');
ctx.body = userInfo;
}
}
复制代码
egg中的模板渲染,可是我认为前端后端分离的设计,更加有利于做为服务型架构设计,因此这边不描述view的构造
在 URL 中 ?
后面的部分是一个 Query String,这一部分常常用于 GET 类型的请求中传递参数。例如 GET /search?name=egg&age=26
中 name=egg&age=26
就是用户传递过来的参数。咱们能够经过 context.query
(为一个对象)拿到解析事后的这个参数体
module.exports = app => {
class HomeController extends Controller {
async getQuery() {
const queryObj = this.ctx.query;
console.log(queryObj.age);
console.log(queryObj);
//打印结果:{ name: 'egg', age: '26' }
}
}
return SearchController;
};
复制代码
当 Query String 中的 key 重复时,context.query
只取 key 第一次出现时的值,后面再出现的都会被忽略。GET /posts?category=egg&category=koa
经过 context.query
拿到的值是 { category: 'egg' }
。
有时候咱们的系统会设计成让用户传递相同的 key,例如 GET /posts?category=egg&id=1&id=2&id=3
。针对此类状况,框架提供了 context.queries
对象,这个对象也解析了 Query String,可是它不会丢弃任何一个重复的数据,而是将他们都放到一个数组
中:
// GET /posts?category=egg&id=1&id=2&id=3
const Controller = require('egg').Controller;
class HomeController extends Controller {
async getQueries() {
console.log(this.ctx.queries);
//result:
// {
// category: [ 'egg' ],
// id: [ '1', '2', '3' ],
// }
}
};
复制代码
context.queries
上全部的 key 若是有值,也必定会是数组
类型。
// 获取参数方法 post 请求
module.exports = app => {
class HomeController extends Controller {
async postObj() {
const queryObj = ctx.request.body;
ctx.body = queryObj;
}
}
return SearchController;
};
复制代码
可是咱们请求有时是get,有时是post,有时原本应该是post的请求,可是为了测试方便,仍是作成get和post请求都支持的请求,因而一个能同时获取get和post请求参数的中间件就颇有必要了.
/** * 获取请求参数中间件 * 可使用ctx.params获取get或post请求参数 */
module.exports = options => {
return async function params(ctx, next) {
ctx.params = {
...ctx.query,
...ctx.request.body
}
await next();
};
};
复制代码
本质上就是把get请求的参数和post请求的参数都放到params这个对象里,因此,不论是get仍是post都能获取到请求参数
'use strict';
module.exports = appInfo => {
const config = exports = {};
// 注入中间件
config.middleware = [
'params',
];
return config;
};
复制代码
/** * 添加文章接口 */
'use strict';
const Service = require('egg').Service;
class ArticleService extends Service {
async add() {
const { ctx } = this;
// 获取请求参数
const {
userId,
title,
content,
} = ctx.params;
const result = await ctx.model.Article.create({
userId,
title,
content,
});
return result;
}
}
module.exports = ArticleService;
复制代码
// config/plugin.js
exports.cors = {
enable: true,
package: 'egg-cors',
};
复制代码
// config/config.default.js
config.security = {
csrf: {
enable: false,
ignoreJSON: true,
},
domainWhiteList: [ 'http://www.baidu.com' ], // 配置白名单
};
config.cors = {
// origin: '*',//容许全部跨域访问,注释掉则容许上面 白名单 访问
allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH',
};
复制代码
*通常性最好使用白名单,不要使用所有容许跨域,不安全
框架提供了 egg-mysql 插件来访问 MySQL 数据库。这个插件既能够访问普通的 MySQL 数据库,也能够访问基于 MySQL 协议的在线数据库服务。
安装对应的插件 egg-mysql :
npm i --save egg-mysql
复制代码
开启插件:
// config/plugin.js
exports.mysql = {
enable: true,
package: 'egg-mysql',
};
复制代码
在 config/config.${env}.js
配置各个环境的数据库链接信息。
若是咱们的应用只须要访问一个 MySQL 数据库实例,能够以下配置:
使用方式:
// config/config.${env}.js
exports.mysql = {
// 单数据库信息配置
client: {
// host
host: 'mysql.com',
// 端口号
port: '3306',
// 用户名
user: 'test_user',
// 密码
password: 'test_password',
// 数据库名
database: 'test',
},
// 是否加载到 app 上,默认开启
app: true,
// 是否加载到 agent 上,默认关闭
agent: false,
};
复制代码
await app.mysql.query(sql, values); // 单实例能够直接经过 app.mysql 访问
复制代码
若是咱们的应用须要访问多个 MySQL 数据源,能够按照以下配置:
exports.mysql = {
clients: {
// clientId, 获取client实例,须要经过 app.mysql.get('clientId') 获取
db1: {
// host
host: 'mysql.com',
// 端口号
port: '3306',
// 用户名
user: 'test_user',
// 密码
password: 'test_password',
// 数据库名
database: 'test',
},
db2: {
// host
host: 'mysql2.com',
// 端口号
port: '3307',
// 用户名
user: 'test_user',
// 密码
password: 'test_password',
// 数据库名
database: 'test',
},
// ...
},
// 全部数据库配置的默认值
default: {
},
// 是否加载到 app 上,默认开启
app: true,
// 是否加载到 agent 上,默认关闭
agent: false,
};
复制代码
const result = await this.app.mysql.insert('users', {
name: 'wjw',
age: 18
})
// 判断:result.affectedRows === 1
复制代码
const result = await this.app.mysql.select('users', {
columns: ['id', 'name'], //查询字段,所有查询则不写,至关于查询*
where: {
name: 'wjw'
}, //查询条件
orders: [
['id', 'desc'] //降序desc,升序asc
],
limit: 10, //查询条数
offset: 0 //数据偏移量(分页查询使用)
})
//判断:result.length > 0
复制代码
const result = await this.app.mysql.update('users', {
age: 20 //须要修改的数据
}, {
where: {
id: 1
} //修改查询条件
});
//判断:result.affectedRows === 1
复制代码
const result = await this.app.mysql.delete('users', {
name: 'wjw'
})
复制代码
ctx.cookies.set(key, value, options)
this.ctx.cookies.set('name','zhangsan');
复制代码
ctx.cookies.get(key, options)
this.ctx.cookies.get('name')
复制代码
this.ctx.cookies.set('name',null);
复制代码
或者设置 maxAge 过时时间为 0
ctx.cookies.set(key, value, {
maxAge:24 * 3600 * 1000,
httpOnly: true, // 默认状况下是正确的
encrypt: true, // cookie在网络传输期间加密
ctx.cookies.get('frontend-cookie', {
encrypt: true
});
复制代码
console.log(new Buffer('hello, world!').toString('base64'));
// 转换成 base64字符串:aGVsbG8sIHdvcmxkIQ==
console.log(new Buffer('aGVsbG8sIHdvcmxkIQ==', 'base64').toString()); // 还原 base64字符串:hello, world!
复制代码
ctx.cookies.set(key, value, {
maxAge:24 * 3600 * 1000,
httpOnly: true, // 默认状况下是正确的
encrypt: true, // cookie在网络传输期间进行加密
});
复制代码
session 是另外一种记录客户状态的机制,不一样的是 Cookie 保存在客户端浏览器中,而session 保存在服务器上。
当浏览器访问服务器并发送第一次请求时,服务器端会建立一个 session 对象,生成一个相似于 key,value 的键值对, 而后将 key(cookie)返回到浏览器(客户)端,浏览器下次再访问时,携带 key(cookie),找到对应的 session(value)。
egg.js 中 session 基于 egg-session 内置了对 session 的操做
this.ctx.session.userinfo={
name:'张三',
age:'20'
}
复制代码
var userinfo=this.ctx.session
复制代码
exports.session = {
key: 'EGG_SESS',
maxAge: 24 * 3600 * 1000, // 1 day httpOnly: true,
encrypt: true
};
复制代码
config.session={
key:'SESSION_ID',
maxAge:864000,
renew: true //延长会话有效期
}
复制代码
egg提供了强大的定时任务系统。经过定时任务,能够系统修改服务的缓存数据,以便处理须要定时更新的数据。
在app/schedule目录下新建一个js文件,每个js文件就是一个定时任务
// app/schedule
module.exports = {
schedule: {
interval: '1m', // 1 分钟间隔
type: 'all', // 指定全部的 worker 都须要执行
},
async task(ctx) {
i++
console.log(i)
},
};
/* 注释: 1ms -> 1毫秒 1s -> 1秒 1m -> 1分钟 */
复制代码
定点任务(以每周一的5点30分0秒更新排行榜为例)
一、使用cron参数设定时间,cron参数分为6个部分,*表示全部都知足
* * * * * *
┬ ┬ ┬ ┬ ┬ ┬
│ │ │ │ │ |
│ │ │ │ │ └ 星期 (0 - 7) (0或7都是星期日)
│ │ │ │ └───── 月份 (1 - 12)
│ │ │ └────────── 日期 (1 - 31)
│ │ └─────────────── 小时 (0 - 23)
│ └──────────────────── 分钟 (0 - 59)
└───────────────────────── 秒 (0 - 59, optional)
复制代码
// app/schedule
module.exports = {
schedule: {
cron: '0 30 5 * * 1', //每周一的5点30分0秒更新
type: 'all', // 指定全部的 worker 都须要执行
},
async task(ctx) {
i++
console.log(i)
},
};
复制代码
设置immediate参数为true时,该定时任务会在项目启动时,当即执行一次定时任务
module.exports = {
schedule: {
interval: '1m', // 1 分钟间隔
type: 'all', // 指定全部的 worker 都须要执行
immediate: true, //项目启动就执行一次定时任务
},
async task(ctx) {
i++
console.log(i)
},
};
复制代码
配置disable参数为true时,该定时任务即关闭
env: ["dev", "debug"] //该定时任务在开发环境和debug模式下才执行
复制代码
首先固然是在你的服务器上部署好node服务,而后安装好。
服务器须要预装 Node.js,框架支持的 Node 版本为 >= 8.0.0。
框架内置了 egg-cluster 来启动 Master 进程,Master 有足够的稳定性,再也不须要使用 pm2 等进程守护模块。
同时,框架也提供了 egg-scripts 来支持线上环境的运行和中止。
egg-scripts start --port=7001 --daemon --title=egg-server-showcase
复制代码
--port=7001
端口号,默认会读取环境变量 process.env.PORT
,如未传递将使用框架内置端口 7001
。--daemon
是否容许在后台模式,无需 nohup
。若使用 Docker 建议直接前台运行。--env=prod
框架运行环境,默认会读取环境变量 process.env.EGG_SERVER_ENV
, 如未传递将使用框架内置环境 prod
。--workers=2
框架 worker 线程数,默认会建立和 CPU 核数至关的 app worker 数,能够充分的利用 CPU 资源。--title=egg-server-showcase
用于方便 ps 进程时 grep 用,默认为 egg-server-${appname}
。--framework=yadan
若是应用使用了能够配置 package.json
的 egg.framework
或指定该参数。--ignore-stderr
忽略启动期的报错。你也能够在 config.{env}.js
中配置指定启动配置。
// config/config.default.js
exports.cluster = {
listen: {
port: 7001,
hostname: '127.0.0.1',
// path: '/var/run/egg.sock',
}
}
复制代码
path
,port
,hostname
均为 server.listen 的参数,egg-scripts
和 egg.startCluster
方法传入的 port 优先级高于此配置。
s
该命令将杀死 master 进程,并通知 worker 和 agent 优雅退出。
支持如下参数:
--title=egg-server
用于杀死指定的 egg 应用,未传递则会终止全部的 Egg 应用。"start": "egg-scripts start --daemon --title=${进程名称}",
"stop": "egg-scripts stop --title=${进程名称}"
复制代码
ps -eo "pid,command" | grep -- "--title=egg-server"
复制代码
来找到 master 进程,并 kill
掉,无需 kill -9
。
由于egg的知识点太多,故分上下两章