koa2+mysql+vue实现用户注册、登录、token验证

说明:

  node.js提供接口,vue展现页面,前后端分离,出于编辑器功能和编辑习惯,vue用HbuilderX,node.js用VScode。(PS:仅作为学习笔记,如有不当之处欢迎指出,在此先谢为敬~~~)

环境:

  首先需要有node.js环境,安装教程 在这里,最好下载较新的版本,对es6、es7有更好的支持,再装个 淘宝镜像,完毕!

后台:

1、安装mysql

  1.1、mysql下载地址

  解压到安装位置,修改环境变量,win10编辑环境变量很方便了,win7的话记得以 ; 分割开

  

  1.2、添加配置文件

  在mysql的bin目录下,新建my.ini文件(如果没有),打开my.ini文件,写入以下配置内容

[mysqld]
# 设置3306端口
port=3306
# 设置mysql的安装目录
basedir=D:\\myInstalls\\mysql-8.0.11
# 设置mysql数据库的数据的存放目录
datadir=D:\\myInstalls\\mysql-8.0.11\\Data
# 允许最大连接数
max_connections=200
# 允许连接失败的次数。这是为了防止有人从该主机试图攻击数据库系统
max_connect_errors=10
# 服务端使用的字符集默认为UTF8
character-set-server=utf8
# 创建新表时将使用的默认存储引擎
default-storage-engine=INNODB
# 默认使用“mysql_native_password”插件认证
default_authentication_plugin=mysql_native_password
[mysql]
# 设置mysql客户端默认字符集
default-character-set=utf8
[client]
# 设置mysql客户端连接服务端时默认使用的端口
port=3306
default-character-set=utf8

  1.3、安装

  以管理员身份运行cmd,进入mysql的bin目录下,不进入也行,因为我们已经配置了环境变量

  初始化数据库,运行 mysqld --initialize --console,记住红色框内的初始密码

     

  安装mysql服务,运行 mysqld --install [服务名] ,服务名可以不写,安装完毕 net start mysql 启动mysql

  

  启动成果,mysql停止指令 net stop mysql

  默认密码太复杂,改个简单的,首先运行 mysql -u root -p 进入mysql,密码是刚才记住的初始密码

  

  修改密码指令:ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '新密码'; 

  

  OK,mysql我们已经有了,接下来搭建koa2!

2、搭建koa2项目

  (你可以使用系统自带的cmd窗口,也可以用编辑器自带的。我这里用VScode的命令行终端,看起来特别虚浮~~~)

  2.1、我们不一步步搭建,采用koa2框架,并使用koa-generator生成项目,类似vue-cli

  安装指令:cnpm install koa-generator -g

  

  2.2、在你的项目目录下,运行 koa2 项目名,生成项目,如:koa2 paopao(泡泡是我的猫的名字~~~)

  

  成功,根据上面提示走~~~

  cd paopao 进入项目目录

  cnpm install 安装项目依赖

  cnpm start paopao 运行项目(cnpm是淘宝镜像)

  有个报错大概意思是这个包不再维护了,cnpm uninstall koa-onerror 卸载,重新装最新的版本 cnpm install koa-onerror --save

  

  在浏览器输入:localhost:3000,浏览器运行结果(左),项目结构(右)

      

3、实现API

  3.1、用sequelize来操作数据库,同时安装mysql、mysql2

  cnpm install sequelize mysql mysql2 --save

  

  所有安装的依赖可以在package.json里查看:

  

  注意:我在使用时发现koa-static(处理静态文件的中间件),默认3.0.0版本会报错,于是更新成了最新版本

  使用cnpm install [email protected] --save更新,再查看package.json,版本变成了5.0.0即可

  3.2、连接数据库

  在项目根目录下建一个config文件夹,在该文件夹建一个js文件,取名db.js,用来配置数据库连接

  config-->db.js

var Sequelize = require("sequelize")
var sequelize = new Sequelize('paopao','root','happy',{
    host:'localhost',
    dialect:'mysql',
    operatorsAliases:false,
    dialectOptions:{
        //字符集
        charset:'utf8mb4',
        collate:'utf8mb4_unicode_ci',
        supportBigNumbers: true,
        bigNumberStrings: true
    },
    pool:{
        max: 5,
        min: 0,
        acquire: 30000,
        idle: 10000
    },
    timezone: '+08:00'  //东八时区
});

module.exports = {
    sequelize
};

  paopao是我的数据库表名,root数据库用户名,happy数据库用户密码

  3.3、定义数据库模型

  在根目录建一个module文件夹,在module文件下面建一个user.js,用来定义数据模型,告诉sequelize怎么跟数据库的数据一一对应

  module-->user.js

