小Hub领读:

一个残缺的Spirngboot+vue实现登录的小例子,我之前在vueblog中也搞过,哈哈,再来回顾一下!


作者:Eli Shaw

https://blog.csdn.net/xiaojin...

一、简述

最近学习应用 Vue 实现前端后端拆散,在 Github 上有一个很好的开源我的项目:mall,正所谓百看不如一练,本人入手实现了一个 Springboot+Vue 的登录操作,在此记录一下踩过的坑。

文章最初补充两端的 GitHub 代码,之所以放在最初,是因为文章写的很粗疏了,入手操作一下会更有帮忙,如果有很大出入能够比对原码,找找问题。

二、开发工具

VSCode

IDEA

Vue 的装置就不说了,有很多文章,然而 Springboot+Vue 整合的残缺文章绝对较少,所以我次要记录一下这两端整合时的内容。

(Vue 装置后就会有 npm 或 cnpm,相应的介绍也不说了,Vue 官网可查看)

一、关上 cmd 创立 Vue 我的项目,并增加 Vue 依赖的框架:

1\. 创立 Vue 我的项目 (进入本人想创立的文件夹地位,我放在 D:\VSCodeWorkSpace),创立语句 vue create vue-spring-login-summed,方向键抉择创立形式,我抉择的默认

2\. 进入到创立的 Vue 我的项目目录,增加依赖框架:

cd vue-spring-login-summed (进入到我的项目根目录)vue add element (增加 element,一个 element 格调的 UI 框架)npm install axios (装置 axios,用于网络申请)npm install vuex --save(装置 Vuex,用于治理状态)npm install vue-router (装置 路由,用于实现两个 Vue 页面的跳转)

以上命令截图如下:

1) 增加 Element

2) 增加 axios

3) 增加 Vuex

4) 增加 路由

到此相干依赖的架包增加结束,输出 code . 关上 VSCode

二、增加目录构造

在 VSCode 下看到 Vue 整体我的项目构造如下

当初须要创立相应性能的目录构造,进行分层开发,须要在 src 目录下创立上面几个目录

api (网络申请接口包)router (路由配置包)store (Vuex 状态治理包)utils (工具包)views (vue 视图包,寄存所有 vue 代码,可依据功能模块进行相应分包)

创立后的目录构造如下

三、运行我的项目

当初能够运行我的项目了,在 VSCode 菜单栏顺次抉择:终端 —— 运行工作...

这里应用的是 serve 模式,即开发模式运行的我的项目

在浏览器输出:http://localhost:8080/

这是 Vue 默认的页面,代表我的项目创立胜利了,在进行代码开发前,先贴上我的项目整体构造,避免不晓得在哪创立

四、View 层代码编写

编写三个 vue 文件:login.vue(登录页面)、success.vue(登录胜利页面)、error.vue(登录失败页面)

1.login.vue 

代码如下 (比拟懒,间接从 mall 扒下来的代码,去掉了一些性能)

<template>  <div>    <el-card>      <el-form        autocomplete="on"        :model="loginForm"        ref="loginForm"        label-position="left"      >        <div>          <svg-icon icon-class="login-mall"></svg-icon>        </div>        <h2>mall-admin-web</h2>        <el-form-item prop="username">          <el-input                        type="text"            v-model="loginForm.username"            autocomplete="on"            placeholder="请输出用户名"          >            <span slot="prefix">              <svg-icon icon-class="user"></svg-icon>            </span>          </el-input>        </el-form-item>        <el-form-item prop="password">          <el-input                        :type="pwdType"            @keyup.enter.native="handleLogin"            v-model="loginForm.password"            autocomplete="on"            placeholder="请输出明码"          >            <span slot="prefix">              <svg-icon icon-class="password"></svg-icon>            </span>            <span slot="suffix" @click="showPwd">              <svg-icon icon-class="eye"></svg-icon>            </span>          </el-input>        </el-form-item>        <el-form-item>          <el-button                       type="primary"            :loading="loading"            @click.native.prevent="handleLogin"          >登录</el-button>        </el-form-item>      </el-form>    </el-card>  </div></template> <script>export default {  name: "login",  data() {    return {      loginForm: {        username: "admin",        password: "123456"      },      loading: false,      pwdType: "password",    };  },  methods: {    showPwd() {      if (this.pwdType === "password") {        this.pwdType = "";      } else {        this.pwdType = "password";      }    },    handleLogin() {      this.$refs.loginForm.validate(valid => {        if (valid) {          this.loading = true;          this.$store            .dispatch("Login", this.loginForm)            .then(response => {              this.loading = false;              let code = response.data.code;              if (code == 200) {                this.$router.push({                  path: "/success",                  query: { data: response.data.data }                });              } else {                this.$router.push({                  path: "/error",                  query: { message: response.data.message }                });              }            })            .catch(() => {              this.loading = false;            });        } else {          // eslint-disable-next-line no-console          console.log("参数验证不非法!");          return false;        }      });    }  }};</script> <style scoped>.login-form-layout {  position: absolute;  left: 0;  right: 0;  width: 360px;  margin: 140px auto;  border-top: 10px solid #409eff;} .login-title {  text-align: center;} .login-center-layout {  background: #409eff;  width: auto;  height: auto;  max-width: 100%;  max-height: 100%;  margin-top: 200px;}</style>

2.success.vue

<template>  <div>    <h1>Welcome!{{msg}}</h1>  </div></template><script>export default {  data() {    return {      msg: this.$route.query.data    };  },//   data() { //这种形式也能够//     return {//       msg: null//     };//   },  // created() {  //   this.msg = this.$route.query.data;  // }}</script>

3.error.vue

<template>  <div>    <h1>登录谬误:{{msg}}</h1>  </div></template><script>export default {  // data() {  //   return {  //     msg: this.$route.query.data  //   };  // }, //应用这种形式也能够显示 msg  data() {    return {      msg: null    };  },  created() {    this.msg = this.$route.query.message;  }};</script>

五、路由

页面写好了,咱们须要顺次显示这三个页面,这里咱们对立应用路由来治理显示页面,路由的官网文档见:vue 路由

本着先实际,后了解的码农学习形式。咱们先应用路由显示三个页面后,再去了解 Vue 路由这个性能点。

1\. 创立路由配置文件

在方才建设的 router 文件夹下创立一个 index.js 文件,内容如下

import Vue from 'vue' //引入 Vueimport VueRouter from 'vue-router' //引入 Vue 路由 Vue.use(VueRouter); //装置插件 export const constantRouterMap = \[    //配置默认的门路,默认显示登录页    { path: '/', component: () => import('@/views/login')},     //配置登录胜利页面,应用时须要应用 path 门路来实现跳转    { path: '/success', component: () => import('@/views/success')},     //配置登录失败页面,应用时须要应用 path 门路来实现跳转    { path: '/error', component: () => import('@/views/error'), hidden: true }\] export default new VueRouter({    // mode: 'history', //后端反对可开    scrollBehavior: () => ({ y: 0 }),    routes: constantRouterMap //指定路由列表})

2\. 将路由增加到程序入口

路由配置文件写好,咱们须要把他引入到 main.js 中,在我的项目的 src 目录根节点下,找到 main.js,增加内容如下:

import Vue from 'vue'import App from './App.vue'import './plugins/element.js'import router from './router' //引入路由配置 Vue.config.productionTip = false new Vue({  render: h => h(App),  router, //应用路由配置}).$mount('#app')

3\. 配置路由的出入口

当初路由曾经齐全引入到我的项目了,然而路由还须要一个出入口,这个出入口用来通知路由将路由的内容显示在这里。下面 main.js 配置的第一个 vue 显示页面为 App.vue ,因而咱们批改 App.vue 内容如下

<template>  <div>    <!-- 路由的出入口,路由的内容将被显示在这里 -->    <router-view/>  </div></template> <script>  export default {    name: 'App'  }</script>

<router-view/> 就是显示路由的出入口。

当初保留 App.vue 文件后,以后我的项目会被从新装载运行,在方才浏览的界面就会看到登录界面如下:

4\. 路由跳转

在 login.vue 中能够应用 this.$router.push({path: "门路"}) 来跳转到指定门路的路由组件中,上面是通过路由跳转到 error.vue 与 success.vue 的代码

this.$router.push({path: "/success"}); //跳转到胜利页或this.$router.push({path: "/error"}); //跳转到失败页

六、应用 Vuex + Axios 形式进行网络申请

1.Axios

axios 是一个网络申请构架,官网举荐应用这种形式进行 http 的申请。

1) 在 utils 包下封装一个申请工具类 request.js

import axios from 'axios' //引入 axiosimport baseUrl from '../api/baseUrl' //应用环境变量 + 模式的形式定义根底URL // 创立 axios 实例const service = axios.create({  baseURL: baseUrl, // api 的 base\_url  timeout: 15000, // 申请超时工夫}) export default service

这里的 baseUrl 波及 Vue CLI3 的环境变量与模式的概念,见:Vue 环境变量和模式 (设置通用 baseUrl)

2) 登录申请接口 API

在 api 文件夹下,创立一个登录 API 文件:login.js

import request from '@/utils/request' //引入封装好的 axios 申请 export function login(username, password) { //登录接口  return request({ //应用封装好的 axios 进行网络申请    url: '/admin/login',    method: 'post',    data: { //提交的数据      username,      password    }  })}

2\. 应用 Vuex 封装 axios

Vuex 是一个状态治理构架,官网文档:Vuex

1) 封装 Vuex 中的 module

在 store 文件夹下创立一个 modules 文件夹,而后在此文件夹下创立一个 user.js 文件

import { login } from '@/api/login'//引入登录 api 接口 const user = {  actions: {    // 登录    Login({ commit }, userInfo) { //定义 Login 办法,在组件中应用 this.$store.dispatch("Login") 调用      const username = userInfo.username.trim()      return new Promise((resolve, reject) => { //封装一个 Promise        login(username, userInfo.password).then(response => { //应用 login 接口进行网络申请          commit('') //提交一个 mutation,告诉状态扭转          resolve(response) //将后果封装进 Promise        }).catch(error => {          reject(error)        })      })    },  }}export default user

这里的代码值得解释一下:官网文档对应:Vuex actions

1\. 首先引入 login 接口,之后应用登录接口进行网络申请。

2\. 定义一个 名为 Login 的 action 办法,Vue 组件通过 this.$store.dispatch("Login") 调用

3.Promise,这个类很有意思,官网的解释是 “store.dispatch 能够解决被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise”。这话的意思组件中的 dispatch 返回的仍是一个 Promise 类,因而揣测 Promise 中的两个办法 resolve() 与 reject() 别离对应 dispatch 中的 then 与 catch。

2) 创立 Vuex

在 store 文件夹下创立一个 index.js 文件

import Vue from 'vue' //引入 Vueimport Vuex from 'vuex' //引入 Vueximport user from './modules/user' //引入 user module Vue.use(Vuex) const store = new Vuex.Store({  modules: {    user //应用 user.js 中的 action  }}) export default store

3) 将 Vuex 增加到 main.js 文件

批改之前的 main.js 文件如下:

import Vue from 'vue'import App from './App.vue'import './plugins/element.js'import router from './router' //引入路由配置import store from './store' //引入 Vuex 状态治理 Vue.config.productionTip = false new Vue({  render: h => h(App),  router, //应用路由配置  store //应用 Vuex 进行状态治理}).$mount('#app')

从新运行我的项目,在 Chrome 浏览器中进入调试模式,点击登录按钮

能够看到有发送一个 8088 端口的申请,至此 Vue 端的所有代码曾经实现。

\-------------------------------Springboot 开发 -------------------------------

我的项目创立就不提了,网上有很多,只有应用 Spring Assistant 创立就好。

整体目录构造如下

1\. 在 application.yml 批改端口号

不要和 Vue 在一个 8080 端口上:

server:  port: 8088

2\. 解决跨域问题

这里有一个跨域问题,即 Vue 应用 8080 端口,要拜访 8088 端口的服务器,会报错。错误信息如下:

Access to XMLHttpRequest at 'http://localhost:8088/admin/login' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No'Access-Control-Allow-Origin' header is present on the requested resource.

这个问题在 Vue 端或在 Springboot 端解决都能够,我在 Springboot 端解决的,写一个 CorsConfig 类内容如下,不要忘了 @Configuration 注解。

@Configurationpublic class CorsConfig {    private CorsConfiguration buildConfig() {        CorsConfiguration corsConfiguration = new CorsConfiguration();        corsConfiguration.addAllowedOrigin("\*"); // 1        corsConfiguration.addAllowedHeader("\*"); // 2        corsConfiguration.addAllowedMethod("\*"); // 3        return corsConfiguration;    }     @Bean    public CorsFilter corsFilter() {        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();        source.registerCorsConfiguration("/\*\*", buildConfig()); // 4        return new CorsFilter(source);    }}

3.IErrorCode 接口

Java 版本

public interface IErrorCode {    long getCode();    String getMessage();}

Kotlin 版本

interface IErrorCode {    fun getCode(): Long    fun getMessage(): String}

4.CommonResult 类

Java 版本

public class CommonResult<T> {    private long code;    private String message;    private T data;     protected CommonResult() {    }     protected CommonResult(long code, String message, T data) {        this.code = code;        this.message = message;        this.data = data;    }     /\*\*     \* 胜利返回后果     \*     \* @param data 获取的数据     \*/    public static <T> CommonResult<T> success(T data) {        return new CommonResult<T>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);    }     /\*\*     \* 胜利返回后果     \*     \* @param data    获取的数据     \* @param message 提示信息     \*/    public static <T> CommonResult<T> success(T data, String message) {        return new CommonResult<T>(ResultCode.SUCCESS.getCode(), message, data);    }     /\*\*     \* 失败返回后果     \*     \* @param errorCode 错误码     \*/    public static <T> CommonResult<T> failed(IErrorCode errorCode) {        return new CommonResult<T>(errorCode.getCode(), errorCode.getMessage(), null);    }     /\*\*     \* 失败返回后果     \*     \* @param message 提示信息     \*/    public static <T> CommonResult<T> failed(String message) {        return new CommonResult<T>(ResultCode.FAILED.getCode(), message, null);    }     /\*\*     \* 失败返回后果     \*/    public static <T> CommonResult<T> failed() {        return failed(ResultCode.FAILED);    }     /\*\*     \* 参数验证失败返回后果     \*/    public static <T> CommonResult<T> validateFailed() {        return failed(ResultCode.VALIDATE\_FAILED);    }     /\*\*     \* 参数验证失败返回后果     \*     \* @param message 提示信息     \*/    public static <T> CommonResult<T> validateFailed(String message) {        return new CommonResult<T>(ResultCode.VALIDATE\_FAILED.getCode(), message, null);    }     /\*\*     \* 未登录返回后果     \*/    public static <T> CommonResult<T> unauthorized(T data) {        return new CommonResult<T>(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), data);    }     /\*\*     \* 未受权返回后果     \*/    public static <T> CommonResult<T> forbidden(T data) {        return new CommonResult<T>(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), data);    }     public long getCode() {        return code;    }     public void setCode(long code) {        this.code = code;    }     public String getMessage() {        return message;    }     public void setMessage(String message) {        this.message = message;    }     public T getData() {        return data;    }     public void setData(T data) {        this.data = data;    }}

Kotlin 版本

class CommonResult<T> {    var code: Long = 0    var message: String? = null    var data: T? = null     constructor(code: Long, message: String, data: T?) {        this.code = code        this.message = message        this.data = data    }     companion object {         /\*\*         \* 胜利返回后果         \* @param data 获取的数据         \*/        fun <T> success(data: T): CommonResult<T> {            return CommonResult(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data)        }         /\*\*         \* 胜利返回后果         \* @param data 获取的数据         \* @param  message 提示信息         \*/        fun <T> success(data: T, message: String): CommonResult<T> {            return CommonResult(ResultCode.SUCCESS.getCode(), message, data)        }         /\*\*         \* 失败返回后果         \* @param errorCode 错误码         \*/        fun <T> failed(errorCode: IErrorCode): CommonResult<T> {            return CommonResult<T>(errorCode.getCode(), errorCode.getMessage(), null)        }         /\*\*         \* 失败返回后果         \* @param message 提示信息         \*/        fun <T> failed(message: String): CommonResult<T> {            return CommonResult<T>(ResultCode.FAILED.getCode(), message, null)        }         /\*\*         \* 失败返回后果         \*/        fun failed(): CommonResult<Any> {            return failed(ResultCode.FAILED)        }         /\*\*         \* 参数验证失败返回后果         \*/        fun validateFailed(): CommonResult<Any> {            return failed(ResultCode.VALIDATE\_FAILED)        }         /\*\*         \* 参数验证失败返回后果         \* @param message 提示信息         \*/        fun <T> validateFailed(message: String): CommonResult<T> {            return CommonResult<T>(ResultCode.VALIDATE\_FAILED.getCode(), message, null)        }         /\*\*         \* 未登录返回后果         \*/        fun <T> unauthorized(data: T): CommonResult<T> {            return CommonResult(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), data)        }         /\*\*         \* 未受权返回后果         \*/        fun <T> forbidden(data: T): CommonResult<T> {            return CommonResult(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), data)        }    }}

