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.mzph.cn/web/58301.shtml

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

相关文章

信息学科平台系统构建:Spring Boot框架深度解析

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式,是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示: 图4-1系统工作原理…

【JavaEE】认识进程

一、操作系统(operating system) 操作系统是一组做计算机资源管理的软件的统称,它能够把一个计算机上的所有硬件资源和软件资源都管理好:能够管理好各种硬件资源,让他们很好的相互配合,能够管理好各种软件…

Chromium Mojo(IPC)进程通信演示 c++(4)

122版本自带的mojom通信例子仅供学习参考: codelabs\mojo_examples\01-multi-process 其余定义参考文章: Chromium Mojo(IPC)进程通信演示 c(2)-CSDN博客 01-mojo-browser.exe 与 01mojo-renderer.exe进程通信完整例子。 一、…

像`npm i`作为`npm install`的简写一样,使用`pdm i`作为`pdm install`的简写

只需安装插件pdm-plugin-i即可: pdm plugin add pdm-plugin-i 然后就可以愉快地pdm i了,例如: git clone https://github.com/waketzheng/fast-dev-cli cd fast-dev-cli python -m pip install --user pipx pipx install pdm pdm plugin a…

尚庭公寓-小程序接口

7. 项目开发 7.4 移动端后端开发 7.4.1 项目初始配置 7.4.1.1 SpringBoot配置 1. 创建application.yml文件 在web-app模块的src/main/resources目录下创建application.yml配置文件,内容如下: server:port: 80812. 创建SpringBoot启动类 在web-app…

FPGA视频GTH 8b/10b编解码转PCIE3.0传输,基于XDMA中断架构,提供工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐我已有的PCIE方案我已有的 GT 高速接口解决方案 3、PCIE基础知识扫描4、工程详细设计方案工程设计原理框图输入Sensor之-->芯片解码的HDMI视频数据组包基于GTH高速接口的视频传输架构GTH IP 简介GTH 基本结构GTH 发送和接收处理…

qt QFontDialog详解

1、概述 QFontDialog 是 Qt 框架中的一个对话框类,用于选择字体。它提供了一个可视化的界面,允许用户选择所需的字体以及相关的属性,如字体样式、大小、粗细等。用户可以通过对话框中的选项进行选择,并实时预览所选字体的效果。Q…

华为2288HV2服务器安装BCLinux8U6无法显示完整安装界面的问题处理

本文记录了华为2288HV2服务器安装BCLinux8U6无法显示完整安装界面,在安装过程中配置选择时,右侧安装按钮不可见,导致安装无法继续的问题处理过程。 一、问题现象 华为2288HV2服务器安装BCLinux8U6时无法显示完整的安装界面,问题…

人工智能技术的未来展望:变革行业、优化生活与工作方式的无限可能

文章目录 每日一句正能量前言人工智能技术的发展历程和现状人工智能的应用领域人工智能的前景 人工智能的未来1. AI技术的应用前景是乐观的2. AI技术的发展需要跨学科合作3. AI技术的伦理和隐私问题不容忽视4. AI技术可能带来的就业问题需要重视5. AI技术的发展需要全球合作6. …

【LuatOS】修改LuatOS源码为PC模拟器添加高精度时间戳库timeplus

0x00 缘起 LuatOS以及Lua能够提供微秒或者毫秒的时间戳获取工具,但并没有提供获取纳秒的工具。通过编辑LuatOS源码以及相关BSP源码,添加能够获取纳秒的timeplus库并重新编译,以解决在64位Windows操作系统中LuatOS模拟器获取纳秒的问题&#…

7.2 设计模式

设计模式 7.3.1 设计模式的要素7.3.2 创建型设计模式7.3.3 结构性设计模式1. Adapter (适配器)2. Bridge(桥接)3.Composite(组合)4.Decorator(装饰)5.Facade(外观)6.Flyweight(享元)7.Proxy(代理)8. 结构型模式比较 7.3.4 行为型设计模式1 Chain of Responsibility [ &#xff…

数字信号处理Python示例(6)使用指数衰减函数建模放射性衰变过程

文章目录 前言一、放射性衰变方程二、放射性衰变过程的Python仿真三、仿真结果分析写在后面的话 前言 使用指数衰减函数对放射性衰变进行了建模仿真,给出完整的Python仿真代码,并对仿真结果进行了分析。 一、放射性衰变方程 放射性衰变是一种自然现象&…

大数据新视界 -- 大数据大厂之 Impala 与内存管理:如何避免资源瓶颈(上)(5/30)

💖💖💖亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

AntFlow一款开源免费且自主可控的仿钉钉工作流引擎

在现代企业管理中,流程审批的高效性直接影响到工作的流畅度与生产力。最近,我发现了一个非常有趣的项目——AntFlow。这个项目不仅提供了一个灵活且可定制的工作流平台,还能让用户以可视化的方式创建和管理审批流程。 如果你寻找一个快速集成…

理解 WordPress | 第二篇:结构化分析

WordPress 专题致力于从 0 到 1 搞懂、用熟这种可视化建站工具。 第一阶段主要是理解。 第二阶段开始实践个人博客、企业官网、独立站的建设。 如果感兴趣,点个关注吧,防止迷路。 WordPress 的内容和功能结构可以按照层级来划分,这种层次化的…

省级-社会保障水平数据(2007-2022年)

社会保障水平是一个综合性的概念,它不仅涉及到一个国家或地区的社会保障制度覆盖范围,还包括了提供的保障种类与水平,以及这些制度在满足公民基本生活需求方面的能力。 2007-2022年省级-社会保障水平数据.zip资源-CSDN文库https://download.…

【毫米波雷达(三)】汽车控制器启动流程——BootLoader

汽车控制器启动流程——BootLoader 一、什么是Bootloader(BT)?二、FBL、PBL、SBL、ESS的区别三、MCU的 A/B分区的实现 一、什么是Bootloader(BT)? BT就是一段程序,一段引导程序。它包含了启动代码、中断、主程序等。 雷达启动需要由BT跳转到…

计算机网络——网络层导论

转发是局部功能——数据平面 路由是全局的功能——控制平面 网卡 网卡,也称为网络适配器,是计算机硬件中的一种设备,主要负责在计算机和网络之间进行数据传输。 一、主要功能 1、数据传输: 发送数据时,网卡将计算机…

使用 Python 调用云 API 实现批量共享自定义镜像

本文介绍如何通过 Python SDK 调用 API 接口,通过子用户批量共享云服务器自定义镜像。若您具备类似需求,或想了解如何使用 SDK,可参考本文进行操作。 前提条件 已创建子用户,并已具备云服务器及云 API 所有权限。 创建子用户请…

[mysql]修改表和课后练习

目录 DDL数据定义语言 添加一个字段 添加一个字段到最后一个 添加到表中的第一个一个字段 选择其中一个位置: 修改一个字段:数据类型,长度,默认值(略) 重命名一个字段 删除一个字段 重命名表 删除表 清空表 DCL中事务相关内容 DCL中COMMIT和ROLLBACK的讲解 对比TR…