module.exports = function(sequelize,DataTypes){
    return sequelize.define(
        'user',
        {
            userId:{
                type: DataTypes.INTEGER,
                primaryKey: true,
                allowNull: true,
                autoIncrement: true
            },
            mobileNo:{
                type: DataTypes.STRING,
                allowNull: false,
                field: 'mobileNo'
            },
            password:{
                type: DataTypes.STRING,
                allowNull: false,
                field: 'password'
            }
        },
        {
            timestamps: false
        }
    );
}

  3.4、数据库操作和功能处理

  controller-->user.js 添加以下代码 

//引入db配置
const db = require('../config/db')

//引入sequelize对象
const Sequelize = db.sequelize

//引入数据表模型
const user = Sequelize.import('../module/user')
//自动创建表
user.sync({ force: false }); 

//数据库操作类
class userModule {
    static async userRegist(data) {
        return await user.create({
            password: data.password,
            mobileNo: data.mobileNo
        })
    }

    static async getUserInfo(mobileNo) {
        return await user.findOne({
            where: {
                mobileNo
            }
        })
    }
}

  数据库操作有了,接下来进行功能处理,还是在该文件添加

  controller-->user.js 里添加该userController 类,并将之exports出去

//功能处理
class userController {
    
}

module.exports = userController;

  用户注册:

  在 userController 类里添加用户注册逻辑

//注册用户
    static async create(ctx) {
        const req = ctx.request.body;
        if (req.mobileNo && req.password) {
            try {
                const query = await userModule.getUserInfo(req.mobileNo);
                if (query) {
                    ctx.response.status = 200;
                    ctx.body = {
                        code: -1,
                        desc: '用户已存在'
                    }
                } else {
                    const param = {
                        password: req.password,
                        mobileNo: req.mobileNo,
                        userName: req.mobileNo
                    }
                    const data = await userModule.userRegist(param);

                    ctx.response.status = 200;
                    ctx.body = {
                        code: 0,
                        desc: '用户注册成功',
                        userInfo: {
                            mobileNo: req.mobileNo
                        }
                    }
                }

            } catch (error) {
                ctx.response.status = 416;
                ctx.body = {
                    code: -1,
                    desc: '参数不齐全'
                }
            }
        }
    }

  因为还要做登录超时token验证,用户登录成功还要返回token,为了生成token,我们需要安装几个中间件

  cnpm install jsonwebtoken --save  导入jwt模块

  cnpm install koa-jwt --save  koa提供的jwt中间件

  在app.js里添加如下代码:

  unless()表示里面的regist、login不做token验证

const koajwt = require('koa-jwt')

// logger
app.use(async (ctx, next) => {
  return next().catch((err) => {
    if(err.status === 401){
      ctx.status = 401;
      ctx.body = {
        code: '-2000',
        desc: '登陆过期,请重新登陆'
      };
    }else{
      throw err;
    }
  })
})

app.use(koajwt({
  secret: '123456'
}).unless({
  path: [/^\/user\/regist/,/^\/user\/login/]
}))

  为了解析token,在public目录下新建tool.js,加入解析token的代码

const getToken = require('jsonwebtoken')

exports.verToken = function(token){
    return new Promise((resolve,rejece) => {
        const info = getToken.verify(token.split(' ')[1],"123456");
        resolve(info);
    })
}

  返回controller-->user.js,添加

//引入jwt做token验证
const jwt = require('jsonwebtoken')

//解析token
const tools = require('../public/tool')

//统一设置token有效时间  为了方便观察,设为10s
const expireTime = '10s'

  用户登录:

  之后就可以写用户登录逻辑了

  controller-->user.js-->userController 类里添加

  通过 jwt.asign() 方法生成token,这里的123456跟app.js里的123456相同,就理解为一个秘钥吧~~

//密码登陆
    static async login(ctx) {
        const req = ctx.request.body;
        if (!req.mobileNo || !req.password) {
            return ctx.body = {
                code: '-1',
                msg: '用户名或密码不能为空'
            }
        } else {
            const data = await userModule.getUserInfo(req.mobileNo);
            if (data) {
                if (data.password === req.passWord) {
                    //生成token,验证登录有效期
                    const token = jwt.sign({
                        user: req.mobileNo,
                        passWord: req.password
                    }, '123456', { expiresIn: expireTime });
                    const info = {
                        createdAt: data.createdAt,
                        updatedAt: data.updatedAt,
                        mobileNo: data.mobileNo,
                        userId: data.userId
                    }
                    return ctx.body = {
                        code: '0',
                        token: token,
                        userInfo: JSON.stringify(info),
                        desc: '登陆成功'
                    }
                } else {
                    return ctx.body = {
                        code: '-1',
                        desc: '用户密码错误'
                    }
                }
            } else {
                return ctx.body = {
                    code: '-1',
                    desc: '该用户尚未注册'
                }
            }
        };
    }

  为了验证token是否过期,我们再定义一个获取用户信息的逻辑,登陆10s后获取用户信息,验证token是否过期

  获取用户信息:

  controller-->user.js-->userController 类里添加

