zl程序教程

您现在的位置是:首页 >  其他

当前栏目

node-koa 框架 项目搭建 ?

2023-02-25 18:16:33 时间

这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战

代码[链接?]:(https://gitee.com/yang-yiming1234/koa/tree/master) 持续更新~

简介

Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。

官网地址

初始化项目

生成package.json

 npm init

安装 koa

npm install koa

新建入口文件

新建src文件夹 新建一个mian.js(叫什么都可以比如index.js)

const Koa = require('koa')

const app = new Koa()

app.use((ctx, next)=>{
    ctx.body = 'hello'
})
const port = process.env.PORT || 8000
app.listen(port,()=>{
    console.log(`server is running on ${port}`)
})

node src/main.js启,在浏览器访问端口,

自动启动配置文件 nodemon

安装

npm i nodemon

添加启动命令

package.json dev替换成别的也可以

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "nodemon ./src/main.js"
  },

之后在控制台执行命令 npm run dev 就可以启动了。并且当我们改动代码,他也会自动的重启。

读取配置文件

安装 dotenv

npm i dotenv

在根目录下新建.env文件(存一些项目配置的环境变量)

然后新建 src/config/config.default.js

const dotenv = require('dotenv')

dotenv.config()
console.log(process.env.APP_PORT)
// process node的进程 
// env 环境变量
module.exports = process.env

main.js中引用

const Koa = require('koa')
// 解构出 APP_PORT
const { APP_PORT } = require('./config/config.default')
const app = new Koa()

app.use((ctx, next) => {
    ctx.body = 'hello api'
})
app.listen(APP_PORT, () => {
    console.log(`server is running on ${APP_PORT}`)
})

添加路由配置

安装 koa-router

npm i koa-router

看一下 koa-router 文档

首先去npmjs的官网查看 ,好巧不巧 ,没有写。 npmjs

不要 紧的 我们再看看github上有没有 ✈️

使用步骤:

  1. 导入包 引入koa-router
const Router = require('koa-router')
  1. 实例化对象
const indexRouter =  new Router()
  1. 编写路由
indexRouter.get('/',(ctx, next)=>{
    ctx.body = 'hello index'
})
  1. 注册中间件
  • 把router和app关联起来,通过router.routes()方法去注册中间键。
  • router 是一个对象,但是我们app.use()只能接收一个函数作为参数。// 必须是一个函数 app.use(indexRouter) app.use(indexRouter.routes())

抽离出路由文件

假如我们有一类uers的路由,我们需要新实例化一个 UserRouter,还有之前的IndexRouter。都写到main.js文件中并不是很好的写法。

新建router文件夹用于存放各种api

router/user.route.js

const Router = require('koa-router')

const router = new Router({prefix:'/users'})

// 会和 /users拼接
router.get('/',(ctx,next)=>{
    ctx.body = 'hello users'
})
// 导出
module.exports = router

main.js

const Koa = require('koa')
const { APP_PORT } = require('./config/config.default')
const userRouter = require('./router/user.route')
const app = new Koa()

// 必须是一个函数
app.use(userRouter.routes())

app.listen(APP_PORT, () => {
    console.log(`server is running on ${APP_PORT}`)
})

目录结构优化

目的:将http服务和app业务分开

新建app文件,在其下面新建index.js 将mian.js中的实例化对象和使用router的都抽离出来

const Koa = require('koa')
const userRouter = require('./router/user.route')
const app = new Koa()

// 必须是一个函数
app.use(userRouter.routes())

module.exports = app

main.js

const { APP_PORT } = require('./config/config.default')

const app = require('./app/index')
app.listen(APP_PORT, () => {
    console.log(`server is running on ${APP_PORT}`)
})

然后我们发现报错了,因为把main.js中的代码直接剪切到index.js中 涉及到了文件引用的路径层级改变的问题。

const userRouter = require('../router/user.route')

抽离出controller

route中的处理函数这块拆分出来

新建controller文件夹,在其下面新建

user.controller.js

// 写成一个类
class UserController{
    async register(ctx, next){
      ctx.body  = '用户注册成功'
    }
}

// 导出实例化的对象
module.exports = new UserController()

user.route.js

const Router = require('koa-router')
// 引入controller
const { register } = require('../controller/user.controller')
const router = new Router({prefix:'/users'})

// 注册接口
router.post('/register',register)
// 导出
module.exports = router

使用postman测试一下

koa-body

这里可以跳到其官网: koa-body

翻译一下:

一个完全特征的 koa 中间件,支持文件上传、form表单、json格式。提供了像express的bodyParser的相同功能。用于解析数据

安装

npm i koa-body

注册中间件 /app/index.js 中添加

const KoaBody = require('koa-body')
// 在注册路由前注册
app.use(KoaBody())

添加后完整的

const KoaBody = require('koa-body')
const userRouter = require('../router/user.route')
const app = new Koa()

// 在注册路由前注册
app.use(KoaBody())
// 必须是一个函数
app.use(userRouter.routes())

module.exports = app

解析请求数据

改写controller user.controller.js

//注意 createUser是异步函数 抽离出的service层
const { createUser } = require('../service/user.service')
class UserController {
    async register(ctx, next) {
        // 1.获取数据
        console.log(ctx.request.body)
        const { usesr_name, password } = ctx.request.body
        // 2.操作数据库
        const res = await createUser(usesr_name, password)
        console.log(res)
        // 3.返回给客户端
        ctx.body = ctx.request.body
    }
    async login(ctx, next) {
        ctx.body = '用户登录'
    }
}

// 导出实例化的对象
module.exports = new UserController()

抽离servcie层

用于操作数据库处理 创建 /src/service/user.service.js

class UserService {
    // 因为存入数据库 是异步的
    async createUser(user_name,password){
        return '写入数据库'
    }
}
// 导出后 在控制器controller中使用
module.exports = new UserService()

搞下数据库

什么是Sequelize?

官网

Sequelize 是一个基于 promise 的 Node.js ORM, 目前支持 PostgresMySQLMariaDBSQLite 以及 Microsoft SQL Server. 它具有强大的事务支持, 关联关系, 预读和延迟加载,读取复制等功能。

什么是ORM?

对象关系映射

  • 数据表映射(对应)一个类
  • 数据表中的数据行(记录)对应一个对象
  • 数据表字段对应对象的属性
  • 数据表的操作对应对象的方法 也就是用面向对象的方法去操作数据库。

安装两个包

可以两个一起下载

npm i mysql2 sequelize

连接到数据库

新建 db/seq.js

const { Sequelize } = require('sequelize')
/**
 * database
 * username
 * password
 */
const seq = new Sequelize('mine', 'root', 'root', {
    host: 'localhost',
    // 数据库类型
    dialect: 'mysql'
})
// 返回值是一个promise对象
seq.authenticate().then(() => {
    console.log('数据库连接成功')
}).catch(err => {
    console.log('数据库连接失败', err)
})

module.exports = seq

可以使用node命令node src/db/seq.js 试一下连接是否成功

我们应该把链接数据库的配置变量放到.env中(不知道是什么翻看上一篇) .env

APP_PORT = 8000
MYSQL_HOST = localhost
MYSQL_PORT = 3306
MYSQL_USER = root
MYSQL_PWD = root
MYSQL_DB = mine

然后再使用node命令node src/db/seq.js 试一下连接是否成功

创建模型

模型是要和表对应的,建立一个映射关系。 我们还是需要使用sequelize,这是它官网关于model的 ?介绍

// 解构出
const { Sequelize, Model, DataTypes } = require("sequelize");
// 创建的实例   参数是数据库的类型
const sequelize = new Sequelize("sqlite::memory:");
// 创建模型 define:定义      第一个参数是数据库的表名         
const User = sequelize.define("user", {
  // 表的字段名 DataTypes.数据类型
  name: DataTypes.TEXT,
  // 如果是多项配置,比如还要加表的默认值 就要写成对象
  favoriteColor: {
    type: DataTypes.TEXT,
    defaultValue: 'green'
  },
  age: DataTypes.INTEGER,
  cash: DataTypes.INTEGER
},{
// 这是其他模型参数 
sequelize, // 我们需要传递连接实例 
modelName: 'User' // 我们需要选择模型名称
});

