当前位置: 首页 > news >正文

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

前言:

目前,前后端分离开发已经成为当前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测试中生成一条数据,再在前端进行登录;

前端登录的结果如下:

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

相关文章:

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

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

【C++ | 委托构造函数】委托构造函数 详解 及 例子源码

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…...

iCloud邮件全攻略:设置与使用终极指南

标题&#xff1a;iCloud邮件全攻略&#xff1a;设置与使用终极指南 摘要 iCloud邮件是Apple提供的一项邮件服务&#xff0c;允许用户在所有Apple设备上访问自己的邮件。本文将详细介绍如何在各种设备和邮件客户端上设置和使用iCloud邮件账户&#xff0c;确保用户能够充分利用…...

【计算机毕业设计】基于微信小程序的电子购物系统的设计与实现【源码+lw+部署文档】

包含论文源码的压缩包较大&#xff0c;请私信或者加我的绿色小软件获取 免责声明&#xff1a;资料部分来源于合法的互联网渠道收集和整理&#xff0c;部分自己学习积累成果&#xff0c;供大家学习参考与交流。收取的费用仅用于收集和整理资料耗费时间的酬劳。 本人尊重原创作者…...

CSS实现动画

CSS实现动画主要有三种方式&#xff1a;transition&#xff0c;transform&#xff0c;和animation1。以下是一些详细的逻辑&#xff0c;实例和注意事项&#xff1a; Transition&#xff1a;transition可以为一个元素在不同状态之间切换的时候定义不同的过渡效果。例如&#xff…...

Python+Pytest+Allure+Yaml+Jenkins+GitLab接口自动化测试框架详解

PythonPytestAllureYaml接口自动化测试框架详解 编撰人&#xff1a;CesareCheung 更新时间&#xff1a;2024.06.20 一、技术栈 PythonPytestAllureYamlJenkinsGitLab 版本要求&#xff1a;Python3.7.0,Pytest7.4.4,Allure2.18.1,PyYaml6.0 二、环境配置 安装python3.7&…...

[OtterCTF 2018]Bit 4 Bit

我们已经发现这个恶意软件是一个勒索软件。查找攻击者的比特币地址。** 勒索软件总喜欢把勒索标志丢在显眼的地方&#xff0c;所以搜索桌面的记录 volatility.exe -f .\OtterCTF.vmem --profileWin7SP1x64 filescan | Select-String “Desktop” 0x000000007d660500 2 0 -W-r-…...

计算机视觉全系列实战教程 (十四):图像金字塔(高斯金字塔、拉普拉斯金字塔)

1.图像金字塔 (1)下采样 从G0 -> G1、G2、G3 step01&#xff1a;对图像Gi进行高斯核卷积操作&#xff08;高斯滤波&#xff09;step02&#xff1a;删除所有的偶数行和列 void cv::pyrDown(cv::Mat &imSrc, //输入图像cv::Mat &imDst, //下采样后的输出图像cv::Si…...

正确重写equals和hashcode方法

1. 重写的原因 如有个User对象如下&#xff1a; public class User {private String name;private Integer age; }如果不重写equals方法和hashcode方法&#xff0c;则&#xff1a; public static void main(String[] args) {User user1 new User("userA", 30);Us…...

数据质量管理-时效性管理

前情提要 根据GB/T 36344-2018《信息技术 数据质量评价指标》的标准文档&#xff0c;当前数据质量评价指标框架中包含6评价指标&#xff0c;在实际的数据治理过程中&#xff0c;存在一个关联性指标。7个指标中存在4个定性指标&#xff0c;3个定量指标&#xff1b; 定性指标&am…...

python 实例002 - 数据转换

题目&#xff1a; 有一组用例数据如下&#xff1a; cases [[case_id, case_title, url, data, excepted],[1, 用例1, www.baudi.com, 001, ok],[4, 用例4, www.baudi.com, 002, ok],[2, 用例2, www.baudi.com, 002, ok],[3, 用例3, www.baudi.com, 002, ok],[5, 用例5, www.ba…...

1.k8s:架构,组件,基础概念

目录 一、k8s了解 1.什么是k8s 2.为什么要k8s &#xff08;1&#xff09;部署方式演变 &#xff08;2&#xff09;k8s作用 &#xff08;3&#xff09;Mesos&#xff0c;Swarm&#xff0c;K8S三大平台对比 二、k8s架构、组件 1.k8s架构 2.k8s基础组件 3.k8s附加组件 …...

动态规划基础练习

