前后端分离的后台管理系统开发模板(带你从零开发一套自己的若依框架)上

前言:

目前,前后端分离开发已经成为当前web开发的主流。目前最流行的技术选型是前端vue3+后端的spring boot3,本次。就基于这两个市面上主流的框架来开发出一套基本的后台管理系统的模板,以便于我们今后的开发。

前端使用vue3+element-plus开发。后端使用spring boot3+spring security作为项目的支撑,使用MySQL8.0.30作数据存储,使用redis作缓存,使用minio作为项目的存储机构。

后台管理系统是比较注重权限的,本项目使用市面上最流行的RBAC模型。建立用户、角色、权限和它们两两之间的对映关系一共五张表,日志管理两张表,一张记录用户的行为、一张记录用户的操作。

搭建基础的环境:

后端:

创建一个spring boot项目,并导入一些基础的maven依赖:

 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId><version>4.3.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId></dependency><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.21</version></dependency><dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.5.2</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.4</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.22</version></dependency><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>4.3.0</version></dependency><dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.4.7</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.6</version></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>

项目使用MybatisPlus进行数据库的操作,使用redis作为缓存。使用minio作为项目的存储机构。

MySQL是我本地的服务,redis和minio服务我放在了Linux服务器上。如果有对minio不熟悉的,可以看一下我之前写过的文章:

springboot整合minio(实现文件的上传和下载超详细入门)_minio下载文件-CSDN博客

根据数据库中的表,先创建出相应的controller、service、mapper和相应的实体类。我直接使用mybatisX插件进行相应数据的生成。一共有七张表,相应的SQL脚本,我会连同前后端的代码一起放在git。

至此。后端项目就暂时搭建出了一个基础的模板,我们接下来开始进行前端项目的部署;

前端:

挑选一个文件夹,运行

 npm create vue@latest

命令来创建一个基础的前端vue3项目,在创建项目时可以进行一些基础配置的选择。我在创建前端项目时,选择的编程语言是js,如果有选择ts的可能需要对数据的类型进行相应的指定。

在创建完前端项目之后,我们可能还需要引入一些相应的包。

npm install element-plus --save
npm install @element-plus/icons-vue
npm install sass
npm install pinia-plugin-persistedstate
npm install axios

在项目的终端运行命令来完成相应的依赖下载。下载完成之后。在package.json文件中相应的依赖如下:

至此,我们的前端vue3项目也已经搭建完成了,接下来,就可以开始我们前后端代码的编写了。

代码编写:

先进行前端代码的编写,一步步向后端靠拢,最终完成我们要实现的功能。

前端代码

在搭建环境时,我们引入了一些包的依赖,我们需要在main.js中进行依赖的声明和引用。


import { createApp } from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css' 
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import App from '@/App.vue'
import router from '@/router'
// 1、pinia的持久化插件
import { createPersistedState } from 'pinia-plugin-persistedstate'
// element-plus的图标
import * as ElementPlusIconsVue from '@element-plus/icons-vue'const app = createApp(App)
const pinia = createPinia()//2、 接收createPersistedState函数
const piniaPersistedState = createPersistedState()// 3、在pinia中引入持久化插件
pinia.use(piniaPersistedState)// 全局引入图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {app.component(key, component)}app.use(pinia)
app.use(router)
app.use(ElementPlus, {locale: zhCn,
})
app.mount('#app')

删除vue3项目自带的一些vue组件,将整个vue项目恢复成一个纯净的项目。

首先,我们要做的页面是后台管理的登录页面。

在views目录下创建一个Login.vue 页面,这个页面中进行我们后台管理系统的登录操作。

在router路由中进行我们登录页面的配置,要求在运行项目时,首先跳转的就是登录页面(这也符合我们项目的预期,后台管理类的所有项目一定是要先登录,接下来才能进行功能的操作)

