diff --git a/schoolNewsServ/.bin/mysql/sql/createTableResource.sql b/schoolNewsServ/.bin/mysql/sql/createTableResource.sql index 1aa7628..55f46cd 100644 --- a/schoolNewsServ/.bin/mysql/sql/createTableResource.sql +++ b/schoolNewsServ/.bin/mysql/sql/createTableResource.sql @@ -74,8 +74,7 @@ CREATE TABLE `tb_resource_recommend` ( `deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除', PRIMARY KEY (`id`), KEY `idx_resource_id` (`resource_id`), - KEY `idx_recommend_type` (`recommend_type`), - CONSTRAINT `fk_resource_recommend_resource` FOREIGN KEY (`resource_id`) REFERENCES `tb_resource` (`resource_id`) ON DELETE CASCADE + KEY `idx_recommend_type` (`recommend_type`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='资源推荐表'; -- 标签表 diff --git a/schoolNewsServ/.bin/mysql/sql/createTableSystem.sql b/schoolNewsServ/.bin/mysql/sql/createTableSystem.sql index 4799c45..c979ff1 100644 --- a/schoolNewsServ/.bin/mysql/sql/createTableSystem.sql +++ b/schoolNewsServ/.bin/mysql/sql/createTableSystem.sql @@ -3,8 +3,8 @@ use school_news; DROP TABLE IF EXISTS `tb_sys_operation_log`; CREATE TABLE `tb_sys_operation_log` ( `id` VARCHAR(50) NOT NULL COMMENT '日志ID', - `user_id` VARCHAR(50) NOT NULL COMMENT '用户ID', - `username` VARCHAR(50) NOT NULL COMMENT '用户名', + `user_id` VARCHAR(50) NULL COMMENT '用户ID', + `username` VARCHAR(50) NULL COMMENT '用户名', `module` VARCHAR(100) DEFAULT NULL COMMENT '操作模块', `operation` VARCHAR(100) DEFAULT NULL COMMENT '操作类型', `method` VARCHAR(200) DEFAULT NULL COMMENT '请求方法', diff --git a/schoolNewsServ/.bin/mysql/sql/createTableUser.sql b/schoolNewsServ/.bin/mysql/sql/createTableUser.sql index 374f8bd..bcb0cf6 100644 --- a/schoolNewsServ/.bin/mysql/sql/createTableUser.sql +++ b/schoolNewsServ/.bin/mysql/sql/createTableUser.sql @@ -46,7 +46,7 @@ CREATE TABLE `tb_sys_user_info` ( DROP TABLE IF EXISTS `tb_sys_login_log`; CREATE TABLE `tb_sys_login_log` ( `id` VARCHAR(50) NOT NULL COMMENT '登录日志ID', - `user_id` VARCHAR(50) NOT NULL COMMENT '用户ID', + `user_id` VARCHAR(50) NULL COMMENT '用户ID', `username` VARCHAR(50) NOT NULL COMMENT '用户名', `ip_address` VARCHAR(45) DEFAULT NULL COMMENT 'IP地址', `ip_source` VARCHAR(100) DEFAULT NULL COMMENT 'IP来源', diff --git a/schoolNewsServ/.bin/mysql/sql/initMenuData.sql b/schoolNewsServ/.bin/mysql/sql/initMenuData.sql index cfbc7ae..3c9c797 100644 --- a/schoolNewsServ/.bin/mysql/sql/initMenuData.sql +++ b/schoolNewsServ/.bin/mysql/sql/initMenuData.sql @@ -64,26 +64,41 @@ INSERT INTO `tb_sys_permission` (id,permission_id, name, code, description, modu -- 插入角色-权限关联数据 INSERT INTO `tb_sys_role_permission` (id, role_id, permission_id, creator, create_time) VALUES -('0', 'superadmin', 'perm_default', '1', now()), -('1', 'superadmin', 'perm_system_manage', '1', now()), -('2', 'superadmin', 'perm_system_dept_manage', '1', now()), -('3', 'superadmin', 'perm_system_menu_manage', '1', now()), -('4', 'superadmin', 'perm_system_permission_manage', '1', now()), -('5', 'superadmin', 'perm_system_role_manage', '1', now()), -('6', 'superadmin', 'perm_system_user_manage', '1', now()), -('7', 'superadmin', 'perm_system_module_manage', '1', now()), -('8', 'superadmin', 'perm_news_manage', '1', now()), -('9', 'superadmin', 'perm_news_article_add', '1', now()), -('10', 'superadmin', 'perm_study_manage', '1', now()), -('10.1', 'superadmin', 'perm_achievement_manage', '1', now()), -('11', 'superadmin', 'perm_ai_manage', '1', now()), -('12', 'superadmin', 'perm_usercenter_manage', '1', now()), -('13', 'superadmin', 'perm_file_manage', '1', now()), -('14', 'freedom', 'perm_default', '1', now()), -('15', 'superadmin', 'perm_crontab_manage', '1', now()), -('16', 'superadmin', 'perm_crontab_execute', '1', now()), -('17', 'admin', 'perm_crontab_manage', '1', now()), -('18', 'admin', 'perm_crontab_execute', '1', now()); +-- 超级管理员:拥有所有权限 +('1', 'superadmin', 'perm_default', '1', now()), +('2', 'superadmin', 'perm_system_manage', '1', now()), +('3', 'superadmin', 'perm_system_dept_manage', '1', now()), +('4', 'superadmin', 'perm_system_menu_manage', '1', now()), +('5', 'superadmin', 'perm_system_permission_manage', '1', now()), +('6', 'superadmin', 'perm_system_role_manage', '1', now()), +('7', 'superadmin', 'perm_system_user_manage', '1', now()), +('8', 'superadmin', 'perm_system_module_manage', '1', now()), +('9', 'superadmin', 'perm_news_manage', '1', now()), +('10', 'superadmin', 'perm_news_article_add', '1', now()), +('11', 'superadmin', 'perm_study_manage', '1', now()), +('12', 'superadmin', 'perm_achievement_manage', '1', now()), +('13', 'superadmin', 'perm_ai_manage', '1', now()), +('14', 'superadmin', 'perm_usercenter_manage', '1', now()), +('15', 'superadmin', 'perm_file_manage', '1', now()), +('16', 'superadmin', 'perm_crontab_manage', '1', now()), +('17', 'superadmin', 'perm_crontab_execute', '1', now()), + +-- 管理员:拥有业务管理权限,但没有系统日志等系统管理权限 +('20', 'admin', 'perm_default', '1', now()), +('21', 'admin', 'perm_news_manage', '1', now()), +('22', 'admin', 'perm_news_article_add', '1', now()), +('23', 'admin', 'perm_study_manage', '1', now()), +('24', 'admin', 'perm_achievement_manage', '1', now()), +('25', 'admin', 'perm_ai_manage', '1', now()), +('26', 'admin', 'perm_usercenter_manage', '1', now()), +('27', 'admin', 'perm_file_manage', '1', now()), + +-- 自由角色:拥有用户视图相关的所有权限(前台用户权限) +('30', 'freedom', 'perm_default', '1', now()), +('31', 'freedom', 'perm_news_article_add', '1', now()), +('32', 'freedom', 'perm_ai_manage', '1', now()), +('33', 'freedom', 'perm_usercenter_manage', '1', now()), +('34', 'freedom', 'perm_file_manage', '1', now()); -- 插入前端菜单数据 INSERT INTO `tb_sys_menu` VALUES @@ -135,7 +150,7 @@ INSERT INTO `tb_sys_menu` VALUES ('6002', 'menu_admin_ai_config', 'AI配置', 'menu_admin_ai_manage', '/admin/manage/ai/config', 'admin/manage/ai/AIConfigView', NULL, 2, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0), ('6003', 'menu_admin_knowledge', '知识库管理', 'menu_admin_ai_manage', '/admin/manage/ai/knowledge', 'admin/manage/ai/KnowledgeManagementView', NULL, 3, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0), ('7000', 'menu_admin_logs_manage', '系统日志', NULL, '', '', 'admin/logs.svg', 7, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:52:53', NULL, 0), --- ('7001', 'menu_admin_system_logs', '系统日志', 'menu_admin_logs_manage', '/admin/manage/logs/system', 'admin/manage/logs/SystemLogsView', NULL, 1, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0), +('7001', 'menu_admin_system_logs', '系统日志', 'menu_admin_logs_manage', '/admin/manage/logs/system', 'admin/manage/logs/SystemLogsView', NULL, 1, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0), ('7002', 'menu_admin_login_logs', '登录日志', 'menu_admin_logs_manage', '/admin/manage/logs/login', 'admin/manage/logs/LoginLogsView', NULL, 2, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0), -- ('7003', 'menu_admin_operation_logs', '操作日志', 'menu_admin_logs_manage', '/admin/manage/logs/operation', 'admin/manage/logs/OperationLogsView', NULL, 3, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0), -- ('7004', 'menu_admin_system_config', '系统配置', 'menu_admin_logs_manage', '/admin/manage/logs/config', 'admin/manage/logs/SystemConfigView', NULL, 4, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0), diff --git a/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/service/impl/ACHAchievementServiceImpl.java b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/service/impl/ACHAchievementServiceImpl.java index 7985deb..8de4230 100644 --- a/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/service/impl/ACHAchievementServiceImpl.java +++ b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/service/impl/ACHAchievementServiceImpl.java @@ -11,6 +11,7 @@ import org.xyzh.achievement.mapper.AchievementMapper; import org.xyzh.achievement.mapper.UserAchievementMapper; import org.xyzh.achievement.mapper.UserAchievementProgressMapper; import org.xyzh.api.achievement.AchievementService; +import org.xyzh.common.core.domain.LoginDomain; import org.xyzh.common.core.domain.ResultDomain; import org.xyzh.common.core.enums.AchievementEventType; import org.xyzh.common.core.event.AchievementEvent; @@ -20,6 +21,7 @@ import org.xyzh.common.dto.user.TbSysUser; import org.xyzh.common.dto.usercenter.TbAchievement; import org.xyzh.common.dto.usercenter.TbUserAchievement; import org.xyzh.common.dto.usercenter.TbUserAchievementProgress; +import org.xyzh.common.redis.service.RedisService; import org.xyzh.common.utils.IDUtils; import org.xyzh.common.vo.AchievementVO; import org.xyzh.system.utils.LoginUtil; @@ -41,6 +43,7 @@ import java.util.stream.Collectors; public class ACHAchievementServiceImpl implements AchievementService { private static final Logger logger = LoggerFactory.getLogger(ACHAchievementServiceImpl.class); + private static final String REDIS_LOGIN_PREFIX = "login:token:"; @Autowired private AchievementMapper achievementMapper; @@ -56,6 +59,9 @@ public class ACHAchievementServiceImpl implements AchievementService { @Autowired private ResourcePermissionService resourcePermissionService; + + @Autowired + private RedisService redisService; // ==================== 成就定义管理 ==================== @@ -510,7 +516,8 @@ public class ACHAchievementServiceImpl implements AchievementService { logger.debug("处理成就事件: {}", event); // 获取该事件类型相关的所有成就 - List achievements = getAchievementsByEventType(event.getEventType()); + // 传入 userID,在异步线程中根据 userID 查询用户部门角色 + List achievements = getAchievementsByEventType(event.getEventType(), event.getUserID()); if (achievements.isEmpty()) { resultDomain.success("无相关成就", newAchievements); return resultDomain; @@ -763,12 +770,38 @@ public class ACHAchievementServiceImpl implements AchievementService { /** * 根据事件类型获取相关成就 + * @param eventType 事件类型 + * @param userID 用户ID(用于在异步线程中从Redis获取用户登录信息) */ - private List getAchievementsByEventType(AchievementEventType eventType) { + private List getAchievementsByEventType(AchievementEventType eventType, String userID) { // 获取所有成就 TbAchievement filter = new TbAchievement(); filter.setDeleted(false); - List userDeptRoles = LoginUtil.getCurrentDeptRole(); + + // 【关键优化】在异步线程中从Redis缓存获取用户的LoginDomain + // 避免数据库查询,性能更好 + List userDeptRoles = new ArrayList<>(); + + if (StringUtils.hasText(userID)) { + try { + // 从Redis获取用户登录信息 + String redisKey = REDIS_LOGIN_PREFIX + userID; + LoginDomain loginDomain = (LoginDomain) redisService.get(redisKey); + + if (loginDomain != null && loginDomain.getRoles() != null) { + userDeptRoles = loginDomain.getRoles(); + logger.debug("从Redis缓存获取用户部门角色成功,userID: {}, 角色数: {}", + userID, userDeptRoles.size()); + } else { + logger.warn("Redis缓存中未找到用户登录信息或角色为空,userID: {}", userID); + } + } catch (Exception e) { + logger.error("从Redis获取用户部门角色异常,userID: {}", userID, e); + } + } else { + logger.warn("userID为空,无法获取用户部门角色"); + } + List allAchievements = achievementMapper.selectAchievements(filter, userDeptRoles); // 筛选支持该事件类型的成就 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 index 3319936..5a24051 100644 --- a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/controller/AuthController.java +++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/controller/AuthController.java @@ -435,14 +435,13 @@ public class AuthController { return result; } - // 3. 密码加密 - String encryptedPassword = passwordEncoder.encode(password); - user.setPassword(encryptedPassword); + // 3. 设置密码(明文,Service层会加密) + user.setPassword(password); // 4. 设置用户状态为正常 user.setStatus(0); - // 5. 调用UserService注册用户 + // 5. 调用UserService注册用户(Service层会加密密码) ResultDomain registerResult = userService.registerUser(user); if (!registerResult.isSuccess()) { 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 index 4930fb0..9bb3a3b 100644 --- a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/domain/UserPrincipal.java +++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/domain/UserPrincipal.java @@ -7,9 +7,11 @@ 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 org.xyzh.common.vo.UserDeptRoleVO; import java.util.Collection; import java.util.List; +import java.util.ArrayList; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -36,6 +38,29 @@ public class UserPrincipal implements UserDetails { return new UserPrincipal(user, roles, permissions); } + /** + * @description 从LoginDomain创建UserPrincipal(用于从Redis缓存恢复认证信息) + * @param user 用户信息 + * @param userDeptRoles 用户部门角色列表(来自LoginDomain) + * @param permissions 权限列表 + * @return UserPrincipal 用户主体 + * @author yslg + * @since 2025-11-03 + */ + public static UserPrincipal createFromLoginDomain(TbSysUser user, List userDeptRoles, List permissions) { + // 将UserDeptRoleVO转换为TbSysDeptRole(简化处理,实际使用时UserDeptRoleVO已包含所需信息) + List roles = new ArrayList<>(); + if (userDeptRoles != null) { + for (UserDeptRoleVO vo : userDeptRoles) { + TbSysDeptRole role = new TbSysDeptRole(); + role.setRoleID(vo.getRoleID()); + role.setDeptID(vo.getDeptID()); + roles.add(role); + } + } + return new UserPrincipal(user, roles, permissions != null ? permissions : new ArrayList<>()); + } + @Override public Collection getAuthorities() { // 角色权限 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 index edfca2a..84a8a69 100644 --- a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/filter/JwtAuthenticationFilter.java +++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/filter/JwtAuthenticationFilter.java @@ -12,12 +12,16 @@ import org.springframework.security.web.authentication.WebAuthenticationDetailsS 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 org.xyzh.common.core.domain.LoginDomain; +import org.xyzh.common.redis.service.RedisService; import java.io.IOException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * @description JwtAuthenticationFilter.java文件描述 JWT认证过滤器 * @filename JwtAuthenticationFilter.java @@ -28,14 +32,18 @@ import java.io.IOException; @Component public class JwtAuthenticationFilter extends OncePerRequestFilter { + private static final String REDIS_LOGIN_PREFIX = "login:token:"; + + private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class); + @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired - private UserDetailsServiceImpl userDetailsService; + private AuthProperties authProperties; @Autowired - private AuthProperties authProperties; + private RedisService redisService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, @@ -56,17 +64,38 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { String userId = jwtTokenUtil.getUserIdFromToken(token); if (userId != null && SecurityContextHolder.getContext().getAuthentication() == null) { - UserDetails userDetails = userDetailsService.loadUserByUserId(userId); + // 验证token有效性 + if (!jwtTokenUtil.validateToken(token, userId)) { + logger.warn("Token验证失败,userId: {}", userId); + filterChain.doFilter(request, response); + return; + } - if (jwtTokenUtil.validateToken(token, userId)) { + // 【优化】从Redis缓存中获取LoginDomain,避免每次都查数据库 + String redisKey = REDIS_LOGIN_PREFIX + userId; + LoginDomain loginDomain = (LoginDomain) redisService.get(redisKey); + + if (loginDomain != null && loginDomain.getUser() != null) { + // 【优化】直接使用缓存中的用户信息构建UserDetails,不查数据库 + // 使用UserPrincipal从LoginDomain创建UserDetails + UserDetails userDetails = org.xyzh.auth.domain.UserPrincipal.createFromLoginDomain( + loginDomain.getUser(), + loginDomain.getRoles(), + loginDomain.getPermissions() + ); + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); + + logger.debug("用户认证成功(从缓存),userId: {}", userId); + } else { + logger.warn("Redis缓存中未找到用户登录信息,userId: {}, 可能已过期或未登录", userId); } } } catch (Exception e) { - logger.error("JWT token validation failed: " + e.getMessage()); + logger.error("JWT token validation failed: " + e.getMessage(), e); } } 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 deleted file mode 100644 index 9fa99bf..0000000 --- a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/service/UserDetailsServiceImpl.java +++ /dev/null @@ -1,103 +0,0 @@ -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); - List users = userService.getUserByFilter(filter).getDataList(); - if(users.isEmpty()) { - throw new UsernameNotFoundException("用户不存在: " + username); - } - TbSysUser user = users.get(0); - - 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); - List users = userService.getUserByFilter(filter).getDataList(); - if(users.isEmpty()) { - throw new UsernameNotFoundException("用户不存在: " + userId); - } - TbSysUser user = users.get(0); - - 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/impl/PasswordLoginStrategy.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/PasswordLoginStrategy.java index ba059e6..1c7f6b9 100644 --- a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/PasswordLoginStrategy.java +++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/PasswordLoginStrategy.java @@ -65,7 +65,8 @@ public class PasswordLoginStrategy implements LoginStrategy { }else{ filter.setUsername(loginParam.getUsername()); } - filter.setPassword(passwordEncoder.encode(loginParam.getPassword())); + // 【优化】删除无用的密码编码,SQL查询不使用password字段 + // 密码验证在 verifyCredential() 方法中进行 TbSysUser user = userService.getLoginUser(filter).getData(); if(user == null) { return null; @@ -75,7 +76,7 @@ public class PasswordLoginStrategy implements LoginStrategy { @Override public boolean verifyCredential(String inputCredential, String storedCredential) { - logger.info(passwordEncoder.encode(inputCredential)); + // 使用BCrypt的matches方法验证密码(内部会自动处理salt) return passwordEncoder.matches(inputCredential, storedCredential); } } \ No newline at end of file diff --git a/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/scheduler/SchedulerManager.java b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/scheduler/SchedulerManager.java index 38d4b7a..724d066 100644 --- a/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/scheduler/SchedulerManager.java +++ b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/scheduler/SchedulerManager.java @@ -3,7 +3,7 @@ package org.xyzh.crontab.scheduler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.support.CronTrigger; import org.springframework.stereotype.Component; @@ -29,9 +29,7 @@ public class SchedulerManager { private TaskScheduler taskScheduler; @Autowired - private ApplicationContext applicationContext; - - @Autowired + @Qualifier("crontabTaskExecutor") private TaskExecutor taskExecutor; /** diff --git a/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/scheduler/TaskExecutor.java b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/scheduler/TaskExecutor.java index e4dc9ba..abd6c59 100644 --- a/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/scheduler/TaskExecutor.java +++ b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/scheduler/TaskExecutor.java @@ -20,7 +20,7 @@ import java.util.Date; * @copyright xyzh * @since 2025-10-25 */ -@Component +@Component("crontabTaskExecutor") public class TaskExecutor { private static final Logger logger = LoggerFactory.getLogger(TaskExecutor.class); diff --git a/schoolNewsServ/system/src/main/java/org/xyzh/system/config/AsyncConfig.java b/schoolNewsServ/system/src/main/java/org/xyzh/system/config/AsyncConfig.java new file mode 100644 index 0000000..ab12606 --- /dev/null +++ b/schoolNewsServ/system/src/main/java/org/xyzh/system/config/AsyncConfig.java @@ -0,0 +1,63 @@ +package org.xyzh.system.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * @description 异步任务配置 + * @filename AsyncConfig.java + * @author AI + * @copyright xyzh + * @since 2025-11-03 + */ +@Configuration +@EnableAsync +public class AsyncConfig { + + private static final Logger logger = LoggerFactory.getLogger(AsyncConfig.class); + + /** + * @description 配置异步任务执行器(用于@Async和事件发布) + * @return Executor + * @author AI + * @since 2025-11-03 + */ + @Bean(name = "taskExecutor") + public Executor taskExecutor() { + logger.info("初始化异步任务执行器 taskExecutor"); + + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + + // 核心线程数 + executor.setCorePoolSize(5); + + // 最大线程数 + executor.setMaxPoolSize(10); + + // 队列容量 + executor.setQueueCapacity(100); + + // 线程名称前缀 + executor.setThreadNamePrefix("async-event-"); + + // 拒绝策略:由调用线程处理 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + + // 等待所有任务完成后再关闭线程池 + executor.setWaitForTasksToCompleteOnShutdown(true); + + // 等待时间(秒) + executor.setAwaitTerminationSeconds(60); + + executor.initialize(); + return executor; + } +} + diff --git a/schoolNewsServ/system/src/main/java/org/xyzh/system/department/service/impl/SysDepartmentServiceImpl.java b/schoolNewsServ/system/src/main/java/org/xyzh/system/department/service/impl/SysDepartmentServiceImpl.java index 5489bad..95a09e7 100644 --- a/schoolNewsServ/system/src/main/java/org/xyzh/system/department/service/impl/SysDepartmentServiceImpl.java +++ b/schoolNewsServ/system/src/main/java/org/xyzh/system/department/service/impl/SysDepartmentServiceImpl.java @@ -195,11 +195,17 @@ public class SysDepartmentServiceImpl implements SysDepartmentService { // 设置基础信息 department.setID(IDUtils.generateID()); - department.setDeptID(IDUtils.generateID()); + String deptId = IDUtils.generateID(); + department.setDeptID(deptId); department.setCreator(currentUser.getID()); department.setCreateTime(new Date()); department.setDeleted(false); + // 【修复】设置dept_path + String deptPath = buildDeptPath(department.getParentID(), deptId); + department.setDeptPath(deptPath); + logger.info("创建部门,设置dept_path: {}", deptPath); + // 插入数据库 int result = departmentMapper.insertDept(department); @@ -456,4 +462,37 @@ public class SysDepartmentServiceImpl implements SysDepartmentService { return resultDomain; } } + + /** + * @description 构建部门路径(优化版:只查询一次父部门路径) + * @param parentId 父部门ID + * @param deptId 当前部门ID + * @return String 部门路径 + * @author yslg + * @since 2025-11-03 + */ + private String buildDeptPath(String parentId, String deptId) { + // 如果没有父部门,说明是根部门 + if (parentId == null || parentId.trim().isEmpty()) { + return "/" + deptId + "/"; + } + + try { + // 【优化】直接通过一条简单SQL查询父部门的dept_path + String parentPath = departmentMapper.getDeptPathByDeptId(parentId); + + if (parentPath != null && !parentPath.trim().isEmpty()) { + // 父部门路径 + 当前部门ID + / + return parentPath + deptId + "/"; + } else { + // 父部门没有dept_path,使用父部门ID构建 + logger.warn("父部门dept_path为空,parentId: {},将使用父部门ID构建路径", parentId); + return "/" + parentId + "/" + deptId + "/"; + } + } catch (Exception e) { + logger.error("构建dept_path失败,parentId: {}, deptId: {}", parentId, deptId, e); + // 发生异常时,返回简单路径 + return "/" + deptId + "/"; + } + } } diff --git a/schoolNewsServ/system/src/main/java/org/xyzh/system/log/DatabaseAppender.java b/schoolNewsServ/system/src/main/java/org/xyzh/system/log/DatabaseAppender.java index 19c2dc5..7d690dc 100644 --- a/schoolNewsServ/system/src/main/java/org/xyzh/system/log/DatabaseAppender.java +++ b/schoolNewsServ/system/src/main/java/org/xyzh/system/log/DatabaseAppender.java @@ -65,6 +65,9 @@ public class DatabaseAppender extends AbstractAppender { java.lang.reflect.Method getUsernameMethod = loginUtil.getClass().getMethod("getCurrentUsername"); userId = (String) getUserIdMethod.invoke(null); username = (String) getUsernameMethod.invoke(null); + if(username == null || username.trim().isEmpty()) { + username = "system"; + } } } } catch (Exception e) { diff --git a/schoolNewsServ/system/src/main/java/org/xyzh/system/mapper/DepartmentMapper.java b/schoolNewsServ/system/src/main/java/org/xyzh/system/mapper/DepartmentMapper.java index 801b9ba..232cce2 100644 --- a/schoolNewsServ/system/src/main/java/org/xyzh/system/mapper/DepartmentMapper.java +++ b/schoolNewsServ/system/src/main/java/org/xyzh/system/mapper/DepartmentMapper.java @@ -90,4 +90,13 @@ public interface DepartmentMapper extends BaseMapper { * @since 2025-09-28 */ int deleteDept(TbSysDept department); + + /** + * @description 根据部门ID获取部门路径(不带权限过滤,仅用于构建子部门路径) + * @param deptId 部门ID + * @return String 部门路径 + * @author yslg + * @since 2025-11-03 + */ + String getDeptPathByDeptId(@Param("deptId") String deptId); } diff --git a/schoolNewsServ/system/src/main/java/org/xyzh/system/user/service/impl/SysUserServiceImpl.java b/schoolNewsServ/system/src/main/java/org/xyzh/system/user/service/impl/SysUserServiceImpl.java index 997b534..d082779 100644 --- a/schoolNewsServ/system/src/main/java/org/xyzh/system/user/service/impl/SysUserServiceImpl.java +++ b/schoolNewsServ/system/src/main/java/org/xyzh/system/user/service/impl/SysUserServiceImpl.java @@ -74,6 +74,7 @@ public class SysUserServiceImpl implements SysUserService { userInfo.setAvatar("default"); TbSysUserDeptRole userDeptRole = new TbSysUserDeptRole(); + userDeptRole.setID(IDUtils.generateID()); // 设置ID userDeptRole.setUserID(user.getID()); userDeptRole.setDeptID("default_department"); userDeptRole.setRoleID("freedom"); @@ -482,51 +483,55 @@ public class SysUserServiceImpl implements SysUserService { ResultDomain resultDomain = new ResultDomain<>(); TbSysUser currentUser = LoginUtil.getCurrentUser(); - - // 收集所有用户ID - List userIds = new ArrayList<>(); - for (TbSysUser user : userDeptRoleVO.getUsers()) { - userIds.add(user.getID()); - } - - logger.info("准备为 {} 个用户绑定部门角色", userIds.size()); - - // 批量删除所有涉及用户的旧绑定关系(物理删除,包括软删除的记录) - int deleteCount = userDeptRoleMapper.deleteUserDeptRoleByUserIds(userIds); - if (deleteCount <= 0) { - resultDomain.fail("批量删除旧绑定记录失败:没有记录被删除"); - return resultDomain; - } - - // 准备新的绑定数据 - List userDeptRoles = new ArrayList<>(); - Date now = new Date(); - for (TbSysUser user : userDeptRoleVO.getUsers()) { - for (TbSysUserDeptRole userDeptRole : userDeptRoleVO.getUserDeptRoles()) { - TbSysUserDeptRole newUserDeptRole = new TbSysUserDeptRole(); - newUserDeptRole.setID(IDUtils.generateID()); - newUserDeptRole.setUserID(user.getID()); - newUserDeptRole.setDeptID(userDeptRole.getDeptID()); - newUserDeptRole.setRoleID(userDeptRole.getRoleID()); - newUserDeptRole.setCreateTime(now); - newUserDeptRole.setCreator(currentUser.getID()); - userDeptRoles.add(newUserDeptRole); - } - } - - logger.info("准备插入 {} 条新绑定记录", userDeptRoles.size()); - - // 插入新的绑定关系 - int result = userDeptRoleMapper.bindUser(userDeptRoles); - logger.info("成功插入 {} 条绑定记录", result); - - if (result > 0) { - resultDomain.success("绑定用户部门角色成功", userDeptRoleVO); - } else { - resultDomain.fail("绑定用户部门角色失败:没有记录被插入"); - } - + // 【限制】检查是否只有一个部门-角色配置(一个用户只能有一个部门-角色) + if (userDeptRoleVO.getUserDeptRoles() == null || userDeptRoleVO.getUserDeptRoles().size() != 1) { + resultDomain.fail("每个用户只能绑定一个部门-角色,请提供且仅提供一个部门-角色配置"); + logger.warn("绑定失败:提供了 {} 个部门-角色配置,但系统限制为1个", + userDeptRoleVO.getUserDeptRoles() != null ? userDeptRoleVO.getUserDeptRoles().size() : 0); return resultDomain; + } + + // 收集所有用户ID + List userIds = new ArrayList<>(); + for (TbSysUser user : userDeptRoleVO.getUsers()) { + userIds.add(user.getID()); + } + + logger.info("准备为 {} 个用户绑定部门角色(每个用户一个部门-角色)", userIds.size()); + + // 批量删除所有涉及用户的旧绑定关系(物理删除,包括软删除的记录) + int deleteCount = userDeptRoleMapper.deleteUserDeptRoleByUserIds(userIds); + logger.info("删除了 {} 条旧绑定记录", deleteCount); + + // 准备新的绑定数据(每个用户只绑定一个部门-角色) + List userDeptRoles = new ArrayList<>(); + Date now = new Date(); + TbSysUserDeptRole templateDeptRole = userDeptRoleVO.getUserDeptRoles().get(0); + + for (TbSysUser user : userDeptRoleVO.getUsers()) { + TbSysUserDeptRole newUserDeptRole = new TbSysUserDeptRole(); + newUserDeptRole.setID(IDUtils.generateID()); + newUserDeptRole.setUserID(user.getID()); + newUserDeptRole.setDeptID(templateDeptRole.getDeptID()); + newUserDeptRole.setRoleID(templateDeptRole.getRoleID()); + newUserDeptRole.setCreateTime(now); + newUserDeptRole.setCreator(currentUser.getID()); + userDeptRoles.add(newUserDeptRole); + } + + logger.info("准备插入 {} 条新绑定记录(每个用户1条)", userDeptRoles.size()); + + // 插入新的绑定关系 + int result = userDeptRoleMapper.bindUser(userDeptRoles); + logger.info("成功插入 {} 条绑定记录", result); + + if (result > 0) { + resultDomain.success("绑定用户部门角色成功", userDeptRoleVO); + } else { + resultDomain.fail("绑定用户部门角色失败:没有记录被插入"); + } + + return resultDomain; } diff --git a/schoolNewsServ/system/src/main/resources/mapper/DepartmentMapper.xml b/schoolNewsServ/system/src/main/resources/mapper/DepartmentMapper.xml index a1a1351..99fb88a 100644 --- a/schoolNewsServ/system/src/main/resources/mapper/DepartmentMapper.xml +++ b/schoolNewsServ/system/src/main/resources/mapper/DepartmentMapper.xml @@ -111,6 +111,15 @@ + + +