5.ResultCode 枚举

Java 版本

public enum ResultCode implements IErrorCode {    SUCCESS(200, "操作胜利"),    FAILED(500, "操作失败"),    VALIDATE\_FAILED(404, "参数检验失败"),    UNAUTHORIZED(401, "暂未登录或token曾经过期"),    FORBIDDEN(403, "没有相干权限");    private long code;    private String message;     private ResultCode(long code, String message) {        this.code = code;        this.message = message;    }     public long getCode() {        return code;    }     public String getMessage() {        return message;    }}

Kotlin 版本

enum class ResultCode(private val code: Long, private val message: String) : IErrorCode {    SUCCESS(200, "操作胜利"),    FAILED(500, "操作失败"),    VALIDATE\_FAILED(404, "参数检验失败"),    UNAUTHORIZED(401, "暂未登录或token曾经过期"),    FORBIDDEN(403, "没有相干权限");     override fun getCode(): Long {        return code    }     override fun getMessage(): String {        return message    }}

6.User 类

Java 版本

public class User {     private int id;    private String username;    private String password;     public int getId() {        return id;    }     public void setId(int id) {        this.id = id;    }     public String getUsername() {        return username;    }     public void setUsername(String username) {        this.username = username;    }     public String getPassword() {        return password;    }     public void setPassword(String password) {        this.password = password;    }}

