大前端连载(8) – 实现一个API接口(下)

微信扫一扫,分享到朋友圈

大前端连载(8) – 实现一个API接口(下)

本文属于“前端solo”系列文章第八篇


导语
:本文主要讲述egg.js中 处理业务逻辑的Service
以及 操作数据库的egg-sequelize插件
的使用。

连载的博客风格可能会作调整,具体原因会在公众号文章中暗示。我的博客不会讲的太深入,仅仅作入门的指示,个人风格,仅此而已!

前文浅谈了egg.js中的 Router
Controller
,又写了几个不成熟的接口,仅为说明egg具有接口能力。实际开发中,业务逻辑并不会如此简单,通常要跟数据库打交道,我们并不想在controller里把所有事情做完,所以我们把业务逻辑拎出来放到一个专门的地方,这就是今天要讲的 “Service”

Egg中的“Service”

什么是Service

egg官方文档中,一言蔽之:

Service 就是在复杂业务场景下用于做业务逻辑封装的一个抽象层

人话就是:一块专门写业务逻辑的地方。

了解后端mvc对egg的学习是非常有帮助的,我曾经学习过 PHP
,学习 egg
的时候对其设计模式几乎触类旁通。建议百度一下后端的mvc,或者看下

egg.js

官方文档中关于Router, Controller,Service的讲解也是极好的。

为了帮助理解 egg
service
的作用,做了一张粗粒度的egg.js开发 流程图
[1]
,省略了目前热身项目中没有涉及到的环节,渐进式博客就是这样子的,基于当前开发进度一次解决一个问题。

egg请求处理流程

Service层的意义

如流程图所示,业务逻辑是放在Service中进行处理的,抽象出Service层来处理业务逻辑的好处如下:

  • 保持 Controller 中的逻辑简洁;

  • 保持业务逻辑的独立性,抽象出来的 Service 可以被多个 Controller 重复调用;

  • 将逻辑和展现分离,更容易编写测试用例;

不是说controller中无法处理业务逻辑,而是我们希望它目的单一且明确,只做一件事,那就是

解析用户请求调用service处理后返回相应结果

,就像下面代码中这样:

class UserController extends Controller {
  // 新增用户
  async create(){
    // 1.获取请求参数 user(Object) 
    const { ctx } = this;
    const user = ctx.request.body;
    // 2.验证数据完整性,合法性,根据需要组装传递给service处理
    const legal = await validate(user);
    if(!legal) return;
    // 3.调用service处理
    const response = await ctx.service.user.create(user);
    // 4.返回新增用户结果
    ctx.body = response;
  }
}
复制代码

我们能看到controller承担的事务还是比较多的,如果把业务逻辑也放在这里写,那就是一锅乱炖,不方便开发和维护。

Service与数据库交互的方法

如前面所说,实际业务中经常需要跟数据库进行交互。在egg中,我们在service中处理业务逻辑,在service里,则借助专门的ORM(Object-Relational Mapping,把关系数据库的表结构映射到对象上)框架来帮助我们实现操作数据库的能力。

目前node生态中,最好的ORM框架是 sequelize
[2]
,egg团队对它进行了一些适用性封装,这便是 egg-sequelize插件
[3]
插件
[4]
在egg.js生态中扮演的角色,就像awesome-vue中的插件之于vue一样,有着相对独立的业务逻辑(功能),用来增强框架的能力。

如果决心使用egg去做全栈开发,


深入学习和实践sequelize是非常重要的


。下面简单讲一下egg-sequelize的使用方法!

使用egg-sequelize插件

1.安装MySQL

既然是要操作数据库,总得有个数据库把,egg中推荐使用MySQL ,请自行安装MySQL。为了方便管理数据库,请再安装一个数据库可视化管理工具,我使用的是MySQL-Front。

2.安装egg-sequelize插件

在egg项目中帮助我们操作数据库

npm install --save egg-sequelize mysql2 复制代码

3.项目中配置

  • 开启插件:

// config/plugin.js  
exports.sequelize = {
  enable: true,
  package: 'egg-sequelize',
};
复制代码
  • 配置数据库基本信息(基于热身项目)

// config/config.default.js中
  config.sequelize = {
    dialect: 'mysql',
    host: 'localhost',
    port: 3306,
    database: 'fesolo', //数据库名
    username: "root", //数据库用户名
    password: '123456' //数据库密码
  };
复制代码

如果不记得数据库信息了,可以回看一下之前文章中 数据库建表
那一节。

fesolo数据库信息

配置好了之后我们先启动mysql,再运行项目 npm run dev
。就可以在项目中使用egg-sequelize。

