二十一、SpringBoot + Jwt + Vue 权限管理系统(1)

后端人员直接跳过一二三节,可从第四节开始,前端部署可看第三节最后步骤

前端所有完整代码

一、Vue 前端搭建

文章使用:

前后端工具ideaIU-2018.2.5下载

拓展使用:

前端工具HBuilder下载

前端使用工具VSCode下载

1.1 安装vue环境,并新建vue项目

对比参考 👉🏾👉🏾二、项目环境搭建

  1. 安装 node js
  2. 安装vue的脚手架工具 npm install -g vue-cli/cnpm install -g vue-cli
  3. 创建项目,必须到对应的一个项目里面 vue init webpack vue-admin
  4. 生成node_modules文件 cnpm installnpm install
  5. 运行项目 npm run dev

前端依然选择的是 Vue+ElementUI 的组合,主要还是因为这个主流呀。针对Vue,如果还不熟悉的同胞们,建议去看看这个视频 【4个小时带你快速入门vue】,或 狂神讲解的这个 我也是学这个视频入门的,就学了半天,哈哈。主要Js基础扎实点学起来挺快的。

首先我们安装vue的环境,我实践的环境是windows 10哈。
1、首先我们上 node.js官网,下载最新的长期版本,直接运行安装完成之后,我们就已经具备了node和npm的环境啦。

在这里插入图片描述

安装成功测试 👇🏾👇🏾 安装参考 一、Node 的安装

在这里插入图片描述

这就表示你已经安装成功啦,牛逼开始的第一步!

2、接下来,我们安装vue的环境

# 安装淘宝npm
npm install -g cnpm --registry=https://registry.npm.taobao.org
# vue-cli 安装依赖包
cnpm install --g vue-cli
# 打开vue的可视化管理工具界面
vue ui

在这里插入图片描述

上面我们分别安装了淘宝npm,cnpm是为了提高我们安装依赖的速度。vue ui是@vue/cli3.0增加一个可视化项目管理工具,可以运行项目、打包项目,检查等操作。对于初学者来说,可以少记一些命令,哈哈。

出现的问题解决 👉🏾👉🏾七、使用vue ui命令没有反应

3、从而跳转至vue可视化工具界面,创建vue-admin前端项目

  1. 会为我们打开一个http://localhost:8080 的页面:
    我们将在这个页面完成我们的前端Vue项目的新建。然后切换到【创建】,注意创建的目录最好是和你运行vue ui同一级。这样方便管理和切换。

在这里插入图片描述

  1. 然后点击按钮【在此创建新项目】下一步中,项目文件夹中输入项目名称“vue-admin”,其他不用改。

在这里插入图片描述

  1. 点击下一步,选择【手动】,再点击下一步,如图点击按钮,勾选上路由Router、状态管理Vuex,去掉js的校验。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 下一步中,也选上【Use history mode for router】,点击创建项目,然后弹窗中选择按钮【创建项目,不保存预设】,就进入项目创建啦。

在这里插入图片描述

  1. 稍等片刻之后,项目就初始化完成了。上面的步骤中,我们创建了一个vue项目,并且安装了Router、Vuex。这样我们后面就可以直接使用。
  • Router: WebApp的链接路径管理系统,简单就是建立起url和页面之间的映射关系
  • Vuex: 一个专为 Vue.js 应用程序开发的状态管理模式,简单来说就是为了方便数据的操作而建立的一个临时” 前端数据库“,用于各个组件间共享和检测数据变化。

在这里插入图片描述

  1. 创建成功并运行此项目,显示如下

在这里插入图片描述
在这里插入图片描述

1.2 导入项目到IDEA中

注: 前提是idea中安装vue.js插件

在这里插入图片描述

1.3 安装 Element-ui 全局样式

接下来我们引入 element-ui组件,这样我们就可以获得好看的vue组件,开发好看的后台管理系统的界面啦~

在这里插入图片描述

命令很简单:

# 切换到项目根目录
cd vue admin-vue
# 或者直接在idea中执行下面命令
# 安装element-ui
cnpm install element-ui --save

在这里插入图片描述

然后我们打开项目src目录下的main.js,引入element-ui依赖。

import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);

在这里插入图片描述
拷贝到 main.js

在这里插入图片描述

这样我们就可以愉快得在官网上选择组件复制代码到我们项目中直接使用啦。

