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的知識點太多,故分上下兩章