在上一篇文章 Egg入门学习一 中,我们简单的了解了Egg是什么东西,且能做什么,这篇文章我们首先来看看官网对Egg的整个框架的约定如下,及约定对应的目录是做什么的,来有个简单的理解,注意:我也是按照官网的来理解的。

egg-project
├── package.json
├── app.js (可选)
├── app
| ├── router.js
│ ├── controller
│ | └── home.js
│ ├── service (可选)
│ | └── user.js
│ ├── middleware (可选)
│ | └── xxx.js
│ ├── schedule (可选)
│ | └── xxx.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

app/router.js 是使用与配置url的路由规则的。
app/controller/** 用于解析用户的输入,处理后返回响应的结果。
app/service/** 用于编写业务逻辑层。
app/middleware/** 用于编写中间件。
app/public/** 用于放置静态资源。
app/extend/** 用于框架的扩展。
config/config.{env}.js 用于编写配置文件。
config/plugin.js 用于编写需要加载的插件。
test/** 一般用于单元测试。
app.js 一般用于启动时候的初始化。
app/view/** 用于放置模板文件,具体是做模板渲染的。
app/model/** 用于放置领域模型,由领域类相关插件约定。如 egg-sequelize

如上就是官网中对egg目录的约定,我们只需要在对应目录中写对应的代码即可,框架内部会自动会帮我们把内部代码组织起来,具体怎么组织的,它的主要逻辑应该在 egg-core 中,在接下来的学习中,我会逐步学习egg-core源码来理解egg整个框架的原理的。
现在我们只需要知道就是这样使用就行了。

下面我们来回过头来看看理解下我们第一篇文章Egg入门相关的搭建 和渲染整个框架的页面是怎么样的逻辑,上一篇文章我们是使用的是静态数据来渲染页面的,这边文章我们使用 app/service 文件下来使用ajax接口来获取数据的demo。因为在项目当中数据不可能是我们写死的,而是接口动态获取的。
在上一篇Egg入门学习中,我们项目渲染整个目录结构如下:

egg-demo2
├── app
│ ├── controller
│ │ └── home.js
| | |-- index.js
│ └── router.js
│ ├──public
| | |---css
| | | |-- index.css
| | |---js
| | | |-- index.js
| |--- view
| | |-- index
| | | |-- list.tpl(模板文件list)
├── config
│ └── config.default.js
└── package.json

app/controller/home.js 代码如下:

const Controller = require('egg').Controller;

class HomeController extends Controller {
async index() {
this.ctx.body = 'Hello world';
}
}
module.exports = HomeController;

app/controller/index.js 代码如下:

// app/controller/index.js
const Controller = require('egg').Controller; class IndexController extends Controller {
async list() {
const dataList = {
list: [
{ id: 1, title: '今天是我第一天学习egg了', url: '/index/1' },
{ id: 2, title: '今天是我第一次学习egg了', url: '/index/2' }
]
};
await this.ctx.render('index/list.tpl', dataList);
}
}
module.exports = IndexController;

app/controller/** 用于解析用户的输入,处理后返回响应的结果。 如上 home.js 和 index.js 使用是Es6的类来编写代码,它都继承了 egg中的Controller,其中index.js 定义了 dataList 对象数据,然后使用ctx.render把数据渲染到 模板里面去。
这里的模板就是 app/view/index/list.tpl的,在上面的目录中,我们可以看到 view和controller是同级目录的,在egg内部会直接找到view这个目录的,然后对模板 index/list.tpl这个目录进行解析。这就是 app/controller/** 的作用,它用于解析用户输入,然后把结果会渲染到模板里面去,处理模板后就会返回响应的结果。

app/public/** 目录的的作用是 用于放置静态资源。比如css和js,然后在 app/view/** 中的模板文件引入该资源文件即可
在页面中调用。

app/view/** 文件的作用是用于放置模板文件,具体是做模板渲染的。我们在 app/view/index/list.tpl 的代码如下:

<!-- app/view/index/list.tpl -->
<html>
<head>
<title>第一天学习egg</title>
<link rel="stylesheet" href="/public/css/index.css" />
</head>
<body>
<ul class="view-list">
{% for item in list %}
<li class="item">
<a href = "{{ item.url }}">{{ item.title }}</a>
</li>
{% endfor %}
</ul>
</body>
</html>

如上,在app/controller/index.js 中,我们把 dataList 对象渲染到该模板中,其中 dataList 对象中有一个list数组。
因此在该模板中,我们直接使用 egg-view-nunjucks 模板引擎的语法来循环遍历即可把数据渲染出来。

app/router.js 的作用是配置url路由规则的,代码如下:

module.exports = app => {
const { router, controller } = app;
router.get('/', controller.home.index);
router.get('/index', controller.index.list);
}

在如上参数 app 可能会把 router, controller 等等都挂载该对象上面,因此也是使用es6语法把它导入进来,然后使用router路由get请求,当我们访问:http://127.0.0.1:7001/ 的时候,我们就会调用 controller.home.index 模板,也就是会找到app/controller/home.js 的文件,然后调用里面的 index()方法。即可执行。

当我们访问 http://127.0.0.1:7001/index 的时候,我们就会调用 app/controller/index.js 的文件,然后调用里面的list方法,然后执行list方法,就会把数据渲染到对应中的模板里面去,然后对应的模板就会对数据进行渲染,渲染完成后就会在页面中返回对应的结果出来。

在项目中 会有一个config配置文件,所有的配置写在该 config/config.default.js 中,当然官网还有其他的配置文件,比如叫:config.prod.js,config.local.js 等等。config/config.default.js 代码配置如下:

// 下面是我自己的 Cookie 安全字符串密钥
exports.keys = '123456'; // 添加view配置
exports.view = {
defaultViewEngine: 'nunjucks',
mapping: {
'.tpl': 'nunjucks'
}
};

比如上面叫 export.view 是对 view下的模板文件配置默认的模板引擎。其中mapping含义应该是映射的含义吧,应该是把模板引擎映射到有关 .tpl后缀的文件中。

这就是之前那篇文章的所有的简单的理解目录结构。那么我们知道之前那篇文章是数据是写死在 app/controller/** 中的,但是在我们项目实际应用中,我们的数据不应该是写死的,那就可能请求ajax接口,然后把接口的数据返回回来,我们再把对应的数据渲染出来。
从上面我们了解到 app/controller/** 用于解析用户的输入,处理后返回响应的结果。所以对于ajax接口请求具体的业务逻辑,我们复杂的业务逻辑不应该放在该目录下,该目录下只是做一些简单的用户输入,那么复杂的业务逻辑,我们这边就应该放到 app/service/** 目录下。因此我们需要把具体的业务逻辑代码写到 app/service/** 中。

现在我们需要在 app/ 下新建一个 service目录,在该目录下新建一个 index.js 来处理具体的业务逻辑代码。

业务代码如下:

// app/service/index.js

const Service = require('egg').Service;
class IndexService extends Service {
async list(page = 1) {
// 读取config下的默认配置
const { serverUrl, pageSize } = this.config.index; const { data: idList } = await this.ctx.curl(`${serverUrl}/topstories.json`, {
data: {
orderBy: '"$key"',
startAt: `"${pageSize * (page - 1)}"`,
endAt: `"${pageSize * page - 1}"`
},
dataType: 'json',
}); const indexList = await Promise.all(
Object.keys(idList).map(key => {
const url = `${serverUrl}/item/${idList[key]}.json`;
return this.ctx.curl(url, { dataType: 'json' });
})
);
return indexList.map(res => res.data);
}
}; module.exports = IndexService;

我们现在需要把 app/controller/index.js 代码改成如下:

// app/controller/index.js
const Controller = require('egg').Controller; class IndexController extends Controller {
async list() {
/*
const dataList = {
list: [
{ id: 1, title: '今天是我第一天学习egg了', url: '/index/1' },
{ id: 2, title: '今天是我第一次学习egg了', url: '/index/2' }
]
};
*/
const ctx = this.ctx;
const page = ctx.query.page || 1;
const indexList = await ctx.service.index.list(page); await ctx.render('index/list.tpl', { list: indexList });
}
} module.exports = IndexController;

然后在 config/config.default.js 配置中添加对应的请求 url 和 页码大小配置如下:

// 下面是我自己的 Cookie 安全字符串密钥

exports.keys = '123456';

// 添加view配置
exports.view = {
defaultViewEngine: 'nunjucks',
mapping: {
'.tpl': 'nunjucks'
}
}; // 添加index 的 配置项
exports.index = {
pageSize: 10,
serverUrl: 'https://hacker-news.firebaseio.com/v0'
};

然后我们在 浏览器访问 http://127.0.0.1:7001/index 后,在页面中返回如下页面:

因为接口是node服务器端渲染的,所以在浏览器中是看不到请求的。

注意: https://hacker-news.firebaseio.com/v0 这个请求想请求成功 需要chrome翻墙下才能请求成功,当然我们也可以换成
自己的请求接口地址的。

app/service/index.js 中,我们继承了egg中的Service实列,在用户的每次请求中,框架都会实列化对应的Service实列。因此Service会提供有如下属性值:

this.ctx: 当前请求的上下文 Context对象的实列,我们就可以拿到该框架封装好的当前请求的各种属性和方法。
this.app: 当前应用的Application对象的实列,通过它我们就可以拿到框架提供的全局对象和方法。
this.servie: 应用定义的Service,通过它可以访问到其他的业务层。等价于 this.ctx.service.
this.config: 可以拿到应用时的配置项对应的目录。默认指向与 config.default.js.

