KOA 入门,完善用户注册和登录逻辑
在上次简单实现用户注册和登录逻辑的时候「传送门」,提到了很多优化的地方,在这里我们来解决优化这些问题。
在开始之前,因为上次我们测试的时候创建了多个相同账号名字的账号,所以我们先进行强制同步模型来清空上次创建的相关数据。调用 node ./src/db/connect.js 来处理相关逻辑。
这里提个小小的优化,每次我们都去执行 node ./src/db/connect.js 可能会有点麻烦,并且同步模式每次切换还要去代码上做相对应的修改。所以这里我们可以在 package.json 中的 scripts 中配置对应的指令,根据对应指令来进行对应的同步模式。首先,
// 根目录 > package.json
{
// 其他内容
"scripts": {
// ... 其他已有配置
// 新增内容
"sync": "node ./src/db/connect.js",
"sync:force": "cross-env TYPE=force node ./src/db/connect.js",
"sync:alter": "cross-env TYPE=alter node ./src/db/connect.js"
}
}
这里需要安装一个 cross-env 的第三方包,这个包的作用就是可以在 npm 上轻松的使用环境变量。
npm i cross-env -D
配置 scripts 后,将 connect.js 修改成:
// src > db > connect.js
// ...
// 获取传的参数,也就是 cross-env TYPE=xxx
const type = process.env.TYPE
/** model 同步数据库 */
if (!type) {
// 只有数据库中不存在与模型同名的数据表时才同步
sequelize.sync().then(() => {
console.log('同步数据库成功')
process.exit()
})
} else if (type === 'force') {
// 强制同步,会将原先的表格以及数据清空,重新创建新的表格
sequelize.sync({ force: true }).then(() => {
console.log('同步数据库成功')
process.exit()
})
} else if (type === 'alter') {
// 修改同名数据表结构(这个选项表示 Sequelize 会检查数据库表和模型之间的差异,并尝试自动更新数据库表以匹配模型定义)
sequelize.sync({ alter: true }).then(() => {
console.log('同步数据库成功')
process.exit()
})
}
然后,我们就可以直接通过 npm run 的方式进行对应的同步了
npm run sync
npm run sync:force
npm run sync:alter
注册前判断当前用户是否已存在
验证用户是否已存在,其实这个功能我们已经实现过。查询用户是否存在,和用户登录逻辑其实有一部分相通的地方。都是去数据库查询用户信息,只不过一个通过 userName,而另一个则是通过 userName 和 password。所以我们先改一下 service 中的内容。
// src > db > service > user.js
async function findOneUser({userName, password}) {
// 默认查询条件 userName, 当存在 password 的时候再传入 password
const where = {
userName
}
if (password) where.password = password
const res = await User.findOne({
attributes: ['userName', 'introduce', 'email', 'id'], // 获取哪些属性
where
})
if (res) return res
return 0
}
然后后面流程就简单了,写接口,controller
// src > routes > user.js
router.get('/nameAvailable', async (ctx, next) => {
const { userName } = ctx.query
ctx.body = await checkNameAvailable(userName)
})
// src > controller > user.js
async function checkNameAvailable(userName) {
const res = await findOneUser({ userName })
if (!res) {
return {
code: 0,
message: '用户名可用'
}
} else {
return {
code: 3,
message: '用户名已注册'
}
}
}
以上就完成了查询用户名是否可用的接口,我们可以在输入用户名的输入框中绑定 blur 事件去触发查询当前用户名是否允许注册。
当然,这里在注册接口前我们也最好去查询一次用户名是否可用。
async function register({ userName, password, email, introduce }) {
const availableRes = await findOneUser({ userName })
// 新增
if (availableRes) return { code: 3, message: '用户名已注册' }
try {
const res = await createUser({
userName,
password,
email,
introduce,
})
return {
code: 0,
data: res
}
} catch (err) {
console.log(err)
return {
code: 1,
message: '创建用户失败'
}
}
}
用户密码加密
首先我们下载 bcrypt 来对密码进行加密
npm install bcrypt
因为 bcrypt 加密后并没有提供解密的方法,相当于密码加密后不能转回原先的明文密码。相当于它是一种单向的加密算法,提供了一个接口让我们对比输入的密码与存储的哈希值来验证。所以这里我们在 service 中改一下。
- 注册接口在注册
createUser的时候对密码进行加密,数据库存储的是加密后的哈希值。 - 登录查询
findOneUser,先通过用户名去查找对应的账号,再通过对比输入密码与哈希值的方式来进行密码配对。
直接展示修改后的两个方法
// src > service > user.js
const bcrypt = require('bcrypt')
const saltRounds = 10
/** 创建用户 */
async function createUser({ userName, password, email, introduce }) {
// 密码加密
const hashedPassword = await bcrypt.hash(password, saltRounds)
const res = await User.create({
userName,
password: hashedPassword,
email,
introduce,
})
const data = res.dataValues
delete data.password
return data
}
/** 查询 */
async function findOneUser({userName, password}) {
const attributes = ['userName', 'introduce', 'email', 'id']
if (password) attributes.push('password')
const res = await User.findOne({
attributes, // 获取哪些属性
where: {
userName
}
})
if (password && res) {
const isPasswordValid = await bcrypt.compare(password, res.dataValues.password)
if (isPasswordValid) {
// 删除密码,不展示返回
delete res.dataValues.password
return res.dataValues
}
return 0
}
if (res) return res.dataValues
return 0
}
然后我们可以测试下注册和登录

数据库中数据,密码已经不是明文

登录的话也是没问题的

「失败」

身份认证
身份认证一般有两种 session 和 JWT。在这里我们采用 session 的认证机制。
首先下载相关依赖
npm i koa-generic-session koa-redis
因为这里使用了 redis 来存储 session,所以要在本地下载 redis。「下载地址」
下载完成后,我们就可以在 app.js 中配置 session
// src > app.js
// ... 其他引用
const session = require('koa-generic-session')
const redisStore = require('koa-redis')
// ... 其他代码
// session 设置
app.keys = ['SESSION_SECRET'] // session 密钥
app.use(session({
key: 'koa:sess', // cookie 名称
maxAge: 24 * 60 * 60 * 1000, // session 过期时间
signed: true, // 是否签名 cookie
httpOnly: true, // 是否只允许 http 访问
store: redisStore({ // 使用 Redis 存储 session
all: `localhost:6379`
})
}))
// ... 其他代码
在配置中,其中 session 密钥 和 cookie 名称 按照自己的需求随意修改。redis 配置也需要按照本地情况填写。koa-session 也提供了集群的相关配置,具体可以看官方的配置。「传送门」
配置完后,要在接口中使用才可以。这里我们改一下登录接口
// src > routes > api > user.js
router.post('/login', async(ctx, next) => {
const { userName, password } = ctx.request.body
// 传 ctx 到 controller
ctx.body = await userLogin(ctx, { userName, password })
})
// src > controller > user.js
async function userLogin(ctx, { userName, password }) {
const res = await findOneUser({ userName, password })
if (res) {
// 设置 session
ctx.session.userInfo = res
return {
code: 0,
data: res,
message: '登录成功'
}
} else {
return {
code: 2,
message: '登录失败'
}
}
}
然后我们在登录的时候就能看到 响应头 中已经有了 Set-Cookie 属性

这时候在终端进入 redis-cli,输入 keys * 也可看到存储的 session 信息

后面判断用户身份就容易多了,接口调用的时候查询一下 session.userInfo 是否存在即可。

浙公网安备 33010602011771号