diff --git a/pom.xml b/pom.xml index b018316..50dd5cd 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,7 @@ 2.9.2 2.9.2 2.5.0 + 0.11.5 @@ -69,6 +70,32 @@ + + + io.jsonwebtoken + jjwt-api + ${jjwt} + + + io.jsonwebtoken + jjwt-impl + ${jjwt} + runtime + + + io.jsonwebtoken + jjwt-jackson + ${jjwt} + runtime + + + + + org.springframework.boot + spring-boot-starter-security + + + @@ -79,6 +106,11 @@ + + + + + @@ -87,6 +119,10 @@ springdoc-openapi-starter-webmvc-ui ${springdoc-openapi-starter-webmvc-ui} + + org.projectlombok + lombok + diff --git a/src/main/java/com/example/testspring/demos/modules/common/config/SecurityConfig.java b/src/main/java/com/example/testspring/demos/modules/common/config/SecurityConfig.java new file mode 100644 index 0000000..b61b5af --- /dev/null +++ b/src/main/java/com/example/testspring/demos/modules/common/config/SecurityConfig.java @@ -0,0 +1,94 @@ +package com.example.testspring.demos.modules.common.config; + + +import com.example.testspring.demos.modules.common.utils.JwtAuthEntryPoint; +import com.example.testspring.demos.modules.common.utils.JwtAuthenticationFilter; +//import com.example.testspring.demos.modules.user.service.impl.CustomUserDetailsService; +import com.example.testspring.demos.modules.user.service.impl.DatabaseUserDetailsService; +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.authentication.AuthenticationProvider; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +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.core.userdetails.UserDetailsService; +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; + + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + private final JwtAuthEntryPoint unauthorizedHandler; + private final JwtAuthenticationFilter jwtAuthFilter; + private final DatabaseUserDetailsService userDetailsService; + + @Autowired + public SecurityConfig(JwtAuthEntryPoint unauthorizedHandler, + JwtAuthenticationFilter jwtAuthFilter, + DatabaseUserDetailsService userDetailsService + ) { + this.unauthorizedHandler = unauthorizedHandler; + this.jwtAuthFilter = jwtAuthFilter; + this.userDetailsService = userDetailsService; + } + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + // 禁用CSRF(因使用JWT,无需CSRF保护) + .csrf(csrf -> csrf.disable()) + + // 异常处理配置 + .exceptionHandling(handling -> handling + .authenticationEntryPoint(unauthorizedHandler) + ) + + // 设置会话无状态(JWT不需要Session) + .sessionManagement(session -> session + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + ) + + // 授权规则配置 + .authorizeHttpRequests(auth -> auth + .requestMatchers("/api/auth/login").permitAll() + .requestMatchers("/test/**").permitAll() + .requestMatchers("/api/public/**").permitAll() + .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() + .anyRequest().authenticated() + ); + + // 添加JWT认证过滤器 + http.authenticationProvider(authenticationProvider()) + .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public AuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); + authProvider.setUserDetailsService(userDetailsService); + authProvider.setPasswordEncoder(passwordEncoder()); + return authProvider; + } + + @Bean + public AuthenticationManager authenticationManager( + AuthenticationConfiguration config) throws Exception { + return config.getAuthenticationManager(); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/testspring/demos/modules/common/entity/UserDetails.java b/src/main/java/com/example/testspring/demos/modules/common/entity/UserDetails.java new file mode 100644 index 0000000..21741e7 --- /dev/null +++ b/src/main/java/com/example/testspring/demos/modules/common/entity/UserDetails.java @@ -0,0 +1,22 @@ +package com.example.testspring.demos.modules.common.entity; + +import org.springframework.security.core.GrantedAuthority; + +import java.io.Serializable; +import java.util.Collection; + +public interface UserDetails extends Serializable { + Collection getAuthorities(); + + String getPassword(); + + String getUsername(); + + boolean isAccountNonExpired(); + + boolean isAccountNonLocked(); + + boolean isCredentialsNonExpired(); + + boolean isEnabled(); +} diff --git a/src/main/java/com/example/testspring/demos/modules/common/exception/UtilException.java b/src/main/java/com/example/testspring/demos/modules/common/exception/UtilException.java new file mode 100644 index 0000000..f2f1013 --- /dev/null +++ b/src/main/java/com/example/testspring/demos/modules/common/exception/UtilException.java @@ -0,0 +1,21 @@ +package com.example.testspring.demos.modules.common.exception; + +public class UtilException extends RuntimeException +{ + private static final long serialVersionUID = 8247610319171014183L; + + public UtilException(Throwable e) + { + super(e.getMessage(), e); + } + + public UtilException(String message) + { + super(message); + } + + public UtilException(String message, Throwable throwable) + { + super(message, throwable); + } +} diff --git a/src/main/java/com/example/testspring/demos/modules/common/utils/JwtAuthEntryPoint.java b/src/main/java/com/example/testspring/demos/modules/common/utils/JwtAuthEntryPoint.java new file mode 100644 index 0000000..a88b0c6 --- /dev/null +++ b/src/main/java/com/example/testspring/demos/modules/common/utils/JwtAuthEntryPoint.java @@ -0,0 +1,48 @@ +package com.example.testspring.demos.modules.common.utils; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.MediaType; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + + +/** + * 处理认证异常的统一入口点 + */ + +@Component +public class JwtAuthEntryPoint implements AuthenticationEntryPoint { + +// private static final Logger logger = LoggerFactory.getLogger(JwtAuthEntryPoint.class); + + @Override + public void commence(HttpServletRequest request, + HttpServletResponse response, + AuthenticationException authException) throws IOException { + +// logger.error("未经授权的错误: {}", authException.getMessage()); + + // 构建标准化的错误响应 + Map errorDetails = new HashMap<>(); + errorDetails.put("timestamp", System.currentTimeMillis()); + errorDetails.put("status", HttpServletResponse.SC_UNAUTHORIZED); + errorDetails.put("error", "Unauthorized"); + errorDetails.put("message", authException.getMessage()); + errorDetails.put("path", request.getServletPath()); + + // 设置响应 + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + + // 序列化错误信息 + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.writeValue(response.getOutputStream(), errorDetails); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/testspring/demos/modules/common/utils/JwtAuthenticationFilter.java b/src/main/java/com/example/testspring/demos/modules/common/utils/JwtAuthenticationFilter.java new file mode 100644 index 0000000..62dff8f --- /dev/null +++ b/src/main/java/com/example/testspring/demos/modules/common/utils/JwtAuthenticationFilter.java @@ -0,0 +1,120 @@ +package com.example.testspring.demos.modules.common.utils; +import com.example.testspring.demos.modules.user.entity.SysUser; +import com.example.testspring.demos.modules.user.service.impl.SysUserServiceImpl; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.security.SignatureException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +/** + * JWT 认证过滤器 - 每个请求都会经过此过滤器 + */ +@Component +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class); + + private final JwtUtil jwtUtil; + private final UserDetailsService userDetailsService; + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) + throws ServletException, IOException { + + try { + // 1. 从请求头中提取JWT令牌 + String jwtToken = parseJwt(request); + + // 2. 如果令牌存在且有效 + if (jwtToken != null && jwtUtil.validateToken(jwtToken)) { + + // 3. 从令牌中提取用户名 + String username = jwtUtil.getUsernameFromToken(jwtToken); + + // 4. 加载用户详情 + UserDetails userDetails = userDetailsService.loadUserByUsername(username); + + // 5. 创建认证令牌 + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken( + userDetails, + null, + userDetails.getAuthorities()); + + // 6. 设置用户请求详情 + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + // 7. 更新安全上下文 + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } catch (ExpiredJwtException ex) { + logger.error("JWT令牌已过期: {}", ex.getMessage()); + request.setAttribute("expired", ex.getMessage()); + } catch (SignatureException ex) { + logger.error("无效的JWT签名: {}", ex.getMessage()); + request.setAttribute("invalid_signature", ex.getMessage()); + } catch (Exception ex) { + logger.error("JWT认证失败: {}", ex.getMessage()); + request.setAttribute("authentication_failure", ex.getMessage()); + } + + // 继续过滤器链 + filterChain.doFilter(request, response); + } + + /** + * 从请求头中提取JWT令牌 + */ + private String parseJwt(HttpServletRequest request) { + String headerAuth = request.getHeader("Authorization"); + + if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) { + return headerAuth.substring(7); + } + + // 尝试从cookie获取token(备用) + /* + if (request.getCookies() != null) { + for (Cookie cookie : request.getCookies()) { + if ("JWT".equals(cookie.getName())) { + return cookie.getValue(); + } + } + } + */ + + return null; + } + + /** + * 排除不需要JWT验证的请求(可选) + */ + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + String path = request.getServletPath(); + // 白名单路径(不需要JWT验证) + return path.startsWith("/api/auth") + || path.startsWith("/public/") + || path.startsWith("/swagger") + || path.startsWith("/v3/api-docs"); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/testspring/demos/modules/common/utils/JwtKeyGenerator.java b/src/main/java/com/example/testspring/demos/modules/common/utils/JwtKeyGenerator.java new file mode 100644 index 0000000..343db43 --- /dev/null +++ b/src/main/java/com/example/testspring/demos/modules/common/utils/JwtKeyGenerator.java @@ -0,0 +1,20 @@ +package com.example.testspring.demos.modules.common.utils; + +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; + +import javax.crypto.SecretKey; +import java.util.Base64; + +public class JwtKeyGenerator { + public static void main(String[] args) { + // 生成符合 HS512 安全要求的密钥 + SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS512); + + // 转换为 Base64 字符串以便存储 + String base64Key = Base64.getEncoder().encodeToString(key.getEncoded()); + System.out.println("Generated Base64-encoded secret key: " + base64Key); + System.out.println("Key length: " + (base64Key.length() * 6) + " bits (Base64 encoded)"); + System.out.println("Actual key length: " + (key.getEncoded().length * 8) + " bits"); + } +} diff --git a/src/main/java/com/example/testspring/demos/modules/common/utils/JwtUtil.java b/src/main/java/com/example/testspring/demos/modules/common/utils/JwtUtil.java new file mode 100644 index 0000000..8ff66eb --- /dev/null +++ b/src/main/java/com/example/testspring/demos/modules/common/utils/JwtUtil.java @@ -0,0 +1,144 @@ +package com.example.testspring.demos.modules.common.utils; + + +import com.example.testspring.demos.modules.user.entity.LoginUser; +import com.example.testspring.demos.modules.user.entity.SysUser; +import io.jsonwebtoken.*; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import io.jsonwebtoken.security.SignatureException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import java.security.Key; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Component +public class JwtUtil { + + private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class); + + @Value("${app.jwt.secret}") + private String jwtSecret; + + @Value("${app.jwt.expiration-ms}") + private int jwtExpirationMs; + +// @Value("${app.jwt.issuer}") +// private String jwtIssuer; + + // 解析令牌 + public String getUsernameFromToken(String token) { + return extractClaim(token, Claims::getSubject); + } + + public Date getExpirationDateFromToken(String token) { + return extractClaim(token, Claims::getExpiration); + } + + public T extractClaim(String token, Function claimsResolver) { + final Claims claims = extractAllClaims(token); + return claimsResolver.apply(claims); + } + + private Claims extractAllClaims(String token) { + return Jwts.parserBuilder() + .setSigningKey(getSigningKey()) + .build() + .parseClaimsJws(token) + .getBody(); + } + + // 生成令牌 +// public String generateToken(Authentication authentication) { +// UserDetails userDetails = (UserDetails) authentication.getPrincipal(); +// +// Map claims = new HashMap<>(); +// claims.put("roles", userDetails.getAuthorities().stream() +// .map(GrantedAuthority::getAuthority) +// .collect(Collectors.toList())); +// claims.put("iss", jwtIssuer); +// claims.put("jti", generateJti()); // 唯一标识符 +// +// return createToken(claims, userDetails.getUsername()); +// } + + // 生成令牌 + public String generateToken(LoginUser loginUser) { + String token = UUID.fastUUID().toString(); + loginUser.setToken(token); +// setUserAgent(loginUser); +// refreshToken(loginUser); + + Map claims = new HashMap<>(); + claims.put("login_user_key", token); + return createToken(claims, loginUser.getUsername()); + + } + + private String createToken(Map claims, String username) { + return Jwts.builder() + .setClaims(claims) + .setSubject(username) + .setIssuedAt(new Date(System.currentTimeMillis())) + .setExpiration(new Date(System.currentTimeMillis() + jwtExpirationMs)) +// .signWith(getSigningKey(), SignatureAlgorithm.HS256) +// .signWith(Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtSecret)), SignatureAlgorithm.HS512) + .signWith(getSigningKey(), SignatureAlgorithm.HS512) + .compact(); + } + + // 验证令牌 + public boolean validateToken(String token) { + try { + Jwts.parserBuilder() + .setSigningKey(getSigningKey()) + .build() + .parseClaimsJws(token); + return true; + } catch (SignatureException e) { + logger.error("无效的JWT签名: {}", e.getMessage()); + } catch (MalformedJwtException e) { + logger.error("无效的JWT令牌: {}", e.getMessage()); + } catch (ExpiredJwtException e) { + logger.error("JWT令牌已过期: {}", e.getMessage()); + } catch (UnsupportedJwtException e) { + logger.error("不支持的JWT令牌: {}", e.getMessage()); + } catch (IllegalArgumentException e) { + logger.error("JWT声明字符串为空: {}", e.getMessage()); + } + return false; + } + + // 辅助方法 + private Key getSigningKey() { + // 1. 将Base64编码的JWT密钥字符串解码为字节数组 + byte[] keyBytes = Decoders.BASE64.decode(jwtSecret); + // 2. 使用字节数组生成HMAC-SHA算法所需的密钥 + return Keys.hmacShaKeyFor(keyBytes); + } + +// /** +// * 获取令牌的过期时间(秒) +// * +// * @param token JWT令牌 +// * @return 过期时间(秒) +// */ +// public long getExpirationTime(String token) { +// Date expiration = extractExpiration(token); +// return (expiration.getTime() - System.currentTimeMillis()) / 1000; +// } + + private String generateJti() { + return java.util.UUID.randomUUID().toString(); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/testspring/demos/modules/common/utils/StringUtils.java b/src/main/java/com/example/testspring/demos/modules/common/utils/StringUtils.java new file mode 100644 index 0000000..1569a3b --- /dev/null +++ b/src/main/java/com/example/testspring/demos/modules/common/utils/StringUtils.java @@ -0,0 +1,598 @@ +package com.example.testspring.demos.modules.common.utils; + +import org.springframework.util.AntPathMatcher; + +import java.util.*; + +public class StringUtils extends org.apache.commons.lang3.StringUtils +{ + /** 空字符串 */ + private static final String NULLSTR = ""; + + /** 下划线 */ + private static final char SEPARATOR = '_'; + + /** + * 获取参数不为空值 + * + * @param value defaultValue 要判断的value + * @return value 返回值 + */ + public static T nvl(T value, T defaultValue) + { + return value != null ? value : defaultValue; + } + + /** + * * 判断一个Collection是否为空, 包含List,Set,Queue + * + * @param coll 要判断的Collection + * @return true:为空 false:非空 + */ + public static boolean isEmpty(Collection coll) + { + return isNull(coll) || coll.isEmpty(); + } + + /** + * * 判断一个Collection是否非空,包含List,Set,Queue + * + * @param coll 要判断的Collection + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Collection coll) + { + return !isEmpty(coll); + } + + /** + * * 判断一个对象数组是否为空 + * + * @param objects 要判断的对象数组 + ** @return true:为空 false:非空 + */ + public static boolean isEmpty(Object[] objects) + { + return isNull(objects) || (objects.length == 0); + } + + /** + * * 判断一个对象数组是否非空 + * + * @param objects 要判断的对象数组 + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Object[] objects) + { + return !isEmpty(objects); + } + + /** + * * 判断一个Map是否为空 + * + * @param map 要判断的Map + * @return true:为空 false:非空 + */ + public static boolean isEmpty(Map map) + { + return isNull(map) || map.isEmpty(); + } + + /** + * * 判断一个Map是否为空 + * + * @param map 要判断的Map + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Map map) + { + return !isEmpty(map); + } + + /** + * * 判断一个字符串是否为空串 + * + * @param str String + * @return true:为空 false:非空 + */ + public static boolean isEmpty(String str) + { + return isNull(str) || NULLSTR.equals(str.trim()); + } + + /** + * * 判断一个字符串是否为非空串 + * + * @param str String + * @return true:非空串 false:空串 + */ + public static boolean isNotEmpty(String str) + { + return !isEmpty(str); + } + + /** + * * 判断一个对象是否为空 + * + * @param object Object + * @return true:为空 false:非空 + */ + public static boolean isNull(Object object) + { + return object == null; + } + + /** + * * 判断一个对象是否非空 + * + * @param object Object + * @return true:非空 false:空 + */ + public static boolean isNotNull(Object object) + { + return !isNull(object); + } + + /** + * * 判断一个对象是否是数组类型(Java基本型别的数组) + * + * @param object 对象 + * @return true:是数组 false:不是数组 + */ + public static boolean isArray(Object object) + { + return isNotNull(object) && object.getClass().isArray(); + } + + /** + * 去空格 + */ + public static String trim(String str) + { + return (str == null ? "" : str.trim()); + } + + /** + * 截取字符串 + * + * @param str 字符串 + * @param start 开始 + * @return 结果 + */ + public static String substring(final String str, int start) + { + if (str == null) + { + return NULLSTR; + } + + if (start < 0) + { + start = str.length() + start; + } + + if (start < 0) + { + start = 0; + } + if (start > str.length()) + { + return NULLSTR; + } + + return str.substring(start); + } + + /** + * 截取字符串 + * + * @param str 字符串 + * @param start 开始 + * @param end 结束 + * @return 结果 + */ + public static String substring(final String str, int start, int end) + { + if (str == null) + { + return NULLSTR; + } + + if (end < 0) + { + end = str.length() + end; + } + if (start < 0) + { + start = str.length() + start; + } + + if (end > str.length()) + { + end = str.length(); + } + + if (start > end) + { + return NULLSTR; + } + + if (start < 0) + { + start = 0; + } + if (end < 0) + { + end = 0; + } + + return str.substring(start, end); + } + + /** + * 格式化文本, {} 表示占位符
+ * 此方法只是简单将占位符 {} 按照顺序替换为参数
+ * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
+ * 例:
+ * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
+ * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
+ * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
+ * + * @param template 文本模板,被替换的部分用 {} 表示 + * @param params 参数值 + * @return 格式化后的文本 + */ +// public static String format(String template, Object... params) +// { +// if (isEmpty(params) || isEmpty(template)) +// { +// return template; +// } +// return StrFormatter.format(template, params); +// } + + /** + * 是否为http(s)://开头 + * + * @param link 链接 + * @return 结果 + */ +// public static boolean ishttp(String link) +// { +// return StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS); +// } + + /** + * 字符串转set + * + * @param str 字符串 + * @param sep 分隔符 + * @return set集合 + */ + public static final Set str2Set(String str, String sep) + { + return new HashSet(str2List(str, sep, true, false)); + } + + /** + * 字符串转list + * + * @param str 字符串 + * @param sep 分隔符 + * @param filterBlank 过滤纯空白 + * @param trim 去掉首尾空白 + * @return list集合 + */ + public static final List str2List(String str, String sep, boolean filterBlank, boolean trim) + { + List list = new ArrayList(); + if (StringUtils.isEmpty(str)) + { + return list; + } + + // 过滤空白字符串 + if (filterBlank && StringUtils.isBlank(str)) + { + return list; + } + String[] split = str.split(sep); + for (String string : split) + { + if (filterBlank && StringUtils.isBlank(string)) + { + continue; + } + if (trim) + { + string = string.trim(); + } + list.add(string); + } + + return list; + } + + /** + * 判断给定的set列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value + * +// * @param set 给定的集合 + * @param array 给定的数组 + * @return boolean 结果 + */ + public static boolean containsAny(Collection collection, String... array) + { + if (isEmpty(collection) || isEmpty(array)) + { + return false; + } + else + { + for (String str : array) + { + if (collection.contains(str)) + { + return true; + } + } + return false; + } + } + + /** + * 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写 + * + * @param cs 指定字符串 + * @param searchCharSequences 需要检查的字符串数组 + * @return 是否包含任意一个字符串 + */ + public static boolean containsAnyIgnoreCase(CharSequence cs, CharSequence... searchCharSequences) + { + if (isEmpty(cs) || isEmpty(searchCharSequences)) + { + return false; + } + for (CharSequence testStr : searchCharSequences) + { + if (containsIgnoreCase(cs, testStr)) + { + return true; + } + } + return false; + } + + /** + * 驼峰转下划线命名 + */ + public static String toUnderScoreCase(String str) + { + if (str == null) + { + return null; + } + StringBuilder sb = new StringBuilder(); + // 前置字符是否大写 + boolean preCharIsUpperCase = true; + // 当前字符是否大写 + boolean curreCharIsUpperCase = true; + // 下一字符是否大写 + boolean nexteCharIsUpperCase = true; + for (int i = 0; i < str.length(); i++) + { + char c = str.charAt(i); + if (i > 0) + { + preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1)); + } + else + { + preCharIsUpperCase = false; + } + + curreCharIsUpperCase = Character.isUpperCase(c); + + if (i < (str.length() - 1)) + { + nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1)); + } + + if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) + { + sb.append(SEPARATOR); + } + else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) + { + sb.append(SEPARATOR); + } + sb.append(Character.toLowerCase(c)); + } + + return sb.toString(); + } + + /** + * 是否包含字符串 + * + * @param str 验证字符串 + * @param strs 字符串组 + * @return 包含返回true + */ + public static boolean inStringIgnoreCase(String str, String... strs) + { + if (str != null && strs != null) + { + for (String s : strs) + { + if (str.equalsIgnoreCase(trim(s))) + { + return true; + } + } + } + return false; + } + + /** + * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld + * + * @param name 转换前的下划线大写方式命名的字符串 + * @return 转换后的驼峰式命名的字符串 + */ + public static String convertToCamelCase(String name) + { + StringBuilder result = new StringBuilder(); + // 快速检查 + if (name == null || name.isEmpty()) + { + // 没必要转换 + return ""; + } + else if (!name.contains("_")) + { + // 不含下划线,仅将首字母大写 + return name.substring(0, 1).toUpperCase() + name.substring(1); + } + // 用下划线将原始字符串分割 + String[] camels = name.split("_"); + for (String camel : camels) + { + // 跳过原始字符串中开头、结尾的下换线或双重下划线 + if (camel.isEmpty()) + { + continue; + } + // 首字母大写 + result.append(camel.substring(0, 1).toUpperCase()); + result.append(camel.substring(1).toLowerCase()); + } + return result.toString(); + } + + /** + * 驼峰式命名法 例如:user_name->userName + */ + public static String toCamelCase(String s) + { + if (s == null) + { + return null; + } + s = s.toLowerCase(); + StringBuilder sb = new StringBuilder(s.length()); + boolean upperCase = false; + for (int i = 0; i < s.length(); i++) + { + char c = s.charAt(i); + + if (c == SEPARATOR) + { + upperCase = true; + } + else if (upperCase) + { + sb.append(Character.toUpperCase(c)); + upperCase = false; + } + else + { + sb.append(c); + } + } + return sb.toString(); + } + + /** + * 查找指定字符串是否匹配指定字符串列表中的任意一个字符串 + * + * @param str 指定字符串 + * @param strs 需要检查的字符串数组 + * @return 是否匹配 + */ + public static boolean matches(String str, List strs) + { + if (isEmpty(str) || isEmpty(strs)) + { + return false; + } + for (String pattern : strs) + { + if (isMatch(pattern, str)) + { + return true; + } + } + return false; + } + + /** + * 判断url是否与规则配置: + * ? 表示单个字符; + * * 表示一层路径内的任意字符串,不可跨层级; + * ** 表示任意层路径; + * + * @param pattern 匹配规则 + * @param url 需要匹配的url + * @return + */ + public static boolean isMatch(String pattern, String url) + { + AntPathMatcher matcher = new AntPathMatcher(); + return matcher.match(pattern, url); + } + + @SuppressWarnings("unchecked") + public static T cast(Object obj) + { + return (T) obj; + } + + /** + * 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。 + * + * @param num 数字对象 + * @param size 字符串指定长度 + * @return 返回数字的字符串格式,该字符串为指定长度。 + */ + public static final String padl(final Number num, final int size) + { + return padl(num.toString(), size, '0'); + } + + /** + * 字符串左补齐。如果原始字符串s长度大于size,则只保留最后size个字符。 + * + * @param s 原始字符串 + * @param size 字符串指定长度 + * @param c 用于补齐的字符 + * @return 返回指定长度的字符串,由原字符串左补齐或截取得到。 + */ + public static final String padl(final String s, final int size, final char c) + { + final StringBuilder sb = new StringBuilder(size); + if (s != null) + { + final int len = s.length(); + if (s.length() <= size) + { + for (int i = size - len; i > 0; i--) + { + sb.append(c); + } + sb.append(s); + } + else + { + return s.substring(len - size, len); + } + } + else + { + for (int i = size; i > 0; i--) + { + sb.append(c); + } + } + return sb.toString(); + } +} diff --git a/src/main/java/com/example/testspring/demos/modules/common/utils/UUID.java b/src/main/java/com/example/testspring/demos/modules/common/utils/UUID.java new file mode 100644 index 0000000..e9306b4 --- /dev/null +++ b/src/main/java/com/example/testspring/demos/modules/common/utils/UUID.java @@ -0,0 +1,481 @@ +package com.example.testspring.demos.modules.common.utils; + +import com.example.testspring.demos.modules.common.exception.UtilException; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +public final class UUID implements java.io.Serializable, Comparable +{ + private static final long serialVersionUID = -1185015143654744140L; + + /** + * SecureRandom 的单例 + * + */ + private static class Holder + { + static final SecureRandom numberGenerator = getSecureRandom(); + } + + /** 此UUID的最高64有效位 */ + private final long mostSigBits; + + /** 此UUID的最低64有效位 */ + private final long leastSigBits; + + /** + * 私有构造 + * + * @param data 数据 + */ + private UUID(byte[] data) + { + long msb = 0; + long lsb = 0; + assert data.length == 16 : "data must be 16 bytes in length"; + for (int i = 0; i < 8; i++) + { + msb = (msb << 8) | (data[i] & 0xff); + } + for (int i = 8; i < 16; i++) + { + lsb = (lsb << 8) | (data[i] & 0xff); + } + this.mostSigBits = msb; + this.leastSigBits = lsb; + } + + /** + * 使用指定的数据构造新的 UUID。 + * + * @param mostSigBits 用于 {@code UUID} 的最高有效 64 位 + * @param leastSigBits 用于 {@code UUID} 的最低有效 64 位 + */ + public UUID(long mostSigBits, long leastSigBits) + { + this.mostSigBits = mostSigBits; + this.leastSigBits = leastSigBits; + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的本地线程伪随机数生成器生成该 UUID。 + * + * @return 随机生成的 {@code UUID} + */ + public static UUID fastUUID() + { + return randomUUID(false); + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 + * + * @return 随机生成的 {@code UUID} + */ + public static UUID randomUUID() + { + return randomUUID(true); + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 + * + * @param isSecure 是否使用{@link SecureRandom}如果是可以获得更安全的随机码,否则可以得到更好的性能 + * @return 随机生成的 {@code UUID} + */ + public static UUID randomUUID(boolean isSecure) + { + final Random ng = isSecure ? Holder.numberGenerator : getRandom(); + + byte[] randomBytes = new byte[16]; + ng.nextBytes(randomBytes); + randomBytes[6] &= 0x0f; /* clear version */ + randomBytes[6] |= 0x40; /* set to version 4 */ + randomBytes[8] &= 0x3f; /* clear variant */ + randomBytes[8] |= 0x80; /* set to IETF variant */ + return new UUID(randomBytes); + } + + /** + * 根据指定的字节数组获取类型 3(基于名称的)UUID 的静态工厂。 + * + * @param name 用于构造 UUID 的字节数组。 + * + * @return 根据指定数组生成的 {@code UUID} + */ + public static UUID nameUUIDFromBytes(byte[] name) + { + MessageDigest md; + try + { + md = MessageDigest.getInstance("MD5"); + } + catch (NoSuchAlgorithmException nsae) + { + throw new InternalError("MD5 not supported"); + } + byte[] md5Bytes = md.digest(name); + md5Bytes[6] &= 0x0f; /* clear version */ + md5Bytes[6] |= 0x30; /* set to version 3 */ + md5Bytes[8] &= 0x3f; /* clear variant */ + md5Bytes[8] |= 0x80; /* set to IETF variant */ + return new UUID(md5Bytes); + } + + /** + * 根据 {@link #toString()} 方法中描述的字符串标准表示形式创建{@code UUID}。 + * + * @param name 指定 {@code UUID} 字符串 + * @return 具有指定值的 {@code UUID} + * @throws IllegalArgumentException 如果 name 与 {@link #toString} 中描述的字符串表示形式不符抛出此异常 + * + */ + public static UUID fromString(String name) + { + String[] components = name.split("-"); + if (components.length != 5) + { + throw new IllegalArgumentException("Invalid UUID string: " + name); + } + for (int i = 0; i < 5; i++) + { + components[i] = "0x" + components[i]; + } + + long mostSigBits = Long.decode(components[0]).longValue(); + mostSigBits <<= 16; + mostSigBits |= Long.decode(components[1]).longValue(); + mostSigBits <<= 16; + mostSigBits |= Long.decode(components[2]).longValue(); + + long leastSigBits = Long.decode(components[3]).longValue(); + leastSigBits <<= 48; + leastSigBits |= Long.decode(components[4]).longValue(); + + return new UUID(mostSigBits, leastSigBits); + } + + /** + * 返回此 UUID 的 128 位值中的最低有效 64 位。 + * + * @return 此 UUID 的 128 位值中的最低有效 64 位。 + */ + public long getLeastSignificantBits() + { + return leastSigBits; + } + + /** + * 返回此 UUID 的 128 位值中的最高有效 64 位。 + * + * @return 此 UUID 的 128 位值中最高有效 64 位。 + */ + public long getMostSignificantBits() + { + return mostSigBits; + } + + /** + * 与此 {@code UUID} 相关联的版本号. 版本号描述此 {@code UUID} 是如何生成的。 + *