Service 提供如下方法:
this.ctx.curl 发起网络调用请求。
this.ctx.service.otherService 调用其他的Service.
this.ctx.db 发起数据库调用等。db可能是其他插件提取挂载到app上的模块。

注意:
1. 一个Service文件只能包含一个类,这个类需要通过 module.exports 的方式返回。
2. Service需要通过Class的方式定义,父类必须是 egg.Service.
3. Service不是单列,是请求级别的对象,框架在每次请求中首次访问 ctx.service.xx 时延迟实例化,所以我们建议在Service中
可以通过 this.ctx获取当前请求的上下文。

因此现在项目目录结构就变成如下了:

egg-demo2
├── app
│ ├── controller
│ │ └── home.js
| | |-- index.js
│ └── router.js
│ ├──public
| | |---css
| | | |-- index.css
| | |---js
| | | |-- index.js
| |--- view
| | |-- index
| | | |-- list.tpl(模板文件list)
| |--- service
| | |--- index.js
├── config
│ └── config.default.js
└── package.json

其他有关Egg相关的文章下篇待续,继续来了解下egg相关的知识点。

查看github上的源码

Egg入门学习(二)---理解service作用的更多相关文章

  1. Egg入门学习(三)---理解中间件作用

    Egg是基于koa的,因此Egg的中间件和Koa的中间件是类似的.都是基于洋葱圈模型的. 在Egg中,比如我想禁用某些IP地址来访问我们的网页的时候,在egg.js中我们可以使用中间件来实现这个功能, ...

  2. ReactJS入门学习二

    ReactJS入门学习二 阅读目录 React的背景和基本原理 理解React.render() 什么是JSX? 为什么要使用JSX? JSX的语法 如何在JSX中如何使用事件 如何在JSX中如何使用 ...

  3. SpringMVC入门学习(二)

    SpringMVC入门学习(二) ssm框架 springMVC  在上一篇博客中,我简单介绍了一下SpringMVC的环境配置,和简单的使用,今天我们将进一步的学习下Springmvc的操作. mo ...

  4. git入门学习(二):新建分支/上传代码/删除分支

    一.git新建分支,上传代码到新的不同分支  我要实现的效果,即是多个内容的平行分支:这样做的主要目的是方便统一管理属于同一个内容的不同的项目,互不干扰.如图所示: 前提是我的github上已经有we ...

  5. Egg入门学习(一)

    一:什么是Egg? 它能做什么?Egg.js是nodejs的一个框架,它是基于koa框架的基础之上的上层框架,它继承了koa的,它可以帮助开发人员提高开发效率和维护成本.Egg约定了一些规则,在开发中 ...

  6. dubbo入门学习(二)-----dubbo hello world

    一.dubbo hello world入门示例 1.提出需求 某个电商系统,订单服务需要调用用户服务获取某个用户的所有地址: 我们现在需要创建两个服务模块进行测试: 模块 功能 订单服务web模块 创 ...

  7. iOS中 Swift初级入门学习(二)

    // Copyright (c) 2015年 韩俊强. All rights reserved. // import Foundation /* // 控制语句 // for - in // 遍历字符 ...

  8. node入门学习(二)

    一.模块系统 1.创建模块和引用模块 //如何创建一个模块 exports.hello = function(){ console.log('hello worl'); }; //这创建了一个模块 / ...

  9. 第15.21节 PyQt(Python+Qt)入门学习:QListView的作用及属性详解

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 一.概述 QListView是从QAbstractItemView 派生的类,实现了QAbstrac ...

