本文共 9731 字,大约阅读时间需要 32 分钟。
我们在上一篇文章, 已经简单的分析了Eggjs 的启动机制, 以及其相应的实现原理,Eggjs 就是针对一系列的约定俗成的规则,在项目启动时,自动加载对应文件夹下面的文件,进行项目的初始化,我们可以参考官网给出的,去对我们的项目进行规范,包括文件结构规范, 代码逻辑分层规范,从而达到整个项目的规范。
之所以有这样的一个目录结构 ,其实还是针对于我们上一篇文章 分析所得,在项目启动时,会加载如下的配置, 下面代码后面加了备注,只标注了我们应用对应的文件名称(没有标注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 的使用方式(规范)
我们利用egg-init 的手脚架egg-init 先初始化一个项目, 我们可以运行如下命令(一直没怎么接触APP 开发, 所以最近想研究下react-native ,所以创建了一个react-native-learning-server项目):
$ npm i egg-init -g$ egg-init react-native-learning-server --type=simple$ cd react-native-learning-server$ npm i$ npm run dev复制代码
浏览器会打开默认端口:, 页面会显示hi, egg
, 说明我们项目创建成功.
我们现在假设我们想做一个类似掘金一样的APP, 我们可以有四个大菜单 Mine , Find , Message, Home
我们简单的设计几个API:
Mine:
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:
API | Method | 描叙 |
---|---|---|
/search/:keyword | GET | 根据关键字,查询信息 |
Home:
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, 所以,我们可以选择用 插件,我们可以按照文档进行操作: 首先安装插件:
$ 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.jsmodule.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.jsmodule.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
(),代码如下:'use strict';// app.jsmodule.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 的基本使用。
作者:bluebrid 链接:https://juejin.im/post/5bf362f0e51d4543850ff46c 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。