koa项目实战 == 实现注册登录鉴权

一. 项目的初始化

1 npm 初始化

npm init -y

生成package.json文件:

  • 记录项目的依赖

2 git 初始化

git init

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

3 创建 ReadMe 文件

二. 搭建项目

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

image-20210521142016066

三. 项目的基本优化

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启动服务

image-20210521142807478

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}`);
});

四. 添加路由

路由: 根据不同的 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}`);
});

五. 目录结构优化

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

image-20210521165536780

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();

七. 集成 sequlize

sequelize ORM 数据库工具

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 编写配置文件

APP_PORT = 8000MYSQL_HOST = localhost
MYSQL_PORT = 3306
MYSQL_USER = root
MYSQL_PWD = 123456
MYSQL_DB = zdsc

八. 创建 User 模型

1 拆分 Model 层

sequelize 主要通过 Model 对应数据表

创建src/model/user.model.js

const { DataTypes } = require("sequelize");const seq = require("../db/seq");// 创建模型(Model zd_user -> 表 zd_users)
const User = seq.define("zd_user", {// id 会被sequelize自动创建, 管理user_name: {type: DataTypes.STRING,allowNull: false,unique: true,comment: "用户名, 唯一",},password: {type: DataTypes.CHAR(64),allowNull: false,comment: "密码",},is_admin: {type: DataTypes.BOOLEAN,allowNull: false,defaultValue: 0,comment: "是否为管理员, 0: 不是管理员(默认); 1: 是管理员",},
});// 强制同步数据库(创建数据表)
// User.sync({ force: true })module.exports = User;

九. 添加用户入库

所有数据库的操作都在 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");class UserController {async register(ctx, next) {// 1. 获取数据// console.log(ctx.request.body)const { user_name, password } = ctx.request.body;// 合法性if (!user_name || !password) {console.error("用户名或密码为空", ctx.request.body);ctx.status = 400;ctx.body = {code: "10001",message: "用户名或密码为空",result: "",};return;}// 合理性if (getUerInfo({ user_name })) {ctx.status = 409;ctx.body = {code: "10002",message: "用户已经存在",result: "",};return;}// 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();

在 service 中封装函数

