KAO2 入门到熟练 看这一篇文章就够了

在这里插入图片描述

KOA 介绍

官网地址: https://koa.bootcss.com/

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

文章目录

  • KOA 介绍
  • 一. 项目的初始化
    • 1 npm 初始化
    • 2 git 初始化
  • 二. 搭建项目
    • 1 安装 Koa 框架
    • 2 编写最基本的 app
    • 3 测试
  • 三. 项目的基本优化
    • 1 自动重启服务
    • 2 读取配置文件
    • 3 洋葱模型
  • 四. 添加路由
    • 1 安装 koa-router
    • 2 编写路由
    • 3 改写 main.js
    • 4.ctx对象
  • 五. 目录结构优化
    • 1 将 http 服务和 app 业务拆分
    • 2 将路由和控制器拆分
  • 六. 解析 body
    • 1 安装 koa-body
    • 2 注册中间件
    • 3 解析请求数据
    • 4 拆分 service 层
    • 5 洋葱模型
  • 七. 集成 sequlize
    • 1 安装 sequelize
    • 2 连接数据库
    • 3 编写配置文件
  • 八. 创建 User 模型
    • 1 拆分 Model 层
  • 九. 添加用户入库
  • 十. 错误处理
  • 十一. 拆分中间件
    • 1 拆分中间件
    • 2 统一错误处理
    • 3 错误处理函数
  • 十二. 加密
    • 1 安装 bcryptjs
    • 2 编写加密中间件
    • 3 在 router 中使用
  • 十三. 登录验证
  • 十四. 用户的认证
    • 1 颁发 token
      • 1) 安装 jsonwebtoken
      • 2) 在控制器中改写 login 方法
      • 3) 定义私钥
    • 2 用户认证
      • 1) 创建 auth 中间件
      • 2) 改写 router
  • 十五 接口练习
  • 十六 koa集成 模板引擎
    • EJS 的使用
    • art-template

一. 项目的初始化

1 npm 初始化

npm init -y

生成package.json文件:

  • 记录项目的依赖

2 git 初始化

git init

生成’.git’隐藏文件夹, git 的本地仓库

二. 搭建项目

1 安装 Koa 框架

npm install koa

2 编写最基本的 app

创建src/main.js

const Koa = require('koa')const app = new Koa()app.use((ctx, next) => {ctx.body = 'hello world'
})app.listen(3000, () => {console.log('server is running on http://localhost:3000')
})

3 测试

在终端, 使用node src/main.js

三. 项目的基本优化

1 自动重启服务

安装 nodemon 工具

npm i nodemon -D

编写package.json脚本

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

执行npm run dev启动服务

2 读取配置文件

安装dotenv, 读取根目录中的.env文件, 将配置写到process.env

npm i dotenv

创建.env文件

APP_PORT=8000

创建src/config/config.default.js

const dotenv = require('dotenv')dotenv.config()// console.log(process.env.APP_PORT)module.exports = process.env

改写main.js

const Koa = require('koa')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 http://localhost:${APP_PORT}`)
})

3 洋葱模型

image.png

洋葱模型,就是将数据顺序传入到多个中间件中,让它们进行处理传递,并利用函数递归的特性,让我们可以在一个中间件内先执行前半部分逻辑,再执行之后的所有中间件的完整逻辑后,再掉转方向继续执行这个中间件的后半部分。

const Koa = require('koa');
const app = new Koa();// 中间件1
app.use((ctx, next) => {console.log("<<<1");next();console.log("1>>>");
});// 中间件 2 
app.use((ctx, next) => {console.log("<<<2");next();console.log("2>>>");
});// 中间件 3 
app.use((ctx, next) => {console.log("<<<3");next();console.log("3>>>");
});
app.listen(8000, '0.0.0.0', () => {console.log(`Server is starting`);
});

在 koa 中,中间件被 next() 方法分成了两部分。next() 方法上面部分会先执行,下面部门会在后续中间件执行全部结束之后再执行。可以通过下图直观看出:

  • 输出的结果
<<<1
<<<2
<<<3
3>>>
2>>>
1>>>
const Koa = require('koa');
const app = new Koa();// 中间件1
app.use(async(ctx, next) => {console.log("<<<1");await next();console.log("1>>>");
});// 中间件 2 
app.use(async(ctx, next) => {console.log("<<<2");const axios= require('axios')    const res= await axios.get('http://www.baidu.com')next();console.log("2>>>");
});// 中间件 3 
app.use((ctx, next) => {console.log("<<<3");next();console.log("3>>>");
});
app.listen(8000, '0.0.0.0', () => {console.log(`Server is starting`);
});

四. 添加路由

路由: 根据不同的 URL, 调用对应处理函数

1 安装 koa-router

npm i koa-router

步骤:

  1. 导入包
  2. 实例化对象
  3. 编写路由
  4. 注册中间件

2 编写路由

创建src/router目录, 编写user.route.js

const Router = require('koa-router')const router = new Router({ prefix: '/users' })// GET /users/
router.get('/', (ctx, next) => {ctx.body = 'hello users'
})module.exports = router

3 改写 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 http://localhost:${APP_PORT}`)
})
  • mock 返回
router.get('/api/data', async (ctx) => {// 查询成功返回数据const data = {message: '查询成功',value: 'xx数据',};ctx.body = data;
});router.post('/api/data', async (ctx) => {// 创建数据成功const data = {message: '创建成功',value: 'xx数据',};ctx.body = data;
});router.put('/api/data', async (ctx) => {// 修改成功const data = {message: '修改成功',value: 'xx数据',};ctx.body = data;
});router.delete('/api/data', async (ctx) => {// 删除成功const data = {message: '删除成功',value: 'xx数据',};ctx.body = data;
});
  • 链式调用