import { createRouter, createWebHistory } from 'vue-router'
import  useToeknStore from '@/stores/useToken'const router = createRouter({history: createWebHistory(import.meta.env.BASE_URL),routes: [// 系统用户登录{path: '/',name: 'login',component: () => import('@/views/Login.vue')}]})// 前置守卫
// 全局拦截、除了登录页面,其他页面都必须授权(这里为pinia定义的token不为空)才能访问
router.beforeEach((to, from) => {const useToken=useToeknStore()if (to.name !== 'login' && !useToken.token) {//  alert("没有登录,自动跳转到登录页面")return { name: 'login' }}else{return true}})export default router

在这个路由文件中,不仅定义了登录页面,同时引入了一个路由前置守卫。这个守卫的功能是,如果没有登录,那么就只能访问登录页面。

(判断有没有登录的标识,就是pinia中token有没有值。如果登录成功,那么就会在pipin中部存入token的值,如果退出登录。那么前端也会删除token的值。借此,我们就可以判断出用户有没有登录了。我们知道,存入pipin中的值,其实是存储到了我们浏览器的localstore中,对于稍微懂点前端的人来说,都是很容易获取和改变的。在真实的项目中,肯定是不会使用这么简单的方法的。这个项目相当于我们个人开发的一个简单项目,所以就无所谓了。)

接下来,我们对axios进行一下封装,这样,每次发送请求到后端时就可以大大简化了。

创建util目录,在这个目录下新建一个request.js文件,在这个文件中封装我们axios。

import axios from "axios";
import  useTokenStore from '@/stores/useToken'
import { ElMessage } from 'element-plus';
// 先建一个api,系统
const api = axios.create({baseURL: "http://localhost:8888",timeout: 5000});// 发送请求前拦截
api.interceptors.request.use(config =>{
const useToken = useTokenStore();
// 系统用户的请求头
if(useToken.token){console.log("请求头toekn=====>", useToken.token);// 设置请求头// config.headers['token'] = useToken.token;config.headers.token = useToken.token;
}return config;},
error =>{return Promise.reject(error);
}
)// 响应前拦截
api.interceptors.response.use(response =>{console.log("响应数据====>", response);
if(response.data.code ==200){
return response.data;
}// 响应失败
if(response.data.code !=200){
console.log("响应失败====>", response.data);}return response.data;
},
error =>{return Promise.reject(error);
}
)export  {api}

现在,我们就可以正式的编写我们后台管理系统的登录页面了。

(需要注意的是,我们的系统是一个后台管理类的系统,所以在首页不能让所有用户自行注册,在首页就写一个登录按钮和一个忘记密码的按钮。用户的添加需要有权限的用户在后台管理系统的功能区进行添加才行)

登录页面的内容如下:

<template><!-- style="font-family:kaiti" --><div class="background"  ><!-- 注册表单,一个对话框 -->
<el-dialog v-model="isRegister"   title="用户注册"   width="30%"  draggable=true><el-form label-width="120px" v-model="registerForm"><el-form-item label="用户名"><el-input type="text"   v-model="registerForm.username"   ><template #prefix><el-icon><Avatar /></el-icon></template></el-input></el-form-item><el-form-item label="密码"><el-input  type="password" v-model="registerForm.password" ><template #prefix><el-icon><Lock /></el-icon></template></el-input></el-form-item><el-form-item><el-button type="primary" @click="registerAdd" >提交</el-button><el-button @click="closeRegister">取消</el-button></el-form-item></el-form>
</el-dialog><!-- 登陆框 -->
<div class="login-box">
<el-formlabel-width="100px":model="loginFrom"style="max-width: 460px":rules="Loginrules"ref="ruleFormRef"><div style=" text-align: center;font-weight: bold;">后台管理系统模板</div> <el-form-item label="用户名"  prop="username"><el-input v-model="loginFrom.username"  clearable  ><template #prefix><el-icon><Avatar /></el-icon></template></el-input></el-form-item><el-form-item label="密码" prop="password"><el-input v-model="loginFrom.password"   show-password   clearable  type="password" ><template #prefix><el-icon><Lock /></el-icon></template></el-input></el-form-item><el-form-item label="验证码"  prop="codeValue"><el-input v-model="loginFrom.codeValue"  style="width: 100px;"  clearable  ></el-input><img :src="codeImage" @click="getCode" style="transform: scale(0.9);"/></el-form-item><!-- <el-checkbox v-model="rememberMe.rememberMe"   >记住我</el-checkbox>  --><!-- 跳转一个新页面 --><el-link type="primary"  style="transform: translateX(330px)"  @click="resetPassword()">忘记密码</el-link><br>
<el-button type="success" @click="getLogin(ruleFormRef)" size="small"   class="my-button">登录</el-button><!-- <el-button type="primary" @click="isRegister=true"   class="my-button">注册</el-button> --></el-form><!-- 按下enter键提交登录请求 --><!-- <input @keyup.enter="getLogin(ruleFormRef)"> --></div></div>
</template><script  setup>
import { ref,onMounted,reactive, onUnmounted } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import useTokenStore  from '@/stores/useToken'
import {  getCodeApi ,loginApi,registerApi} from '@/api/SysLogin';// <!-- 按下enter键提交登录请求 -->const keyDownHandler = (event) => {if (event.key === 'Enter') {// 执行你想要的操作console.log('Enter键被按下了');getLogin(ruleFormRef.value)}}onMounted(() => {window.addEventListener('keydown', keyDownHandler);});onUnmounted(() => {window.removeEventListener('keydown', keyDownHandler);});// 重置密码
const resetPassword=()=>{router.push({name:'resetPassword',query:{type:"sys"
}})}const ruleFormRef =  ref()// const rememberMe=rememberMeStore()const loginFrom=ref({
username:'',
password:'',
codeKey:'',
codeValue:''
// rememberMe:rememberMe.rememberMe
})const Loginrules=reactive({username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],password: [{ required: true, message: '请输入密码', trigger: 'blur' },{ min: 6, max: 12, message: '长度在 6 到 12 个字符', trigger: 'blur'}],codeValue: [{ required: true, message: '请输入验证码', trigger: 'blur' }]})const registerForm=ref({username:'',password:''
})const codeImage=ref('')const isRegister=ref(false)const tokenStore = useTokenStore();const router = useRouter()// 登录,提交阶段
const getLogin = async(formEl) => {if (!formEl)  returnawait formEl.validate(async (valid, fields) => {if (valid) {console.log('submit!')let data = await loginApi(loginFrom.value)console.log("几点几分上了飞机",data.code);if(data.code===200){ElMessage('登录成功')console.log(data.data);tokenStore.token=data.datarouter.replace({name:'layout'})}else{ElMessage('登录失败')ElMessage('失败原因:'+data.message)}} else {ElMessage('请输入完整信息')return;}})}const getCode=async()=>{let {data}=await getCodeApi()console.log(data);console.log("验证码的值为============>",data);loginFrom.value.codeKey=data.codeKeycodeImage.value=data.codeValue
}// 注册,提交阶段
const registerAdd=async()=>{let data=await registerApi(registerForm.value)if(data.code==200){ElMessage('注册成功')registerForm.value={username:'',password:''}isRegister.value=false
}else{ElMessage('注册失败')registerForm.value={username:'',password:''}isRegister.value=false}}const closeRegister=()=>{registerForm.value={username:'',password:''}isRegister.value=false
}// 页面加载完成获取验证码onMounted(()=>{getCode()})</script><style lang="scss" scoped>
// 登录页面总样式
.background{background: url("@/assets/20.png");width:100%;height:100%;  /**宽高100%是为了图片铺满屏幕 */position: fixed;background-size: 100% 100%;font-family:kaiti
}.login-box{border:1px solid #dccfcf;width: 350px;margin:180px auto;padding: 20px 50px 20px 50px;border-radius: 5px;-webkit-border-radius: 5px;-moz-border-radius: 5px;box-shadow: 0 0 25px #909399;background-color:rgba(255,255,255,0.7);//这里最后那个0.7就是为了防止背景表单颜色太浅
}.my-button {margin-right: 100px;width: 400px;padding: 10px 20px;font-size: 16px;background-color: #4CAF50;color: white;border: none;border-radius: 5px;cursor: pointer;}</style>

具体的展现效果如图:

挂载组件时,会发送一个请求验证码的接口到后端。后端我会使用hutoll工具包生成验证码。并将验证码放在redis中,并设置过期时间为5分钟。再通过base64编码的形式将验证码的图片传到前端直接显示。

在这个登录页面中,用户需要输入用户名、密码和验证码到后端。后端会先验证验证码的值如果正确,再验证用户名和密码。如果都正确,那么后端会返回登录成功的提示,并根据用户id生成一个token返回前端,前端收到token之后,会将这个token存入pinia中。接下来跳转到后台管理系统的功能页面

登录页面有一个“忘记密码”的按钮,我们通过这个按钮应该能实现用户密码的重置。

(密码重置通过用户邮箱发送验证码来进行验证,也可以使用短信验证码的形式进行验证,都是可以的)

重置密码页面:

<template><div class="resetPassword">
<h1>密码重置页面</h1>
请输入邮箱:<input  v-model="emailNumber" class="inputEmail" type=email >  </input>
<el-button type="primary" @click="sendEmail">重置密码</el-button><br>
请输入验证码:<input type="text" v-model="code" class="inputEmail"/><el-button type="success" @click="sendCode">提交</el-button><div>
<!-- 修改密码表单,一个对话框 -->
<el-dialog v-model="isPassword"   title="用户修改密码"   width="30%"  draggable=true><el-form label-width="120px" v-model="passwordFrom"><el-form-item label="密码"><el-input  type="password" v-model="passwordFrom.password" show-password   clearable   ><template #prefix><el-icon><Lock /></el-icon></template></el-input></el-form-item><el-form-item label="确认密码"><el-input  type="password" v-model="passwordFrom.doPassword" show-password   clearable  ><template #prefix><el-icon><Lock /></el-icon></template></el-input></el-form-item><el-form-item><el-button type="primary" @click="sendPassword" >提交</el-button><el-button @click="closeRegister">取消</el-button></el-form-item></el-form>
</el-dialog>
</div>
</div></template><script  setup>
import {resetPassword,sendSysCode,sendSysPassword} from "@/api/ResetPassword"
import { ref } from "vue";
import { useRouter } from 'vue-router';let emailNumber = ref('')let code = ref('')let isPassword = ref(false)let passwordFrom = ref({password:'',doPassword:'',email:""
})const router = useRouter()const sendEmail = async() => {
console.log(emailNumber.value);const data= await resetPassword(emailNumber.value)console.log(data);}const sendCode = async() => {const data= await sendSysCode(code.value,emailNumber.value)if(data.code==200){alert("验证码正确,请修改密码")isPassword.value=true
}else{alert("验证码错误,请重新输入")
}console.log(data);}const sendPassword = async() => {if(passwordFrom.value.password==passwordFrom.value.doPassword){passwordFrom.value.email=emailNumber.valueconst data= await sendSysPassword(passwordFrom.value)console.log(data);if(data.code==200){alert("密码修改成功")isPassword.value=falserouter.replace({name:"login"})}else{alert("密码修改失败====>"+data.message)isPassword.value=false}}else{alert("两次密码不一致,请重新输入")}}const closeRegister = () => {isPassword.value=false
}</script><style lang="scss" scoped>
.inputEmail{width: 300px;height: 40px;
}.resetPassword{max-width: 400px;margin: 0 auto;padding: 20px;background-color: #f0f2f5;border-radius: 8px;box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);text-align: center;}</style>

在路由中添加相应的路径,并在路由守卫中加入重置页面。因为这个页面是不需要登录就能访问的:

import { createRouter, createWebHistory } from 'vue-router'
import  useToeknStore from '@/stores/useToken'const router = createRouter({history: createWebHistory(import.meta.env.BASE_URL),routes: [// 系统用户登录{path: '/',name: 'login',component: () => import('@/views/Login.vue')},// 重置密码页面{path: '/resetPassword',name: 'resetPassword',component: () => import('@/views/ResetPassword.vue')},// 通配符路由,匹配所有无法识别的路径
{path: '/:pathMatch(.*)*',component: () => import('@/error/NotFount.vue')
}]})// 前置守卫
// 全局拦截、除了登录页面,其他页面都必须授权(这里为pinia定义的token不为空)才能访问
router.beforeEach((to, from) => {const useToken=useToeknStore()if (to.name !== 'login' && to.name!=='resetPassword'    && !useToken.token) {//  alert("没有登录,自动跳转到登录页面")return { name: 'login' }}else{return true}})export default router

再写一个错误路由统一处理的页面,并加入到路由中。

接下来就可以编写后端的代码来实现登录功能了;

后端代码:

由于我们后端使用了spring security作为安全框架。所以在controller层编写登录逻辑之前,我们还需要在后端做一些security的处理。

在yml配置文件中进行一些信息的配置:

spring:data:redis:host: 192.168.231.110port: 6379password: 123456database: 0timeout: 1000datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/zhangqiao-admin?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTCusername: rootpassword: 123456mail:host: smtp.qq.comusername: 2996809820@qq.compassword: jbtjqbhxeaerdfdidefault-encoding: UTF-8servlet:multipart:max-file-size:  10MB   # 单个文件上传的最大上限max-request-size:  100MB  # 整个请求体的最大上限
mybatis-plus:global-config:db-config:id-type: autologic-delete-value: 1logic-not-delete-value: 0logic-delete-field: isDeletedconfiguration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpllogging:file:path: D:/logs/zhangqiao-admin/spring-admin# PageHelper 分页插件配置
pagehelper:helper-dialect: mysqlreasonable: truesupport-methods-arguments: trueparams: count=countsql
minio:url: http://192.168.231.110:9001username: adminpassword: admin123456bucketName: zhangqiao-adminexclude:syspaths:- /sys/getCaptcha- /sys/user/login- /sys/resetPassword- /sys/sendSysCode- /sys/sendSysPassword
#    - /sys/user/addUserjwt:
#  expiration: 3600000Lsecret: zhangqiao

创建一个Security的配置类。来编写spring security的一些配置。

@Configuration
@EnableWebSecurity
public class MyServiceConfig {@Autowiredprivate JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;@Resourceprivate ExcludeSysPath excludeSysPath;/** security的过滤器链* */@Resourceprivate   CustomAccessDeniedHandler customAccessDeniedHandler;@Resourceprivate CustomAuthenticationEntryPoint customAuthenticationEntryPoint;@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http)throws Exception {
http.csrf(AbstractHttpConfigurer::disable);http.authorizeHttpRequests((auth) ->auth.requestMatchers(excludeSysPath.getSyspaths().toArray(new String[0])).permitAll().anyRequest().authenticated());http.exceptionHandling(e -> e.accessDeniedHandler(customAccessDeniedHandler).authenticationEntryPoint(customAuthenticationEntryPoint));http.cors(cors->{cors.configurationSource(corsConfigurationSource());});
//自定义过滤器放在UsernamePasswordAuthenticationFilter过滤器之前http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);return http.build();
}@Autowired
private MyUserDetailServerImpl myUserDetailsService;/*
* 验证管理器
* */@Beanpublic AuthenticationManager authenticationManager(PasswordEncoder passwordEncoder){DaoAuthenticationProvider provider=new DaoAuthenticationProvider();
//将编写的UserDetailsService注入进来provider.setUserDetailsService(myUserDetailsService);
//将使用的密码编译器加入进来provider.setPasswordEncoder(passwordEncoder);
//将provider放置到AuthenticationManager 中ProviderManager providerManager=new ProviderManager(provider);return providerManager;}//跨域配置@Beanpublic CorsConfigurationSource corsConfigurationSource() {CorsConfiguration configuration = new CorsConfiguration();configuration.setAllowedOrigins(Arrays.asList("*"));configuration.setAllowedMethods(Arrays.asList("*"));configuration.setAllowedHeaders(Arrays.asList("*"));UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", configuration);return source;}/** 密码加密器*/
@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}}

这个配置类中编写了密码加密器、跨域相关、验证管理、定义了一个有关排除路径的bean,注入进配置文件。配置了未登录和权限不足时后端返回的一些具体信息,添加了一个JWT的拦截器,放在了security的登录拦截器之前。我们知道security的实现就是基于拦截器链的形式,在登录拦截器之前加入JWT的token拦截器,这样就能实现我们已经登录过的用户再访问其他资源时能正常访问。并且在 JWT拦截器中实现token的刷新;

定义一个MyTUserDetail,实现UserDetails接口,来当security的用户类:

@Data
public class MyTUserDetail implements Serializable, UserDetails {private static final long serialVersionUID = 1L;private User users;//    角色private List<String> roles;//    权限private  List<String> permissions;@JsonIgnore  //json忽略@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {List<GrantedAuthority> list =  new ArrayList<>();//        如果角色不用空,则将角色添加到list中if (!ObjectUtils.isEmpty(roles)){roles.forEach(role->list.add(new SimpleGrantedAuthority(role)));}//                如果权限不用空,则将权限添加到list中if (!ObjectUtils.isEmpty(permissions)){permissions.forEach(permission->list.add(new SimpleGrantedAuthority(permission)));}return list;}@JsonIgnore@Overridepublic String getPassword() {return this.getUsers().getPassword();}@JsonIgnore@Overridepublic String getUsername() {return this.getUsers().getUsername();}@JsonIgnore@Overridepublic boolean isAccountNonExpired() {return this.getUsers().getStatus()==0;}@JsonIgnore@Overridepublic boolean isAccountNonLocked() {return this.getUsers().getStatus()==0;}@JsonIgnore@Overridepublic boolean isCredentialsNonExpired() {return this.getUsers().getStatus()==0;}@JsonIgnore@Overridepublic boolean isEnabled() {return this.getUsers().getStatus()==0;}
}

定义一个MyUserDetailServerImpl类,实现UserDetailsService接口,在这个类中实现用户名和密码的具体查询:(现在我还没有实现权限的查询,按理说在登录时就应该一块进行的,现在,我只进行用户的登录,查询权限等到之后再开发。)

@Service
@Slf4j
public class MyUserDetailServerImpl implements UserDetailsService {@AutowiredUserMapper userService;@Autowired
private RedisTemplate<String,String> redisTemplate;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userService.selectOne(new LambdaQueryWrapper<User>().eq(StringUtils.hasText(username), User::getUsername, username));if (user == null) {throw new UsernameNotFoundException("用户名不存在");}MyTUserDetail myTUserDetail=new MyTUserDetail();myTUserDetail.setUsers(user);return myTUserDetail;}
}

JWT的token拦截器,获取登录的用户信息和用户拥有的权限,放在SecurityContextHolder。在后面要用到用户信息时可以直接在SecurityContextHolder中得到登陆的用户信息。并同时在JWT拦截器中进行过期时间的刷新。

@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Autowiredprivate RedisTemplate<String,String> redisTemplate;@Autowiredprivate JwtUtil jwtUtil;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {//获取请求头中的tokenString token = request.getHeader("token");System.out.println("前端的token信息=======>"+token);//如果token为空直接放行,由于用户信息没有存放在SecurityContextHolder.getContext()中所以后面的过滤器依旧认证失败符合要求if(!StringUtils.hasText(token)){filterChain.doFilter(request,response);return;}//        解析Jwt中的用户idInteger userId = jwtUtil.getUsernameFromToken(token);//从redis中获取用户信息String redisUser = redisTemplate.opsForValue().get("UserLogin:"+ userId);if(!StringUtils.hasText(redisUser)){filterChain.doFilter(request,response);return;}//        刷新tokenString newToken = jwtUtil.refreshToken(token);redisTemplate.opsForValue().set("UserLogin:"+userId,redisUser,60, TimeUnit.MINUTES);//        将redis中的用户信息转换成MyTUserDetail对象MyTUserDetail myTUserDetail= JSON.parseObject(redisUser, MyTUserDetail.class);log.info("Jwt过滤器中MyUserDetail的值============>"+myTUserDetail.toString());//将用户信息存放在SecurityContextHolder.getContext(),后面的过滤器就可以获得用户信息了。这表明当前这个用户是登录过的,后续的拦截器就不用再拦截了UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=new UsernamePasswordAuthenticationToken(myTUserDetail,null,myTUserDetail.getAuthorities());SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);filterChain.doFilter(request,response);}}

现在,我们就可以在controller中实现用户的登录功能了。

用户登录controller:
 

  @PostMapping("/login")public Result<String> login(@RequestBody LoginDto loginDto){String token = userService.login(loginDto);return Result.successData(token);}

相应的service实现:

 @Autowired
private RedisTemplate<String,String> redisTemplate;@AutowiredAuthenticationManager authenticationManager;@Overridepublic String login(LoginDto loginDto) {
//        先检验验证码String codeRedis = redisTemplate.opsForValue().get(loginDto.getCodeKey());if (codeRedis==null){throw new ResultException(555,"验证码不存在");}if (!codeRedis.equals(loginDto.getCodeValue().toLowerCase())) {throw new ResultException(555, "验证码错误");}
//        验证码正确,删除redis中的验证码redisTemplate.delete(loginDto.getCodeKey());log.info("用户登录");
//        用户登录UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginDto.getUsername(),loginDto.getPassword());Authentication authenticate = authenticationManager.authenticate(authenticationToken);if(authenticate==null){throw new  ResultException(400,"用户名或密码错误");}
//        获取返回的用户信息Object principal = authenticate.getPrincipal();MyTUserDetail myTUserDetail=(MyTUserDetail) principal;System.out.println(myTUserDetail);
//        使用Jwt生成token,并将用户的id传入String token = jwtUtil.generateToken(myTUserDetail.getUsers().getId());redisTemplate.opsForValue().set("UserLogin:"+ myTUserDetail.getUsers().getId(),JSON.toJSONString(myTUserDetail),60, TimeUnit.MINUTES);return token;}

至此,我们就完成了用户登录的全过程。要注意我们需要放行的路径,验证码的生成路径,用户登录路径、重置密码的路径等都需要进行放行。

由于数据库中还没有数据,所以我先在test测试中生成一条数据,再在前端进行登录;

前端登录的结果如下:

至此,我们的登录功能就实行了。在下篇文章中,我会实现其他的功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/863258.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

狼人杀系列

目录 杀人游戏&#xff08;天黑请闭眼&#xff09; &#xff08;1&#xff09;入门版 &#xff08;2&#xff09;标准版 &#xff08;3&#xff09;延伸版——百度百科 &#xff08;3.1&#xff09;引入医生和秘密警察 &#xff08;3.2&#xff09;引入狙击手、森林老人和…

Python WebSocket自动化测试:构建高效接口测试框架

为了更高效地进行WebSocket接口的自动化测试&#xff0c;我们可以搭建一个专门的测试框架。本文将介绍如何使用Python构建一个高效的WebSocket接口测试框架&#xff0c;并重点关注以下四个方面的内容&#xff1a;运行测试文件封装、报告和日志的封装、数据驱动测试以及测试用例…

50-2 内网信息收集 - 内网工作环境(域相关知识)

一、工作组 工作组(Work Group)是局域网中最基本的资源管理模式,适用于小规模网络环境。 工作组的定义: 工作组是将不同功能或部门的计算机分组管理的方式。它提供了层次化的网络资源管理,使得组织内的计算机可以按照功能或部门分类。每个工作组有一个自定义的主机名称,…

1-爬虫基础知识(6节课学会爬虫)

1-爬虫基础知识&#xff08;6节课学会爬虫&#xff09; 1.什么是爬虫2.爬取的数据去哪了3.需要的软件和环境4.浏览器的请求&#xff08;1&#xff09;Url&#xff08;2&#xff09;浏览器请求url地址&#xff08;3&#xff09;url地址对应的响应 5.认识HTTP/HTTPS5.1 http协议之…

海康+libtorch的血泪教训

一、LibTorch使用&#xff0c; 详见&#xff1a; /INCLUDE:?warp_sizecudaatYAHXZ 二、海康二次开发&#xff0c; 目前选4.31&#xff0c;只能c14。 三、做dll注意&#xff1a;

Excel+vue+java实现批量处理功能

需求背景: 产品创建流程比较复杂&#xff0c;有时候需要一次性创建多至10个&#xff0c;所以做了Excel维护产品信息&#xff0c;直接导入创建的功能。能极大提高效率。 简要概括实现&#xff1a; 一、参考单个创建&#xff0c;设计创建模板&#xff0c;表头对应填写字段名&…

ComfyUI汉化插件安装

步骤一&#xff1a;点击Manager 步骤二&#xff1a;选择安装插件 步骤三&#xff1a;搜索Translation&#xff0c;选择第一个点击右边得安装 步骤四&#xff1a;点击下放得RESTART进行重启 步骤五&#xff1a;等待重启完成后&#xff0c;点击设置 步骤六&#xff1a;选择中文语…

【Nginx】源码安装

1.安装地址 Nginx官网&#xff1a;nginx: download 2.下载依赖 //一键安装上面四个依赖 yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel 3.上传解压编译安装 //解压压缩包tar -xvf nginx-1.26.1.tar.gz //进入nginx cd nginx-1.26.1/ //安装到指定位…

llm学习-1(包含如何使用github的codespace):

本文学习参考&#xff1a;datawhalechina/llm-universe: 本项目是一个面向小白开发者的大模型应用开发教程&#xff0c;在线阅读地址&#xff1a;https://datawhalechina.github.io/llm-universe/ 一些可使用的大模型地址&#xff1a; Claude 使用地址 PaLM 官方地址 Gemini…

力扣第一道困难题《3. 无重复字符的最长子串》,c++

目录 方法一&#xff1a; 方法二&#xff1a; 方法三&#xff1a; 方法四&#xff1a; 没有讲解&#xff0c;但给出了优秀题解 本题链接&#xff1a;4. 寻找两个正序数组的中位数 - 力扣&#xff08;LeetCode&#xff09; 话不多说&#xff0c;我们直接开始进行本题的思路解…

24/06/24(12.1117)指针进阶 ,冒泡和快排 习题为依托巩固概念(strlen,sizeof,字符串,数组,指针大小的区别)

回调函数 回过头来调用的函数 #include <stdio.h> #include <stdlib.h> int Find_Max(int arr[], int n){ int max_value arr[0]; for (int i 1; i < n; i){ if (max_value < arr[i]) max_value arr[i]; } return…

1,Windows-本地Linux 系统(WSL)

目录 第一步电脑设置 第二步安装Ubuntu 第三文件传递 开发人员可以在 Windows 计算机上同时访问 Windows 和 Linux 的强大功能。 通过适用于 Linux 的 Windows 子系统 (WSL)&#xff0c;开发人员可以安装 Linux 发行版&#xff08;例如 Ubuntu、OpenSUSE、Kali、Debian、Arc…

【子串】3. 无重复的最长子串

3. 无重复的最长子串 难度&#xff1a;中等难度 力扣地址&#xff1a;https://leetcode.cn/problems/longest-substring-without-repeating-characters/description/ 题目看起来简单&#xff0c;刷起来有好几个坑&#xff0c;特此记录一下&#xff0c;解法比官网的更加简单&…

[OtterCTF 2018]Play Time

还是这个程序 。。要找到游戏名字查看 进程 psscan pstree pslist 0x000000007d686b30 Rick And Morty 3820 2728 0x000000000b59a000 2018-08-04 19:32:55 UTC0000 0x000000007d7cb740 LunarMS.exe 708 2728 0x00000000731cb000 2018-08-04 19:27:39 UTC0000…

《昇思25天学习打卡营第12天 | 昇思MindSpore基于MindSpore的GPT2文本摘要》

12天 本节学习了基于MindSpore的GPT2文本摘要。 1.数据集加载与处理 1.1.数据集加载 1.2.数据预处理 2.模型构建 2.1构建GPT2ForSummarization模型 2.2动态学习率 3.模型训练 4.模型推理

支持纳管达梦数据库,命令存储支持对接Elasticsearch 8,JumpServer堡垒机v3.10.11 LTS版本发布

2024年6月24日&#xff0c;JumpServer开源堡垒机正式发布v3.10.11 LTS版本。JumpServer开源项目组将对v3.10 LTS版本提供长期的支持和优化&#xff0c;并定期迭代发布小版本。欢迎广大社区用户升级至v3.10 LTS最新版本&#xff0c;以获得更佳的使用体验。 在JumpServer v3.10.…

Github Page 使用手册(保姆级教程!)

搭建个人网站&#xff1f;没有服务器&#xff1f;那不如尝试一下 Github Page &#xff01; 最近我正好在搭建个人网站&#xff0c;于是就写一篇博客来详细介绍 Github Page 的使用、部署方式吧&#xff01; 一、进入 Github 访问&#xff1a;github.com 如果你没有 github…

Linux中彩色打印

看之前关注下公众号呗 第1部分&#xff1a;引言 1.1 Python在文本处理中的重要性 Python作为一种广泛使用的高级编程语言&#xff0c;以其简洁的语法和强大的功能在文本处理领域占有一席之地。无论是数据清洗、自动化脚本编写&#xff0c;还是复杂的文本分析&#xff0c;Py…

RHCE四---web服务器的高级优化方案

一、Web服务器&#xff08;2&#xff09; 基于https协议的静态网站 概念解释 HTTPS&#xff08;全称&#xff1a;Hyper Text Transfer Protocol over Secure Socket Layer 或 Hypertext TransferProtocol Secure&#xff0c;超文本传输安全协议&#xff09;&#xff0c;是以…

在Ubuntu 18.04.6 LTS 交叉编译生成Windows 11下的gdb 8.1.1

1. 安装mingw sudo apt-get install mingw-w64 2. 下载 gdb 8.1.1源码 https://ftp.gnu.org/gnu/gdb/gdb-8.1.1.tar.gz 解压命令 tar -xf gdb-8.1.1.tar.gz 进入目录,创建build目录: hq@hq:~/gdb-8.1.1/build$ 执行配置 ../confi