(async () => {
  // 执行创建数据库
  await sequelize.sync({ force: true });
  // 这里是代码
})();

sequelize.define的三个参数

  • 表名 但是有时候它会在表前加前缀,可以在第三个对象参数中添加属性modelName: 'User'
  • 数据model,是一个对象。 首先,说一下常用的数据类型,这是针对【MySQL】数据库的。
// 字符串
DataTypes.STRING // VARCHAR(255)
DataTypes.STRING(1234) // VARCHAR(1234)
DataTypes.STRING.BINARY // VARCHAR BINARY
DataTypes.TEXT // TEXTDataTypes.TEXT('tiny') // TINYTEXT
// 布尔
DataTypes.BOOLEAN // TINYINT(1)
// 数值
DataTypes.INTEGER // INTEGER
DataTypes.BIGINT // BIGINT
DataTypes.BIGINT(11) // BIGINT(11) 
DataTypes.FLOAT // FLOAT
DataTypes.FLOAT(11) // FLOAT(11)
DataTypes.FLOAT(11, 10) // FLOAT(11,10) 
DataTypes.DOUBLE // DOUBLE
DataTypes.DOUBLE(11) // DOUBLE(11)
DataTypes.DOUBLE(11, 10) // DOUBLE(11,10) 
DataTypes.DECIMAL // DECIMAL
DataTypes.DECIMAL(10, 2) // DECIMAL(10,2)
// 日期
DataTypes.DATE // DATETIME 适用于 mysql / sqlite, 带时区的TIMESTAMP 适用于 postgresDataTypes.DATE(6) // DATETIME(6) 适用于 mysql 5.6.4+. 支持6位精度的小数秒DataTypes.DATEONLY // 不带时间的 DATE
// UUID 
{ type: DataTypes.UUID, defaultValue: Sequelize.UUIDV4 // 或 Sequelize.UUIDV1}

然后再看一些常用参数

// 是否为空
allowNull: false, 
// 默认值
defaultValue: true
// 主键
primaryKey: true
// 自动增长
autoIncrement: true
  • 一些数据库的配置 可选的 对象 如:
{
 timestamps: false // 默认会为数据表 创建时间戳字段,如果添加次属性 则在创建时不添加时间戳字段
 modelName: 'User' // 我们需要选择模型名称
}
 

可以看到多了两个字段,是时间戳。

模型同步

定义模型时,你要告诉 Sequelize 有关数据库中表的一些信息. 但是,如果该表实际上不存在于数据库中怎么办? 如果存在,但具有不同的列,较少的列或任何其他差异,该怎么办?

这就是模型同步的来源.可以通过调用一个异步函数(返回一个Promise)model.sync(options). 通过此调用,Sequelize 将自动对数据库执行 SQL 查询. 请注意,这仅更改数据库中的表,而不更改 JavaScript 端的模型.

  • User.sync() - 如果表不存在,则创建该表(如果已经存在,则不执行任何操作)
  • User.sync({ force: true }) - 将创建表,如果表已经存在,则将其首先删除
  • User.sync({ alter: true }) - 这将检查数据库中表的当前状态(它具有哪些列,它们的数据类型等),然后在表中进行必要的更改以使其与模型匹配.编写我们的model 新建model文件夹 在其下面新建 user.model.js

我们的model对象User中一共定义了三个字段user_name、password、is_admin(sequelize创建表会自动创建id)

// 解构出sequelize的DataTypes
const { DataTypes } = require('sequelize')
// 这是我们创建好的 连接数据库的
const seq = require('../db/seq')

// 创建模型   可以给表加前缀因为其自动化推断表名称,也可以让他不推断
const User = seq.define('User', {
    // id 自动创建
    user_name: {
        // 去问档查看
        type: DataTypes.STRING,
        // 约束是否为空
        allowNull: false,
        // 唯一
        unique: true,
        comment: '用户名 唯一'
    },
    password: {
        type: DataTypes.CHAR(64),
        allowNull: false,
        comment: '密码'
    },
    is_admin: {
        // boolean 就是 tinity(1)
        type: DataTypes.BOOLEAN,
        allowNull: false,
        defaultValue: 0,
        comment: '是否为管理员 0不是管理员'
    }
})
// force如果之前存在这张表 会删了重建 文档:模型重建  用过后要注释掉
// User.sync({ force: true })

module.exports = User

创建表

在终端中执行命令

node src/model/user.model.js

其实是执行了一个建表语句

去看一下我们新建成的数据库

操作数据库

我们已经抽离出了 service层,用于写操作数据库的逻辑。

注册接口的实现

先插进去

user.service.js

// 引入我们写好的model
const User = require('../model/user.model')
class UserService {
    // 因为存入数据库 是异步的
    async createUser(user_name,password)
        // 当我们属性名和传过来的值一致的时候可以简写
        // await表达式: 返回成功promise对象的值
       const res = await User.create({user_name, password})
       console.log(res)
       //    返回到controller
       return res
    }
}
// 导出后 在控制器controller中使用
module.exports = new UserService()
  • sequelize中的create()方法 就相当于我们执行sqlINSERT INTO Users(id,name) VALUES (?,?)
  • 因为是存入数据库的操作,所以是异步执行的。所以用到了 async await
  • 当属性名和传过来的参数的属性名一致的时候可以简写
   User.create({
          user_name: user_name,
          password: password
    })
    
    User.create({user_name,password})
  • 最后把这个res再返回到controller层。

