实现前端后端分离,在 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-formautocomplete="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-inputtype="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-buttontype="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-consoleconsole.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//   };// }, //使用这种方式也可以显示 msgdata() {return {msg: null};},created() {this.msg = this.$route.query.message;}
};
</script>

五、路由

页面写好了,我们需要依次显示这三个页面,这里我们统一使用路由来管理显示页面,路由的官方文档见:vue 路由

本着先实践,后理解的码农学习方式。我们先使用路由显示三个页面后,再去理解 Vue 路由这个功能点。

1. 创建路由配置文件

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

import Vue from 'vue' //引入 Vue
import 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 = falsenew 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' //引入 axios
import baseUrl from '../api/baseUrl' //使用环境变量 + 模式的方式定义基础URL// 创建 axios 实例
const service = axios.create({baseURL: baseUrl, // api 的 base\_urltimeout: 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) => { //封装一个 Promiselogin(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' //引入 Vue
import Vuex from 'vuex' //引入 Vuex
import user from './modules/user' //引入 user moduleVue.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 = falsenew 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 注解。

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

3.IErrorCode 接口

Java 版本

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

Kotlin 版本

interface IErrorCode {fun getCode(): Longfun 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 = 0var message: String? = nullvar data: T? = nullconstructor(code: Long, message: String, data: T?) {this.code = codethis.message = messagethis.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 版本

@RestController
public 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");elsereturn CommonResult.validateFailed();}
}

Kotlin 版本

@RestController //此注解是 @ResponseBody 和 @Controller 的组合注解,可返回一个 JSON
class 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

另外准备了500套应用实战代码,关注二维码微信公众号,回复「java」获取

Springboot Vue Login(从零开始实现Springboot+Vue登录)相关推荐

  1. 【024】Vue+Springboot+mysql员工考勤管理系统(多角色登录、请假、打卡)(含源码、数据库、运行教程、实验报告)

    前排提示:项目源码已放在文末 基于Vue+Springboot+mysql员工考勤管理系统(多角色登录.请假.打卡) 开发环境:Springboot+Mysql+Vue+Nodejs+Maven+JD ...

  2. SpringBoot实现分页查询——基于SpringBoot和Vue的后台管理系统项目系列博客(七)

    系列文章目录 系统功能演示--基于SpringBoot和Vue的后台管理系统项目系列博客(一) Vue2安装并集成ElementUI--基于SpringBoot和Vue的后台管理系统项目系列博客(二) ...

  3. vue写ajax访问springboot后台发送和接收数据

    vue写ajax访问springboot后台发送和接收数据 遇到的问题 一.没有引入js,以前一直使用公司封装后的,现在自己建立项目忘记引入很多js. 二.JS的顺序不能变,在用 this.$http ...

  4. springboot+vue全栈开发_springboot+vue(一)___开发环境以及前后端项目搭建

    nodejs安装 安装: nodejs官网地址:https://nodejs.org/en/ 安装node.js,安装路径我默认安装在C盘  ,可以改变路径 安装配置全局安装路径和缓存 现在配置全局模 ...

  5. Springboot毕设项目理财管理系统mnl7cjava+VUE+Mybatis+Maven+Mysql+sprnig)

    Springboot毕设项目理财管理系统mnl7cjava+VUE+Mybatis+Maven+Mysql+sprnig) 项目运行 环境配置: Jdk1.8 + Tomcat8.5 + Mysql ...

  6. Springboot毕设项目股票交易模拟系统76wrijava+VUE+Mybatis+Maven+Mysql+sprnig)

    Springboot毕设项目股票交易模拟系统76wrijava+VUE+Mybatis+Maven+Mysql+sprnig) 项目运行 环境配置: Jdk1.8 + Tomcat8.5 + Mysq ...

  7. Springboot毕设项目基于springboot的模拟面试平台 7tch0java+VUE+Mybatis+Maven+Mysql+sprnig)

    Springboot毕设项目基于springboot的模拟面试平台 7tch0java+VUE+Mybatis+Maven+Mysql+sprnig) 项目运行 环境配置: Jdk1.8 + Tomc ...

  8. springboot+vue计算机旅游管理系统 springboot+vue旅游网

    springboot+vue计算机旅游管理系统 springboot+vue旅游网 包含前台用户系统和后台管理系统 源码: vx:daihq713

  9. Springboot毕设项目出租车管理系统qlk13java+VUE+Mybatis+Maven+Mysql+sprnig)

    Springboot毕设项目出租车管理系统qlk13java+VUE+Mybatis+Maven+Mysql+sprnig) 项目运行 环境配置: Jdk1.8 + Tomcat8.5 + Mysql ...

最新文章

  1. 年薪40-100万 | 北京思无界科技招聘三维重建算法工程师
  2. lnmp.org + phpstorm + xdebug
  3. 现在计算机学什么好找工作吗,计算机专业都学什么 毕业好找工作吗
  4. 4行代码AC——L1-024 后天(5分)
  5. Spring AOP功能和目标
  6. linux下怎么编译动态库并且调用
  7. 在电脑上显示未知发布者怎么办_笔记本电脑显示器花屏怎么办?电脑屏幕花屏的解决方法...
  8. 电力企业信息化系统之调度信息报送系统整体解决方案
  9. oracle数据库无法查询,关于oracle中无法查询中文条件的解决办法
  10. 两个正数相乘为什么结果是负数
  11. atitit.数据验证--db数据库数据验证约束
  12. hbase的协处理器
  13. redhat红帽操作系统下载
  14. 【遗传编程/基因规划】python DEAP框架学习笔记
  15. SPSS学习笔记(二)T检验
  16. java2的n次方表达式,某个数是2的N次方
  17. Git 修改提交者信息
  18. easyexcel自定义拦截器,实现自定义单元格样式
  19. iOS开发:GitHub上传代码错误提示fatal: Authentication failed for 'https://gitee.com/XXX/XXX.git/‘的解决方法
  20. 态度决定高度,高度决定命运。对自己要狠一点,再狠一点,因为,你要的比别人多,就必须付出得比别人多。...

热门文章

  1. IOS 创建简单表视图
  2. 问题2 String类equals 和 “==” 比较
  3. nhibernate事务锁表的问题
  4. 基于COM的矢量图像控件VectorDraw
  5. 详解C#防访问修饰符:public,private,protected,internal
  6. android资源收藏贴[持续更新]
  7. 线段树、优先队列、单调队列小结
  8. Windows Live Writer 测试
  9. 关于JavaScript中cookie的用法例子
  10. JS-原型-原型链-值和引用类型