1.4 安装axios、qs、mockjs

  • axios:一个基于 promise 的 HTTP 库,类ajax
  • qs:查询参数序列化和解析库
  • mockjs:为我们生成随机数据的工具库
  1. 接下来,我们来安装 axios,axios是一个基于 promise 的 HTTP 库,这样我们进行前后端对接的时候,使用这个工具可以提高我们的开发效率。安装命令:
cnpm install axios --save
  1. 然后同样我们在main.js中全局引入axios。
import axios from 'axios'
Vue.prototype.$axios = axios 

在这里插入图片描述

  1. 组件中,我们就可以通过 this.$axios.get()来发起我们的请求了哈。当然了,后面我们添加axios拦截的时候我们需要修改引入的编写。 同时,我们同步安装一个qs,什么是qs?qs是一个流行的查询参数序列化和解析库。可以将一个普通的object序列化成一个查询字符串,或者反过来将一个查询字符串解析成一个object,帮助我们查询字符串解析和序列化字符串。
cnpm install qs --save
  1. 然后因为后台我们现在还没有搭建,无法与前端完成数据交互,因此我们这里需要mock数据,因此我们引入 mockjs,方便后续我们提供api返回数据。
cnpm install mockjs --save-dev

在这里插入图片描述

  1. 然后我们在src目录下新建mock.js文件,用于编写随机数据的api,然后我们需要在main.js中引入这个文件:
  • src/main.js
require("./mock"); //引入mock数据,关闭则注释该行

在这里插入图片描述

后面我们mackjs会自动为我们拦截ajax,并自动匹配路径返回数据!

  1. 删除下面生成的三个vue

在这里插入图片描述

1.5 页面路由

接下来,在开发页面之前我们需要先定义路由。传统项目开发,我们都是通过链接到达控制器然后再到页面渲染的。而类似于Vue这样的前后端分离性质的框架,我们是先访问页面,然后再异步加载数据渲染。而在Vue中,路由的管理是有个专门的组件叫 Router 管理的。我们在新建项目的时候也提了一下,大家还记得吧。我们当时说:

  • Router:WebApp的链接路径管理系统,简单就是建立起url和页面之间的映射关系

所以我们要打开页面然后开发页面,我们需要先配置路由,然后再开发,这样我们可以试试看到效果。项目中,src\router\index.js 就是用来配置路由的。我们在views文件夹下定义几个页面:

  • Login.vue(登录页面)
  • Index.vue(首页)

我们新建Vue页面的时候可以这样新建:

在这里插入图片描述
在这里插入图片描述

启动项目 npm run serve

在这里插入图片描述
在这里插入图片描述

二、后台登陆界面开发

2.1 页面布局建设

  1. 拷贝element-ui布局到Login.vue中,并做修改,提前导入二维码图片到 assets 下面

在这里插入图片描述

element-ui 引入assets文件夹下面的图片

<el-image :src="require('@/assets/public.jpg')"></el-image>
  1. 垂直线的引入

在这里插入图片描述

  1. 登录框的引入

在这里插入图片描述
由1.2.3 引入代码

Login.vue

<template>
    <el-row type="flex" class="row-bg" justify="center">
        <el-col :span="6">

            <h3>欢迎来到Daniel的vue-admin管理系统</h3>
            <el-image :src="require('@/assets/public.jpg')"></el-image>
            <p>公众号 爪哇知识库</p>
            <p> 扫码二维码,即可获取更多精彩文章</p>

        </el-col>

        <el-col :span="1">
            <el-divider direction="vertical"></el-divider>
        </el-col>

        <el-col :span="6">
            <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
                <el-form-item label="用户名" prop="username">
                    <el-input v-model="ruleForm.username"></el-input>
                </el-form-item>

                <el-form-item label="密码" prop="password">
                    <el-input v-model="ruleForm.password"></el-input>
                </el-form-item>

                <el-form-item label="验证码" prop="code">
                    <el-input v-model="ruleForm.code"></el-input>
                    <el-image src=""></el-image>
                </el-form-item>

                <el-form-item>
                    <el-button type="primary" @click="submitForm('ruleForm')">立即创建</el-button>
                    <el-button @click="resetForm('ruleForm')">重置</el-button>
                </el-form-item>
            </el-form>
        </el-col>
    </el-row>
</template>