controller 那么controller负责什么呐?

路由找到对应的controller后,接收传来的参数,然后调用我们的service层。

//注意 createUser是异步函数
const { createUser, getUserInfo } = require('../service/user.service')
class UserController {
    async register(ctx, next) {
        // 1.获取数据   注意一定传的要是JSON结构才能对request.body解构
         const { user_name, password } = ctx.request.body
        // 2.操作数据库 将参数传到service层 
         const res = await createUser(user_name, password)
         console.log(res)
        // 3.返回给客户端
            ctx.body = {
                code: 0,
                messgae: '用户注册成功',
                user_name: res.user_name
            }
        
    }
}
// 导出实例化的对象
module.exports = new UserController()

做到这里我们可以在postman里试一试

成功插入后会在控制台自动打印

我也打印了一下service中的res,可以看到它是这么样的一个对象

那么这就做好了吗?当然没有,根本没考虑到用户存在的情况。

错误处理

一些小问题

再插试试

再一次插入相同的值,100%会报错

Internal Server Error服务器内部错误。

插入空值

正常来说,没填写用户名肯定是不能存到数据库中的,这又是一个问题

少传一个参数

Internal Server Error服务器内部错误。

因此 我们需要做一些处理。

校验

合法性

首先是合法性,什么是合法 就是传过来的参数不为空。我们需要判断传过来的参数是否为空,那么写在哪个文件里?先写到controller。 解构出参数,判断它们如果为空,返回状态码 400,并在body中返回code码(自己定义),和错误信息。

        const { user_name, password } = ctx.request.body
        // 合法性
        if (!user_name || !password) {
            console.log('用户名或密码为空', ctx.request.body)
            ctx.status = 400
            ctx.body = {
                code: '10001',
                messgae: '用户名或密码为空',
                result: ''
            }
        }

合理性

如果我们已经有了这个用户名的存在,就不可以再注册了。

那这里其实就需要走一个查询接口,看数据库中是否存在这个用户名的用户

在service中写一个新方法

user.service.js

  • User.findOne()查找一条
// 先把参数预留出来 虽然我们这次只需根据user_name查找
 async getUserInfo({id,user_name,password,is_admin}){
        const whereOpt = {}
        // 短路运算
        id && Object.assign(whereOpt,{ id })  // 不为空则添加到whereOpt中
        user_name && Object.assign(whereOpt,{ user_name })
        password && Object.assign(whereOpt,{ password })
        is_admin && Object.assign(whereOpt,{ is_admin })

        const res = await User.findOne({
            attributes:['id', 'user_name', 'password','is_admin'],
            where : whereOpt
        })
        // 可以看一下上面res 的结构
        return res ? res.dataValues : null
    }

