階段三:項目開發---搭建項目前后端系統基礎架構:任務13:實現基本的登錄功能

任務描述

任務名稱:

實現基本的登錄功能

知識點

了解前端Vue項目的基本執行過程

重? 點

構建項目的基本登陸功能

內? 容

通過實現項目的基本登錄功能,來了解前端Vue項目的基本執行過程,并完成基礎環境的準備,包括:工具類、靜態資源文件等。

任務指導

通過實現項目的基本登錄功能,來了解前端Vue項目的基本執行過程,并完成基礎環境的準備,包括:工具類、靜態資源文件等:

1、掌握各工具類的作用

2、完成登錄的前端設計

3、完成登錄的后端設計

4、最終掌握前端Vue項目的開發流程,以及各個文件的功能和作用

任務實現

在上一個階段任務創建的前端基礎框架的基礎上,進行本階段任務的開發。

一、登錄功能的前端處理過程

1、導入項目所需的圖片和CSS等靜態文件

  • 參考代碼存放client節點的/opt/code目錄下

  • 執行如下命令:
[root@client ~]# cp -r /opt/code/kongguan_web/src/assets/* /root/kongguan_web/src/assets/
  • 將參考代碼中的css、icon、images等文件夾或文件直接拷貝到當前創建的新項目的src/assets/目錄中

