咱們在上一篇文章Egg.js 源碼分析-項目啓動, 已經簡單的分析了Eggjs 的啓動機制, 以及其相應的實現原理,Eggjs 就是針對一系列的約定俗成的規則,在項目啓動時,自動加載對應文件夾下面的文件,進行項目的初始化,咱們能夠參考官網給出的目錄結構,去對咱們的項目進行規範,包括文件結構規範, 代碼邏輯分層規範,從而達到整個項目的規範。javascript
之因此有這樣的一個 目錄結構 ,其實仍是針對於咱們上一篇文章 Egg.js 源碼分析-項目啓動 分析所得,在項目啓動時,會加載以下的配置, 下面代碼後面加了備註,只標註了咱們應用對應的文件名稱(沒有標註eggjs 對應的文件名稱)loadConfig() {
// your-project-name/config/plugin.js
this.loadPlugin();
// your-project-name/config/config.default.js
// your-project-name/config/`config.${this.serverEnv}`.js
super.loadConfig();
}
複製代碼
load() {
// app > plugin > core
this.loadApplicationExtend();
this.loadRequestExtend();
this.loadResponseExtend();
this.loadContextExtend();
this.loadHelperExtend();
// your_project_name/app.js
this.loadCustomApp();
// your_project_name/app/service/**.js
this.loadService();
this.loadMiddleware();
// your_project_name/app/controller/**.js
this.loadController();
// your_project_name/ app/router.js
this.loadRouter(); // Dependent on controllers
}
複製代碼
從上面能夠知道咱們應用的一個大體的結構,咱們下面就來一一從頭開始建立一個項目,深刻了解Eggjs 的使用方式(規範)html
咱們利用egg-init 的手腳架egg-init 先初始化一個項目, 咱們能夠運行以下命令(一直沒怎麼接觸APP 開發, 因此最近想研究下react-native ,因此建立了一個react-native-learning-server項目):java
$ npm i egg-init -g
$ egg-init react-native-learning-server --type=simple
$ cd react-native-learning-server
$ npm i
$ npm run dev
複製代碼
瀏覽器會打開默認端口:http://localhost:7001, 頁面會顯示hi, egg
, 說明咱們項目建立成功.react
咱們如今假設咱們想作一個相似掘金同樣的APP, 咱們能夠有四個大菜單 Mine , Find , Message, Homegit
咱們簡單的設計幾個API:github
Mine:mongodb
API | Method | 描敘 |
---|---|---|
/users | GET | 獲取全部的用戶 |
/user/:id | GET | 獲取指定用戶信息 |
/user | POST | 添加用戶 |
/user/:id | PUT | 編輯用戶 |
/user/:id | DELETE | 刪除用戶 |
Message:數據庫
API | Method | 描敘 |
---|---|---|
/messages/:userId | GET | 獲取用戶全部的信息 |
/message | POST | 發送信息 |
/message/:id | DELETE | 刪除指定信息 |
Find:npm
API | Method | 描敘 |
---|---|---|
/search/:keyword | GET | 根據關鍵字,查詢信息 |
Home:json
API | Method | 描敘 |
---|---|---|
/hot | GET | 查詢最熱信息 |
/heart | GET | 查詢關注的熱點 |
咱們先只設計如上幾個簡單的API(咱們這篇文章,只是想經過一個僞業務來實現Egg的一些使用方式, 重點是Eggjs 的使用)
上面咱們已經初始化了項目, 咱們如今編輯app/router.js
去設計路由,其代碼以下:
module.exports = app => {
const { router, controller } = app;
router.get('/', controller.home.index);
// User
router.get('/users', controller.user.findAll);
router.get('/user/:id', controller.user.findOne);
router.post('/user', controller.user.add);
router.put('/user/:id', controller.user.update);
router.del('/user/:id', controller.user.delete);
// Message
router.get('/messages/:userId', controller.message.findByUserId);
router.post('/message', controller.message.add);
router.del('/messages/:id', controller.message.delete);
// Find
router.get('/search/:keyword', controller.search.find);
// Home
router.get('/hot', controller.home.findHot);
router.get('/heart', controller.home.findHeart);
};
複製代碼
咱們先無論Controller的實現, 上面咱們就已經實現了咱們的路由了, 可是咱們發現一個問題,就是當項目愈來愈大,那這個router.js 會愈來愈大,也會愈來愈難以維護,因此咱們能夠作以下的調整:
'use strict';
const userRouter = require('./routers/user');
const messageRouter = require('./routers/message');
const homeRouter = require('./routers/home');
const searchRouter = require('./routers/search');
module.exports = app => {
const { router, controller } = app;
router.get('/', controller.home.index);
userRouter(app);
messageRouter(app);
homeRouter(app);
searchRouter(app);
};
複製代碼
其routers/user.js 代碼以下:
'use strict';
module.exports = app => {
const { router, controller } = app;
router.get('/users', controller.user.findAll);
router.get('/user/:id', controller.user.findOne);
router.post('/user', controller.user.add);
router.put('/user/:id', controller.user.update);
router.del('/user/:id', controller.user.delete);
};
複製代碼
通過如上的拆分, router.js 的代碼變得整潔, 並且相應的路由變得更加容易維護.
上面咱們已經開發(配置)好了Router, 可是Router 的回調函數,都指向的是app 的controller下面的對象, 如:controller.user.findAll
咱們在上一章節已經分析了這個路徑怎麼來的: controller 是app(應用)的一個屬性對象, eggjs 會在啓動的時候調用this.loadController();
方法,去加載整個應用app/controller
文件下的全部的js 文件, 會將文件名做爲屬性名稱,掛載在app.controller 對象上, 而後將對應js 文件export(暴露)出來的全部的方法有掛在在文件名稱爲屬性的對象上,而後就能夠經過controller.user.findAll
這樣的方式來引用Controller 下面的方法了。
有了這個思路,咱們就能夠很清晰的去維護咱們控制層了,下面是咱們一個home的範例:
'use strict';
const Controller = require('egg').Controller;
class HomeController extends Controller {
async index() {
this.ctx.body = 'hi , egg.';
}
async findHot() {
this.ctx.body = this.ctx.request.url;
}
async findHeart() {
this.ctx.body = this.ctx.request.url;
}
}
module.exports = HomeController;
複製代碼
咱們已經開發好了Router 和Controller , 可是在咱們的controller 中,都是靜態的內容, 一個項目咱們須要跟數據庫交互,咱們通常將跟DB 交互的內容,都放在Service 層,下面咱們就來開發咱們的service.
咱們首先在app目錄下面,建立一個service 目錄, 而且建立 user.js, message.js, search.js, home.js,文件, 咱們先不鏈接真實的數據庫, 建立的service 以下(home.js):
'use strict';
const Service = require('egg').Service;
class HomeService extends Service {
async index() {
return 'hi, egg';
}
findHot() {
const hotArticle = [
{
title: 'Title 0001',
desc: 'This is hot article 0001',
},
{
title: 'Title 0002',
desc: 'This is hot article 0002',
},
];
return hotArticle;
}
findHeart() {
const heartArticle = [
{
title: 'Title 0001',
desc: 'This is heart article 0001',
},
{
title: 'Title 0002',
desc: 'This is heart article 0002',
},
];
return heartArticle;
}
}
module.exports = HomeService;
複製代碼
咱們接下來修改Controller文件:
'use strict';
const Controller = require('egg').Controller;
class HomeController extends Controller {
async index() {
this.ctx.body = this.service.home.index();
}
async findHot() {
this.ctx.body = this.service.home.findHot();
}
async findHeart() {
this.ctx.body = this.service.home.findHeart();
}
}
module.exports = HomeController;
複製代碼
咱們調用service方法如: this.service.home.index();
, 跟controller 原理相似。
到此位置,咱們項目的基本框架,已經搭建完成,咱們如今能夠思考先,咱們怎麼鏈接數據庫。
咱們的數據庫咱們選擇用MongoDB, 因此,咱們能夠選擇用egg-mongoose 插件,咱們能夠按照文檔進行操做: 首先安裝插件:
$ npm i egg-mongoose --save
由於egg-mongoose 是做爲Eggjs 的一個插件,因此咱們要配置這個插件,咱們如今app/config/plugin.js
中配置插件:
'use strict';
module.exports = {
// enable plugins
mongoose: {
enable: true,
package: 'egg-mongoose',
},
};
複製代碼
接下來,咱們要配置MongoDB 的鏈接, 咱們修改app/config/config.default.js
:
'use strict';
module.exports = appInfo => {
const config = exports = {};
// use for cookie sign key, should change to your own and keep security
config.keys = appInfo.name + '_1541735701381_1116';
// add your config here
config.middleware = [];
config.cluster = {
listen: {
path: '',
port: 7001,
hostname: '',
},
};
config.mongoose = {
client: {
url: 'mongodb://127.0.0.1/react-native-demo',
options: {},
},
};
return config;
};
複製代碼
接下來咱們須要給MongoDB 配置Model,咱們在app 目錄下面,建立一個model 文件夾, 而且建立user.js, 代碼以下:
'use strict';
// {app_root}/app/model/user.js
module.exports = app => {
const mongoose = app.mongoose;
const Schema = mongoose.Schema;
const UserSchema = new Schema({
userName: { type: String },
password: { type: String },
});
return mongoose.model('User', UserSchema);
};
複製代碼
而後咱們接下來,修改Service 來真正的鏈接數據庫,修改service/user.js
代碼以下:
'use strict';
const Service = require('egg').Service;
class UserService extends Service {
async findAll() {
return await this.ctx.model.User.find();
}
}
module.exports = UserService;
複製代碼
鏈接數據庫的真個流程完成了,咱們能夠打開http://localhost:7001/users
, 頁面就會顯示從MongoDB 數據庫裏面查詢的全部的數據。
總結
$ npm i egg-mongoose --save
app/config/plugin.js
中配置插件app/config/config.default.js
,添加egg-mongoose鏈接信息{app_root}/app/model/user.js
下建立Model.await this.ctx.model.User.find();
問題
config.default.js
中,配置mongoose 的鏈接信息,掛載在config.mongoose
上, mongoose
這個名稱是不是固定的, 能夠修改爲其餘的?app/config/plugin.js
中,配置mongoose的插件信息, mongoose
的屬性名稱是不是固定的, 能夠修改爲其餘的?答案
function loadModelToApp(app) {
const dir = path.join(app.config.baseDir, 'app/model');
app.loader.loadToApp(dir, 'model', {
inject: app,
caseStyle: 'upper',
filter(model) {
return typeof model === 'function' && model.prototype instanceof app.mongoose.Model;
},
});
}
複製代碼
mongoose
這個名稱,由於在egg-mongoose這個插件中,會直接去讀取應用中app.config.mongoose
的配置const { client, clients, url, options, defaultDB, customPromise, loadModel } = app.config.mongoose;
,因此這個規則是egg-mongoose
插件制定的。plugin.js
的配置名稱, 不是固定的, 能夠隨意,由於其真正重要的配置是: package: 'egg-mongoose'
,指明這個pulgin 用的是那個具體的包。在一個項目上線的時候,咱們常常須要準備一些初始化數據, 好比用戶數據,咱們通常會建立一個超級管理員的賬號, 這個賬號,是不須要用戶註冊的,因此咱們能夠在項目初始化的時候用腳本生成,咱們按照以下步驟進行操做:
app/model/user.js
添加isMaster
屬性,以下:'use strict';
// {app_root}/app/model/user.js
module.exports = app => {
const mongoose = app.mongoose;
const Schema = mongoose.Schema;
const UserSchema = new Schema({
userName: { type: String, required: true },
password: { type: String, required: true },
isMaster: { type: Boolean, default: false, required: true },
});
return mongoose.model('User', UserSchema);
};
複製代碼
{app_root}/app
目錄下,建立一個data
的文件夾,而後建立一個user.json
, 其內容以下:[
{
"userName": "admin",
"password": "admin",
"isMaster": true
}
]
複製代碼
{app_root}
目錄下,添加一個app.js
(this.loadCustomApp()),代碼以下:'use strict';
// app.js
module.exports = app => {
app.beforeStart(async () => {
if (app.config.initData) {
const initUsers = require('./app/data/user.json');
const ctx = app.createAnonymousContext();
ctx.model.User.create(initUsers, err => {
if (err) {
app.coreLogger.console.warn('[egg-app-beforeStart] init user data fail %s', err);
}
});
}
});
};
複製代碼
咱們在配置文件中添加了一個initData
的開關用來表示是否須要初始化數據,由於初始化數據,通常就是第一次須要(這個配置,應該做爲運行腳本命令的參數傳遞,這樣更易於維護,並且不用每次都去該config.default.js 的代碼)
按照上面的操做,咱們基本完成了一個項目的基本骨架,咱們只須要在上面搭積木就能夠了,並且瞭解了Eggjs 的基本使用。源碼