controller controller中再新增一个判断,如果返回的这个对象不为空。则说明数据库中存在这个用户。注意调用getUserInfo()需要加await关键字(异步)

 else if (await getUserInfo({ user_name })) {
            ctx.status = 409
            ctx.body = {
                code: '10002',
                messgae: '用户已存在',
                result: ''
            }
        }

controller完整代码

//注意 createUser是异步函数
const { createUser, getUserInfo } = require('../service/user.service')
class UserController {
    async register(ctx, next) {
        // 1.获取数据
        console.log(ctx.request.body)
        const { user_name, password } = ctx.request.body
        // 合法性
        if (!user_name || !password) {
            console.log('用户名或密码为空', ctx.request.body)
            ctx.status = 400
            ctx.body = {
                code: '10001',
                messgae: '用户名或密码为空',
                result: ''
            }
        }
        // 合理性
        else if (await getUserInfo({ user_name })) {
            ctx.status = 409
            ctx.body = {
                code: '10002',
                messgae: '用户已存在',
                result: ''
            }
        }
        else {
            // 2.操作数据库
            console.log(user_name, password, "user_name, password")
            const res = await createUser(user_name, password)
            console.log(res)
            // 3.返回给客户端
            ctx.body = {
                code: 0,
                messgae: '用户注册成功',
                user_name: res.user_name
            }
        }
    }
    async login(ctx, next) {
        ctx.body = '用户登录'
    }
}

// 导出实例化的对象
module.exports = new UserController()

service完整代码

const User = require('../model/user.model')
class UserService {
    // 因为存入数据库 是异步的
    async createUser(user_name,password){
        console.log(user_name,password,"service")
    // 对象
        // User.create({
        //     user_name: user_name,
        //     password: password
        // })
        // 当我们属性名和传过来的值一致的时候可以简写
        // await表达式: 返回成功promise对象的值
       const res = await User.create({user_name, password})
       console.log(res,"res")
    //    返回到controller
       return res
    }
    async getUserInfo({id,user_name,password,is_admin}){
        const whereOpt = {}
        // 短路运算
        id && Object.assign(whereOpt,{ id })  // 不为空则添加到whereOpt中
        user_name && Object.assign(whereOpt,{ user_name })
        password && Object.assign(whereOpt,{ password })
        is_admin && Object.assign(whereOpt,{ is_admin })

        const res = await User.findOne({
            attributes:['id', 'user_name', 'password','is_admin'],
            where : whereOpt
        })
        return res ? res.dataValues : null
    }
}
// 导出后 在控制器controller中使用
module.exports = new UserService()

抽离成中间件

我们的校验都写在了controller中,我们想将这部分抽离出来,抽离成中间件

 // 合法性
        if (!user_name || !password) {
            console.log('用户名或密码为空', ctx.request.body)
            ctx.status = 400
            ctx.body = {
                code: '10001',
                messgae: '用户名或密码为空',
                result: ''
            }
        }
        // 合理性
        else if (await getUserInfo({ user_name })) {
            ctx.status = 409
            ctx.body = {
                code: '10002',
                messgae: '用户已存在',
                result: ''
            }
        }

将用户或密码为空抽离出来

新建文件夹 middleware/user.middle.js

编写函数再把函数导出

const userValidator = async(ctx,next)=>{
    const {user_name, password} = ctx.request.body
      // 合法性
      if (!user_name || !password) {
        console.log('用户名或密码为空', ctx.request.body)
        ctx.status = 400
        ctx.body = {
            code: '10001',
            messgae: '用户名或密码为空',
            result: ''
        }
        // 如果用户名或密码为空 就返回
       return
    }
    // 否则放行向下执行
    await next()
} 
module.exports = {
    userValidator,
}

导出了这个中间件,我们去哪使用那? 去router中

const Router = require('koa-router')
const { register, login } = require('../controller/user.controller')
// 引入中间件
const  {userValidator} = require('../middleware/user.middleware')
const router = new Router({prefix:'/users'})

// 注册接口 先交给userValidator去验证,验证通过再交由register
router.post('/register',userValidator,register)
// 登录接口
router.post('/login', login)
// 导出
module.exports = router

然后测试一下

将用户是否存在抽离出来