<script>
    export default {
        name: "Login.vue",

        data() {
            return {
                ruleForm: {
                    username: '',
                    password: '',
                    code: '',
                },
                rules: {
                    username: [
                        { required: true, message: '请输入用户名', trigger: 'blur' }
                    ],
                    password: [
                        { required: true, message: '请输入密码', trigger: 'blur' }
                    ],
                    code: [
                        { required: true, message: '请输入验证码', trigger: 'blur' },
                        { min: 5, max: 5, message: '长度为5个字符', trigger: 'blur' }
                    ]

                }
            };
        },
        methods: {
            submitForm(formName) {
                this.$refs[formName].validate((valid) => {
                    if (valid) {
                        alert('submit!');
                    } else {
                        console.log('error submit!!');
                        return false;
                    }
                });
            },
            resetForm(formName) {
                this.$refs[formName].resetFields();
            }
        }
    }
</script>

<style scoped>

</style>

通过 F12 查看模型加恰当的样式,如下

在这里插入图片描述

<style >

    .el-row{

        background-color: #fafafa;
        height: 600px;
        display: flex;
        align-items: center;
        text-align: center;
        justify-content: center;
    }

    .el-divider{

        height: 200px;

    }

    .captchaImg {
        float: left;
        margin-left: 8px;
        border-radius: 4px;
    }
    
</style>

刷新显示 http://localhost:8080/Login

在这里插入图片描述
在这里插入图片描述

完整代码:

<template>

    <el-row type="flex" class="row-bg" justify="center">
        <el-col :span="6">

            <h3>欢迎来到Daniel的vue-admin管理系统</h3>
            <el-image :src="require('@/assets/public.jpg')" style="width: 180px; height: 180px" ></el-image>
            <p>公众号 爪哇知识库</p>
            <p> 扫码二维码,即可获取更多精彩文章</p>

        </el-col>

        <el-col :span="1">
            <el-divider direction="vertical"></el-divider>
        </el-col>

        <el-col :span="6">
            <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
                <el-form-item label="用户名" prop="username" style="width: 340px;">
                    <el-input v-model="ruleForm.username"></el-input>
                </el-form-item>

                <el-form-item label="密码" prop="password" style="width: 340px;">
                    <el-input v-model="ruleForm.password" ></el-input>
                </el-form-item>

                <el-form-item label="验证码" prop="code" style="width: 340px;">
                    <el-input v-model="ruleForm.code" style="width: 172px; float: left" maxlength="5"></el-input>
                    <el-image :src="captchaImg" class="captchaImg"></el-image>
                </el-form-item>

                <el-form-item>
                    <el-button type="primary" @click="submitForm('ruleForm')">立即创建</el-button>
                    <el-button @click="resetForm('ruleForm')">重置</el-button>
                </el-form-item>
            </el-form>
        </el-col>
    </el-row>
</template>

<script>
    export default {
        name: "Login.vue",

        data() {
            return {
                ruleForm: {
                    username: '',
                    password: '',
                    code: '',
                    token:'',
                },
                rules: {
                    username: [
                        { required: true, message: '请输入用户名', trigger: 'blur' }
                    ],
                    password: [
                        { required: true, message: '请输入密码', trigger: 'blur' }
                    ],
                    code: [
                        { required: true, message: '请输入验证码', trigger: 'blur' },
                        { min: 5, max: 5, message: '长度为5个字符', trigger: 'blur' }
                    ]
                },
                captchaImg:null
            };
        },
        methods: {
            submitForm(formName) {
                this.$refs[formName].validate((valid) => {
                    if (valid) {
                        //alert('submit!');
                        this.$axios.post('/login',this.ruleForm).then(resp=>{

                        })
                    } else {
                        console.log('error submit!!');
                        return false;
                    }
                });
            },
            resetForm(formName) {
                this.$refs[formName].resetFields();
            },
            //验证码
            getCaptcha(){
                this.$axios.post('/captcha').then(resp=>{
                    this.ruleForm.token = resp.data.data.code;
                    this.captchaImg = resp.data.data.captchaImg
                })
            }
        }
    }
</script>

<style >

    .el-row{
        background-color: #fafafa;
        height: 600px;
        display: flex;
        align-items: center;
        text-align: center;
        justify-content: center;
    }
    .el-divider{

        height: 200px;

    }

    .captchaImg {
        float: left;
        margin-left: 8px;
        border-radius: 4px;
    }
</style>

没有后端的时候我们怎么请求呢,看下面 👇🏾👇🏾

2.2 发起登录请求,Mock测试返回结果