随机推荐

  1. web框架的本质

    一 web框架的本质及自定义web框架 我们可以这样理解:所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端,基于请求做出响应,客户都先请求,服务端做出对应的响 ...

  2. 浅谈运维中的安全问题-FTP篇

    写这一系列文章的动因很简单,在年前最后一个项目的时候在客户现场做了的几个安全加固.由于时间问题,很多东西就拿来主义没经过思考直接更改了,并未细细品味其中的原理和方法,所以特地搭建实验环境,分析下其中的 ...

  3. Android SurfaceView实现跟随手指移动的光标

    实例 public class DragSurfaceView extends SurfaceView implements SurfaceHolder.Callback,Runnable{ priv ...

  4. 生产者、消费者模型---Queue类

    Queue队列在几乎每种编程语言都会有,python的列表隐藏的一个特点就是一个后进先出(LIFO)队列.而本文所讨论的Queue是python标准库queue中的一个类.它的原理与列表相似,但是先进 ...

  5. (后端)swagger

    Swagger 文档提供了一个方法,使我们可以用指定的 JSON 或者 YAML 摘要来描述你的 API,包括了比如 names.order 等 API 信息. 你可以通过一个文本编辑器来编辑 Swa ...

  6. recovery&linux系统升级数据更新分析总结

    先说说对升级的理解吧.系统升级是软件更新及BUG修复的主要方式,升级的主要原理就是数据搬移的过程,把我们需要的数据,从某个地方,更新到另外的一个地方.这个过程就叫做升级.一般是当我们系统有了新的功能增 ...

  7. C# 代码中调用 Javascript 代码段以提高应用程序的配置灵活性(使用 Javascript .NET 与 Jint)

    一般来说,我们需要在开发应用软件的配置文件中,添加一些参数,用于后续用户根据实际情况,自行调整. 配置参数,可以放在配置文件中.环境变量中.或数据库表中(如果使用了数据库的话).通常,配置数据,以 k ...

  8. The concurrent snapshot for publication 'xxx' is not available because it has not been fully generated or the Log Reader Agent is not running to activate it

    在两台测试服务器部署了复制(发布订阅)后,发现订阅的表一直没有同步过来.重新生成过snapshot ,也重新初始化过订阅,都不能同步数据,后面检查Distributor To Subscriber H ...

  9. 任意activity作为启动页

    在androidManifest.xml中对应的activity中添加 android:exported=“true”

  10. sql server 通用修改表数据存储过程

    ALTER PROC [dbo].[UpdateTableData] ), ), ), ), ) AS BEGIN ) SET @sql ='UPDATE '+@TableName; --获取SqlS ...