middleware/user.middle.js 新增一个方法,并且它用到了service的getUserInfo(),所以需要引入service。最后不要忘记导出

const { getUserInfo }  = require('../service/user.service')
const verifyUser = async (ctx, next) => {
    // 合理性
    const { user_name } = ctx.request.body
    if (await getUserInfo({ user_name })) {
        ctx.status = 409
        ctx.body = {
            code: '10002',
            messgae: '用户已存在',
            result: ''
        }
        return
    }
    await next()
}
module.exports = {
    userValidator,verifyUser
}

然后去我们的route文件

const Router = require('koa-router')
const { register, login } = require('../controller/user.controller')
const  {userValidator,verifyUser} = require('../middleware/user.middleware')
const router = new Router({prefix:'/users'})

// 注册接口 先交给userValidator 和 verifyUser去验证,验证通过再交由register
router.post('/register',userValidator,verifyUser,register)

// 登录接口
router.post('/login', login)
// 导出
module.exports = router

再次测试一下

将错误信息抽离出来

新建一个constant常量文件夹

err.type.js

module.exports = {
    userFormateError:{
        code : '10001',
        message : '用户名或密码为空',
        result : ''
    },
    userAlreadyExisted:{
        code : '10002',
        message : '用户已存在',
        result : ''
    }
}

然后我们的中间件中 user.middleware.js 再去引入这个 错误信息

const { userFormateError, userAlreadyExisted } = require('../constant/err.type')

将 ctx.body 替换成 ctx.app.emit 方法

        ctx.status = 400
        ctx.body = {
            code: '10001',
            messgae: '用户名或密码为空',
            result: ''
        }
        替换为
        
        // 封装了错误信息 ctx.app.emit在提示错误信息时使用
        ctx.app.emit('error', userFormateError, ctx)

替换后为

const { getUserInfo } = require('../service/user.service')
const { userFormateError, userAlreadyExisted } = require('../constant/err.type')
const userValidator = async (ctx, next) => {
    const { user_name, password } = ctx.request.body
    // 合法性
    if (!user_name || !password) {
        console.log('用户名或密码为空', ctx.request.body)
        
        // 封装了错误信息 ctx.app.emit在提示错误信息时使用
        ctx.app.emit('error', userFormateError, ctx)
        return
    }
    await next()
}

const verifyUser = async (ctx, next) => {
    // 合理性
    const { user_name } = ctx.request.body
    const isExist = await getUserInfo({ user_name })
    if (isExist) {
        ctx.app.emit('error', userAlreadyExisted, ctx)
        return
    }
    await next()
}
module.exports = {
    userValidator, verifyUser
}

emit的错误事件 需要在我们的app的index.js文件中通过app.on进行监听

app.on('error',(err,ctx)=>{

})

刚才我们在user.middleware.js文件中 也将状态码也删掉了,那我们在这个地方再加上。 我们在app文件夹下新建一个errHandler.js 来处理状态码。

module.exports = (err,ctx)=>{
    let status =500
    switch (err.code){
        case '10001':
            status = 400
        break
        case '10002':
            status = 409
        break
        default:
            status = 500
    }
    ctx.status = status
    ctx.body = err
}

然后再在app/index.js中引入

const Koa = require('koa')
const KoaBody = require('koa-body')
const userRouter = require('../router/user.route')
const errHandler = require('./errHandler')
const app = new Koa()

// 在注册路由前注册
app.use(KoaBody())
// 必须是一个函数
app.use(userRouter.routes())

app.on('error', errHandler)
module.exports = app

密码加密

我们之前存入数据库中的密码是没有经过加密的。这篇文章我们对密码进行一下加密处理。其实如果要求不是很高的话,可以使用md5进行加密。

我们这里使用另一个库bycrptjs,? 是bycrptjs,在npmjs官网还有一个bycrpt,二者区别是bycrptjs进过了c++的编译。不需要其他的依赖,而bycrpt需要其他依赖。

最好对应项目代码进行学习:持续更新中

bycrptjs

npmjs中 bycrptjs 相关内容

安装

npm i bycrptjs

文档

可以看到它分为同步 和 异步。To hash a password:生成密码。To check a password:去验证这个生成的密码

