关于node.js:koa实战

我的项目初始化

// 初始化我的项目,生成package.json
npm init

指定入口文件 main.js

我的项目的根底搭建

创立 src 工作目录

创立 main.js 主入口文件

在 main.js 中引入 koa

const koa = require('koa')
const app = new Koa()

app.listen(3000, () => {
  // 监听胜利的回调
  console.log('server is running:http://localhost:3000')
})

node main.js 后即可通过拜访 http://localhost:3000,拜访到此我的项目

我的项目的根本优化

主动重启

// 热更新,只在开发模式下才会用的到
npm install nodemon -D

这时候咱们装置的 nodemon 会在 package.json 中的 devDependencies 下

批改 script 选项

"scripts":{
   "dev":"nodemon ./src/main.js"
}

应用 nodemon 启动,开发过程中的改变会主动重启

配置文件

咱们开发的过程中还须要辨别环境,开发、正式、测试等

// 装置dotenv
npm install dotenv -S

在我的项目的根目录下创立.env文件

尽可能早的在我的项目中引入 dotenv 并进行环境变量的配置

const dotenv = require('dotenv')
dotenv.config()
// 通过了下面的配置,咱们在.env文件中所配置的环境变量就曾经被加载进process.env中了
// 能够将环境变量导出,在须要用到的时候进行引入
module.exports = process.env

这样咱们就在我的项目中配置了环境变量,配置环境变量还有另外一种形式,就是在 package.json 中的 script 中配置执行的命令,并指定环境变量,这样咱们就不必新开一个文件在 js 文件中援用了

增加路由

// 这是一个构造函数
const Router = require('koa-router')
const router = new Router({ prefix: '/user' })

router.post('/register', (ctx, next) => {})

通过引入 koa 的路由中间件 koa-router,咱们能够设置我的项目的路由,通过在构造函数中传入prefix:'/user'能够设置路由的前缀,以作为不同功能模块的辨别

目录构造的划分

咱们在 main.js 中引入了 koa 启动了服务

又在 main.js 中引入了 koa-router 设置了我的项目的路由

然而随着性能的逐步增多,我的项目变大,咱们不能把所有的货色都写在 main.js 中,咱们须要做功能模块的辨别

抽离路由

在 src 目录下新建 router 文件夹,这个文件夹专门寄存并治理我的项目中的路由。

  • 如果须要新增 user 的路由,就新建 user.route.js 文件
  • 如果须要新增 order 的路由,就新建 order.route.js 文件

    const Router = require('koa-router')
    const router = new Router({ prefix: '/order' })
    router.post('/add', addOrderController)
    
    module.exports = router
    

新建好了各个功能模块的路由,咱们须要一个index.js文件来作为路由的总入口文件,它负责引入各个功能模块的路由

const Router = require('koa-router')
const router = new Router()
const fs = require('fs')

// 须要应用nodejs的fs模块,来进行文件的读取和引入
fs.readdirSync(__dirname).forEach((file) => {
  // 读取当前目录下的文件['user.route.js','order.route.js']
  if (file !== 'index.js') {
    const currentFile = require('./' + file)
    // 注册路由
    router.use(currentFile.routes())
  }
})

module.exports = router

这样咱们所有的路由都注册在了index.js中的总路由中,咱们只须要在main.js中注册在 app 上,就能够实现路由的性能

const router = require('./router') // 引入index.js能够不必写

app.use(router.routes())
// 这个是路由做的http容许的申请办法解决,如果不写这条语句,那么在应用别的httpMethod申请时,会抛出500的谬误,加上了这一句,在申请办法不当的时候,会进行提醒
app.use(router.allowedMethods())

抽离 app 服务

咱们须要在 src 底下新建一个 app 文件夹专门治理咱们的服务,因为有时候咱们可能在一个我的项目中应用多个服务。可能是 express、可能是 koa、也可能是 node 中 http 模块

  • 在 src 下新建 app 目录
  • 在 app 目录下新建一个index.js文件,这个文件用于编写咱们当初这个我的项目中次要用的服务,例如 koa,其余的服务可新开文件编写。
const koa = require('koa')
const app = new Koa()
const router = require('./router')

app.use(router.routes())
app.use(router.allowedMethods())

// 注册号路由之后将app导出
module.exports = app

这样咱们将服务抽离进去,在main.js中进行引入的时候,将 app 服务引入并监听即可

const app = require('./app/index.js')