router.get('/api/data', async (ctx) => {ctx.body = {message: '查询成功',value: 'xx数据',};}).post('/api/data', async (ctx) => {ctx.body = {message: '创建成功',value: 'xx数据',};}).put('/api/data', async (ctx) => {ctx.body = {message: '修改成功',value: 'xx数据',};}).delete('/api/data', async (ctx) => {ctx.body = {message: '删除成功',value: 'xx数据',};});
// 定义处理数据的函数
const handleData = (ctx, method, message) => {const data = {message,value: 'xx数据',};ctx.body = data;
};// 定义路由处理
router.get('/api/data', async (ctx) => {handleData(ctx, '查询成功', 'xx数据');}).post('/api/data', async (ctx) => {handleData(ctx, '创建成功', 'xx数据');}).put('/api/data', async (ctx) => {handleData(ctx, '修改成功', 'xx数据');}).delete('/api/data', async (ctx) => {handleData(ctx, '删除成功', 'xx数据');});

4.ctx对象

在 Koa 框架中,ctxcontext 的简写。context 是 Koa 内部提供的一个全局变量,它是一个带有请求和响应信息的上下文对象。ctx 对象封装了 context,提供了访问请求和响应的属性和方法的方便方式。

通过 ctx 对象,你可以访问 ctx.requestctx.responsectx.cookies 等属性来处理请求、响应和操作 cookie 数据。例如,ctx.request.query 用于获取查询参数,ctx.response.body 用于设置响应体内容,ctx.cookies.set() 用于设置 cookie 数据等。

因此,可以将 ctx 理解为上下文对象 context 的实例化对象,它提供了更简洁和易用的方式来访问和操作请求和响应的相关信息。通过使用 ctx,你可以在 Koa 应用程序的中间件中轻松地处理和操作请求和响应的数据。

  1. ctx.request:用于处理请求相关的属性和方法。
router.get('/api/data', async (ctx) => {// 获取请求参数const queryParam = ctx.request.query; // 获取查询参数const bodyParam = ctx.request.body; // 获取请求体参数// 设置响应状态码ctx.response.status = 200;// 设置响应类型ctx.response.type = 'application/json';// 设置响应头ctx.response.set('X-Custom-Header', 'Custom Value');// 发送响应ctx.response.body = { message: '请求成功' };
});
  1. ctx.response:用于处理响应相关的属性和方法。
router.post('/api/data', async (ctx, next) => {// 获取请求体参数const bodyParam = ctx.request.body;// 设置响应状态码ctx.response.status = 201;// 设置响应类型ctx.response.type = 'application/json';// 设置响应头ctx.response.set('X-Custom-Header', 'Custom Value');// 发送响应ctx.response.body = { message: '创建成功', data: bodyParam };await next();
});
  1. ctx.cookies:用于操作 cookie。
router.get('/api/cookie', async (ctx) => {// 设置 cookiectx.cookies.set('name', 'value', {httpOnly: true,maxAge: 86400000, // 设置过期时间为一天});// 获取 cookieconst name = ctx.cookies.get('name');ctx.body = { cookie: name };
});

通过这些属性,你可以方便地处理请求的参数、设置响应的状态码、类型、头部信息以及操作 cookie 数据。以上是一些简单的示例,实际使用中可能还会涉及更多的属性和方法,具体的使用取决于你的业务需求。

const Koa = require('koa');
const bodyParser = require('koa-bodyparser');const app = new Koa();
app.use(bodyParser());app.use(async (ctx) => {if (ctx.url === '/api/data' && ctx.method === 'POST') {const bodyData = ctx.request.body;console.log(bodyData); // 获取请求体数据ctx.body = { message: '请求成功', data: bodyData };}
});app.listen(3000, () => {console.log('服务器启动,监听3000端口...');
});

ctx.response.body===ctx.body : 本质上是一个东西

五. 目录结构优化

1 将 http 服务和 app 业务拆分

创建src/app/index.js

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')app.listen(APP_PORT, () => {console.log(`server is running on http://localhost:${APP_PORT}`)
})

2 将路由和控制器拆分

路由: 解析 URL, 分布给控制器对应的方法

控制器: 处理不同的业务

改写user.route.js

const Router = require('koa-router')const { register, login } = require('../controller/user.controller')const router = new Router({ prefix: '/users' })// 注册接口
router.post('/register', register)// 登录接口
router.post('/login', login)module.exports = router

创建controller/user.controller.js

class UserController {async register(ctx, next) {ctx.body = '用户注册成功'}async login(ctx, next) {ctx.body = '登录成功'}
}module.exports = new UserController()

六. 解析 body

1 安装 koa-body

npm i koa-body

2 注册中间件

改写app/index.js

3 解析请求数据

改写user.controller.js文件