这段生成密码的代码中 genSaltSync 方法,我们称它为 “加盐”。 它的加密方式大概是:

[密码:abc]-加上字符->[123abc]-通过算法加密一次->[具体变成什么样我们看不到]--再进行加盐--> 下面参数中的10就是进行了十次的加盐。

var bcrypt = require('bcryptjs');
var salt = bcrypt.genSaltSync(10);
var hash = bcrypt.hashSync("B4c0//", salt)

写到哪里?

按照我们的封装,在user.router.js中经过校验之后才会进入到register,因此我们想要加密就需要在进入register前,进行加密。我们将加密也写到 处理中间件的文件中 :user.middleware.js

user.route.js

const  {userValidator,verifyUser,cryptPassword} = require('../middleware/user.middleware')
// 注册接口 先交给userValidator去验证,验证通过再交由register
router.post('/register',userValidator,verifyUser,cryptPassword,register)

user.middleware.js

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()
}
// 导出
module.exports = {
    userValidator, verifyUser, cryptPassword
}

然后我们测试一下,注册一个新用户

可以看到数据库的相应数据第密码已经经过了加密处理

密码的解密我们在下一篇文章再说,也就是在登录时进行解密。

实现登录

接下来实现一下登录的验证。最好先把代码下下来再看,因为我们进行了封装处理。流程大概如下

首先,我们项目的目录结构如下

路由

进入到路由文件中 user.router.js。因为我们的项目结构,都是先进行校验后才会进入到login的controller中。所以在login的路由中新增 用户名和密码是否为空的校验 和 密码是否正确的校验。也就是 userValidator 和 verifyLogin。这两个方法我们写到中间件中。

const Router = require('koa-router')
const { register, login ,getTest} = require('../controller/user.controller')
const  {userValidator,verifyUser,cryptPassword,verifyLogin} = require('../middleware/user.middleware')
const router = new Router({prefix:'/users'})

// 注册接口 先交给userValidator去验证,验证通过再交由register
router.post('/register',userValidator,verifyUser,cryptPassword,register)

// 登录接口
router.post('/login',userValidator,verifyLogin,login)
router.get('/test', getTest),
// 导出
module.exports = router

中间件

middleware/user.middleware.js

用户密码是否为空(这是我们之前写注册就已经写好的)

const userValidator = async (ctx, next) => {
    const { user_name, password } = ctx.request.body
    // 合法性
    if (!user_name || !password) {
        console.log('用户名或密码为空', ctx.request.body)

        // 封装了错误信息 ctx.app.emit在提示错误信息时使用
        ctx.app.emit('error', userFormateError, ctx)
        return
    }
    await next()
}

用户是否存在 存在验证密码是否正确

其中bcrypt.compareSync传入两个密码作为参数,返回值为true/false。

还有具体的报错,我们写到了constant/err.type.js

const verifyLogin = async (ctx, next) => {
    // 1.判断用户是否存在 不存在报错
    const { user_name, password } = ctx.request.body
    try {
        const res = await getUserInfo({ user_name })
        if (!res) {
            console.error('用户不存在', { user_name })
            ctx.app.emit('error', userUnExist, ctx)
            return
        }
        // 2.密码是否匹配 不匹配报错
        // compareSync 返回值是 true false
        if (!bcrypt.compareSync(password, res.password)) {
             ctx.app.emit('error', invalidPassword, ctx)
             return
        }
    } catch (err) {
        console.error(err)
        return ctx.app.emit('error', userLoginError)
    }

    await next()
}

错误提示

constant/err.type.js

     userUnExist:{
        code:'10004',
        message:'用户不存在',
        result:''
    },
    userLoginError:{
        code:'10005',
        message:'用户登录失败',
        result:''
    },
    invalidPassword:{
        code:'10006',
        message:'密码不匹配',
        result:''
    }

controller中

因为我们的错误处理都已经写过了,并且只有错误处理都通过了,我们才会进入到login的controller中。所以只需要在这里写成功的即可

  async login(ctx, next) {
        const {user_name} = ctx.request.body
        ctx.body = `欢迎回来${user_name}`
    }