在上一篇文章 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. elementUI 设置input的只读或禁用

    只读:readonly 在data里定义:readonly: true, 然后在input框里加上readonly就可以了. 禁用:disabled 在data里定义:edit: true, 然后在i ...

  2. 利用Syslog Watcher在windows下部署syslog日志服务器

    1.概述 syslog协议是各种网络设备.服务器支持的网络日志记录标准.Syslog消息提供有关网络事件和错误的信息.系统管理员使用Syslog进行网络管理和安全审核. 通过专用的syslog服务器和 ...

  3. 折半插入排序算法的C++实现

    折半插入排序思想和直接插入排序类似. 1)找到插入位置: 2)依次后移正确位置及后面的元素. 区别是查找插入位置的方法不同. 折半插入排序使用的折半查找法在一个已经有序的序列中找到查找位置. 注意,折 ...

  4. Linux 新磁盘分区与挂载

      1.查看未分区的盘   2.新建分区   3.格式化分区(/dev/sdb1)   4.查看磁盘uuid [root@web-node1 ~]# blkid /dev/vdb1 /dev/vdb1 ...

  5. Python 多线程并发程序设计与分析

    多线程并发程序设计与分析 by:授客 QQ:1033553122 1.技术难点分析与总结 难点1:线程运行时,运行顺序不固定 难点2:同一段代码,再不加锁的情况下,可能被多个线程同时执行,这会造成很多 ...

  6. day15(PYTHON)推导式{生成器,字典,列表,集合}

    #[每一个元素或者是和元素相关的操作 for 元素 in 可迭代数据类型] #遍历之后挨个处理 #[满足条件的元素相关的操作 for 元素 in 可迭代数据类型 if 元素相关的条件] #筛选功能 # ...

  7. 编程经验点滴----巧妙解决 Oracle NClob 读写问题

    最近一个新项目中,尝试在 Oracle 数据库中使用 NCLOB 来保存大的 xml 字符串. 在代码自动生成工具(通过 JDBC 驱动程序,读数据库表结构,自动生成对应的 java 代码,包含增加. ...

  8. Android 官方DEMO BasicNetworking

    本示例演示如何使用Android API检查网络连接. Demo下载地址:https://github.com/googlesamples/android-BasicNetworking/#readm ...

  9. Wampserver或者帝国CMS安装后, 打开localhost显示IIS欢迎界面图片

    我们在安装集成环境Wampserver或者帝国CMS之后,有时会遇到一个问题, 打开localhost显示一张IIS欢迎界面图片,这个问题该如何解决呢,我在这里简单整理了一下解决方法 电脑win10系 ...

  10. MySQL 授予普通用户PROCESS权限

    在MySQL中如何给普通用户授予查看所有用户线程/连接的权限,当然,默认情况下show processlist是可以查看当前用户的线程/连接的. mysql> grant process on ...