From faa7411ecc5ed2e76ea6f0afd06d9312a0e346d3 Mon Sep 17 00:00:00 2001
From: wangys <3401275564@qq.com>
Date: Sun, 28 Sep 2025 15:50:59 +0800
Subject: [PATCH] service auth
---
schoolNewsServ/auth/pom.xml | 124 +++++++++-
.../auth/src/main/java/org/xyzh/Main.java | 7 -
.../org/xyzh/auth/config/AuthProperties.java | 153 ++++++++++++
.../org/xyzh/auth/config/SecurityConfig.java | 74 ++++++
.../xyzh/auth/controller/AuthController.java | 100 ++++++++
.../org/xyzh/auth/domain/UserPrincipal.java | 98 ++++++++
.../auth/filter/JwtAuthenticationFilter.java | 102 ++++++++
.../xyzh/auth/service/LoginLogService.java | 69 ++++++
.../xyzh/auth/service/LoginServiceImpl.java | 228 ++++++++++++++++++
.../auth/service/UserDetailsServiceImpl.java | 95 ++++++++
.../org/xyzh/auth/strategy/LoginStrategy.java | 50 ++++
.../auth/strategy/LoginStrategyFactory.java | 51 ++++
.../strategy/impl/EmailLoginStrategy.java | 49 ++++
.../strategy/impl/PhoneLoginStrategy.java | 53 ++++
.../strategy/impl/UsernameLoginStrategy.java | 49 ++++
.../strategy/impl/WechatLoginStrategy.java | 47 ++++
.../java/org/xyzh/auth/util/JwtTokenUtil.java | 151 ++++++++++++
.../auth/src/main/resources/application.yml | 63 +++++
.../auth/src/main/resources/log4j2-spring.xml | 107 ++++++++
19 files changed, 1662 insertions(+), 8 deletions(-)
delete mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/Main.java
create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/config/AuthProperties.java
create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/config/SecurityConfig.java
create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/controller/AuthController.java
create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/domain/UserPrincipal.java
create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/filter/JwtAuthenticationFilter.java
create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/service/LoginLogService.java
create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/service/LoginServiceImpl.java
create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/service/UserDetailsServiceImpl.java
create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/LoginStrategy.java
create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/LoginStrategyFactory.java
create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/EmailLoginStrategy.java
create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/PhoneLoginStrategy.java
create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/UsernameLoginStrategy.java
create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/WechatLoginStrategy.java
create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/util/JwtTokenUtil.java
create mode 100644 schoolNewsServ/auth/src/main/resources/application.yml
create mode 100644 schoolNewsServ/auth/src/main/resources/log4j2-spring.xml
diff --git a/schoolNewsServ/auth/pom.xml b/schoolNewsServ/auth/pom.xml
index 9361f66..6358857 100644
--- a/schoolNewsServ/auth/pom.xml
+++ b/schoolNewsServ/auth/pom.xml
@@ -12,10 +12,132 @@
org.xyzh
auth
${school-news.version}
+ jar
21
21
-
+
+
+
+
+ org.xyzh
+ common-core
+ ${school-news.version}
+
+
+ org.xyzh
+ common-dto
+ ${school-news.version}
+
+
+ org.xyzh
+ common-exception
+ ${school-news.version}
+
+
+ org.xyzh
+ common-redis
+ ${school-news.version}
+
+
+ org.xyzh
+ common-util
+ ${school-news.version}
+
+
+ org.xyzh
+ api-auth
+ ${school-news.version}
+
+
+
+ org.xyzh
+ api-system
+ ${school-news.version}
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-logging
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+
+ org.springframework.boot
+ spring-boot-starter-logging
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-log4j2
+
+
+
+
+ io.jsonwebtoken
+ jjwt-api
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ runtime
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ runtime
+
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jdbc
+
+
+
+ org.springframework.boot
+ spring-boot-starter-logging
+
+
+
+
+ com.mysql
+ mysql-connector-j
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
\ No newline at end of file
diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/Main.java b/schoolNewsServ/auth/src/main/java/org/xyzh/Main.java
deleted file mode 100644
index f660b7a..0000000
--- a/schoolNewsServ/auth/src/main/java/org/xyzh/Main.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package org.xyzh;
-
-public class Main {
- public static void main(String[] args) {
- System.out.println("Hello world!");
- }
-}
\ No newline at end of file
diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/config/AuthProperties.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/config/AuthProperties.java
new file mode 100644
index 0000000..e1aabb1
--- /dev/null
+++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/config/AuthProperties.java
@@ -0,0 +1,153 @@
+package org.xyzh.auth.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * @description AuthProperties.java文件描述 认证配置属性
+ * @filename AuthProperties.java
+ * @author yslg
+ * @copyright xyzh
+ * @since 2025-09-28
+ */
+@Component
+@ConfigurationProperties(prefix = "school-news.auth")
+public class AuthProperties {
+
+ /**
+ * @description 免登录白名单路径
+ * @author yslg
+ * @since 2025-09-28
+ */
+ private List whiteList = new ArrayList<>();
+
+ /**
+ * @description JWT密钥
+ * @author yslg
+ * @since 2025-09-28
+ */
+ private String jwtSecret = "schoolNewsDefaultSecretKeyForJWT2025";
+
+ /**
+ * @description JWT过期时间(秒)
+ * @author yslg
+ * @since 2025-09-28
+ */
+ private Long jwtExpiration = 86400L;
+
+ /**
+ * @description 最大登录失败次数
+ * @author yslg
+ * @since 2025-09-28
+ */
+ private Integer maxLoginAttempts = 5;
+
+ /**
+ * @description 账户锁定时间(分钟)
+ * @author yslg
+ * @since 2025-09-28
+ */
+ private Integer lockoutDuration = 30;
+
+ /**
+ * @description 获取白名单
+ * @return List 白名单路径列表
+ * @author yslg
+ * @since 2025-09-28
+ */
+ public List getWhiteList() {
+ return whiteList;
+ }
+
+ /**
+ * @description 设置白名单
+ * @param whiteList 白名单路径列表
+ * @author yslg
+ * @since 2025-09-28
+ */
+ public void setWhiteList(List whiteList) {
+ this.whiteList = whiteList;
+ }
+
+ /**
+ * @description 获取JWT密钥
+ * @return String JWT密钥
+ * @author yslg
+ * @since 2025-09-28
+ */
+ public String getJwtSecret() {
+ return jwtSecret;
+ }
+
+ /**
+ * @description 设置JWT密钥
+ * @param jwtSecret JWT密钥
+ * @author yslg
+ * @since 2025-09-28
+ */
+ public void setJwtSecret(String jwtSecret) {
+ this.jwtSecret = jwtSecret;
+ }
+
+ /**
+ * @description 获取JWT过期时间
+ * @return Long JWT过期时间(秒)
+ * @author yslg
+ * @since 2025-09-28
+ */
+ public Long getJwtExpiration() {
+ return jwtExpiration;
+ }
+
+ /**
+ * @description 设置JWT过期时间
+ * @param jwtExpiration JWT过期时间(秒)
+ * @author yslg
+ * @since 2025-09-28
+ */
+ public void setJwtExpiration(Long jwtExpiration) {
+ this.jwtExpiration = jwtExpiration;
+ }
+
+ /**
+ * @description 获取最大登录失败次数
+ * @return Integer 最大登录失败次数
+ * @author yslg
+ * @since 2025-09-28
+ */
+ public Integer getMaxLoginAttempts() {
+ return maxLoginAttempts;
+ }
+
+ /**
+ * @description 设置最大登录失败次数
+ * @param maxLoginAttempts 最大登录失败次数
+ * @author yslg
+ * @since 2025-09-28
+ */
+ public void setMaxLoginAttempts(Integer maxLoginAttempts) {
+ this.maxLoginAttempts = maxLoginAttempts;
+ }
+
+ /**
+ * @description 获取账户锁定时间
+ * @return Integer 账户锁定时间(分钟)
+ * @author yslg
+ * @since 2025-09-28
+ */
+ public Integer getLockoutDuration() {
+ return lockoutDuration;
+ }
+
+ /**
+ * @description 设置账户锁定时间
+ * @param lockoutDuration 账户锁定时间(分钟)
+ * @author yslg
+ * @since 2025-09-28
+ */
+ public void setLockoutDuration(Integer lockoutDuration) {
+ this.lockoutDuration = lockoutDuration;
+ }
+}
diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/config/SecurityConfig.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/config/SecurityConfig.java
new file mode 100644
index 0000000..ff5fb95
--- /dev/null
+++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/config/SecurityConfig.java
@@ -0,0 +1,74 @@
+package org.xyzh.auth.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+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.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;
+import org.xyzh.auth.filter.JwtAuthenticationFilter;
+
+/**
+ * @description SecurityConfig.java文件描述 Spring Security配置
+ * @filename SecurityConfig.java
+ * @author yslg
+ * @copyright xyzh
+ * @since 2025-09-28
+ */
+@Configuration
+@EnableWebSecurity
+public class SecurityConfig {
+
+ @Autowired
+ private AuthProperties authProperties;
+
+ @Autowired
+ private JwtAuthenticationFilter jwtAuthenticationFilter;
+
+ /**
+ * @description 密码编码器
+ * @return PasswordEncoder 密码编码器
+ * @author yslg
+ * @since 2025-09-28
+ */
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+ /**
+ * @description 安全过滤器链配置
+ * @param http HTTP安全配置
+ * @return SecurityFilterChain 安全过滤器链
+ * @author yslg
+ * @since 2025-09-28
+ */
+ @Bean
+ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+ // 白名单路径转换为数组
+ String[] whiteListArray = authProperties.getWhiteList().toArray(new String[0]);
+
+ http
+ // 禁用CSRF
+ .csrf(csrf -> csrf.disable())
+ // 无状态session
+ .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+ // 配置授权规则
+ .authorizeHttpRequests(authz -> authz
+ .requestMatchers(whiteListArray).permitAll()
+ .requestMatchers("/auth/login", "/auth/logout", "/auth/captcha").permitAll()
+ .requestMatchers("/actuator/**").permitAll()
+ .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
+ .requestMatchers("/favicon.ico", "/error").permitAll()
+ .anyRequest().authenticated()
+ )
+ // 添加JWT过滤器
+ .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
+
+ return http.build();
+ }
+}
diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/controller/AuthController.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/controller/AuthController.java
new file mode 100644
index 0000000..de275e8
--- /dev/null
+++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/controller/AuthController.java
@@ -0,0 +1,100 @@
+package org.xyzh.auth.controller;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.xyzh.api.auth.login.LoginService;
+import org.xyzh.common.core.domain.LoginParam;
+import org.xyzh.common.core.domain.LoginDomain;
+import org.xyzh.common.core.domain.ResultDomain;
+import org.xyzh.common.utils.IDUtils;
+
+/**
+ * @description AuthController.java文件描述 认证控制器
+ * @filename AuthController.java
+ * @author yslg
+ * @copyright xyzh
+ * @since 2025-09-28
+ */
+@RestController
+@RequestMapping("/auth")
+public class AuthController {
+
+ @Autowired
+ private LoginService loginService;
+
+ /**
+ * @description 用户登录
+ * @param loginParam 登录参数
+ * @return ResultDomain 登录结果
+ * @author yslg
+ * @since 2025-09-28
+ */
+ @PostMapping("/login")
+ public ResultDomain login(@RequestBody LoginParam loginParam) {
+ return loginService.login(loginParam);
+ }
+
+ /**
+ * @description 用户退出登录
+ * @param loginDomain 登录域对象
+ * @return ResultDomain 退出结果
+ * @author yslg
+ * @since 2025-09-28
+ */
+ @PostMapping("/logout")
+ public ResultDomain logout(@RequestBody LoginDomain loginDomain) {
+ return loginService.logout(loginDomain);
+ }
+
+ /**
+ * @description 获取验证码
+ * @return ResultDomain 验证码
+ * @author yslg
+ * @since 2025-09-28
+ */
+ @GetMapping("/captcha")
+ public ResultDomain getCaptcha() {
+ // TODO: 实现验证码生成逻辑
+ ResultDomain result = new ResultDomain<>();
+
+ // 生成验证码会话ID,用于验证时匹配
+ String captchaId = IDUtils.generateID();
+ String captchaData = captchaId + ":captcha-placeholder"; // 格式: ID:验证码内容
+
+ result.success("验证码获取成功", captchaData);
+ return result;
+ }
+
+ /**
+ * @description 刷新令牌
+ * @param token 原令牌
+ * @return ResultDomain 新令牌
+ * @author yslg
+ * @since 2025-09-28
+ */
+ @PostMapping("/refresh")
+ public ResultDomain refreshToken(@RequestHeader("Authorization") String token) {
+ // TODO: 实现令牌刷新逻辑
+ ResultDomain result = new ResultDomain<>();
+
+ // 为新令牌生成唯一ID
+ String newTokenId = IDUtils.generateID();
+ String newToken = "new-token-" + newTokenId; // 临时占位符
+
+ result.success("令牌刷新成功", newToken);
+ return result;
+ }
+
+ /**
+ * @description 健康检查
+ * @return ResultDomain 健康状态
+ * @author yslg
+ * @since 2025-09-28
+ */
+ @GetMapping("/health")
+ public ResultDomain health() {
+ ResultDomain result = new ResultDomain<>();
+ result.success("认证服务运行正常", "OK");
+ return result;
+ }
+}
diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/domain/UserPrincipal.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/domain/UserPrincipal.java
new file mode 100644
index 0000000..4930fb0
--- /dev/null
+++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/domain/UserPrincipal.java
@@ -0,0 +1,98 @@
+package org.xyzh.auth.domain;
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.xyzh.common.dto.user.TbSysUser;
+import org.xyzh.common.dto.dept.TbSysDeptRole;
+import org.xyzh.common.dto.permission.TbSysPermission;
+import org.xyzh.common.core.enums.UserStatus;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * @description UserPrincipal.java文件描述 用户主体类
+ * @filename UserPrincipal.java
+ * @author yslg
+ * @copyright xyzh
+ * @since 2025-09-28
+ */
+public class UserPrincipal implements UserDetails {
+
+ private TbSysUser user;
+ private List roles;
+ private List permissions;
+
+ private UserPrincipal(TbSysUser user, List roles, List permissions) {
+ this.user = user;
+ this.roles = roles;
+ this.permissions = permissions;
+ }
+
+ public static UserPrincipal create(TbSysUser user, List roles, List permissions) {
+ return new UserPrincipal(user, roles, permissions);
+ }
+
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ // 角色权限
+ List roleAuthorities = roles.stream()
+ .map(role -> new SimpleGrantedAuthority("ROLE_" + role.getID()))
+ .collect(Collectors.toList());
+
+ // 功能权限
+ List permissionAuthorities = permissions.stream()
+ .map(permission -> new SimpleGrantedAuthority(permission.getCode()))
+ .collect(Collectors.toList());
+
+ return Stream.concat(roleAuthorities.stream(), permissionAuthorities.stream())
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public String getPassword() {
+ return user.getPassword();
+ }
+
+ @Override
+ public String getUsername() {
+ return user.getUsername();
+ }
+
+ @Override
+ public boolean isAccountNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isAccountNonLocked() {
+ UserStatus status = UserStatus.fromCode(String.valueOf(user.getStatus()));
+ return status != UserStatus.LOCKED;
+ }
+
+ @Override
+ public boolean isCredentialsNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ UserStatus status = UserStatus.fromCode(String.valueOf(user.getStatus()));
+ return status == UserStatus.NORMAL;
+ }
+
+ public TbSysUser getUser() {
+ return user;
+ }
+
+ public List getRoles() {
+ return roles;
+ }
+
+ public List getPermissions() {
+ return permissions;
+ }
+}
diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/filter/JwtAuthenticationFilter.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/filter/JwtAuthenticationFilter.java
new file mode 100644
index 0000000..edfca2a
--- /dev/null
+++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/filter/JwtAuthenticationFilter.java
@@ -0,0 +1,102 @@
+package org.xyzh.auth.filter;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+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.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.filter.OncePerRequestFilter;
+import org.xyzh.auth.service.UserDetailsServiceImpl;
+import org.xyzh.auth.util.JwtTokenUtil;
+import org.xyzh.auth.config.AuthProperties;
+
+import java.io.IOException;
+
+/**
+ * @description JwtAuthenticationFilter.java文件描述 JWT认证过滤器
+ * @filename JwtAuthenticationFilter.java
+ * @author yslg
+ * @copyright xyzh
+ * @since 2025-09-28
+ */
+@Component
+public class JwtAuthenticationFilter extends OncePerRequestFilter {
+
+ @Autowired
+ private JwtTokenUtil jwtTokenUtil;
+
+ @Autowired
+ private UserDetailsServiceImpl userDetailsService;
+
+ @Autowired
+ private AuthProperties authProperties;
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
+ FilterChain filterChain) throws ServletException, IOException {
+
+ String requestURI = request.getRequestURI();
+
+ // 检查是否在白名单中
+ if (isWhitelisted(requestURI)) {
+ filterChain.doFilter(request, response);
+ return;
+ }
+
+ String token = getTokenFromRequest(request);
+
+ if (StringUtils.hasText(token)) {
+ try {
+ String userId = jwtTokenUtil.getUserIdFromToken(token);
+
+ if (userId != null && SecurityContextHolder.getContext().getAuthentication() == null) {
+ UserDetails userDetails = userDetailsService.loadUserByUserId(userId);
+
+ if (jwtTokenUtil.validateToken(token, userId)) {
+ UsernamePasswordAuthenticationToken authentication =
+ new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
+ authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ }
+ }
+ } catch (Exception e) {
+ logger.error("JWT token validation failed: " + e.getMessage());
+ }
+ }
+
+ filterChain.doFilter(request, response);
+ }
+
+ /**
+ * @description 从请求中获取Token
+ * @param request HTTP请求
+ * @return String Token
+ * @author yslg
+ * @since 2025-09-28
+ */
+ private String getTokenFromRequest(HttpServletRequest request) {
+ String bearerToken = request.getHeader("Authorization");
+ if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
+ return bearerToken.substring(7);
+ }
+ return null;
+ }
+
+ /**
+ * @description 检查请求路径是否在白名单中
+ * @param requestURI 请求URI
+ * @return boolean 是否在白名单中
+ * @author yslg
+ * @since 2025-09-28
+ */
+ private boolean isWhitelisted(String requestURI) {
+ return authProperties.getWhiteList().stream()
+ .anyMatch(whitePath -> requestURI.matches(whitePath.replace("*", ".*")));
+ }
+}
diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/service/LoginLogService.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/service/LoginLogService.java
new file mode 100644
index 0000000..8aa1c05
--- /dev/null
+++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/service/LoginLogService.java
@@ -0,0 +1,69 @@
+package org.xyzh.auth.service;
+
+import org.springframework.stereotype.Service;
+import org.xyzh.common.dto.log.TbSysLoginLog;
+import org.xyzh.common.utils.IDUtils;
+
+/**
+ * @description LoginLogService.java文件描述 登录日志服务
+ * @filename LoginLogService.java
+ * @author yslg
+ * @copyright xyzh
+ * @since 2025-09-28
+ */
+@Service
+public class LoginLogService {
+
+ /**
+ * @description 保存登录日志
+ * @param loginLog 登录日志
+ * @author yslg
+ * @since 2025-09-28
+ */
+ public void saveLoginLog(TbSysLoginLog loginLog) {
+ // 确保登录日志有ID,如果没有则生成一个
+ if (loginLog.getID() == null || loginLog.getID().isEmpty()) {
+ loginLog.setID(IDUtils.generateID());
+ }
+
+ // TODO: 实现登录日志保存逻辑
+ // 这里应该调用数据层保存日志
+ System.out.println("保存登录日志: " + loginLog);
+ }
+
+ /**
+ * @description 根据用户ID查询登录日志
+ * @param userId 用户ID
+ * @return List 登录日志列表
+ * @author yslg
+ * @since 2025-09-28
+ */
+ public java.util.List findLoginLogsByUserId(String userId) {
+ // TODO: 实现根据用户ID查询登录日志的逻辑
+ return new java.util.ArrayList<>();
+ }
+
+ /**
+ * @description 查询登录失败次数
+ * @param userId 用户ID
+ * @param timeRange 时间范围(分钟)
+ * @return int 失败次数
+ * @author yslg
+ * @since 2025-09-28
+ */
+ public int countFailedLoginAttempts(String userId, int timeRange) {
+ // TODO: 实现查询指定时间范围内的登录失败次数
+ return 0;
+ }
+
+ /**
+ * @description 清除登录失败记录
+ * @param userId 用户ID
+ * @author yslg
+ * @since 2025-09-28
+ */
+ public void clearFailedLoginAttempts(String userId) {
+ // TODO: 实现清除登录失败记录的逻辑
+ System.out.println("清除用户 " + userId + " 的登录失败记录");
+ }
+}
diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/service/LoginServiceImpl.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/service/LoginServiceImpl.java
new file mode 100644
index 0000000..24139e5
--- /dev/null
+++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/service/LoginServiceImpl.java
@@ -0,0 +1,228 @@
+package org.xyzh.auth.service;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+import org.xyzh.api.auth.login.LoginService;
+import org.xyzh.auth.strategy.LoginStrategy;
+import org.xyzh.auth.strategy.LoginStrategyFactory;
+import org.xyzh.auth.util.JwtTokenUtil;
+import org.xyzh.common.core.domain.LoginParam;
+import org.xyzh.common.core.domain.LoginDomain;
+import org.xyzh.common.core.domain.ResultDomain;
+import org.xyzh.common.core.enums.UserStatus;
+import org.xyzh.common.dto.user.TbSysUser;
+import org.xyzh.common.dto.log.TbSysLoginLog;
+import org.xyzh.common.exception.auth.AuthException;
+import org.xyzh.common.utils.IDUtils;
+import org.xyzh.api.system.user.UserService;
+import org.xyzh.api.system.role.RoleService;
+import org.xyzh.api.system.permission.PermissionService;
+
+import java.util.Date;
+import java.util.ArrayList;
+
+/**
+ * @description LoginServiceImpl.java文件描述 登录服务实现
+ * @filename LoginServiceImpl.java
+ * @author yslg
+ * @copyright xyzh
+ * @since 2025-09-28
+ */
+@Service
+public class LoginServiceImpl implements LoginService {
+
+ @Autowired
+ private LoginStrategyFactory loginStrategyFactory;
+
+ @Autowired
+ private UserService userService;
+
+ @Autowired(required = false)
+ private RoleService roleService;
+
+ @Autowired(required = false)
+ private PermissionService permissionService;
+
+ @Autowired
+ private JwtTokenUtil jwtTokenUtil;
+
+ @Autowired
+ private LoginLogService loginLogService;
+
+ @Override
+ public ResultDomain login(LoginParam loginParam) {
+ ResultDomain result = new ResultDomain<>();
+
+ try {
+ // 自动检测登录类型
+ String loginType = detectLoginType(loginParam);
+ loginParam.setLoginType(loginType);
+
+ // 获取对应的登录策略
+ LoginStrategy strategy = loginStrategyFactory.getStrategy(loginType);
+
+ // 验证登录参数
+ if (!strategy.validate(loginParam)) {
+ result.fail("登录参数不正确");
+ return result;
+ }
+
+ // 查找用户
+ TbSysUser user = strategy.findUser(loginParam);
+ if (user == null) {
+ result.fail("用户不存在");
+ logLoginAttempt(loginParam, null, false, "用户不存在");
+ return result;
+ }
+
+ // 检查用户状态
+ UserStatus userStatus = UserStatus.fromCode(String.valueOf(user.getStatus()));
+ if (userStatus != UserStatus.NORMAL) {
+ result.fail("用户状态异常: " + userStatus.getName());
+ logLoginAttempt(loginParam, user, false, "用户状态异常: " + userStatus.getName());
+ return result;
+ }
+
+ // 验证密码
+ if (!strategy.verifyPassword(loginParam.getPassword(), user.getPassword())) {
+ result.fail("密码错误");
+ logLoginAttempt(loginParam, user, false, "密码错误");
+ return result;
+ }
+
+ // 构建登录域对象
+ LoginDomain loginDomain = buildLoginDomain(user, loginType);
+
+ // 生成JWT令牌
+ String token = jwtTokenUtil.generateToken(loginDomain);
+
+ // 记录成功登录日志
+ logLoginAttempt(loginParam, user, true, "登录成功");
+
+ result.success("登录成功", (String)null);
+ result.setData(token);
+
+ } catch (AuthException e) {
+ result.fail(e.getMessage());
+ } catch (Exception e) {
+ result.fail("登录失败: " + e.getMessage());
+ }
+
+ return result;
+ }
+
+ @Override
+ public ResultDomain logout(LoginDomain loginDomain) {
+ ResultDomain result = new ResultDomain<>();
+
+ try {
+ // TODO: 将token加入黑名单或从Redis中删除
+ // 这里可以实现token黑名单机制
+
+ result.success("退出登录成功", (String)null);
+ } catch (Exception e) {
+ result.fail("退出登录失败: " + e.getMessage());
+ }
+
+ return result;
+ }
+
+ /**
+ * @description 自动检测登录类型
+ * @param loginParam 登录参数
+ * @return String 登录类型
+ * @author yslg
+ * @since 2025-09-28
+ */
+ private String detectLoginType(LoginParam loginParam) {
+ if (StringUtils.hasText(loginParam.getLoginType())) {
+ return loginParam.getLoginType();
+ }
+
+ if (StringUtils.hasText(loginParam.getEmail())) {
+ return "email";
+ }
+ if (StringUtils.hasText(loginParam.getUsername())) {
+ return "username";
+ }
+ if (StringUtils.hasText(loginParam.getPhone())) {
+ return "phone";
+ }
+ if (StringUtils.hasText(loginParam.getWechatID())) {
+ return "wechat";
+ }
+
+ throw new AuthException("INVALID_LOGIN_TYPE", "无法确定登录类型");
+ }
+
+ /**
+ * @description 构建登录域对象
+ * @param user 用户对象
+ * @param loginType 登录类型
+ * @return LoginDomain 登录域对象
+ * @author yslg
+ * @since 2025-09-28
+ */
+ private LoginDomain buildLoginDomain(TbSysUser user, String loginType) {
+ LoginDomain loginDomain = new LoginDomain();
+ loginDomain.setUser(user);
+ loginDomain.setLoginType(loginType);
+ loginDomain.setLoginTime(new Date());
+
+ // 获取用户角色和权限(如果服务可用)
+ if (roleService != null) {
+ try {
+ // TODO: 需要在RoleService中实现findRolesByUserId方法
+ // loginDomain.setRoles(roleService.findRolesByUserId(user.getID()));
+ loginDomain.setRoles(new ArrayList<>());
+ } catch (Exception e) {
+ loginDomain.setRoles(new ArrayList<>());
+ }
+ } else {
+ loginDomain.setRoles(new ArrayList<>());
+ }
+
+ if (permissionService != null) {
+ try {
+ // TODO: 需要在PermissionService中实现findPermissionsByUserId方法
+ // loginDomain.setPermissions(permissionService.findPermissionsByUserId(user.getID()));
+ loginDomain.setPermissions(new ArrayList<>());
+ } catch (Exception e) {
+ loginDomain.setPermissions(new ArrayList<>());
+ }
+ } else {
+ loginDomain.setPermissions(new ArrayList<>());
+ }
+
+ return loginDomain;
+ }
+
+ /**
+ * @description 记录登录日志
+ * @param loginParam 登录参数
+ * @param user 用户对象
+ * @param success 是否成功
+ * @param message 消息
+ * @author yslg
+ * @since 2025-09-28
+ */
+ private void logLoginAttempt(LoginParam loginParam, TbSysUser user, boolean success, String message) {
+ TbSysLoginLog loginLog = new TbSysLoginLog();
+
+ // 使用IDUtils生成登录日志ID
+ loginLog.setID(IDUtils.generateID());
+
+ if (user != null) {
+ loginLog.setUserID(user.getID());
+ loginLog.setUsername(user.getUsername());
+ }
+ // 注意:实际生产中不应记录密码
+ // loginLog.setPassword(loginParam.getPassword());
+ loginLog.setStatus(success ? 1 : 0);
+ loginLog.setMessage(message);
+ loginLog.setLoginTime(new Date().toString());
+
+ loginLogService.saveLoginLog(loginLog);
+ }
+}
diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/service/UserDetailsServiceImpl.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/service/UserDetailsServiceImpl.java
new file mode 100644
index 0000000..45d88f2
--- /dev/null
+++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/service/UserDetailsServiceImpl.java
@@ -0,0 +1,95 @@
+package org.xyzh.auth.service;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+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 org.xyzh.auth.domain.UserPrincipal;
+import org.xyzh.common.dto.user.TbSysUser;
+import org.xyzh.common.dto.dept.TbSysDeptRole;
+import org.xyzh.common.dto.permission.TbSysPermission;
+import org.xyzh.api.system.user.UserService;
+import org.xyzh.api.system.role.RoleService;
+import org.xyzh.api.system.permission.PermissionService;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * @description UserDetailsServiceImpl.java文件描述 用户详情服务实现
+ * @filename UserDetailsServiceImpl.java
+ * @author yslg
+ * @copyright xyzh
+ * @since 2025-09-28
+ */
+@Service
+public class UserDetailsServiceImpl implements UserDetailsService {
+
+ private static final Logger logger = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
+
+ @Autowired
+ private UserService userService;
+
+ @Autowired(required = false)
+ private RoleService roleService;
+
+ @Autowired(required = false)
+ private PermissionService permissionService;
+
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+ TbSysUser filter = new TbSysUser();
+ filter.setUsername(username);
+ TbSysUser user = userService.find(filter);
+
+ if (user == null) {
+ throw new UsernameNotFoundException("用户不存在: " + username);
+ }
+
+ return loadUserByUserId(user.getID());
+ }
+
+ /**
+ * @description 根据用户ID加载用户详情
+ * @param userId 用户ID
+ * @return UserDetails 用户详情
+ * @author yslg
+ * @since 2025-09-28
+ */
+ public UserDetails loadUserByUserId(String userId) {
+ TbSysUser filter = new TbSysUser();
+ filter.setID(userId);
+ TbSysUser user = userService.find(filter);
+
+ if (user == null) {
+ throw new UsernameNotFoundException("用户不存在: " + userId);
+ }
+
+ // 获取用户角色(如果角色服务可用)
+ List roles = new ArrayList<>();
+ if (roleService != null) {
+ try {
+ // TODO: 需要在RoleService中实现findRolesByUserId方法
+ // roles = roleService.findRolesByUserId(userId);
+ } catch (Exception e) {
+ logger.warn("无法获取用户角色: " + e.getMessage());
+ }
+ }
+
+ // 获取用户权限(如果权限服务可用)
+ List permissions = new ArrayList<>();
+ if (permissionService != null) {
+ try {
+ // TODO: 需要在PermissionService中实现findPermissionsByUserId方法
+ // permissions = permissionService.findPermissionsByUserId(userId);
+ } catch (Exception e) {
+ logger.warn("无法获取用户权限: " + e.getMessage());
+ }
+ }
+
+ return UserPrincipal.create(user, roles, permissions);
+ }
+}
diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/LoginStrategy.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/LoginStrategy.java
new file mode 100644
index 0000000..06a4979
--- /dev/null
+++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/LoginStrategy.java
@@ -0,0 +1,50 @@
+package org.xyzh.auth.strategy;
+
+import org.xyzh.common.core.domain.LoginParam;
+import org.xyzh.common.dto.user.TbSysUser;
+
+/**
+ * @description LoginStrategy.java文件描述 登录策略接口
+ * @filename LoginStrategy.java
+ * @author yslg
+ * @copyright xyzh
+ * @since 2025-09-28
+ */
+public interface LoginStrategy {
+
+ /**
+ * @description 支持的登录类型
+ * @return String 登录类型
+ * @author yslg
+ * @since 2025-09-28
+ */
+ String getLoginType();
+
+ /**
+ * @description 验证登录参数
+ * @param loginParam 登录参数
+ * @return boolean 是否有效
+ * @author yslg
+ * @since 2025-09-28
+ */
+ boolean validate(LoginParam loginParam);
+
+ /**
+ * @description 根据登录参数查找用户
+ * @param loginParam 登录参数
+ * @return TbSysUser 用户对象
+ * @author yslg
+ * @since 2025-09-28
+ */
+ TbSysUser findUser(LoginParam loginParam);
+
+ /**
+ * @description 验证密码
+ * @param inputPassword 输入密码
+ * @param storedPassword 存储密码
+ * @return boolean 是否匹配
+ * @author yslg
+ * @since 2025-09-28
+ */
+ boolean verifyPassword(String inputPassword, String storedPassword);
+}
\ No newline at end of file
diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/LoginStrategyFactory.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/LoginStrategyFactory.java
new file mode 100644
index 0000000..2f578d5
--- /dev/null
+++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/LoginStrategyFactory.java
@@ -0,0 +1,51 @@
+package org.xyzh.auth.strategy;
+
+import org.springframework.stereotype.Component;
+import org.xyzh.common.exception.auth.AuthException;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * @description LoginStrategyFactory.java文件描述 登录策略工厂
+ * @filename LoginStrategyFactory.java
+ * @author yslg
+ * @copyright xyzh
+ * @since 2025-09-28
+ */
+@Component
+public class LoginStrategyFactory {
+
+ private final Map strategies;
+
+ public LoginStrategyFactory(List loginStrategies) {
+ this.strategies = loginStrategies.stream()
+ .collect(Collectors.toMap(LoginStrategy::getLoginType, Function.identity()));
+ }
+
+ /**
+ * @description 获取登录策略
+ * @param loginType 登录类型
+ * @return LoginStrategy 登录策略
+ * @author yslg
+ * @since 2025-09-28
+ */
+ public LoginStrategy getStrategy(String loginType) {
+ LoginStrategy strategy = strategies.get(loginType);
+ if (strategy == null) {
+ throw new AuthException("UNSUPPORTED_LOGIN_TYPE", "不支持的登录类型: " + loginType);
+ }
+ return strategy;
+ }
+
+ /**
+ * @description 获取所有支持的登录类型
+ * @return Set 登录类型集合
+ * @author yslg
+ * @since 2025-09-28
+ */
+ public java.util.Set getSupportedLoginTypes() {
+ return strategies.keySet();
+ }
+}
\ No newline at end of file
diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/EmailLoginStrategy.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/EmailLoginStrategy.java
new file mode 100644
index 0000000..acad866
--- /dev/null
+++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/EmailLoginStrategy.java
@@ -0,0 +1,49 @@
+package org.xyzh.auth.strategy.impl;
+
+import org.springframework.stereotype.Component;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.xyzh.auth.strategy.LoginStrategy;
+import org.xyzh.common.core.domain.LoginParam;
+import org.xyzh.common.dto.user.TbSysUser;
+import org.xyzh.api.system.user.UserService;
+
+/**
+ * @description EmailLoginStrategy.java文件描述 邮箱登录策略
+ * @filename EmailLoginStrategy.java
+ * @author yslg
+ * @copyright xyzh
+ * @since 2025-09-28
+ */
+@Component
+public class EmailLoginStrategy implements LoginStrategy {
+
+ @Autowired
+ private UserService userService;
+
+ @Autowired
+ private PasswordEncoder passwordEncoder;
+
+ @Override
+ public String getLoginType() {
+ return "email";
+ }
+
+ @Override
+ public boolean validate(LoginParam loginParam) {
+ return loginParam.getEmail() != null && !loginParam.getEmail().trim().isEmpty()
+ && loginParam.getPassword() != null && !loginParam.getPassword().trim().isEmpty();
+ }
+
+ @Override
+ public TbSysUser findUser(LoginParam loginParam) {
+ TbSysUser filter = new TbSysUser();
+ filter.setEmail(loginParam.getEmail());
+ return userService.find(filter);
+ }
+
+ @Override
+ public boolean verifyPassword(String inputPassword, String storedPassword) {
+ return passwordEncoder.matches(inputPassword, storedPassword);
+ }
+}
\ No newline at end of file
diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/PhoneLoginStrategy.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/PhoneLoginStrategy.java
new file mode 100644
index 0000000..fd2c1d2
--- /dev/null
+++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/PhoneLoginStrategy.java
@@ -0,0 +1,53 @@
+package org.xyzh.auth.strategy.impl;
+
+import org.springframework.stereotype.Component;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.xyzh.auth.strategy.LoginStrategy;
+import org.xyzh.common.core.domain.LoginParam;
+import org.xyzh.common.dto.user.TbSysUser;
+import org.xyzh.api.system.user.UserService;
+
+/**
+ * @description PhoneLoginStrategy.java文件描述 手机号登录策略
+ * @filename PhoneLoginStrategy.java
+ * @author yslg
+ * @copyright xyzh
+ * @since 2025-09-28
+ */
+@Component
+public class PhoneLoginStrategy implements LoginStrategy {
+
+ @Autowired
+ private UserService userService;
+
+ @Autowired
+ private PasswordEncoder passwordEncoder;
+
+ @Override
+ public String getLoginType() {
+ return "phone";
+ }
+
+ @Override
+ public boolean validate(LoginParam loginParam) {
+ return loginParam.getPhone() != null && !loginParam.getPhone().trim().isEmpty()
+ && (loginParam.getPassword() != null || loginParam.getCaptcha() != null);
+ }
+
+ @Override
+ public TbSysUser findUser(LoginParam loginParam) {
+ TbSysUser filter = new TbSysUser();
+ filter.setPhone(loginParam.getPhone());
+ return userService.find(filter);
+ }
+
+ @Override
+ public boolean verifyPassword(String inputPassword, String storedPassword) {
+ // 手机号登录可能使用验证码,如果有验证码则跳过密码验证
+ if (inputPassword == null) {
+ return true; // 假设验证码已经在其他地方验证过了
+ }
+ return passwordEncoder.matches(inputPassword, storedPassword);
+ }
+}
\ No newline at end of file
diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/UsernameLoginStrategy.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/UsernameLoginStrategy.java
new file mode 100644
index 0000000..3f1d2fb
--- /dev/null
+++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/UsernameLoginStrategy.java
@@ -0,0 +1,49 @@
+package org.xyzh.auth.strategy.impl;
+
+import org.springframework.stereotype.Component;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.xyzh.auth.strategy.LoginStrategy;
+import org.xyzh.common.core.domain.LoginParam;
+import org.xyzh.common.dto.user.TbSysUser;
+import org.xyzh.api.system.user.UserService;
+
+/**
+ * @description UsernameLoginStrategy.java文件描述 用户名登录策略
+ * @filename UsernameLoginStrategy.java
+ * @author yslg
+ * @copyright xyzh
+ * @since 2025-09-28
+ */
+@Component
+public class UsernameLoginStrategy implements LoginStrategy {
+
+ @Autowired
+ private UserService userService;
+
+ @Autowired
+ private PasswordEncoder passwordEncoder;
+
+ @Override
+ public String getLoginType() {
+ return "username";
+ }
+
+ @Override
+ public boolean validate(LoginParam loginParam) {
+ return loginParam.getUsername() != null && !loginParam.getUsername().trim().isEmpty()
+ && loginParam.getPassword() != null && !loginParam.getPassword().trim().isEmpty();
+ }
+
+ @Override
+ public TbSysUser findUser(LoginParam loginParam) {
+ TbSysUser filter = new TbSysUser();
+ filter.setUsername(loginParam.getUsername());
+ return userService.find(filter);
+ }
+
+ @Override
+ public boolean verifyPassword(String inputPassword, String storedPassword) {
+ return passwordEncoder.matches(inputPassword, storedPassword);
+ }
+}
\ No newline at end of file
diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/WechatLoginStrategy.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/WechatLoginStrategy.java
new file mode 100644
index 0000000..6a46f5c
--- /dev/null
+++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/WechatLoginStrategy.java
@@ -0,0 +1,47 @@
+package org.xyzh.auth.strategy.impl;
+
+import org.springframework.stereotype.Component;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.xyzh.auth.strategy.LoginStrategy;
+import org.xyzh.common.core.domain.LoginParam;
+import org.xyzh.common.dto.user.TbSysUser;
+import org.xyzh.api.system.user.UserService;
+
+/**
+ * @description WechatLoginStrategy.java文件描述 微信登录策略
+ * @filename WechatLoginStrategy.java
+ * @author yslg
+ * @copyright xyzh
+ * @since 2025-09-28
+ */
+@Component
+public class WechatLoginStrategy implements LoginStrategy {
+
+ @Autowired
+ private UserService userService;
+
+
+ @Override
+ public String getLoginType() {
+ return "wechat";
+ }
+
+ @Override
+ public boolean validate(LoginParam loginParam) {
+ return loginParam.getWechatID() != null && !loginParam.getWechatID().trim().isEmpty();
+ }
+
+ @Override
+ public TbSysUser findUser(LoginParam loginParam) {
+ TbSysUser filter = new TbSysUser();
+ filter.setWechatID(loginParam.getWechatID());
+ return userService.find(filter);
+ }
+
+ @Override
+ public boolean verifyPassword(String inputPassword, String storedPassword) {
+ // 微信登录通常不需要密码验证,通过微信授权码验证
+ // 这里可以添加微信授权验证逻辑
+ return true;
+ }
+}
diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/util/JwtTokenUtil.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/util/JwtTokenUtil.java
new file mode 100644
index 0000000..2eabe8a
--- /dev/null
+++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/util/JwtTokenUtil.java
@@ -0,0 +1,151 @@
+package org.xyzh.auth.util;
+
+import io.jsonwebtoken.*;
+import io.jsonwebtoken.security.Keys;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.xyzh.common.core.domain.LoginDomain;
+import org.xyzh.common.dto.user.TbSysUser;
+import org.xyzh.common.utils.IDUtils;
+
+import javax.crypto.SecretKey;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @description JwtTokenUtil.java文件描述 JWT工具类
+ * @filename JwtTokenUtil.java
+ * @author yslg
+ * @copyright xyzh
+ * @since 2025-09-28
+ */
+@Component
+public class JwtTokenUtil {
+
+ @Value("${school-news.auth.jwt-secret:schoolNewsDefaultSecretKeyForJWT2025}")
+ private String secret;
+
+ @Value("${school-news.auth.jwt-expiration:86400}")
+ private Long expiration;
+
+ private SecretKey getSigningKey() {
+ return Keys.hmacShaKeyFor(secret.getBytes());
+ }
+
+ /**
+ * @description 生成JWT令牌
+ * @param loginDomain 登录域对象
+ * @return String JWT令牌
+ * @author yslg
+ * @since 2025-09-28
+ */
+ public String generateToken(LoginDomain loginDomain) {
+ Map claims = new HashMap<>();
+ TbSysUser user = loginDomain.getUser();
+
+ claims.put("userId", user.getID());
+ claims.put("username", user.getUsername());
+ claims.put("email", user.getEmail());
+ claims.put("loginType", loginDomain.getLoginType());
+ claims.put("ipAddress", loginDomain.getIpAddress());
+
+ return Jwts.builder()
+ .setClaims(claims)
+ .setSubject(user.getID())
+ .setId(IDUtils.generateID()) // 使用IDUtils生成JWT ID
+ .setIssuedAt(new Date())
+ .setExpiration(generateExpirationDate())
+ .signWith(getSigningKey())
+ .compact();
+ }
+
+ /**
+ * @description 从令牌中获取用户ID
+ * @param token JWT令牌
+ * @return String 用户ID
+ * @author yslg
+ * @since 2025-09-28
+ */
+ public String getUserIdFromToken(String token) {
+ return getClaimFromToken(token, Claims::getSubject);
+ }
+
+ /**
+ * @description 从令牌中获取过期时间
+ * @param token JWT令牌
+ * @return Date 过期时间
+ * @author yslg
+ * @since 2025-09-28
+ */
+ public Date getExpirationDateFromToken(String token) {
+ return getClaimFromToken(token, Claims::getExpiration);
+ }
+
+ /**
+ * @description 从令牌中获取指定信息
+ * @param token JWT令牌
+ * @param claimsResolver 信息解析器
+ * @return T 指定信息
+ * @author yslg
+ * @since 2025-09-28
+ */
+ public T getClaimFromToken(String token, java.util.function.Function claimsResolver) {
+ final Claims claims = getAllClaimsFromToken(token);
+ return claimsResolver.apply(claims);
+ }
+
+ /**
+ * @description 从令牌中获取所有信息
+ * @param token JWT令牌
+ * @return Claims 所有信息
+ * @author yslg
+ * @since 2025-09-28
+ */
+ public Claims getAllClaimsFromToken(String token) {
+ return Jwts.parserBuilder()
+ .setSigningKey(getSigningKey())
+ .build()
+ .parseClaimsJws(token)
+ .getBody();
+ }
+
+ /**
+ * @description 验证令牌
+ * @param token JWT令牌
+ * @param userId 用户ID
+ * @return boolean 是否有效
+ * @author yslg
+ * @since 2025-09-28
+ */
+ public boolean validateToken(String token, String userId) {
+ try {
+ final String tokenUserId = getUserIdFromToken(token);
+ return (userId.equals(tokenUserId) && !isTokenExpired(token));
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /**
+ * @description 检查令牌是否过期
+ * @param token JWT令牌
+ * @return boolean 是否过期
+ * @author yslg
+ * @since 2025-09-28
+ */
+ public boolean isTokenExpired(String token) {
+ final Date expiration = getExpirationDateFromToken(token);
+ return expiration.before(new Date());
+ }
+
+ /**
+ * @description 生成过期时间
+ * @return Date 过期时间
+ * @author yslg
+ * @since 2025-09-28
+ */
+ private Date generateExpirationDate() {
+ return new Date(System.currentTimeMillis() + expiration * 1000);
+ }
+}
diff --git a/schoolNewsServ/auth/src/main/resources/application.yml b/schoolNewsServ/auth/src/main/resources/application.yml
new file mode 100644
index 0000000..6687dc0
--- /dev/null
+++ b/schoolNewsServ/auth/src/main/resources/application.yml
@@ -0,0 +1,63 @@
+server:
+ port: 8081
+
+spring:
+ application:
+ name: school-news-auth
+
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/school_news?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
+ username: root
+ password: root
+ hikari:
+ maximum-pool-size: 20
+ minimum-idle: 5
+
+# 认证配置
+school-news:
+ auth:
+ # JWT配置
+ jwt-secret: schoolNewsSecretKeyForJWT2025SecureEnough
+ jwt-expiration: 86400 # 24小时
+
+ # 安全配置
+ max-login-attempts: 5
+ lockout-duration: 30 # 锁定30分钟
+
+ # 免登录白名单
+ white-list:
+ - "/auth/login"
+ - "/auth/logout"
+ - "/auth/captcha"
+ - "/auth/health"
+ - "/actuator/**"
+ - "/swagger-ui/**"
+ - "/v3/api-docs/**"
+ - "/favicon.ico"
+ - "/error"
+ - "/public/**"
+ - "/static/**"
+
+# 日志配置(使用log4j2配置文件)
+logging:
+ config: classpath:log4j2-spring.xml
+
+# 管理端点配置
+management:
+ endpoints:
+ web:
+ exposure:
+ include: health,info,metrics
+ endpoint:
+ health:
+ show-details: when-authorized
+
+# 文档配置
+springdoc:
+ api-docs:
+ path: /v3/api-docs
+ swagger-ui:
+ path: /swagger-ui.html
+ tags-sorter: alpha
+ operations-sorter: alpha
diff --git a/schoolNewsServ/auth/src/main/resources/log4j2-spring.xml b/schoolNewsServ/auth/src/main/resources/log4j2-spring.xml
new file mode 100644
index 0000000..44801e2
--- /dev/null
+++ b/schoolNewsServ/auth/src/main/resources/log4j2-spring.xml
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+