const { createUser } = require('../service/user.service')class UserController {async register(ctx, next) {// 1. 获取数据// console.log(ctx.request.body)const { user_name, password } = ctx.request.body// 2. 操作数据库const res = await createUser(user_name, password)// console.log(res)// 3. 返回结果ctx.body = ctx.request.body}async login(ctx, next) {ctx.body = '登录成功'}
}module.exports = new UserController()

4 拆分 service 层

service 层主要是做数据库处理

创建src/service/user.service.js

class UserService {async createUser(user_name, password) {// todo: 写入数据库return '写入数据库成功'}
}module.exports = new UserService()

5 洋葱模型

image.png

洋葱模型,就是将数据顺序传入到多个中间件中,让它们进行处理传递,并利用函数递归的特性,让我们可以在一个中间件内先执行前半部分逻辑,再执行之后的所有中间件的完整逻辑后,再掉转方向继续执行这个中间件的后半部分。

const Koa = require('koa');
const app = new Koa();// 中间件1
app.use((ctx, next) => {console.log("<<<1");next();console.log("1>>>");
});// 中间件 2 
app.use((ctx, next) => {console.log("<<<2");next();console.log("2>>>");
});// 中间件 3 
app.use((ctx, next) => {console.log("<<<3");next();console.log("3>>>");
});
app.listen(8000, '0.0.0.0', () => {console.log(`Server is starting`);
});

在 koa 中,中间件被 next() 方法分成了两部分。next() 方法上面部分会先执行,下面部门会在后续中间件执行全部结束之后再执行。可以通过下图直观看出:

  • 输出的结果
<<<1
<<<2
<<<3
3>>>
2>>>
1>>>
const Koa = require('koa');
const app = new Koa();// 中间件1
app.use(async(ctx, next) => {console.log("<<<1");await next();console.log("1>>>");
});// 中间件 2 
app.use(async(ctx, next) => {console.log("<<<2");const axios= require('axios')    const res= await axios.get('http://www.baidu.com')next();console.log("2>>>");
});// 中间件 3 
app.use((ctx, next) => {console.log("<<<3");next();console.log("3>>>");
});
app.listen(8000, '0.0.0.0', () => {console.log(`Server is starting`);
});

七. 集成 sequlize

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

sequelize ORM 数据库工具
当我们在开发 Node.js 应用程序时,经常需要与数据库进行交互来存储和检索数据。这时候,Sequelize就成了我们的得力助手。Sequelize是一个强大的ORM(对象关系映射)库,它帮助我们以更直观的方式操作数据库,而无需编写复杂的SQL查询语句。

简单来说,ORM是一种编程技术,让我们能够使用面向对象的方法来处理数据库。而Sequelize正是为此而生。它让我们可以用JavaScript代码定义模型和数据之间的关系,就像在创建类和对象一样简单。每个模型类对应数据库中的一张表,而实例则是表中的记录。

Sequelize的好处不胜枚举。首先,它支持多种流行的数据库,比如 PostgreSQL、MySQL、SQLite和MSSQL,让我们在选择数据库时更加灵活。其次,Sequelize允许我们在模型上设置验证规则,确保数据的合法性和完整性。这为我们避免了许多繁琐的数据验证工作,让数据操作更加安全可靠。

查询构建是Sequelize的又一个亮点。它提供了一套流畅的查询构建器,让我们可以轻松地创建复杂的数据库查询。不再需要手动编写冗长的SQL语句,Sequelize可以帮我们处理这些细节,让我们专注于业务逻辑的实现。

如果你担心数据库模式的变更会让开发变得混乱,别担心!Sequelize还支持数据库迁移,让我们可以轻松地管理数据库模式的更新和变更。这样,我们可以在应用程序的生命周期中,保持数据库结构的清晰和一致。

最后,Sequelize还提供了强大的关联功能,让我们可以轻松地定义模型之间的关联关系,比如一对一、一对多、多对多等。这样的话,我们可以轻松地进行跨表查询,从而更加高效地处理复杂的数据需求。

总的来说,Sequelize是一个非常强大且灵活的Node.js ORM库。它简化了数据库操作,提高了开发效率,并且让我们的代码更易于维护。无论是开发小型应用还是大型项目,Sequelize都能成为我们最好的朋友,让我们更加专注于业务逻辑的实现,而不用为繁杂的数据库操作而烦恼。

能让我们以面向对象的方式去操作数据库

ORM: 对象关系映射

  • 数据表映射(对应)一个类
  • 数据表中的数据行(记录)对应一个对象
  • 数据表字段对应对象的属性
  • 数据表的操作对应对象的方法

1 安装 sequelize

npm i mysql2 sequelize

2 连接数据库

src/db/seq.js

const { Sequelize } = require('sequelize')const {MYSQL_HOST,MYSQL_PORT,MYSQL_USER,MYSQL_PWD,MYSQL_DB,
} = require('../config/config.default')const seq = new Sequelize(MYSQL_DB, MYSQL_USER, MYSQL_PWD, {host: MYSQL_HOST,dialect: 'mysql',
})seq.authenticate().then(() => {console.log('数据库连接成功')}).catch((err) => {console.log('数据库连接失败', err)})module.exports = seq

3 编写配置文件

  • 第一种方式
const dotenv = require('dotenv')dotenv.config()
// process.env 根据目录识别的
// ../config/config_default.js
// module.export= {key:value} 第一种方式
module.exports = {MYSQL_HOST: 'localhost',MYSQL_PORT: '3306',MYSQL_USER: 'root',MYSQL_PWD: 'root',MYSQL_DB: 'auth',APP_PORT:'3000'
};// module.exports=process.env
  • 第二种方式
const dotenv = require('dotenv')dotenv.config()
// process.env 根据目录识别的
// ../config/config_default.js
// module.export= {key:value} 第一种方式
// module.exports = {
//     MYSQL_HOST: 'localhost',
//     MYSQL_PORT: '3306',
//     MYSQL_USER: 'root',
//     MYSQL_PWD: 'root',
//     MYSQL_DB: 'auth',
//     APP_PORT:'3000'
//   };module.exports=process.env
APP_PORT=3001MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PWD=root
MYSQL_DB=auth

八. 创建 User 模型

1 拆分 Model 层

sequelize 主要通过 Model 对应数据表

创建src/model/user.model.js

const { DataTypes } = require('sequelize');
const seq = require('../db/db_sequ');const User = seq.define('zd_user', {user_name: {type: DataTypes.STRING,allowNull: false,},password: {type: DataTypes.STRING,allowNull: false,},is_admin: {type: DataTypes.INTEGER,defaultValue: 0,},
}, {tableName: 'zd_user',timestamps: false,
});module.exports = User;
  • 对应sql
-- auth.zd_user definitionCREATE TABLE `zd_user` (`id` int(11) NOT NULL AUTO_INCREMENT,`user_name` varchar(255) NOT NULL,`password` varchar(255) NOT NULL,`is_admin` int(1) DEFAULT '0',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;INSERT INTO auth.zd_user
(id, user_name, password, is_admin)
VALUES(5, 'root', 'root123', 0);

九. 添加用户入库

所有数据库的操作都在 Service 层完成, Service 调用 Model 完成数据库操作

改写src/service/user.service.js

const User = require('../model/use.model')class UserService {async createUser(user_name, password) {// 插入数据// User.create({//   // 表的字段//   user_name: user_name,//   password: password// })// await表达式: promise对象的值const res = await User.create({ user_name, password })// console.log(res)return res.dataValues}
}module.exports = new UserService()

同时, 改写user.controller.js

const { createUser } = require('../service/user.service')class UserController {async register(ctx, next) {// 1. 获取数据// console.log(ctx.request.body)const { user_name, password } = ctx.request.body// 2. 操作数据库const res = await createUser(user_name, password)// console.log(res)// 3. 返回结果ctx.body = {code: 0,message: '用户注册成功',result: {id: res.id,user_name: res.user_name,},}}async login(ctx, next) {ctx.body = '登录成功'}
}module.exports = new UserController()

十. 错误处理

在控制器中, 对不同的错误进行处理, 返回不同的提示错误提示, 提高代码质量

const { createUser,getUerInfo } = require('../service/user.service')// 发起请求 -》 controller -》 service --》 model -》 操作数据 -》 
//使用了 sequelize 完成对数据的保存操作
class UserController {// 方法被 async 修饰了,调用处必须加上awaitasync register(ctx, next) {// 1. 获取数据// console.log(ctx.request.body)const { username, password } = ctx.request.body// 合法性if (!username || !password) {console.error('用户名或密码为空', ctx.request.body)ctx.status = 400ctx.body = {code: '10001',message: '用户名或密码为空',result: '',}return}// 合理性if (await getUerInfo({ user_name:username })) {ctx.status = 409ctx.body = {code: '10002',message: '用户已经存在',result: '',}return}// 2. 操作数据库const res = await createUser(username, password)// console.log(res)// 3. 返回结果ctx.body = {code: 0,message: '用户注册成功',result: {id: res.id,user_name: res.user_name,},}}async login(ctx, next) {ctx.body = '登录成功'}}module.exports = new UserController()

在 service 中封装函数

const User = require('../model/user.model')// Service 处理了业务逻辑
class UserService {async createUser(user_name, password) {const userData = { user_name, password };const res = await User.create(userData);return res;}// 查询 用户名是否存在, trueasync getUerInfo({ id, user_name, password, is_admin }) {const whereOpt = {} console.log('user_name:',user_name)id && Object.assign(whereOpt, { id })user_name && Object.assign(whereOpt, { user_name })// User.findOne 查询 用户名是否存在const res = await User.findOne({attributes: ['id', 'user_name', 'password', 'is_admin'],where: whereOpt,})return res ? res.dataValues : null}}module.exports = new UserService()

十一. 拆分中间件

为了使代码的逻辑更加清晰, 我们可以拆分一个中间件层, 封装多个中间件函数

1 拆分中间件

添加src/middleware/user.middleware.js

const { getUerInfo } = require('../service/user.service')
const { userFormateError, userAlreadyExited } = require('../constant/err.type')const userValidator = async (ctx, next) => {const { user_name, password } = ctx.request.body// 合法性if (!user_name || !password) {console.error('用户名或密码为空', ctx.request.body)// 抛出给下方报错的地方ctx.app.emit('error', userFormateError, ctx)return}await next()
}const verifyUser = async (ctx, next) => {const { user_name } = ctx.request.bodyif (getUerInfo({ user_name })) {// 抛出给报错的地方ctx.app.emit('error', userAlreadyExited, ctx)return}await next()
}module.exports = {userValidator,verifyUser,
}

2 统一错误处理

  • 在出错的地方使用ctx.app.emit提交错误
  • 在 app 中通过app.on监听

编写统一的错误定义文件

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

3 错误处理函数

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

改写app/index.js

const errHandler = require('./errHandler')
// 统一的错误处理
app.on('error', errHandler)
// 注册接口
router.post('/register', userValidator, verifyUser, register)

十二. 加密

bcryptjs 是Node.js中比较简洁好用的一款第三方加盐(salt)加密包,并且支持跨平台的特性,用于实现在Node环境下重要资源的加密。
由它加密的文件可在所有支持的操作系统和处理器上进行转移。它的口令必须是8至56个字符,并将在内部被转化为448位的密钥。

在将密码保存到数据库之前, 要对密码进行加密处理

123123abc (加盐) 加盐加密

1 安装 bcryptjs

npm i bcryptjs

2 编写加密中间件

const crpytPassword = async (ctx, next) => {const { password } = ctx.request.bodyconst salt = bcrypt.genSaltSync(10)// hash保存的是 密文const hash = bcrypt.hashSync(password, salt)ctx.request.body.password = hashawait next()
}
const verifyUser = async (ctx, next) => {const { username } = ctx.request.body// if (await getUerInfo({ user_name })) {//   ctx.app.emit('error', userAlreadyExited, ctx)//   return// }try {const res = await getUerInfo({ user_name:username })if (res) {console.error('用户名已经存在', { username })ctx.app.emit('error', userAlreadyExited, ctx)return}} catch (err) {console.error('获取用户信息错误', err)ctx.app.emit('error', userRegisterError, ctx)return}await next()
}

3 在 router 中使用

改写user.router.js

const Router = require('koa-router')const {userValidator,verifyUser,crpytPassword,
} = require('../middleware/user.middleware')
const { register, login } = require('../controller/user.controller')const router = new Router({ prefix: '/users' })// 注册接口
router.post('/register', userValidator, verifyUser, crpytPassword, register)// 登录接口
router.post('/login', login)module.exports = router

十三. 登录验证

流程:

  • 验证格式
  • 验证用户是否存在
  • 验证密码是否匹配

改写src/middleware/user.middleware.js

const bcrypt = require('bcryptjs')const { getUerInfo } = require('../service/user.service')
const {userFormateError,userAlreadyExited,userRegisterError,userDoesNotExist,userLoginError,invalidPassword,
} = require('../constant/err.type')const userValidator = async (ctx, next) => {const { user_name, password } = ctx.request.body// 合法性if (!user_name || !password) {console.error('用户名或密码为空', ctx.request.body)ctx.app.emit('error', userFormateError, ctx)return}await next()
}const verifyUser = async (ctx, next) => {const { user_name } = ctx.request.body// if (await getUerInfo({ user_name })) {//   ctx.app.emit('error', userAlreadyExited, ctx)//   return// }try {const res = await getUerInfo({ user_name })if (res) {console.error('用户名已经存在', { user_name })ctx.app.emit('error', userAlreadyExited, ctx)return}} catch (err) {console.error('获取用户信息错误', err)ctx.app.emit('error', userRegisterError, ctx)return}await next()
}const crpytPassword = async (ctx, next) => {const { password } = ctx.request.bodyconst salt = bcrypt.genSaltSync(10)// hash保存的是 密文const hash = bcrypt.hashSync(password, salt)ctx.request.body.password = hashawait next()
}const verifyLogin = async (ctx, next) => {// 1. 判断用户是否存在(不存在:报错)const { user_name, password } = ctx.request.bodytry {const res = await getUerInfo({ user_name })if (!res) {console.error('用户名不存在', { user_name })ctx.app.emit('error', userDoesNotExist, ctx)return}// 2. 密码是否匹配(不匹配: 报错)if (!bcrypt.compareSync(password, res.password)) {ctx.app.emit('error', invalidPassword, ctx)return}} catch (err) {console.error(err)return ctx.app.emit('error', userLoginError, ctx)}await next()
}module.exports = {userValidator,verifyUser,crpytPassword,verifyLogin,
}

定义错误类型

module.exports = {userFormateError: {code: '10001',message: '用户名或密码为空',result: '',},userAlreadyExited: {code: '10002',message: '用户已经存在',result: '',},userRegisterError: {code: '10003',message: '用户注册错误',result: '',},userDoesNotExist: {code: '10004',message: '用户不存在',result: '',},userLoginError: {code: '10005',message: '用户登录失败',result: '',},invalidPassword: {code: '10006',message: '密码不匹配',result: '',},
}

改写路由

// 登录接口
router.post('/login', userValidator, verifyLogin, login)

十四. 用户的认证

官方介绍:JSON Web Token(JWT)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间作为JSON对象安全地传输信息。此信息可以验证和信任,因为它是数字签名的。JWT可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对签名。
https://jwt.io/

登录成功后, 给用户颁发一个令牌 token, 用户在以后的每一次请求中携带这个令牌.
jwt: jsonwebtoken

  • header: 头部
  • payload: 载荷
  • signature: 签名

1 颁发 token

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

image.png

1) 安装 jsonwebtoken

npm i jsonwebtoken

2) 在控制器中改写 login 方法

async login(ctx, next) {const { user_name } = ctx.request.body// 1. 获取用户信息(在token的payload中, 记录id, user_name, is_admin)try {// 从返回结果对象中剔除password属性, 将剩下的属性放到res对象const { password, ...res } = await getUerInfo({ user_name })ctx.body = {code: 0,message: '用户登录成功',result: {token: jwt.sign(res, JWT_SECRET, { expiresIn: '1d' }),},}} catch (err) {console.error('用户登录失败', err)}
}

3) 定义私钥

.env定义

JWT_SECRET = xzd

2 用户认证

1) 创建 auth 中间件

const jwt = require('jsonwebtoken')const { JWT_SECRET } = require('../config/config.default')const { tokenExpiredError, invalidToken } = require('../constant/err.type')const auth = async (ctx, next) => {const { authorization } = ctx.request.headerconst token = authorization.replace('Bearer ', '')console.log(token)try {// user中包含了payload的信息(id, user_name, is_admin)const user = jwt.verify(token, JWT_SECRET)ctx.state.user = user} catch (err) {switch (err.name) {case 'TokenExpiredError':console.error('token已过期', err)return ctx.app.emit('error', tokenExpiredError, ctx)case 'JsonWebTokenError':console.error('无效的token', err)return ctx.app.emit('error', invalidToken, ctx)}}await next()
}module.exports = {auth,
}
  • 登录生成 toekn 后, 后续任意接口都需要携带 token 进行 访问 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2) 改写 router

  • 具体操作之前, 验签
// 修改密码接口
router.patch('/', auth, (ctx, next) => {console.log(ctx.state.user)ctx.body = '修改密码成功'
})

十五 接口练习

  • 安装依赖
{"name": "test","version": "1.0.0","main": "index.js","license": "MIT","dependencies": {"dotenv": "^16.3.1","koa": "^2.14.2","koa-router": "^11.0.2","mysql2": "^2.2.5","nodemon": "^3.0.1"},"scripts": {"dev": "nodemon ./main.js"}
}

数据库名称 test_spiritmark_db
数据库用户 admin_spiritmark
数据库密码 55726955c65bd84c
数据库地址 mysql.sqlpub.com:3306
注册邮箱 liuluchang@cathay-ins.com.cn
注意 密码只显示一次,请注意保存。
为保障数据库运行安全,密码严禁泄漏到公共环境!!!(如:csdn、zhihu、github等)发现即永久锁定。

  • 测试sql
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
age INT NOT NULL
);INSERT INTO users (name, age) VALUES
('John Doe', 25),
('Jane Smith', 30),
('Mike Johnson', 35);select * from users;
  • 启动应用
const Koa = require('koa');
const Router = require('koa-router');
const mysql = require('mysql2/promise');const app = new Koa();
const router = new Router();// 创建数据库连接池
const pool = mysql.createPool({host: 'mysql.sqlpub.com',user: 'admin_spiritmark',port:3306,password: '55726955c65bd84c',database: 'test_spiritmark_db',
});// 添加数据
router.post('/api', async (ctx) => {const { name, age } = ctx.request.body;try {const [result] = await pool.query('INSERT INTO users (name, age) VALUES (?, ?)', [name, age]);ctx.body = result;} catch (error) {ctx.status = 500;ctx.body = error.message;}
});// 删除数据
router.del('/api/:id', async (ctx) => {const { id } = ctx.params;try {const [result] = await pool.query('DELETE FROM users WHERE id = ?', [id]);ctx.body = result;} catch (error) {ctx.status = 500;ctx.body = error.message;}
});// 更新数据
router.put('/api/:id', async (ctx) => {const { id } = ctx.params;const { name, age } = ctx.request.body;try {const [result] = await pool.query('UPDATE users SET name = ?, age = ? WHERE id = ?', [name, age, id]);ctx.body = result;} catch (error) {ctx.status = 500;ctx.body = error.message;}
});// 查询数据
router.get('/api', async (ctx) => {const page = parseInt(ctx.query.page) || 1;const size = parseInt(ctx.query.size) || 10;const offset = (page - 1) * size;try {const [rows] = await pool.query('SELECT * FROM users LIMIT ?, ?', [offset, size]);ctx.body = rows;} catch (error) {ctx.status = 500;ctx.body = error.message;}
});// 启动应用程序
app.use(router.routes()).use(router.allowedMethods());const PORT = 3000;
app.listen(PORT, () => {console.log(`Server is running on port ${PORT}`);
});

image.png

  • 封装优化
const Koa = require('koa');
const Router = require('koa-router');
const mysql = require('mysql2/promise');const app = new Koa();
const router = new Router();// 创建数据库连接池
const createPool = () => {return mysql.createPool({host: 'mysql.sqlpub.com',user: 'admin_spiritmark',port:3306,password: '55726955c65bd84c',database: 'test_spiritmark_db',});
};// 添加数据
const addUser = async (name, age) => {const pool = createPool();try {const [result] = await pool.query('INSERT INTO users (name, age) VALUES (?, ?)', [name, age]);return result;} catch (error) {throw new Error(error.message);} finally {pool.end();}
};// 删除数据
const deleteUser = async (id) => {const pool = createPool();try {const [result] = await pool.query('DELETE FROM users WHERE id = ?', [id]);return result;} catch (error) {throw new Error(error.message);} finally {pool.end();}
};// 更新数据
const updateUser = async (id, name, age) => {const pool = createPool();try {const [result] = await pool.query('UPDATE users SET name = ?, age = ? WHERE id = ?', [name, age, id]);return result;} catch (error) {throw new Error(error.message);} finally {pool.end();}
};// 查询数据
const getUsers = async (page = 1, size = 10) => {const pool = createPool();const offset = (page - 1) * size;try {const [rows] = await pool.query('SELECT * FROM users LIMIT ?, ?', [offset, size]);return rows;} catch (error) {throw new Error(error.message);} finally {pool.end();}
};// 添加数据
router.post('/api', async (ctx) => {const { name, age } = ctx.request.body;try {const result = await addUser(name, age);ctx.body = result;} catch (error) {ctx.status = 500;ctx.body = error.message;}
});// 删除数据
router.del('/api/:id', async (ctx) => {const { id } = ctx.params;try {const result = await deleteUser(id);ctx.body = result;} catch (error) {ctx.status = 500;ctx.body = error.message;}
});// 更新数据
router.put('/api/:id', async (ctx) => {const { id } = ctx.params;const { name, age } = ctx.request.body;try {const result = await updateUser(id, name, age);ctx.body = result;} catch (error) {ctx.status = 500;ctx.body = error.message;}
});// 查询数据
router.get('/api', async (ctx) => {const page = parseInt(ctx.query.page) || 1;const size = parseInt(ctx.query.size) || 10;try {const rows = await getUsers(page, size);ctx.body = rows;} catch (error) {ctx.status = 500;ctx.body = error.message;}
});// 启动应用程序
app.use(router.routes()).use(router.allowedMethods());const PORT = 3000;
app.listen(PORT, () => {console.log(`Server is running on port ${PORT}`);
});

十六 koa集成 模板引擎

EJS 的使用

    1. 使用模板引擎
# 在项目中下载koa-views包
npm i koa-views
# 在项目中下载ejs包
npm i ejs
    1. 入口index.js
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
// 1. 引入koa-views
const views = require('koa-views');
// 2. 配置模板引擎中间件:views()第一个参数是视图模板所在的路径,第二个参数是应用ejs模板引擎
app.use(views('views', { extension: 'ejs' })); // 若这样配置,模板的后缀名是.ejs
//app.use(views('views', { map: { html: 'ejs' } })); // 若这样配置,模板的后缀名是.html// 我们需要在每个路由的render中都渲染一个公共的数据
// 写一个中间件配置公共信息
app.use(async (ctx, next) => {// 公共的数据要放在ctx.state中ctx.state = {userinfo: '张三',age: '18',}await next()
})router.get('/', async (ctx, next) => {let title = '你好ejs'// 3. 异步渲染模板await ctx.render('index.ejs', {// 绑定数据title: title})
})router.get('/news', async (ctx, next) => {let arr = ['111111', '22222', '444444']let content = "<h2>这是一个h2</h2>"let num = 123await ctx.render('news.ejs', {list: arr,content: content,num: num})
});app.use(router.routes());
app.use(router.allowedMethods());app.listen(3000, () => {console.log('starting at port 3000');
});
  • /views/index.ejs:
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>ejs模板引擎</title>
</head>
<body><%- include('./public/header.ejs') %>这是一个ejs模板引擎<h2><%=title%>-----<%=userinfo%></h2>
</body>
</html>
  • /views/news.ejs:
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>ejs数据绑定</title>
</head>
<body><h2>引入外部模块</h2><%- include('./public/header.ejs') %><h2>ejs循环渲染数据</h2><ul><%for(var i=0;i<list.length;i++){%><li><%=list[i]%></li><%}%></ul><h2>绑定html数据</h2><%-content%><h2>条件判断</h2><% if(num>24){ %>大于24<%} else{ %> 小于24<%} %><h2>公共数据</h2><%=userinfo%>
</body>
</html>

art-template

原始语法和ejs一模一样都用<% %>,标准语法用的是{{ }},这里代码示例用的是标准语法。

  • 安装所需包
npm install --save art-template
npm install --save koa-art-template
  • 入口index.js
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
const path = require('path');
// 1. 引入koa-art-template
const render = require('koa-art-template');
// 2. 配置art-template模板引擎
render(app, {root: path.join(__dirname, 'views'), // 视图的位置extname: '.html', // 后缀名debug: process.env.NODE_ENV !== 'production' // 是否开启调试模式
});// 我们需要在每个路由的render中都渲染一个公共的数据
// 写一个中间件配置公共信息
app.use(async (ctx, next) => {// 公共的数据要放在ctx.state中ctx.state = {userinfo: '张三',age: '18',}await next()
})router.get('/', async (ctx, next) => {let title = '你好koa-art-template'// 3. 异步渲染模板await ctx.render('index', {// 绑定数据title: title})
})router.get('/news', async (ctx, next) => {let arr = ['111111', '22222', '444444']let content = "<h2>这是一个h2</h2>"let num = 123await ctx.render('news', {list: arr,content: content,num: num})
});app.use(router.routes());
app.use(router.allowedMethods());app.listen(3000, () => {console.log('starting at port 3000');
});
  • /views/news.html:
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>数据绑定</title>
</head>
<body><h2>引入外部模块</h2>{{include './public/header.html'}}<h2>绑定数据</h2>{{list.name}}<h2>公共数据</h2>{{userinfo}}<h2>绑定html数据</h2>{{@list.h}}<h2>条件判断</h2>{{if num>20}}<span>大于20</span>{{else}}<span>小于20</span>{{/if}}<h2>循环渲染数据</h2><ul>{{each list.data}}<li>{{$index}}--{{$value}}</li>{{/each}}</ul>
</body>
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/187627.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

万字解析设计模式之策略模式、命令模式

一、策略模式 1.1概述 先看下面的图片&#xff0c;我们去旅游选择出行模式有很多种&#xff0c;可以骑自行车、可以坐汽车、可以坐火车、可以坐飞机。 策略模式&#xff08;Strategy Pattern&#xff09;是一个行为型设计模式&#xff0c;它定义了一组算法家族&#xff0c;分…

看Spring源码不得不会的@Enable模块驱动实现原理讲解

这篇文章我想和你聊一聊 spring的Enable模块驱动的实现原理。 在我们平时使用spring的过程中&#xff0c;如果想要加个定时任务的功能&#xff0c;那么就需要加注解EnableScheduling&#xff0c;如果想使用异步的功能&#xff0c;那就要加EnableScheduling注解&#xff0c;其实…

「盘点」界面控件DevExtreme UI v23.1中的API增强

DevExtreme拥有高性能的HTML5 / JavaScript小部件集合&#xff0c;使您可以利用现代Web开发堆栈&#xff08;包括React&#xff0c;Angular&#xff0c;ASP.NET Core&#xff0c;jQuery&#xff0c;Knockout等&#xff09;构建交互式的Web应用程序。从Angular和Reac&#xff0c…

【赠书第10期】从概念到现实:ChatGPT和Midjourney的设计之旅

文章目录 前言 1 ChatGPT的崛起 2 Midjourney的探索 3 技术创新的交汇 4 对未来的影响 5 结论 6 推荐图书 7 粉丝福利 前言 在过去的几年里&#xff0c;自然语言处理和聊天模型的领域取得了飞速的发展。ChatGPT 作为一个由OpenAI 开发的大型语言模型&#xff0c;以其强…

【opencv】计算机视觉基础知识

目录 前言 1、什么是计算机视觉 2、图片处理基础操作 2.1 图片处理&#xff1a;读入图像 2.2 图片处理&#xff1a;显示图像 2.3 图片处理&#xff1a;图像保存 3、图像处理入门基础 3.1 图像成像原理介绍 3.2 图像分类 3.2.1 二值图像 3.2.2灰度图像 3.2.3彩色图像…

算法与数据结构有区别吗?

算法和数据结构并不是同一件事。严格来说&#xff0c;它们并不是等效的。但是&#xff0c;我们通常在使用的时候会互换这两个术语。为了简便&#xff0c;后文我们会用数据结构这个术语来指代“数据结构及其所有相关的方法”。 有很多方法可以用来说明这两个术语之间的区别&…

企业软件手机app定制开发趋势|小程序网站搭建

企业软件手机app定制开发趋势|小程序网站搭建 随着移动互联网的快速发展和企业数字化转型的加速&#xff0c;企业软件手机App定制开发正成为一个新的趋势。这种趋势主要是由于企业对于手机App的需求增长以及现有的通用应用不能满足企业特定需求的情况下而产生的。 1.企业软件手…

​iOS Class Guard github用法、工作原理和安装详解及使用经验总结

iOS Class Guard是一个用于OC类、协议、属性和方法名混淆的命令行工具。它是class-dump的扩展。这个工具会生成一个symbol table&#xff0c;这个table在编译期间会包含进工程中。iOS-Class-Guard能有效的隐藏绝大多数的类、协议、方法、属性和 实例变量 名。 iOS-Class-Guard不…

在 S/4HANA、ECC 和 ERP 上轻松扩展或简化 SAP WM,并将其自动化到移动环境中

为您的 SAP WM 提供完整的本地 SAP 图形用户界面 基于原生通道架构&#xff08;NCA&#xff09;&#xff0c;iOS、Android 和手持 Scanguns 版 Liquid UI 可与 SAP WM 原生连接&#xff0c;同时保留 SAP GUI 丰富的事务处理功能。它使您无需编程即可直接从移动设备访问 MIGO、…

安全意识成熟度模型

安全意识成熟度模型是指导企业组织有效识别、管理和衡量团队和员工的安全意识风险&#xff0c;评估和衡量团队和员工的安全意识的评估模型。这套模型是多年以来通过对200多个具有安全意识的职员进行调研后建立的&#xff0c;安全意识成熟度模型使组织能够确定当前安全意识的成熟…

深度学习——激活函数汇总

深度学习——激活函数汇总 一、ReLU 一、ReLU 参考资料&#xff1a; https://zhuanlan.zhihu.com/p/428448728

C++核心编程——运算符重载

C核心编程——运算符重载 运算符重载的方法运算符重载函数作成员函数与友元函数重载双目运算符重载单目运算符重载流插入运算符和"<<"和流提取运算符">>"重载流插入运算符和"<<"流提取运算符">>" 运算符重载的…

LabVIEW开发自适应降噪ANC

LabVIEW开发自适应降噪ANC 在许多情况下&#xff0c;信号很嘈杂&#xff0c;必须消除噪声。自适应降噪&#xff08;ANC&#xff09;是可用于消除信号噪声的主要实时方法之一。可以使用LabVIEW自适应滤滤器工具包来设计ANC应用程序。本文介绍使用自适应筛选器工具包的ANC的一些…

【深度学习】概率图模型(二)有向图模型详解(条件独立性、局部马尔可夫性及其证明)

文章目录 一、有向图模型1. 贝叶斯网络的定义2. 条件独立性及其证明a. 间接因果关系 X 3 → X 2 → X 1 X_3 \rightarrow X_2 \rightarrow X_1 X3​→X2​→X1​b. 间接果因关系 X 1 → X 2 → X 3 X_1 \rightarrow X_2 \rightarrow X_3 X1​→X2​→X3​c. 共因关系 X 1 ← X…

android上架之获取平台公钥、签名 MD5 值

app上架需要用到的两个参数公钥、签名 MD5 步骤 1. 下载jadx-gui 工具&#xff0c;下载 2. 下载成后&#xff0c;解压压缩包&#xff0c;双击 jadx-gui-1.4.7.exe 运行。 3. 运行后&#xff0c;在页面左上方单击文件 > 打开文件&#xff0c;打开 APK 包 4. 打开 APK 包后…

Jvm常见问题

1. 为什么用元空间替换永久代 避免OOM异常&#xff1a;永久代中存放了很多JVM需要的类信息&#xff0c;这些数据大多数是不会被清理的&#xff0c;所以Full GC往往无法回收多少空间。而永久代的空间是有限的&#xff0c;如果经常加载新的类进来或者频繁的创建和删除类&#xf…

前端页面转pdf

首先&#xff0c;需要安装两个库 html2canvasjspdf 先引入这个公用的html转pdf的方法 /**path:src/utils/htmlToPdf.jsname:导出页面为pdf格式 **/ import html2Canvas from "html2canvas1.4.1"; import JsPDF from "jspdf2.5.1";const htmlToPdf {get…

2.Ansible的copy模块,我最常用的模块

1. 简述 先从我自身的情况来说&#xff0c;我不是运维人员&#xff0c;并且对linux操作也不是特别熟悉&#xff0c;所以工作中我使用ansible基本就是在平常的自动化部署中&#xff0c;而使用最多的模块就是copy模块。我使用copy模块也主要是来替换生产环境的配置文件。所以&am…

rabbitMQ镜像队列的使用

在rabbitMQ集群中&#xff0c;默认发送消息时&#xff0c;队列默认时在一个节点上存在的。 我们以node01 node02 node03三节点集群为例&#xff0c;在node01声明队列发送消息后&#xff0c;发现&#xff1a; 测试队列只在节点node01上出现。 我们手动停止node01后&#xff0c…

Tomcat外传

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 本篇开始&#xff0c;我…