egg-sequelize是项目中深度使用的插件,后续会有更多篇幅讲述实际场景中的应用及优化,现在仅展示egg-sequelize的数据库交互能力。

完整开发一个接口

利用之前学习的router,controller,以及本文中service和egg-sequelize。我们先来实现一个的弱智版的接口:热身项目新增后台管理员接口。

1.定义Model

model
是干嘛的?model就是用来告诉 egg-sequelize
如何映射数据库表的,对应的就是数据库中表,字段及字段类型等的定义。让我们能在egg.js中以对象的方式操作数据库。

我们约定在 app/model/
目录下存放model,看一下之前建立的user表:

user表

sequelize.define()
方法用来定义表模型,在egg中我们按照如下方式来定义model。这样一来,这个Model就可以在 Controller
Service
中通过 app.model.User
或者 ctx.model.User
访问到了。

'use strict';

module.exports = app => {
  //数据类型 - 需要参考sequelize 官文 v5版本
  const { STRING, INTEGER, DATE } = app.Sequelize;

  // define() 第一个参数是表名 对应数据库中的表名是users,
  // 因为我数据库中的表明是user,这样有问题,所以在下方传入
  // 配置 tableName 用来指定正确的表名
  const User = app.model.define('user', {
    id: { type: INTEGER, primaryKey: true, autoIncrement: true },
    phone: INTEGER,
    password: STRING(60),
    create_time: DATE,
    update_time: DATE,
  }, {
    tableName:'user', //指定表名
    timestamps: false , //关闭自动添加时间戳功能
  })

  return User;
}
复制代码

建立好了model,接下来就去定义接口的路由规则了。

// app/router.js 中
//新增后台用户
router.post('/user/create', controller.home.createUser);
复制代码

编写controller中的createUser方法。

async createUser() {
    const { ctx } = this;
    const user = ctx.request.body;
    //跳过参数验证
    const res = await ctx.service.user.create(user);
    ctx.body = {
      data: res,
      code: 200,
      message: '新增用户接口测试'
    };
  };
复制代码

最后编写service,同样约定servcie文件全部放在 app/service
目录下 , 其中 create()
方法是 sequelize
自带的新增数据方法,按照规则调用即可。

// app/service文件夹 中
'use strict';
const Service = require('egg').Service;

class UserService extends Service {
  async create(user) {
    const res = await this.ctx.model.User.create(user)
    return res;
  }
}

module.exports = UserService;
复制代码

完成上诉步骤,我们利用 postman
测试下接口,得到如下返回:

再看看数据库有没有保存上这条数据,果然有的啊!就是手机号不对,这是因为我手机号建表时数据类型选错的原因,已经超出int的最大范围了。

结语
:手机号数据类型的设计是我故意而为之的,我想测试一下到底会不会有人真的在认真看博客的内容,不过考虑到讲数据库建表的第三篇文章距离现在第八篇文章只有一个礼拜的间隔,阅读量也才十几,我想应该也不能说明什么问题吧。

前面几年,我在csdn上写了一些提供解决方案的 博客
,几乎都是问题->答案这样简单粗暴编排方式,因为我知道日常开发很多人都是百度方案然后扫两眼就复制粘贴试试行不行,不行就再找下一篇博客复制粘贴。所以为了方便这样的cv工人,我的博客会把完整的代码粘贴出来,确保直接粘贴可用,甚至提供整个项目包下载。这样确实会给我带来很多的流量,很多常见问题百度出来的结果我都会排在靠前的名次。

不过随着IT行业已经变成了所有人都知道的热门行业,我看着后来者不断涌入,身边的朋友纷纷转行加入,再看着各大社区里关于程序员的话题的讨论以及技术社区的**、公众号标题党的出现,一些技术点被反反复复的写成引流文章,我便停止了博客更新。

现在我只想静静地打造自己的 漫岛
,沉淀技术。编程只是我的兴趣之一,我的很多时间花费在旅行,摄影(封面图我拍的),美食以及陪伴家人身上,所以夫唯不争,故天下莫能与之争。人生说长又短,能够做一点自己想做的事,而不是陷入我要成功,我要出人头地的执念里,或许就是这场新冠疫情带给我作为“武汉人”的一点感悟吧。

下一篇文章讲述: 实现一个接口(下)

个人号,不为赚钱,纯日常生活!

微信扫一扫,分享到朋友圈

大前端连载(8) – 实现一个API接口(下)

一个简单示例-刷新你对Vue2响应式原理的认知

上一篇

Semo 系列文章之五:谈谈 REPL

下一篇

你也可能喜欢

大前端连载(8) – 实现一个API接口(下)

长按储存图像,分享给朋友