共计 13858 个字符,预计需要花费 35 分钟才能阅读完成。
小 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' // 引入 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 = 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' // 引入 axios
import 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' // 引入 Vue
import Vuex from 'vuex' // 引入 Vuex
import 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 注解。
@Configuration
public 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 版本
@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");
else
return 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
注:Kotlin 版本只是我自己用习惯了 Kotlin,就性能而言与 Java 是一样的。大家如果不喜爱能够不必理睬,如果有感兴趣的能够看看,Kotlin 是 Google 推出的一种简洁性语言,主推在 Android 上,用习惯后还是蛮喜爱的。学习起来也不难,内容也不多,举荐一个学习 Kotlin 的网址:https://www.kotlincn.net/docs/reference/
(完)
举荐浏览:
B 站 100K 播放量,SpringBoot+Vue 前后端拆散残缺入门教程!
分享一套 SpringBoot 开发博客零碎源码,以及残缺开发文档!速度保留!
Github 上最值得学习的 100 个 Java 开源我的项目,涵盖各种技术栈!
2020 年最新的常问企业面试题大全以及答案