Kotlin 版本

data class User(        val id: Int,        val username: String,        val password: String)

7.LoginController 类

Java 版本

@RestControllerpublic class LoginController {     @RequestMapping(value = "/admin/login", method = RequestMethod.POST)    public CommonResult login(@RequestBody User user) {        if (user.getUsername().equals("admin") && user.getPassword().equals("123456"))            return CommonResult.success("admin");        else            return CommonResult.validateFailed();    }}

Kotlin 版本

@RestController //此注解是 @ResponseBody 和 @Controller 的组合注解,可返回一个 JSONclass LoginController {     @RequestMapping(value = \["/admin/login"\], method = \[RequestMethod.POST\])    fun admin(@RequestBody user: User): CommonResult<\*> {        return if (user.username == "admin" && user.password == "123456") {            CommonResult.success("admin")        } else {            CommonResult.validateFailed()        }    }}

启动两端程序

输出正确的账号密码

输出谬误的账号密码

七、GitHub 源码地址

vue 端:https://github.com/xiaojinlai/vue-spring-login-summed

Java 端:https://github.com/xiaojinlai/vue-login-java

Java 端 - Kotlin 版本:https://github.com/xiaojinlai/vue-login-kotlin

注:Kotlin 版本只是我自己用习惯了 Kotlin,就性能而言与 Java 是一样的。大家如果不喜爱能够不必理睬,如果有感兴趣的能够看看,Kotlin 是 Google 推出的一种简洁性语言,主推在 Android 上,用习惯后还是蛮喜爱的。学习起来也不难,内容也不多,举荐一个学习 Kotlin 的网址:https://www.kotlincn.net/docs/reference/


(完)

举荐浏览:

B站100K播放量,SpringBoot+Vue前后端拆散残缺入门教程!

分享一套SpringBoot开发博客零碎源码,以及残缺开发文档!速度保留!

Github上最值得学习的100个Java开源我的项目,涵盖各种技术栈!

2020年最新的常问企业面试题大全以及答案