大埔县住房和城乡规划建设局网站公司企业做网站好做吗
web/
2025/10/5 15:52:37/
文章来源:
大埔县住房和城乡规划建设局网站,公司企业做网站好做吗,如何做好网站建设内容的策划,wordpress评论邮件前言#xff1a;
目前#xff0c;前后端分离开发已经成为当前web开发的主流。目前最流行的技术选型是前端vue3后端的spring boot3#xff0c;本次。就基于这两个市面上主流的框架来开发出一套基本的后台管理系统的模板#xff0c;以便于我们今后的开发。
前端使用vue3ele…前言
目前前后端分离开发已经成为当前web开发的主流。目前最流行的技术选型是前端vue3后端的spring boot3本次。就基于这两个市面上主流的框架来开发出一套基本的后台管理系统的模板以便于我们今后的开发。
前端使用vue3element-plus开发。后端使用spring boot3spring security作为项目的支撑使用MySQL8.0.30作数据存储使用redis作缓存使用minio作为项目的存储机构。
后台管理系统是比较注重权限的本项目使用市面上最流行的RBAC模型。建立用户、角色、权限和它们两两之间的对映关系一共五张表日志管理两张表一张记录用户的行为、一张记录用户的操作。
搭建基础的环境
后端
创建一个spring boot项目并导入一些基础的maven依赖 dependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-configuration-processor/artifactIdoptionaltrue/optional/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId/dependencydependencygroupIdcom.github.xiaoymin/groupIdartifactIdknife4j-openapi3-jakarta-spring-boot-starter/artifactIdversion4.3.0/version/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-security/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-mail/artifactId/dependencydependencygroupIdcom.alibaba.fastjson2/groupIdartifactIdfastjson2/artifactIdversion2.0.21/version/dependencydependencygroupIdio.minio/groupIdartifactIdminio/artifactIdversion8.5.2/version/dependencydependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-boot-starter/artifactIdversion3.5.4/version/dependencydependencygroupIdcn.hutool/groupIdartifactIdhutool-all/artifactIdversion5.8.22/version/dependencydependencygroupIdcom.auth0/groupIdartifactIdjava-jwt/artifactIdversion4.3.0/version/dependencydependencygroupIdcom.github.pagehelper/groupIdartifactIdpagehelper-spring-boot-starter/artifactIdversion1.4.7/version/dependencydependencygroupIdcom.alibaba/groupIdartifactIddruid-spring-boot-starter/artifactIdversion1.2.6/version/dependencydependencygroupIdcom.mysql/groupIdartifactIdmysql-connector-j/artifactIdscoperuntime/scope/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency/dependencies项目使用MybatisPlus进行数据库的操作使用redis作为缓存。使用minio作为项目的存储机构。
MySQL是我本地的服务redis和minio服务我放在了Linux服务器上。如果有对minio不熟悉的可以看一下我之前写过的文章
springboot整合minio实现文件的上传和下载超详细入门_minio下载文件-CSDN博客
根据数据库中的表先创建出相应的controller、service、mapper和相应的实体类。我直接使用mybatisX插件进行相应数据的生成。一共有七张表相应的SQL脚本我会连同前后端的代码一起放在git。
至此。后端项目就暂时搭建出了一个基础的模板我们接下来开始进行前端项目的部署
前端
挑选一个文件夹运行 npm create vuelatest
命令来创建一个基础的前端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-vueconst 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/useTokenconst 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 useTokenuseToeknStore()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!-- stylefont-family:kaiti --div classbackground !-- 注册表单,一个对话框 --
el-dialog v-modelisRegister title用户注册 width30% draggabletrueel-form label-width120px v-modelregisterFormel-form-item label用户名el-input typetext v-modelregisterForm.username template #prefixel-iconAvatar //el-icon/template/el-input/el-form-itemel-form-item label密码el-input typepassword v-modelregisterForm.password template #prefixel-iconLock //el-icon/template/el-input/el-form-itemel-form-itemel-button typeprimary clickregisterAdd 提交/el-buttonel-button clickcloseRegister取消/el-button/el-form-item/el-form
/el-dialog!-- 登陆框 --
div classlogin-box
el-formlabel-width100px:modelloginFromstylemax-width: 460px:rulesLoginrulesrefruleFormRefdiv style text-align: center;font-weight: bold;后台管理系统模板/div el-form-item label用户名 propusernameel-input v-modelloginFrom.username clearable template #prefixel-iconAvatar //el-icon/template/el-input/el-form-itemel-form-item label密码 proppasswordel-input v-modelloginFrom.password show-password clearable typepassword template #prefixel-iconLock //el-icon/template/el-input/el-form-itemel-form-item label验证码 propcodeValueel-input v-modelloginFrom.codeValue stylewidth: 100px; clearable /el-inputimg :srccodeImage clickgetCode styletransform: scale(0.9);//el-form-item!-- el-checkbox v-modelrememberMe.rememberMe 记住我/el-checkbox --!-- 跳转一个新页面 --el-link typeprimary styletransform: translateX(330px) clickresetPassword()忘记密码/el-linkbr
el-button typesuccess clickgetLogin(ruleFormRef) sizesmall classmy-button登录/el-button!-- el-button typeprimary clickisRegistertrue classmy-button注册/el-button --/el-form!-- 按下enter键提交登录请求 --!-- input keyup.entergetLogin(ruleFormRef) --/div/div
/templatescript 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 rememberMerememberMeStore()const loginFromref({
username:,
password:,
codeKey:,
codeValue:
// rememberMe:rememberMe.rememberMe
})const Loginrulesreactive({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 registerFormref({username:,password:
})const codeImageref()const isRegisterref(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.code200){ElMessage(登录成功)console.log(data.data);tokenStore.tokendata.datarouter.replace({name:layout})}else{ElMessage(登录失败)ElMessage(失败原因data.message)}} else {ElMessage(请输入完整信息)return;}})}const getCodeasync(){let {data}await getCodeApi()console.log(data);console.log(验证码的值为,data);loginFrom.value.codeKeydata.codeKeycodeImage.valuedata.codeValue
}// 注册,提交阶段
const registerAddasync(){let dataawait registerApi(registerForm.value)if(data.code200){ElMessage(注册成功)registerForm.value{username:,password:}isRegister.valuefalse
}else{ElMessage(注册失败)registerForm.value{username:,password:}isRegister.valuefalse}}const closeRegister(){registerForm.value{username:,password:}isRegister.valuefalse
}// 页面加载完成获取验证码onMounted((){getCode()})/scriptstyle langscss 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中。接下来跳转到后台管理系统的功能页面
登录页面有一个“忘记密码”的按钮我们通过这个按钮应该能实现用户密码的重置。
密码重置通过用户邮箱发送验证码来进行验证也可以使用短信验证码的形式进行验证都是可以的
重置密码页面
templatediv classresetPassword
h1密码重置页面/h1
请输入邮箱input v-modelemailNumber classinputEmail typeemail /input
el-button typeprimary clicksendEmail重置密码/el-buttonbr
请输入验证码input typetext v-modelcode classinputEmail/el-button typesuccess clicksendCode提交/el-buttondiv
!-- 修改密码表单,一个对话框 --
el-dialog v-modelisPassword title用户修改密码 width30% draggabletrueel-form label-width120px v-modelpasswordFromel-form-item label密码el-input typepassword v-modelpasswordFrom.password show-password clearable template #prefixel-iconLock //el-icon/template/el-input/el-form-itemel-form-item label确认密码el-input typepassword v-modelpasswordFrom.doPassword show-password clearable template #prefixel-iconLock //el-icon/template/el-input/el-form-itemel-form-itemel-button typeprimary clicksendPassword 提交/el-buttonel-button clickcloseRegister取消/el-button/el-form-item/el-form
/el-dialog
/div
/div/templatescript 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.code200){alert(验证码正确请修改密码)isPassword.valuetrue
}else{alert(验证码错误请重新输入)
}console.log(data);}const sendPassword async() {if(passwordFrom.value.passwordpasswordFrom.value.doPassword){passwordFrom.value.emailemailNumber.valueconst data await sendSysPassword(passwordFrom.value)console.log(data);if(data.code200){alert(密码修改成功)isPassword.valuefalserouter.replace({name:login})}else{alert(密码修改失败data.message)isPassword.valuefalse}}else{alert(两次密码不一致,请重新输入)}}const closeRegister () {isPassword.valuefalse
}/scriptstyle langscss 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/useTokenconst 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 useTokenuseToeknStore()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?useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneUTCusername: rootpassword: 123456mail:host: smtp.qq.comusername: 2996809820qq.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: countcountsql
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 providernew DaoAuthenticationProvider();
//将编写的UserDetailsService注入进来provider.setUserDetailsService(myUserDetailsService);
//将使用的密码编译器加入进来provider.setPasswordEncoder(passwordEncoder);
//将provider放置到AuthenticationManager 中ProviderManager providerManagernew 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 ListString roles;// 权限private ListString permissions;JsonIgnore //json忽略Overridepublic Collection? extends GrantedAuthority getAuthorities() {ListGrantedAuthority 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;}JsonIgnoreOverridepublic String getPassword() {return this.getUsers().getPassword();}JsonIgnoreOverridepublic String getUsername() {return this.getUsers().getUsername();}JsonIgnoreOverridepublic boolean isAccountNonExpired() {return this.getUsers().getStatus()0;}JsonIgnoreOverridepublic boolean isAccountNonLocked() {return this.getUsers().getStatus()0;}JsonIgnoreOverridepublic boolean isCredentialsNonExpired() {return this.getUsers().getStatus()0;}JsonIgnoreOverridepublic boolean isEnabled() {return this.getUsers().getStatus()0;}
}
定义一个MyUserDetailServerImpl类实现UserDetailsService接口在这个类中实现用户名和密码的具体查询现在我还没有实现权限的查询按理说在登录时就应该一块进行的现在我只进行用户的登录查询权限等到之后再开发。
Service
Slf4j
public class MyUserDetailServerImpl implements UserDetailsService {AutowiredUserMapper userService;Autowired
private RedisTemplateString,String redisTemplate;Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user userService.selectOne(new LambdaQueryWrapperUser().eq(StringUtils.hasText(username), User::getUsername, username));if (user null) {throw new UsernameNotFoundException(用户名不存在);}MyTUserDetail myTUserDetailnew MyTUserDetail();myTUserDetail.setUsers(user);return myTUserDetail;}
}
JWT的token拦截器获取登录的用户信息和用户拥有的权限放在SecurityContextHolder。在后面要用到用户信息时可以直接在SecurityContextHolder中得到登陆的用户信息。并同时在JWT拦截器中进行过期时间的刷新。
Component
Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {Autowiredprivate RedisTemplateString,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 usernamePasswordAuthenticationTokennew UsernamePasswordAuthenticationToken(myTUserDetail,null,myTUserDetail.getAuthorities());SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);filterChain.doFilter(request,response);}}
现在我们就可以在controller中实现用户的登录功能了。
用户登录controller PostMapping(/login)public ResultString login(RequestBody LoginDto loginDto){String token userService.login(loginDto);return Result.successData(token);}
相应的service实现 Autowired
private RedisTemplateString,String redisTemplate;AutowiredAuthenticationManager authenticationManager;Overridepublic String login(LoginDto loginDto) {
// 先检验验证码String codeRedis redisTemplate.opsForValue().get(loginDto.getCodeKey());if (codeRedisnull){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(authenticatenull){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/web/87433.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!