因为后台系统我们暂时还没有开发,所以这里我们需要自己mock数据完成交互。前面我们已经引入了mockjs,所以我们到mock.js文件中开发我们的api。

登录交互过程
我们梳理一下交互流程:

  1. 浏览器打开登录页面
  2. 动态加载登录验证码,因为这是前后端分离的项目,我们不再使用session进行交互,所以后端我打算禁用session,那么验证码的验证就是问题了,所以后端设计上我打算生成验证码同时生成一个随机码,随机码作为key,验证码为value保存到redis中,然后把随机码和验证码图片的Base64字符串码发送到前端
  3. 前端提交用户名、密码、验证码还有随机码
  4. 后台验证验证码是否匹配以及密码是否正确
    在这里插入图片描述

ok,这样我们就知道mock应该弄成什么样的api了。

mock 官网

  • mock.js - 获取登录验证码(直接拷贝下面代码到你的 mock.js中即可)
// 引入mockjs
const Mock = require('mockjs');
// 获取 mock.Random 对象
// 参考:https://github.com/nuysoft/Mock/wiki/Mock.Random
const Random = Mock.Random;

let Result = {
    code: 200,
    msg: '操作成功',
    data: null
};
/** *
 * Mock.mock( url, post/get , function(options));
 * url 表示需要拦截的 URL,
 * post/get 需要拦截的 Ajax 请求类型
 * 用于生成响应数据的函数
 */
// 获取验证码图片base64编码以及一个随机码
Mock.mock('/captcha', 'post', () => {
    Result.data = {
        token: Random.string(32), // 获取一个32位的随机字符串,
        captchaImg: Random.dataImage("120x40", "p7n5w") //生成验证码为11111的base64图片编码
    };
    return Result
}

mock生成数据还算简单,一般都是利用Mock.Random对象来生成一些随机数据,具体的用法可以参考 这里。然后Result是为了统一返回结果,因为后台设计的时候,前后端交互,一般都有固定的返回格式,所以就有了Result。

在这里插入图片描述

2.3 mock.js - 登录接口测试

Mock我们不需要什么处理,只需要放回的数据符合前端的要求就行哈。这样我们前端就可以继续往后面开发。

然后编写登录页面的js

  • src/views/Login.vue

在这里插入图片描述

token的状态同步再总结一下,submitForm方法中,提交表单之后做了几个动作,从Header中获取用户的authorization,也就是含有用户登录信息的jwt,然后提交到store中进行状态管理。

①④ Login.vue

this.$axios.post('/login',this.ruleForm).then(resp=>{
    const  jwt = resp.headers['authorization'];
    // 将jwt存储到应用store中
    this.$store.commit('SET_TOKEN',jwt)

    this.$router.push("/index")
})

this.$store.commit(“SET_TOKEN”, jwt)表示调用store中的SET_TOKEN方法,所以我们需要在store中编写方法:

② index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {

    token:''

  },
  getters: {
  },
  mutations: {

    SET_TOKEN:(state,token)=>{
      state.token = token;
      localStorage.setItem("token",token)
    }
  },
  actions: {
  },
  modules: {
  }
})

这样登录之后获取到的jwt就可以存储到应用的store以及localStorage中,方便使用直接从localStorage中获取即可! 这样用户登录成功之后就会跳转到/index页面this.$router.push("/index")

⑤ mock.js

Mock.mock('/login', 'post',() => {

    return Result
});

测试:随便输入用户名密码验证码 点击跳转至首页

在这里插入图片描述

附 Login.vue当前完整代码(调整了下用户密码验证码输入框的样式):

<template>

    <el-row type="flex" class="row-bg" justify="center">
        <el-col :span="6">

            <h3>欢迎来到Daniel的vue-admin管理系统</h3>
            <el-image :src="require('@/assets/public.jpg')" style="width: 180px; height: 180px" ></el-image>
            <p>公众号 爪哇知识库</p>
            <p> 扫码二维码,即可获取更多精彩文章</p>

        </el-col>

        <el-col :span="1">
            <el-divider direction="vertical"></el-divider>
        </el-col>

        <el-col :span="6">
            <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
                <el-form-item label="用户名" prop="username" style="width: 380px;">
                    <el-input v-model="ruleForm.username"></el-input>
                </el-form-item>

                <el-form-item label="密码" prop="password" style="width: 380px;">
                    <el-input v-model="ruleForm.password" ></el-input>
                </el-form-item>

                <el-form-item label="验证码" prop="code" style="width: 380px;">
                    <el-input v-model="ruleForm.code" style="width: 180px; float: left" maxlength="5"></el-input>
                    <el-image :src="captchaImg" class="captchaImg" style="width: 86px;height: 38px"></el-image>
                </el-form-item>

                <el-form-item>
                    <el-button type="primary" @click="submitForm('ruleForm')">立即创建</el-button>
                    <el-button @click="resetForm('ruleForm')">重置</el-button>
                </el-form-item>
            </el-form>
        </el-col>
    </el-row>