我们需要先从数组较大的开始进行处理&#xff0c;每次考察上下左右的&#xff0c;比较当前存储的最大值和转移来的值&#xff0c;哪一个大一点 #define _CRT_SECURE_NO_WARNINGS #include<bits/stdc.h> using namespace std;int n, m; int a[105][105]; int addx[] { 0,…...

基于Java的地方废物回收机构管理系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;Java技术&#xff0c;MIS的总体思想&#xff0c;MySQL数据库 工具&#xff1a;Eclipse&#xff0c;…...

Leetcode 450:删除二叉搜索树中的节点

给定一个二叉搜索树的根节点 root 和一个值 key&#xff0c;删除二叉搜索树中的 key 对应的节点&#xff0c;并保证二叉搜索树的性质不变。返回二叉搜索树&#xff08;有可能被更新&#xff09;的根节点的引用。 一般来说&#xff0c;删除节点可分为两个步骤&#xff1a; 首先…...

Go 中使用map时注意的问题

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…...

english-works

前奏&#xff08;prelude&#xff09;&#xff1a; To build up our body, our school sports meeting was held on our play ground last Thursday. All the students in my class took an active part in sports meeting. It began with an opening ceremony on the play g…...

Kubernetes面试整理-如何利用PodSecurityPolicies来提高集群的安全性?

PodSecurityPolicy (PSP) 是 Kubernetes 中用于定义和控制 Pod 安全配置的策略。通过 PSP,可以设置对 Pod 的一些安全约束条件,从而提高集群的安全性。虽然 PSP 从 Kubernetes 1.21 开始已被弃用,并在 1.25 版本中移除,但在一些旧版 Kubernetes 集群中,PSP 仍然是一个重要…...

YOLO网络结构特点收录

YOLO网络结构特点收录 YOLO&#xff08;You Only Look Once&#xff09;网络结构随着版本迭代不断进化&#xff0c;以下是一些关键版本的网络结构特点概述&#xff1a; YOLOv1 输入&#xff1a;将图像调整至固定尺寸&#xff0c;如448x448像素。骨干网络&#xff1a;初期版本…...

人生最有力,最棒的十句话!

人生最有力&#xff0c;最棒的十句话 1、允许一切事发生&#xff0c;所有一切发生的事不是你能阻挡了的&#xff0c;你接受&#xff0c;他也发生&#xff0c;你不接受&#xff0c;他也发生&#xff0c;你还不如坦然面对接受现实。 2、你焦虑的时候千万不要躺着啥也不干&#xf…...

ASUS华硕A豆14笔记本电脑I421EAYB,I421EQYB_ADOL14EA工厂模式原厂Win11系统安装包下载

适用型号&#xff1a;ADOL14EA笔记本I421EAYB、I421EQYB 链接&#xff1a;https://pan.baidu.com/s/1krU8m_lbApyUfZQo5E4cCQ?pwd0ewl 提取码&#xff1a;0ewl 华硕原装WIN11系统工厂安装包&#xff0c;带有MyASUS WinRE RECOVERY恢复功能、自带所有驱动、出厂主题壁纸、系…...

丙酮传感器TGS1820在呼吸气体丙酮含量分析检测中的应用

随着科技的进步&#xff0c;无创检测技术逐渐成为医疗保健领域的新宠。其中&#xff0c;基于呼吸气体的分析检测技术以其独特的优势受到了广泛关注。呼吸气中的挥发性有机化合物&#xff08;VOCs&#xff09;不仅为研究者们提供了关于人体健康状态的宝贵信息&#xff0c;而且它…...

全国今日油价查询-全国今日油价查询接口-API接口

关于油价的信息&#xff0c;以下是详细的分点表示和归纳&#xff1a; 最新油价调整&#xff1a; 时间&#xff1a;2024年6月28日0时 调整内容&#xff1a;汽油价格上调210元/吨&#xff0c;柴油价格上调200元/吨。 涨幅&#xff1a;加油站油价上涨0.16元/升-0.20元/升。 具体油…...

MT1568 学生成绩

题目 有3个学生&#xff0c;每个学生有3门课的成绩&#xff0c;从键盘输入数据&#xff0c;包括学号、姓名、三门课成绩&#xff0c;学号整型&#xff0c;姓名字符型&#xff0c;成绩实型&#xff0c;计算3门课程总平均成绩&#xff0c;以及平均分最高的学生信息。不考虑非法成…...

医院消防设施设备管理系统