//获取用户信息(除密码外)
    static async getUserInfo(ctx){
        const req = ctx.request.body;
        const token = ctx.headers.authorization;
        if(token){
            try {
                const result = await tools.verToken(token);
                if (!req.mobileNo) {
                    return ctx.body = {
                        code: '-1',
                        desc: '参数错误'
                    }
                } else {
                    let data = await userModule.getUserInfo(req.mobileNo);
                    if (req.mobileNo == data.mobileNo) {
                        const info = {
                            createdAt: data.createdAt,
                            updatedAt: data.updatedAt,
                            mobileNo: data.mobileNo,
                            userId: data.userId
                        };
                        return ctx.body = {
                            code: '0',
                            userInfo: JSON.stringify(info),
                            desc: '获取用户信息成功'
                        }
                    }
                }
            } catch (error) {
                ctx.status = 401;
                return ctx.body = {
                    code: '-1',
                    desc: '登陆过期,请重新登陆'
                }
            }
        }else{
            ctx.status = 401;
            return ctx.body = {
                code: '-1',
                desc: '登陆过期,请重新登陆'
            }
        }
    }

  3.5、路由,即处理请求的url,使用koa-router

  不用重新导入,koa-generator已经帮我们导入了,直接使用

  在routes目录下新建文件 user.js

  写入以下代码:

  routes-->user.js

const Router = require('koa-router');
const userController = require('../controller/user')

const router = new Router({
    prefix: '/user'
});

//用户注册
router.post('/regist',userController.create)

//密码登陆
router.post('/login',userController.login)

//获取用户信息
router.post('/getUserInfo',userController.getUserInfo)

module.exports = router;

  然后在入口文件app.js引入

  

  使用

  

  完成这些以后,cnpm run dev 启动项目(依赖nodemon,package.json里面有,这样每次更改代码以后不用手动重新启动)

  启动正常如下:

  

  如果有报错,提示缺少这包那包的,不用着急!

  把根目录下的node_modules目录删除

  检查一遍package.json

  确认无误后重新cnpm install

  再次启动 cnpm run dev ~~~

  补充一点,如果想在其他端口启动,在app.js里添加 app.listen(3333),修改为3333端口,自动热刷新~~~蛋是此时接口仍然不可调试,因为存在跨域问题

  

  3.6、解决跨域,koa-cors

  koa同样提供了解决跨域的依赖包

  cnpm install koa-cors --save

  在app.js添加:

  

  现在可以测试接口了,随便写个ajax或者使用postman,postman测试结果:

  注册:

  

  登录:

  

  查看数据库结果(使用的是破解版Navicat图形化数据库管理工具):

  

  到此为止,API就完成了,最后一步,验证token过期有没有效果

4、结合VUE验证token

  写到太晚了,想起来今天还没给泡泡铲屎,VUE就不写那么详细了,有空再补上   ~.~

  我就贴一下代码和验证结果

  vue项目里,在接口文件里:

import axios from 'axios';
import qs from 'qs';
import route from '../router';
import {
    message
} from 'ant-design-vue'

axios.interceptors.request.use(function(config) {
    // 处理请求参数
    config.data = qs.stringify(config.data)

    //将token写入请求头
    if (window.localStorage.getItem('token')) {
        config.headers.Authorization = `Bearer ${window.localStorage.getItem('token')}`;
    }

    return config;
}, function(error) {
    // 对请求错误做些什么
    return Promise.reject(error);
});

axios.interceptors.response.use(
    response => {
        return response
    },
    error => {
        if (error.response) {
            switch (error.response.status) {
                case 401:
                    message.error("登录过期,请重新登录!", ()=>{
                        window.localStorage.removeItem("token"); //可能是token过期,清除它
                        route.replace({ //跳转到登录页面
                            path: '/login',
                            query: {
                                // 将跳转的路由path作为参数,登录成功后跳转到该路由
                                redirect: route.currentRoute.fullPath
                            } 
                        });
                    })
            }
        }
        return Promise.reject(error) // 返回接口返回的错误信息
    }
);

//注册
export const regist = params => {
    return axios.post('http://localhost:3333/user/regist', params, {}).then(res => res.data)
}

//登录
export const login = params => {
    return axios.post('http://localhost:3333/user/login', params, {}).then(res => res.data)
}

//获取用户信息
export const getUserInfo = params => {
    return axios.post('http://localhost:3333/user/getUserInfo', params, {}).then(res => res.data)
}

  axios.interceptors.request.use拦截请求,给请求头加上token

  axios.interceptors.response.use拦截响应,如果返回401,token过期,跳回login路由

  登录后10s再请求用户数据,返回登录过期:

  

总结:完结撒花,如有不当指出,欢迎各位大神指出,我该铲屎去了 ~.~

  

  

  

  

 

 

猜你喜欢

转载自www.cnblogs.com/pzxnm/p/10500083.html