app.listen(3000, () => {
  // 监听胜利的回调
  console.log('server is running:http://localhost:3000')
})

这样main.js就变的更加简洁了

抽离 controller

咱们在 user.route.js 中写下了这样的代码

router.post('/register', (ctx, next) => {})
// (ctx,next)=>{} 这个是用来解决register逻辑的函数,咱们能够把它抽离成一个controller,专门用于解决各个拜访的逻辑
  • 在 src 目录下新建一个文件夹 controller
  • 在 controller 文件夹下新建一个文件,叫做 user.controller.js
  • 在 controller 文件夹下新建一个文件,叫做 order.controller.js
class UserController {
  async registerUser() {
    // 这里解决register的逻辑
  }
  async loginUser() {
    // 这里解决login的逻辑
  }
}

module.exports = new UserController()

抽离进去之后在路由文件中引入相应的 controller

抽离 service

咱们在 controller 中要进行数据库的操作,咱们把操作数据库的这一部分,抽离为 service

  • 在 src 文件夹下创立 service 文件夹
  • 在 service 文件夹下创立 user.service.js
class UserService {
  // 解决创立用户的service
  async createUser() {
    // 这个外部封装了数据库的操作
  }

  // 解决更新用户的service
  async updateUser() {}
}

抽离数据库定义

sequelize这个包专门用于我的项目中解决关系型数据库的操作,它是基于 promise 的

咱们须要借助它来对数据库进行操作

  • npm install sequelize -S先装置
  • 在 src 下新建一个 db 目录用于治理此我的项目须要连贯的数据库
  • 在 db 目录下新建一个 seq.js
const { Sequelize } = require('sequelize')
const { HOST, PORT /*等等须要的配置*/ } = process.env

// 实例化sequelize对象
const seq = new Sequelize(
  '要连贯的数据库名称',
  '数据路的用户名',
  '数据库的明码',
  {
    // options
    host: '要连贯的数据库的host',
    port: '要连贯的数据路的端口',
    dialect: 'mysql', // 要操作的数据库类型
  }
)

// 实例化过后就进行连贯
seq
  .authenticate()
  .then((res) => {
    console.log(res, '连贯胜利的回调')
  })
  .catch((err) => {
    console.log(err, '连贯失败的回调')
  })

module.exports = seq

抽离 model

连贯好了数据库之后,咱们须要定义数据库表,这时候须要抽离一个 model 层,来定义数据库的表构造

  • 在 src 下新建一个 model 目录
  • 在 model 中新建一个 user.model.js,进行如下定义:
  • 参考nodejs进阶视频解说:进入学习
const seq = require('../db/seq')

// 创立User表,表名为user,user中有各项字段
const User = seq.define('user', {
    {
      userName:{
       type:DataTypes.String,// DataTypes是Sequelize中为咱们提供的类型,须要引入
       allowNull:false, // 是否容许空值,参考Sequelize文档
       unique:true,// 是否容许惟一
       comment:'字段正文',
    },
    {
      password:{
        type:DataTypes.String,
        allowNull:false,
        //...
      }
    }
  }
})

module.exports = User

当咱们定义好了 User 的 model 之后,就能够在 user.service.js 中引入并应用它

const User = require('../model/user.model.js')

class UserService {
  // 解决创立用户的service
  async createUser(userName, password) {
    // 这个外部封装了数据库的操作,都是基于promise,须要进行try...catch谬误捕捉
    const res = User.create({ userName, password })
    return res.dataValues
  }

  // 解决更新用户的service
  async updateUser() {}
}

module.exports = new UserService()

抽离中间件

当咱们实现了这一系列的操作之后,就搭建起了一个接口编写的框架。咱们能够在每一个 controller 中编写对应的业务解决。然而在咱们编写接口的过程中,时常会碰到雷同或类似的解决模块,这时候咱们为了防止反复冗余的代码,须要把这些雷同或类似的性能抽离成中间件。

  • 在 src 下新建一个文件夹叫做 middleware
  • 在 middleware 中新建一个文件叫做 user.middleware.js
const validateUserInfo = (ctx, next) => {
  // 这里能够填充用户登录或注册时的校验办法
}

const comparePassword = (ctx, next) => {
  // 这里能够填充批改明码时,两个明码进行比照的办法
}

module.exports = {
  validateUserInfo,
  comparePassword,
}

抽离错误处理