const User = require("../model/use.model");class UserService {async createUser(user_name, password) {// 插入数据// await表达式: promise对象的值const res = await User.create({ user_name, password });// console.log(res)return res.dataValues;}async getUerInfo({ id, user_name, password, is_admin }) {const whereOpt = {};id && Object.assign(whereOpt, { id });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;}
}module.exports = new UserService();

十一. 拆分中间件

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

image-20210524154353520

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.body;if (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 = 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 errHandler = require("./errHandler");
// 统一的错误处理
app.on("error", errHandler);

十二. 加密

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

123123abc (加盐) 加盐加密

1 安装 bcryptjs

npm i bcryptjs

2 编写加密中间件

const crpytPassword = async (ctx, next) => {const { password } = ctx.request.body;const salt = bcrypt.genSaltSync(10);// hash保存的是 密文const hash = bcrypt.hashSync(password, salt);ctx.request.body.password = hash;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.body;const salt = bcrypt.genSaltSync(10);// hash保存的是 密文const hash = bcrypt.hashSync(password, salt);ctx.request.body.password = hash;await next();
};const verifyLogin = async (ctx, next) => {// 1. 判断用户是否存在(不存在:报错)const { user_name, password } = ctx.request.body;try {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);

十四. 用户的认证

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

jwt: jsonwebtoken

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

1 颁发 token

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.header;const 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,
};

2) 改写 router

// 修改密码接口
router.patch("/", auth, (ctx, next) => {console.log(ctx.state.user);ctx.body = "修改密码成功";
});

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

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

相关文章

ONLYOFFICE 文档8.2更新评测:PDF 协作编辑、性能优化及更多新功能体验

文章目录 🍀引言🍀ONLYOFFICE 产品简介🍀功能与特点🍀体验与测评ONLYOFFICE 8.2🍀邀请用户使用🍀 ONLYOFFICE 项目介绍🍀总结 🍀引言 在日常办公软件的选择中,WPS 和微软…

MATLAB下的四个模型的IMM例程(CV、CT左转、CT右转、CA四个模型),附下载链接

基于IMM算法的目标跟踪。利用卡尔曼滤波和多模型融合技术,能够在含噪声的环境中提高估计精度,带图像输出 文章目录 概述源代码运行结果代码结构与功能1. 初始化2. 仿真参数设置3. 模型参数设置4. 生成量测数据5. IMM算法初始化6. IMM迭代7. 绘图8. 辅助函…

Segmentation fault 问题解决

问题描述 执行有import torch代码的py 文件报Segmentation fault 原因分析: 查了网上说的几种可能性 import torch 时出现 “Segmentation fault” 错误,通常表示 PyTorch 的安装或配置存在问题 可能的原因 不兼容的库版本: PyTorch、CUDA 或其他依赖…

如何搭建汽车行业AI知识库:定义+好处+方法步骤

在汽车行业,大型车企面临着员工众多、价值链长、技术密集和知识传播难等挑战。如何通过有效的知识沉淀与应用,提升各部门协同效率,快速响应客户咨询,降低销售成本,并开启体系化、可持续性的知识管理建设,成…

QGIS:HCMGIS插件

插件GitHub地址:https://github.com/thangqd/HCMGIS。 以下对HCMGIS插件进行简单介绍,并演示如何进行地图数据下载。 插件简介 HCMGIS - Basemaps, Download OpenData, Batch Converter, VN-2000 Projections, and Field Calculation Utilities for QGI…

SpringBoot集成Shiro+Jwt+Redis

1. 概述 首先需要知道为什么使用 ShiroJwtRedis 进行登录认证和权限控制。 1. 为什么用Shiro? 主要用的是 shiro 的登录认证和权限控制功能。 Shiro 参见本栏目文章 🍃《Shiro实战》 2. 为什么用Jwt? Shiro 默认的 Session 机制来帮助实现…

jenkins 构建报错 Cannot run program “sh”

原因 在 windows 操作系统 jenkins 自动化部署的时候, 由于自动化构建的命令是 shell 执行的,而默认windows 从 path 路径拿到的 shell 没有 sh.exe ,因此报错。 解决方法 前提是已经安装过 git WINR 输入cmd 打开命令行, 然后输入where git 获取 git 的路径, …

Springboot——对接支付宝实现扫码支付

文章目录 前言官方文档以及说明1、申请沙箱2、进入沙箱获取对应的关键信息3、拿到系统生成的公钥和密钥 注意事项创建springboot项目1、引入依赖2、配置连接参数3、创建配置类,用于接收这些参数4、中间类的定义(订单类)5、编写测试接口场景一、pc端请求后端后&#…

【云备份项目】json以及jsoncpp库的使用

目录 1.JSON 2.什么是 JSON? 3.JSON 发展史 4.为什么要使用 JSON? 5.JSON 的不足 6.JSON 应该如何存储? 7.什么时候会使用 JSON 7.1.定义接口 7.2.序列化 7.3.生成 Token 7.4.配置文件 8.JSON的语法规则 8.1.对象和数组 8.2.JS…

【C++篇】在秩序与混沌的交响乐中: STL之map容器的哲学探寻

文章目录 C map 容器详解:高效存储与快速查找前言第一章:C map 的概念1.1 map 的定义1.2 map 的特点 第二章:map 的构造方法2.1 常见构造函数2.1.1 示例:不同构造方法 2.2 相关文档 第三章:map 的常用操作3.1 插入操作…

HOT100_最大子数组和

class Solution {public int maxSubArray(int[] nums) {int[] dp new int[nums.length];int res nums[0];dp[0] nums[0];for(int i 1; i< nums.length; i){dp[i] Math.max(nums[i] ,dp[i-1] nums[i]);res Math.max(res, dp[i]);}return res;} }

contenteditable实现需要一个像文本域一样的可编辑框

我这里是因为左上和右下有一个固定的模板&#xff0c;所有用textarea有点不方便&#xff0c;查了下还有一个方法可以解决就是在需要编辑的元素上加上 :contenteditable"true" 完整代码如下&#xff0c;因为这个弹窗是两用的&#xff0c;所以用messageType做了一下判…

SpringBoot源码解析(一)

SpringBoot自动装配原理 SpringBootApplication注解 我们在使用SpringBoot时&#xff0c;通常使用的是SpringBootApplication这个注解&#xff0c;比如&#xff1a; 而这个注解的定义为下图&#xff0c;可以发现这个注解上有另外三个注解&#xff1a;SpringBootConfiguration…

WPF+MVVM案例实战与特效(二十四)- 粒子字体效果实现

文章目录 1、案例效果2、案例实现1、文件创建2.代码实现3、界面与功能代码3、总结1、案例效果 提示:这里可以添加本文要记录的大概内容: 2、案例实现 1、文件创建 打开 Wpf_Examples 项目,在 Views 文件夹下创建窗体界面 ParticleWindow.xaml,在 Models 文件夹下创建粒子…

js中怎么把excel和pdf文件转换成图片打包下载

index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>文件转图片工具</title><!-- 本…

盘点 2024 十大免费/开源 WAF

WAF 是 Web Application Firewall 的缩写&#xff0c;也被称为 Web 应用防火墙。区别于传统防火墙&#xff0c;WAF 工作在应用层&#xff0c;对基于 HTTP/HTTPS 协议的 Web 系统有着更好的防护效果&#xff0c;使其免于受到黑客的攻击。 近几年经济增速开始放缓&#xff0c;科…

蓝牙资讯|苹果AirPods Pro 2推出听力测试、助听器和听力保护等功能

苹果推送iOS 18.1 系统版本更新&#xff0c;AirPods Pro 2 用户也在 iOS 18.1 中获得了强大的新功能。 运行固件 7B19 的 AirPods Pro 2 用户&#xff0c;搭配 iOS 18.1 系统的 iPhone&#xff0c;将获得三项强大的听力健康功能&#xff1a;听力测试、助听器和听力保护。 听力…

如何检查雷池社区版 WAF 是否安装成功?

容器运行状态检查&#xff1a; 使用命令行检查&#xff1a;打开终端&#xff0c;连接到安装雷池的服务器。运行 docker ps 命令&#xff0c;查看是否有与雷池相关的容器正在运行。 如果能看到类似 safeline-mgt、safeline-tengine 等相关容器&#xff0c;并且状态为 Up&#x…

【AI开源项目】Botpress - 开源智能聊天机器人平台及其部署方案

文章目录 Botpress 概述Botpress 的定位 Botpress 的主要特点1. OpenAI 集成2. 易于使用3. 定制和扩展性4. 多平台支持5. 集成和扩展 API6. 活跃的社区和详尽的文档 部署方案集成集成开发集成部署机器人示例开发工具代理本地开发先决条件从源代码构建 Botpress 如何解决常见问题…

Rust 力扣 - 1652. 拆炸弹

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 我们只需要遍历长度长度为k的窗口&#xff0c;然后把窗口内数字之和填充到结果数组中的对应位置即可 题解代码 impl Solution {pub fn decrypt(code: Vec<i32>, k: i32) -> Vec<i32> {let n c…