2、完成項目中使用的工具類的編寫(此處的代碼可由學生獨立完成,也可以由老師提供,直接導入到項目中

  • 編寫或直接導入工具類文件,所有的工具類都放在src/utils目錄下
src/utils/message.js消息彈窗管理
src/utils/request.js用于請求的身份驗證
  • 在src/utils/message.js中,定義了消息彈窗管理,代碼如下:
import { Message } from "element-ui";
let messageInstance = null;
let mainMessage = function DoneMessage(options) {//如果彈窗已存在先關閉if (messageInstance) {messageInstance.close();}messageInstance = Message(options);
}
let arr = ['success', 'warning', 'info', 'error'];
arr.forEach(function (type) {mainMessage[type] = function (options) {if (typeof options === 'string') {options = {message: options};}options.type = type;return mainMessage(options);};
});
export const message = mainMessage;
  • 在src/utils/request.js中,定義了攔截器,用于攔截“請求”和“響應”,進行身份驗證,代碼如下:
import Vue from 'vue'
import axios from 'axios'Vue.prototype.$ajax = Vue.ajax = axiosaxios.defaults.baseURL = process.env.NODE_ENV !== 'production' ? "" : (process.env.VUE_APP_BASE_API)axios.interceptors.request.use(config => {if (localStorage.getItem('Authorization')) {config.headers.common['Authorization'] = localStorage.getItem('Authorization');} else {}return config;
}, err => {return Promise.reject(err);
})
axios.interceptors.response.use(function (response) {// console.log(response);// console.log(response.headers.Authorization);// 對響應數據做點什么return response.data;
}, function (error) {if (JSON.stringify(error).indexOf('401') !== -1) { // token失效localStorage.removeItem('Authorization')Vue.prototype.$message.error("token過期")Vue.prototype.$router.push({path: '/login',})} else if (JSON.stringify(error).indexOf('403') !== -1) {localStorage.removeItem('Authorization')Vue.prototype.$message.error("token無效")Vue.prototype.$router.push({path: '/login',})}// 對響應錯誤做點什么return Promise.reject(error);
});export default axios;

3、編寫 src/api/login/login.js文件,向服務端發送請求,實現登錄

  • 定義login方法,使用POST請求向服務端發送表單數據data,服務端返回的數據包括:登錄狀態、身份token、權限等信息。
import request from '../../utils/request'const baseUrl="/api"/*** 用戶登錄*/
export function login(data){return request({url:baseUrl+"/login",method:"post",data:data})
}

4、編寫src/store/index.js,用于Token的存儲

  • src/store/index.js文件,用于存儲token,修改token,并將token存入localStorage
import Vue from 'vue'
import Vuex from 'vuex'Vue.use(Vuex)
export default new Vuex.Store({state: {// 存儲tokenAuthorization: localStorage.getItem('Authorization') ? localStorage.getItem('Authorization') : '',},mutations: {// 修改token,并將token存入localStoragechangeLogin (state, user) {state.Authorization = user.Authorization;localStorage.setItem('Authorization', user.Authorization);console.log(state.Authorization);}}
})

5、創建登錄的Vue視圖組件Login.vue

  • 登錄頁面設計:在src/views目錄下創建Login目錄,然后在Login目錄下,創建Login.vue 登錄頁面
<template><div class="login"><div class="top_logo"></div><!-- 登錄框區域 --><div class="form_box"><div style="color: #f0f0f0;">大數據航空案例</div><el-form style="margin-top: 60px" :model="loginForm" status-icon :rules="rules" ref="loginForm"><el-row type="flex" justify="left" :gutter="20"><el-col :span="3" align="center"><img src="../../assets/images/user-icon.png" style="margin-top: 3px"/></el-col><el-col :span="21" align="center"><el-form-item prop="account"><el-input  class="el-input__inner1" v-model="loginForm.account" placeholder="請輸入用戶名" maxlength="20"></el-input></el-form-item></el-col></el-row><el-row type="flex"><div class="tableTitle"/></el-row><el-row type="flex" justify="left" style="margin-top: 50px" :gutter="20"><el-col :span="3" align="center"><img src="../../assets/images/pwd-icon.png" style="margin-top: 3px"/></el-col><el-col :span="21"><el-form-item prop="password"><el-input type="password" v-model="loginForm.password" placeholder="請輸入密碼" maxlength="16"></el-input></el-form-item></el-col></el-row><el-row type="flex"><div class="tableTitle"/></el-row><el-form-item align="center" style="margin-top: 100px"><el-button class="el-button1" type="primary;" @click="submitForm('loginForm')">登 錄</el-button></el-form-item></el-form></div><!-- 底部區域 --></div>
</template>
... 接下頁...
  • 初始化表單數據,并定義表單驗證規則,例如:如果用戶名輸入框為空,則提示“請輸入登陸賬號”
 ... 接上頁...<script>import {mapMutations} from 'vuex';import {login} from "@/api/login/login";export default {data() {return {loginForm: {account: "",password: "",},otherQuery: {},rules: {account: [{required: true, message: "請輸入登陸賬號", trigger: "blur"}],password: [{required: true, message: "請輸入登陸密碼", trigger: "blur"}]}};},
... 接下頁...
  • 處理請求的重定向地址,獲取請求的地址,登錄后,直接跳轉到請求的地址

其中getOtherQuery(query)方法是在下面的代碼中定義的方法,返回上一次請求的地址,登錄成功后,直接跳轉到該地址。

... 接上頁...watch: {$route: {handler: function (route) {const query = route.queryif (query) {this.redirect = query.redirectthis.otherQuery = this.getOtherQuery(query)}},immediate: true}},
... 接下頁...
  • 提交表單的過程如下:

調用src/api/Login/Login.js中的login方法,向服務端發送請求,登錄成功后將用戶token和權限保存到本地vuex中,然后跳轉到上一次請求的地址。

... 接上頁...//methods: {...mapMutations(['changeLogin']),submitForm(formName) {this.$refs[formName].validate(valid => {if (valid) {login(this.loginForm).then(data => {if (data.successful) {console.log("登錄成功");// 將用戶token保存到vuex中// this.changeLogin({Authorization: data.result.token});//localStorage.setItem('account', data.result.name)if (data.resultValue.Authorization != null) {localStorage.setItem('Authorization', data.resultValue.Authorization);}if (data.resultValue.userAuth != null) {localStorage.setItem('userAuth', data.resultValue.userAuth);}this.$router.push({path: this.redirect || '/', query: this.otherQuery});console.log("跳轉");} else {console.log("登錄失敗");this.$message.error("登錄失敗");}});} else {return false;}});},//獲取請求的地址,登錄后,直接跳轉到請求的地址getOtherQuery(query) {return Object.keys(query).reduce((acc, cur) => {if (cur !== 'redirect') {acc[cur] = query[cur]}return acc}, {})}}};
</script>
... 接下頁...
  • 頁面樣式如下
... 接上頁...
<style scoped>.login {background: url('../../assets/images/loginBg.png') no-repeat;background-size: cover;height: 100%;}.top_logo {height: 130px;}.form_box {text-align: center;width: 550px;margin: 0 auto;}.login-footer {width: 100%;text-align: center;color: #ffffff;position:fixed;bottom:0;margin-bottom: 64px;size: 23px;padding-top: 100px;}.el-button1{background: #0376bf;border-color: #0376bf;width: 100%;color: #f0f0f0;height: 62px;}.tableTitle {margin: 0 auto;margin-top: 10px;width: 550px;height: 1px;background-color: #d4d4d4;}
</style>
<style>.el-form-item {margin-bottom: 1px;}.el-form-item__error{margin-left: 15px;margin-top: 20px;}.el-input__inner {background: transparent;border: 0;color: #f0f0f0;}
</style>

6、創建登錄成功后的主頁面的Vue視圖組件src/views/Home/Index.vue

  • 由于當前任務是實現登錄功能,所以登錄成功后的src/views/Home/Index.vue頁面只顯示簡單的內容即可,詳細展示會在后續任務中完成。
<template><div style="color: #000000;">大數據航空案例</div>
</template><script>export default{}
</script><style>
</style>

7、為了實現頁面風格的統一,所以這里還需要創建一個src/views/Layout/Layout.vue和一個src/views/Layout/Header.vue 布局視圖組件

除登錄頁面外,其他顯示的頁面都嵌套在一個Layout.vue內,且都顯示同一個頭部內容Header.vue。由于這是在后續任務中完成的工作,所以這里只顯示簡單的內容即可。

  • src/views/Layout/Layout.vue
<template><div class="main"><Header></Header><div class="common-right"><router-view/></div></div>
</template><script>export default{}
</script><style>
</style>
  • src/views/Layout/Header.vue
<template><div style="color: red;">大數據航空案例</div>
</template><script>export default{}
</script><style>
</style>

8、編寫src/router/index.js路由文件,當我們訪問任何頁面時,如果沒有身份認證信息,則會跳轉到Login.vue

  • 通過vue-router重寫路由的push方法,解決相同路徑跳轉報錯的問題
import Vue from 'vue'
import Router from 'vue-router'const originalPush = Router.prototype.push;
Router.prototype.push = function push(location) {return originalPush.call(this, location).catch(err => err)
}
... 接下文 ...
  • 設置基本路由規則
... 接上文 ...
Vue.use(Router)
/* Layout */
import Layout from '@/views/Layout/Layout'
const router =  new Router({base: process.env.BASE_URL,mode: 'history',routes: [{path: "/login",component: resolve => require(['@/views/Login/Login'], resolve),hidden: true,meta: {auth: true}},{path: '/',component: Layout, redirect: '/home',children: [{path: 'home',component: resolve => require(['@/views/Home/Index'], resolve),name: 'home',meta: { title: 'home' }}]},]
})
... 接下文 ...
  • 導航守衛:使用router.beforeEach注冊一個全局前置守衛,用于獲取Token來判斷用戶是否登陸,如果沒有登錄,則跳轉到/Login.vue頁面
... 接上文 ...
router.beforeEach((to, from, next) => {if (to.path === '/login') {next();} else {let token = localStorage.getItem('Authorization');if (token === null || token === '') {next('/login');} else {next();}}
});
export default router

9、編寫src/main.js文件,該文件是應用系統入口、主方法

  • 在src/main.js中導入 ?src/store/index 等文件,有些組件,如ECharts報表組件,在當前任務中沒有使用,可以先導入暫不使用
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store/index'
import './assets/css/basic.css'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import './assets/icon/iconfont.css';
import * as echarts from 'echarts';
import axios from 'axios';
import moment from 'moment';
import { message } from './utils/message'
... 略,接下文 ...
  • 定義全局屬性,以便可以在項目的任意位置使用
... 略,接上文 ...
Vue.prototype.$echarts = echarts;
Vue.prototype.$axios = axios;
Vue.config.productionTip = false
Vue.use(ElementUI);
Vue.prototype.$message = message;
Vue.prototype.$moment = moment;new Vue({router,store,render:h =>h(App)
}).$mount('#app')
... 略,接下文 ...
  • 定義全局響應(response)攔截器和請求(request)攔截器
... 略,接上文 ...
//定義一個響應攔截器
axios.interceptors.response.use(function (config) {let status = config.code;//401,未登錄,跳轉到登錄頁if (status == 401) {console.log(401);router.push("/login");}//403,無權限,跳轉到登錄頁if (status == 403) {console.log(403);router.push("/login");}return config
})// request攔截器
axios.interceptors.request.use(config => {// 如果想請求可以重復發起,給在請求參數中加allowedRepeat:true  (后續會刪除,不會發送給服務端)if (!config.data || !config.data.allowedRepeat) { // 如果不允許重復請求,開啟攔截// todo: 1. 設置攔截 防止重復請求// 攔截重復請求(即當前正在進行的相同請求)const requestData = getRequestIdentify(config)removePending(requestData, true)// 使用 cancel token 取消請求 參考:http://www.axios-js.com/zh-cn/docs/#%E6%8B%A6%E6%88%AA%E5%99%A8config.cancelToken = new CancelToken((c) => {pending[requestData] = c})} else { // 允許重復請求,不進行攔截delete config.data.allowedRepeat // 把自定義的請求參數給刪掉,不發送給服務端}return config
}, error => {// Do something with request errorconsole.log(error) // for debugPromise.reject(error)
})
... 略,接下文 ...
  • 攔截重復請求
... 略,接上文 ...
// 攔截重復請求
let pending = {}
const CancelToken = axios.CancelToken
// 請求標識;完成請求后也需要執行刪除記錄,所以添加此參數避免執行無用操作
const removePending = (key, isRequest = false) => {if (pending[key] && isRequest) {pending[key]('取消重復請求')}delete pending[key]
}
/*** 由于我們請求用了代理 直接代理到測試服務器 因此請求響應攔截器的config.url是一致的,不需要標識值區分* 如果請求攔截器和響應攔截器的config.url不一致,就需要一個標識值用來區分判斷*/
const getRequestIdentify = (config) => {const url = config.url// 返回url及請求參數 post方法請求參數為config.data  get方法請求參數為config.paramsif (config.method === 'post') {return encodeURIComponent(config.url + JSON.stringify(config.data))}return encodeURIComponent(url + JSON.stringify(config.params))
}

10、配置環境變量,在項目根目錄下創建 .env.development 文件,內容如下:

NODE_ENV='development'
VUE_APP_TITLE = 'development'
#測試環境,使用此配置文件
#請求前綴
VUE_APP_BASE_API = 'http://localhost:8848/api'

11、修改vue.config.js文件,覆蓋文件內容,配置服務器端的IP和訪問端口等,文件的完整內容如下:

module.exports = {runtimeCompiler: true,lintOnSave: process.env.NODE_ENV !== 'production',lintOnSave: false,productionSourceMap:false,chainWebpack(config) {config.plugins.delete('prefetch')  },pwa: {iconPaths: {}},devServer: {host: "0.0.0.0",port: 8089,proxy: {'/api': {target: process.env.VUE_APP_BASE_API,pathRewrite:{"^/api":"/"},changeOrigin: true,ws: false},}}
}

12、運行測試前端Vue程序

  • 進入命令行模式,在項目根目錄下執行npm run dev命令運行項目(注意:npm默認使用國外鏡像,可能會出現連接失敗的問題,如出現連接失敗可將npm命令切換成cnpm命令)
[root@client KongGuan-Web]# npm run dev
  • 打開瀏覽器,可以查看到登錄頁面

二、登錄功能的后端處理過程

1、在后端BigData-KongGuan項目的pom.xml文件中引入 spring-boot-starter-security 包和Redis相關包

  • 當前項目使用SpringBoot的WebSecurityConfig安全組件,并使用Redis保存用戶token和權限,并為Redis設置過期時間,在前面搭建后端基礎框架的任務中,已經在pom.xml文件引入了相關依賴包,打開pom.xml文件可以查看到以下內容。
<!-- Spring Boot Security --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!-- spring-redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><exclusions><exclusion><groupId>io.lettuce</groupId><artifactId>lettuce-core</artifactId></exclusion></exclusions></dependency><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId></dependency>

2、編寫com/qrsoft/config/WebSecurityConfig.java類,使用SpringBoot的WebSecurity安全組件

  • 使用自定義身份驗證組件TokenAuthenticationProvider
    // ... ...// ... 略 ...@Autowiredprivate TokenAuthenticationProvider tokenAuthenticationProvider;@Overrideprotected void configure(AuthenticationManagerBuilder auth) {// 使用自定義身份驗證組件auth.authenticationProvider(tokenAuthenticationProvider);}// ... 略 ...// ... ...
  • 在類中添加攔截器,攔截請求,如果非/api/login請求,則執行TokenLoginFilter過濾器進行登錄驗證,執行TokenAuthenticationFilter進行身份驗證。
@Override
protected void configure(HttpSecurity http) throws Exception {/// ... ...// ... 略 ...// 添加攔截器http.addFilterBefore(new TokenLoginFilter("/api/login", authenticationManager(), redisTemplate), UsernamePasswordAuthenticationFilter.class).addFilterBefore(new TokenAuthenticationFilter(userMapper, redisTemplate), UsernamePasswordAuthenticationFilter.class);// ... 略 ...// ... ...
}
  • com/qrsoft/config/WebSecurityConfig.java類的完整代碼如下:
package com.qrsoft.config;import com.google.gson.Gson;
import com.qrsoft.common.WrappedResult;
import com.qrsoft.filter.TokenAuthenticationFilter;
import com.qrsoft.filter.TokenAuthenticationProvider;
import com.qrsoft.filter.TokenLoginFilter;
import com.qrsoft.mapper.SysUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import javax.servlet.http.HttpServletResponse;@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate SysUserMapper userMapper;@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate TokenAuthenticationProvider tokenAuthenticationProvider;@Overrideprotected void configure(AuthenticationManagerBuilder auth) {// 使用自定義身份驗證組件auth.authenticationProvider(tokenAuthenticationProvider);}@Overrideprotected void configure(HttpSecurity http) throws Exception {// 配置 CSRF 關閉,允許跨域訪問http.csrf().disable();// 開啟Spring Security cors支持,允許跨域訪問http.cors();// 關閉 Sessionhttp.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);// 允許 登錄接口 的無授權訪問,其他需要授權訪問http.authorizeRequests().antMatchers("/*").permitAll().anyRequest().authenticated();// 禁用緩存http.headers().cacheControl();// 添加攔截器http.addFilterBefore(new TokenLoginFilter("/api/login", authenticationManager(), redisTemplate), UsernamePasswordAuthenticationFilter.class).addFilterBefore(new TokenAuthenticationFilter(userMapper, redisTemplate), UsernamePasswordAuthenticationFilter.class);// 指定錯誤未授權訪問的處理類http.exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -> {response.setContentType("application/json");response.setCharacterEncoding("UTF-8");response.setStatus(HttpServletResponse.SC_FORBIDDEN);response.getWriter().print(new Gson().toJson(WrappedResult.failedWrappedResult(accessDeniedException.getMessage(), "403")));});}@Overridepublic void configure(WebSecurity web) {web.ignoring().antMatchers("/doc.html/**", "/swagger-ui.html/**", "/v2/**", "/swagger-resources/**", "/webjars/**", "/minio/**");}
}

3、編寫com/qrsoft/filter下面的類,TokenLoginFilter是登錄過濾器,除此之外,還會用到TokenAuthenticationFilter身份認證過濾器和TokenAuthenticationProvider用來處理認證實體。

  • 登錄過程包括4個部分(按①②③④的順序執行),其中com/qrsoft/filter/TokenLoginFilter.java類是登錄的核心過濾器,包括①④兩個部分:
package com.qrsoft.filter;import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.google.gson.Gson;
import com.qrsoft.common.TokenVO;
import com.qrsoft.common.WrappedResult;
import com.qrsoft.entity.SysUser;
import com.qrsoft.util.TokenUtil;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;public class TokenLoginFilter extends AbstractAuthenticationProcessingFilter {private final StringRedisTemplate redisTemplate;public TokenLoginFilter(String url, AuthenticationManager authManager, StringRedisTemplate redisTemplate) {super(new AntPathRequestMatcher(url));setAuthenticationManager(authManager);this.redisTemplate = redisTemplate;}/*** 嘗試認證(從request中取用戶名和密碼,生成認證實體)*/@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException {return null;//① ...}/*** 認證成功返回統一格式的JSON*/@Overrideprotected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChainchain, Authentication auth) throws IOException {//④...}/*** 認證失敗返回統一格式的JSON*/@Overrideprotected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponseresponse, AuthenticationException failed) throws IOException {//④ ...}
}
  • com/qrsoft/filter/TokenAuthenticationProvider.java類是用來處理認證實體,包括②③兩個部分
package com.qrsoft.filter;import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.qrsoft.entity.SysAuth;
import com.qrsoft.entity.SysUser;
import com.qrsoft.mapper.SysAuthMapper;
import com.qrsoft.mapper.SysUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;@Component
public class TokenAuthenticationProvider implements AuthenticationProvider {private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();@Autowiredprivate SysUserMapper userMapper;@Autowiredprivate SysAuthMapper authMapper;/*** 身份認證*/@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {return null;//③ ... }@Overridepublic boolean supports(Class<?> authentication) {return false;//② ... }/*** 獲取用戶權限*/private List<GrantedAuthority> getGrantedAuthorities(SysUser sysUser) {List<SysAuth> authList = authMapper.getAuthByUserId(sysUser.getId());//先過濾,在轉換Set<String> perms = authList.stream().map(SysAuth::getAuthCode).filter(StringUtils::isNotBlank).collect(Collectors.toSet());return AuthorityUtils.createAuthorityList(perms.toArray(new String[0]));}
}
  • 在TokenLoginFilter類中的 ① 部分的代碼如下(請在TokenLoginFilter類中的相應位置替換為如下代碼):
	/*** 嘗試認證(從request中取用戶名和密碼,生成認證實體)*/@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException { //①String account = request.getParameter("account");String password = request.getParameter("password");if (StringUtils.isBlank(account) && StringUtils.isBlank(password)) {InputStreamReader streamReader = new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8);BufferedReader reader = new BufferedReader(streamReader);StringBuilder builder = new StringBuilder();String inputStr;while ((inputStr = reader.readLine()) != null)builder.append(inputStr);JSONObject jsonObject = JSONObject.parseObject(builder.toString());account = jsonObject.getString("account");password = jsonObject.getString("password");streamReader.close();reader.close();}if (account.contains(" ")) throw new BadCredentialsException("用戶名或密碼錯誤");// 返回一個驗證令牌return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(account, password));}
  • 在TokenAuthenticationProvider類中的 ② 部分的代碼如下(請在TokenAuthenticationProvider類中的相應位置替換為如下代碼):
   @Overridepublic boolean supports(Class<?> authentication) {//②return authentication.equals(UsernamePasswordAuthenticationToken.class);}
  • 在TokenAuthenticationProvider類中的 ③ 部分代碼如下(請在TokenAuthenticationProvider類中的相應位置替換為如下代碼):
	/*** 身份認證*/@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {//③if (authentication.getPrincipal() == null || authentication.getCredentials() == null)throw new BadCredentialsException("用戶名或密碼錯誤");// 獲取認證的用戶名 & 密碼String account = authentication.getPrincipal().toString();String password = authentication.getCredentials().toString();SysUser sysUser = userMapper.getByAccount(account);if (sysUser != null) {// 密碼不匹配直接拋出異常if (!passwordEncoder.matches(password, sysUser.getPassword()))throw new BadCredentialsException("用戶名或密碼錯誤");// 獲取用戶權限List<GrantedAuthority> authorities = getGrantedAuthorities(sysUser);UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(sysUser.getAccount(), sysUser.getPassword(), authorities);token.setDetails(sysUser);return token;} else {throw new UsernameNotFoundException("用戶不存在或已刪除");}}
  • 在TokenLoginFilter類中的 ④ 部分的代碼如下(請在TokenLoginFilter類中的相應位置替換為如下代碼):
/**
* 認證成功返回統一格式的JSON
*/
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication auth) throws IOException {//④response.setContentType("application/json");response.setStatus(HttpServletResponse.SC_OK);response.setCharacterEncoding("UTF-8");String token;//生成tokentry {Map<String, Object> payload = new HashMap<>();payload.put("account", auth.getName());token = TokenUtil.genToken(payload);} catch (Exception e) {response.getWriter().print(new Gson().toJson("Token生成失敗"));return;}if (auth.getDetails() instanceof SysUser) {SysUser sysUser = (SysUser) auth.getDetails();TokenVO tokenVO = new TokenVO();tokenVO.setAccount(auth.getName());Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();List<String> authorityList = authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());tokenVO.setName(sysUser.getName());tokenVO.setId(sysUser.getId().toString());tokenVO.setType(sysUser.getType());Boolean userboo = redisTemplate.hasKey(sysUser.getId().toString());if (userboo != null && userboo) {String tok = redisTemplate.boundValueOps(sysUser.getId().toString()).get();if (tok != null) {Boolean tokboo = redisTemplate.hasKey(tok);if (tokboo != null && tokboo) {redisTemplate.delete(tok);}}redisTemplate.delete(sysUser.getId().toString());}//存儲用戶對應tokenredisTemplate.boundValueOps(sysUser.getId().toString()).set(token);//存儲token對應權限BoundSetOperations<String, String> setOperations = redisTemplate.boundSetOps(token);setOperations.add(authorityList.toArray(new String[]{}));response.setHeader("Authorization", token);response.setHeader("userAuth",authorityList.toString());tokenVO.setAuthorization(token);tokenVO.setUserAuth(authorityList.toString());response.getWriter().print(new Gson().toJson(WrappedResult.successWrapedResult(tokenVO)));} else {response.setContentType("application/json");response.setStatus(HttpServletResponse.SC_OK);response.setCharacterEncoding("UTF-8");response.getWriter().print(new Gson().toJson(WrappedResult.failedWrappedResult("登錄失敗")));}
}
/**
* 認證失敗返回統一格式的JSON
*/
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException {//④response.setContentType("application/json");response.setStatus(HttpServletResponse.SC_OK);response.setCharacterEncoding("UTF-8");response.getWriter().print(new Gson().toJson(WrappedResult.failedWrappedResult(failed.getMessage())));
}
  • com/qrsoft/filter/TokenAuthenticationFilter.java類是用于身份認證的過濾器,其完整代碼如下:
package com.qrsoft.filter;import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.google.gson.Gson;
import com.qrsoft.common.R;
import com.qrsoft.common.WrappedResult;
import com.qrsoft.entity.SysUser;
import com.qrsoft.mapper.SysUserMapper;
import com.qrsoft.util.TokenUtil;
import org.apache.commons.collections.MapUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Set;/*** 身份認證過濾器*/
public class TokenAuthenticationFilter extends GenericFilterBean {private final SysUserMapper userMapper;private final StringRedisTemplate redisTemplate;public TokenAuthenticationFilter(SysUserMapper sysUserDao, StringRedisTemplate redisTemplate) {this.userMapper = sysUserDao;this.redisTemplate = redisTemplate;}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {System.out.println("權限信息打印");// 從Http頭中取tokenString token = ((HttpServletRequest) request).getHeader("Authorization");if (token != null) {// 校驗token,并獲取token中存儲的用戶名R r = TokenUtil.valid(token);// 驗證redis是否存在tokenBoolean tokenBoo = redisTemplate.hasKey(token);if (tokenBoo != null && tokenBoo && r.isSuccess()) {// 從token檢驗結果獲取用戶名String account = MapUtils.getString(r.getPayloadMap(), "account", StringUtils.EMPTY);// 根據token獲取redis中權限setSet<String> authoritySet = redisTemplate.boundSetOps(token).members();SysUser sysUser = userMapper.getByAccount(account);UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(sysUser.getAccount(), sysUser.getPassword(), AuthorityUtils.createAuthorityList(authoritySet.toArray(new String[]{})));authentication.setDetails(sysUser);SecurityContextHolder.getContext().setAuthentication(authentication);filterChain.doFilter(request, response);} else {//token驗證不通過writeResponse((HttpServletResponse) response, "Token失效");}} else {// token不存在放回401(未登錄)writeResponse((HttpServletResponse) response, "未登錄");}}private void writeResponse(HttpServletResponse response, String str) throws IOException {response.setContentType("application/json");response.setCharacterEncoding("UTF-8");response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);response.getWriter().print(new Gson().toJson(WrappedResult.failedWrappedResult(str, String.valueOf(HttpServletResponse.SC_UNAUTHORIZED))));}
}

4、由于com.qrsoft.entity、com.qrsoft.mapper、com.qrsoft.common、com.qrsoft.util包下的幫助類不是當前項目的重點,所以請參考相應的源代碼,自行完成或直接拷貝這些通用類即可。主要使用到以下的類:

類/接口

功能

com.qrsoft.mapper.SysUserMapper用戶基礎信息表對應的數據訪問類
com.qrsoft.mapper.SysAuthMapper權限表對應的數據訪問類
com.qrsoft.common.R通用類,定義了返回結果
com.qrsoft.common.WrappedResult通用類,定義了返回結果的接口規范
com.qrsoft.common.TokenVO通用類,定義了Token的內容
com.qrsoft.common.AuthAndMenu通用類,定義了權限和菜單
com.qrsoft.entity.SysUser用戶基礎信息表對應的數據實體類
com.qrsoft.entity.SysAuth權限表對應的數據實體類
com.qrsoft.util.TokenUtilToken工具類,用于生成Token、驗證Token有效性等
com.qrsoft.config.MybatisPlusConfigMybatisPlus的配置類
  • com.qrsoft.mapper.SysUserMapper類:用戶基礎信息表對應的數據訪問類
package com.qrsoft.mapper;import com.qrsoft.entity.SysUser;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;@Repository
@Mapper
public interface SysUserMapper {@Select("select id,account,password,name,is_enable,type,user_type_id " +"from sys_user where account = #{account} and is_del = 0")SysUser getByAccount(String account);
}
  • com.qrsoft.mapper.SysAuthMapper類:權限表對應的數據訪問類
package com.qrsoft.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.qrsoft.entity.SysAuth;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.springframework.stereotype.Repository;import java.util.List;@Repository
@Mapper
public interface SysAuthMapper extends BaseMapper<SysAuth> {/*** 根據用戶id獲取按鈕權限*/@Select("select sa.id,sa.auth_name,sa.auth_code,sa.parent_id from sys_auth sa left join role_auth ra on sa.id = ra.auth_id where " +"sa.is_del = 0 and sa.type = 0 and ra.role_id in " +"(select sr.id from sys_role sr join user_role ur on sr.id = ur.role_id where sr.is_del = 0 and sr.is_enable = 0 and ur.user_id = #{userId})" +" order by sa.parent_id")List<SysAuth> getAuthByUserId(Integer userId);/*** 根據用戶id獲取菜單權限*/@Select("select sa.id,sa.auth_name,sa.auth_code,sa.parent_id,sa.menu_url,sa.menu_icon,sa.menu_order from sys_auth sa left join role_auth ra on sa.id = ra.auth_id where " +"sa.is_del = 0 and sa.type = 1 and ra.role_id in " +"(select sr.id from sys_role sr join user_role ur on sr.id = ur.role_id where sr.is_del = 0 and sr.is_enable = 0 and ur.user_id = #{userId})" +" group by sa.id order by sa.menu_order")List<SysAuth> getMenuByUserId(Integer userId);/*** 根據權限id獲取權限詳情*/@Select("select id,auth_name,auth_code,type,menu_url,parent_id,menu_icon,menu_order from sys_auth where id = #{id} and is_del=0")SysAuth getOneAuth(Integer id);/*** 根據權限標識統計權限數量*/@Select("select count(1) from sys_auth where auth_code = #{authCode} and is_del=0")Integer countAuthCode(String authCode);/*** 權限取消全部角色*/@Delete("delete from role_auth where auth_id = #{authId}")void deleteRole(Integer authId);/*** 刪除權限*/@Update("update sys_auth set is_del = 1 where id = #{id} and is_del = 0")Boolean delAuth(Integer id);@Select("select DISTINCT sa.id,sa.auth_name,sa.auth_code,sa.parent_id,sa.type,sa.menu_icon,sa.menu_order from sys_auth sa left join role_auth ra on sa.id = ra.auth_id where " +"sa.is_del = 0 and ra.role_id = #{roleId} order by sa.parent_id")List<SysAuth> getAuthByRole(Integer roleId);/*** 根據角色獲取權限id*/@Select("select auth_id from role_auth where role_id in (${roleId})")List<Integer> getIdByRole(String roleId);
}
  • com.qrsoft.common.R類:通用類,定義了返回結果
package com.qrsoft.common;import java.util.HashMap;
import java.util.Map;public class R {private final boolean success;private final String msg;private final Map<String, Object> payloadMap;private R(boolean success, String msg, Map<String, Object> payloadMap) {this.success = success;this.msg = msg;this.payloadMap = payloadMap;}public static R ok(Map<String, Object> payloadMap) {return new R(true, "", payloadMap);}public static R error(String msg) {return new R(false, msg, new HashMap<>());}public boolean isSuccess() {return success;}public Map<String, Object> getPayloadMap() {return payloadMap;}@Overridepublic String toString() {return "R{" +"success=" + success +", msg='" + msg + '\'' +", payloadMap=" + payloadMap +'}';}
}
  • com.qrsoft.common.WrappedResult類:通用類,定義了返回結果的接口規范
package com.qrsoft.common;import io.swagger.annotations.ApiModelProperty;
import org.springframework.security.access.AccessDeniedException;
import java.io.Serializable;public class WrappedResult<T> implements Serializable {private static final long serialVersionUID = 1L;private static final String ERROR = "error";private static final String SUCCESS = "success";/*** 接口狀態*/@ApiModelProperty("接口狀態,true成功,false失敗")private final boolean successful;/*** 接口返回數據*/@ApiModelProperty("接口成功返回數據主體")private final T resultValue;/*** 接口錯誤信息*/@ApiModelProperty("接口錯誤信息")private final String resultHint;/*** 接口返回狀態* 失敗:401,403,error* 成功:success*/@ApiModelProperty("接口返回狀態,成功:success,失敗:401,403,error")private final String type;private WrappedResult(boolean isSuccess, T data, String resultHint, String type) {this.successful = isSuccess;this.resultValue = data;this.resultHint = resultHint;this.type = type;}public static <T> WrappedResult<T> successWrapedResult(T data) {return new WrappedResult<>(true, data, "", SUCCESS);}public static <T> WrappedResult<T> failedWrappedResult(String exMessage) {return new WrappedResult<>(false, null, exMessage, ERROR);}public static WrappedResult<Boolean> failedWrappedResult(Exception e, Boolean data) {if ("java.lang.RuntimeException".equals(e.getClass().getName())) {return new WrappedResult<>(false, data, e.getMessage(), ERROR);} else if ("org.springframework.security.access.AccessDeniedException".equals(e.getClass().getName())) {throw new AccessDeniedException(e.getMessage());} else {return new WrappedResult<>(false, data, "操作失敗", ERROR);}}public static <T> WrappedResult<T> failedWrappedResult(Exception e) {if ("java.lang.RuntimeException".equals(e.getClass().getName())) {return new WrappedResult<>(false, null, e.getMessage(), ERROR);} else if ("org.springframework.security.access.AccessDeniedException".equals(e.getClass().getName())) {throw new AccessDeniedException(e.getMessage());} else {return new WrappedResult<>(false, null, "操作失敗", ERROR);}}public static <T> WrappedResult<T> failedWrappedResult(Exception e, String exMessage) {if ("java.lang.RuntimeException".equals(e.getClass().getName())) {return new WrappedResult<>(false, null, e.getMessage(), ERROR);} else if ("org.springframework.security.access.AccessDeniedException".equals(e.getClass().getName())) {throw new AccessDeniedException(e.getMessage());} else {return new WrappedResult<>(false, null, exMessage, ERROR);}}public static <T> WrappedResult<T> failedWrappedResult(String exMessage, String type) {return new WrappedResult<>(false, null, exMessage, type);}public boolean isSuccessful() {return this.successful;}public T getResultValue() {return this.resultValue;}public String getType() {return this.type;}public String getResultHint() {return resultHint;}
}
  • com.qrsoft.common.TokenVO類:通用類,定義了Token的內容
package com.qrsoft.common;import lombok.Data;import java.io.Serializable;
import java.util.List;@Data
public class TokenVO implements Serializable {private static final long serialVersionUID = -5501706435587205188L;/*** token*/private String token;/*** 用戶ID*/private String id;/*** 姓名*/private String name;/*** 用戶名*/private String account;/*** 用戶類型*/private Integer type;/*** 按鈕權限列表*/private List<AuthAndMenu> authList;/*** 菜單權限列表*/private List<AuthAndMenu> menuList;private String Authorization;private String userAuth;
}
  • com.qrsoft.common.AuthAndMenu類: 通用類,定義了權限和菜單
package com.qrsoft.common;import com.qrsoft.entity.SysAuth;
import lombok.Data;import java.util.List;@Data
public class AuthAndMenu {/*** id*/private Integer id;/*** 權限名稱*/private String authName;/*** 權限編碼*/private String authCode;/*** 權限類型(0:按鈕;1,菜單)*/private Integer type;/*** 菜單Url*/private String menuUrl;/*** 菜單Icon*/private String menuIcon;/*** 菜單order*/private Integer menuOrder;/***  子菜單*/private List<AuthAndMenu> childs;/*** 父級id*/private Integer parentId;public static AuthAndMenu authTOAuth(SysAuth auth) {AuthAndMenu output = new AuthAndMenu();output.setId(auth.getId());output.setAuthName(auth.getAuthName());output.setAuthCode(auth.getAuthCode());output.setType(0);output.setParentId(auth.getParentId());output.setMenuUrl(auth.getMenuUrl());output.setMenuIcon(auth.getMenuIcon());output.setMenuOrder(auth.getMenuOrder());return output;}public static AuthAndMenu authTOMenu(SysAuth auth) {AuthAndMenu output = new AuthAndMenu();output.setId(auth.getId());output.setAuthName(auth.getAuthName());output.setType(1);output.setParentId(auth.getParentId());output.setMenuUrl(auth.getMenuUrl());output.setMenuIcon(auth.getMenuIcon());output.setMenuOrder(auth.getMenuOrder());return output;}
}
  • com.qrsoft.entity.SysUser類:用戶基礎信息表對應的數據實體類
package com.qrsoft.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;import java.io.Serializable;
import java.util.Date;/*** 用戶基礎*/
@Data
@TableName("sys_user")
public class SysUser implements Serializable {private static final long serialVersionUID = 1L;/*** 主鍵id*/@TableId(value = "id", type = IdType.AUTO)private Integer id;/*** 用戶賬號*/@TableField("account")private String account;/*** 用戶密碼*/@TableField("password")private String password;/*** 用戶姓名*/@TableField("name")private String name;/*** 聯系方式*/@TableField("contact")private String contact;/*** 用戶類型(0:質檢員(管理員);1:司機;2:客戶)*/@TableField("type")private Integer type = 0;/*** 司機id/客戶id*/@TableField("user_type_id")private Integer userTypeId;/*** 是否啟用(啟用:0,未啟用:1)*/@TableField("is_enable")private Integer isEnable = 0;/*** 是否刪除(正常:0,刪除:1)*/@TableField("is_del")private Integer isDel = 0;/*** 創建人*/@TableField(value = "create_user", select = false)private Integer createUser;/*** 創建時間*/@TableField(value = "create_time", select = false)private Date createTime;
}
  • com.qrsoft.entity.SysAuth類:權限表對應的數據實體類
package com.qrsoft.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;import java.io.Serializable;
import java.util.Date;/*** 權限表*/
@Data
@TableName("sys_auth")
public class SysAuth implements Serializable {private static final long serialVersionUID = 1L;/*** 主鍵id*/@TableId(value = "id", type = IdType.AUTO)private Integer id;/*** 權限名稱*/@TableField("auth_name")private String authName;/*** 權限編碼*/@TableField("auth_code")private String authCode;/*** 權限類型(0:按鈕;1,菜單)*/@TableField("type")private Integer type = 0;/*** 菜單Url*/@TableField("menu_url")private String menuUrl;/*** 父級id*/@TableField("parent_id")private Integer parentId;/*** 菜單圖標*/@TableField("menu_icon")private String menuIcon;/*** 菜單順序*/@TableField("menu_order")private Integer menuOrder;/*** 刪除狀態,0正常,1刪除*/@TableField("is_del")private Integer isDel = 0;/*** 創建人*/@TableField(value = "create_user", select = false)private Integer createUser;/*** 創建時間*/@TableField(value = "create_time", select = false)private Date createTime;
}
  • com.qrsoft.util.TokenUtil類:Token工具類,用于生成Token、驗證Token有效性等
package com.qrsoft.util;import com.qrsoft.common.R;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;import java.util.Calendar;
import java.util.Map;/*** token工具類*/
public class TokenUtil {private TokenUtil() {}/*** 密鑰(至少32字節,后續可根據需求換成RSA加密)*/private static final byte[] SECRET = "sarnath-sarnath-sarnath-sarnath.".getBytes();/*** token失效時間(分)*/public static final int EXP_TIME = 24 * 60;/*** 生成token*/public static String genToken(Map<String, Object> payloadMap) {// JwtBuilder的base64UrlEncoder默認Encoders.BASE64URLJwtBuilder jwtBuilder = Jwts.builder();// 設置載荷jwtBuilder.setClaims(payloadMap);// 設置失效時間Calendar calendar = Calendar.getInstance();calendar.add(Calendar.MINUTE, EXP_TIME);jwtBuilder.setExpiration(calendar.getTime());// 設置簽名jwtBuilder.signWith(Keys.hmacShaKeyFor(SECRET));return jwtBuilder.compact();}/*** 驗證token有效性*/public static R valid(String token) {try {JwtParser jwtParser = Jwts.parser();// 設置時鐘偏移jwtParser.setAllowedClockSkewSeconds(3 * 60);// 設置簽名jwtParser.setSigningKey(SECRET);Map<String, Object> payload = jwtParser.parseClaimsJws(token).getBody();return R.ok(payload);} catch (Exception e) {return R.error(e.getMessage());}}
}
  • com.qrsoft.config.MyBatisPlusConfig類:MyBatisPlus的配置類
package com.qrsoft.config;import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class MyBatisPlusConfig {@Beanpublic PaginationInterceptor paginationInterceptor() {PaginationInterceptor paginationInterceptor = new PaginationInterceptor();paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));return paginationInterceptor;}
}

5、因為需要訪問MySQL數據庫,所以在resources目錄下打開application.yml文件(在前面步驟中已經創建),并填寫如下配置:

server:port: 8848
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverusername: root    password: 123456    url: jdbc:mysql://node3:3306/kongguan?autoReconnect=true&autoReconnectForPools=true&failOverReadOnly=false&serverTimezone=UTC    redis:host: node3    port: 6379    database: 15

注意:需要確保node3節點上的Redis和MySQL都已經正常啟動,可以參照前面安裝部署的任務中的步驟進行驗證。

6、項目啟動類BigDataKongGuanApplication?的內容如下:

package com.qrsoft;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.scheduling.annotation.EnableScheduling;@SpringBootApplication
@EnableScheduling
public class BigDataKongGuanApplication {public static ConfigurableApplicationContext appConfig;public static void main(String[] args) {appConfig=SpringApplication.run(BigDataKongGuanApplication.class, args);}
}

三、測試登錄功能

1、啟動后端Spring Boot程序。

2、啟動前端VUE程序。

3、輸入用戶名和密碼(例如:admin/admin)進行登錄。?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/42372.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/42372.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/42372.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

如何讓代碼兼容 Python 2 和 Python 3?Future 庫助你一臂之力

目錄 01Future 是什么? 為什么選擇 Future? 安裝與配置 02Future 的基本用法 1、兼容 print 函數 2、兼容整數除法 3、兼容 Unicode 字符串 03Future 的高級功能 1. 處理字符串與字節 2. 統一異常處理…

linux kthread任務管理

目錄 一、linux 創建內核線程1.1 kthread_create1.2 kthread_create_worker kthread_queue_work 二、設置線程優先級和調度策略2.1 sched_setscheduler2.2 調度策略 一、linux 創建內核線程 1.1 kthread_create 在 linux 中&#xff0c;可以使用 kthread_create 接口創建內核…

移動校園(7)ii:uniapp路由響應攔截器處理token,以及微信小程序報錯當前頁面正在處于跳轉狀態,請稍后再進行跳轉....

依據昨天的寫完&#xff0c;在token過期之后&#xff0c;再次調用接口&#xff0c;會觸發后端攔截&#xff0c;扔進全局錯誤處理中間件 前端說明提示都沒有&#xff0c;只有一個這個&#xff0c;現在優化一下&#xff0c;再寫一個類似全局后置守衛&#xff0c;當狀態碼是401的時…

MySQL——數據連接池

數據庫連接 --- 執行完畢 --- 釋放&#xff08;連接到釋放的過程十分浪費系統資源&#xff09; 池化技術&#xff1a;準備一些預先的資源&#xff0c;過來就連接預先準備好的 編寫連接池&#xff0c;實現一個接口 DataSource 開源數據源實現&#xff08;拿來即用&#xff09;…

增強安全防護,解讀智慧校園系統的登錄日志功能

在構建智慧校園系統時&#xff0c;登錄日志功能扮演著不可或缺的角色&#xff0c;它不僅是系統安全的守護者&#xff0c;也是提升管理效率和確保合規性的有力工具。這一機制詳細記錄每次登錄嘗試的方方面面&#xff0c;涵蓋了時間戳、用戶身份、登錄來源的IP地址乃至使用的設備…

phpcms 升級php8.3.8

windows 2008 server 不支持php8.3.8,需升級為windows 2012 1.下載php8.3.8 PHP8.3.9 For Windows: Binaries and sources Releases 2.配置php.ini (1.)在php目錄下找到php.ini-development文件&#xff0c;把它復制一份&#xff0c;改名為php.ini (2.)修改php安裝目錄 根…

《昇思 25 天學習打卡營第 10 天 | ResNet50 遷移學習 》

《昇思 25 天學習打卡營第 10 天 | ResNet50 遷移學習 》 活動地址&#xff1a;https://xihe.mindspore.cn/events/mindspore-training-camp 簽名&#xff1a;Sam9029 使用遷移學習進行狼狗圖像分類 簡介 在機器學習和深度學習中&#xff0c;我們經常面臨數據不足的問題。 遷…

python【文件操作】

文件操作 一、創建文件夾二、文件操作模式1.覆蓋寫入2.讀取3.追加 三、 Python腳本在文件中查找和替換文本四、 python清空文件夾 一、創建文件夾 判斷文件或者文件夾是否存在 import ospathrD://測試文件夾 if not os.path.exists(path):os.mkdir(path)print(os.path.exists…

C++模板元編程(二)——完美轉發

完美轉發指的是函數模板可以將自己的參數“完美”地轉發給內部調用的其它函數。所謂完美&#xff0c;即不僅能準確地轉發參數的值&#xff0c;還能保證被轉發參數的左、右值屬性不變。 文章目錄 場景舊的方法新的方法內部實現參考文獻 場景 思考下面的代碼&#xff1a; templ…

高防服務器的重要性

在數字化時代&#xff0c;網絡安全已成為企業和個人最為關注的問題之一。隨著網絡攻擊的日益頻繁和復雜&#xff0c;傳統的服務器租用服務已難以滿足高安全需求的市場。高防服務器租用應運而生&#xff0c;成為保護網絡安全的重要解決方案。本文將探討高防服務器租用的概念、重…

專業140+總分420+天津大學815信號與系統考研經驗天大電子信息與通信工程,真題,大綱,參考書。

順利上岸天津大學&#xff0c;專業課815信號與系統140&#xff0c;總分420&#xff0c;總結一些自己的復習經歷&#xff0c;希望對于報考天大的同學有些許幫助&#xff0c;少走彎路&#xff0c;順利上岸。專業課&#xff1a; 815信號與系統&#xff1a;指定教材吳大正&#xf…

2-26 基于matlab開發的制冷循環模型

基于matlab開發的制冷循環模型。Simscape兩相流域中的制冷循環模型&#xff0c;在simulink中完成多循環溫度控制。程序已調通&#xff0c;可直接運行。 2-26 制冷循環模型 Simscape兩相流域 - 小紅書 (xiaohongshu.com)

Arduino ESP8266 開發環境搭建

Arduino ESP8266 開發環境搭建 很久之前學嵌入式時&#xff0c;用過Arduino8266進行開發&#xff0c;開發成本低、難度小&#xff0c;體驗很不錯。 近期&#xff0c;又突然要用&#xff0c;遂再次搭建環境&#xff0c;但變動挺多&#xff0c;有些小波折&#xff0c;開貼記錄。…

生成式AI應用實列和價值鏈

生成式AI應用實列和價值鏈 生成式AI應用實列ChatGPTGeminiGitHub CopilotSynthesia 價值鏈 生成式AI應用實列 ChatGPT ChatGPT 并不是生成式 AI 行業中唯一的公司。 Stability AI 的 Stable Diffusion 可以根據文本描述生成圖像&#xff0c;發布后 90 天內&#xff0c;在 Git…

vue是如何進行監聽數據變化的?vue2和vue3分別是什么,vue3為什么要更換

在 Vue 中&#xff0c;數據變化的監聽是通過響應式系統來實現的。Vue 2.x 和 Vue 3 在這方面有一些區別。 Vue 2.x 的數據監聽 Vue 2.x 使用的是 Object.defineProperty() 方法來實現數據的響應式。當你聲明一個 Vue 實例的數據對象時&#xff0c;Vue 將遍歷這個對象的屬性&a…

清除屏幕上信息的命令clear

清除屏幕上信息的命令clear There is no nutrition in the blog content. After reading it, you will not only suffer from malnutrition, but also impotence. The blog content is all parallel goods. Those who are worried about being cheated should leave quickly. 清…

高考志愿填報千萬要注意這四點

在高考志愿填報過程中&#xff0c;確實有很多需要留心的點。我為你總結了四個關鍵點&#xff0c;希望能幫助你順利完成志愿填報&#xff1a; 1、學校提供的支持 學校作為學生志愿填報咨詢服務的主陣地&#xff0c;應提供體系化和制度化的支持。包括及時關注并傳達政策動向和相…

行內元素、塊級元素居中

行內元素居中 水平居中 {text-align&#xff1a;center;}垂直居中 單行——行高等于盒子高度 <head><style>.father {width: 400px;height: 200px;/* 行高等于盒子高度&#xff1a;line-height: 200px; */line-height: 200px;background-color: pink;}.son {}&…

如何做好IT類的技術面試?

我們在找工作時&#xff0c;需要結合自己的現狀&#xff0c;針對意向企業做好充分準備。作為程序員&#xff0c;你有哪些面試IT技術崗的技巧&#xff1f; 方向一&#xff1a;分享你面試IT公司的小技巧 我分享一些基于廣泛觀察和用戶反饋的面試IT公司的小技巧&#xff1a; 技術準…

孟德爾隨機化-痛風

寫在前面 昨天看到文獻&#xff0c;稱飲酒與痛風無關聯&#xff0c;甚是疑惑&#xff0c;今天剛好看了一篇新文獻&#xff0c;雖然不是主要講飲酒與痛風的&#xff0c;但也有牽扯到這方面內容&#xff0c;而且是相反的內容&#xff0c;特記錄一下。 孟德爾隨機化-受教育程度與…