</template>

<script>
    export default {
        name: "Login.vue",

        data() {
            return {
                ruleForm: {
                    username: '',
                    password: '',
                    code: '',
                    token:''
                },
                rules: {
                    username: [
                        { required: true, message: '请输入用户名', trigger: 'blur' }
                    ],
                    password: [
                        { required: true, message: '请输入密码', trigger: 'blur' }
                    ],
                    code: [
                        { required: true, message: '请输入验证码', trigger: 'blur' },
                        { min: 5, max: 5, message: '长度为5个字符', trigger: 'blur' }
                    ]
                },
                captchaImg:null
            };
        },
        methods: {
            submitForm(formName) {
                this.$refs[formName].validate((valid) => {
                    if (valid) {
                        //alert('submit!');
                        this.$axios.post('/login',this.ruleForm).then(resp=>{
                            const  jwt = resp.headers['authorization'];
                            // 将jwt存储到应用store中
                            this.$store.commit('SET_TOKEN',jwt)

                            this.$router.push("/index")
                        })
                    } else {
                        console.log('error submit!!');
                        return false;
                    }
                });
            },
            resetForm(formName) {
                this.$refs[formName].resetFields();
            },
            //验证码
            getCaptcha(){
                this.$axios.get('/captcha').then(resp=>{

                    console.log('/captcha');
                    console.log(resp);

                    this.ruleForm.token = resp.data.data.token; //resp.data 表示结果再.data表示结果里面的一个属性
                    this.captchaImg = resp.data.data.captchaImg
                })
            }
        },
        created(){
            this.getCaptcha();
        }
    }
</script>

<style >

    .el-row{
        background-color: #fafafa;
        height: 600px;
        display: flex;
        align-items: center;
        text-align: center;
        justify-content: center;
    }
    .el-divider{

        height: 200px;

    }

    .captchaImg {
        float: left;
        margin-left: 8px;
        border-radius: 4px;
    }
</style>

2.4 axios请求前后置拦截处理

这里有个问题,那么如果登录失败,我们是需要弹窗显示错误的,比如验证码错误,用户名或密码不正确等。不仅仅是这个登录接口,所有的接口调用都会有这个情况,所以我们想做个拦截器,对返回的结果进行分析,如果是异常就直接弹窗显示错误,这样我们就省得每个接口都写一遍了。

在src目录下创建一个文件axios.js(与main.js同级),定义axios的拦截:

  • src/axios.js

JS/HTML格式化

axios.js

import axios from "axios";
import Element from 'element-ui'

//axios.defaults.baseURL = "http://localhost:8081";
const request = axios.create({
    timeout: 5000,
    headers: {
        'Content-Type': 'application/json; charset=utf-8'
    }
});
request.interceptors.request.use(config => {
    config.headers['Authorization'] = localStorage.getItem("token") // 请求头带上token
    return config
});
request.interceptors.response.use(
                response => {

    console.log("response ->"+response);

    let res = response.data;

    if (res.code === 200) {
        return response
    } else {
        Element.Message.error(!res.msg ? '系统异常':res.msg);
        return Promise.reject(response.data.msg)
    }
},
    error => {
        console.log(error);
        if (error.response.data) {
            error.message = error.response.data.msg
        }
        if (error.response.status === 401) {
            router.push("/login")
        }
        Element.Message.error(error.message, {duration: 3 * 1000});
        return Promise.reject(error)
    }
);
export default request

前置拦截,其实可以统一为所有需要权限的请求装配上header的token信息,后置拦截中,判断status.code和error.response.status,如果是401未登录没权限的就调到登录页面,其他的就直接弹窗显示错误。

  • 然后再main.js中导入axios.js,同时,记得去掉我们之前添加的

在这里插入图片描述

这样axios每次请求都会被前置拦截器和后置拦截器拦截了。 登录异常弹窗效果如下:

在这里插入图片描述

当前文章完整源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Daniel521-Spark

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值