关于node.js:koa实战

2次阅读

共计 9165 个字符,预计需要花费 23 分钟才能阅读完成。

我的项目初始化

// 初始化我的项目,生成 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 文档

查问接口

查问接口的思路同上

正文完
 0