背景
最近在用electron写桌面应用,后来随着想法越来越多,就准备增加用户相关的功能,这就涉及到了用户的登录、注册等功能
在此简单做下记录,勉强维持下月更😂
正文
初始化项目
1-安装相关包
js
npm i koa koa-router sequelize minimist mysql2
2-创建目录
- app.js
项目的启动入口,用于启动koa和一些异常处理
- koa.js
启动一个http服务
- .env
配置相关
- src/util
辅助方法或变量
- src/routr文件夹
按模块管理的路由文件
- src/db文件夹
管理数据库相关的操作,建立action和model两个子文件夹,一个专门用来定义模型,一个专用于对数据库进行增删改查
- src/controller文件夹
逻辑层
代码开发
进入koa.js,在此处创建一个http服务
登录和注册一般需要提交post请求,所以还需要一个koa-bodyparser中间件来对参数进行解析
js
import Koa from "koa";
import bodyParser from "koa-bodyparser";
import initController from "./src/index.js";
...
const app = new Koa();
// 解析post请求参数
app.use(bodyParser());
// 注册路由
await initController(app);
...
const server = app.listen(APP_RUN_PORT, () => {
emitLog(`koa服务已启动:http://127.0.0.1:${APP_RUN_PORT}/...`, "success");
});
...
进入router文件夹
注册用户相关的路由,如下,注册了/user/login和/user/register这两个
ts
import Router from "koa-router";
...
export default async function (app, ...) {
const router = new Router();
router.prefix("/user");
router.post("/login", async (ctx) => {
...
});
router.post('/register', async (ctx) => {
...
})
app.use(router.routes());
}
启动项目,控制台打印如下即是启动成功
现在用postman测试下刚刚注册的路由通不通,显然是通的,只不过接口里默认是什么都不处理直接返回ok的
接着到db文件夹下,通过Sequelize与mysql服务建立连接
js
import Sequelize from "sequelize";
import initModels from "./model/index.js";
...
// 读取.env文件中定义的数据库相关的变量
const { MYSQL_OPTIONS } = process.env;
const [database, username, password, port] = MYSQL_OPTIONS.split("|");
// 建立连接
const sequelize = new Sequelize(database, username, password, {
host: "localhost",
port,
dialect: "mysql",
logging: false,
});
...
// 初始化模型
... = await initModels.call(this, sequelize);
进入model文件夹下的user.js中创建user模型,它主要包含了名称、密码和唯一的主键id (省略部分是用于创建表关联的逻辑,由于与本文内容无关,不展开)
js
import Sequelize from "sequelize";
const TABLE_NAME = "users";
export default async function (options) {
const { sequelize, sequelizeOptions } = options;
const model = await sequelize.define(
TABLE_NAME,
{
id: {
type: Sequelize.STRING,
primaryKey: true,
autoIncrement: false,
},
username: {
type: Sequelize.STRING,
allowNull: false,
},
password: {
type: Sequelize.STRING,
allowNull: false,
},
...
},
// 公共配置,用于修改或设置Sequelize的默认行为
sequelizeOptions
);
...
return {
model,
...
};
}
进入action文件夹下的user.js中,这里是最终与数据库交互的地方,也即增删改查
js
export default async function ({ model }) {
...
async function api() {
...
}
return {
...,
api,
};
}
在不考虑行为验证、不接入验证码、不通过第三方授权的情况下,注册就只需要客户端提交用户名和密码就可以了
js
const { username,password } = ctx.request.body || {}
接着需要对用户名进行校验,这需要用到db/action/user下定义的resolveUserListBy接口
当发现表中已经存在传入的用户名时,就直接return
js
const repeat = await db.user.resolveUserListBy(() => ({
username
}))
if (Array.isArray(repeat) && repeat.length) return generateResponse(ctx, 422, '用户名已存在')
只有不存在时,才向数据库表中插入
js
const pushed = await db.user.storeUser({
id: randomUUID(),
username: username,
password
})
if (pushed !== 911) return generateResponse(ctx, 200, 'ok')
再无异常的情况下,表中就有新增的用户啦
但此时的密码是明文存储的,这显然是不合理的且不安全的,所以需要对这个密码进行加密处理
这需要用到bcrypt包,并在存储时进行调用
js
import { hashSync } from 'bcrypt';
... = await db.user.storeUser({
id: randomUUID(),
username: username,
password:hashSync(password, 10)
})
再次注册个用户看一下,密码就已经是密文了
这顺带着提出了一个新问题:我咋知道当前要登录的用户的密码到底对不对呢?
找到登录接口
和注册一样先校验下用户名存在性,由于校验的逻辑几乎一致,故简单做下封装
并在login接口中调用
js
const { username, password } = ctx.request.body || {}
const { status, message,data } = await _validateUser(username)
if (message === '用户不存在') return generateResponse(ctx, status, message)
至于密码校验,直接使用bcrypt的compareSync就可以了
js
if (!compareSync(password, data.password)) return generateResponse(ctx, 422, '用户名或密码输入有误');
这样一来,当输入错误的密码后,就能够被正确拦截了
最后为了区分用户是否登录,我们还得为其签发身份标识,以达到某些接口不登录无法访问的效果,这就需要所谓的token了
这需要再安装个包:jsonwebtoken
然后调用它的sign方法进行签名即可
js
generateResponse(ctx, 200, {
username: data.username,
// 这里可以加任意的值,比如设置过期时间
token: jwt.sign({id: data.id}, TOKEN_SECRECT)
});
这样用户就有身份了
最后的最后,在需要鉴权的接口进行解析就好了
新建个user/information接口测试下
当token不存在或错误时及时拦截后续的处理逻辑就好啦(如果有)