+ * 版本号具有以下含意: + *

    + *
  • 1 基于时间的 UUID + *
  • 2 DCE 安全 UUID + *
  • 3 基于名称的 UUID + *
  • 4 随机生成的 UUID + *
+ * + * @return 此 {@code UUID} 的版本号 + */ + public int version() + { + // Version is bits masked by 0x000000000000F000 in MS long + return (int) ((mostSigBits >> 12) & 0x0f); + } + + /** + * 与此 {@code UUID} 相关联的变体号。变体号描述 {@code UUID} 的布局。 + *

+ * 变体号具有以下含意: + *

    + *
  • 0 为 NCS 向后兼容保留 + *
  • 2 IETF RFC 4122(Leach-Salz), 用于此类 + *
  • 6 保留,微软向后兼容 + *
  • 7 保留供以后定义使用 + *
+ * + * @return 此 {@code UUID} 相关联的变体号 + */ + public int variant() + { + // This field is composed of a varying number of bits. + // 0 - - Reserved for NCS backward compatibility + // 1 0 - The IETF aka Leach-Salz variant (used by this class) + // 1 1 0 Reserved, Microsoft backward compatibility + // 1 1 1 Reserved for future definition. + return (int) ((leastSigBits >>> (64 - (leastSigBits >>> 62))) & (leastSigBits >> 63)); + } + + /** + * 与此 UUID 相关联的时间戳值。 + * + *

+ * 60 位的时间戳值根据此 {@code UUID} 的 time_low、time_mid 和 time_hi 字段构造。
+ * 所得到的时间戳以 100 毫微秒为单位,从 UTC(通用协调时间) 1582 年 10 月 15 日零时开始。 + * + *

+ * 时间戳值仅在在基于时间的 UUID(其 version 类型为 1)中才有意义。
+ * 如果此 {@code UUID} 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 + * + * @throws UnsupportedOperationException 如果此 {@code UUID} 不是 version 为 1 的 UUID。 + */ + public long timestamp() throws UnsupportedOperationException + { + checkTimeBase(); + return (mostSigBits & 0x0FFFL) << 48// + | ((mostSigBits >> 16) & 0x0FFFFL) << 32// + | mostSigBits >>> 32; + } + + /** + * 与此 UUID 相关联的时钟序列值。 + * + *

+ * 14 位的时钟序列值根据此 UUID 的 clock_seq 字段构造。clock_seq 字段用于保证在基于时间的 UUID 中的时间唯一性。 + *

+ * {@code clockSequence} 值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。 如果此 UUID 不是基于时间的 UUID,则此方法抛出 + * UnsupportedOperationException。 + * + * @return 此 {@code UUID} 的时钟序列 + * + * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 + */ + public int clockSequence() throws UnsupportedOperationException + { + checkTimeBase(); + return (int) ((leastSigBits & 0x3FFF000000000000L) >>> 48); + } + + /** + * 与此 UUID 相关的节点值。 + * + *

+ * 48 位的节点值根据此 UUID 的 node 字段构造。此字段旨在用于保存机器的 IEEE 802 地址,该地址用于生成此 UUID 以保证空间唯一性。 + *

+ * 节点值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。
+ * 如果此 UUID 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 + * + * @return 此 {@code UUID} 的节点值 + * + * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 + */ + public long node() throws UnsupportedOperationException + { + checkTimeBase(); + return leastSigBits & 0x0000FFFFFFFFFFFFL; + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

+ * UUID 的字符串表示形式由此 BNF 描述: + * + *

+     * {@code
+     * UUID                   = ----
+     * time_low               = 4*
+     * time_mid               = 2*
+     * time_high_and_version  = 2*
+     * variant_and_sequence   = 2*
+     * node                   = 6*
+     * hexOctet               = 
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * 
+ * + * + * + * @return 此{@code UUID} 的字符串表现形式 + * @see #toString(boolean) + */ + @Override + public String toString() + { + return toString(false); + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

+ * UUID 的字符串表示形式由此 BNF 描述: + * + *

+     * {@code
+     * UUID                   = ----
+     * time_low               = 4*
+     * time_mid               = 2*
+     * time_high_and_version  = 2*
+     * variant_and_sequence   = 2*
+     * node                   = 6*
+     * hexOctet               = 
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * 
+ * + * + * + * @param isSimple 是否简单模式,简单模式为不带'-'的UUID字符串 + * @return 此{@code UUID} 的字符串表现形式 + */ + public String toString(boolean isSimple) + { + final StringBuilder builder = new StringBuilder(isSimple ? 32 : 36); + // time_low + builder.append(digits(mostSigBits >> 32, 8)); + if (!isSimple) + { + builder.append('-'); + } + // time_mid + builder.append(digits(mostSigBits >> 16, 4)); + if (!isSimple) + { + builder.append('-'); + } + // time_high_and_version + builder.append(digits(mostSigBits, 4)); + if (!isSimple) + { + builder.append('-'); + } + // variant_and_sequence + builder.append(digits(leastSigBits >> 48, 4)); + if (!isSimple) + { + builder.append('-'); + } + // node + builder.append(digits(leastSigBits, 12)); + + return builder.toString(); + } + + /** + * 返回此 UUID 的哈希码。 + * + * @return UUID 的哈希码值。 + */ + @Override + public int hashCode() + { + long hilo = mostSigBits ^ leastSigBits; + return ((int) (hilo >> 32)) ^ (int) hilo; + } + + /** + * 将此对象与指定对象比较。 + *

+ * 当且仅当参数不为 {@code null}、而是一个 UUID 对象、具有与此 UUID 相同的 varriant、包含相同的值(每一位均相同)时,结果才为 {@code true}。 + * + * @param obj 要与之比较的对象 + * + * @return 如果对象相同,则返回 {@code true};否则返回 {@code false} + */ + @Override + public boolean equals(Object obj) + { + if ((null == obj) || (obj.getClass() != UUID.class)) + { + return false; + } + UUID id = (UUID) obj; + return (mostSigBits == id.mostSigBits && leastSigBits == id.leastSigBits); + } + + // Comparison Operations + + /** + * 将此 UUID 与指定的 UUID 比较。 + * + *

+ * 如果两个 UUID 不同,且第一个 UUID 的最高有效字段大于第二个 UUID 的对应字段,则第一个 UUID 大于第二个 UUID。 + * + * @param val 与此 UUID 比较的 UUID + * + * @return 在此 UUID 小于、等于或大于 val 时,分别返回 -1、0 或 1。 + * + */ + @Override + public int compareTo(UUID val) + { + // The ordering is intentionally set up so that the UUIDs + // can simply be numerically compared as two numbers + return (this.mostSigBits < val.mostSigBits ? -1 : // + (this.mostSigBits > val.mostSigBits ? 1 : // + (this.leastSigBits < val.leastSigBits ? -1 : // + (this.leastSigBits > val.leastSigBits ? 1 : // + 0)))); + } + + // ------------------------------------------------------------------------------------------------------------------- + // Private method start + /** + * 返回指定数字对应的hex值 + * + * @param val 值 + * @param digits 位 + * @return 值 + */ + private static String digits(long val, int digits) + { + long hi = 1L << (digits * 4); + return Long.toHexString(hi | (val & (hi - 1))).substring(1); + } + + /** + * 检查是否为time-based版本UUID + */ + private void checkTimeBase() + { + if (version() != 1) + { + throw new UnsupportedOperationException("Not a time-based UUID"); + } + } + + /** + * 获取{@link SecureRandom},类提供加密的强随机数生成器 (RNG) + * + * @return {@link SecureRandom} + */ + public static SecureRandom getSecureRandom() + { + try + { + return SecureRandom.getInstance("SHA1PRNG"); + } + catch (NoSuchAlgorithmException e) + { + throw new UtilException(e); + } + } + + /** + * 获取随机数生成器对象
+ * ThreadLocalRandom是JDK 7之后提供并发产生随机数,能够解决多个线程发生的竞争争夺。 + * + * @return {@link ThreadLocalRandom} + */ + public static ThreadLocalRandom getRandom() + { + return ThreadLocalRandom.current(); + } +} + diff --git a/src/main/java/com/example/testspring/demos/modules/user/controller/AdminController.java b/src/main/java/com/example/testspring/demos/modules/user/controller/AdminController.java new file mode 100644 index 0000000..2ff239d --- /dev/null +++ b/src/main/java/com/example/testspring/demos/modules/user/controller/AdminController.java @@ -0,0 +1,20 @@ +package com.example.testspring.demos.modules.user.controller; + + +import com.example.testspring.demos.modules.common.utils.R; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/admin") +public class AdminController { + + @GetMapping("/users") + @Parameter(name = "Authorization", description = "Authorization token", required = true, in = ParameterIn.HEADER) + public R getAllUsers() { + return R.ok("管理员用户列表"); + } +} diff --git a/src/main/java/com/example/testspring/demos/modules/user/controller/LoginController.java b/src/main/java/com/example/testspring/demos/modules/user/controller/LoginController.java new file mode 100644 index 0000000..3ce9ce7 --- /dev/null +++ b/src/main/java/com/example/testspring/demos/modules/user/controller/LoginController.java @@ -0,0 +1,148 @@ +package com.example.testspring.demos.modules.user.controller; + + +//import com.example.testspring.demos.modules.common.utils.JwtUtil; +import com.example.testspring.demos.modules.common.utils.JwtUtil; +import com.example.testspring.demos.modules.common.utils.R; +import com.example.testspring.demos.modules.user.entity.LoginUser; +import com.example.testspring.demos.modules.user.entity.dto.LoginDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +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 java.util.List; +import java.util.stream.Collectors; + +@RestController +@RequestMapping("/api/auth") +@Tag(name = "登录接口") +public class LoginController { + + private final AuthenticationManager authenticationManager; + private final JwtUtil jwtUtil; +// private final UserRepository userRepository; +// private final LoginRateLimiter loginRateLimiter; +// private final LoginAuditService loginAuditService; + + // 使用构造函数注入 + @Autowired + public LoginController(AuthenticationManager authenticationManager, + JwtUtil jwtUtil + + /* 如果需要,添加以下参数: + LoginRateLimiter loginRateLimiter, + LoginAuditService loginAuditService */ + ) { + this.authenticationManager = authenticationManager; + this.jwtUtil = jwtUtil; + + // this.loginRateLimiter = loginRateLimiter; + // this.loginAuditService = loginAuditService; + } + + + @PostMapping("/login") + @Operation(summary = "用户登录") + public R authenticateUser(@RequestBody LoginDto loginRequest, + HttpServletRequest request) { + // 获取客户端IP地址 + String clientIp = getClientIp(request); + + System.out.println("登录请求:" + loginRequest); + System.out.println("客户端IP地址:" + clientIp); + + + System.out.println("用户名:" + loginRequest.getUsername()); + System.out.println("密码:" + loginRequest.getPassword()); + + // 检查是否允许登录(限流) +// if (!loginRateLimiter.allowLogin(loginRequest.getUsername())) { +// return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS) +// .body("登录尝试过多,请稍后再试"); +// } + + try { + // 创建认证令牌 + Authentication authentication = authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken( + loginRequest.getUsername(), + loginRequest.getPassword() + ) + ); + + // 设置认证信息到安全上下文 + SecurityContextHolder.getContext().setAuthentication(authentication); + +// User user = userRepository.findByUsername(loginRequest.getUsername()) +// .orElseThrow(() -> new UsernameNotFoundException("用户未找到: " + loginRequest.getUsername())); + +// // 获取用户详情 +// UserDetails userDetails = (UserDetails) authentication.getPrincipal(); +// +// // 提取用户角色 +// List roles = userDetails.getAuthorities().stream() +// .map(GrantedAuthority::getAuthority) +// .collect(Collectors.toList()); + + // 生成JWT令牌 + LoginUser loginUser = (LoginUser) authentication.getPrincipal(); + String jwt = jwtUtil.generateToken(loginUser); + + // 获取令牌有效期(秒) +// long expiresIn = jwtUtil.getExpirationTime(jwt); + +// // 创建响应对象 +// LoginResponseDTO response = new LoginResponseDTO( +// jwt, +// expiresIn, +// userDetails.getUsername(), +// roles +// ); +// +// // 登录成功,重置限流器 +// loginRateLimiter.loginSuccess(loginRequest.getUsername()); +// +// // 记录登录成功审计日志 +// loginAuditService.logLoginSuccess(loginRequest.getUsername(), clientIp); + + R response = R.ok("登录成功"); + response.put("token", jwt); + + return response; + + } catch (AuthenticationException e) { + // 记录登录失败审计日志 +// loginAuditService.logLoginFailure(loginRequest.getUsername(), clientIp); + return R.error(500,"用户名或密码错误"); + } + } + + // 获取客户端IP地址 + private String getClientIp(HttpServletRequest request) { + String ip = request.getHeader("X-Forwarded-For"); + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + return ip; + } +} diff --git a/src/main/java/com/example/testspring/demos/modules/user/controller/testController.java b/src/main/java/com/example/testspring/demos/modules/user/controller/testController.java index 3ef7728..54f57cc 100644 --- a/src/main/java/com/example/testspring/demos/modules/user/controller/testController.java +++ b/src/main/java/com/example/testspring/demos/modules/user/controller/testController.java @@ -44,4 +44,10 @@ public class testController { public R testR(){ return R.ok().put("data",robotService.selectRobotList(new Robot())); } + + @GetMapping("/testUser") + @Operation(summary = "测试获取用户列表") + public R testUser(){ + return R.ok().put("data",sysUserService.selectUserList(new SysUser())); + } } diff --git a/src/main/java/com/example/testspring/demos/modules/user/entity/LoginUser.java b/src/main/java/com/example/testspring/demos/modules/user/entity/LoginUser.java new file mode 100644 index 0000000..121e031 --- /dev/null +++ b/src/main/java/com/example/testspring/demos/modules/user/entity/LoginUser.java @@ -0,0 +1,260 @@ +package com.example.testspring.demos.modules.user.entity; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.Set; + +public class LoginUser implements UserDetails +{ + private static final long serialVersionUID = 1L; + + /** + * 用户ID + */ + private Long userId; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 用户唯一标识 + */ + private String token; + + /** + * 登录时间 + */ + private Long loginTime; + + /** + * 过期时间 + */ + private Long expireTime; + + /** + * 登录IP地址 + */ + private String ipaddr; + + /** + * 登录地点 + */ + private String loginLocation; + + /** + * 浏览器类型 + */ + private String browser; + + /** + * 操作系统 + */ + private String os; + + /** + * 权限列表 + */ + private Set permissions; + + /** + * 用户信息 + */ + private SysUser user; + + public Long getUserId() + { + return userId; + } + + public void setUserId(Long userId) + { + this.userId = userId; + } + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + public String getToken() + { + return token; + } + + public void setToken(String token) + { + this.token = token; + } + + public LoginUser() + { + } + + public LoginUser(SysUser user, Set permissions) + { + this.user = user; + this.permissions = permissions; + } + + public LoginUser(Long userId, Long deptId, SysUser user, Set permissions) + { + this.userId = userId; + this.deptId = deptId; + this.user = user; + this.permissions = permissions; + } + +// @JSONField(serialize = false) + @Override + public String getPassword() + { + return user.getPassword(); + } + + @Override + public String getUsername() + { + return user.getUserName(); + } + + /** + * 账户是否未过期,过期无法验证 + */ +// @JSONField(serialize = false) + @Override + public boolean isAccountNonExpired() + { + return true; + } + + /** + * 指定用户是否解锁,锁定的用户无法进行身份验证 + * + * @return + */ +// @JSONField(serialize = false) + @Override + public boolean isAccountNonLocked() + { + return true; + } + + /** + * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证 + * + * @return + */ +// @JSONField(serialize = false) + @Override + public boolean isCredentialsNonExpired() + { + return true; + } + + /** + * 是否可用 ,禁用的用户不能身份验证 + * + * @return + */ +// @JSONField(serialize = false) + @Override + public boolean isEnabled() + { + return true; + } + + public Long getLoginTime() + { + return loginTime; + } + + public void setLoginTime(Long loginTime) + { + this.loginTime = loginTime; + } + + public String getIpaddr() + { + return ipaddr; + } + + public void setIpaddr(String ipaddr) + { + this.ipaddr = ipaddr; + } + + public String getLoginLocation() + { + return loginLocation; + } + + public void setLoginLocation(String loginLocation) + { + this.loginLocation = loginLocation; + } + + public String getBrowser() + { + return browser; + } + + public void setBrowser(String browser) + { + this.browser = browser; + } + + public String getOs() + { + return os; + } + + public void setOs(String os) + { + this.os = os; + } + + public Long getExpireTime() + { + return expireTime; + } + + public void setExpireTime(Long expireTime) + { + this.expireTime = expireTime; + } + + public Set getPermissions() + { + return permissions; + } + + public void setPermissions(Set permissions) + { + this.permissions = permissions; + } + + public SysUser getUser() + { + return user; + } + + public void setUser(SysUser user) + { + this.user = user; + } + + @Override + public Collection getAuthorities() + { + return null; + } +} diff --git a/src/main/java/com/example/testspring/demos/modules/user/entity/SysUser.java b/src/main/java/com/example/testspring/demos/modules/user/entity/SysUser.java index 8158eb7..5874abf 100644 --- a/src/main/java/com/example/testspring/demos/modules/user/entity/SysUser.java +++ b/src/main/java/com/example/testspring/demos/modules/user/entity/SysUser.java @@ -1,335 +1,297 @@ package com.example.testspring.demos.modules.user.entity; -import java.util.Date; +import com.example.testspring.demos.modules.common.entity.BaseEntity; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; -public class SysUser { - /** - * - * This field was generated by MyBatis Generator. - * This field corresponds to the database column sys_user.user_id - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ +import java.util.Date; +import java.util.List; + +public class SysUser extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 用户ID */ private Long userId; - /** - * - * This field was generated by MyBatis Generator. - * This field corresponds to the database column sys_user.username - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - private String username; + /** 部门ID */ + private Long deptId; - /** - * - * This field was generated by MyBatis Generator. - * This field corresponds to the database column sys_user.password - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - private String password; + /** 用户账号 */ + private String userName; - /** - * - * This field was generated by MyBatis Generator. - * This field corresponds to the database column sys_user.name - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - private String name; + /** 用户昵称 */ + private String nickName; - /** - * - * This field was generated by MyBatis Generator. - * This field corresponds to the database column sys_user.salt - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - private String salt; - - /** - * - * This field was generated by MyBatis Generator. - * This field corresponds to the database column sys_user.email - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ + /** 用户邮箱 */ private String email; - /** - * - * This field was generated by MyBatis Generator. - * This field corresponds to the database column sys_user.mobile - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - private String mobile; + /** 手机号码 */ + private String phonenumber; - /** - * - * This field was generated by MyBatis Generator. - * This field corresponds to the database column sys_user.status - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - private Byte status; + /** 用户性别 */ + private String sex; - /** - * - * This field was generated by MyBatis Generator. - * This field corresponds to the database column sys_user.create_user_id - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - private Long createUserId; + /** 用户头像 */ + private String avatar; - /** - * - * This field was generated by MyBatis Generator. - * This field corresponds to the database column sys_user.create_time - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - private Date createTime; + /** 密码 */ + private String password; + + /** 帐号状态(0正常 1停用) */ + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + private String delFlag; + + /** 最后登录IP */ + private String loginIp; + + /** 最后登录时间 */ + private Date loginDate; + + /** 部门对象 */ + +// private SysDept dept; + + /** 角色对象 */ +// private List roles; + + /** 角色组 */ + private Long[] roleIds; + + /** 岗位组 */ + private Long[] postIds; + + /** 角色ID */ + private Long roleId; + + public SysUser() + { - /** - * This method was generated by MyBatis Generator. - * This method returns the value of the database column sys_user.user_id - * - * @return the value of sys_user.user_id - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - public Long getUserId() { - return userId; } - /** - * This method was generated by MyBatis Generator. - * This method sets the value of the database column sys_user.user_id - * - * @param userId the value for sys_user.user_id - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - public void setUserId(Long userId) { + public SysUser(Long userId) + { this.userId = userId; } - /** - * This method was generated by MyBatis Generator. - * This method returns the value of the database column sys_user.username - * - * @return the value of sys_user.username - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - public String getUsername() { - return username; + public Long getUserId() + { + return userId; } - /** - * This method was generated by MyBatis Generator. - * This method sets the value of the database column sys_user.username - * - * @param username the value for sys_user.username - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - public void setUsername(String username) { - this.username = username; + public void setUserId(Long userId) + { + this.userId = userId; } - /** - * This method was generated by MyBatis Generator. - * This method returns the value of the database column sys_user.password - * - * @return the value of sys_user.password - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - public String getPassword() { - return password; + public boolean isAdmin() + { + return isAdmin(this.userId); } - /** - * This method was generated by MyBatis Generator. - * This method sets the value of the database column sys_user.password - * - * @param password the value for sys_user.password - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - public void setPassword(String password) { - this.password = password; + public static boolean isAdmin(Long userId) + { + return userId != null && 1L == userId; } - /** - * This method was generated by MyBatis Generator. - * This method returns the value of the database column sys_user.name - * - * @return the value of sys_user.name - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - public String getName() { - return name; + public Long getDeptId() + { + return deptId; } - /** - * This method was generated by MyBatis Generator. - * This method sets the value of the database column sys_user.name - * - * @param name the value for sys_user.name - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - public void setName(String name) { - this.name = name; + public void setDeptId(Long deptId) + { + this.deptId = deptId; } - /** - * This method was generated by MyBatis Generator. - * This method returns the value of the database column sys_user.salt - * - * @return the value of sys_user.salt - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - public String getSalt() { - return salt; + + public String getNickName() + { + return nickName; } - /** - * This method was generated by MyBatis Generator. - * This method sets the value of the database column sys_user.salt - * - * @param salt the value for sys_user.salt - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - public void setSalt(String salt) { - this.salt = salt; + public void setNickName(String nickName) + { + this.nickName = nickName; } - /** - * This method was generated by MyBatis Generator. - * This method returns the value of the database column sys_user.email - * - * @return the value of sys_user.email - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - public String getEmail() { + + public String getUserName() + { + return userName; + } + + public void setUserName(String userName) + { + this.userName = userName; + } + + + public String getEmail() + { return email; } - /** - * This method was generated by MyBatis Generator. - * This method sets the value of the database column sys_user.email - * - * @param email the value for sys_user.email - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - public void setEmail(String email) { + public void setEmail(String email) + { this.email = email; } - /** - * This method was generated by MyBatis Generator. - * This method returns the value of the database column sys_user.mobile - * - * @return the value of sys_user.mobile - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - public String getMobile() { - return mobile; + + public String getPhonenumber() + { + return phonenumber; } - /** - * This method was generated by MyBatis Generator. - * This method sets the value of the database column sys_user.mobile - * - * @param mobile the value for sys_user.mobile - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - public void setMobile(String mobile) { - this.mobile = mobile; + public void setPhonenumber(String phonenumber) + { + this.phonenumber = phonenumber; } - /** - * This method was generated by MyBatis Generator. - * This method returns the value of the database column sys_user.status - * - * @return the value of sys_user.status - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - public Byte getStatus() { + public String getSex() + { + return sex; + } + + public void setSex(String sex) + { + this.sex = sex; + } + + public String getAvatar() + { + return avatar; + } + + public void setAvatar(String avatar) + { + this.avatar = avatar; + } + + public String getPassword() + { + return password; + } + + public void setPassword(String password) + { + this.password = password; + } + + public String getStatus() + { return status; } - /** - * This method was generated by MyBatis Generator. - * This method sets the value of the database column sys_user.status - * - * @param status the value for sys_user.status - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - public void setStatus(Byte status) { + public void setStatus(String status) + { this.status = status; } - /** - * This method was generated by MyBatis Generator. - * This method returns the value of the database column sys_user.create_user_id - * - * @return the value of sys_user.create_user_id - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - public Long getCreateUserId() { - return createUserId; + public String getDelFlag() + { + return delFlag; } - /** - * This method was generated by MyBatis Generator. - * This method sets the value of the database column sys_user.create_user_id - * - * @param createUserId the value for sys_user.create_user_id - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - public void setCreateUserId(Long createUserId) { - this.createUserId = createUserId; + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; } - /** - * This method was generated by MyBatis Generator. - * This method returns the value of the database column sys_user.create_time - * - * @return the value of sys_user.create_time - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - public Date getCreateTime() { - return createTime; + public String getLoginIp() + { + return loginIp; } - /** - * This method was generated by MyBatis Generator. - * This method sets the value of the database column sys_user.create_time - * - * @param createTime the value for sys_user.create_time - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 - */ - public void setCreateTime(Date createTime) { - this.createTime = createTime; + public void setLoginIp(String loginIp) + { + this.loginIp = loginIp; } -} \ No newline at end of file + + public Date getLoginDate() + { + return loginDate; + } + + public void setLoginDate(Date loginDate) + { + this.loginDate = loginDate; + } + +// public SysDept getDept() +// { +// return dept; +// } +// +// public void setDept(SysDept dept) +// { +// this.dept = dept; +// } +// +// public List getRoles() +// { +// return roles; +// } +// +// public void setRoles(List roles) +// { +// this.roles = roles; +// } + + public Long[] getRoleIds() + { + return roleIds; + } + + public void setRoleIds(Long[] roleIds) + { + this.roleIds = roleIds; + } + + public Long[] getPostIds() + { + return postIds; + } + + public void setPostIds(Long[] postIds) + { + this.postIds = postIds; + } + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("userId", getUserId()) + .append("deptId", getDeptId()) + .append("userName", getUserName()) + .append("nickName", getNickName()) + .append("email", getEmail()) + .append("phonenumber", getPhonenumber()) + .append("sex", getSex()) + .append("avatar", getAvatar()) + .append("password", getPassword()) + .append("status", getStatus()) + .append("delFlag", getDelFlag()) + .append("loginIp", getLoginIp()) + .append("loginDate", getLoginDate()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) +// .append("dept", getDept()) + .toString(); + } +} diff --git a/src/main/java/com/example/testspring/demos/modules/user/entity/dto/LoginDto.java b/src/main/java/com/example/testspring/demos/modules/user/entity/dto/LoginDto.java new file mode 100644 index 0000000..2f4da80 --- /dev/null +++ b/src/main/java/com/example/testspring/demos/modules/user/entity/dto/LoginDto.java @@ -0,0 +1,23 @@ +package com.example.testspring.demos.modules.user.entity.dto; + +public class LoginDto { + private String username; + private String password; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + +} diff --git a/src/main/java/com/example/testspring/demos/modules/user/mapper/SysUserMapper.java b/src/main/java/com/example/testspring/demos/modules/user/mapper/SysUserMapper.java index 6f820df..5e3169a 100644 --- a/src/main/java/com/example/testspring/demos/modules/user/mapper/SysUserMapper.java +++ b/src/main/java/com/example/testspring/demos/modules/user/mapper/SysUserMapper.java @@ -1,52 +1,131 @@ package com.example.testspring.demos.modules.user.mapper; + import com.example.testspring.demos.modules.user.entity.SysUser; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; import java.util.List; - +/** + * 用户表 数据层 + * + * @author ruoyi + */ @Mapper -public interface SysUserMapper { +public interface SysUserMapper +{ /** - * This method was generated by MyBatis Generator. - * This method corresponds to the database table sys_user - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 + * 根据条件分页查询用户列表 + * + * @param sysUser 用户信息 + * @return 用户信息集合信息 */ - int deleteByPrimaryKey(Long userId); + public List selectUserList(SysUser sysUser); /** - * This method was generated by MyBatis Generator. - * This method corresponds to the database table sys_user - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 + * 根据条件分页查询已配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 */ - int insert(SysUser record); + public List selectAllocatedList(SysUser user); /** - * This method was generated by MyBatis Generator. - * This method corresponds to the database table sys_user - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 + * 根据条件分页查询未分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 */ - SysUser selectByPrimaryKey(Long userId); + public List selectUnallocatedList(SysUser user); /** - * This method was generated by MyBatis Generator. - * This method corresponds to the database table sys_user - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 */ - List selectAll(); + public SysUser selectUserByUserName(String userName); /** - * This method was generated by MyBatis Generator. - * This method corresponds to the database table sys_user - * - * @mbg.generated Thu Jun 26 17:58:24 CST 2025 + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 */ - int updateByPrimaryKey(SysUser record); + public SysUser selectUserById(Long userId); - List selectUserList(SysUser user); -} \ No newline at end of file + /** + * 新增用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int insertUser(SysUser user); + + /** + * 修改用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUser(SysUser user); + + /** + * 修改用户头像 + * + * @param userName 用户名 + * @param avatar 头像地址 + * @return 结果 + */ + public int updateUserAvatar(@Param("userName") String userName, @Param("avatar") String avatar); + + /** + * 重置用户密码 + * + * @param userName 用户名 + * @param password 密码 + * @return 结果 + */ + public int resetUserPwd(@Param("userName") String userName, @Param("password") String password); + + /** + * 通过用户ID删除用户 + * + * @param userId 用户ID + * @return 结果 + */ + public int deleteUserById(Long userId); + + /** + * 批量删除用户信息 + * + * @param userIds 需要删除的用户ID + * @return 结果 + */ + public int deleteUserByIds(Long[] userIds); + + /** + * 校验用户名称是否唯一 + * + * @param userName 用户名称 + * @return 结果 + */ + public SysUser checkUserNameUnique(String userName); + + /** + * 校验手机号码是否唯一 + * + * @param phonenumber 手机号码 + * @return 结果 + */ + public SysUser checkPhoneUnique(String phonenumber); + + /** + * 校验email是否唯一 + * + * @param email 用户邮箱 + * @return 结果 + */ + public SysUser checkEmailUnique(String email); +} diff --git a/src/main/java/com/example/testspring/demos/modules/user/service/ISysUserService.java b/src/main/java/com/example/testspring/demos/modules/user/service/ISysUserService.java index 3519574..4b6de63 100644 --- a/src/main/java/com/example/testspring/demos/modules/user/service/ISysUserService.java +++ b/src/main/java/com/example/testspring/demos/modules/user/service/ISysUserService.java @@ -1,14 +1,19 @@ package com.example.testspring.demos.modules.user.service; -import com.example.testspring.demos.modules.user.entity.SysMenu; + + import com.example.testspring.demos.modules.user.entity.SysUser; import java.util.List; -public interface ISysUserService { - +/** + * 用户 业务层 + * + * @author ruoyi + */ +public interface ISysUserService +{ + public SysUser selectUserByUserName(String userName); public List selectUserList(SysUser user); -// public List selectMenuList(); - } diff --git a/src/main/java/com/example/testspring/demos/modules/user/service/impl/CustomUserDetailsService.java b/src/main/java/com/example/testspring/demos/modules/user/service/impl/CustomUserDetailsService.java new file mode 100644 index 0000000..0c04afa --- /dev/null +++ b/src/main/java/com/example/testspring/demos/modules/user/service/impl/CustomUserDetailsService.java @@ -0,0 +1,37 @@ +package com.example.testspring.demos.modules.user.service.impl; + +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 java.util.Map; + +//@Service +//public class CustomUserDetailsService implements UserDetailsService { +// +// // 临时模拟数据库用户数据 +// private final Map users = Map.of( +// "admin", User.builder() +// .username("admin") +// .password("$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2") // 密码是 "admin123" +// .roles("ADMIN") +// .build(), +// "user", User.builder() +// .username("user") +// .password("$2a$10$jZ5uF5rKx1Yl2dCz3lqWDu5c8lD7vUwEf9XJ5oM3rH2gNk7Wm4i6t") // 密码是 "user123" +// .roles("USER") +// .build() +// ); +// +// @Override +// public UserDetails loadUserByUsername(String username) +// throws UsernameNotFoundException { +// +// if (!users.containsKey(username)) { +// throw new UsernameNotFoundException("用户未找到: " + username); +// } +// return users.get(username); +// } +//} diff --git a/src/main/java/com/example/testspring/demos/modules/user/service/impl/DatabaseUserDetailsService.java b/src/main/java/com/example/testspring/demos/modules/user/service/impl/DatabaseUserDetailsService.java new file mode 100644 index 0000000..2e63582 --- /dev/null +++ b/src/main/java/com/example/testspring/demos/modules/user/service/impl/DatabaseUserDetailsService.java @@ -0,0 +1,54 @@ +package com.example.testspring.demos.modules.user.service.impl; + + +import com.example.testspring.demos.modules.common.utils.StringUtils; +import com.example.testspring.demos.modules.user.entity.LoginUser; +import com.example.testspring.demos.modules.user.entity.SysUser; +import com.example.testspring.demos.modules.user.service.ISysUserService; + +//import org.hibernate.service.spi.ServiceException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +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 java.util.HashSet; +import java.util.Set; + +@Service +public class DatabaseUserDetailsService implements UserDetailsService { + + private static final Logger log = LoggerFactory.getLogger(DatabaseUserDetailsService.class); + + @Autowired + private ISysUserService sysUserService; + + @Override + public UserDetails loadUserByUsername(String username) + throws UsernameNotFoundException { + + SysUser user = sysUserService.selectUserByUserName(username); + if (StringUtils.isNull(user)) + { + log.info("登录用户:{} 不存在.", username); + throw new UsernameNotFoundException("用户未找到: " + username); + } + + return createLoginUser(user); + + + } + + public UserDetails createLoginUser(SysUser user) + { + Set permissions = new HashSet<>(); +// permissions.addAll(["system:user:list", "system:user:query"]); + permissions.add("admin"); + return new LoginUser(user.getUserId(), user.getDeptId(), user, permissions); + } + +} diff --git a/src/main/java/com/example/testspring/demos/modules/user/service/impl/SysUserServiceImpl.java b/src/main/java/com/example/testspring/demos/modules/user/service/impl/SysUserServiceImpl.java index 211bf38..8e4ef14 100644 --- a/src/main/java/com/example/testspring/demos/modules/user/service/impl/SysUserServiceImpl.java +++ b/src/main/java/com/example/testspring/demos/modules/user/service/impl/SysUserServiceImpl.java @@ -1,29 +1,60 @@ package com.example.testspring.demos.modules.user.service.impl; -import com.example.testspring.demos.modules.user.entity.SysMenu; + import com.example.testspring.demos.modules.user.entity.SysUser; -import com.example.testspring.demos.modules.user.mapper.SysMenuMapper; import com.example.testspring.demos.modules.user.mapper.SysUserMapper; import com.example.testspring.demos.modules.user.service.ISysUserService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; +/** + * 用户 业务层处理 + * + * @author ruoyi + */ @Service -public class SysUserServiceImpl implements ISysUserService { +public class SysUserServiceImpl implements ISysUserService +{ + private static final Logger log = LoggerFactory.getLogger(SysUserServiceImpl.class); @Autowired - private SysUserMapper sysUserMapper; - - @Autowired - private SysMenuMapper sysMenuMapper; + private SysUserMapper userMapper; +// @Autowired +// private SysRoleMapper roleMapper; +// +// @Autowired +// private SysPostMapper postMapper; +// +// @Autowired +// private SysUserRoleMapper userRoleMapper; +// +// @Autowired +// private SysUserPostMapper userPostMapper; +// +// @Autowired +// private ISysConfigService configService; +// +// @Autowired +// protected Validator validator; @Override - public List selectUserList(SysUser user) { - return sysUserMapper.selectUserList(user); + public SysUser selectUserByUserName(String userName) + { + return userMapper.selectUserByUserName(userName); } - + @Override + public List selectUserList(SysUser user){ + return userMapper.selectUserList(user); + } } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 938d030..61d2c2e 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -2,8 +2,8 @@ spring: datasource: type: com.alibaba.druid.pool.DruidDataSource # 如使用Druid则取消注释 driver-class-name: com.mysql.cj.jdbc.Driver -# url: 'jdbc:mysql://localhost:3306/ruoyi-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8' - url: 'jdbc:mysql://localhost:3306/kangda?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8' + url: 'jdbc:mysql://localhost:3306/ruoyi-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8' +# url: 'jdbc:mysql://localhost:3306/kangda?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8' username: root password: root # Druid 连接池专属配置区 (注意缩进对齐) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 307b164..545783a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -3,10 +3,6 @@ spring: name: testspring -# mvc: -# # ????, ?????swagger??????? -# pathmatch: -# matching-strategy: ANT_PATH_MATCHER # ??ant???? profiles: active: dev @@ -15,6 +11,14 @@ server: port: 9992 +app: + jwt: +# secret: "bXktc3VwZXItc2VjdXJlLWp3dC1zZWNyZXQtc2hvdWxkLWJlLXN1ZmZpY2llbnRseS1sb25nLTEyMzQ=" # Base64编码密钥 + secret : "SGxb0sdMEEierPA99jMpG2F8Mqd+tqAQhpfZNo646CEI6pARnPSj4QzjVNzU2qAx+B9Q6nzwCgMI4tQkk/Pkfg==" + expiration-ms: 3600000 # 1小时(单位毫秒) + issuer: "my-application" + + mybatis: diff --git a/src/main/resources/mapper/SysUserMapper.xml b/src/main/resources/mapper/SysUserMapper.xml index 2dc1975..0bd94de 100644 --- a/src/main/resources/mapper/SysUserMapper.xml +++ b/src/main/resources/mapper/SysUserMapper.xml @@ -1,100 +1,231 @@ - - + + - - - - - - - - - - - - - - - - delete from sys_user - where user_id = #{userId,jdbcType=BIGINT} - - - - insert into sys_user (user_id, username, password, - name, salt, email, - mobile, status, create_user_id, - create_time) - values (#{userId,jdbcType=BIGINT}, #{username,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, - #{name,jdbcType=VARCHAR}, #{salt,jdbcType=VARCHAR}, #{email,jdbcType=VARCHAR}, - #{mobile,jdbcType=VARCHAR}, #{status,jdbcType=TINYINT}, #{createUserId,jdbcType=BIGINT}, - #{createTime,jdbcType=TIMESTAMP}) - - - - update sys_user - set username = #{username,jdbcType=VARCHAR}, - password = #{password,jdbcType=VARCHAR}, - name = #{name,jdbcType=VARCHAR}, - salt = #{salt,jdbcType=VARCHAR}, - email = #{email,jdbcType=VARCHAR}, - mobile = #{mobile,jdbcType=VARCHAR}, - status = #{status,jdbcType=TINYINT}, - create_user_id = #{createUserId,jdbcType=BIGINT}, - create_time = #{createTime,jdbcType=TIMESTAMP} - where user_id = #{userId,jdbcType=BIGINT} - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, + d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status, + r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status + from sys_user u + left join sys_dept d on u.dept_id = d.dept_id + left join sys_user_role ur on u.user_id = ur.user_id + left join sys_role r on r.role_id = ur.role_id + + + + select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, + from sys_user u + + + + + + + + + + + + + + + + + + + + + + insert into sys_user( + user_id, + dept_id, + user_name, + nick_name, + email, + avatar, + phonenumber, + sex, + password, + status, + create_by, + remark, + create_time + )values( + #{userId}, + #{deptId}, + #{userName}, + #{nickName}, + #{email}, + #{avatar}, + #{phonenumber}, + #{sex}, + #{password}, + #{status}, + #{createBy}, + #{remark}, + sysdate() + ) + + + + update sys_user + + dept_id = #{deptId}, + user_name = #{userName}, + nick_name = #{nickName}, + email = #{email}, + phonenumber = #{phonenumber}, + sex = #{sex}, + avatar = #{avatar}, + password = #{password}, + status = #{status}, + login_ip = #{loginIp}, + login_date = #{loginDate}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = sysdate() + + where user_id = #{userId} + + + + update sys_user set status = #{status} where user_id = #{userId} + + + + update sys_user set avatar = #{avatar} where user_name = #{userName} + + + + update sys_user set password = #{password} where user_name = #{userName} + + + + update sys_user set del_flag = '2' where user_id = #{userId} + + + + update sys_user set del_flag = '2' where user_id in + + #{userId} + + + + \ No newline at end of file