Sequelize 是基于 promise 的数据库操作工具,咱们在进行数据库操作或者日常代码编写的时候要进行错误处理,将错误处理的这一部分抽离进去,也会不便咱们排查问题。

  • 在 src 下新建一个 constant 问价夹,专门用来寄存代码中须要用到的常量
  • 在 constant 文件夹下新建一个 err.type.js 用来存储返回给前端的谬误提醒
module.exports = {
  UserValidError = {
    code:'10001',
    message:'用户校验失败',
    result:''
  },
  UserLoginError = {
    code:'10002',
    message:'用户登录失败',
    result:''
  }
}

将谬误归集起来了之后,咱们只须要在捕捉到这个谬误的时候应用它

// ctx中提供了以后的app,其中有一个emit的办法,能够传递一个事件,前面为该事件须要的参数
ctx.app.emit('error', UserValidError, ctx)
// 在app中应用on作为接管
app.on('error', errHandler)
// errHandler
module.exports = (error, ctx) => {
  // 这里的error就是UserValidError
  // ctx 就是传递过去的ctx上下文
  ctx.body = error // 把以后谬误返回给前端
}

至此咱们就实现了我的项目中的性能拆分,接下来就是在每个模块中填充相应的内容

注册接口的编写

注册的逻辑个别为用户提供用户名明码,传递给后端,后端拿到用户名和明码当前,首先要判断数据库中是否曾经存在此用户,如果曾经存在了这个用户,就返回提醒码并告知前端,此用户曾经注册。如果不存在,则对用户传递过去的明码进行加密,而后存储到数据库中。

这里只记录加密接口的步骤

// 应用bcryptjs
const bcrypt = require('bcrypt')

const cryptPassword = async (ctx, next) => {
  const { password } = ctx.request.body
  const salt = bcrypt.genSaltSync(10) // 加盐
  const hash = bcrypt.hashSync(password, salt)
  ctx.request.body.password = hash
  await next()
}

通过此步骤加密之后,咱们就能够往下解决注册是逻辑

  • 从 request.body 中取出加密之后的明码
  • 存储用户名和明码至数据库
  • 向用户返回后果

登录接口的编写

登录的逻辑个别为,用户输出用户名和明码进行登录。咱们拿取到用户名和明码之后,要和数据库中的用户名和明码进行比拟,如果比拟失败,则返回用户失败的后果,否则登录胜利,胜利之后须要下发 token 以及 cookie 等。

这里只记录明码比照和 token 下发的步骤

// 明码比照
bcrypt.compareSync('以后明码', '用户传递过去的明码') // 如果雷同返回true,如果不同返回false

// 下发token  须要用到jsonwebtoken这个库
// npm install jsonwebtoken -S
const jwt = require('jsonwebtoken')
// 从数据库中拿取出数据之后,除了明码以外,将其它的信息都用于token下发,也能够用作userInfo返回
const { password, ...res } = await getUserInfo({ userName })
// 那么这个res就是咱们下发token须要用到
const token = jwt.sign(res, '本人设置的加密串', {
  expiresIn: '1d' /*token的无效工夫*/,
})
ctx.body = {
  code: 200,
  message: '登录胜利',
  result: {
    token,
  },
}

这样咱们就实现了登录的流程,将 token 下发给用户之后,用户当前的资源申请都须要将 token 携带过去,咱们进行验证,如果验证胜利,那么能够进行后续的操作,如果验证失败,那么用户就不能获取咱们的实在资源。

验证中间件的编写

因为咱们下发 token 之后的每一个接口都要通过验证之后能力向下进行,所以咱们须要编写一个验证 token 的中间件

  • 在 middleware 这个文件加下创立 auth.middleware.js 文件
const jwt = require('jsonwebtoken')

const auth = (ctx, next) => {
  // 这里编写验证token的相干内容
  const { authorization } = ctx.request.header
  const token = authorization.replace('Bearer ', '')

  try {
    // 如果通过验证,会把咱们先前用来生成token的信息返回,这里也就是除了password之外的其余用户信息
    const user = jwt.verify(token, '咱们先前设置的加密串')
    ctx.state.user = user // 咱们把通过验证的用户信息放入state属性下的user中
  } catch (error) {
    // 如果没有通过验证,那么有几种状况
    // error.name === TokenExpiredError
    // error.name === JsonWebTokenError
    // 详情参考jsonwebtoken这个库的介绍
  }
}

验证的中间件编写实现之后,咱们的每一次须要验证 token 的申请,都会应用到它

数据上传

编写接口的同时咱们要解决前端传递过去的数据,那么在 koa 中,数据上传须要用到一个中间件,就是koa-body

  • npm install koa-body -S 装置依赖
  • 在路由注册之前先注册koa-body
const KoaBody = require('koa-body')

app.use(
  KoaBody({
    // ...options
  })
)

// 注册路由
app.use(router.routes())

因为通过 koa-body 的解决,前端传递过去的申请数据会挂在ctx.request.body 上,咱们在后续的路由解决中,在此处获取并解决即可

koa-body 有很多选项(比方是否反对文件上传等),具体参考手册

动态资源管理

如果想要前端通过浏览器的 uri 拜访到本服务的动态资源,那么须要进行动态资源配置

须要应用到koa-static

  • npm install koa-static -S
const koaStatic = require('koa-static')

app.use(koaStatic('动态资源门路,最好借助path模块'))

通过了这样的动态资源配置,前端就能够在浏览器上输出 uri 来拜访到本服务的动态资源

sequelize 的根本了解

模型 model 时 sequelize 的实质,是数据库中表的形象,在 sequelize 中是一个类

比如说,咱们要创立一个用户表,那么首先须要定义一个 User 类,这个 User 类就是 sequelize 的模型。表中的每一条数据都是一个对象,每一个对象都是这个类的实例。而咱们对 User 类的操作,或者是对实例(表中的每一条数据)的操作,都是相似操作 js 对象一样思维。有了这样的意识,能够帮忙咱们更好的了解 sequelize 的各项操作。

sequelize 文档

增删改查

做完后面的一些根底工作之后,最常见也是最常常写的就是 CRUD 了

新增接口

第一步:定义路由,恪守 restfull 标准,定义为router.post('/order','中间件1','中间件2')

第二步:在controller中定义解决该路由的中间件

第三步:在service中定义写入数据库的办法,如果这一步须要用到新的 model,则先在model中定义好数据字段

//须要借助sequelize来进行数据库操作
// 先把User模型给引进来
const User = require('../model/user.model.js')
// 新增操作须要在User表中新增一条数据,从类的角度来说,就是创立一个实例
// 假如咱们此时是在/src/service/user.service.js
class UserService {
  // 创立一个用于解决User model的类
  async addUser({ id, userName }) {
    // ID,userName是从controller中解析的
    // 模型中有一个创立的办法
    // 办法一:
    const res = await User.create({ id, userName })
    // 在没有谬误的状况下,执行结束这个操作,就会在user表中新增一条数据
    // 办法二:
    // sequelize的model为咱们提供了创立实例的办法build
    const res = User.build({ id, userName })
    // 然而此时的build的办法,仅仅是创立出的一个对象,示意能够将这个对象映射到数据库中的数据,这个对象还并未真正的保留在数据库中,咱们应该应用save办法,将其同步
    return res.save()
    // 执行完这一步才算是真正的同步至了数据库中
    // 倡议间接应用create办法,具体操作详见sequelize官网文档
  }
}
module.exports = new UserService()

第四步:留神谬误捕捉与错误处理

批改接口

第一步:定义路由,定义为router.put('/order/:id','中间件','中间件')

第二步:在controller中定义解决该路由的中间件

第三步:在 service 中定义批改数据库的办法

// 批改接口同新增接口
// 假如咱们此时在/src/service/user.service.js
class UserService {
// 新增用户的接口
async addUser(){}
// 更新用户的接口
async updateUser({id,userName}){
 // sequelize中为咱们提供的更新办法也有两种
 // 办法一:
 const res = await User.update({userName},{
   where:{
     id,
     userName
   }
 })
 // 办法二:
 const res = await User.create({id,userName})
 res.set({
   userName:'xxx'
 })
 return awaut res.save()
}
}

第四步:留神谬误捕捉与错误处理

删除接口

删除首先要确定是应用硬删除,还是软删除。这二者的区别为硬删除为间接从数据库中的记录抹去,软删除为在数据库中减少一个标识字段,该字段标记了就代表删除了,但不是真正意义上的删除。

第一步:定义路由,定义为router.delete('/order/:id','中间件','中间件','中间件')

第二步:在controller中定义解决该路由的中间件

第三步:在service中定义删除该数据的办法,此时能够抉择硬删除,或者是软删除,详见sequelize文档

查问接口

查问接口的思路同上

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理