医院为人员密集场所&#xff0c;且多为各类病患及其陪护人员&#xff0c;一旦发生火灾&#xff0c;人员疏散逃生困难&#xff0c;容易造成较严重的生命与财产损失。为规范医院的消防设施设备管理&#xff0c;通过凡尔码系统对医院消防设施设备进行信息化管理&#xff0c;提高医…...

[Go 微服务] go-micro + consul 的使用

文章目录 1.go-micro 介绍2.go-micro 的主要功能3.go-micro 安装4.go-micro 的使用4.1 创建服务端4.2 配置服务端 consul4.3 生成客户端 5.goodsinfo 服务5.1 服务端开发5.2 客户端开发 1.go-micro 介绍 Go Micro是一个简化分布式开发 的微服务生态系统&#xff0c;该系统为开…...

嵌入式网页服务实现

嵌入式网页服务的实现方式主要可以归纳为以下几种&#xff1a; 单片机Webchip网关&#xff1a; Webchip&#xff1a;作为专用网络接口芯片&#xff0c;独立于单片机与网关&#xff0c;通过SPI接口与MCU进行指令交互&#xff0c;并通过RS232、USB、Modem等接口与PC作为网关进行通…...

python---OpenCv(二),背景分离方法较有意思

目录 边界矩形 旋转矩形(最小外接矩形): 计算轮廓 找4个点的坐标 把浮点型转为Int 画轮廓 边界矩形--&#xff08;最大外接矩形&#xff09; 转灰度 找轮廓 找顶点 画矩形 显示 背景分离方法&#xff08;这个很好玩&#xff0c;可以识别在动的物体&#xff09; 边…...

java TCP服务器与客户端通信示例

目录 背景: TCP服务代码解析: TCP服务器的代码: 服务器程序的工作流程: TCP客户端代码解析 : TCP客户端代码: 客户端工作流程: 总结: 背景: 如何使用Java编程语言实现一个简单的TCP服务器和客户端。那么TCP是什么?TCP&#xff08;Transmission Control Protocol&…...

【C++】构造函数和析构函数

目录 对象初始化-构造函数构造函数的分类构造函数的调用拷贝构造的应用构造函数调用规则深拷贝和浅拷贝初始化列表类对象作为类成员静态成员 对象释放-析构函数 对象初始化-构造函数 构造函数是类实例化的时候会自动调用的初始化函数&#xff0c;如果用户不写编译器会提供一个…...

wordpress 淘宝优惠券/关键词推广营销

EasyHook 中的注入方法。 函数原型 // EasyHook 中的命名比较有意思&#xff0c;Rh 代表的就是Remote Hook&#xff0c;此函数就是远程钩子的一个子过程----注入&#xff0c;前面的宏代表它是导出函数。 EASYHOOK_NT_EXPORT RhInjectLibrary(ULONG InTargetPID,ULONG InWakeUpT…...

中国建设银行网站运营模式/杭州线上推广

综合整理自&#xff1a;https://docs.microsoft.com/zh-cn/windows/python/近日&#xff0c;微软上线了一套 Python 教程《Develop with Python on Windows》。无论你是初学者想要学习 Python 入门&#xff0c;还是将 Python 用于 Web 开发&#xff0c;或是 将 Python 用于脚本…...

电商主图制作软件/优化大师优化项目有

#PS&#xff1a;要转载请注明出处&#xff0c;本人版权所有 #PS:这个只是 《 我自己 》理解&#xff0c;如果和你的 #原则相冲突&#xff0c;请谅解&#xff0c;勿喷 此文的编译篇在&#xff1a;http://blog.csdn.net/u011728480/article/details/78037404 原本不想写这篇…...

企业名称的英文做网站名/西安关键词排名软件

作者&#xff1a;朱金灿 来源&#xff1a;http://blog.csdn.net/clever101Windows的批处理命令固然比不上unix的shell脚本强大&#xff0c;但用好了仍能给我们的工作带来很大作用。一个朋友问我为什么学习批处理命令&#xff0c;我以《程序员修炼之道——从小工到专家》一书的一…...

b2c网站源码/免费刷赞网站推广qq免费

展开全部在数据库中储e69da5e887aa62616964757a686964616f31333365646333存选择题&#xff0c;设置3张表比较好。以下是设置方法&#xff1a;方法一&#xff1a;如果能确定选项最多就是6个可以考虑建一个表,字段定义为&#xff1a;题目编号(PK)、题干、选项1、选项2……选项6。…...

最新章节 第一百四十七章 做视频网站/可靠的网站优化

添加verifyed标签 http://blog.csdn.net/terence427/article/details/16840697转载于:https://www.cnblogs.com/itzxy/p/6255833.html...