From 20f8a9d1324c8c7edcd870523b9b10983de7e311 Mon Sep 17 00:00:00 2001 From: qingfeng1121 Date: Thu, 4 Dec 2025 14:03:29 +0800 Subject: [PATCH] =?UTF-8?q?feat(security):=20=E5=AE=9E=E7=8E=B0JWT?= =?UTF-8?q?=E8=AE=A4=E8=AF=81=E4=B8=8E=E6=8E=88=E6=9D=83=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构用户登录服务,引入Spring Security和JWT认证机制 - 新增JwtUtils工具类处理JWT生成与验证 - 添加JwtAuthenticationFilter拦截请求验证token - 实现UserDetailsService从数据库加载用户信息 - 创建AuthController处理登录请求返回JWT - 重构用户角色权限相关接口,支持基于角色的访问控制 - 移除旧的安全配置,启用新的SecurityConfig - 新增LoginResponse DTO替代旧的LoginUser - 优化用户密码加密存储,使用BCryptPasswordEncoder --- .../config/JwtAuthenticationFilter.java | 80 +++++++ .../qf/backend/config/RoleInitializer.java | 11 +- .../com/qf/backend/config/SecurityConfig.java | 163 +++++++++----- .../qf/backend/config/UserInitializer.java | 69 ++++++ .../config/UsersDetailsServiceConfig.java | 43 ---- .../qf/backend/controller/AuthController.java | 89 ++++++++ .../backend/controller/LoginController.java | 33 --- .../controller/PermissionsController.java | 145 ++++++++++++ .../backend/controller/RolesController.java | 133 +++++++++++ .../controller/UserRolesController.java | 169 ++++++++++++++ .../backend/controller/UsersController.java | 79 ++++++- .../{LoginUser.java => LoginResponse.java} | 18 +- .../qf/backend/entity/RolePermissions.java | 1 - .../java/com/qf/backend/entity/Users.java | 2 + .../qf/backend/service/UserLoginService.java | 6 +- .../qf/backend/service/UserRolesService.java | 4 +- .../service/impl/UserDetailsServiceImpl.java | 209 ++++++------------ .../service/impl/UserLoginServiceImpl.java | 24 +- .../service/impl/UsersServiceImpl.java | 10 +- src/main/resources/application.properties | 2 +- 20 files changed, 970 insertions(+), 320 deletions(-) create mode 100644 src/main/java/com/qf/backend/config/JwtAuthenticationFilter.java create mode 100644 src/main/java/com/qf/backend/config/UserInitializer.java delete mode 100644 src/main/java/com/qf/backend/config/UsersDetailsServiceConfig.java create mode 100644 src/main/java/com/qf/backend/controller/AuthController.java delete mode 100644 src/main/java/com/qf/backend/controller/LoginController.java create mode 100644 src/main/java/com/qf/backend/controller/PermissionsController.java create mode 100644 src/main/java/com/qf/backend/controller/RolesController.java create mode 100644 src/main/java/com/qf/backend/controller/UserRolesController.java rename src/main/java/com/qf/backend/dto/{LoginUser.java => LoginResponse.java} (61%) diff --git a/src/main/java/com/qf/backend/config/JwtAuthenticationFilter.java b/src/main/java/com/qf/backend/config/JwtAuthenticationFilter.java new file mode 100644 index 0000000..fd4a183 --- /dev/null +++ b/src/main/java/com/qf/backend/config/JwtAuthenticationFilter.java @@ -0,0 +1,80 @@ +package com.qf.backend.config; + +import java.io.IOException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import com.qf.backend.util.JwtUtils; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +/** + * JWT认证过滤器,用于解析和验证JWT + * 拦截所有请求,检查Authorization头中的Bearer token + */ +@Component +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class); + + @Autowired + private JwtUtils jwtUtils; + + @Autowired + private UserDetailsService userDetailsService; + /** + * 过滤请求,检查JWT并设置认证信息 + */ + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + try { + // 1. 从请求头中获取Authorization + String authorizationHeader = request.getHeader("Authorization"); + String token = null; + String username = null; + + // 2. 检查Authorization头格式 + if (authorizationHeader != null && authorizationHeader.startsWith(jwtUtils.getTokenPrefix() + " ")) { + token = authorizationHeader.substring(jwtUtils.getTokenPrefix().length() + 1); + username = jwtUtils.getUsernameFromToken(token); + } + + // 3. 如果token有效且用户未认证,设置认证信息 + if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { + UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); + + // 4. 验证token + if (jwtUtils.validateToken(token, userDetails)) { + // 5. 创建认证对象 + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.getAuthorities()); + + // 6. 设置认证细节 + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + // 7. 设置认证信息到SecurityContext + SecurityContextHolder.getContext().setAuthentication(authenticationToken); + } + } + } catch (Exception e) { + logger.error("JWT认证失败: {}", e.getMessage()); + } + + // 8. 继续过滤链 + filterChain.doFilter(request, response); + } +} \ No newline at end of file diff --git a/src/main/java/com/qf/backend/config/RoleInitializer.java b/src/main/java/com/qf/backend/config/RoleInitializer.java index 57e9f90..1ddda35 100644 --- a/src/main/java/com/qf/backend/config/RoleInitializer.java +++ b/src/main/java/com/qf/backend/config/RoleInitializer.java @@ -9,8 +9,6 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.qf.backend.entity.Roles; import com.qf.backend.service.RolesService; -import jakarta.annotation.PostConstruct; - /** * 角色初始化配置类,用于在系统启动时创建内置角色 * @author 30803 @@ -21,19 +19,18 @@ public class RoleInitializer { @Autowired private RolesService rolesService; - /** * 系统启动时初始化内置角色 */ - @PostConstruct + // @PostConstruct public void initRoles() { logger.info("开始初始化内置角色..."); // 定义内置角色信息 String[][] roleInfos = { - {"用户", "默认用户角色", "0"}, // roleType: 0-默认用户 - {"店主", "店铺管理员角色", "1"}, // roleType: 1-店主 - {"管理员", "系统管理员角色", "2"} // roleType: 2-管理员 + {"User", "默认用户角色", "0"}, // roleType: 0-默认用户 + {"Shopkeeper", "店铺管理员角色", "1"}, // roleType: 1-店主 + {"Admin", "系统管理员角色", "2"} // roleType: 2-管理员 }; for (String[] roleInfo : roleInfos) { diff --git a/src/main/java/com/qf/backend/config/SecurityConfig.java b/src/main/java/com/qf/backend/config/SecurityConfig.java index 5e57fa4..42f922b 100644 --- a/src/main/java/com/qf/backend/config/SecurityConfig.java +++ b/src/main/java/com/qf/backend/config/SecurityConfig.java @@ -1,57 +1,114 @@ -// /* -// * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license -// * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template -// */ -// package com.qf.backend.config; +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.qf.backend.config; -// import org.springframework.context.annotation.Bean; -// import org.springframework.context.annotation.Configuration; -// import org.springframework.security.config.annotation.web.builders.HttpSecurity; -// import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -// import org.springframework.security.provisioning.InMemoryUserDetailsManager; -// import org.springframework.security.web.SecurityFilterChain; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -// /** -// * 安全配置类(仅开发 禁用安全认证) -// * -// * @author 30803 -// */ -// @Configuration -// @EnableWebSecurity -// public class SecurityConfig { +/** + * 安全配置类,使用JWT认证 + * 该类是Spring Security的核心配置类,负责配置安全策略、认证机制和授权规则 + * 与AuthController.java的关系: + * 1. AuthController处理登录请求,调用AuthenticationManager进行认证 + * 2. SecurityConfig配置AuthenticationManager和相关组件 + * 3. SecurityConfig配置JWT过滤器,用于拦截后续请求并验证JWT + * 4. 两者协同工作,完成完整的认证授权流程 + */ +@Configuration +@EnableWebSecurity // 启用Spring Security +public class SecurityConfig { -// @Bean -// public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { -// http -// .authorizeHttpRequests(auth -> auth -// .requestMatchers("/users/**").permitAll() // 公开路径 -// .requestMatchers("/admin/**").hasRole("ADMIN") // 需要 ADMIN 角色 -// .anyRequest().authenticated() // 其他请求需登录 -// ) -// .formLogin(form -> form -// .loginPage("/login") // 自定义登录页(可选) -// .permitAll() -// ) -// .logout(logout -> logout -// .permitAll() -// ); -// return http.build(); -// } + /** + * 注入JWT认证过滤器 + * 该过滤器会拦截所有请求,检查Authorization头中的Bearer token + */ + @Autowired + private JwtAuthenticationFilter jwtAuthenticationFilter; + + /** + * 配置SecurityFilterChain + * SecurityFilterChain是Spring Security 3.x的新特性,替代了旧版的WebSecurityConfigurerAdapter + * 该方法配置了安全策略,包括: + * 1. 禁用CSRF保护(适合API服务,因为API通常使用JWT而不是Session) + * 2. 配置会话管理为无状态(适合RESTful API,不使用Session) + * 3. 配置请求授权规则 + * 4. 禁用默认的登录和注销功能(使用自定义的AuthController) + * 5. 添加JWT过滤器 + * + * @param http HttpSecurity对象,用于配置安全策略 + * @return SecurityFilterChain对象 + * @throws Exception 配置过程中可能抛出的异常 + */ + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + // 1. 禁用CSRF保护 + // CSRF(跨站请求伪造)保护主要用于Web应用,防止第三方网站伪造请求 + // API服务通常使用JWT认证,不需要CSRF保护 + .csrf(csrf -> csrf.disable()) + + // 2. 配置会话管理:无状态 + // 无状态意味着服务器不存储用户会话信息,每个请求都需要携带完整的认证信息 + // 这是RESTful API的最佳实践,提高了系统的可扩展性和安全性 + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + + // 3. 配置请求授权规则 + // 使用Lambda DSL配置请求匹配规则和授权要求 + .authorizeHttpRequests(auth -> auth + // 登录接口公开访问,不需要认证 + .requestMatchers("/api/auth/login").permitAll() + // 其他所有请求都需要认证 + .anyRequest().authenticated() + ) + + // 4. 禁用默认的登录和注销功能 + // 因为我们使用自定义的AuthController处理登录请求 + .formLogin(form -> form.disable()) + .logout(logout -> logout.disable()); + + // 5. 添加JWT过滤器 + // 在UsernamePasswordAuthenticationFilter之前添加JWT过滤器 + // 这样所有请求都会先经过JWT过滤器,验证token的有效性 + http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } -// @Bean -// public UserDetailsService userDetailsService() { -// UserDetails user = User.withDefaultPasswordEncoder() -// .username("user") -// .password("123456") -// .roles("USER") -// .build(); - -// UserDetails admin = User.withDefaultPasswordEncoder() -// .username("admin") -// .password("admin123") -// .roles("USER", "ADMIN") -// .build(); - -// return new InMemoryUserDetailsManager(user, admin); -// } -// } + /** + * 配置PasswordEncoder + * PasswordEncoder用于加密和验证密码 + * BCryptPasswordEncoder是Spring Security推荐的密码编码器,使用随机盐值 + * + * @return PasswordEncoder对象 + */ + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + /** + * 配置AuthenticationManager + * AuthenticationManager是Spring Security的核心组件,负责处理认证请求 + * 该方法从AuthenticationConfiguration中获取AuthenticationManager实例 + * + * @param authenticationConfiguration AuthenticationConfiguration对象 + * @return AuthenticationManager对象 + * @throws Exception 配置过程中可能抛出的异常 + */ + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { + return authenticationConfiguration.getAuthenticationManager(); + } +} diff --git a/src/main/java/com/qf/backend/config/UserInitializer.java b/src/main/java/com/qf/backend/config/UserInitializer.java new file mode 100644 index 0000000..f13c21b --- /dev/null +++ b/src/main/java/com/qf/backend/config/UserInitializer.java @@ -0,0 +1,69 @@ +package com.qf.backend.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.qf.backend.entity.Users; +import com.qf.backend.service.UsersService; +import com.qf.backend.util.ValidateUtil; + +import jakarta.annotation.PostConstruct; + +/** + * 用户初始化配置类,用于在系统启动时创建内置用户 + * @author 30803 + */ +@Component +public class UserInitializer { + private static final Logger logger = LoggerFactory.getLogger(UserInitializer.class); + + @Autowired + private UsersService usersService; + + /** + * 系统启动时初始化内置用户 + */ + // @PostConstruct + public void initUsers() { + logger.info("开始初始化内置用户..."); + + // 定义内置用户信息 + String[][] userInfos = { + // 用户名,密码,手机号,邮箱,状态 + {"admin", "admin123", "13800000000", "admin@qq.com", "1"}, // 管理员用户 + {"shopkeeper", "123456", "13800000001", "shopkeeper@qq.com", "1"}, // 店主用户 + {"user", "123456", "13800000002", "user@qq.com", "1"} // 普通用户 + }; + + for (String[] userInfo : userInfos) { + String username = userInfo[0]; + String password = userInfo[1]; + String phone = userInfo[2]; + String email = userInfo[3]; + Integer status = Integer.parseInt(userInfo[4]); + + // 检查用户是否已存在 + Users existingUser = usersService.getOne(new QueryWrapper().eq("username", username)); + if (existingUser == null) { + // 创建新用户 + Users user = new Users(); + user.setUsername(username); + user.setPassword(password); + user.setPhone(phone); + user.setEmail(email); + user.setStatus(status); + // 注意:不设置last_login_time字段,因为数据库中可能不存在该字段 + + usersService.createUser(user); + logger.info("成功创建内置用户: {}", username); + } else { + logger.info("内置用户 {} 已存在,跳过创建", username); + } + } + + logger.info("内置用户初始化完成"); + } +} \ No newline at end of file diff --git a/src/main/java/com/qf/backend/config/UsersDetailsServiceConfig.java b/src/main/java/com/qf/backend/config/UsersDetailsServiceConfig.java deleted file mode 100644 index 56c3859..0000000 --- a/src/main/java/com/qf/backend/config/UsersDetailsServiceConfig.java +++ /dev/null @@ -1,43 +0,0 @@ -// /* -// * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license -// * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template -// */ - -// package com.qf.backend.config; - -// import org.springframework.beans.factory.annotation.Autowired; -// import org.springframework.security.core.userdetails.UserDetailsService; -// import org.springframework.stereotype.Component; -// import org.springframework.stereotype.Service; -// import org.springframework.security.core.userdetails.UserDetails; -// import org.springframework.security.core.userdetails.UsernameNotFoundException; -// import org.springframework.security.core.authority.AuthorityUtils; -// import com.qf.backend.mapper.UsersMapper; -// import com.qf.backend.entity.Users; -// import com.qf.backend.mapper.RolesMapper; - - - -// /** -// * 自定义UserDetailsService -// * 用于从数据库加载用户信息进行认证 -// * @author 30803 -// */ -// @Component -// public class UsersDetailsServiceConfig implements UserDetailsService { -// @Autowired -// private UsersMapper usersMapper; -// @Autowired -// private RolesMapper rolesMapper; - - -// @Override -// public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { -// Users users = usersMapper.selectByUsername(username); -// if (users == null) { -// throw new UsernameNotFoundException("用户不存在"+username); -// } -// int roleType = rolesMapper.selectById(users.getId()).getRoleType(); -// } - -// } diff --git a/src/main/java/com/qf/backend/controller/AuthController.java b/src/main/java/com/qf/backend/controller/AuthController.java new file mode 100644 index 0000000..8d309dc --- /dev/null +++ b/src/main/java/com/qf/backend/controller/AuthController.java @@ -0,0 +1,89 @@ +package com.qf.backend.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +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.userdetails.UserDetails; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.qf.backend.dto.LoginRequest; +import com.qf.backend.dto.LoginResponse; +import com.qf.backend.util.JwtUtils; + +/** + * 认证控制器,用于处理用户登录请求 + * 该控制器接收前端发送的用户名和密码,通过Spring Security进行认证,认证成功后生成JWT返回给前端 + */ +@RestController +@RequestMapping("/api/auth") +public class AuthController { + + /** + * 注入AuthenticationManager,用于处理认证请求 + * AuthenticationManager是Spring Security的核心组件,负责协调认证过程 + */ + @Autowired + private AuthenticationManager authenticationManager; + + /** + * 注入JWT工具类,用于生成和验证JWT + */ + @Autowired + private JwtUtils jwtUtils; + + /** + * 用户登录接口 + * @param loginRequest 登录请求体,包含用户名和密码 + * @return ResponseEntity 包含JWT令牌的响应 + * + * 登录流程: + * 1. 前端发送POST请求到/api/auth/login,携带用户名和密码 + * 2. 该方法被调用,创建UsernamePasswordAuthenticationToken对象 + * 3. 调用AuthenticationManager.authenticate()方法进行认证 + * 4. 认证成功后,从Authentication对象中获取UserDetails + * 5. 使用JwtUtils生成JWT令牌 + * 6. 返回包含JWT令牌的响应 + */ + @PostMapping("/login") + public ResponseEntity login(@RequestBody LoginRequest loginRequest) { + try { + // 1. 创建认证令牌,将用户名和密码封装到UsernamePasswordAuthenticationToken中 + // 这里的令牌是未认证状态的,因为还没有验证密码是否正确 + UsernamePasswordAuthenticationToken authenticationToken = + new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()); + + // 2. 调用AuthenticationManager.authenticate()方法进行认证 + // 这个方法会触发以下流程: + // a. 调用UserDetailsService.loadUserByUsername()方法,从数据库加载用户信息 + // b. 使用PasswordEncoder验证密码是否匹配 + // c. 认证成功后,返回一个已认证的Authentication对象 + Authentication authentication = authenticationManager.authenticate(authenticationToken); + + // 3. 从已认证的Authentication对象中获取UserDetails + // UserDetails包含了用户的基本信息和权限列表 + UserDetails userDetails = (UserDetails) authentication.getPrincipal(); + + // 4. 使用JwtUtils生成JWT令牌 + // 令牌中包含了用户名、权限等信息,以及过期时间 + String jwt = jwtUtils.generateToken(userDetails); + + // 5. 创建LoginResponse对象,封装JWT令牌和令牌类型 + LoginResponse loginResponse = new LoginResponse(); + loginResponse.setToken(jwt); + loginResponse.setTokenType(jwtUtils.getTokenPrefix()); + // 5. 返回包含JWT令牌的响应 + // 响应格式为:{"token": "xxx", "tokenType": "Bearer"} + return ResponseEntity.ok(loginResponse); + } catch (BadCredentialsException e) { + // 认证失败,通常是用户名不存在或密码错误 + // 返回401 Unauthorized响应 + return ResponseEntity.status(401).build(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/qf/backend/controller/LoginController.java b/src/main/java/com/qf/backend/controller/LoginController.java deleted file mode 100644 index 89e39d0..0000000 --- a/src/main/java/com/qf/backend/controller/LoginController.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license - * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template - */ - -package com.qf.backend.controller; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import com.qf.backend.common.Result; -import com.qf.backend.dto.LoginRequest; -import com.qf.backend.dto.LoginUser; -import com.qf.backend.service.UserLoginService; -/** - * - * @author 30803 - */ -@RestController -@RequestMapping("/api/login") -public class LoginController { - @Autowired - private UserLoginService userLoginService; - - @PostMapping("/login") - public Result login(@RequestBody LoginRequest loginRequest) { - return userLoginService.login(loginRequest.getUsername(), loginRequest.getPassword()); - } - -} diff --git a/src/main/java/com/qf/backend/controller/PermissionsController.java b/src/main/java/com/qf/backend/controller/PermissionsController.java new file mode 100644 index 0000000..faf2615 --- /dev/null +++ b/src/main/java/com/qf/backend/controller/PermissionsController.java @@ -0,0 +1,145 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ + +package com.qf.backend.controller; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.security.access.prepost.PreAuthorize; + +import com.qf.backend.common.Result; +import com.qf.backend.entity.Permissions; +import com.qf.backend.service.PermissionsService; +import java.util.List; + +/** + * 权限管理控制器 + * 处理权限相关的HTTP请求 + * 遵循RESTful API设计规范 + * @author 30803 + */ +@RequestMapping("/api/permissions") +@RestController +public class PermissionsController { + private static final Logger logger = LoggerFactory.getLogger(PermissionsController.class); + + @Autowired + private PermissionsService permissionsService; + + /** + * 查询所有权限 + * @return 权限列表 + */ + @GetMapping + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result> listAllPermissions() { + logger.info("管理员查询所有权限"); + return permissionsService.listAllPermissions(); + } + + /** + * 根据权限ID查询权限 + * @param id 权限ID + * @return 权限信息 + */ + @GetMapping("/{id}") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result getPermissionById(@PathVariable Long id) { + logger.info("管理员根据ID查询权限,ID:{}", id); + return permissionsService.getPermissionById(id); + } + + /** + * 根据权限编码查询权限 + * @param permissionCode 权限编码 + * @return 权限信息 + */ + @GetMapping("/code/{permissionCode}") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result getPermissionByCode(@PathVariable String permissionCode) { + logger.info("管理员根据权限编码查询权限,权限编码:{}", permissionCode); + return permissionsService.getPermissionByCode(permissionCode); + } + + /** + * 创建权限 + * @param permissions 权限信息 + * @return 是否成功 + */ + @PostMapping + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result createPermission(@RequestBody Permissions permissions) { + logger.info("管理员创建权限:{}", permissions); + return permissionsService.createPermission(permissions); + } + + /** + * 更新权限信息 + * @param permissions 权限信息 + * @return 是否成功 + */ + @PutMapping + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result updatePermission(@RequestBody Permissions permissions) { + logger.info("管理员更新权限:{}", permissions); + return permissionsService.updatePermission(permissions); + } + + /** + * 删除权限 + * @param id 权限ID + * @return 是否成功 + */ + @DeleteMapping("/{id}") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result deletePermission(@PathVariable Long id) { + logger.info("管理员删除权限,ID:{}", id); + return permissionsService.deletePermission(id); + } + + /** + * 批量删除权限 + * @param ids 权限ID列表 + * @return 是否成功 + */ + @DeleteMapping("/batch") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result batchDeletePermissions(@RequestBody List ids) { + logger.info("管理员批量删除权限,IDs:{}", ids); + return permissionsService.batchDeletePermissions(ids); + } + + /** + * 根据菜单ID查询权限 + * @param menuId 菜单ID + * @return 权限列表 + */ + @GetMapping("/menu/{menuId}") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result> listPermissionsByMenuId(@PathVariable Long menuId) { + logger.info("管理员根据菜单ID查询权限,菜单ID:{}", menuId); + return permissionsService.listPermissionsByMenuId(menuId); + } + + /** + * 根据权限类型查询权限 + * @param permissionType 权限类型 + * @return 权限列表 + */ + @GetMapping("/type/{permissionType}") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result> listPermissionsByType(@PathVariable String permissionType) { + logger.info("管理员根据权限类型查询权限,权限类型:{}", permissionType); + return permissionsService.listPermissionsByType(permissionType); + } +} \ No newline at end of file diff --git a/src/main/java/com/qf/backend/controller/RolesController.java b/src/main/java/com/qf/backend/controller/RolesController.java new file mode 100644 index 0000000..9f80ec1 --- /dev/null +++ b/src/main/java/com/qf/backend/controller/RolesController.java @@ -0,0 +1,133 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ + +package com.qf.backend.controller; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.security.access.prepost.PreAuthorize; + +import com.qf.backend.common.Result; +import com.qf.backend.entity.Roles; +import com.qf.backend.service.RolesService; +import java.util.List; + +/** + * 角色管理控制器 + * 处理角色相关的HTTP请求 + * 遵循RESTful API设计规范 + * @author 30803 + */ +@RequestMapping("/api/roles") +@RestController +public class RolesController { + private static final Logger logger = LoggerFactory.getLogger(RolesController.class); + + @Autowired + private RolesService rolesService; + + /** + * 查询所有角色 + * @return 角色列表 + */ + @GetMapping + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result> listAllRoles() { + logger.info("管理员查询所有角色"); + return rolesService.listAllRoles(); + } + + /** + * 根据角色ID查询角色 + * @param id 角色ID + * @return 角色信息 + */ + @GetMapping("/{id}") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result getRoleById(@PathVariable Long id) { + logger.info("管理员根据ID查询角色,ID:{}", id); + return rolesService.getRoleById(id); + } + + /** + * 根据角色名称查询角色 + * @param roleName 角色名称 + * @return 角色信息 + */ + @GetMapping("/name/{roleName}") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result getRoleByName(@PathVariable String roleName) { + logger.info("管理员根据名称查询角色,名称:{}", roleName); + return rolesService.getRoleByName(roleName); + } + + /** + * 根据用户ID查询其拥有的角色列表 + * @param userId 用户ID + * @return 角色列表 + */ + @GetMapping("/user/{userId}") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result> listRolesByUserId(@PathVariable Long userId) { + logger.info("管理员根据用户ID查询角色列表,用户ID:{}", userId); + return rolesService.listRolesByUserId(userId); + } + + /** + * 创建角色 + * @param roles 角色信息 + * @return 是否成功 + */ + @PostMapping + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result createRole(@RequestBody Roles roles) { + logger.info("管理员创建角色:{}", roles); + return rolesService.createRole(roles); + } + + /** + * 更新角色信息 + * @param roles 角色信息 + * @return 是否成功 + */ + @PutMapping + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result updateRole(@RequestBody Roles roles) { + logger.info("管理员更新角色:{}", roles); + return rolesService.updateRole(roles); + } + + /** + * 删除角色 + * @param id 角色ID + * @return 是否成功 + */ + @DeleteMapping("/{id}") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result deleteRole(@PathVariable Long id) { + logger.info("管理员删除角色,ID:{}", id); + return rolesService.deleteRole(id); + } + + /** + * 批量删除角色 + * @param ids 角色ID列表 + * @return 是否成功 + */ + @DeleteMapping("/batch") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result batchDeleteRoles(@RequestBody List ids) { + logger.info("管理员批量删除角色,IDs:{}", ids); + return rolesService.batchDeleteRoles(ids); + } +} \ No newline at end of file diff --git a/src/main/java/com/qf/backend/controller/UserRolesController.java b/src/main/java/com/qf/backend/controller/UserRolesController.java new file mode 100644 index 0000000..7cf42c5 --- /dev/null +++ b/src/main/java/com/qf/backend/controller/UserRolesController.java @@ -0,0 +1,169 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ + +package com.qf.backend.controller; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.security.access.prepost.PreAuthorize; + +import com.qf.backend.common.Result; +import com.qf.backend.entity.UserRoles; +import com.qf.backend.service.UserRolesService; +import java.util.List; + +/** + * 用户角色关联控制器 + * 处理用户与角色关联相关的HTTP请求 + * 遵循RESTful API设计规范 + * @author 30803 + */ +@RequestMapping("/api/user-roles") +@RestController +public class UserRolesController { + private static final Logger logger = LoggerFactory.getLogger(UserRolesController.class); + + @Autowired + private UserRolesService userRolesService; + + /** + * 根据用户ID查询角色关联 + * @param userId 用户ID + * @return 用户角色关联列表 + */ + @GetMapping("/user/{userId}") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result> getUserRolesByUserId(@PathVariable Long userId) { + logger.info("管理员根据用户ID查询角色关联,用户ID:{}", userId); + return userRolesService.getUserRolesByUserId(userId); + } + + /** + * 根据角色ID查询用户关联 + * @param roleId 角色ID + * @return 用户角色关联列表 + */ + @GetMapping("/role/{roleId}") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result> getUserRolesByRoleId(@PathVariable Long roleId) { + logger.info("管理员根据角色ID查询用户关联,角色ID:{}", roleId); + return userRolesService.getUserRolesByRoleId(roleId); + } + + /** + * 为用户添加角色 + * @param userId 用户ID + * @param roleId 角色ID + * @return 是否成功 + */ + @PostMapping("/add") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result addRoleToUser(@RequestBody UserRolesRequest request) { + logger.info("管理员为用户添加角色,用户ID:{},角色ID:{}", request.getUserId(), request.getRoleId()); + return userRolesService.addRoleToUser(request.getUserId(), request.getRoleId()); + } + + /** + * 从用户移除角色 + * @param userId 用户ID + * @param roleId 角色ID + * @return 是否成功 + */ + @DeleteMapping("/remove") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result removeRoleFromUser(@RequestBody UserRolesRequest request) { + logger.info("管理员从用户移除角色,用户ID:{},角色ID:{}", request.getUserId(), request.getRoleId()); + return userRolesService.removeRoleFromUser(request.getUserId(), request.getRoleId()); + } + + /** + * 批量为用户添加角色 + * @param userId 用户ID + * @param roleIds 角色ID列表 + * @return 是否成功 + */ + @PostMapping("/batch-add") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result batchAddRolesToUser(@RequestBody BatchUserRolesRequest request) { + logger.info("管理员批量为用户添加角色,用户ID:{},角色ID列表:{}", request.getUserId(), request.getRoleIds()); + return userRolesService.batchAddRolesToUser(request.getUserId(), request.getRoleIds()); + } + + /** + * 清空用户的所有角色 + * @param userId 用户ID + * @return 是否成功 + */ + @DeleteMapping("/clear/{userId}") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result clearUserRoles(@PathVariable Long userId) { + logger.info("管理员清空用户的所有角色,用户ID:{}", userId); + return userRolesService.clearUserRoles(userId); + } + + /** + * 检查用户是否拥有指定角色 + * @param userId 用户ID + * @param roleId 角色ID + * @return 是否拥有 + */ + @GetMapping("/check") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result checkUserHasRole(Long userId, Long roleId) { + logger.info("管理员检查用户是否拥有指定角色,用户ID:{},角色ID:{}", userId, roleId); + return userRolesService.checkUserHasRole(userId, roleId); + } + + /** + * 用户角色关联请求体 + */ + public static class UserRolesRequest { + private Long userId; + private Long roleId; + + // getter和setter + public Long getUserId() { + return userId; + } + public void setUserId(Long userId) { + this.userId = userId; + } + public Long getRoleId() { + return roleId; + } + public void setRoleId(Long roleId) { + this.roleId = roleId; + } + } + + /** + * 批量用户角色关联请求体 + */ + public static class BatchUserRolesRequest { + private Long userId; + private List roleIds; + + // getter和setter + public Long getUserId() { + return userId; + } + public void setUserId(Long userId) { + this.userId = userId; + } + public List getRoleIds() { + return roleIds; + } + public void setRoleIds(List roleIds) { + this.roleIds = roleIds; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/qf/backend/controller/UsersController.java b/src/main/java/com/qf/backend/controller/UsersController.java index 6c47630..26b2cfc 100644 --- a/src/main/java/com/qf/backend/controller/UsersController.java +++ b/src/main/java/com/qf/backend/controller/UsersController.java @@ -4,36 +4,91 @@ */ package com.qf.backend.controller; - -import java.util.List; - +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.security.access.prepost.PreAuthorize; import com.qf.backend.common.Result; import com.qf.backend.entity.Users; import com.qf.backend.service.UsersService; +import java.util.List; /** - * + * 用户管理控制器 + * 处理用户相关的HTTP请求 + * 遵循RESTful API设计规范 * @author 30803 */ -@RestController @RequestMapping("/api/users") +@RestController public class UsersController { + private static final Logger logger = LoggerFactory.getLogger(UsersController.class); @Autowired private UsersService usersService; - @GetMapping("/list") + /** + * 分页获取用户列表 仅管理员角色 + * @param pageNum 页码 + * @param pageSize 每页数量 + * @return 用户列表 + */ + @GetMapping("/page") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result> listUsersByPage(int pageNum, int pageSize) { + logger.info("管理员获取用户列表,页码:{},每页数量:{}", pageNum, pageSize); + return usersService.listUsersByPage(pageNum, pageSize); + } + + /** + * 根据id查询用户 仅管理员角色 + * @param id 用户ID + * @return 用户信息 + */ + @GetMapping("/{id}") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result getUserById(@PathVariable Long id) { + logger.info("管理员根据id查询用户,id:{}", id); + return usersService.getUserById(id); + } + + /** + * 根据用户名查询用户 用户可以查询自己的信息 + * @param username 用户名 + * @return 用户信息 + */ + @GetMapping("/username/{username}") + // @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result getUserByUsername(@PathVariable String username) { + logger.info("管理员根据用户名查询用户,用户名:{}", username); + return usersService.getUserByUsername(username); + } + + /** + * 根据邮箱查询用户 仅管理员角色 + * @param email 邮箱 + * @return 用户信息 + */ + @GetMapping("/email/{email}") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Result getUserByEmail(@PathVariable String email) { + logger.info("管理员根据邮箱查询用户,邮箱:{}", email); + return usersService.getUserByEmail(email); + } + + /** + * 查询所有用户 仅管理员角色 + * @return 用户列表 + */ + @GetMapping + @PreAuthorize("hasRole('ROLE_ADMIN')") public Result> listAllUsers() { + logger.info("管理员查询所有用户"); return usersService.listAllUsers(); } - @DeleteMapping("/delete/{id}") - public Result deleteUser(Long id) { - System.out.println(id); - return usersService.deleteUser(id); - } + } diff --git a/src/main/java/com/qf/backend/dto/LoginUser.java b/src/main/java/com/qf/backend/dto/LoginResponse.java similarity index 61% rename from src/main/java/com/qf/backend/dto/LoginUser.java rename to src/main/java/com/qf/backend/dto/LoginResponse.java index 243e1b1..60ce33b 100644 --- a/src/main/java/com/qf/backend/dto/LoginUser.java +++ b/src/main/java/com/qf/backend/dto/LoginResponse.java @@ -2,17 +2,17 @@ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template */ - package com.qf.backend.dto; + import java.util.List; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; + /** - * 登录用户DTO(登录时使用) - * @param id 用户ID + * (登录时返回的用户信息DTO) * @param username 用户名 * @param password 密码 * @param roles 角色列表 @@ -23,10 +23,10 @@ import lombok.NoArgsConstructor; @Builder @NoArgsConstructor @AllArgsConstructor -public class LoginUser { - private Long id; - private String username; - private String password; - private List roles; - private List permissions; +public class LoginResponse { + private String username; // 用户名 + private List roles; // 角色列表 暂时无用 + private List permissions; // 权限列表 暂时无用 + private String token; // JWT令牌 + private String tokenType; // 令牌类型,通常为Bearer } diff --git a/src/main/java/com/qf/backend/entity/RolePermissions.java b/src/main/java/com/qf/backend/entity/RolePermissions.java index bf1f16a..7e489fd 100644 --- a/src/main/java/com/qf/backend/entity/RolePermissions.java +++ b/src/main/java/com/qf/backend/entity/RolePermissions.java @@ -23,7 +23,6 @@ public class RolePermissions { @TableId(type = IdType.AUTO) private Long id; // 关联ID,主键,自增 - private Long roleId; // 角色ID,外键,关联roles表 private Long permissionId; // 权限ID,外键,关联permissions表 private Date createdAt; // 创建时间 diff --git a/src/main/java/com/qf/backend/entity/Users.java b/src/main/java/com/qf/backend/entity/Users.java index 4d0abf4..d8cf461 100644 --- a/src/main/java/com/qf/backend/entity/Users.java +++ b/src/main/java/com/qf/backend/entity/Users.java @@ -3,6 +3,7 @@ package com.qf.backend.entity; import java.util.Date; import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; @@ -28,6 +29,7 @@ public class Users { private String email; // 邮箱,唯一 private String phone; // 手机号,唯一 private String avatar; // 头像URL + @TableField(exist = false) // 标记该字段在数据库中不存在 private Date lastLoginTime; // 最后登录时间 private Integer status; // 状态:0:禁用, 1:启用 private Date createdAt; // 创建时间 diff --git a/src/main/java/com/qf/backend/service/UserLoginService.java b/src/main/java/com/qf/backend/service/UserLoginService.java index c8e0d31..b6fef0c 100644 --- a/src/main/java/com/qf/backend/service/UserLoginService.java +++ b/src/main/java/com/qf/backend/service/UserLoginService.java @@ -6,7 +6,7 @@ package com.qf.backend.service; import com.qf.backend.common.Result; -import com.qf.backend.dto.LoginUser; +import com.qf.backend.dto.LoginResponse; /** * 用户登录服务接口 @@ -16,7 +16,7 @@ public interface UserLoginService { * 用户登录 * @param username 用户名 * @param password 密码 - * @return 登录结果 + * @return 登录结果,包含登录状态、token等信息 */ - Result login(String username, String password); + Result login(String username, String password); } diff --git a/src/main/java/com/qf/backend/service/UserRolesService.java b/src/main/java/com/qf/backend/service/UserRolesService.java index ebff741..c1377bc 100644 --- a/src/main/java/com/qf/backend/service/UserRolesService.java +++ b/src/main/java/com/qf/backend/service/UserRolesService.java @@ -1,11 +1,11 @@ package com.qf.backend.service; +import java.util.List; + import com.baomidou.mybatisplus.extension.service.IService; import com.qf.backend.common.Result; import com.qf.backend.entity.UserRoles; -import java.util.List; - /** * 用户角色关联服务接口 */ diff --git a/src/main/java/com/qf/backend/service/impl/UserDetailsServiceImpl.java b/src/main/java/com/qf/backend/service/impl/UserDetailsServiceImpl.java index b0d3251..4aab6a9 100644 --- a/src/main/java/com/qf/backend/service/impl/UserDetailsServiceImpl.java +++ b/src/main/java/com/qf/backend/service/impl/UserDetailsServiceImpl.java @@ -1,158 +1,87 @@ package com.qf.backend.service.impl; +import java.util.ArrayList; +import java.util.List; + import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.qf.backend.common.Result; -import com.qf.backend.common.ResultUtils; -import com.qf.backend.entity.UserDetails; -import com.qf.backend.exception.BusinessException; -import com.qf.backend.exception.ErrorCode; -import com.qf.backend.mapper.UserDetailsMapper; -import com.qf.backend.service.UserDetailsService; +import com.qf.backend.entity.Roles; +import com.qf.backend.entity.UserRoles; +import com.qf.backend.entity.Users; +import com.qf.backend.service.RolesService; +import com.qf.backend.service.UserRolesService; +import com.qf.backend.service.UsersService; + + /** - * 用户详情服务实现类 + * UserDetailsService实现类,用于从数据库中加载用户信息 + * 该类实现了Spring Security的UserDetailsService接口,用于根据用户名加载用户信息 */ @Service -public class UserDetailsServiceImpl extends ServiceImpl implements UserDetailsService { - +public class UserDetailsServiceImpl implements UserDetailsService { + + /** + * 注入用户服务,用于查询用户信息 + */ @Autowired - private UserDetailsMapper userDetailsMapper; + private UsersService usersService; + @Autowired + private UserRolesService userRolesService; + @Autowired + private RolesService RolesService; + /** + * 根据用户名加载用户信息 + * @param username 用户名 + * @return UserDetails 用户详情对象,包含用户名、密码、权限等信息 + * @throws UsernameNotFoundException 如果用户名不存在 + */ @Override - public Result getUserDetailsByUserId(Long userId) { - if (userId == null) { - throw new BusinessException(ErrorCode.MISSING_PARAM, "用户ID不能为空"); + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + // 1. 从数据库中查询用户 + Result result = usersService.getUserByUsername(username); + if (result == null || result.getData() == null) { + throw new UsernameNotFoundException("用户名不存在: " + username); } - try { - UserDetails userDetails = userDetailsMapper.selectOne( - new QueryWrapper().eq("user_id", userId)); - return ResultUtils.success(userDetails); - } catch (BusinessException e) { - throw e; - } catch (Exception e) { - throw new BusinessException(ErrorCode.DATABASE_ERROR, "查询用户详情失败", e); + + Users user = result.getData(); + + // 2. 构建权限列表(这里简化处理,实际应从数据库中查询用户的角色和权限) + List authorities = new ArrayList<>(); + // 查询用户角色关联 + Result> userRoleResultList = userRolesService.getUserRolesByUserId(user.getId()); + if (userRoleResultList == null || userRoleResultList.getData() == null) { + throw new UsernameNotFoundException("用户角色不存在: " + user.getId()); } - } - - @Override - public Result createUserDetails(UserDetails userDetails) { - if (userDetails == null) { - throw new BusinessException(ErrorCode.MISSING_PARAM, "用户详情信息不能为空"); - } - if (userDetails.getUserId() == null) { - throw new BusinessException(ErrorCode.MISSING_PARAM, "用户ID不能为空"); - } - try { - // 检查是否已存在该用户的详情 - UserDetails existing = userDetailsMapper.selectOne( - new QueryWrapper().eq("user_id", userDetails.getUserId())); - if (existing != null) { - throw new BusinessException(ErrorCode.BUSINESS_ERROR, "该用户已存在详情信息"); + // 3. 查询角色权限 + for (UserRoles userRole : userRoleResultList.getData()) { + Result roleResult = RolesService.getRoleById(userRole.getRoleId()); + if (roleResult == null || roleResult.getData() == null) { + throw new UsernameNotFoundException("权限不存在: " + userRole.getRoleId()); } - int result = userDetailsMapper.insert(userDetails); - return ResultUtils.success(result > 0); - } catch (BusinessException e) { - throw e; - } catch (Exception e) { - throw new BusinessException(ErrorCode.DATABASE_ERROR, "创建用户详情失败", e); + Roles role = roleResult.getData(); + // 4. 转换为Spring Security的GrantedAuthority对象 + authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getRoleName().toUpperCase())); } + // 3. 返回UserDetails对象 + // 注意:在实际应用中,密码应该加密存储,这里直接使用明文密码(仅用于演示) + return User.builder() + .username(user.getUsername()) // 用户名 + .password(user.getPassword()) // 密码需要加密存储,这里直接使用明文密码(仅用于演示) + .authorities(authorities) // 假设用户默认拥有USER权限 + .accountExpired(false) // 假设账号永不过期 + .accountLocked(false) // 假设账号永不过期 + .credentialsExpired(false) // 假设密码永不过期 + .disabled(user.getStatus() == 0) // 假设status为0表示禁用 + .build(); } - - @Override - public Result updateUserDetails(UserDetails userDetails) { - if (userDetails == null) { - throw new BusinessException(ErrorCode.MISSING_PARAM, "用户详情信息不能为空"); - } - if (userDetails.getId() == null) { - throw new BusinessException(ErrorCode.MISSING_PARAM, "用户详情ID不能为空"); - } - try { - // 检查用户详情是否存在 - UserDetails existing = userDetailsMapper.selectById(userDetails.getId()); - if (existing == null) { - throw new BusinessException(ErrorCode.NOT_FOUND, "用户详情不存在"); - } - int result = userDetailsMapper.updateById(userDetails); - return ResultUtils.success(result > 0); - } catch (BusinessException e) { - throw e; - } catch (Exception e) { - throw new BusinessException(ErrorCode.DATABASE_ERROR, "更新用户详情失败", e); - } - } - - @Override - public Result deleteUserDetailsByUserId(Long userId) { - if (userId == null) { - throw new BusinessException(ErrorCode.MISSING_PARAM, "用户ID不能为空"); - } - try { - // 检查用户详情是否存在 - UserDetails existing = userDetailsMapper.selectOne( - new QueryWrapper().eq("user_id", userId)); - if (existing == null) { - throw new BusinessException(ErrorCode.NOT_FOUND, "用户详情不存在"); - } - int result = userDetailsMapper.delete( - new QueryWrapper().eq("user_id", userId)); - return ResultUtils.success(result > 0); - } catch (BusinessException e) { - throw e; - } catch (Exception e) { - throw new BusinessException(ErrorCode.DATABASE_ERROR, "删除用户详情失败", e); - } - } - - @Override - public Result getUserDetailsById(Long id) { - if (id == null) { - throw new BusinessException(ErrorCode.MISSING_PARAM, "用户详情ID不能为空"); - } - try { - UserDetails userDetails = userDetailsMapper.selectById(id); - if (userDetails == null) { - throw new BusinessException(ErrorCode.NOT_FOUND, "用户详情不存在"); - } - return ResultUtils.success(userDetails); - } catch (BusinessException e) { - throw e; - } catch (Exception e) { - throw new BusinessException(ErrorCode.DATABASE_ERROR, "查询用户详情失败", e); - } - } - - // @Override - // public Result updateContactInfo(Long userId, String phone, String email) { - // if (userId == null) { - // throw new BusinessException(ErrorCode.MISSING_PARAM, "用户ID不能为空"); - // } - // if (ValidateUtil.isEmpty(phone) && ValidateUtil.isEmpty(email)) { - // throw new BusinessException(ErrorCode.MISSING_PARAM, "手机号和邮箱不能同时为空"); - // } - // try { - // // 检查用户详情是否存在 - // UserDetails existing = userDetailsMapper.selectOne( - // new QueryWrapper().eq("user_id", userId)); - // if (existing == null) { - // throw new BusinessException(ErrorCode.NOT_FOUND, "用户详情不存在"); - // } - // // 更新联系方式 - // if (phone != null) { - // existing.setPhone(phone); - // } - // if (email != null) { - // existing.setEmail(email); - // } - // int result = userDetailsMapper.updateById(existing); - // return ResultUtils.success(result > 0); - // } catch (BusinessException e) { - // throw e; - // } catch (Exception e) { - // throw new BusinessException(ErrorCode.DATABASE_ERROR, "更新用户联系方式失败", e); - // } - // } -} +} \ No newline at end of file diff --git a/src/main/java/com/qf/backend/service/impl/UserLoginServiceImpl.java b/src/main/java/com/qf/backend/service/impl/UserLoginServiceImpl.java index 7189ef6..b4c1ea1 100644 --- a/src/main/java/com/qf/backend/service/impl/UserLoginServiceImpl.java +++ b/src/main/java/com/qf/backend/service/impl/UserLoginServiceImpl.java @@ -17,7 +17,7 @@ import org.springframework.stereotype.Service; import com.qf.backend.common.Result; import com.qf.backend.common.ResultUtils; -import com.qf.backend.dto.LoginUser; +import com.qf.backend.dto.LoginResponse; import com.qf.backend.entity.Permissions; import com.qf.backend.entity.Roles; import com.qf.backend.entity.UserRoles; @@ -57,17 +57,15 @@ public class UserLoginServiceImpl implements UserLoginService { * @return 登录结果 */ @Override - public Result login(String username, String password) { + public Result login(String username, String password) { logger.info("用户登录,用户名:{}", username); // 1. 校验用户名和密码是否为空 try{ if (ValidateUtil.isEmpty(username) || ValidateUtil.isEmpty(password)) { throw new IllegalArgumentException("用户名或密码不能为空"); } - // 加密密码 - String encryptedPassword = ValidateUtil.encryptPassword(password); // 2. 登录 - Result result = usersServiceImpl.login(username, encryptedPassword); + Result result = usersServiceImpl.login(username, password); if (result == null || result.getData() == null) { throw new IllegalArgumentException("用户名不存在或密码错误"); } @@ -87,7 +85,7 @@ public class UserLoginServiceImpl implements UserLoginService { for (UserRoles ur : userRoles) { Roles role = rolesServiceImpl.getById(ur.getRoleId()); if (role != null) { - roleNames.add(role.getRoleName()); + roleNames.add(String.valueOf(role.getRoleType())); roleIds.add(role.getId()); } } @@ -104,14 +102,12 @@ public class UserLoginServiceImpl implements UserLoginService { } } - // 6. 构建LoginUser对象 - LoginUser loginUser = new LoginUser(); - loginUser.setId(user.getId()); - loginUser.setUsername(user.getUsername()); - loginUser.setRoles(new ArrayList<>(roleNames)); - loginUser.setPermissions(new ArrayList<>(permissionCodes)); - - return ResultUtils.success(loginUser); + // 6. 构建LoginResponse对象 + LoginResponse loginResponse = new LoginResponse(); + loginResponse.setUsername(user.getUsername()); + loginResponse.setRoles(new ArrayList<>(roleNames)); + loginResponse.setPermissions(new ArrayList<>(permissionCodes)); + return ResultUtils.success(loginResponse); } catch (Exception e) { logger.error("用户登录失败,用户名:{}", username, e); return ResultUtils.fail(ErrorCode.SYSTEM_ERROR, e.getMessage()); diff --git a/src/main/java/com/qf/backend/service/impl/UsersServiceImpl.java b/src/main/java/com/qf/backend/service/impl/UsersServiceImpl.java index 95a2b17..1adb649 100644 --- a/src/main/java/com/qf/backend/service/impl/UsersServiceImpl.java +++ b/src/main/java/com/qf/backend/service/impl/UsersServiceImpl.java @@ -88,12 +88,18 @@ public class UsersServiceImpl extends ServiceImpl implements throw new BusinessException(ErrorCode.INVALID_PARAM, "用户名或密码不能为空"); } - // 校验用户名是否存在 - Users user = usersMapper.login(username, password); + // 根据用户名查询用户 + Users user = usersMapper.selectByUsername(username); if (user == null) { throw new BusinessException(ErrorCode.USER_NOT_FOUND, "用户名不存在或密码错误"); } + // 使用BCryptPasswordEncoder验证密码 + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + if (!passwordEncoder.matches(password, user.getPassword())) { + throw new BusinessException(ErrorCode.USER_NOT_FOUND, "用户名不存在或密码错误"); + } + return ResultUtils.success(user); } catch (BusinessException e) { throw e; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 857b5e2..0ae3fa7 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -6,4 +6,4 @@ spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # 暂时关闭Spring Security -spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration +# spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration