serv\web- 日志
This commit is contained in:
@@ -134,10 +134,10 @@ 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),
|
||||
-- ('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),
|
||||
('8000', 'menu_admin_crontab_manage', '定时任务管理', NULL, '', '', NULL, 8, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('8001', 'menu_admin_crontab_task', '任务管理', 'menu_admin_crontab_manage', '/admin/manage/crontab/task', 'admin/manage/crontab/TaskManagementView', NULL, 1, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
('8002', 'menu_admin_crontab_log', '执行日志', 'menu_admin_crontab_manage', '/admin/manage/crontab/log', 'admin/manage/crontab/LogManagementView', NULL, 2, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0),
|
||||
|
||||
@@ -36,19 +36,21 @@ public interface AchievementMapper extends BaseMapper<TbAchievement> {
|
||||
TbAchievement selectByAchievementId(@Param("achievementId") String achievementId);
|
||||
|
||||
/**
|
||||
* @description 根据类型查询成就列表
|
||||
* @description 根据类型查询成就列表(包含权限过滤)
|
||||
* @param type 成就类型
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbAchievement> 成就列表
|
||||
*/
|
||||
List<TbAchievement> selectByType(@Param("type") Integer type);
|
||||
List<TbAchievement> selectByType(@Param("type") Integer type, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 根据类型和等级查询成就列表
|
||||
* @description 根据类型和等级查询成就列表(包含权限过滤)
|
||||
* @param type 成就类型
|
||||
* @param level 成就等级
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbAchievement> 成就列表
|
||||
*/
|
||||
List<TbAchievement> selectByTypeAndLevel(@Param("type") Integer type, @Param("level") Integer level);
|
||||
List<TbAchievement> selectByTypeAndLevel(@Param("type") Integer type, @Param("level") Integer level, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 根据条件类型查询成就列表
|
||||
@@ -65,12 +67,13 @@ public interface AchievementMapper extends BaseMapper<TbAchievement> {
|
||||
List<TbAchievement> selectAchievementRanking(@Param("limit") Integer limit);
|
||||
|
||||
/**
|
||||
* @description 检查成就名称是否存在
|
||||
* @description 检查成就名称是否存在(包含权限过滤)
|
||||
* @param name 成就名称
|
||||
* @param excludeId 排除的成就ID(用于更新时排除自身)
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return int 存在的数量
|
||||
*/
|
||||
int countByName(@Param("name") String name, @Param("excludeId") String excludeId);
|
||||
int countByName(@Param("name") String name, @Param("excludeId") String excludeId, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 插入成就
|
||||
|
||||
@@ -71,7 +71,8 @@ public class ACHAchievementServiceImpl implements AchievementService {
|
||||
}
|
||||
|
||||
// 检查名称是否已存在
|
||||
int count = achievementMapper.countByName(achievement.getName(), null);
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
int count = achievementMapper.countByName(achievement.getName(), null, userDeptRoles);
|
||||
if (count > 0) {
|
||||
resultDomain.fail("成就名称已存在");
|
||||
return resultDomain;
|
||||
@@ -92,7 +93,6 @@ public class ACHAchievementServiceImpl implements AchievementService {
|
||||
if (result > 0) {
|
||||
// 创建成就资源权限
|
||||
try {
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
if (userDeptRoles != null && !userDeptRoles.isEmpty()) {
|
||||
// 使用用户的第一个部门角色创建权限
|
||||
resourcePermissionService.createResourcePermission(
|
||||
@@ -137,7 +137,8 @@ public class ACHAchievementServiceImpl implements AchievementService {
|
||||
|
||||
// 检查名称是否重复
|
||||
if (StringUtils.hasText(achievement.getName())) {
|
||||
int count = achievementMapper.countByName(achievement.getName(), achievement.getAchievementID());
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
int count = achievementMapper.countByName(achievement.getName(), achievement.getAchievementID(), userDeptRoles);
|
||||
if (count > 0) {
|
||||
resultDomain.fail("成就名称已存在");
|
||||
return resultDomain;
|
||||
|
||||
@@ -126,22 +126,22 @@
|
||||
WHERE achievement_id = #{achievementId} AND deleted = 0
|
||||
</select>
|
||||
|
||||
<!-- 根据类型查询成就列表 -->
|
||||
<!-- 根据类型查询成就列表 - 添加权限过滤 -->
|
||||
<select id="selectByType" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_achievement
|
||||
WHERE type = #{type} AND deleted = 0
|
||||
ORDER BY order_num ASC, create_time DESC
|
||||
SELECT DISTINCT a.*
|
||||
FROM tb_achievement a
|
||||
<include refid="Permission_Filter" />
|
||||
WHERE a.type = #{type} AND a.deleted = 0
|
||||
ORDER BY a.order_num ASC, a.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 根据类型和等级查询成就列表 -->
|
||||
<!-- 根据类型和等级查询成就列表 - 添加权限过滤 -->
|
||||
<select id="selectByTypeAndLevel" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_achievement
|
||||
WHERE type = #{type} AND level = #{level} AND deleted = 0
|
||||
ORDER BY order_num ASC, create_time DESC
|
||||
SELECT DISTINCT a.*
|
||||
FROM tb_achievement a
|
||||
<include refid="Permission_Filter" />
|
||||
WHERE a.type = #{type} AND a.level = #{level} AND a.deleted = 0
|
||||
ORDER BY a.order_num ASC, a.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 根据条件类型查询成就列表 -->
|
||||
@@ -165,13 +165,14 @@
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<!-- 检查成就名称是否存在 -->
|
||||
<!-- 检查成就名称是否存在 - 添加权限过滤 -->
|
||||
<select id="countByName" resultType="int">
|
||||
SELECT COUNT(1)
|
||||
FROM tb_achievement
|
||||
WHERE name = #{name} AND deleted = 0
|
||||
SELECT COUNT(DISTINCT a.id)
|
||||
FROM tb_achievement a
|
||||
<include refid="Permission_Filter" />
|
||||
WHERE a.name = #{name} AND a.deleted = 0
|
||||
<if test="excludeId != null and excludeId != ''">
|
||||
AND id != #{excludeId}
|
||||
AND a.id != #{excludeId}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
|
||||
@@ -77,6 +77,11 @@
|
||||
<DefaultRolloverStrategy max="15"/>
|
||||
</RollingFile>
|
||||
|
||||
<!-- 数据库日志Appender - 异步写入DEBUG级别及以上的日志到数据库 -->
|
||||
<DatabaseAppender name="DatabaseAppender" ignoreExceptions="false">
|
||||
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||
</DatabaseAppender>
|
||||
|
||||
</appenders>
|
||||
|
||||
<!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。-->
|
||||
@@ -111,6 +116,7 @@
|
||||
<AppenderRef ref="RollingFileInfo"/>
|
||||
<AppenderRef ref="RollingFileWarn"/>
|
||||
<AppenderRef ref="RollingFileError"/>
|
||||
<AppenderRef ref="DatabaseAppender"/>
|
||||
</Logger>
|
||||
|
||||
<!-- 项目包日志配置 - System模块 -->
|
||||
@@ -120,6 +126,7 @@
|
||||
<AppenderRef ref="RollingFileInfo"/>
|
||||
<AppenderRef ref="RollingFileWarn"/>
|
||||
<AppenderRef ref="RollingFileError"/>
|
||||
<AppenderRef ref="DatabaseAppender"/>
|
||||
</Logger>
|
||||
|
||||
<!-- 项目包日志配置 - News模块 -->
|
||||
@@ -129,6 +136,7 @@
|
||||
<AppenderRef ref="RollingFileInfo"/>
|
||||
<AppenderRef ref="RollingFileWarn"/>
|
||||
<AppenderRef ref="RollingFileError"/>
|
||||
<AppenderRef ref="DatabaseAppender"/>
|
||||
</Logger>
|
||||
|
||||
<!-- 项目包日志配置 - Common模块 -->
|
||||
@@ -138,6 +146,7 @@
|
||||
<AppenderRef ref="RollingFileInfo"/>
|
||||
<AppenderRef ref="RollingFileWarn"/>
|
||||
<AppenderRef ref="RollingFileError"/>
|
||||
<AppenderRef ref="DatabaseAppender"/>
|
||||
</Logger>
|
||||
|
||||
<!-- 项目包日志配置 - Achievement模块 -->
|
||||
@@ -147,6 +156,7 @@
|
||||
<AppenderRef ref="RollingFileInfo"/>
|
||||
<AppenderRef ref="RollingFileWarn"/>
|
||||
<AppenderRef ref="RollingFileError"/>
|
||||
<AppenderRef ref="DatabaseAppender"/>
|
||||
</Logger>
|
||||
|
||||
<root level="info">
|
||||
@@ -155,6 +165,7 @@
|
||||
<appender-ref ref="RollingFileInfo"/>
|
||||
<appender-ref ref="RollingFileWarn"/>
|
||||
<appender-ref ref="RollingFileError"/>
|
||||
<appender-ref ref="DatabaseAppender"/>
|
||||
</root>
|
||||
</loggers>
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import java.util.List;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.dto.dept.TbSysDept;
|
||||
import org.xyzh.common.dto.dept.TbSysDeptRole;
|
||||
import org.xyzh.common.dto.role.TbSysRole;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
|
||||
/**
|
||||
@@ -90,13 +89,13 @@ public interface DepartmentService {
|
||||
ResultDomain<Boolean> checkDepartmentNameExists(String deptName, String excludeId);
|
||||
|
||||
/**
|
||||
* @description 查询部门绑定角色
|
||||
* @description 查询部门绑定角色(包含名称)
|
||||
* @param deptId 部门ID
|
||||
* @return ResultDomain<TbSysRole> 角色信息
|
||||
* @return ResultDomain<UserDeptRoleVO> 部门角色信息
|
||||
* @author yslg
|
||||
* @since 2025-09-28
|
||||
*/
|
||||
ResultDomain<TbSysRole> getDeptByRole(String deptId);
|
||||
ResultDomain<UserDeptRoleVO> getDeptByRole(String deptId);
|
||||
|
||||
/**
|
||||
* @description 查询部门绑定角色列表(包含名称)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.xyzh.api.system.log;
|
||||
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
import org.xyzh.common.dto.system.TbSysLoginLog;
|
||||
|
||||
public interface LoginLogService {
|
||||
|
||||
ResultDomain<TbSysLoginLog> insertLoginLog(TbSysLoginLog loginLog);
|
||||
|
||||
ResultDomain<TbSysLoginLog> selectLoginLogList(TbSysLoginLog loginLog);
|
||||
|
||||
ResultDomain<TbSysLoginLog> selectLoginLogPage(TbSysLoginLog loginLog, PageParam pageParam);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.xyzh.api.system.log;
|
||||
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
import org.xyzh.common.dto.system.TbSysOperationLog;
|
||||
|
||||
/**
|
||||
* @description 操作日志服务接口
|
||||
* @filename OperationLogService.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
public interface OperationLogService {
|
||||
|
||||
/**
|
||||
* @description 分页查询操作日志
|
||||
* @param operationLog 查询条件
|
||||
* @param pageParam 分页参数
|
||||
* @return ResultDomain<TbSysOperationLog> 操作日志分页列表
|
||||
* @author yslg
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
ResultDomain<TbSysOperationLog> selectOperationLogPage(TbSysOperationLog operationLog, PageParam pageParam);
|
||||
|
||||
/**
|
||||
* @description 统计操作日志数量
|
||||
* @param operationLog 查询条件
|
||||
* @return int 日志数量
|
||||
* @author yslg
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
int countOperationLog(TbSysOperationLog operationLog);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.xyzh.api.system.menu;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.dto.menu.TbSysMenu;
|
||||
import org.xyzh.common.dto.menu.TbSysMenuPermission;
|
||||
import org.xyzh.common.vo.PermissionVO;
|
||||
|
||||
/**
|
||||
* @description MenuRemoteService.java文件描述 菜单远程服务接口
|
||||
@@ -132,11 +133,11 @@ public interface MenuService {
|
||||
ResultDomain<TbSysMenu> changeMenuVisibility(String menuId, Boolean visible);
|
||||
|
||||
/**
|
||||
* @description 查询菜单权限
|
||||
* @description 查询菜单权限(包含模块名称等信息)
|
||||
* @param menuId 菜单ID
|
||||
* @return ResultDomain<TbSysMenuPermission> 菜单权限信息
|
||||
* @return ResultDomain<PermissionVO> 权限信息
|
||||
* @author yslg
|
||||
* @since 2025-09-28
|
||||
*/
|
||||
ResultDomain<TbSysMenuPermission> getMenuPermission(String menuId);
|
||||
ResultDomain<PermissionVO> getMenuPermission(String menuId);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.dto.permission.TbSysPermission;
|
||||
import org.xyzh.common.dto.role.TbSysRole;
|
||||
import org.xyzh.common.dto.role.TbSysRolePermission;
|
||||
import org.xyzh.common.vo.PermissionVO;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
|
||||
|
||||
@@ -22,16 +23,22 @@ public interface RoleService {
|
||||
* @author yslg
|
||||
* @since 2025-09-28
|
||||
*/
|
||||
ResultDomain<TbSysRole> getAllRoles();
|
||||
/**
|
||||
* @description 查询所有角色(包含权限过滤)
|
||||
* @return ResultDomain<PermissionVO> 角色VO列表
|
||||
* @author yslg
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
ResultDomain<PermissionVO> getAllRoles();
|
||||
|
||||
/**
|
||||
* @description 根据过滤条件获取角色列表
|
||||
* @description 根据过滤条件获取角色列表(包含权限过滤)
|
||||
* @param filter 过滤条件
|
||||
* @return ResultDomain<TbSysRole> 角色列表
|
||||
* @return ResultDomain<PermissionVO> 角色VO列表
|
||||
* @author yslg
|
||||
* @since 2025-10-09
|
||||
*/
|
||||
ResultDomain<TbSysRole> getRoleList(TbSysRole filter);
|
||||
ResultDomain<PermissionVO> getRoleList(TbSysRole filter);
|
||||
|
||||
/**
|
||||
* @description 创建角色
|
||||
|
||||
@@ -39,6 +39,8 @@ public interface UserService {
|
||||
*/
|
||||
ResultDomain<Boolean> checkUserExists(TbSysUser user);
|
||||
|
||||
ResultDomain<TbSysUser> getLoginUser(TbSysUser filter);
|
||||
|
||||
/**
|
||||
* @description 获取所有用户
|
||||
* @return ResultDomain<TbSysUser> 用户列表
|
||||
@@ -74,6 +76,15 @@ public interface UserService {
|
||||
*/
|
||||
ResultDomain<TbSysUser> getUserByFilter(TbSysUser filter);
|
||||
|
||||
/**
|
||||
* @description 根据过滤条件查询用户VO列表(包含userinfo和deptrole信息)
|
||||
* @param filter 过滤条件
|
||||
* @return ResultDomain<UserVO> 用户VO列表
|
||||
* @author yslg
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
ResultDomain<UserVO> getUserVOByFilter(TbSysUser filter);
|
||||
|
||||
/**
|
||||
* @description 获取用户列表分页
|
||||
* @param filter 过滤条件
|
||||
@@ -84,6 +95,16 @@ public interface UserService {
|
||||
*/
|
||||
ResultDomain<TbSysUser> getUserPage(TbSysUser filter, PageParam pageParam);
|
||||
|
||||
/**
|
||||
* @description 获取用户VO列表分页(包含userinfo和deptrole信息)
|
||||
* @param filter 过滤条件
|
||||
* @param pageParam 分页参数
|
||||
* @return ResultDomain<UserVO> 用户VO列表
|
||||
* @author yslg
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
ResultDomain<UserVO> getUserVOPage(TbSysUser filter, PageParam pageParam);
|
||||
|
||||
/**
|
||||
* @description 创建用户
|
||||
* @param user 用户对象
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
package org.xyzh.auth.service;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.xyzh.common.dto.system.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<TbSysLoginLog> 登录日志列表
|
||||
* @author yslg
|
||||
* @since 2025-09-28
|
||||
*/
|
||||
public java.util.List<TbSysLoginLog> 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 + " 的登录失败记录");
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import org.xyzh.common.dto.user.TbSysUser;
|
||||
import org.xyzh.common.dto.menu.TbSysMenu;
|
||||
import org.xyzh.common.exception.auth.AuthException;
|
||||
import org.xyzh.common.utils.IDUtils;
|
||||
import org.xyzh.common.utils.ServletUtils;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
@@ -24,6 +25,7 @@ import org.xyzh.api.system.user.UserService;
|
||||
import org.xyzh.api.system.role.RoleService;
|
||||
import org.xyzh.api.system.permission.PermissionService;
|
||||
import org.xyzh.common.redis.service.RedisService;
|
||||
import org.xyzh.api.system.log.LoginLogService;
|
||||
import org.xyzh.api.system.menu.MenuService;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
|
||||
@@ -70,6 +72,21 @@ public class LoginServiceImpl implements LoginService {
|
||||
public ResultDomain<LoginDomain> login(LoginParam loginParam, HttpServletRequest request) {
|
||||
ResultDomain<LoginDomain> result = new ResultDomain<>();
|
||||
String ipAddress = request.getRemoteAddr();
|
||||
|
||||
// Redis 自动递增(不存在则创建并返回1,存在则递增后返回)
|
||||
String attemptKey = "login:attempt:" + loginParam.getUsername();
|
||||
int loginAttempt = (int) redisService.incr(attemptKey, 1);
|
||||
|
||||
// 如果是第一次尝试,设置10分钟过期时间
|
||||
if (loginAttempt == 1) {
|
||||
redisService.setExpire(attemptKey, 10, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
// 检查是否超过最大尝试次数
|
||||
if (loginAttempt > 3) {
|
||||
result.fail("登录失败次数过多,请稍后再试");
|
||||
return result;
|
||||
}
|
||||
try {
|
||||
// 自动检测登录类型
|
||||
String loginType = detectLoginType(loginParam);
|
||||
@@ -88,7 +105,7 @@ public class LoginServiceImpl implements LoginService {
|
||||
TbSysUser user = strategy.findUser(loginParam);
|
||||
if (user == null) {
|
||||
result.fail("用户不存在");
|
||||
logLoginAttempt(loginParam, null, false, "用户不存在");
|
||||
logLoginAttempt(loginParam, null, false, loginAttempt, "用户不存在");
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -96,7 +113,7 @@ public class LoginServiceImpl implements LoginService {
|
||||
UserStatus userStatus = UserStatus.fromCode(String.valueOf(user.getStatus()));
|
||||
if (userStatus != UserStatus.NORMAL) {
|
||||
result.fail("用户状态异常: " + userStatus.getName());
|
||||
logLoginAttempt(loginParam, user, false, "用户状态异常: " + userStatus.getName());
|
||||
logLoginAttempt(loginParam, user, false, loginAttempt, "用户状态异常: " + userStatus.getName());
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -104,7 +121,7 @@ public class LoginServiceImpl implements LoginService {
|
||||
// 验证凭据(密码或验证码)
|
||||
if (!strategy.verifyCredential(loginParam.getPassword(), user.getPassword())) {
|
||||
result.fail("密码错误");
|
||||
logLoginAttempt(loginParam, user, false, "密码错误");
|
||||
logLoginAttempt(loginParam, user, false, loginAttempt, "密码错误");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -113,7 +130,7 @@ public class LoginServiceImpl implements LoginService {
|
||||
String storedCaptcha = (String) redisService.get("captcha:" + loginParam.getPhone());
|
||||
if (!strategy.verifyCredential(loginParam.getCaptcha(), storedCaptcha)) {
|
||||
result.fail("验证码错误");
|
||||
logLoginAttempt(loginParam, user, false, "验证码错误");
|
||||
logLoginAttempt(loginParam, user, false, loginAttempt, "验证码错误");
|
||||
return result;
|
||||
}
|
||||
// 验证码使用后删除
|
||||
@@ -129,8 +146,9 @@ public class LoginServiceImpl implements LoginService {
|
||||
String redisKey = "login:token:" + user.getID();
|
||||
redisService.set(redisKey, loginDomain, 24 * 60 * 60, TimeUnit.SECONDS);
|
||||
|
||||
// 记录成功登录日志
|
||||
logLoginAttempt(loginParam, user, true, "登录成功");
|
||||
// 登录成功后清除失败次数并记录成功日志
|
||||
redisService.delete(attemptKey);
|
||||
logLoginAttempt(loginParam, user, true, 0, "登录成功");
|
||||
|
||||
result.success("登录成功", loginDomain);
|
||||
result.setData(loginDomain);
|
||||
@@ -253,7 +271,7 @@ public class LoginServiceImpl implements LoginService {
|
||||
* @author yslg
|
||||
* @since 2025-09-28
|
||||
*/
|
||||
private void logLoginAttempt(LoginParam loginParam, TbSysUser user, boolean success, String message) {
|
||||
private void logLoginAttempt(LoginParam loginParam, TbSysUser user, boolean success, int errorCount, String message) {
|
||||
TbSysLoginLog loginLog = new TbSysLoginLog();
|
||||
|
||||
// 使用IDUtils生成登录日志ID
|
||||
@@ -266,9 +284,14 @@ public class LoginServiceImpl implements LoginService {
|
||||
// 注意:实际生产中不应记录密码
|
||||
// loginLog.setPassword(loginParam.getPassword());
|
||||
loginLog.setStatus(success ? 1 : 0);
|
||||
loginLog.setErrorCount(errorCount);
|
||||
loginLog.setMessage(message);
|
||||
loginLog.setLoginTime(new Date().toString());
|
||||
loginLog.setLoginTime(new Date());
|
||||
loginLog.setIpAddress(ServletUtils.getClientIp());
|
||||
loginLog.setIpSource(ServletUtils.getIpSource());
|
||||
loginLog.setBrowser(ServletUtils.getBrowser());
|
||||
loginLog.setOs(ServletUtils.getOs());
|
||||
|
||||
loginLogService.saveLoginLog(loginLog);
|
||||
loginLogService.insertLoginLog(loginLog);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,11 +62,12 @@ public class PasswordLoginStrategy implements LoginStrategy {
|
||||
if (NonUtils.isNotEmpty(loginParam.getPhone())) {
|
||||
filter.setPhone(loginParam.getPhone());
|
||||
}
|
||||
List<TbSysUser> users = userService.getUserByFilter(filter).getDataList();
|
||||
if(users.isEmpty()) {
|
||||
filter.setPassword(passwordEncoder.encode(loginParam.getPassword()));
|
||||
TbSysUser user = userService.getLoginUser(filter).getData();
|
||||
if(user == null) {
|
||||
return null;
|
||||
}
|
||||
return users.get(0);
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
package org.xyzh.common.dto.system;
|
||||
import java.util.Date;
|
||||
|
||||
import org.xyzh.common.dto.BaseDTO;
|
||||
|
||||
/**
|
||||
@@ -65,7 +67,7 @@ public class TbSysLoginLog extends BaseDTO {
|
||||
* @author yslg
|
||||
* @since 2025-09-16
|
||||
*/
|
||||
private String loginTime;
|
||||
private Date loginTime;
|
||||
|
||||
/**
|
||||
* @description 登录状态(0失败 1成功)
|
||||
@@ -144,11 +146,11 @@ public class TbSysLoginLog extends BaseDTO {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getLoginTime() {
|
||||
public Date getLoginTime() {
|
||||
return loginTime;
|
||||
}
|
||||
|
||||
public void setLoginTime(String loginTime) {
|
||||
public void setLoginTime(Date loginTime) {
|
||||
this.loginTime = loginTime;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,20 @@ public class PermissionVO extends BaseDTO{
|
||||
*/
|
||||
private String moduleID;
|
||||
|
||||
/**
|
||||
* @description 模块名称
|
||||
* @author yslg
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
private String moduleName;
|
||||
|
||||
/**
|
||||
* @description 模块描述
|
||||
* @author yslg
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
private String moduleDescription;
|
||||
|
||||
/**
|
||||
* @description 权限名称
|
||||
* @author yslg
|
||||
@@ -77,6 +91,48 @@ public class PermissionVO extends BaseDTO{
|
||||
*/
|
||||
private String bindType;
|
||||
|
||||
/**
|
||||
* @description 菜单ID(用于权限绑定菜单查询)
|
||||
* @author yslg
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
private String menuID;
|
||||
|
||||
/**
|
||||
* @description 菜单名称(用于权限绑定菜单查询)
|
||||
* @author yslg
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
private String menuName;
|
||||
|
||||
/**
|
||||
* @description 菜单描述(用于权限绑定菜单查询)
|
||||
* @author yslg
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
private String menuUrl;
|
||||
|
||||
/**
|
||||
* @description 角色ID(用于权限绑定角色查询)
|
||||
* @author yslg
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
private String roleID;
|
||||
|
||||
/**
|
||||
* @description 角色名称(用于权限绑定角色查询)
|
||||
* @author yslg
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
private String roleName;
|
||||
|
||||
/**
|
||||
* @description 角色描述(用于权限绑定角色查询)
|
||||
* @author yslg
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
private String roleDescription;
|
||||
|
||||
/**
|
||||
* @description 绑定菜单
|
||||
* @author yslg
|
||||
@@ -120,6 +176,22 @@ public class PermissionVO extends BaseDTO{
|
||||
this.moduleID = moduleID;
|
||||
}
|
||||
|
||||
public String getModuleName() {
|
||||
return moduleName;
|
||||
}
|
||||
|
||||
public void setModuleName(String moduleName) {
|
||||
this.moduleName = moduleName;
|
||||
}
|
||||
|
||||
public String getModuleDescription() {
|
||||
return moduleDescription;
|
||||
}
|
||||
|
||||
public void setModuleDescription(String moduleDescription) {
|
||||
this.moduleDescription = moduleDescription;
|
||||
}
|
||||
|
||||
public String getPermissionID() {
|
||||
return permissionID;
|
||||
}
|
||||
@@ -224,4 +296,52 @@ public class PermissionVO extends BaseDTO{
|
||||
this.module = module;
|
||||
}
|
||||
|
||||
public String getMenuID() {
|
||||
return menuID;
|
||||
}
|
||||
|
||||
public void setMenuID(String menuID) {
|
||||
this.menuID = menuID;
|
||||
}
|
||||
|
||||
public String getMenuName() {
|
||||
return menuName;
|
||||
}
|
||||
|
||||
public void setMenuName(String menuName) {
|
||||
this.menuName = menuName;
|
||||
}
|
||||
|
||||
public String getMenuUrl() {
|
||||
return menuUrl;
|
||||
}
|
||||
|
||||
public void setMenuUrl(String menuUrl) {
|
||||
this.menuUrl = menuUrl;
|
||||
}
|
||||
|
||||
public String getRoleID() {
|
||||
return roleID;
|
||||
}
|
||||
|
||||
public void setRoleID(String roleID) {
|
||||
this.roleID = roleID;
|
||||
}
|
||||
|
||||
public String getRoleName() {
|
||||
return roleName;
|
||||
}
|
||||
|
||||
public void setRoleName(String roleName) {
|
||||
this.roleName = roleName;
|
||||
}
|
||||
|
||||
public String getRoleDescription() {
|
||||
return roleDescription;
|
||||
}
|
||||
|
||||
public void setRoleDescription(String roleDescription) {
|
||||
this.roleDescription = roleDescription;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,13 +16,33 @@ public class TaskItemVO extends TbLearningTask {
|
||||
private String resourceName;
|
||||
private String userID;
|
||||
private String username;
|
||||
private String deptID;
|
||||
private String deptName;
|
||||
private String parentID;
|
||||
private Boolean required;
|
||||
private Integer orderNum;
|
||||
private Integer status;
|
||||
private BigDecimal progress;
|
||||
private Date completeTime;
|
||||
|
||||
|
||||
public String getDeptID() {
|
||||
return deptID;
|
||||
}
|
||||
public void setDeptID(String deptID) {
|
||||
this.deptID = deptID;
|
||||
}
|
||||
public String getDeptName() {
|
||||
return deptName;
|
||||
}
|
||||
public void setDeptName(String deptName) {
|
||||
this.deptName = deptName;
|
||||
}
|
||||
public String getParentID() {
|
||||
return parentID;
|
||||
}
|
||||
public void setParentID(String parentID) {
|
||||
this.parentID = parentID;
|
||||
}
|
||||
|
||||
public TaskItemVO() {
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ public class UserVO {
|
||||
private Integer level;
|
||||
private String idCard;
|
||||
private String address;
|
||||
private String deptID;
|
||||
private String parentID;
|
||||
private String deptName;
|
||||
private String roleName;
|
||||
private Date createTime;
|
||||
@@ -25,6 +27,21 @@ public class UserVO {
|
||||
private Date deleteTime;
|
||||
private Boolean deleted;
|
||||
private Integer status;
|
||||
|
||||
public String getDeptID() {
|
||||
return deptID;
|
||||
}
|
||||
public void setDeptID(String deptID) {
|
||||
this.deptID = deptID;
|
||||
}
|
||||
public String getParentID() {
|
||||
return parentID;
|
||||
}
|
||||
public void setParentID(String parentID) {
|
||||
this.parentID = parentID;
|
||||
}
|
||||
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
package org.xyzh.common.utils;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* @description ServletUtil.java文件描述 Servlet工具类
|
||||
* @filename ServletUtil.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-10-07
|
||||
*/
|
||||
public class ServletUtil {
|
||||
|
||||
private static final String TOKEN_PREFIX = "Bearer ";
|
||||
|
||||
/**
|
||||
* @description 获取当前请求对象
|
||||
* @return HttpServletRequest 当前请求对象,无当前请求时返回null
|
||||
* @author yslg
|
||||
* @since 2025-10-07
|
||||
*/
|
||||
public static HttpServletRequest getRequest() {
|
||||
ServletRequestAttributes requestAttributes =
|
||||
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
return requestAttributes != null ? requestAttributes.getRequest() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取当前响应对象
|
||||
* @return HttpServletResponse 当前响应对象,无当前请求时返回null
|
||||
* @author yslg
|
||||
* @since 2025-10-07
|
||||
*/
|
||||
public static HttpServletResponse getResponse() {
|
||||
ServletRequestAttributes requestAttributes =
|
||||
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
return requestAttributes != null ? requestAttributes.getResponse() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 从当前请求中获取token(无参方法)
|
||||
* @return String token值,未找到返回null
|
||||
* @author yslg
|
||||
* @since 2025-10-07
|
||||
*/
|
||||
public static String getToken() {
|
||||
HttpServletRequest request = getRequest();
|
||||
return request != null ? extractTokenFromRequest(request) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 从当前请求中获取指定请求头
|
||||
* @param headerName 请求头名称
|
||||
* @return String 请求头值,未找到返回null
|
||||
* @author yslg
|
||||
* @since 2025-10-07
|
||||
*/
|
||||
public static String getHeader(String headerName) {
|
||||
HttpServletRequest request = getRequest();
|
||||
return request != null ? request.getHeader(headerName) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 从当前请求中获取指定请求参数
|
||||
* @param paramName 参数名称
|
||||
* @return String 参数值,未找到返回null
|
||||
* @author yslg
|
||||
* @since 2025-10-07
|
||||
*/
|
||||
public static String getParameter(String paramName) {
|
||||
HttpServletRequest request = getRequest();
|
||||
return request != null ? request.getParameter(paramName) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取当前请求的客户端IP地址
|
||||
* @return String IP地址,未找到返回null
|
||||
* @author yslg
|
||||
* @since 2025-10-07
|
||||
*/
|
||||
public static String getClientIpAddress() {
|
||||
HttpServletRequest request = getRequest();
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String ip = request.getHeader("X-Forwarded-For");
|
||||
if (NonUtils.isNotEmpty(ip) && !"unknown".equalsIgnoreCase(ip)) {
|
||||
// 多次反向代理后会有多个IP值,第一个为真实IP
|
||||
int index = ip.indexOf(',');
|
||||
if (index != -1) {
|
||||
return ip.substring(0, index);
|
||||
} else {
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
|
||||
ip = request.getHeader("X-Real-IP");
|
||||
if (NonUtils.isNotEmpty(ip) && !"unknown".equalsIgnoreCase(ip)) {
|
||||
return ip;
|
||||
}
|
||||
|
||||
ip = request.getHeader("Proxy-Client-IP");
|
||||
if (NonUtils.isNotEmpty(ip) && !"unknown".equalsIgnoreCase(ip)) {
|
||||
return ip;
|
||||
}
|
||||
|
||||
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||
if (NonUtils.isNotEmpty(ip) && !"unknown".equalsIgnoreCase(ip)) {
|
||||
return ip;
|
||||
}
|
||||
|
||||
ip = request.getHeader("HTTP_CLIENT_IP");
|
||||
if (NonUtils.isNotEmpty(ip) && !"unknown".equalsIgnoreCase(ip)) {
|
||||
return ip;
|
||||
}
|
||||
|
||||
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
|
||||
if (NonUtils.isNotEmpty(ip) && !"unknown".equalsIgnoreCase(ip)) {
|
||||
return ip;
|
||||
}
|
||||
|
||||
return request.getRemoteAddr();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取当前请求的User-Agent
|
||||
* @return String User-Agent值,未找到返回null
|
||||
* @author yslg
|
||||
* @since 2025-10-07
|
||||
*/
|
||||
public static String getUserAgent() {
|
||||
return getHeader("User-Agent");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取当前请求的完整URL
|
||||
* @return String 完整URL,无当前请求时返回null
|
||||
* @author yslg
|
||||
* @since 2025-10-07
|
||||
*/
|
||||
public static String getRequestUrl() {
|
||||
HttpServletRequest request = getRequest();
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuffer url = request.getRequestURL();
|
||||
String queryString = request.getQueryString();
|
||||
if (NonUtils.isNotEmpty(queryString)) {
|
||||
url.append('?').append(queryString);
|
||||
}
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 从请求中提取token
|
||||
* @param request HTTP请求对象
|
||||
* @return String token
|
||||
* @author yslg
|
||||
* @since 2025-10-07
|
||||
*/
|
||||
private static String extractTokenFromRequest(HttpServletRequest request) {
|
||||
// 优先从Authorization头获取
|
||||
String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
if (NonUtils.isNotEmpty(authHeader) && authHeader.startsWith(TOKEN_PREFIX)) {
|
||||
return authHeader.substring(TOKEN_PREFIX.length());
|
||||
}
|
||||
|
||||
// 从请求参数中获取token
|
||||
String token = request.getParameter("token");
|
||||
if (NonUtils.isNotEmpty(token)) {
|
||||
return token;
|
||||
}
|
||||
|
||||
// 从请求头中获取token
|
||||
token = request.getHeader("token");
|
||||
if (NonUtils.isNotEmpty(token)) {
|
||||
return token;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,404 +1,337 @@
|
||||
package org.xyzh.common.utils;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.BufferedReader;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* @description ServletUtils.java文件描述:Servlet相关常用工具方法
|
||||
* @filename ServletUtils.java
|
||||
* @description ServletUtil.java文件描述 Servlet工具类
|
||||
* @filename ServletUtil.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2023-12-19
|
||||
* @since 2025-10-07
|
||||
*/
|
||||
public class ServletUtils {
|
||||
|
||||
private static final String TOKEN_PREFIX = "Bearer ";
|
||||
|
||||
/**
|
||||
* @description 获取请求的真实IP地址
|
||||
* @param request HTTP请求对象
|
||||
* @return 客户端真实IP地址
|
||||
* @description 获取当前请求对象
|
||||
* @return HttpServletRequest 当前请求对象,无当前请求时返回null
|
||||
* @author yslg
|
||||
* @since 2023-12-19
|
||||
* @since 2025-10-07
|
||||
*/
|
||||
public static String getClientIp(HttpServletRequest request) {
|
||||
public static HttpServletRequest getRequest() {
|
||||
ServletRequestAttributes requestAttributes =
|
||||
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
return requestAttributes != null ? requestAttributes.getRequest() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取当前响应对象
|
||||
* @return HttpServletResponse 当前响应对象,无当前请求时返回null
|
||||
* @author yslg
|
||||
* @since 2025-10-07
|
||||
*/
|
||||
public static HttpServletResponse getResponse() {
|
||||
ServletRequestAttributes requestAttributes =
|
||||
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
return requestAttributes != null ? requestAttributes.getResponse() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 从当前请求中获取token(无参方法)
|
||||
* @return String token值,未找到返回null
|
||||
* @author yslg
|
||||
* @since 2025-10-07
|
||||
*/
|
||||
public static String getToken() {
|
||||
HttpServletRequest request = getRequest();
|
||||
return request != null ? extractTokenFromRequest(request) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 从当前请求中获取指定请求头
|
||||
* @param headerName 请求头名称
|
||||
* @return String 请求头值,未找到返回null
|
||||
* @author yslg
|
||||
* @since 2025-10-07
|
||||
*/
|
||||
public static String getHeader(String headerName) {
|
||||
HttpServletRequest request = getRequest();
|
||||
return request != null ? request.getHeader(headerName) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 从当前请求中获取指定请求参数
|
||||
* @param paramName 参数名称
|
||||
* @return String 参数值,未找到返回null
|
||||
* @author yslg
|
||||
* @since 2025-10-07
|
||||
*/
|
||||
public static String getParameter(String paramName) {
|
||||
HttpServletRequest request = getRequest();
|
||||
return request != null ? request.getParameter(paramName) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取当前请求的客户端IP地址
|
||||
* @return String IP地址,未找到返回null
|
||||
* @author yslg
|
||||
* @since 2025-10-07
|
||||
*/
|
||||
public static String getClientIpAddress() {
|
||||
HttpServletRequest request = getRequest();
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String ip = request.getHeader("X-Forwarded-For");
|
||||
if (ip != null && ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
|
||||
// 多级代理时取第一个
|
||||
int idx = ip.indexOf(',');
|
||||
if (idx > -1) {
|
||||
ip = ip.substring(0, idx);
|
||||
if (NonUtils.isNotEmpty(ip) && !"unknown".equalsIgnoreCase(ip)) {
|
||||
// 多次反向代理后会有多个IP值,第一个为真实IP
|
||||
int index = ip.indexOf(',');
|
||||
if (index != -1) {
|
||||
return ip.substring(0, index);
|
||||
} else {
|
||||
return ip;
|
||||
}
|
||||
return ip.trim();
|
||||
}
|
||||
|
||||
ip = request.getHeader("X-Real-IP");
|
||||
if (NonUtils.isNotEmpty(ip) && !"unknown".equalsIgnoreCase(ip)) {
|
||||
return ip;
|
||||
}
|
||||
|
||||
ip = request.getHeader("Proxy-Client-IP");
|
||||
if (ip != null && ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
|
||||
if (NonUtils.isNotEmpty(ip) && !"unknown".equalsIgnoreCase(ip)) {
|
||||
return ip;
|
||||
}
|
||||
|
||||
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||
if (ip != null && ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
|
||||
return ip;
|
||||
}
|
||||
ip = request.getRemoteAddr();
|
||||
if (NonUtils.isNotEmpty(ip) && !"unknown".equalsIgnoreCase(ip)) {
|
||||
return ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 向响应写出JSON字符串
|
||||
* @param response HTTP响应对象
|
||||
* @param json 要写出的JSON字符串
|
||||
* @return void
|
||||
* @author yslg
|
||||
* @since 2023-12-19
|
||||
*/
|
||||
public static void writeJson(HttpServletResponse response, String json) throws IOException {
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
PrintWriter writer = response.getWriter();
|
||||
writer.write(json);
|
||||
writer.flush();
|
||||
writer.close();
|
||||
ip = request.getHeader("HTTP_CLIENT_IP");
|
||||
if (NonUtils.isNotEmpty(ip) && !"unknown".equalsIgnoreCase(ip)) {
|
||||
return ip;
|
||||
}
|
||||
|
||||
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
|
||||
if (NonUtils.isNotEmpty(ip) && !"unknown".equalsIgnoreCase(ip)) {
|
||||
return ip;
|
||||
}
|
||||
|
||||
return request.getRemoteAddr();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取请求参数(支持默认值)
|
||||
* @param request HTTP请求对象
|
||||
* @param name 参数名
|
||||
* @param defaultValue 默认值
|
||||
* @return 参数值或默认值
|
||||
* @description 获取当前请求的User-Agent
|
||||
* @return String User-Agent值,未找到返回null
|
||||
* @author yslg
|
||||
* @since 2023-12-19
|
||||
* @since 2025-10-07
|
||||
*/
|
||||
public static String getParameter(HttpServletRequest request, String name, String defaultValue) {
|
||||
String value = request.getParameter(name);
|
||||
return value != null ? value : defaultValue;
|
||||
public static String getUserAgent() {
|
||||
return getHeader("User-Agent");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断请求是否为Ajax
|
||||
* @param request HTTP请求对象
|
||||
* @return 是否为Ajax请求
|
||||
* @description 获取客户端IP(别名方法)
|
||||
* @return String IP地址
|
||||
* @author yslg
|
||||
* @since 2023-12-19
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
public static boolean isAjaxRequest(HttpServletRequest request) {
|
||||
String header = request.getHeader("X-Requested-With");
|
||||
return "XMLHttpRequest".equalsIgnoreCase(header);
|
||||
public static String getClientIp() {
|
||||
return getClientIpAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取请求体内容
|
||||
* @param request HTTP请求对象
|
||||
* @return 请求体内容
|
||||
* @description 获取IP来源地址(简化版)
|
||||
* @return String IP来源
|
||||
* @author yslg
|
||||
* @since 2023-12-19
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
public static String getRequestBody(HttpServletRequest request) throws IOException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
BufferedReader reader = request.getReader();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line);
|
||||
public static String getIpSource() {
|
||||
String ip = getClientIp();
|
||||
if (ip == null || ip.isEmpty()) {
|
||||
return "未知";
|
||||
}
|
||||
return sb.toString();
|
||||
|
||||
// 判断是否为内网IP
|
||||
if (isInternalIp(ip)) {
|
||||
return "内网IP";
|
||||
}
|
||||
|
||||
// 对于公网IP,返回简单标识
|
||||
// TODO: 可以集成第三方IP地址库进行详细查询
|
||||
return "外网IP";
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 重定向到指定URL
|
||||
* @param response HTTP响应对象
|
||||
* @param url 目标URL
|
||||
* @return void
|
||||
* @description 获取浏览器信息
|
||||
* @return String 浏览器类型和版本
|
||||
* @author yslg
|
||||
* @since 2023-12-19
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
public static void redirect(HttpServletResponse response, String url) throws IOException {
|
||||
response.sendRedirect(url);
|
||||
public static String getBrowser() {
|
||||
String userAgent = getUserAgent();
|
||||
if (userAgent == null || userAgent.isEmpty()) {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
userAgent = userAgent.toLowerCase();
|
||||
|
||||
// 检测常见浏览器
|
||||
if (userAgent.contains("edg")) {
|
||||
return "Microsoft Edge";
|
||||
} else if (userAgent.contains("chrome")) {
|
||||
return "Chrome";
|
||||
} else if (userAgent.contains("firefox")) {
|
||||
return "Firefox";
|
||||
} else if (userAgent.contains("safari") && !userAgent.contains("chrome")) {
|
||||
return "Safari";
|
||||
} else if (userAgent.contains("opera") || userAgent.contains("opr")) {
|
||||
return "Opera";
|
||||
} else if (userAgent.contains("trident") || userAgent.contains("msie")) {
|
||||
return "Internet Explorer";
|
||||
} else if (userAgent.contains("postman")) {
|
||||
return "Postman";
|
||||
} else if (userAgent.contains("apifox")) {
|
||||
return "Apifox";
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取完整请求URL
|
||||
* @param request HTTP请求对象
|
||||
* @return 完整URL
|
||||
* @description 获取操作系统信息
|
||||
* @return String 操作系统类型
|
||||
* @author yslg
|
||||
* @since 2023-12-19
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
public static String getFullUrl(HttpServletRequest request) {
|
||||
StringBuilder url = new StringBuilder();
|
||||
url.append(request.getScheme()).append("://");
|
||||
url.append(request.getServerName());
|
||||
int port = request.getServerPort();
|
||||
if (("http".equals(request.getScheme()) && port != 80) ||
|
||||
("https".equals(request.getScheme()) && port != 443)) {
|
||||
url.append(":").append(port);
|
||||
public static String getOs() {
|
||||
String userAgent = getUserAgent();
|
||||
if (userAgent == null || userAgent.isEmpty()) {
|
||||
return "Unknown";
|
||||
}
|
||||
url.append(request.getRequestURI());
|
||||
|
||||
userAgent = userAgent.toLowerCase();
|
||||
|
||||
// 检测操作系统
|
||||
if (userAgent.contains("windows nt 10.0")) {
|
||||
return "Windows 10/11";
|
||||
} else if (userAgent.contains("windows nt 6.3")) {
|
||||
return "Windows 8.1";
|
||||
} else if (userAgent.contains("windows nt 6.2")) {
|
||||
return "Windows 8";
|
||||
} else if (userAgent.contains("windows nt 6.1")) {
|
||||
return "Windows 7";
|
||||
} else if (userAgent.contains("windows")) {
|
||||
return "Windows";
|
||||
} else if (userAgent.contains("mac os x")) {
|
||||
return "macOS";
|
||||
} else if (userAgent.contains("linux")) {
|
||||
if (userAgent.contains("android")) {
|
||||
return "Android";
|
||||
}
|
||||
return "Linux";
|
||||
} else if (userAgent.contains("iphone") || userAgent.contains("ipad")) {
|
||||
return "iOS";
|
||||
} else if (userAgent.contains("android")) {
|
||||
return "Android";
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取当前请求的完整URL
|
||||
* @return String 完整URL,无当前请求时返回null
|
||||
* @author yslg
|
||||
* @since 2025-10-07
|
||||
*/
|
||||
public static String getRequestUrl() {
|
||||
HttpServletRequest request = getRequest();
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuffer url = request.getRequestURL();
|
||||
String queryString = request.getQueryString();
|
||||
if (queryString != null) {
|
||||
url.append("?").append(queryString);
|
||||
if (NonUtils.isNotEmpty(queryString)) {
|
||||
url.append('?').append(queryString);
|
||||
}
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断请求是否为GET方法
|
||||
* @param request HTTP请求对象
|
||||
* @return 是否为GET请求
|
||||
* @description 判断是否为内网IP
|
||||
* @param ip IP地址
|
||||
* @return boolean true-内网IP,false-外网IP
|
||||
* @author yslg
|
||||
* @since 2023-12-19
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
public static boolean isGet(HttpServletRequest request) {
|
||||
return "GET".equalsIgnoreCase(request.getMethod());
|
||||
private static boolean isInternalIp(String ip) {
|
||||
if (ip == null || ip.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 本地回环地址
|
||||
if (ip.startsWith("127.") || ip.equals("localhost") || ip.equals("0:0:0:0:0:0:0:1") || ip.equals("::1")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 内网IP段
|
||||
if (ip.startsWith("10.") ||
|
||||
ip.startsWith("192.168.") ||
|
||||
ip.startsWith("172.")) {
|
||||
// 172.16.0.0 - 172.31.255.255
|
||||
if (ip.startsWith("172.")) {
|
||||
String[] parts = ip.split("\\.");
|
||||
if (parts.length >= 2) {
|
||||
try {
|
||||
int secondOctet = Integer.parseInt(parts[1]);
|
||||
return secondOctet >= 16 && secondOctet <= 31;
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断请求是否为POST方法
|
||||
* @description 从请求中提取token
|
||||
* @param request HTTP请求对象
|
||||
* @return 是否为POST请求
|
||||
* @return String token
|
||||
* @author yslg
|
||||
* @since 2023-12-19
|
||||
* @since 2025-10-07
|
||||
*/
|
||||
public static boolean isPost(HttpServletRequest request) {
|
||||
return "POST".equalsIgnoreCase(request.getMethod());
|
||||
private static String extractTokenFromRequest(HttpServletRequest request) {
|
||||
// 优先从Authorization头获取
|
||||
String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
if (NonUtils.isNotEmpty(authHeader) && authHeader.startsWith(TOKEN_PREFIX)) {
|
||||
return authHeader.substring(TOKEN_PREFIX.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断请求是否为PUT方法
|
||||
* @param request HTTP请求对象
|
||||
* @return 是否为PUT请求
|
||||
* @author yslg
|
||||
* @since 2023-12-19
|
||||
*/
|
||||
public static boolean isPut(HttpServletRequest request) {
|
||||
return "PUT".equalsIgnoreCase(request.getMethod());
|
||||
// 从请求参数中获取token
|
||||
String token = request.getParameter("token");
|
||||
if (NonUtils.isNotEmpty(token)) {
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断请求是否为DELETE方法
|
||||
* @param request HTTP请求对象
|
||||
* @return 是否为DELETE请求
|
||||
* @author yslg
|
||||
* @since 2023-12-19
|
||||
*/
|
||||
public static boolean isDelete(HttpServletRequest request) {
|
||||
return "DELETE".equalsIgnoreCase(request.getMethod());
|
||||
// 从请求头中获取token
|
||||
token = request.getHeader("token");
|
||||
if (NonUtils.isNotEmpty(token)) {
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取所有请求参数
|
||||
* @param request HTTP请求对象
|
||||
* @return 参数Map
|
||||
* @author yslg
|
||||
* @since 2023-12-19
|
||||
*/
|
||||
public static Map<String, String> getParameterMap(HttpServletRequest request) {
|
||||
Map<String, String> paramMap = new HashMap<>();
|
||||
Enumeration<String> parameterNames = request.getParameterNames();
|
||||
while (parameterNames.hasMoreElements()) {
|
||||
String paramName = parameterNames.nextElement();
|
||||
String paramValue = request.getParameter(paramName);
|
||||
paramMap.put(paramName, paramValue);
|
||||
}
|
||||
return paramMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取请求头信息
|
||||
* @param request HTTP请求对象
|
||||
* @return 头信息Map
|
||||
* @author yslg
|
||||
* @since 2023-12-19
|
||||
*/
|
||||
public static Map<String, String> getHeaderMap(HttpServletRequest request) {
|
||||
Map<String, String> headerMap = new HashMap<>();
|
||||
Enumeration<String> headerNames = request.getHeaderNames();
|
||||
while (headerNames.hasMoreElements()) {
|
||||
String headerName = headerNames.nextElement();
|
||||
String headerValue = request.getHeader(headerName);
|
||||
headerMap.put(headerName, headerValue);
|
||||
}
|
||||
return headerMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取Cookie值
|
||||
* @param request HTTP请求对象
|
||||
* @param name Cookie名
|
||||
* @return Cookie值
|
||||
* @author yslg
|
||||
* @since 2023-12-19
|
||||
*/
|
||||
public static String getCookieValue(HttpServletRequest request, String name) {
|
||||
Cookie[] cookies = request.getCookies();
|
||||
if (cookies != null) {
|
||||
for (Cookie cookie : cookies) {
|
||||
if (name.equals(cookie.getName())) {
|
||||
return cookie.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 设置Cookie
|
||||
* @param response HTTP响应对象
|
||||
* @param name Cookie名
|
||||
* @param value Cookie值
|
||||
* @param maxAge 过期时间(秒)
|
||||
* @return void
|
||||
* @author yslg
|
||||
* @since 2023-12-19
|
||||
*/
|
||||
public static void setCookie(HttpServletResponse response, String name, String value, int maxAge) {
|
||||
Cookie cookie = new Cookie(name, value);
|
||||
cookie.setMaxAge(maxAge);
|
||||
cookie.setPath("/");
|
||||
cookie.setHttpOnly(true);
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 删除Cookie
|
||||
* @param response HTTP响应对象
|
||||
* @param name Cookie名
|
||||
* @return void
|
||||
* @author yslg
|
||||
* @since 2023-12-19
|
||||
*/
|
||||
public static void removeCookie(HttpServletResponse response, String name) {
|
||||
Cookie cookie = new Cookie(name, null);
|
||||
cookie.setMaxAge(0);
|
||||
cookie.setPath("/");
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断是否为HTTPS请求
|
||||
* @param request HTTP请求对象
|
||||
* @return 是否为HTTPS请求
|
||||
* @author yslg
|
||||
* @since 2023-12-19
|
||||
*/
|
||||
public static boolean isHttps(HttpServletRequest request) {
|
||||
return "https".equals(request.getScheme()) || request.isSecure() ||
|
||||
"443".equals(request.getHeader("X-Forwarded-Port")) ||
|
||||
"https".equals(request.getHeader("X-Forwarded-Proto"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取上下文路径
|
||||
* @param request HTTP请求对象
|
||||
* @return 上下文路径
|
||||
* @author yslg
|
||||
* @since 2023-12-19
|
||||
*/
|
||||
public static String getContextPath(HttpServletRequest request) {
|
||||
return request.getContextPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 检测请求是否包含指定参数
|
||||
* @param request HTTP请求对象
|
||||
* @param paramName 参数名
|
||||
* @return 是否包含参数
|
||||
* @author yslg
|
||||
* @since 2023-12-19
|
||||
*/
|
||||
public static boolean hasParameter(HttpServletRequest request, String paramName) {
|
||||
return request.getParameter(paramName) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取Session对象
|
||||
* @param request HTTP请求对象
|
||||
* @return HttpSession对象
|
||||
* @author yslg
|
||||
* @since 2023-12-19
|
||||
*/
|
||||
public static HttpSession getSession(HttpServletRequest request) {
|
||||
return request.getSession();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取Session属性
|
||||
* @param request HTTP请求对象
|
||||
* @param attributeName 属性名
|
||||
* @return 属性值
|
||||
* @author yslg
|
||||
* @since 2023-12-19
|
||||
*/
|
||||
public static Object getSessionAttribute(HttpServletRequest request, String attributeName) {
|
||||
HttpSession session = request.getSession(false);
|
||||
return session != null ? session.getAttribute(attributeName) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 设置Session属性
|
||||
* @param request HTTP请求对象
|
||||
* @param attributeName 属性名
|
||||
* @param attributeValue 属性值
|
||||
* @return void
|
||||
* @author yslg
|
||||
* @since 2023-12-19
|
||||
*/
|
||||
public static void setSessionAttribute(HttpServletRequest request, String attributeName, Object attributeValue) {
|
||||
request.getSession().setAttribute(attributeName, attributeValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 移除Session属性
|
||||
* @param request HTTP请求对象
|
||||
* @param attributeName 属性名
|
||||
* @return void
|
||||
* @author yslg
|
||||
* @since 2023-12-19
|
||||
*/
|
||||
public static void removeSessionAttribute(HttpServletRequest request, String attributeName) {
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session != null) {
|
||||
session.removeAttribute(attributeName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 防止XSS攻击的字符串过滤
|
||||
* @param input 输入字符串
|
||||
* @return 过滤后的字符串
|
||||
* @author yslg
|
||||
* @since 2023-12-19
|
||||
*/
|
||||
public static String escapeXss(String input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
return input
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace("'", "'")
|
||||
.replace("\"", """)
|
||||
.replace("&", "&");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取所有请求参数名
|
||||
* @param request HTTP请求对象
|
||||
* @return 参数名集合
|
||||
* @author yslg
|
||||
* @since 2023-12-19
|
||||
*/
|
||||
public static Set<String> getParameterNames(HttpServletRequest request) {
|
||||
Set<String> paramNames = new HashSet<>();
|
||||
Enumeration<String> names = request.getParameterNames();
|
||||
while (names.hasMoreElements()) {
|
||||
paramNames.add(names.nextElement());
|
||||
}
|
||||
return paramNames;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.xyzh.common.utils.spring;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @description Spring上下文工具类 - 用于在非Spring管理的类中获取Bean
|
||||
* @filename SpringContextUtil.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
@Component
|
||||
public class SpringContextUtil implements ApplicationContextAware {
|
||||
|
||||
private static ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext context) throws BeansException {
|
||||
applicationContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取Spring容器中的Bean
|
||||
* @param beanClass Bean的Class类型
|
||||
* @return T Bean实例
|
||||
*/
|
||||
public static <T> T getBean(Class<T> beanClass) {
|
||||
if (applicationContext == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return applicationContext.getBean(beanClass);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取Spring容器中的Bean
|
||||
* @param beanName Bean的名称
|
||||
* @return Object Bean实例
|
||||
*/
|
||||
public static Object getBean(String beanName) {
|
||||
if (applicationContext == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return applicationContext.getBean(beanName);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取ApplicationContext
|
||||
*/
|
||||
public static ApplicationContext getApplicationContext() {
|
||||
return applicationContext;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
import org.xyzh.common.dto.crontab.TbCrontabTask;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -55,31 +56,42 @@ public interface CrontabTaskMapper extends BaseMapper<TbCrontabTask> {
|
||||
TbCrontabTask selectTaskById(@Param("taskId") String taskId);
|
||||
|
||||
/**
|
||||
* @description 根据过滤条件查询任务列表
|
||||
* @description 根据过滤条件查询任务列表(包含权限过滤)
|
||||
* @param filter 过滤条件
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbCrontabTask> 任务列表
|
||||
* @author yslg
|
||||
* @since 2025-10-25
|
||||
*/
|
||||
List<TbCrontabTask> selectTaskList(@Param("filter") TbCrontabTask filter);
|
||||
List<TbCrontabTask> selectTaskList(@Param("filter") TbCrontabTask filter, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 分页查询任务列表
|
||||
* @description 分页查询任务列表(包含权限过滤)
|
||||
* @param filter 过滤条件
|
||||
* @param pageParam 分页参数
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbCrontabTask> 任务列表
|
||||
* @author yslg
|
||||
* @since 2025-10-25
|
||||
*/
|
||||
List<TbCrontabTask> selectTaskPage(@Param("filter") TbCrontabTask filter, @Param("pageParam") PageParam pageParam);
|
||||
List<TbCrontabTask> selectTaskPage(@Param("filter") TbCrontabTask filter, @Param("pageParam") PageParam pageParam, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 查询所有运行中的任务
|
||||
* @description 查询所有运行中的任务(包含权限过滤)
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbCrontabTask> 任务列表
|
||||
* @author yslg
|
||||
* @since 2025-10-25
|
||||
*/
|
||||
List<TbCrontabTask> selectRunningTasks();
|
||||
List<TbCrontabTask> selectRunningTasks(@Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 查询所有运行中的任务(系统级,不含权限过滤,用于系统初始化)
|
||||
* @return List<TbCrontabTask> 任务列表
|
||||
* @author yslg
|
||||
* @since 2025-10-25
|
||||
*/
|
||||
List<TbCrontabTask> selectAllRunningTasks();
|
||||
|
||||
/**
|
||||
* @description 更新任务状态
|
||||
@@ -102,12 +114,13 @@ public interface CrontabTaskMapper extends BaseMapper<TbCrontabTask> {
|
||||
TbCrontabTask selectTaskByBeanAndMethod(@Param("beanName") String beanName, @Param("methodName") String methodName);
|
||||
|
||||
/**
|
||||
* @description 查询任务总数
|
||||
* @description 查询任务总数(包含权限过滤)
|
||||
* @param filter 过滤条件
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return int 任务总数
|
||||
* @author yslg
|
||||
* @since 2025-10-25
|
||||
*/
|
||||
int countSelectTask(@Param("filter") TbCrontabTask filter);
|
||||
int countSelectTask(@Param("filter") TbCrontabTask filter, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,8 +33,8 @@ public class SchedulerInitializer implements CommandLineRunner {
|
||||
try {
|
||||
logger.info("开始初始化定时任务...");
|
||||
|
||||
// 查询所有运行中的任务
|
||||
List<TbCrontabTask> runningTasks = taskMapper.selectRunningTasks();
|
||||
// 查询所有运行中的任务(系统级,不受权限限制)
|
||||
List<TbCrontabTask> runningTasks = taskMapper.selectAllRunningTasks();
|
||||
|
||||
if (runningTasks != null && !runningTasks.isEmpty()) {
|
||||
for (TbCrontabTask task : runningTasks) {
|
||||
|
||||
@@ -217,7 +217,8 @@ public class CrontabServiceImpl implements CrontabService {
|
||||
}
|
||||
filter.setDeleted(false);
|
||||
|
||||
List<TbCrontabTask> list = taskMapper.selectTaskList(filter);
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbCrontabTask> list = taskMapper.selectTaskList(filter, userDeptRoles);
|
||||
resultDomain.success("查询成功", list);
|
||||
} catch (Exception e) {
|
||||
logger.error("查询定时任务列表异常: ", e);
|
||||
@@ -239,8 +240,9 @@ public class CrontabServiceImpl implements CrontabService {
|
||||
pageParam = new PageParam();
|
||||
}
|
||||
|
||||
List<TbCrontabTask> list = taskMapper.selectTaskPage(filter, pageParam);
|
||||
int total = taskMapper.countSelectTask(filter);
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbCrontabTask> list = taskMapper.selectTaskPage(filter, pageParam, userDeptRoles);
|
||||
int total = taskMapper.countSelectTask(filter, userDeptRoles);
|
||||
|
||||
PageDomain<TbCrontabTask> pageDomain = new PageDomain<>();
|
||||
pageDomain.setDataList(list);
|
||||
|
||||
@@ -90,6 +90,37 @@
|
||||
</where>
|
||||
</sql>
|
||||
|
||||
<!-- 权限过滤条件(基于dept_path的高效继承) -->
|
||||
<sql id="Permission_Filter">
|
||||
INNER JOIN tb_resource_permission rp ON ct.task_id = rp.resource_id
|
||||
AND rp.resource_type = 7
|
||||
AND rp.deleted = 0
|
||||
AND rp.can_read = 1
|
||||
AND (
|
||||
-- 全局权限:所有用户可访问
|
||||
(rp.dept_id IS NULL AND rp.role_id IS NULL)
|
||||
<if test="userDeptRoles != null and userDeptRoles.size() > 0">
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM (
|
||||
<foreach collection="userDeptRoles" item="udr" separator=" UNION ALL ">
|
||||
SELECT #{udr.deptID} AS dept_id, #{udr.deptPath} AS dept_path, #{udr.roleID} AS role_id
|
||||
</foreach>
|
||||
) user_roles
|
||||
LEFT JOIN tb_sys_dept perm_dept ON perm_dept.dept_id = rp.dept_id AND perm_dept.deleted = 0
|
||||
WHERE
|
||||
-- 部门级权限:当前部门或父部门(通过dept_path判断继承关系)
|
||||
(rp.role_id IS NULL AND rp.dept_id IS NOT NULL
|
||||
AND user_roles.dept_path LIKE CONCAT(perm_dept.dept_path, '%'))
|
||||
-- 角色级权限:跨部门的角色权限
|
||||
OR (rp.dept_id IS NULL AND rp.role_id = user_roles.role_id)
|
||||
-- 精确权限:特定部门的特定角色
|
||||
OR (rp.dept_id = user_roles.dept_id AND rp.role_id = user_roles.role_id)
|
||||
)
|
||||
</if>
|
||||
)
|
||||
</sql>
|
||||
|
||||
<!-- 插入任务 -->
|
||||
<insert id="insertTask">
|
||||
INSERT INTO tb_crontab_task
|
||||
@@ -168,27 +199,69 @@
|
||||
|
||||
<!-- 根据过滤条件查询任务列表 -->
|
||||
<select id="selectTaskList" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_crontab_task
|
||||
<include refid="Filter_Where_Clause" />
|
||||
ORDER BY create_time DESC
|
||||
SELECT DISTINCT ct.*
|
||||
FROM tb_crontab_task ct
|
||||
<include refid="Permission_Filter" />
|
||||
WHERE ct.deleted = 0
|
||||
<if test="filter != null">
|
||||
<if test="filter.ID != null and filter.ID != ''">
|
||||
AND ct.id = #{filter.ID}
|
||||
</if>
|
||||
<if test="filter.taskId != null and filter.taskId != ''">
|
||||
AND ct.task_id = #{filter.taskId}
|
||||
</if>
|
||||
<if test="filter.taskName != null and filter.taskName != ''">
|
||||
AND ct.task_name LIKE CONCAT('%', #{filter.taskName}, '%')
|
||||
</if>
|
||||
<if test="filter.taskGroup != null and filter.taskGroup != ''">
|
||||
AND ct.task_group = #{filter.taskGroup}
|
||||
</if>
|
||||
<if test="filter.status != null">
|
||||
AND ct.status = #{filter.status}
|
||||
</if>
|
||||
</if>
|
||||
ORDER BY ct.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 分页查询任务列表 -->
|
||||
<select id="selectTaskPage" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_crontab_task
|
||||
<include refid="Filter_Where_Clause" />
|
||||
ORDER BY create_time DESC
|
||||
SELECT DISTINCT ct.*
|
||||
FROM tb_crontab_task ct
|
||||
<include refid="Permission_Filter" />
|
||||
WHERE ct.deleted = 0
|
||||
<if test="filter != null">
|
||||
<if test="filter.ID != null and filter.ID != ''">
|
||||
AND ct.id = #{filter.ID}
|
||||
</if>
|
||||
<if test="filter.taskId != null and filter.taskId != ''">
|
||||
AND ct.task_id = #{filter.taskId}
|
||||
</if>
|
||||
<if test="filter.taskName != null and filter.taskName != ''">
|
||||
AND ct.task_name LIKE CONCAT('%', #{filter.taskName}, '%')
|
||||
</if>
|
||||
<if test="filter.taskGroup != null and filter.taskGroup != ''">
|
||||
AND ct.task_group = #{filter.taskGroup}
|
||||
</if>
|
||||
<if test="filter.status != null">
|
||||
AND ct.status = #{filter.status}
|
||||
</if>
|
||||
</if>
|
||||
ORDER BY ct.create_time DESC
|
||||
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
||||
</select>
|
||||
|
||||
<!-- 查询所有运行中的任务 -->
|
||||
<!-- 查询所有运行中的任务(包含权限过滤) -->
|
||||
<select id="selectRunningTasks" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
SELECT DISTINCT ct.*
|
||||
FROM tb_crontab_task ct
|
||||
<include refid="Permission_Filter" />
|
||||
WHERE ct.status = 1 AND ct.deleted = 0
|
||||
ORDER BY ct.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 查询所有运行中的任务(系统级,不含权限过滤,用于系统初始化) -->
|
||||
<select id="selectAllRunningTasks" resultMap="BaseResultMap">
|
||||
SELECT *
|
||||
FROM tb_crontab_task
|
||||
WHERE status = 1 AND deleted = 0
|
||||
ORDER BY create_time DESC
|
||||
@@ -214,9 +287,27 @@
|
||||
</select>
|
||||
|
||||
<!-- countSelectTask -->
|
||||
|
||||
<select id="countSelectTask">
|
||||
SELECT COUNT(1) FROM tb_crontab_task
|
||||
<include refid="Filter_Where_Clause" />
|
||||
<select id="countSelectTask" resultType="int">
|
||||
SELECT COUNT(DISTINCT ct.id)
|
||||
FROM tb_crontab_task ct
|
||||
<include refid="Permission_Filter" />
|
||||
WHERE ct.deleted = 0
|
||||
<if test="filter != null">
|
||||
<if test="filter.ID != null and filter.ID != ''">
|
||||
AND ct.id = #{filter.ID}
|
||||
</if>
|
||||
<if test="filter.taskId != null and filter.taskId != ''">
|
||||
AND ct.task_id = #{filter.taskId}
|
||||
</if>
|
||||
<if test="filter.taskName != null and filter.taskName != ''">
|
||||
AND ct.task_name LIKE CONCAT('%', #{filter.taskName}, '%')
|
||||
</if>
|
||||
<if test="filter.taskGroup != null and filter.taskGroup != ''">
|
||||
AND ct.task_group = #{filter.taskGroup}
|
||||
</if>
|
||||
<if test="filter.status != null">
|
||||
AND ct.status = #{filter.status}
|
||||
</if>
|
||||
</if>
|
||||
</select>
|
||||
</mapper>
|
||||
|
||||
@@ -53,13 +53,14 @@ public interface BannerMapper extends BaseMapper<TbBanner> {
|
||||
TbBanner selectByBannerId(@Param("bannerId") String bannerId);
|
||||
|
||||
/**
|
||||
* @description 根据状态查询Banner列表
|
||||
* @description 根据状态查询Banner列表(包含权限过滤)
|
||||
* @param status 状态
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbBanner> Banner列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbBanner> selectByStatus(@Param("status") Integer status);
|
||||
List<TbBanner> selectByStatus(@Param("status") Integer status, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 根据位置查询Banner列表
|
||||
@@ -71,22 +72,24 @@ public interface BannerMapper extends BaseMapper<TbBanner> {
|
||||
List<TbBanner> selectByPosition(@Param("position") String position);
|
||||
|
||||
/**
|
||||
* @description 查询有效的Banner列表(按排序)
|
||||
* @description 查询有效的Banner列表(按排序,包含权限过滤)
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbBanner> Banner列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbBanner> selectActiveBanners();
|
||||
List<TbBanner> selectActiveBanners(@Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 检查Banner标题是否存在
|
||||
* @description 检查Banner标题是否存在(包含权限过滤)
|
||||
* @param title Banner标题
|
||||
* @param excludeId 排除的Banner ID(用于更新时排除自身)
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return int 存在的数量
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
int countByTitle(@Param("title") String title, @Param("excludeId") String excludeId);
|
||||
int countByTitle(@Param("title") String title, @Param("excludeId") String excludeId, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 插入Banner
|
||||
|
||||
@@ -39,68 +39,75 @@ public interface ResourceMapper extends BaseMapper<TbResource> {
|
||||
TbResource selectByResourceId(@Param("resourceId") String resourceId);
|
||||
|
||||
/**
|
||||
* @description 根据标签ID查询资源列表
|
||||
* @description 根据标签ID查询资源列表(包含权限过滤)
|
||||
* @param tagId 标签ID(文章分类标签,tagType=1)
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbResource> 资源列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbResource> selectByTagId(@Param("tagId") String tagId);
|
||||
List<TbResource> selectByTagId(@Param("tagId") String tagId, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 根据状态查询资源列表
|
||||
* @description 根据状态查询资源列表(包含权限过滤)
|
||||
* @param status 状态
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbResource> 资源列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbResource> selectByStatus(@Param("status") Integer status);
|
||||
List<TbResource> selectByStatus(@Param("status") Integer status, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 根据类型查询资源列表
|
||||
* @description 根据类型查询资源列表(包含权限过滤)
|
||||
* @param type 类型
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbResource> 资源列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbResource> selectByType(@Param("type") Integer type);
|
||||
List<TbResource> selectByType(@Param("type") Integer type, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 查询热门资源列表
|
||||
* @description 查询热门资源列表(包含权限过滤)
|
||||
* @param limit 限制数量
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbResource> 资源列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbResource> selectHotResources(@Param("limit") Integer limit);
|
||||
List<TbResource> selectHotResources(@Param("limit") Integer limit, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 查询最新资源列表
|
||||
* @description 查询最新资源列表(包含权限过滤)
|
||||
* @param limit 限制数量
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbResource> 资源列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbResource> selectLatestResources(@Param("limit") Integer limit);
|
||||
List<TbResource> selectLatestResources(@Param("limit") Integer limit, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 根据关键词搜索资源
|
||||
* @description 根据关键词搜索资源(包含权限过滤)
|
||||
* @param keyword 关键词
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbResource> 资源列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbResource> searchByKeyword(@Param("keyword") String keyword);
|
||||
List<TbResource> searchByKeyword(@Param("keyword") String keyword, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 检查资源标题是否存在
|
||||
* @description 检查资源标题是否存在(包含权限过滤)
|
||||
* @param title 资源标题
|
||||
* @param excludeId 排除的资源ID(用于更新时排除自身)
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return int 存在的数量
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
int countByTitle(@Param("title") String title, @Param("excludeId") String excludeId);
|
||||
int countByTitle(@Param("title") String title, @Param("excludeId") String excludeId, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 插入资源
|
||||
|
||||
@@ -39,22 +39,24 @@ public interface TagMapper extends BaseMapper<TbTag> {
|
||||
TbTag selectByTagId(@Param("tagId") String tagId);
|
||||
|
||||
/**
|
||||
* @description 根据标签名称查询标签
|
||||
* @description 根据标签名称查询标签(包含权限过滤)
|
||||
* @param name 标签名称
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return TbTag 标签信息
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
TbTag selectByName(@Param("name") String name);
|
||||
TbTag selectByName(@Param("name") String name, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 根据状态查询标签列表
|
||||
* @description 根据状态查询标签列表(包含权限过滤)
|
||||
* @param status 状态
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbTag> 标签列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbTag> selectByStatus(@Param("status") Integer status);
|
||||
List<TbTag> selectByStatus(@Param("status") Integer status, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 根据标签类型查询标签列表
|
||||
@@ -75,15 +77,16 @@ public interface TagMapper extends BaseMapper<TbTag> {
|
||||
List<TbTag> selectHotTags(@Param("limit") Integer limit);
|
||||
|
||||
/**
|
||||
* @description 检查标签名称是否存在(同类型下)
|
||||
* @description 检查标签名称是否存在(同类型下,包含权限过滤)
|
||||
* @param name 标签名称
|
||||
* @param tagType 标签类型
|
||||
* @param excludeId 排除的标签ID(用于更新时排除自身)
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return int 存在的数量
|
||||
* @author yslg
|
||||
* @since 2025-10-27
|
||||
*/
|
||||
int countByNameAndType(@Param("name") String name, @Param("tagType") Integer tagType, @Param("excludeId") String excludeId);
|
||||
int countByNameAndType(@Param("name") String name, @Param("tagType") Integer tagType, @Param("excludeId") String excludeId, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 插入标签
|
||||
|
||||
@@ -85,7 +85,8 @@ public class NCBannerServiceImpl implements BannerService {
|
||||
}
|
||||
|
||||
// 检查标题是否已存在
|
||||
int count = bannerMapper.countByTitle(banner.getTitle(), null);
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
int count = bannerMapper.countByTitle(banner.getTitle(), null, userDeptRoles);
|
||||
if (count > 0) {
|
||||
resultDomain.fail("横幅标题已存在");
|
||||
return resultDomain;
|
||||
@@ -115,7 +116,6 @@ public class NCBannerServiceImpl implements BannerService {
|
||||
|
||||
// 创建横幅资源权限
|
||||
try {
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
if (userDeptRoles != null && !userDeptRoles.isEmpty()) {
|
||||
// 使用用户的第一个部门角色创建权限
|
||||
resourcePermissionService.createResourcePermission(
|
||||
@@ -162,7 +162,8 @@ public class NCBannerServiceImpl implements BannerService {
|
||||
|
||||
// 如果修改了标题,检查标题是否已被使用
|
||||
if (StringUtils.hasText(banner.getTitle()) && !banner.getTitle().equals(existing.getTitle())) {
|
||||
int count = bannerMapper.countByTitle(banner.getTitle(), banner.getBannerID());
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
int count = bannerMapper.countByTitle(banner.getTitle(), banner.getBannerID(), userDeptRoles);
|
||||
if (count > 0) {
|
||||
resultDomain.fail("横幅标题已存在");
|
||||
return resultDomain;
|
||||
@@ -318,7 +319,8 @@ public class NCBannerServiceImpl implements BannerService {
|
||||
public ResultDomain<TbBanner> getActiveBanners(Integer limit) {
|
||||
ResultDomain<TbBanner> resultDomain = new ResultDomain<>();
|
||||
try {
|
||||
List<TbBanner> list = bannerMapper.selectActiveBanners();
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbBanner> list = bannerMapper.selectActiveBanners(userDeptRoles);
|
||||
|
||||
// 如果指定了limit,截取列表
|
||||
if (limit != null && limit > 0 && list != null && list.size() > limit) {
|
||||
|
||||
@@ -146,7 +146,8 @@ public class NCResourceServiceImpl implements ResourceService {
|
||||
}
|
||||
|
||||
// 检查标题是否已存在
|
||||
int count = resourceMapper.countByTitle(resourceVO.getResource().getTitle(), null);
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
int count = resourceMapper.countByTitle(resourceVO.getResource().getTitle(), null, userDeptRoles);
|
||||
if (count > 0) {
|
||||
resultDomain.fail("资源标题已存在");
|
||||
return resultDomain;
|
||||
@@ -205,7 +206,6 @@ public class NCResourceServiceImpl implements ResourceService {
|
||||
|
||||
// 创建资源权限
|
||||
try {
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
if (userDeptRoles != null && !userDeptRoles.isEmpty()) {
|
||||
// 使用用户的第一个部门角色创建权限
|
||||
resourcePermissionService.createResourcePermission(
|
||||
@@ -259,7 +259,8 @@ public class NCResourceServiceImpl implements ResourceService {
|
||||
|
||||
// 如果修改了标题,检查标题是否已被使用
|
||||
if (StringUtils.hasText(resource.getTitle()) && !resource.getTitle().equals(existing.getTitle())) {
|
||||
int count = resourceMapper.countByTitle(resource.getTitle(), resource.getResourceID());
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
int count = resourceMapper.countByTitle(resource.getTitle(), resource.getResourceID(), userDeptRoles);
|
||||
if (count > 0) {
|
||||
resultDomain.fail("资源标题已存在");
|
||||
return resultDomain;
|
||||
@@ -762,7 +763,8 @@ public class NCResourceServiceImpl implements ResourceService {
|
||||
}
|
||||
|
||||
// 先按关键词搜索
|
||||
List<TbResource> list = resourceMapper.searchByKeyword(keyword);
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbResource> list = resourceMapper.searchByKeyword(keyword, userDeptRoles);
|
||||
|
||||
// 如果指定了分类ID,进行过滤
|
||||
if (StringUtils.hasText(tagID) && list != null) {
|
||||
|
||||
@@ -58,7 +58,8 @@ public class NCTagServiceImpl implements TagService {
|
||||
}
|
||||
|
||||
// 检查标签名称是否已存在(同类型下)
|
||||
int count = tagMapper.countByNameAndType(tag.getName(), tag.getTagType(), null);
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
int count = tagMapper.countByNameAndType(tag.getName(), tag.getTagType(), null, userDeptRoles);
|
||||
if (count > 0) {
|
||||
resultDomain.fail("该类型下标签名称已存在");
|
||||
return resultDomain;
|
||||
@@ -83,7 +84,6 @@ public class NCTagServiceImpl implements TagService {
|
||||
|
||||
// 创建标签资源权限
|
||||
try {
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
if (userDeptRoles != null && !userDeptRoles.isEmpty()) {
|
||||
// 使用用户的第一个部门角色创建权限
|
||||
resourcePermissionService.createResourcePermission(
|
||||
|
||||
@@ -76,7 +76,6 @@
|
||||
AND rp.deleted = 0
|
||||
AND rp.can_read = 1
|
||||
AND (
|
||||
-- 全局权限:所有用户可访问
|
||||
(rp.dept_id IS NULL AND rp.role_id IS NULL)
|
||||
<if test="userDeptRoles != null and userDeptRoles.size() > 0">
|
||||
OR EXISTS (
|
||||
@@ -88,12 +87,9 @@
|
||||
) user_roles
|
||||
LEFT JOIN tb_sys_dept perm_dept ON perm_dept.dept_id = rp.dept_id AND perm_dept.deleted = 0
|
||||
WHERE
|
||||
-- 部门级权限:当前部门或父部门(通过dept_path判断继承关系)
|
||||
(rp.role_id IS NULL AND rp.dept_id IS NOT NULL
|
||||
AND user_roles.dept_path LIKE CONCAT(perm_dept.dept_path, '%'))
|
||||
-- 角色级权限:跨部门的角色权限
|
||||
OR (rp.dept_id IS NULL AND rp.role_id = user_roles.role_id)
|
||||
-- 精确权限:特定部门的特定角色
|
||||
OR (rp.dept_id = user_roles.dept_id AND rp.role_id = user_roles.role_id)
|
||||
)
|
||||
</if>
|
||||
@@ -157,13 +153,13 @@
|
||||
WHERE banner_id = #{bannerID} AND deleted = 0
|
||||
</select>
|
||||
|
||||
<!-- 根据状态查询Banner列表 -->
|
||||
<!-- 根据状态查询Banner列表 - 添加权限过滤 -->
|
||||
<select id="selectByStatus" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_banner
|
||||
WHERE status = #{status} AND deleted = 0
|
||||
ORDER BY order_num ASC, create_time DESC
|
||||
SELECT DISTINCT b.*
|
||||
FROM tb_banner b
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE b.status = #{status} AND b.deleted = 0
|
||||
ORDER BY b.order_num ASC, b.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 根据位置查询Banner列表 -->
|
||||
@@ -175,22 +171,23 @@
|
||||
ORDER BY order_num ASC, create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 查询有效的Banner列表(按排序) -->
|
||||
<!-- 查询有效的Banner列表(按排序) - 添加权限过滤 -->
|
||||
<select id="selectActiveBanners" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_banner
|
||||
WHERE status = 1 AND deleted = 0
|
||||
ORDER BY order_num ASC, create_time DESC
|
||||
SELECT DISTINCT b.*
|
||||
FROM tb_banner b
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE b.status = 1 AND b.deleted = 0
|
||||
ORDER BY b.order_num ASC, b.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 检查Banner标题是否存在 -->
|
||||
<!-- 检查Banner标题是否存在 - 添加权限过滤 -->
|
||||
<select id="countByTitle" resultType="int">
|
||||
SELECT COUNT(1)
|
||||
FROM tb_banner
|
||||
WHERE title = #{title} AND deleted = 0
|
||||
SELECT COUNT(DISTINCT b.id)
|
||||
FROM tb_banner b
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE b.title = #{title} AND b.deleted = 0
|
||||
<if test="excludeId != null and excludeId != ''">
|
||||
AND banner_id != #{excludeId}
|
||||
AND b.banner_id != #{excludeId}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
|
||||
@@ -130,38 +130,38 @@
|
||||
|
||||
<!-- 根据标签ID查询资源列表 -->
|
||||
<select id="selectByTagId" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_resource
|
||||
WHERE tag_id = #{tagId} AND deleted = 0
|
||||
ORDER BY publish_time DESC, create_time DESC
|
||||
SELECT DISTINCT r.*
|
||||
FROM tb_resource r
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE r.tag_id = #{tagId} AND r.deleted = 0
|
||||
ORDER BY r.publish_time DESC, r.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 根据状态查询资源列表 -->
|
||||
<select id="selectByStatus" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_resource
|
||||
WHERE status = #{status} AND deleted = 0
|
||||
ORDER BY publish_time DESC, create_time DESC
|
||||
SELECT DISTINCT r.*
|
||||
FROM tb_resource r
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE r.status = #{status} AND r.deleted = 0
|
||||
ORDER BY r.publish_time DESC, r.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 根据标签类型查询资源列表 -->
|
||||
<select id="selectByType" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_resource
|
||||
WHERE tag_id = #{type} AND deleted = 0
|
||||
ORDER BY publish_time DESC, create_time DESC
|
||||
SELECT DISTINCT r.*
|
||||
FROM tb_resource r
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE r.tag_id = #{type} AND r.deleted = 0
|
||||
ORDER BY r.publish_time DESC, r.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 查询热门资源列表 -->
|
||||
<select id="selectHotResources" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_resource
|
||||
WHERE status = 1 AND deleted = 0
|
||||
ORDER BY view_count DESC, like_count DESC, publish_time DESC
|
||||
SELECT DISTINCT r.*
|
||||
FROM tb_resource r
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE r.status = 1 AND r.deleted = 0
|
||||
ORDER BY r.view_count DESC, r.like_count DESC, r.publish_time DESC
|
||||
<if test="limit != null and limit > 0">
|
||||
LIMIT #{limit}
|
||||
</if>
|
||||
@@ -169,11 +169,11 @@
|
||||
|
||||
<!-- 查询最新资源列表 -->
|
||||
<select id="selectLatestResources" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_resource
|
||||
WHERE status = 1 AND deleted = 0
|
||||
ORDER BY publish_time DESC, create_time DESC
|
||||
SELECT DISTINCT r.*
|
||||
FROM tb_resource r
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE r.status = 1 AND r.deleted = 0
|
||||
ORDER BY r.publish_time DESC, r.create_time DESC
|
||||
<if test="limit != null and limit > 0">
|
||||
LIMIT #{limit}
|
||||
</if>
|
||||
@@ -181,23 +181,24 @@
|
||||
|
||||
<!-- 根据关键词搜索资源 -->
|
||||
<select id="searchByKeyword" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_resource
|
||||
WHERE (title LIKE CONCAT('%', #{keyword}, '%')
|
||||
SELECT DISTINCT r.*
|
||||
FROM tb_resource r
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE (r.title LIKE CONCAT('%', #{keyword}, '%')
|
||||
OR content LIKE CONCAT('%', #{keyword}, '%')
|
||||
OR summary LIKE CONCAT('%', #{keyword}, '%'))
|
||||
AND status = 1 AND deleted = 0
|
||||
ORDER BY publish_time DESC, create_time DESC
|
||||
AND r.status = 1 AND r.deleted = 0
|
||||
ORDER BY r.publish_time DESC, r.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 检查资源标题是否存在 -->
|
||||
<select id="countByTitle" resultType="int">
|
||||
SELECT COUNT(1)
|
||||
FROM tb_resource
|
||||
WHERE title = #{title} AND deleted = 0
|
||||
SELECT COUNT(DISTINCT r.id)
|
||||
FROM tb_resource r
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE r.title = #{title} AND r.deleted = 0
|
||||
<if test="excludeId != null and excludeId != ''">
|
||||
AND resource_id != #{excludeId}
|
||||
AND r.resource_id != #{excludeId}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
@@ -205,14 +206,10 @@
|
||||
<insert id="insertResource" parameterType="org.xyzh.common.dto.resource.TbResource">
|
||||
INSERT INTO tb_resource (
|
||||
id, resource_id, title, content, summary, cover_image, tag_id, author, source,
|
||||
source_url, view_count, like_count, collect_count, status, is_recommend,
|
||||
is_banner, publish_time, creator, updater, create_time, update_time,
|
||||
delete_time, deleted
|
||||
source_url, creator,create_time
|
||||
) VALUES (
|
||||
#{id}, #{resourceID}, #{title}, #{content}, #{summary}, #{coverImage}, #{tagID}, #{author}, #{source},
|
||||
#{sourceUrl}, #{viewCount}, #{likeCount}, #{collectCount}, #{status}, #{isRecommend},
|
||||
#{isBanner}, #{publishTime}, #{creator}, #{updater}, #{createTime}, #{updateTime},
|
||||
#{deleteTime}, #{deleted}
|
||||
#{sourceUrl}, #{creator}, #{createTime}
|
||||
)
|
||||
</insert>
|
||||
|
||||
@@ -291,17 +288,13 @@
|
||||
<insert id="batchInsertResources" parameterType="java.util.List">
|
||||
INSERT INTO tb_resource (
|
||||
id, resource_id, title, content, summary, cover_image, tag_id, author, source,
|
||||
source_url, view_count, like_count, collect_count, status, is_recommend,
|
||||
is_banner, publish_time, creator, updater, create_time, update_time,
|
||||
delete_time, deleted
|
||||
source_url, creator,create_time
|
||||
) VALUES
|
||||
<foreach collection="resourceList" item="item" separator=",">
|
||||
(
|
||||
#{item.id}, #{item.resourceID}, #{item.title}, #{item.content}, #{item.summary}, #{item.coverImage},
|
||||
#{item.tagID}, #{item.author}, #{item.source}, #{item.sourceUrl},
|
||||
#{item.viewCount}, #{item.likeCount}, #{item.collectCount}, #{item.status},
|
||||
#{item.isRecommend}, #{item.isBanner}, #{item.publishTime}, #{item.creator},
|
||||
#{item.updater}, #{item.createTime}, #{item.updateTime}, #{item.deleteTime}, #{item.deleted}
|
||||
#{item.creator}, #{item.createTime}
|
||||
)
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
@@ -103,21 +103,21 @@
|
||||
WHERE tag_id = #{tagId} AND deleted = 0
|
||||
</select>
|
||||
|
||||
<!-- 根据标签名称查询标签 -->
|
||||
<!-- 根据标签名称查询标签 - 添加权限过滤 -->
|
||||
<select id="selectByName" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_tag
|
||||
WHERE name = #{name} AND deleted = 0
|
||||
SELECT DISTINCT t.*
|
||||
FROM tb_tag t
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE t.name = #{name} AND t.deleted = 0
|
||||
</select>
|
||||
|
||||
<!-- 根据状态查询标签列表 -->
|
||||
<!-- 根据状态查询标签列表 - 添加权限过滤 -->
|
||||
<select id="selectByStatus" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_tag
|
||||
WHERE deleted = 0
|
||||
ORDER BY create_time DESC
|
||||
SELECT DISTINCT t.*
|
||||
FROM tb_tag t
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE t.deleted = 0
|
||||
ORDER BY t.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 根据标签类型查询标签列表 -->
|
||||
@@ -144,16 +144,17 @@
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<!-- 检查标签名称是否存在(同类型下) -->
|
||||
<!-- 检查标签名称是否存在(同类型下) - 添加权限过滤 -->
|
||||
<select id="countByNameAndType" resultType="int">
|
||||
SELECT COUNT(1)
|
||||
FROM tb_tag
|
||||
WHERE name = #{name} AND deleted = 0
|
||||
SELECT COUNT(DISTINCT t.id)
|
||||
FROM tb_tag t
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE t.name = #{name} AND t.deleted = 0
|
||||
<if test="tagType != null">
|
||||
AND tag_type = #{tagType}
|
||||
AND t.tag_type = #{tagType}
|
||||
</if>
|
||||
<if test="excludeId != null and excludeId != ''">
|
||||
AND id != #{excludeId}
|
||||
AND t.id != #{excludeId}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
|
||||
@@ -39,77 +39,85 @@ public interface CourseMapper extends BaseMapper<TbCourse> {
|
||||
TbCourse selectByCourseId(@Param("courseId") String courseId);
|
||||
|
||||
/**
|
||||
* @description 根据课程名称查询课程
|
||||
* @description 根据课程名称查询课程(包含权限过滤)
|
||||
* @param name 课程名称
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return TbCourse 课程信息
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
TbCourse selectByName(@Param("name") String name);
|
||||
TbCourse selectByName(@Param("name") String name, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 根据状态查询课程列表
|
||||
* @description 根据状态查询课程列表(包含权限过滤)
|
||||
* @param status 状态
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbCourse> 课程列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbCourse> selectByStatus(@Param("status") Integer status);
|
||||
List<TbCourse> selectByStatus(@Param("status") Integer status, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 根据类型查询课程列表
|
||||
* @description 根据类型查询课程列表(包含权限过滤)
|
||||
* @param type 类型
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbCourse> 课程列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbCourse> selectByType(@Param("type") Integer type);
|
||||
List<TbCourse> selectByType(@Param("type") Integer type, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 根据难度查询课程列表
|
||||
* @description 根据难度查询课程列表(包含权限过滤)
|
||||
* @param difficulty 难度
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbCourse> 课程列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbCourse> selectByDifficulty(@Param("difficulty") Integer difficulty);
|
||||
List<TbCourse> selectByDifficulty(@Param("difficulty") Integer difficulty, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 查询热门课程列表
|
||||
* @description 查询热门课程列表(包含权限过滤)
|
||||
* @param limit 限制数量
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbCourse> 课程列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbCourse> selectHotCourses(@Param("limit") Integer limit);
|
||||
List<TbCourse> selectHotCourses(@Param("limit") Integer limit, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 查询最新课程列表
|
||||
* @description 查询最新课程列表(包含权限过滤)
|
||||
* @param limit 限制数量
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbCourse> 课程列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbCourse> selectLatestCourses(@Param("limit") Integer limit);
|
||||
List<TbCourse> selectLatestCourses(@Param("limit") Integer limit, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 根据关键词搜索课程
|
||||
* @description 根据关键词搜索课程(包含权限过滤)
|
||||
* @param keyword 关键词
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbCourse> 课程列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbCourse> searchByKeyword(@Param("keyword") String keyword);
|
||||
List<TbCourse> searchByKeyword(@Param("keyword") String keyword, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 检查课程名称是否存在
|
||||
* @description 检查课程名称是否存在(包含权限过滤)
|
||||
* @param name 课程名称
|
||||
* @param excludeId 排除的课程ID(用于更新时排除自身)
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return int 存在的数量
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
int countByName(@Param("name") String name, @Param("excludeId") String excludeId);
|
||||
int countByName(@Param("name") String name, @Param("excludeId") String excludeId, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 插入课程
|
||||
|
||||
@@ -58,22 +58,24 @@ public interface LearningTaskMapper extends BaseMapper<TbLearningTask> {
|
||||
TbLearningTask selectByName(@Param("name") String name);
|
||||
|
||||
/**
|
||||
* @description 根据状态查询学习任务列表
|
||||
* @description 根据状态查询学习任务列表(包含权限过滤)
|
||||
* @param status 状态
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbLearningTask> 学习任务列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbLearningTask> selectByStatus(@Param("status") Integer status);
|
||||
List<TbLearningTask> selectByStatus(@Param("status") Integer status, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 根据优先级查询学习任务列表
|
||||
* @description 根据优先级查询学习任务列表(包含权限过滤)
|
||||
* @param priority 优先级
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbLearningTask> 学习任务列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
List<TbLearningTask> selectByPriority(@Param("priority") Integer priority);
|
||||
List<TbLearningTask> selectByPriority(@Param("priority") Integer priority, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 查询用户待完成任务列表
|
||||
@@ -94,14 +96,15 @@ public interface LearningTaskMapper extends BaseMapper<TbLearningTask> {
|
||||
List<TbLearningTask> selectCompletedTasksByUserId(@Param("userId") String userId);
|
||||
|
||||
/**
|
||||
* @description 检查任务名称是否存在
|
||||
* @description 检查任务名称是否存在(包含权限过滤)
|
||||
* @param name 任务名称
|
||||
* @param excludeId 排除的任务ID(用于更新时排除自身)
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return int 存在的数量
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
*/
|
||||
int countByName(@Param("name") String name, @Param("excludeId") String excludeId);
|
||||
int countByName(@Param("name") String name, @Param("excludeId") String excludeId, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 插入学习任务
|
||||
|
||||
@@ -140,46 +140,46 @@
|
||||
|
||||
<!-- 根据课程名称查询课程 -->
|
||||
<select id="selectByName" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_course
|
||||
WHERE name = #{name} AND deleted = 0
|
||||
SELECT DISTINCT c.*
|
||||
FROM tb_course c
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE c.name = #{name} AND c.deleted = 0
|
||||
</select>
|
||||
|
||||
<!-- 根据状态查询课程列表 -->
|
||||
<select id="selectByStatus" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_course
|
||||
WHERE status = #{status} AND deleted = 0
|
||||
ORDER BY order_num ASC, create_time DESC
|
||||
SELECT DISTINCT c.*
|
||||
FROM tb_course c
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE c.status = #{status} AND c.deleted = 0
|
||||
ORDER BY c.order_num ASC, c.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 根据类型查询课程列表 -->
|
||||
<select id="selectByType" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_course
|
||||
WHERE deleted = 0
|
||||
ORDER BY order_num ASC, create_time DESC
|
||||
SELECT DISTINCT c.*
|
||||
FROM tb_course c
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE c.deleted = 0
|
||||
ORDER BY c.order_num ASC, c.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 根据难度查询课程列表 -->
|
||||
<select id="selectByDifficulty" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_course
|
||||
WHERE deleted = 0
|
||||
ORDER BY order_num ASC, create_time DESC
|
||||
SELECT DISTINCT c.*
|
||||
FROM tb_course c
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE c.deleted = 0
|
||||
ORDER BY c.order_num ASC, c.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 查询热门课程列表 -->
|
||||
<select id="selectHotCourses" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_course
|
||||
WHERE status = 1 AND deleted = 0
|
||||
ORDER BY view_count DESC, learn_count DESC, create_time DESC
|
||||
SELECT DISTINCT c.*
|
||||
FROM tb_course c
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE c.status = 1 AND c.deleted = 0
|
||||
ORDER BY c.view_count DESC, c.learn_count DESC, c.create_time DESC
|
||||
<if test="limit != null and limit > 0">
|
||||
LIMIT #{limit}
|
||||
</if>
|
||||
@@ -187,11 +187,11 @@
|
||||
|
||||
<!-- 查询最新课程列表 -->
|
||||
<select id="selectLatestCourses" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_course
|
||||
WHERE status = 1 AND deleted = 0
|
||||
ORDER BY create_time DESC
|
||||
SELECT DISTINCT c.*
|
||||
FROM tb_course c
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE c.status = 1 AND c.deleted = 0
|
||||
ORDER BY c.create_time DESC
|
||||
<if test="limit != null and limit > 0">
|
||||
LIMIT #{limit}
|
||||
</if>
|
||||
@@ -199,24 +199,25 @@
|
||||
|
||||
<!-- 根据关键词搜索课程 -->
|
||||
<select id="searchByKeyword" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_course
|
||||
WHERE (name LIKE CONCAT('%', #{keyword}, '%')
|
||||
OR description LIKE CONCAT('%', #{keyword}, '%')
|
||||
OR content LIKE CONCAT('%', #{keyword}, '%')
|
||||
OR teacher LIKE CONCAT('%', #{keyword}, '%'))
|
||||
AND status = 1 AND deleted = 0
|
||||
ORDER BY order_num ASC, create_time DESC
|
||||
SELECT DISTINCT c.*
|
||||
FROM tb_course c
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE (c.name LIKE CONCAT('%', #{keyword}, '%')
|
||||
OR c.description LIKE CONCAT('%', #{keyword}, '%')
|
||||
OR c.content LIKE CONCAT('%', #{keyword}, '%')
|
||||
OR c.teacher LIKE CONCAT('%', #{keyword}, '%'))
|
||||
AND c.status = 1 AND c.deleted = 0
|
||||
ORDER BY c.order_num ASC, c.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 检查课程名称是否存在 -->
|
||||
<!-- 检查课程名称是否存在 - 添加权限过滤 -->
|
||||
<select id="countByName" resultType="int">
|
||||
SELECT COUNT(1)
|
||||
FROM tb_course
|
||||
WHERE name = #{name} AND deleted = 0
|
||||
SELECT COUNT(DISTINCT c.id)
|
||||
FROM tb_course c
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE c.name = #{name} AND c.deleted = 0
|
||||
<if test="excludeId != null and excludeId != ''">
|
||||
AND id != #{excludeId}
|
||||
AND c.id != #{excludeId}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
@@ -224,10 +225,10 @@
|
||||
<insert id="insertCourse" parameterType="org.xyzh.common.dto.study.TbCourse">
|
||||
INSERT INTO tb_course (
|
||||
id, course_id, name, cover_image, description, content, duration,
|
||||
teacher, status, view_count, learn_count, order_num, creator, create_time
|
||||
teacher, status, order_num, creator, create_time
|
||||
) VALUES (
|
||||
#{id}, #{courseID}, #{name}, #{coverImage}, #{description}, #{content}, #{duration},
|
||||
#{teacher}, #{status}, 0, 0, #{orderNum}, #{creator},#{createTime}
|
||||
#{teacher}, #{status}, #{orderNum}, #{creator},#{createTime}
|
||||
)
|
||||
</insert>
|
||||
|
||||
@@ -291,14 +292,14 @@
|
||||
<insert id="batchInsertCourses" parameterType="java.util.List">
|
||||
INSERT INTO tb_course (
|
||||
id, course_id, name, cover_image, description, content, duration,
|
||||
teacher, status, view_count, learn_count, order_num, creator,
|
||||
create_time, delete_time, deleted
|
||||
teacher, status, order_num, creator,
|
||||
create_time
|
||||
) VALUES
|
||||
<foreach collection="courseList" item="item" separator=",">
|
||||
(
|
||||
#{item.id}, #{item.courseID}, #{item.name}, #{item.coverImage}, #{item.description},
|
||||
#{item.content}, #{item.duration}, #{item.teacher}, #{item.status}, 0,
|
||||
0, #{item.orderNum}, #{item.creator}, #{item.createTime}, #{item.deleteTime}, #{item.deleted}
|
||||
#{item.content}, #{item.duration}, #{item.teacher}, #{item.status},
|
||||
#{item.orderNum}, #{item.creator}, #{item.createTime}
|
||||
)
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
@@ -124,28 +124,28 @@
|
||||
|
||||
<!-- 根据任务名称查询任务 -->
|
||||
<select id="selectByName" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_learning_task
|
||||
WHERE name = #{name} AND deleted = 0
|
||||
SELECT DISTINCT t.*
|
||||
FROM tb_learning_task t
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE t.name = #{name} AND t.deleted = 0
|
||||
</select>
|
||||
|
||||
<!-- 根据状态查询学习任务列表 -->
|
||||
<select id="selectByStatus" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_learning_task
|
||||
WHERE status = #{status} AND deleted = 0
|
||||
ORDER BY create_time DESC
|
||||
SELECT DISTINCT t.*
|
||||
FROM tb_learning_task t
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE t.status = #{status} AND t.deleted = 0
|
||||
ORDER BY t.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 根据优先级查询学习任务列表 -->
|
||||
<select id="selectByPriority" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_learning_task
|
||||
WHERE deleted = 0
|
||||
ORDER BY create_time DESC
|
||||
SELECT DISTINCT t.*
|
||||
FROM tb_learning_task t
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE t.deleted = 0
|
||||
ORDER BY t.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 查询用户待完成任务列表 -->
|
||||
@@ -153,7 +153,9 @@
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_learning_task
|
||||
WHERE status = 0 AND deleted = 0
|
||||
LEFT JOIN tb_task_user tu ON t.task_id = tu.task_id
|
||||
WHERE t.status = 0 AND t.deleted = 0
|
||||
AND tu.user_id = #{userId}
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
@@ -162,17 +164,20 @@
|
||||
SELECT
|
||||
<include refid="Base_Column_List" />
|
||||
FROM tb_learning_task
|
||||
WHERE status = 1 AND deleted = 0
|
||||
ORDER BY create_time DESC
|
||||
LEFT JOIN tb_task_user tu ON t.task_id = tu.task_id
|
||||
WHERE t.status = 1 AND t.deleted = 0
|
||||
AND tu.user_id = #{userId}
|
||||
ORDER BY t.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 检查任务名称是否存在 -->
|
||||
<select id="countByName" resultType="int">
|
||||
SELECT COUNT(1)
|
||||
FROM tb_learning_task
|
||||
WHERE name = #{name} AND deleted = 0
|
||||
SELECT COUNT(DISTINCT t.id)
|
||||
FROM tb_learning_task t
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE t.name = #{name} AND t.deleted = 0
|
||||
<if test="excludeId != null and excludeId != ''">
|
||||
AND id != #{excludeId}
|
||||
AND t.id != #{excludeId}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
<result column="resource_name" property="resourceName" jdbcType="VARCHAR"/>
|
||||
<result column="user_id" property="userID" jdbcType="VARCHAR"/>
|
||||
<result column="username" property="username" jdbcType="VARCHAR"/>
|
||||
<result column="dept_id" property="deptID" jdbcType="VARCHAR"/>
|
||||
<result column="dept_name" property="deptName" jdbcType="VARCHAR"/>
|
||||
<result column="parent_id" property="parentID" jdbcType="VARCHAR"/>
|
||||
<result column="status" property="status" jdbcType="INTEGER"/>
|
||||
<result column="progress" property="progress" jdbcType="DECIMAL"/>
|
||||
<result column="complete_time" property="completeTime" jdbcType="TIMESTAMP"/>
|
||||
@@ -86,21 +89,44 @@
|
||||
|
||||
<select id="selectTaskItemByTaskId" resultMap="TaskItemResultMap">
|
||||
SELECT
|
||||
ttu.*,
|
||||
tu.username
|
||||
ttu.id,
|
||||
ttu.task_id,
|
||||
ttu.user_id,
|
||||
ttu.status,
|
||||
ttu.progress,
|
||||
ttu.complete_time,
|
||||
ttu.creator,
|
||||
ttu.create_time,
|
||||
tu.username,
|
||||
ttu.dept_id,
|
||||
d.name as dept_name,
|
||||
d.parent_id
|
||||
FROM tb_task_user ttu
|
||||
INNER JOIN tb_sys_user tu ON ttu.user_id = tu.id
|
||||
WHERE ttu.task_id = #{taskId} and ttu.deleted = 0 and tu.deleted = 0
|
||||
INNER JOIN tb_sys_user tu ON ttu.user_id = tu.id AND tu.deleted = 0
|
||||
LEFT JOIN tb_sys_user_dept_role udr ON tu.id = udr.user_id AND udr.deleted = 0
|
||||
LEFT JOIN tb_sys_dept d ON udr.dept_id = d.dept_id AND d.deleted = 0
|
||||
WHERE ttu.task_id = #{taskId} AND ttu.deleted = 0
|
||||
ORDER BY ttu.create_time ASC
|
||||
</select>
|
||||
|
||||
<select id="selectUserTaskItem" resultMap="TaskItemResultMap">
|
||||
SELECT
|
||||
ttu.*,
|
||||
tu.username
|
||||
ttu.id,
|
||||
ttu.task_id,
|
||||
ttu.user_id,
|
||||
ttu.status,
|
||||
ttu.progress,
|
||||
ttu.complete_time,
|
||||
ttu.creator,
|
||||
ttu.create_time,
|
||||
tu.username,
|
||||
ttu.dept_id,
|
||||
d.name as dept_name,
|
||||
d.parent_id
|
||||
FROM tb_task_user ttu
|
||||
INNER JOIN tb_sys_user tu ON ttu.user_id = tu.id
|
||||
WHERE ttu.task_id = #{taskId} and ttu.user_id = #{userId} and ttu.deleted = 0 and tu.deleted = 0
|
||||
INNER JOIN tb_sys_user tu ON ttu.user_id = tu.id AND tu.deleted = 0
|
||||
LEFT JOIN tb_sys_dept d ON ttu.dept_id = d.dept_id AND d.deleted = 0
|
||||
WHERE ttu.task_id = #{taskId} AND ttu.user_id = #{userId} AND ttu.deleted = 0
|
||||
ORDER BY ttu.create_time ASC
|
||||
</select>
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.xyzh.system.config;
|
||||
|
||||
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 AsyncLogConfig.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
public class AsyncLogConfig {
|
||||
|
||||
/**
|
||||
* @description 配置日志异步执行器
|
||||
* @return Executor
|
||||
*/
|
||||
@Bean(name = "logExecutor")
|
||||
public Executor logExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
|
||||
// 核心线程数
|
||||
executor.setCorePoolSize(2);
|
||||
|
||||
// 最大线程数
|
||||
executor.setMaxPoolSize(5);
|
||||
|
||||
// 队列容量
|
||||
executor.setQueueCapacity(1000);
|
||||
|
||||
// 线程名称前缀
|
||||
executor.setThreadNamePrefix("async-log-");
|
||||
|
||||
// 线程空闲时间(秒)
|
||||
executor.setKeepAliveSeconds(60);
|
||||
|
||||
// 拒绝策略:由调用线程处理(防止日志丢失)
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
|
||||
// 关闭时等待任务完成
|
||||
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||
|
||||
// 等待时间(秒)
|
||||
executor.setAwaitTerminationSeconds(60);
|
||||
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,14 +98,14 @@ public class DeptController {
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 查询部门绑定角色
|
||||
* @description 查询部门绑定角色(包含名称)
|
||||
* @param dept 部门信息
|
||||
* @return ResultDomain<TbSysRole> 角色信息
|
||||
* @return ResultDomain<UserDeptRoleVO> 部门角色信息
|
||||
* @author yslg
|
||||
* @since 2025-10-06
|
||||
*/
|
||||
@PostMapping("/role")
|
||||
public ResultDomain<TbSysRole> getDeptByRole(@RequestBody TbSysDept dept) {
|
||||
public ResultDomain<UserDeptRoleVO> getDeptByRole(@RequestBody TbSysDept dept) {
|
||||
return deptService.getDeptByRole(dept.getDeptID());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.xyzh.system.controller;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.xyzh.api.system.log.LoginLogService;
|
||||
import org.xyzh.api.system.log.OperationLogService;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
import org.xyzh.common.core.page.PageRequest;
|
||||
import org.xyzh.common.dto.system.TbSysLoginLog;
|
||||
import org.xyzh.common.dto.system.TbSysOperationLog;
|
||||
|
||||
/**
|
||||
* @description 日志管理控制器
|
||||
* @filename LogController.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/sys/log")
|
||||
public class LogController {
|
||||
|
||||
@Autowired
|
||||
private LoginLogService loginLogService;
|
||||
|
||||
@Autowired
|
||||
private OperationLogService operationLogService;
|
||||
|
||||
/**
|
||||
* @description 分页查询登录日志
|
||||
* 根据条件分页查询登录日志,superadmin可查看所有日志,其他用户只能查看本部门及子部门的日志
|
||||
* @param loginLog 查询条件
|
||||
* @param pageNum 页码(从1开始)
|
||||
* @param pageSize 每页大小
|
||||
* @return ResultDomain<TbSysLoginLog> 登录日志分页列表
|
||||
*/
|
||||
@PostMapping("/login/page")
|
||||
public ResultDomain<TbSysLoginLog> getLoginLogPage(@RequestBody PageRequest<TbSysLoginLog> pageRequest) {
|
||||
|
||||
return loginLogService.selectLoginLogPage(pageRequest.getFilter(), pageRequest.getPageParam());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 分页查询操作日志
|
||||
* 根据条件分页查询操作日志,superadmin可查看所有日志,其他用户只能查看本部门及子部门的日志
|
||||
* @param operationLog 查询条件
|
||||
* @param pageNum 页码(从1开始)
|
||||
* @param pageSize 每页大小
|
||||
* @return ResultDomain<TbSysOperationLog> 操作日志分页列表
|
||||
*/
|
||||
@PostMapping("/operation/page")
|
||||
public ResultDomain<TbSysOperationLog> getOperationLogPage(@RequestBody PageRequest<TbSysOperationLog> pageRequest) {
|
||||
|
||||
return operationLogService.selectOperationLogPage(pageRequest.getFilter(), pageRequest.getPageParam());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.xyzh.api.system.menu.MenuService;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.dto.menu.TbSysMenu;
|
||||
import org.xyzh.common.dto.menu.TbSysMenuPermission;
|
||||
import org.xyzh.common.vo.PermissionVO;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/menus")
|
||||
@@ -97,14 +98,14 @@ public class MenuController {
|
||||
|
||||
|
||||
/**
|
||||
* @description 查询菜单权限
|
||||
* @param menuId 菜单ID
|
||||
* @return ResultDomain<TbSysMenuPermission> 菜单权限信息
|
||||
* @description 查询菜单权限(包含模块名称等信息)
|
||||
* @param menu 菜单信息
|
||||
* @return ResultDomain<PermissionVO> 权限信息
|
||||
* @author yslg
|
||||
* @since 2025-10-06
|
||||
*/
|
||||
@PostMapping("/permission")
|
||||
public ResultDomain<TbSysMenuPermission> getMenuPermission(@RequestBody TbSysMenu menu) {
|
||||
public ResultDomain<PermissionVO> getMenuPermission(@RequestBody TbSysMenu menu) {
|
||||
return menuService.getMenuPermission(menu.getMenuID());
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.dto.permission.TbSysPermission;
|
||||
import org.xyzh.common.dto.role.TbSysRole;
|
||||
import org.xyzh.common.dto.role.TbSysRolePermission;
|
||||
import org.xyzh.common.vo.PermissionVO;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
|
||||
/**
|
||||
@@ -37,8 +38,14 @@ public class RoleController {
|
||||
* @author yslg
|
||||
* @ since 2025-10-09
|
||||
*/
|
||||
/**
|
||||
* @description 查询所有角色(包含权限过滤)
|
||||
* @return ResultDomain<PermissionVO> 角色VO列表
|
||||
* @author yslg
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
@PostMapping("/all")
|
||||
public ResultDomain<TbSysRole> all() {
|
||||
public ResultDomain<PermissionVO> all() {
|
||||
return roleService.getAllRoles();
|
||||
}
|
||||
|
||||
@@ -55,14 +62,14 @@ public class RoleController {
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 根据过滤条件获取角色列表
|
||||
* @description 根据过滤条件获取角色列表(包含权限过滤)
|
||||
* @param filter 过滤条件
|
||||
* @return ResultDomain<TbSysRole> 角色列表
|
||||
* @return ResultDomain<PermissionVO> 角色VO列表
|
||||
* @author yslg
|
||||
* @since 2025-10-09
|
||||
*/
|
||||
@PostMapping("/list")
|
||||
public ResultDomain<TbSysRole> getRoleList(@RequestBody TbSysRole filter) {
|
||||
public ResultDomain<PermissionVO> getRoleList(@RequestBody TbSysRole filter) {
|
||||
return roleService.getRoleList(filter);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.xyzh.common.dto.user.TbSysUser;
|
||||
import org.xyzh.common.dto.user.TbSysUserDeptRole;
|
||||
import org.xyzh.common.dto.user.TbSysUserInfo;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
import org.xyzh.common.vo.UserVO;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
@@ -92,8 +93,8 @@ public class UserController {
|
||||
* @since 2025-10-09
|
||||
*/
|
||||
@PostMapping("/list")
|
||||
public ResultDomain<TbSysUser> getUserList(@RequestBody TbSysUser filter) {
|
||||
return userService.getUserByFilter(filter);
|
||||
public ResultDomain<UserVO> getUserList(@RequestBody TbSysUser filter) {
|
||||
return userService.getUserVOByFilter(filter);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,10 +104,10 @@ public class UserController {
|
||||
* @since 2025-10-09
|
||||
*/
|
||||
@PostMapping("/page")
|
||||
public ResultDomain<TbSysUser> getUserPage(@RequestBody PageRequest<TbSysUser> pageRequest) {
|
||||
public ResultDomain<UserVO> getUserPage(@RequestBody PageRequest<TbSysUser> pageRequest) {
|
||||
TbSysUser filter = pageRequest.getFilter();
|
||||
PageParam pageParam = pageRequest.getPageParam();
|
||||
return userService.getUserPage(filter, pageParam);
|
||||
return userService.getUserVOPage(filter, pageParam);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -370,11 +370,12 @@ public class SysDepartmentServiceImpl implements SysDepartmentService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbSysRole> getDeptByRole(String deptId) {
|
||||
ResultDomain<TbSysRole> resultDomain = new ResultDomain<>();
|
||||
public ResultDomain<UserDeptRoleVO> getDeptByRole(String deptId) {
|
||||
ResultDomain<UserDeptRoleVO> resultDomain = new ResultDomain<>();
|
||||
try {
|
||||
logger.info("开始查询部门绑定角色:{}", deptId);
|
||||
List<TbSysRole> roles = deptRoleMapper.selectDeptRole(deptId);
|
||||
List<UserDeptRoleVO> roles = deptRoleMapper.selectDeptRole(deptId);
|
||||
logger.info("查询部门绑定角色完成,共找到{}条记录", roles.size());
|
||||
resultDomain.success("查询成功", roles);
|
||||
return resultDomain;
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
package org.xyzh.system.log;
|
||||
|
||||
import org.apache.logging.log4j.core.Appender;
|
||||
import org.apache.logging.log4j.core.Core;
|
||||
import org.apache.logging.log4j.core.Filter;
|
||||
import org.apache.logging.log4j.core.LogEvent;
|
||||
import org.apache.logging.log4j.core.appender.AbstractAppender;
|
||||
import org.apache.logging.log4j.core.config.Property;
|
||||
import org.apache.logging.log4j.core.config.plugins.Plugin;
|
||||
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
|
||||
import org.apache.logging.log4j.core.config.plugins.PluginElement;
|
||||
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
|
||||
import org.xyzh.common.utils.spring.SpringContextUtil;
|
||||
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
/**
|
||||
* @description 数据库日志Appender - 异步写入数据库
|
||||
* @filename DatabaseAppender.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
@Plugin(name = "DatabaseAppender", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
|
||||
public class DatabaseAppender extends AbstractAppender {
|
||||
|
||||
// 异步队列,存储待处理的日志事件上下文
|
||||
private static final BlockingQueue<LogEventContext> logQueue = new ArrayBlockingQueue<>(10000);
|
||||
|
||||
// 日志处理工作线程
|
||||
private static LogEventProcessor processor;
|
||||
|
||||
protected DatabaseAppender(String name, Filter filter, boolean ignoreExceptions) {
|
||||
super(name, filter, null, ignoreExceptions, Property.EMPTY_ARRAY);
|
||||
|
||||
// 启动日志处理线程
|
||||
if (processor == null) {
|
||||
processor = new LogEventProcessor();
|
||||
Thread processorThread = new Thread(processor, "log-db-processor");
|
||||
processorThread.setDaemon(true);
|
||||
processorThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void append(LogEvent event) {
|
||||
// 在主线程中提取HTTP上下文信息(异步线程中无法获取)
|
||||
String userId = null;
|
||||
String username = null;
|
||||
String requestUrl = null;
|
||||
String requestMethod = null;
|
||||
String ipAddress = null;
|
||||
String ipSource = null;
|
||||
String browser = null;
|
||||
String os = null;
|
||||
|
||||
try {
|
||||
// 尝试获取用户信息
|
||||
SpringContextUtil contextUtil = SpringContextUtil.getBean(SpringContextUtil.class);
|
||||
if (contextUtil != null) {
|
||||
Object loginUtil = SpringContextUtil.getBean("loginUtil");
|
||||
if (loginUtil != null) {
|
||||
java.lang.reflect.Method getUserIdMethod = loginUtil.getClass().getMethod("getCurrentUserId");
|
||||
java.lang.reflect.Method getUsernameMethod = loginUtil.getClass().getMethod("getCurrentUsername");
|
||||
userId = (String) getUserIdMethod.invoke(null);
|
||||
username = (String) getUsernameMethod.invoke(null);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 非登录状态或反射调用失败,忽略
|
||||
}
|
||||
|
||||
try {
|
||||
// 尝试获取请求信息
|
||||
Class<?> servletUtilsClass = Class.forName("org.xyzh.common.utils.ServletUtils");
|
||||
requestUrl = (String) servletUtilsClass.getMethod("getRequestUrl").invoke(null);
|
||||
requestMethod = (String) servletUtilsClass.getMethod("getHeader", String.class).invoke(null, "X-HTTP-Method-Override");
|
||||
ipAddress = (String) servletUtilsClass.getMethod("getClientIp").invoke(null);
|
||||
ipSource = (String) servletUtilsClass.getMethod("getIpSource").invoke(null);
|
||||
browser = (String) servletUtilsClass.getMethod("getBrowser").invoke(null);
|
||||
os = (String) servletUtilsClass.getMethod("getOs").invoke(null);
|
||||
} catch (Exception e) {
|
||||
// 非HTTP请求上下文或类不存在,忽略
|
||||
}
|
||||
|
||||
// 创建日志事件上下文(包含HTTP上下文信息)
|
||||
LogEventContext context = new LogEventContext(
|
||||
event.toImmutable(), userId, username,
|
||||
requestUrl, requestMethod, ipAddress, ipSource, browser, os
|
||||
);
|
||||
|
||||
// 将日志事件上下文添加到队列中,非阻塞方式
|
||||
if (!logQueue.offer(context)) {
|
||||
// 队列满了,记录警告但不阻塞应用
|
||||
System.err.println("日志队列已满,丢弃日志: " + event.getMessage().getFormattedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
super.stop();
|
||||
if (processor != null) {
|
||||
processor.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 创建 DatabaseAppender 实例的工厂方法
|
||||
*/
|
||||
@PluginFactory
|
||||
public static DatabaseAppender createAppender(
|
||||
@PluginAttribute("name") String name,
|
||||
@PluginElement("Filter") Filter filter,
|
||||
@PluginAttribute("ignoreExceptions") boolean ignoreExceptions) {
|
||||
|
||||
if (name == null) {
|
||||
LOGGER.error("未指定 DatabaseAppender 名称");
|
||||
return null;
|
||||
}
|
||||
|
||||
return new DatabaseAppender(name, filter, ignoreExceptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取日志队列,供处理器使用
|
||||
*/
|
||||
public static BlockingQueue<LogEventContext> getLogQueue() {
|
||||
return logQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 日志事件处理器(内部类)
|
||||
*/
|
||||
static class LogEventProcessor implements Runnable {
|
||||
private volatile boolean running = true;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (running) {
|
||||
try {
|
||||
// 从队列中获取日志事件上下文(阻塞等待)
|
||||
LogEventContext context = logQueue.poll(1, TimeUnit.SECONDS);
|
||||
if (context != null) {
|
||||
processLogEvent(context);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
System.err.println("处理日志事件失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 处理单个日志事件
|
||||
*/
|
||||
private void processLogEvent(LogEventContext context) {
|
||||
try {
|
||||
// 通过 Spring 容器获取日志Service并保存
|
||||
LogEventHandler handler = SpringContextUtil.getBean(LogEventHandler.class);
|
||||
if (handler != null) {
|
||||
handler.handleLogEvent(context);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("保存日志到数据库失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.xyzh.system.log;
|
||||
|
||||
import org.apache.logging.log4j.core.LogEvent;
|
||||
|
||||
/**
|
||||
* @description 日志事件上下文 - 包含日志事件和HTTP上下文信息
|
||||
* @filename LogEventContext.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
public class LogEventContext {
|
||||
|
||||
private final LogEvent logEvent;
|
||||
private final String userId;
|
||||
private final String username;
|
||||
private final String requestUrl;
|
||||
private final String requestMethod;
|
||||
private final String ipAddress;
|
||||
private final String ipSource;
|
||||
private final String browser;
|
||||
private final String os;
|
||||
|
||||
public LogEventContext(LogEvent logEvent, String userId, String username,
|
||||
String requestUrl, String requestMethod,
|
||||
String ipAddress, String ipSource,
|
||||
String browser, String os) {
|
||||
this.logEvent = logEvent;
|
||||
this.userId = userId;
|
||||
this.username = username;
|
||||
this.requestUrl = requestUrl;
|
||||
this.requestMethod = requestMethod;
|
||||
this.ipAddress = ipAddress;
|
||||
this.ipSource = ipSource;
|
||||
this.browser = browser;
|
||||
this.os = os;
|
||||
}
|
||||
|
||||
public LogEvent getLogEvent() {
|
||||
return logEvent;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getRequestUrl() {
|
||||
return requestUrl;
|
||||
}
|
||||
|
||||
public String getRequestMethod() {
|
||||
return requestMethod;
|
||||
}
|
||||
|
||||
public String getIpAddress() {
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
public String getIpSource() {
|
||||
return ipSource;
|
||||
}
|
||||
|
||||
public String getBrowser() {
|
||||
return browser;
|
||||
}
|
||||
|
||||
public String getOs() {
|
||||
return os;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
package org.xyzh.system.log;
|
||||
|
||||
import org.apache.logging.log4j.core.LogEvent;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.xyzh.common.dto.system.TbSysOperationLog;
|
||||
import org.xyzh.common.utils.IDUtils;
|
||||
import org.xyzh.system.mapper.SysOperationLogMapper;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @description 日志事件处理器 - 将日志事件转换并保存到数据库
|
||||
* @filename LogEventHandler.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
@Component
|
||||
public class LogEventHandler {
|
||||
|
||||
@Autowired
|
||||
private SysOperationLogMapper operationLogMapper;
|
||||
|
||||
/**
|
||||
* @description 处理日志事件(异步)
|
||||
* @param context 日志事件上下文(包含HTTP上下文信息)
|
||||
*/
|
||||
@Async("logExecutor")
|
||||
public void handleLogEvent(LogEventContext context) {
|
||||
try {
|
||||
// 处理 DEBUG 级别及以上的所有日志(由 ThresholdFilter 已过滤)
|
||||
// 这里不需要再次判断级别,直接保存即可
|
||||
TbSysOperationLog operationLog = buildOperationLog(context);
|
||||
operationLogMapper.insertOperationLog(operationLog);
|
||||
} catch (Exception e) {
|
||||
// 使用 System.err 而不是 logger,避免循环依赖
|
||||
System.err.println("保存日志到数据库失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 构建操作日志对象
|
||||
*/
|
||||
private TbSysOperationLog buildOperationLog(LogEventContext context) {
|
||||
LogEvent event = context.getLogEvent();
|
||||
TbSysOperationLog log = new TbSysOperationLog();
|
||||
|
||||
log.setID(IDUtils.generateID());
|
||||
|
||||
// 使用从主线程提取的用户信息
|
||||
log.setUserID(context.getUserId());
|
||||
log.setUsername(context.getUsername());
|
||||
|
||||
// 日志基本信息
|
||||
log.setModule(event.getLoggerName());
|
||||
log.setOperation(event.getLevel().name());
|
||||
log.setMethod(event.getLoggerName());
|
||||
|
||||
// 使用从主线程提取的请求信息
|
||||
log.setRequestUrl(context.getRequestUrl());
|
||||
log.setRequestMethod(context.getRequestMethod());
|
||||
log.setIpAddress(context.getIpAddress());
|
||||
log.setIpSource(context.getIpSource());
|
||||
log.setBrowser(context.getBrowser());
|
||||
log.setOs(context.getOs());
|
||||
|
||||
// 日志消息和异常信息
|
||||
log.setResponseData(event.getMessage().getFormattedMessage());
|
||||
if (event.getThrown() != null) {
|
||||
log.setErrorMessage(getStackTrace(event.getThrown()));
|
||||
log.setStatus(0); // 失败
|
||||
} else {
|
||||
log.setStatus(1); // 成功
|
||||
}
|
||||
|
||||
log.setExecuteTime(0); // 日志无执行时间
|
||||
log.setCreateTime(new Date(event.getTimeMillis()));
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取异常堆栈信息
|
||||
*/
|
||||
private String getStackTrace(Throwable throwable) {
|
||||
if (throwable == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(throwable.getClass().getName()).append(": ").append(throwable.getMessage()).append("\n");
|
||||
|
||||
StackTraceElement[] elements = throwable.getStackTrace();
|
||||
int maxLines = Math.min(elements.length, 10); // 只保留前10行
|
||||
for (int i = 0; i < maxLines; i++) {
|
||||
sb.append("\tat ").append(elements[i].toString()).append("\n");
|
||||
}
|
||||
|
||||
if (elements.length > maxLines) {
|
||||
sb.append("\t... ").append(elements.length - maxLines).append(" more\n");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
# 异步日志监听系统
|
||||
|
||||
## 功能说明
|
||||
|
||||
本系统实现了对 Logger 输出的监听,将ERROR级别及以上的日志异步写入数据库,用于日志审计和问题追踪。
|
||||
|
||||
## 核心组件
|
||||
|
||||
### 1. DatabaseAppender
|
||||
**路径**: `system/src/main/java/org/xyzh/system/log/DatabaseAppender.java`
|
||||
|
||||
自定义 Log4j2 Appender,负责:
|
||||
- 监听所有日志输出
|
||||
- 过滤 ERROR 级别及以上的日志
|
||||
- 将日志事件放入异步队列(非阻塞,防止影响业务性能)
|
||||
- 队列容量:10000条
|
||||
|
||||
### 2. LogEventHandler
|
||||
**路径**: `system/src/main/java/org/xyzh/system/log/LogEventHandler.java`
|
||||
|
||||
日志事件处理器,负责:
|
||||
- 从异步队列获取日志事件
|
||||
- 转换为 `TbSysOperationLog` 对象
|
||||
- 异步批量写入数据库
|
||||
- 自动提取:用户信息、请求信息、IP、浏览器、OS等
|
||||
|
||||
### 3. SpringContextUtil
|
||||
**路径**: `system/src/main/java/org/xyzh/system/log/SpringContextUtil.java`
|
||||
|
||||
Spring上下文工具类,用于在非Spring管理的类(如Log4j2 Appender)中获取Spring Bean。
|
||||
|
||||
### 4. AsyncLogConfig
|
||||
**路径**: `system/src/main/java/org/xyzh/system/config/AsyncLogConfig.java`
|
||||
|
||||
异步执行器配置:
|
||||
- 核心线程数:2
|
||||
- 最大线程数:5
|
||||
- 队列容量:1000
|
||||
- 拒绝策略:CallerRunsPolicy(防止日志丢失)
|
||||
|
||||
## 配置方法
|
||||
|
||||
### 1. 在 log4j2-spring.xml 中添加 Database Appender
|
||||
|
||||
```xml
|
||||
<appenders>
|
||||
<!-- ... 其他 appenders ... -->
|
||||
|
||||
<!-- 数据库日志Appender -->
|
||||
<DatabaseAppender name="DatabaseAppender" ignoreExceptions="false">
|
||||
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||
</DatabaseAppender>
|
||||
</appenders>
|
||||
|
||||
<loggers>
|
||||
<root level="info">
|
||||
<!-- ... 其他 appenders ... -->
|
||||
<!-- 数据库日志Appender -->
|
||||
<appender-ref ref="DatabaseAppender"/>
|
||||
</root>
|
||||
</loggers>
|
||||
```
|
||||
|
||||
### 2. 确保数据库表存在
|
||||
|
||||
表名:`tb_sys_operation_log`
|
||||
|
||||
主要字段:
|
||||
- `id` - 主键
|
||||
- `user_id` - 用户ID
|
||||
- `username` - 用户名
|
||||
- `module` - 模块名(Logger名称)
|
||||
- `operation` - 操作类型(日志级别)
|
||||
- `method` - 方法名
|
||||
- `request_url` - 请求URL
|
||||
- `request_method` - 请求方法
|
||||
- `request_params` - 请求参数
|
||||
- `response_data` - 日志消息
|
||||
- `ip_address` - IP地址
|
||||
- `ip_source` - IP来源
|
||||
- `browser` - 浏览器
|
||||
- `os` - 操作系统
|
||||
- `status` - 状态(0-失败,1-成功)
|
||||
- `error_message` - 异常堆栈
|
||||
- `execute_time` - 执行时间
|
||||
- `create_time` - 创建时间
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 记录错误日志
|
||||
|
||||
```java
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class UserService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
|
||||
|
||||
public void someMethod() {
|
||||
try {
|
||||
// 业务逻辑
|
||||
} catch (Exception e) {
|
||||
// 这条ERROR日志会自动写入数据库
|
||||
logger.error("用户操作失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 记录警告日志(不会写入数据库)
|
||||
|
||||
```java
|
||||
// WARN级别日志不会写入数据库,只写入文件
|
||||
logger.warn("这是一个警告信息");
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
1. **异步队列**:日志事件先放入内存队列,不阻塞业务线程
|
||||
2. **批量处理**:后台线程批量从队列取出并处理
|
||||
3. **线程池**:使用专用线程池处理日志写入
|
||||
4. **非阻塞**:队列满时丢弃日志而不阻塞
|
||||
5. **级别过滤**:只处理ERROR及以上级别,减少数据库压力
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **日志级别**:默认只记录ERROR级别及以上的日志到数据库
|
||||
2. **队列容量**:队列容量10000,超出会丢弃(打印警告到stderr)
|
||||
3. **关闭等待**:应用关闭时会等待60秒完成日志写入
|
||||
4. **循环依赖**:LogEventHandler中不要使用logger.error(),会造成循环
|
||||
5. **性能影响**:每次ERROR日志会触发数据库写入,避免频繁ERROR日志
|
||||
|
||||
## 监控建议
|
||||
|
||||
1. 定期检查 `tb_sys_operation_log` 表大小
|
||||
2. 定期归档或删除旧日志(如30天前的)
|
||||
3. 监控队列满的告警(stderr输出)
|
||||
4. 监控数据库写入性能
|
||||
|
||||
## 扩展功能
|
||||
|
||||
### 自定义日志级别过滤
|
||||
|
||||
修改 `LogEventHandler.handleLogEvent()` 方法:
|
||||
|
||||
```java
|
||||
// 改为记录WARN及以上级别
|
||||
if (event.getLevel().isMoreSpecificThan(org.apache.logging.log4j.Level.WARN)) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 添加自定义字段
|
||||
|
||||
在 `buildOperationLog()` 方法中添加自定义字段提取逻辑。
|
||||
|
||||
### 批量写入优化
|
||||
|
||||
可以修改为收集多条日志后批量写入数据库,进一步提升性能。
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package org.xyzh.system.log.service.impl;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.xyzh.api.system.log.LoginLogService;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.core.page.PageDomain;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
import org.xyzh.common.dto.system.TbSysLoginLog;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.xyzh.system.mapper.SysLoginLogMapper;
|
||||
import org.xyzh.system.utils.LoginUtil;
|
||||
|
||||
@Service
|
||||
public class LoginLogServiceImpl implements LoginLogService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(LoginLogServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private SysLoginLogMapper sysLoginLogMapper;
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbSysLoginLog> insertLoginLog(TbSysLoginLog loginLog) {
|
||||
ResultDomain<TbSysLoginLog> result = new ResultDomain<>();
|
||||
int rows = sysLoginLogMapper.insertLoginLog(loginLog);
|
||||
if (rows > 0) {
|
||||
result.success("插入登录日志成功", loginLog);
|
||||
} else {
|
||||
result.fail( "插入登录日志失败");
|
||||
}
|
||||
logger.info("insertLoginLog: {}", loginLog);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbSysLoginLog> selectLoginLogList(TbSysLoginLog loginLog) {
|
||||
ResultDomain<TbSysLoginLog> result = new ResultDomain<>();
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbSysLoginLog> list = sysLoginLogMapper.selectLoginLogList(loginLog, userDeptRoles);
|
||||
if (list != null) {
|
||||
result.success("查询登录日志成功", list);
|
||||
} else {
|
||||
result.fail("查询登录日志失败");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbSysLoginLog> selectLoginLogPage(TbSysLoginLog loginLog, PageParam pageParam) {
|
||||
ResultDomain<TbSysLoginLog> result = new ResultDomain<>();
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbSysLoginLog> list = sysLoginLogMapper.selectLoginLogPage(loginLog, pageParam, userDeptRoles);
|
||||
PageDomain<TbSysLoginLog> pageDomain = new PageDomain<>();
|
||||
int count = sysLoginLogMapper.countLoginLog(loginLog, userDeptRoles);
|
||||
pageParam.setTotalElements(count);
|
||||
pageParam.setTotalPages((int)Math.ceil(count / pageParam.getPageSize() ));
|
||||
|
||||
pageDomain.setPageParam(pageParam);
|
||||
pageDomain.setDataList(list);
|
||||
if (list != null) {
|
||||
result.success("查询登录日志成功", pageDomain);
|
||||
} else {
|
||||
result.fail("查询登录日志失败");
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.xyzh.system.log.service.impl;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.xyzh.api.system.log.OperationLogService;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.core.page.PageDomain;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
import org.xyzh.common.dto.system.TbSysOperationLog;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
import org.xyzh.system.mapper.SysOperationLogMapper;
|
||||
import org.xyzh.system.utils.LoginUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description 操作日志服务实现
|
||||
* @filename SysOperationLogServiceImpl.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
@Service
|
||||
public class SysOperationLogServiceImpl implements OperationLogService {
|
||||
|
||||
@Autowired
|
||||
private SysOperationLogMapper operationLogMapper;
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbSysOperationLog> selectOperationLogPage(TbSysOperationLog operationLog, PageParam pageParam) {
|
||||
ResultDomain<TbSysOperationLog> result = new ResultDomain<>();
|
||||
|
||||
try {
|
||||
// 获取当前用户的部门角色信息(用于权限过滤)
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
|
||||
// 分页查询操作日志
|
||||
List<TbSysOperationLog> logs = operationLogMapper.selectOperationLogPage(operationLog, pageParam, userDeptRoles);
|
||||
|
||||
// 统计总数(带权限过滤)
|
||||
int count = operationLogMapper.countOperationLog(operationLog, userDeptRoles);
|
||||
pageParam.setTotalElements(count);
|
||||
pageParam.setTotalPages((int) Math.ceil((double) count / pageParam.getPageSize()));
|
||||
|
||||
// 构建分页结果
|
||||
PageDomain<TbSysOperationLog> pageDomain = new PageDomain<>();
|
||||
pageDomain.setPageParam(pageParam);
|
||||
pageDomain.setDataList(logs);
|
||||
|
||||
result.success("查询成功", pageDomain);
|
||||
} catch (Exception e) {
|
||||
result.fail("查询失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countOperationLog(TbSysOperationLog operationLog) {
|
||||
// 获取当前用户的部门角色信息(用于权限过滤)
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
return operationLogMapper.countOperationLog(operationLog, userDeptRoles);
|
||||
}
|
||||
}
|
||||
@@ -7,20 +7,19 @@ import java.util.List;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.common.dto.dept.TbSysDeptRole;
|
||||
import org.xyzh.common.dto.role.TbSysRole;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
|
||||
@Mapper
|
||||
public interface DeptRoleMapper extends BaseMapper<TbSysDeptRole> {
|
||||
|
||||
/**
|
||||
* @description 查询部门绑定角色
|
||||
* @description 查询部门绑定角色(包含名称)
|
||||
* @param deptId 部门ID
|
||||
* @return List<TbSysRole> 角色列表
|
||||
* @return List<UserDeptRoleVO> 部门角色列表
|
||||
* @author yslg
|
||||
* @since 2025-09-28
|
||||
*/
|
||||
List<TbSysRole> selectDeptRole(String deptId);
|
||||
List<UserDeptRoleVO> selectDeptRole(String deptId);
|
||||
|
||||
/**
|
||||
* @description 查询部门绑定角色列表(包含名称)
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.common.dto.menu.TbSysMenuPermission;
|
||||
import org.xyzh.common.vo.PermissionVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -49,13 +50,13 @@ public interface MenuPermissionMapper extends BaseMapper<TbSysMenuPermission> {
|
||||
@Param("permissionId") String permissionId);
|
||||
|
||||
/**
|
||||
* @description 根据菜单ID查询权限关联列表
|
||||
* @description 根据菜单ID查询权限列表(包含模块名称等信息)
|
||||
* @param menuId 菜单ID
|
||||
* @return List<TbSysMenuPermission> 菜单权限关联列表
|
||||
* @return List<PermissionVO> 权限列表
|
||||
* @author yslg
|
||||
* @since 2025-10-07
|
||||
*/
|
||||
List<TbSysMenuPermission> selectByMenuId(@Param("menuId") String menuId);
|
||||
List<PermissionVO> selectByMenuId(@Param("menuId") String menuId);
|
||||
|
||||
/**
|
||||
* @description 根据权限ID查询菜单关联列表
|
||||
|
||||
@@ -141,22 +141,22 @@ public interface PermissionMapper extends BaseMapper<TbSysPermission> {
|
||||
PermissionVO selectPermissionVO(@Param("permission") PermissionVO permission);
|
||||
|
||||
/**
|
||||
* @description 查询权限绑定菜单
|
||||
* @description 查询权限绑定菜单(包含菜单名称描述)
|
||||
* @param permission 权限对象
|
||||
* @return List<TbSysMenu> 权限绑定菜单列表
|
||||
* @return List<PermissionVO> 权限VO列表(包含菜单信息)
|
||||
* @author yslg
|
||||
* @since 2025-10-08
|
||||
*/
|
||||
List<TbSysMenu> selectPermissionBindMenu(@Param("permission") PermissionVO permission);
|
||||
List<PermissionVO> selectPermissionBindMenu(@Param("permission") PermissionVO permission);
|
||||
|
||||
/**
|
||||
* @description 查询权限绑定角色
|
||||
* @description 查询权限绑定角色(包含角色名称描述和创建人更新人)
|
||||
* @param permission 权限对象
|
||||
* @return List<TbSysRole> 权限绑定角色列表
|
||||
* @return List<PermissionVO> 权限VO列表(包含角色信息)
|
||||
* @author yslg
|
||||
* @since 2025-10-08
|
||||
*/
|
||||
List<TbSysRole> selectPermissionBindRole(@Param("permission") PermissionVO permission);
|
||||
List<PermissionVO> selectPermissionBindRole(@Param("permission") PermissionVO permission);
|
||||
|
||||
/**
|
||||
* @description 根据模块ID查询权限列表
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.common.dto.role.TbSysRole;
|
||||
import org.xyzh.common.vo.PermissionVO;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
|
||||
import java.util.List;
|
||||
@@ -24,16 +25,24 @@ public interface RoleMapper extends BaseMapper<TbSysRole> {
|
||||
* @author yslg
|
||||
* @since 2025-10-09
|
||||
*/
|
||||
List<TbSysRole> selectAllRoles();
|
||||
/**
|
||||
* @description 查询所有角色(包含权限过滤和创建人更新人名称)
|
||||
* @param userDeptRoles 用户部门角色列表(用于权限过滤)
|
||||
* @return List<PermissionVO> 角色VO列表
|
||||
* @author yslg
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
List<PermissionVO> selectAllRoles(@Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 根据过滤条件查询角色列表
|
||||
* @description 根据过滤条件查询角色列表(包含权限过滤)
|
||||
* @param filter 过滤条件
|
||||
* @return List<TbSysRole> 角色列表
|
||||
* @param userDeptRoles 用户部门角色列表(用于权限过滤)
|
||||
* @return List<PermissionVO> 角色VO列表
|
||||
* @author yslg
|
||||
* @since 2025-10-09
|
||||
*/
|
||||
List<TbSysRole> selectRole(TbSysRole filter);
|
||||
List<PermissionVO> selectRole(@Param("filter") TbSysRole filter, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 插入角色
|
||||
@@ -71,34 +80,18 @@ public interface RoleMapper extends BaseMapper<TbSysRole> {
|
||||
*/
|
||||
List<UserDeptRoleVO> selectDeptRolesByUserId(@Param("userId") String userId);
|
||||
|
||||
/**
|
||||
* @description 根据角色编码查询角色
|
||||
* @param roleCode 角色编码
|
||||
* @return TbSysRole 角色信息
|
||||
* @author yslg
|
||||
* @since 2025-09-28
|
||||
*/
|
||||
TbSysRole selectByRoleCode(@Param("roleCode") String roleCode);
|
||||
|
||||
/**
|
||||
* @description 检查角色名称是否存在
|
||||
* @description 检查角色名称是否存在(包含权限过滤)
|
||||
* @param roleName 角色名称
|
||||
* @param excludeId 排除的角色ID
|
||||
* @param userDeptRoles 用户部门角色列表(用于权限过滤)
|
||||
* @return int 存在数量
|
||||
* @author yslg
|
||||
* @since 2025-09-28
|
||||
*/
|
||||
int countByRoleName(@Param("roleName") String roleName, @Param("excludeId") String excludeId);
|
||||
int countByRoleName(@Param("roleName") String roleName, @Param("excludeId") String excludeId, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 检查角色编码是否存在
|
||||
* @param roleCode 角色编码
|
||||
* @param excludeId 排除的角色ID
|
||||
* @return int 存在数量
|
||||
* @author yslg
|
||||
* @since 2025-09-28
|
||||
*/
|
||||
int countByRoleCode(@Param("roleCode") String roleCode, @Param("excludeId") String excludeId);
|
||||
|
||||
/**
|
||||
* @description 批量删除角色(逻辑删除)
|
||||
@@ -111,7 +104,7 @@ public interface RoleMapper extends BaseMapper<TbSysRole> {
|
||||
int batchDeleteByIds(@Param("roleIds") List<String> roleIds, @Param("updater") String updater);
|
||||
|
||||
/**
|
||||
* @description 检查角色是否存在
|
||||
* @description 检查角色是否存在(精确查询,不需要权限过滤)
|
||||
* @param roleIds 角色ID列表
|
||||
* @return List<TbSysRole> 角色列表
|
||||
* @author yslg
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.xyzh.system.mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
import org.xyzh.common.dto.system.TbSysLoginLog;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
@Mapper
|
||||
public interface SysLoginLogMapper extends BaseMapper<TbSysLoginLog> {
|
||||
|
||||
int insertLoginLog(TbSysLoginLog loginLog);
|
||||
|
||||
List<TbSysLoginLog> selectLoginLogList(@Param("loginLog") TbSysLoginLog loginLog, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
List<TbSysLoginLog> selectLoginLogPage(@Param("loginLog") TbSysLoginLog loginLog, @Param("pageParam") PageParam pageParam, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
int countLoginLog(@Param("loginLog") TbSysLoginLog loginLog, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
}
|
||||
@@ -2,7 +2,10 @@ package org.xyzh.system.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
import org.xyzh.common.dto.system.TbSysOperationLog;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -17,11 +20,46 @@ import java.util.List;
|
||||
public interface SysOperationLogMapper extends BaseMapper<TbSysOperationLog> {
|
||||
|
||||
/**
|
||||
* @description 查询操作日志列表
|
||||
* @param filter 过滤条件
|
||||
* @description 插入操作日志
|
||||
* @param operationLog 操作日志对象
|
||||
* @return int 影响行数
|
||||
* @author yslg
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
int insertOperationLog(TbSysOperationLog operationLog);
|
||||
|
||||
/**
|
||||
* @description 查询操作日志列表(带权限过滤)
|
||||
* @param operationLog 过滤条件
|
||||
* @param userDeptRoles 当前用户的部门角色列表
|
||||
* @return List<TbSysOperationLog> 操作日志列表
|
||||
* @author yslg
|
||||
* @since 2025-10-15
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
List<TbSysOperationLog> selectSysOperationLogs(TbSysOperationLog filter);
|
||||
List<TbSysOperationLog> selectOperationLogList(@Param("operationLog") TbSysOperationLog operationLog,
|
||||
@Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 分页查询操作日志列表(带权限过滤)
|
||||
* @param operationLog 过滤条件
|
||||
* @param pageParam 分页参数
|
||||
* @param userDeptRoles 当前用户的部门角色列表
|
||||
* @return List<TbSysOperationLog> 操作日志列表
|
||||
* @author yslg
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
List<TbSysOperationLog> selectOperationLogPage(@Param("operationLog") TbSysOperationLog operationLog,
|
||||
@Param("pageParam") PageParam pageParam,
|
||||
@Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 统计操作日志数量(带权限过滤)
|
||||
* @param operationLog 过滤条件
|
||||
* @param userDeptRoles 当前用户的部门角色列表
|
||||
* @return int 日志数量
|
||||
* @author yslg
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
int countOperationLog(@Param("operationLog") TbSysOperationLog operationLog,
|
||||
@Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
import org.xyzh.common.dto.user.TbSysUser;
|
||||
import org.xyzh.common.dto.user.TbSysUserInfo;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
import org.xyzh.common.vo.UserVO;
|
||||
|
||||
import java.util.List;
|
||||
@@ -20,6 +21,8 @@ import java.util.List;
|
||||
@Mapper
|
||||
public interface UserMapper extends BaseMapper<TbSysUser> {
|
||||
|
||||
TbSysUser selectLoginUser(@Param("filter") TbSysUser filter);
|
||||
|
||||
/**
|
||||
* @description 插入用户
|
||||
* @param user 用户信息
|
||||
@@ -75,36 +78,61 @@ public interface UserMapper extends BaseMapper<TbSysUser> {
|
||||
TbSysUser selectByPhone(@Param("phone") String phone);
|
||||
|
||||
/**
|
||||
* @description 根据过滤条件查询用户
|
||||
* @description 根据过滤条件查询用户(包含权限过滤)
|
||||
* @param filter 过滤条件
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return TbSysUser 用户信息
|
||||
* @author yslg
|
||||
* @since 2025-09-28
|
||||
*/
|
||||
List<TbSysUser> selectByFilter(@Param("filter") TbSysUser filter);
|
||||
List<TbSysUser> selectByFilter(@Param("filter") TbSysUser filter, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 查询用户列表(分页)
|
||||
* @description 根据过滤条件查询用户VO列表(包含userinfo和deptrole信息,包含权限过滤)
|
||||
* @param filter 过滤条件
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<UserVO> 用户VO列表
|
||||
* @author yslg
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
List<UserVO> selectUserVOByFilter(@Param("filter") TbSysUser filter, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 查询用户列表(包含权限过滤)
|
||||
* @param username 用户名(模糊查询)
|
||||
* @param email 邮箱(模糊查询)
|
||||
* @param status 用户状态
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbSysUser> 用户列表
|
||||
* @author yslg
|
||||
* @since 2025-09-28
|
||||
*/
|
||||
List<TbSysUser> selectUserList(@Param("username") String username,
|
||||
@Param("email") String email,
|
||||
@Param("status") String status);
|
||||
@Param("status") String status,
|
||||
@Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 查询用户列表(分页)
|
||||
* @description 查询用户列表(分页,包含权限过滤)
|
||||
* @param filter 过滤条件
|
||||
* @param pageParam 分页参数
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<TbSysUser> 用户列表
|
||||
* @author yslg
|
||||
* @since 2025-09-28
|
||||
*/
|
||||
List<TbSysUser> selectUserPage(@Param("filter") TbSysUser filter, @Param("pageParam") PageParam pageParam);
|
||||
List<TbSysUser> selectUserPage(@Param("filter") TbSysUser filter, @Param("pageParam") PageParam pageParam, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 查询用户VO列表(分页,包含userinfo和deptrole信息,包含权限过滤)
|
||||
* @param filter 过滤条件
|
||||
* @param pageParam 分页参数
|
||||
* @param userDeptRoles 用户部门角色列表
|
||||
* @return List<UserVO> 用户VO列表
|
||||
* @author yslg
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
List<UserVO> selectUserVOPage(@Param("filter") TbSysUser filter, @Param("pageParam") PageParam pageParam, @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles);
|
||||
|
||||
/**
|
||||
* @description 批量删除用户(逻辑删除)
|
||||
@@ -153,4 +181,7 @@ public interface UserMapper extends BaseMapper<TbSysUser> {
|
||||
* @since 2025-10-18
|
||||
*/
|
||||
UserVO selectUserInfoTotal(@Param("userId") String userId);
|
||||
|
||||
|
||||
int countDeptUser(@Param("deptId") String deptId);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.dto.menu.TbSysMenu;
|
||||
import org.xyzh.common.dto.menu.TbSysMenuPermission;
|
||||
import org.xyzh.common.utils.IDUtils;
|
||||
import org.xyzh.common.vo.PermissionVO;
|
||||
import org.xyzh.system.mapper.MenuMapper;
|
||||
import org.xyzh.system.mapper.MenuPermissionMapper;
|
||||
import org.xyzh.system.menu.service.SysMenuService;
|
||||
@@ -463,16 +464,13 @@ public class SysMenuServiceImpl implements SysMenuService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbSysMenuPermission> getMenuPermission(String menuId) {
|
||||
ResultDomain<TbSysMenuPermission> resultDomain = new ResultDomain<>();
|
||||
public ResultDomain<PermissionVO> getMenuPermission(String menuId) {
|
||||
ResultDomain<PermissionVO> resultDomain = new ResultDomain<>();
|
||||
try {
|
||||
logger.info("开始查询菜单权限:{}", menuId);
|
||||
List<TbSysMenuPermission> menuPermissions = menuPermissionMapper.selectByMenuId(menuId);
|
||||
if (menuPermissions.isEmpty()) {
|
||||
resultDomain.fail("未找到菜单权限");
|
||||
return resultDomain;
|
||||
}
|
||||
resultDomain.success("查询菜单权限成功", menuPermissions);
|
||||
List<PermissionVO> permissions = menuPermissionMapper.selectByMenuId(menuId);
|
||||
logger.info("查询菜单权限完成,共找到{}个权限", permissions.size());
|
||||
resultDomain.success("查询菜单权限成功", permissions);
|
||||
return resultDomain;
|
||||
} catch (Exception e) {
|
||||
logger.error("查询菜单权限异常:{}", menuId, e);
|
||||
|
||||
@@ -301,14 +301,14 @@ public class SysPermissionServiceImpl implements SysPermissionService {
|
||||
PermissionVO permissionVO = permissionMapper.selectPermissionVO(permission);
|
||||
|
||||
if (permission.getBindType().equals("menu")) {
|
||||
List<TbSysMenu> bindMenus = permissionMapper.selectPermissionBindMenu(permission);
|
||||
permissionVO.setMenus(bindMenus);
|
||||
resultDomain.success("查询权限绑定列表成功", permissionVO);
|
||||
List<PermissionVO> bindMenus = permissionMapper.selectPermissionBindMenu(permission);
|
||||
logger.info("查询权限绑定菜单列表完成,共找到{}个菜单", bindMenus.size());
|
||||
resultDomain.success("查询权限绑定菜单列表成功", bindMenus);
|
||||
return resultDomain;
|
||||
} else {
|
||||
List<TbSysRole> bindRoles = permissionMapper.selectPermissionBindRole(permission);
|
||||
permissionVO.setRoles(bindRoles);
|
||||
resultDomain.success("查询权限绑定角色列表成功", permissionVO);
|
||||
List<PermissionVO> bindRoles = permissionMapper.selectPermissionBindRole(permission);
|
||||
logger.info("查询权限绑定角色列表完成,共找到{}个角色", bindRoles.size());
|
||||
resultDomain.success("查询权限绑定角色列表成功", bindRoles);
|
||||
return resultDomain;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,15 @@ import org.springframework.util.StringUtils;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.dto.permission.TbSysPermission;
|
||||
import org.xyzh.common.dto.role.TbSysRole;
|
||||
import org.xyzh.common.dto.user.TbSysUser;
|
||||
import org.xyzh.common.utils.IDUtils;
|
||||
import org.xyzh.common.vo.PermissionVO;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
import org.xyzh.system.mapper.RolePermissionMapper;
|
||||
import org.xyzh.system.mapper.RoleMapper;
|
||||
import org.xyzh.system.mapper.UserDeptRoleMapper;
|
||||
import org.xyzh.system.role.service.SysRoleService;
|
||||
import org.xyzh.system.utils.LoginUtil;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -40,13 +43,19 @@ public class SysRoleServiceImpl implements SysRoleService {
|
||||
private UserDeptRoleMapper userDeptRoleMapper;
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbSysRole> getAllRoles() {
|
||||
ResultDomain<TbSysRole> resultDomain = new ResultDomain<>();
|
||||
public ResultDomain<PermissionVO> getAllRoles() {
|
||||
ResultDomain<PermissionVO> resultDomain = new ResultDomain<>();
|
||||
|
||||
try {
|
||||
logger.info("开始查询所有角色");
|
||||
logger.info("开始查询所有角色(包含权限过滤)");
|
||||
|
||||
List<TbSysRole> roles = roleMapper.selectAllRoles();
|
||||
// 获取当前用户的部门角色信息
|
||||
TbSysUser currentUser = LoginUtil.getCurrentUser();
|
||||
List<UserDeptRoleVO> userDeptRoles = roleMapper.selectDeptRolesByUserId(currentUser.getID());
|
||||
logger.info("当前用户拥有 {} 个部门角色", userDeptRoles.size());
|
||||
|
||||
// 查询有权限的角色列表
|
||||
List<PermissionVO> roles = roleMapper.selectAllRoles(userDeptRoles);
|
||||
|
||||
logger.info("查询所有角色完成,共找到{}个角色", roles.size());
|
||||
resultDomain.success("查询成功", roles);
|
||||
@@ -60,12 +69,18 @@ public class SysRoleServiceImpl implements SysRoleService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbSysRole> getRoleList(TbSysRole filter) {
|
||||
ResultDomain<TbSysRole> resultDomain = new ResultDomain<>();
|
||||
public ResultDomain<PermissionVO> getRoleList(TbSysRole filter) {
|
||||
ResultDomain<PermissionVO> resultDomain = new ResultDomain<>();
|
||||
try {
|
||||
logger.info("开始根据过滤条件查询角色列表:{}", filter);
|
||||
logger.info("开始根据过滤条件查询角色列表(包含权限过滤):{}", filter);
|
||||
|
||||
List<TbSysRole> roles = roleMapper.selectRole(filter);
|
||||
// 获取当前用户的部门角色信息
|
||||
TbSysUser currentUser = LoginUtil.getCurrentUser();
|
||||
List<UserDeptRoleVO> userDeptRoles = roleMapper.selectDeptRolesByUserId(currentUser.getID());
|
||||
logger.info("当前用户拥有 {} 个部门角色", userDeptRoles.size());
|
||||
|
||||
// 查询有权限的角色列表
|
||||
List<PermissionVO> roles = roleMapper.selectRole(filter, userDeptRoles);
|
||||
|
||||
logger.info("查询角色列表完成,共找到{}个角色", roles.size());
|
||||
resultDomain.success("查询成功", roles);
|
||||
@@ -233,18 +248,28 @@ public class SysRoleServiceImpl implements SysRoleService {
|
||||
resultDomain.fail("角色ID不能为空");
|
||||
return resultDomain;
|
||||
}
|
||||
// ByID 是精确查询,不需要权限过滤
|
||||
TbSysRole filter = new TbSysRole();
|
||||
filter.setRoleID(roleId);
|
||||
filter.setDeleted(false);
|
||||
List<TbSysRole> roles = roleMapper.selectRole(filter);
|
||||
TbSysRole role = roles.isEmpty() ? null : roles.get(0);
|
||||
// 获取当前用户的部门角色信息
|
||||
TbSysUser currentUser = LoginUtil.getCurrentUser();
|
||||
List<UserDeptRoleVO> userDeptRoles = roleMapper.selectDeptRolesByUserId(currentUser.getID());
|
||||
List<PermissionVO> roleVOs = roleMapper.selectRole(filter, userDeptRoles);
|
||||
|
||||
if (role == null) {
|
||||
if (roleVOs.isEmpty()) {
|
||||
logger.warn("未找到角色:{}", roleId);
|
||||
resultDomain.fail("未找到指定角色");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
// 将 PermissionVO 转换为 TbSysRole
|
||||
PermissionVO roleVO = roleVOs.get(0);
|
||||
TbSysRole role = new TbSysRole();
|
||||
role.setRoleID(roleVO.getRoleID());
|
||||
role.setName(roleVO.getRoleName());
|
||||
role.setDescription(roleVO.getRoleDescription());
|
||||
|
||||
logger.info("根据ID查询角色完成:{}", roleId);
|
||||
resultDomain.success("查询成功", role);
|
||||
return resultDomain;
|
||||
@@ -291,7 +316,11 @@ public class SysRoleServiceImpl implements SysRoleService {
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
int count = roleMapper.countByRoleName(roleName, excludeId);
|
||||
// 获取当前用户的部门角色信息(用于权限过滤)
|
||||
TbSysUser currentUser = LoginUtil.getCurrentUser();
|
||||
List<UserDeptRoleVO> userDeptRoles = roleMapper.selectDeptRolesByUserId(currentUser.getID());
|
||||
|
||||
int count = roleMapper.countByRoleName(roleName, excludeId, userDeptRoles);
|
||||
boolean exists = count > 0;
|
||||
|
||||
logger.info("角色名称存在性检查完成:{},存在:{}", roleName, exists);
|
||||
|
||||
@@ -113,8 +113,9 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
try {
|
||||
logger.info("开始查询所有用户");
|
||||
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
TbSysUser filter = new TbSysUser();
|
||||
List<TbSysUser> users = userMapper.selectByFilter(filter);
|
||||
List<TbSysUser> users = userMapper.selectByFilter(filter, userDeptRoles);
|
||||
|
||||
logger.info("查询所有用户完成,共找到{}个用户", users.size());
|
||||
resultDomain.success("查询成功", users);
|
||||
@@ -138,11 +139,12 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
TbSysUser filter = new TbSysUser();
|
||||
filter.setID(userId);
|
||||
filter.setDeleted(false);
|
||||
|
||||
List<TbSysUser> users = userMapper.selectByFilter(filter);
|
||||
List<TbSysUser> users = userMapper.selectByFilter(filter, userDeptRoles);
|
||||
|
||||
if (users.isEmpty()) {
|
||||
logger.warn("未找到用户:{}", userId);
|
||||
@@ -213,7 +215,8 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
// return resultDomain;
|
||||
// }
|
||||
|
||||
List<TbSysUser> users = userMapper.selectByFilter(filter);
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbSysUser> users = userMapper.selectByFilter(filter, userDeptRoles);
|
||||
|
||||
if (users.isEmpty()) {
|
||||
logger.warn("未找到符合条件的用户:{}", filter);
|
||||
@@ -232,13 +235,75 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<UserVO> getUserVOByFilter(TbSysUser filter) {
|
||||
ResultDomain<UserVO> resultDomain = new ResultDomain<>();
|
||||
try {
|
||||
logger.info("开始根据过滤条件查询用户VO:{}", filter);
|
||||
|
||||
if (filter == null) {
|
||||
resultDomain.fail("过滤条件不能为空");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<UserVO> userVOs = userMapper.selectUserVOByFilter(filter, userDeptRoles);
|
||||
|
||||
if (userVOs.isEmpty()) {
|
||||
logger.warn("未找到符合条件的用户:{}", filter);
|
||||
resultDomain.fail("未找到指定用户");
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
logger.info("成功查询到 {} 个用户", userVOs.size());
|
||||
resultDomain.success("查询成功", userVOs);
|
||||
return resultDomain;
|
||||
} catch (Exception e) {
|
||||
logger.error("根据过滤条件查询用户VO失败:{}", filter, e);
|
||||
resultDomain.fail("查询用户失败:" + e.getMessage());
|
||||
return resultDomain;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbSysUser> getLoginUser(TbSysUser filter) {
|
||||
ResultDomain<TbSysUser> resultDomain = new ResultDomain<>();
|
||||
TbSysUser users = userMapper.selectLoginUser(filter);
|
||||
resultDomain.success("查询成功", users);
|
||||
return resultDomain;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbSysUser> getUserPage(TbSysUser filter, PageParam pageParam) {
|
||||
ResultDomain<TbSysUser> resultDomain = new ResultDomain<>();
|
||||
List<TbSysUser> users = userMapper.selectUserPage(filter, pageParam);
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbSysUser> users = userMapper.selectUserPage(filter, pageParam, userDeptRoles);
|
||||
PageDomain<TbSysUser> pageDomain = new PageDomain<>();
|
||||
|
||||
int count = userMapper.countDeptUser(userDeptRoles.get(0).getDeptID());
|
||||
pageDomain.setDataList(users);
|
||||
pageDomain.setPageParam(pageParam);
|
||||
pageParam.setTotalElements(count);
|
||||
pageParam.setTotalPages((int) Math.ceil(count / pageParam.getPageSize()));
|
||||
pageDomain.setPageParam(pageParam);
|
||||
resultDomain.success("查询成功", pageDomain);
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<UserVO> getUserVOPage(TbSysUser filter, PageParam pageParam) {
|
||||
ResultDomain<UserVO> resultDomain = new ResultDomain<>();
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<UserVO> userVOs = userMapper.selectUserVOPage(filter, pageParam, userDeptRoles);
|
||||
PageDomain<UserVO> pageDomain = new PageDomain<>();
|
||||
|
||||
int count = userMapper.countDeptUser(userDeptRoles.get(0).getDeptID());
|
||||
pageDomain.setDataList(userVOs);
|
||||
pageDomain.setPageParam(pageParam);
|
||||
pageParam.setTotalElements(count);
|
||||
pageParam.setTotalPages((int) Math.ceil((double)count / pageParam.getPageSize()));
|
||||
pageDomain.setPageParam(pageParam);
|
||||
resultDomain.success("查询成功", pageDomain);
|
||||
return resultDomain;
|
||||
}
|
||||
@@ -503,6 +568,7 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
TbSysUser filter = new TbSysUser();
|
||||
if (StringUtils.hasText(excludeId)) {
|
||||
filter.setID(excludeId);
|
||||
@@ -510,7 +576,7 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
filter.setUsername(username);
|
||||
filter.setDeleted(false);
|
||||
|
||||
long count = userMapper.selectByFilter(filter).size();
|
||||
long count = userMapper.selectByFilter(filter, userDeptRoles).size();
|
||||
boolean exists = count > 0;
|
||||
|
||||
logger.info("用户名存在性检查完成:{},存在:{}", username, exists);
|
||||
@@ -535,6 +601,7 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
return resultDomain;
|
||||
}
|
||||
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
TbSysUser filter = new TbSysUser();
|
||||
filter.setEmail(email);
|
||||
filter.setDeleted(false);
|
||||
@@ -543,7 +610,7 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
filter.setID(excludeId);
|
||||
}
|
||||
|
||||
long count = userMapper.selectByFilter(filter).size();
|
||||
long count = userMapper.selectByFilter(filter, userDeptRoles).size();
|
||||
boolean exists = count > 0;
|
||||
|
||||
logger.info("邮箱存在性检查完成:{},存在:{}", email, exists);
|
||||
@@ -563,7 +630,8 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
try {
|
||||
logger.info("开始搜索用户,用户名:{},邮箱:{},状态:{}", username, email, status);
|
||||
|
||||
List<TbSysUser> users = userMapper.selectUserList(username, email, status);
|
||||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||||
List<TbSysUser> users = userMapper.selectUserList(username, email, status, userDeptRoles);
|
||||
|
||||
logger.info("搜索用户完成,共找到{}个用户", users.size());
|
||||
resultDomain.success("搜索成功", users);
|
||||
|
||||
@@ -11,7 +11,7 @@ import org.xyzh.common.dto.user.TbSysUser;
|
||||
import org.xyzh.common.dto.user.TbSysUserInfo;
|
||||
import org.xyzh.common.redis.service.RedisService;
|
||||
import org.xyzh.common.utils.NonUtils;
|
||||
import org.xyzh.common.utils.ServletUtil;
|
||||
import org.xyzh.common.utils.ServletUtils;
|
||||
import org.xyzh.common.vo.UserDeptRoleVO;
|
||||
|
||||
/**
|
||||
@@ -48,7 +48,7 @@ public class LoginUtil {
|
||||
public static LoginDomain getCurrentLoginDomain() {
|
||||
try {
|
||||
// 从当前请求获取token
|
||||
String token = ServletUtil.getToken();
|
||||
String token = ServletUtils.getToken();
|
||||
if (NonUtils.isEmpty(token)) {
|
||||
return null;
|
||||
}
|
||||
@@ -238,6 +238,6 @@ public class LoginUtil {
|
||||
* @since 2025-10-07
|
||||
*/
|
||||
public static String getCurrentToken() {
|
||||
return ServletUtil.getToken();
|
||||
return ServletUtils.getToken();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,26 @@
|
||||
<result column="delete_time" property="deleteTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="deleted" property="deleted" jdbcType="BOOLEAN"/>
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="DeptRoleResultMap" type="org.xyzh.common.dto.role.TbSysRole">
|
||||
<id column="role_id" property="roleID" jdbcType="VARCHAR"/>
|
||||
<result column="role_name" property="name" jdbcType="VARCHAR"/>
|
||||
<result column="role_description" property="description" jdbcType="VARCHAR"/>
|
||||
<result column="creator" property="creator" jdbcType="VARCHAR"/>
|
||||
<result column="updater" property="updater" jdbcType="VARCHAR"/>
|
||||
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="delete_time" property="deleteTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="deleted" property="deleted" jdbcType="BOOLEAN"/>
|
||||
</resultMap>
|
||||
<!-- 部门角色VO结果映射 -->
|
||||
<resultMap id="DeptRoleVOResultMap" type="org.xyzh.common.vo.UserDeptRoleVO">
|
||||
<result column="dept_id" property="deptID" jdbcType="VARCHAR"/>
|
||||
<result column="dept_name" property="deptName" jdbcType="VARCHAR"/>
|
||||
<result column="dept_description" property="deptDescription" jdbcType="VARCHAR"/>
|
||||
<result column="role_id" property="roleID" jdbcType="VARCHAR"/>
|
||||
<result column="role_name" property="roleName" jdbcType="VARCHAR"/>
|
||||
<result column="role_description" property="roleDescription" jdbcType="VARCHAR"/>
|
||||
</resultMap>
|
||||
<!-- 基础字段 -->
|
||||
<sql id="Base_Column_List">
|
||||
id, dept_id, role_id, creator, updater,
|
||||
@@ -29,25 +48,24 @@
|
||||
</where>
|
||||
</sql>
|
||||
|
||||
<!-- selectDeptRole -->
|
||||
<!-- selectDeptRole - 查询指定部门的角色绑定(包含名称) -->
|
||||
|
||||
<select id="selectDeptRole">
|
||||
<select id="selectDeptRole" resultMap="DeptRoleVOResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM tb_sys_dept_role
|
||||
<include refid="Where_Clause"/>
|
||||
ORDER BY create_time DESC
|
||||
dr.dept_id,
|
||||
d.name AS dept_name,
|
||||
d.description AS dept_description,
|
||||
dr.role_id,
|
||||
r.name AS role_name,
|
||||
r.description AS role_description
|
||||
FROM tb_sys_dept_role dr
|
||||
LEFT JOIN tb_sys_dept d ON dr.dept_id = d.dept_id AND d.deleted = 0
|
||||
LEFT JOIN tb_sys_role r ON dr.role_id = r.role_id AND r.deleted = 0
|
||||
WHERE dr.deleted = 0
|
||||
AND dr.dept_id = #{deptId}
|
||||
ORDER BY dr.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 部门角色VO结果映射 -->
|
||||
<resultMap id="DeptRoleVOResultMap" type="org.xyzh.common.vo.UserDeptRoleVO">
|
||||
<result column="dept_id" property="deptID" jdbcType="VARCHAR"/>
|
||||
<result column="dept_name" property="deptName" jdbcType="VARCHAR"/>
|
||||
<result column="dept_description" property="deptDescription" jdbcType="VARCHAR"/>
|
||||
<result column="role_id" property="roleID" jdbcType="VARCHAR"/>
|
||||
<result column="role_name" property="roleName" jdbcType="VARCHAR"/>
|
||||
<result column="role_description" property="roleDescription" jdbcType="VARCHAR"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectDeptRoleList" resultMap="DeptRoleVOResultMap">
|
||||
SELECT
|
||||
|
||||
@@ -15,6 +15,24 @@
|
||||
<result column="deleted" property="deleted" jdbcType="BOOLEAN"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 权限VO结果映射(用于菜单权限查询) -->
|
||||
<resultMap id="PermissionVOResultMap" type="org.xyzh.common.vo.PermissionVO">
|
||||
<id column="id" property="id" jdbcType="VARCHAR"/>
|
||||
<result column="module_id" property="moduleID" jdbcType="VARCHAR"/>
|
||||
<result column="module_name" property="moduleName" jdbcType="VARCHAR"/>
|
||||
<result column="module_description" property="moduleDescription" jdbcType="VARCHAR"/>
|
||||
<result column="permission_id" property="permissionID" jdbcType="VARCHAR"/>
|
||||
<result column="permission_name" property="name" jdbcType="VARCHAR"/>
|
||||
<result column="permission_code" property="code" jdbcType="VARCHAR"/>
|
||||
<result column="permission_description" property="description" jdbcType="VARCHAR"/>
|
||||
<result column="creator" property="creator" jdbcType="VARCHAR"/>
|
||||
<result column="updater" property="updater" jdbcType="VARCHAR"/>
|
||||
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="delete_time" property="deleteTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="deleted" property="deleted" jdbcType="BOOLEAN"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 基础字段 -->
|
||||
<sql id="Base_Column_List">
|
||||
id, menu_id, permission_id, creator, updater,
|
||||
@@ -51,14 +69,29 @@
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<!-- 根据菜单ID查询权限关联列表 -->
|
||||
<select id="selectByMenuId" resultMap="BaseResultMap">
|
||||
<!-- 根据菜单ID查询权限列表(返回PermissionVO) -->
|
||||
<select id="selectByMenuId" resultMap="PermissionVOResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM tb_sys_menu_permission
|
||||
WHERE deleted = 0
|
||||
AND menu_id = #{menuId}
|
||||
ORDER BY create_time ASC
|
||||
mp.id,
|
||||
p.module_id,
|
||||
m.name AS module_name,
|
||||
m.description AS module_description,
|
||||
mp.permission_id,
|
||||
p.name AS permission_name,
|
||||
p.code AS permission_code,
|
||||
p.description AS permission_description,
|
||||
mp.creator,
|
||||
mp.updater,
|
||||
mp.create_time,
|
||||
mp.update_time,
|
||||
mp.delete_time,
|
||||
mp.deleted
|
||||
FROM tb_sys_menu_permission mp
|
||||
LEFT JOIN tb_sys_permission p ON mp.permission_id = p.permission_id AND p.deleted = 0
|
||||
LEFT JOIN tb_sys_module m ON p.module_id = m.module_id AND m.deleted = 0
|
||||
WHERE mp.deleted = 0
|
||||
AND mp.menu_id = #{menuId}
|
||||
ORDER BY mp.create_time ASC
|
||||
</select>
|
||||
|
||||
<!-- 根据权限ID查询菜单关联列表 -->
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
|
||||
<resultMap id="PermissionVO" type="org.xyzh.common.vo.PermissionVO">
|
||||
<id column="id" property="id" jdbcType="VARCHAR"/>
|
||||
<result column="module_id" property="moduleID" jdbcType="VARCHAR"/>
|
||||
<result column="module_name" property="moduleName" jdbcType="VARCHAR"/>
|
||||
<result column="module_description" property="moduleDescription" jdbcType="VARCHAR"/>
|
||||
<result column="permission_id" property="permissionID" jdbcType="VARCHAR"/>
|
||||
<result column="name" property="name" jdbcType="VARCHAR"/>
|
||||
<result column="code" property="code" jdbcType="VARCHAR"/>
|
||||
@@ -47,6 +50,26 @@
|
||||
<result column="description" property="description" jdbcType="VARCHAR"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 权限绑定菜单VO结果映射 -->
|
||||
<resultMap id="PermissionBindMenuVO" type="org.xyzh.common.vo.PermissionVO">
|
||||
<id column="id" property="id" jdbcType="VARCHAR"/>
|
||||
<result column="menu_id" property="menuID" jdbcType="VARCHAR"/>
|
||||
<result column="menu_name" property="menuName" jdbcType="VARCHAR"/>
|
||||
<result column="menu_url" property="menuUrl" jdbcType="VARCHAR"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 权限绑定角色VO结果映射 -->
|
||||
<resultMap id="PermissionBindRoleVO" type="org.xyzh.common.vo.PermissionVO">
|
||||
<id column="id" property="id" jdbcType="VARCHAR"/>
|
||||
<result column="role_id" property="roleID" jdbcType="VARCHAR"/>
|
||||
<result column="role_name" property="roleName" jdbcType="VARCHAR"/>
|
||||
<result column="role_description" property="roleDescription" jdbcType="VARCHAR"/>
|
||||
<result column="creator" property="creator" jdbcType="VARCHAR"/>
|
||||
<result column="creator_name" property="creatorName" jdbcType="VARCHAR"/>
|
||||
<result column="updater" property="updater" jdbcType="VARCHAR"/>
|
||||
<result column="updater_name" property="updaterName" jdbcType="VARCHAR"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 基础字段 -->
|
||||
<sql id="Base_Column_List">
|
||||
id, permission_id, name, code, description, module_id, creator, updater,
|
||||
@@ -226,13 +249,15 @@
|
||||
|
||||
<select id="selectPermissionList" resultMap="PermissionVO" >
|
||||
SELECT
|
||||
tsp.id, tsp.permission_id, tsp.name, tsp.code, tsp.description,
|
||||
tsp.id, tsp.module_id, tsp.permission_id, tsp.name, tsp.code, tsp.description,
|
||||
tsp.creator, tsp.updater,
|
||||
tsu.username as creator_name, tuu.username as updater_name,
|
||||
m.name as module_name, m.description as module_description,
|
||||
tsp.create_time, tsp.update_time, tsp.delete_time, tsp.deleted
|
||||
FROM tb_sys_permission tsp
|
||||
INNER JOIN tb_sys_user tsu ON tsp.creator = tsu.id
|
||||
LEFT JOIN tb_sys_user tuu ON tsp.updater = tuu.id
|
||||
LEFT JOIN tb_sys_module m ON tsp.module_id = m.module_id AND m.deleted = 0
|
||||
WHERE tsp.deleted = 0
|
||||
<if test="permission.name != null and permission.name != ''">
|
||||
AND tsp.name LIKE CONCAT('%', #{permission.name}, '%')
|
||||
@@ -247,13 +272,15 @@
|
||||
|
||||
<select id="selectPermissionVO" resultMap="PermissionVO" >
|
||||
SELECT
|
||||
tsp.id, tsp.permission_id, tsp.name, tsp.code, tsp.description,
|
||||
tsp.id, tsp.module_id, tsp.permission_id, tsp.name, tsp.code, tsp.description,
|
||||
tsp.creator, tsp.updater,
|
||||
tsu.username as creator_name, tuu.username as updater_name,
|
||||
m.name as module_name, m.description as module_description,
|
||||
tsp.create_time, tsp.update_time, tsp.delete_time, tsp.deleted
|
||||
FROM tb_sys_permission tsp
|
||||
INNER JOIN tb_sys_user tsu ON tsp.creator = tsu.id
|
||||
LEFT JOIN tb_sys_user tuu ON tsp.updater = tuu.id
|
||||
LEFT JOIN tb_sys_module m ON tsp.module_id = m.module_id AND m.deleted = 0
|
||||
WHERE tsp.deleted = 0
|
||||
<if test="permission.name != null and permission.name != ''">
|
||||
AND tsp.name LIKE CONCAT('%', #{permission.name}, '%')
|
||||
@@ -269,9 +296,12 @@
|
||||
|
||||
<!-- selectPermissionBindMenu -->
|
||||
|
||||
<select id="selectPermissionBindMenu" resultMap="PermissionBindMenu">
|
||||
<select id="selectPermissionBindMenu" resultMap="PermissionBindMenuVO">
|
||||
SELECT
|
||||
tsm.id, tsm.menu_id, tsm.name
|
||||
tsm.id,
|
||||
tsm.menu_id,
|
||||
tsm.name AS menu_name,
|
||||
tsm.url AS menu_url
|
||||
FROM tb_sys_menu tsm
|
||||
INNER JOIN tb_sys_menu_permission tsmp ON tsmp.menu_id = tsm.menu_id
|
||||
WHERE tsm.deleted = 0
|
||||
@@ -281,11 +311,20 @@
|
||||
</select>
|
||||
|
||||
<!-- selectPermissionBindRole -->
|
||||
<select id="selectPermissionBindRole" resultMap="PermissionBindRole">
|
||||
<select id="selectPermissionBindRole" resultMap="PermissionBindRoleVO">
|
||||
SELECT
|
||||
tsr.id, tsr.role_id, tsr.name, tsr.description
|
||||
tsr.id,
|
||||
tsr.role_id,
|
||||
tsr.name AS role_name,
|
||||
tsr.description AS role_description,
|
||||
tsr.creator,
|
||||
cu.username AS creator_name,
|
||||
tsr.updater,
|
||||
uu.username AS updater_name
|
||||
FROM tb_sys_role tsr
|
||||
INNER JOIN tb_sys_role_permission tsrp ON tsrp.role_id = tsr.role_id
|
||||
LEFT JOIN tb_sys_user cu ON tsr.creator = cu.id AND cu.deleted = 0
|
||||
LEFT JOIN tb_sys_user uu ON tsr.updater = uu.id AND uu.deleted = 0
|
||||
WHERE tsr.deleted = 0
|
||||
AND tsrp.deleted = 0
|
||||
AND tsrp.permission_id = #{permission.permissionID}
|
||||
|
||||
@@ -47,23 +47,97 @@
|
||||
</where>
|
||||
</sql>
|
||||
|
||||
<!-- selectAllRoles -->
|
||||
<!-- 权限过滤条件(基于dept_path的高效继承) -->
|
||||
<sql id="Permission_Filter">
|
||||
INNER JOIN tb_resource_permission rp ON r.role_id = rp.resource_id
|
||||
AND rp.resource_type = 5
|
||||
AND rp.deleted = 0
|
||||
AND rp.can_read = 1
|
||||
AND (
|
||||
-- 全局权限:所有用户可访问
|
||||
(rp.dept_id IS NULL AND rp.role_id IS NULL)
|
||||
<if test="userDeptRoles != null and userDeptRoles.size() > 0">
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM (
|
||||
<foreach collection="userDeptRoles" item="udr" separator=" UNION ALL ">
|
||||
SELECT #{udr.deptID} AS dept_id, #{udr.deptPath} AS dept_path, #{udr.roleID} AS role_id
|
||||
</foreach>
|
||||
) user_roles
|
||||
LEFT JOIN tb_sys_dept perm_dept ON perm_dept.dept_id = rp.dept_id AND perm_dept.deleted = 0
|
||||
WHERE
|
||||
-- 部门级权限:当前部门或父部门(通过dept_path判断继承关系)
|
||||
(rp.role_id IS NULL AND rp.dept_id IS NOT NULL
|
||||
AND user_roles.dept_path LIKE CONCAT(perm_dept.dept_path, '%'))
|
||||
-- 角色级权限:跨部门的角色权限
|
||||
OR (rp.dept_id IS NULL AND rp.role_id = user_roles.role_id)
|
||||
-- 精确权限:特定部门的特定角色
|
||||
OR (rp.dept_id = user_roles.dept_id AND rp.role_id = user_roles.role_id)
|
||||
)
|
||||
</if>
|
||||
)
|
||||
</sql>
|
||||
|
||||
<select id="selectAllRoles" resultMap="tbSysRoleResultMap">
|
||||
SELECT
|
||||
<include refid="TbSysRole_Column_List"/>
|
||||
FROM tb_sys_role
|
||||
WHERE deleted = 0
|
||||
ORDER BY role_id, create_time ASC
|
||||
<!-- 角色VO结果映射(包含创建人更新人名称) -->
|
||||
<resultMap id="RoleVOResultMap" type="org.xyzh.common.vo.PermissionVO">
|
||||
<id column="id" property="id" jdbcType="VARCHAR"/>
|
||||
<result column="role_id" property="roleID" jdbcType="VARCHAR"/>
|
||||
<result column="role_name" property="roleName" jdbcType="VARCHAR"/>
|
||||
<result column="role_description" property="roleDescription" jdbcType="VARCHAR"/>
|
||||
<result column="creator" property="creator" jdbcType="VARCHAR"/>
|
||||
<result column="creator_name" property="creatorName" jdbcType="VARCHAR"/>
|
||||
<result column="updater" property="updater" jdbcType="VARCHAR"/>
|
||||
<result column="updater_name" property="updaterName" jdbcType="VARCHAR"/>
|
||||
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- selectAllRoles - 添加权限过滤和VO返回 -->
|
||||
<select id="selectAllRoles" resultMap="RoleVOResultMap">
|
||||
SELECT DISTINCT
|
||||
r.id,
|
||||
r.role_id,
|
||||
r.name AS role_name,
|
||||
r.description AS role_description,
|
||||
r.creator,
|
||||
cu.username AS creator_name,
|
||||
r.updater,
|
||||
uu.username AS updater_name,
|
||||
r.create_time,
|
||||
r.update_time
|
||||
FROM tb_sys_role r
|
||||
<include refid="Permission_Filter"/>
|
||||
LEFT JOIN tb_sys_user cu ON r.creator = cu.id AND cu.deleted = 0
|
||||
LEFT JOIN tb_sys_user uu ON r.updater = uu.id AND uu.deleted = 0
|
||||
WHERE r.deleted = 0
|
||||
ORDER BY r.role_id, r.create_time ASC
|
||||
</select>
|
||||
|
||||
<!-- 根据过滤条件查询角色列表 -->
|
||||
<select id="selectRole" resultMap="tbSysRoleResultMap">
|
||||
SELECT
|
||||
<include refid="TbSysRole_Column_List"/>
|
||||
FROM tb_sys_role
|
||||
<include refid="Where_Clause"/>
|
||||
ORDER BY role_id, create_time ASC
|
||||
<!-- 根据过滤条件查询角色列表 - 添加权限过滤 -->
|
||||
<select id="selectRole" resultMap="RoleVOResultMap">
|
||||
SELECT DISTINCT
|
||||
r.id,
|
||||
r.role_id,
|
||||
r.name AS role_name,
|
||||
r.description AS role_description,
|
||||
r.creator,
|
||||
cu.username AS creator_name,
|
||||
r.updater,
|
||||
uu.username AS updater_name,
|
||||
r.create_time,
|
||||
r.update_time
|
||||
FROM tb_sys_role r
|
||||
<include refid="Permission_Filter"/>
|
||||
LEFT JOIN tb_sys_user cu ON r.creator = cu.id AND cu.deleted = 0
|
||||
LEFT JOIN tb_sys_user uu ON r.updater = uu.id AND uu.deleted = 0
|
||||
WHERE r.deleted = 0
|
||||
<if test="filter.roleID != null and filter.roleID != ''">
|
||||
AND r.role_id = #{filter.roleID}
|
||||
</if>
|
||||
<if test="filter.name != null and filter.name != ''">
|
||||
AND r.name LIKE CONCAT('%', #{filter.name}, '%')
|
||||
</if>
|
||||
ORDER BY r.role_id, r.create_time ASC
|
||||
</select>
|
||||
|
||||
<!-- 插入角色 -->
|
||||
@@ -108,35 +182,15 @@
|
||||
ORDER BY dr.create_time ASC
|
||||
</select>
|
||||
|
||||
<!-- 根据角色编码查询角色 -->
|
||||
<select id="selectByRoleCode" resultMap="tbSysRoleResultMap">
|
||||
SELECT
|
||||
<include refid="TbSysRole_Column_List"/>
|
||||
FROM tb_sys_role
|
||||
WHERE deleted = 0
|
||||
AND role_code = #{roleCode}
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<!-- 检查角色名称是否存在 -->
|
||||
<!-- 检查角色名称是否存在 - 添加权限过滤 -->
|
||||
<select id="countByRoleName" resultType="int">
|
||||
SELECT COUNT(1)
|
||||
FROM tb_sys_role
|
||||
WHERE deleted = 0
|
||||
AND name = #{roleName}
|
||||
SELECT COUNT(DISTINCT r.id)
|
||||
FROM tb_sys_role r
|
||||
<include refid="Permission_Filter"/>
|
||||
WHERE r.deleted = 0
|
||||
AND r.name = #{roleName}
|
||||
<if test="excludeId != null and excludeId != ''">
|
||||
AND id != #{excludeId}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<!-- 检查角色编码是否存在 -->
|
||||
<select id="countByRoleCode" resultType="int">
|
||||
SELECT COUNT(1)
|
||||
FROM tb_sys_role
|
||||
WHERE deleted = 0
|
||||
AND role_code = #{roleCode}
|
||||
<if test="excludeId != null and excludeId != ''">
|
||||
AND id != #{excludeId}
|
||||
AND r.id != #{excludeId}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
@@ -155,9 +209,8 @@
|
||||
|
||||
|
||||
|
||||
<!-- checkRoleExists -->
|
||||
|
||||
<select id="checkRoleExists">
|
||||
<!-- checkRoleExists - 检查角色是否存在,不需要权限过滤 -->
|
||||
<select id="checkRoleExists" resultMap="tbSysRoleResultMap">
|
||||
SELECT
|
||||
<include refid="TbSysRole_Column_List"/>
|
||||
FROM tb_sys_role
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.xyzh.system.mapper.SysLoginLogMapper">
|
||||
|
||||
<resultMap id="BaseResultMap" type="org.xyzh.common.dto.system.TbSysLoginLog">
|
||||
<id column="id" property="id" jdbcType="VARCHAR"/>
|
||||
<result column="user_id" property="userID" jdbcType="VARCHAR"/>
|
||||
<result column="username" property="username" jdbcType="VARCHAR"/>
|
||||
<result column="ip_address" property="ipAddress" jdbcType="VARCHAR"/>
|
||||
<result column="ip_source" property="ipSource" jdbcType="VARCHAR"/>
|
||||
<result column="browser" property="browser" jdbcType="VARCHAR"/>
|
||||
<result column="os" property="os" jdbcType="VARCHAR"/>
|
||||
<result column="password" property="password" jdbcType="VARCHAR"/>
|
||||
<result column="login_time" property="loginTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="status" property="status" jdbcType="INTEGER"/>
|
||||
<result column="error_count" property="errorCount" jdbcType="INTEGER"/>
|
||||
<result column="message" property="message" jdbcType="VARCHAR"/>
|
||||
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 权限过滤:通过用户的部门路径进行权限控制,superadmin可查看所有 -->
|
||||
<sql id="Permission_Filter">
|
||||
INNER JOIN tb_sys_user u ON ll.user_id = u.id AND u.deleted = 0
|
||||
INNER JOIN tb_sys_user_dept_role udr ON u.id = udr.user_id AND udr.deleted = 0
|
||||
INNER JOIN tb_sys_dept d ON udr.dept_id = d.dept_id AND d.deleted = 0
|
||||
WHERE (
|
||||
-- superadmin 可以查看所有登录日志
|
||||
EXISTS (
|
||||
SELECT 1 FROM (
|
||||
<foreach collection="userDeptRoles" item="currentRole" separator=" UNION ALL ">
|
||||
SELECT #{currentRole.deptID} AS dept_id, #{currentRole.roleID} AS role_id
|
||||
</foreach>
|
||||
) admin_check
|
||||
WHERE admin_check.dept_id = 'root_department'
|
||||
AND admin_check.role_id = 'superadmin'
|
||||
)
|
||||
-- 普通用户按部门路径过滤(本部门及子部门)
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM (
|
||||
<foreach collection="userDeptRoles" item="currentRole" separator=" UNION ALL ">
|
||||
SELECT #{currentRole.deptPath} AS user_dept_path
|
||||
</foreach>
|
||||
) user_roles
|
||||
WHERE d.dept_path LIKE CONCAT(user_roles.user_dept_path, '%')
|
||||
)
|
||||
)
|
||||
</sql>
|
||||
|
||||
<insert id="insertLoginLog" parameterType="org.xyzh.common.dto.system.TbSysLoginLog">
|
||||
INSERT INTO tb_sys_login_log (id, user_id, username, ip_address, ip_source, browser, os, password, login_time, status, error_count, message, create_time)
|
||||
VALUES (#{id}, #{userID}, #{username}, #{ipAddress}, #{ipSource}, #{browser}, #{os}, #{password}, #{loginTime, jdbcType=TIMESTAMP}, #{status}, #{errorCount}, #{message}, now());
|
||||
</insert>
|
||||
|
||||
<!-- 查询登录日志列表(带权限过滤) -->
|
||||
<select id="selectLoginLogList" resultMap="BaseResultMap">
|
||||
SELECT DISTINCT ll.*
|
||||
FROM tb_sys_login_log ll
|
||||
<include refid="Permission_Filter"/>
|
||||
<if test="loginLog.userID != null and loginLog.userID != ''">
|
||||
AND ll.user_id = #{loginLog.userID}
|
||||
</if>
|
||||
<if test="loginLog.username != null and loginLog.username != ''">
|
||||
AND ll.username LIKE CONCAT('%', #{loginLog.username}, '%')
|
||||
</if>
|
||||
<if test="loginLog.ipAddress != null and loginLog.ipAddress != ''">
|
||||
AND ll.ip_address = #{loginLog.ipAddress}
|
||||
</if>
|
||||
<if test="loginLog.status != null">
|
||||
AND ll.status = #{loginLog.status}
|
||||
</if>
|
||||
<if test="loginLog.browser != null and loginLog.browser != ''">
|
||||
AND ll.browser LIKE CONCAT('%', #{loginLog.browser}, '%')
|
||||
</if>
|
||||
<if test="loginLog.os != null and loginLog.os != ''">
|
||||
AND ll.os LIKE CONCAT('%', #{loginLog.os}, '%')
|
||||
</if>
|
||||
ORDER BY ll.login_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 分页查询登录日志列表(带权限过滤) -->
|
||||
<select id="selectLoginLogPage" resultMap="BaseResultMap">
|
||||
SELECT DISTINCT ll.*
|
||||
FROM tb_sys_login_log ll
|
||||
<include refid="Permission_Filter"/>
|
||||
<if test="loginLog.userID != null and loginLog.userID != ''">
|
||||
AND ll.user_id = #{loginLog.userID}
|
||||
</if>
|
||||
<if test="loginLog.username != null and loginLog.username != ''">
|
||||
AND ll.username LIKE CONCAT('%', #{loginLog.username}, '%')
|
||||
</if>
|
||||
<if test="loginLog.ipAddress != null and loginLog.ipAddress != ''">
|
||||
AND ll.ip_address = #{loginLog.ipAddress}
|
||||
</if>
|
||||
<if test="loginLog.status != null">
|
||||
AND ll.status = #{loginLog.status}
|
||||
</if>
|
||||
<if test="loginLog.browser != null and loginLog.browser != ''">
|
||||
AND ll.browser LIKE CONCAT('%', #{loginLog.browser}, '%')
|
||||
</if>
|
||||
<if test="loginLog.os != null and loginLog.os != ''">
|
||||
AND ll.os LIKE CONCAT('%', #{loginLog.os}, '%')
|
||||
</if>
|
||||
ORDER BY ll.login_time DESC
|
||||
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
||||
</select>
|
||||
|
||||
<!-- 统计登录日志数量(带权限过滤) -->
|
||||
<select id="countLoginLog" resultType="int">
|
||||
SELECT COUNT(DISTINCT ll.id)
|
||||
FROM tb_sys_login_log ll
|
||||
<include refid="Permission_Filter"/>
|
||||
<if test="loginLog.userID != null and loginLog.userID != ''">
|
||||
AND ll.user_id = #{loginLog.userID}
|
||||
</if>
|
||||
<if test="loginLog.username != null and loginLog.username != ''">
|
||||
AND ll.username LIKE CONCAT('%', #{loginLog.username}, '%')
|
||||
</if>
|
||||
<if test="loginLog.ipAddress != null and loginLog.ipAddress != ''">
|
||||
AND ll.ip_address = #{loginLog.ipAddress}
|
||||
</if>
|
||||
<if test="loginLog.status != null">
|
||||
AND ll.status = #{loginLog.status}
|
||||
</if>
|
||||
<if test="loginLog.browser != null and loginLog.browser != ''">
|
||||
AND ll.browser LIKE CONCAT('%', #{loginLog.browser}, '%')
|
||||
</if>
|
||||
<if test="loginLog.os != null and loginLog.os != ''">
|
||||
AND ll.os LIKE CONCAT('%', #{loginLog.os}, '%')
|
||||
</if>
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -31,37 +31,99 @@
|
||||
browser, os, status, error_message, execute_time, create_time
|
||||
</sql>
|
||||
|
||||
<!-- 通用条件 -->
|
||||
<sql id="Where_Clause">
|
||||
<where>
|
||||
<if test="userID != null and userID != ''">
|
||||
AND user_id = #{userID}
|
||||
</if>
|
||||
<if test="username != null and username != ''">
|
||||
AND username LIKE CONCAT('%', #{username}, '%')
|
||||
</if>
|
||||
<if test="module != null and module != ''">
|
||||
AND module = #{module}
|
||||
</if>
|
||||
<if test="operation != null and operation != ''">
|
||||
AND operation = #{operation}
|
||||
</if>
|
||||
<if test="status != null">
|
||||
AND status = #{status}
|
||||
</if>
|
||||
<if test="ipAddress != null and ipAddress != ''">
|
||||
AND ip_address = #{ipAddress}
|
||||
</if>
|
||||
</where>
|
||||
<!-- 权限过滤:通过用户的部门路径进行权限控制,superadmin可查看所有 -->
|
||||
<sql id="Permission_Filter">
|
||||
INNER JOIN tb_sys_user u ON ol.user_id = u.id AND u.deleted = 0
|
||||
INNER JOIN tb_sys_user_dept_role udr ON u.id = udr.user_id AND udr.deleted = 0
|
||||
INNER JOIN tb_sys_dept d ON udr.dept_id = d.dept_id AND d.deleted = 0
|
||||
WHERE (
|
||||
-- superadmin 可以查看所有操作日志
|
||||
EXISTS (
|
||||
SELECT 1 FROM (
|
||||
<foreach collection="userDeptRoles" item="currentRole" separator=" UNION ALL ">
|
||||
SELECT #{currentRole.deptID} AS dept_id, #{currentRole.roleID} AS role_id
|
||||
</foreach>
|
||||
) admin_check
|
||||
WHERE admin_check.dept_id = 'root_department'
|
||||
AND admin_check.role_id = 'superadmin'
|
||||
)
|
||||
-- 普通用户按部门路径过滤(本部门及子部门)
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM (
|
||||
<foreach collection="userDeptRoles" item="currentRole" separator=" UNION ALL ">
|
||||
SELECT #{currentRole.deptPath} AS user_dept_path
|
||||
</foreach>
|
||||
) user_roles
|
||||
WHERE d.dept_path LIKE CONCAT(user_roles.user_dept_path, '%')
|
||||
)
|
||||
)
|
||||
</sql>
|
||||
|
||||
<!-- selectSysOperationLogs -->
|
||||
<select id="selectSysOperationLogs" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM tb_sys_operation_log
|
||||
<!-- 通用条件 -->
|
||||
<sql id="Where_Clause">
|
||||
<if test="operationLog.userID != null and operationLog.userID != ''">
|
||||
AND ol.user_id = #{operationLog.userID}
|
||||
</if>
|
||||
<if test="operationLog.username != null and operationLog.username != ''">
|
||||
AND ol.username LIKE CONCAT('%', #{operationLog.username}, '%')
|
||||
</if>
|
||||
<if test="operationLog.module != null and operationLog.module != ''">
|
||||
AND ol.module LIKE CONCAT('%', #{operationLog.module}, '%')
|
||||
</if>
|
||||
<if test="operationLog.operation != null and operationLog.operation != ''">
|
||||
AND ol.operation = #{operationLog.operation}
|
||||
</if>
|
||||
<if test="operationLog.status != null">
|
||||
AND ol.status = #{operationLog.status}
|
||||
</if>
|
||||
<if test="operationLog.ipAddress != null and operationLog.ipAddress != ''">
|
||||
AND ol.ip_address = #{operationLog.ipAddress}
|
||||
</if>
|
||||
<if test="operationLog.requestUrl != null and operationLog.requestUrl != ''">
|
||||
AND ol.request_url LIKE CONCAT('%', #{operationLog.requestUrl}, '%')
|
||||
</if>
|
||||
</sql>
|
||||
|
||||
<!-- 插入操作日志 -->
|
||||
<insert id="insertOperationLog" parameterType="org.xyzh.common.dto.system.TbSysOperationLog">
|
||||
INSERT INTO tb_sys_operation_log (
|
||||
id, user_id, username, module, operation, method,
|
||||
request_url, request_method, request_params, response_data,
|
||||
ip_address, ip_source, browser, os, status,
|
||||
error_message, execute_time, create_time
|
||||
) VALUES (
|
||||
#{id}, #{userID}, #{username}, #{module}, #{operation}, #{method},
|
||||
#{requestUrl}, #{requestMethod}, #{requestParams}, #{responseData},
|
||||
#{ipAddress}, #{ipSource}, #{browser}, #{os}, #{status},
|
||||
#{errorMessage}, #{executeTime}, #{createTime, jdbcType=TIMESTAMP}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<!-- 查询操作日志列表(带权限过滤) -->
|
||||
<select id="selectOperationLogList" resultMap="BaseResultMap">
|
||||
SELECT DISTINCT ol.*
|
||||
FROM tb_sys_operation_log ol
|
||||
<include refid="Permission_Filter"/>
|
||||
<include refid="Where_Clause"/>
|
||||
ORDER BY ol.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 分页查询操作日志列表(带权限过滤) -->
|
||||
<select id="selectOperationLogPage" resultMap="BaseResultMap">
|
||||
SELECT DISTINCT ol.*
|
||||
FROM tb_sys_operation_log ol
|
||||
<include refid="Permission_Filter"/>
|
||||
<include refid="Where_Clause"/>
|
||||
ORDER BY ol.create_time DESC
|
||||
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
||||
</select>
|
||||
|
||||
<!-- 统计操作日志数量(带权限过滤) -->
|
||||
<select id="countOperationLog" resultType="int">
|
||||
SELECT COUNT(DISTINCT ol.id)
|
||||
FROM tb_sys_operation_log ol
|
||||
<include refid="Permission_Filter"/>
|
||||
<include refid="Where_Clause"/>
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -33,11 +33,20 @@
|
||||
<result column="deleted" property="deleted" jdbcType="INTEGER"/>
|
||||
</resultMap>
|
||||
<resultMap id="UserInfoTotalResultMap" type="org.xyzh.common.vo.UserVO">
|
||||
<id column="id" property="id" jdbcType="VARCHAR"/>
|
||||
<result column="user_id" property="userID" jdbcType="VARCHAR"/>
|
||||
<result column="username" property="username" jdbcType="VARCHAR"/>
|
||||
<result column="avatar" property="avatar" jdbcType="VARCHAR"/>
|
||||
<result column="gender" property="gender" jdbcType="INTEGER"/>
|
||||
<result column="phone" property="phone" jdbcType="VARCHAR"/>
|
||||
<result column="email" property="email" jdbcType="VARCHAR"/>
|
||||
<result column="wechat_id" property="wechatID" jdbcType="VARCHAR"/>
|
||||
<result column="status" property="status" jdbcType="INTEGER"/>
|
||||
<result column="family_name" property="familyName" jdbcType="VARCHAR"/>
|
||||
<result column="given_name" property="givenName" jdbcType="VARCHAR"/>
|
||||
<result column="full_name" property="fullName" jdbcType="VARCHAR"/>
|
||||
<result column="dept_id" property="deptID" jdbcType="VARCHAR"/>
|
||||
<result column="parent_id" property="parentID" jdbcType="VARCHAR"/>
|
||||
<result column="dept_name" property="deptName" jdbcType="VARCHAR"/>
|
||||
<result column="role_name" property="roleName" jdbcType="VARCHAR"/>
|
||||
<result column="level" property="level" jdbcType="INTEGER"/>
|
||||
@@ -108,6 +117,31 @@
|
||||
</where>
|
||||
</sql>
|
||||
|
||||
<!-- 权限过滤条件(基于dept_path的用户权限过滤,superadmin可查看所有) -->
|
||||
<sql id="Permission_Filter">
|
||||
INNER JOIN tb_sys_user_dept_role udr ON u.id = udr.user_id AND udr.deleted = 0
|
||||
INNER JOIN tb_sys_dept d ON udr.dept_id = d.dept_id AND d.deleted = 0
|
||||
WHERE (
|
||||
EXISTS (
|
||||
SELECT 1 FROM (
|
||||
<foreach collection="userDeptRoles" item="currentRole" separator=" UNION ALL ">
|
||||
SELECT #{currentRole.deptID} AS dept_id, #{currentRole.roleID} AS role_id
|
||||
</foreach>
|
||||
) admin_check
|
||||
WHERE admin_check.dept_id = 'root_department'
|
||||
AND admin_check.role_id = 'superadmin'
|
||||
)
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM (
|
||||
<foreach collection="userDeptRoles" item="currentRole" separator=" UNION ALL ">
|
||||
SELECT #{currentRole.deptPath} AS user_dept_path
|
||||
</foreach>
|
||||
) user_roles
|
||||
WHERE d.dept_path LIKE CONCAT(user_roles.user_dept_path, '%')
|
||||
)
|
||||
)
|
||||
</sql>
|
||||
|
||||
<!-- 根据用户名查询用户 -->
|
||||
<select id="selectByUsername" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
@@ -138,50 +172,223 @@
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<!-- 根据过滤条件查询用户 -->
|
||||
<!-- 根据过滤条件查询用户(包含权限过滤) -->
|
||||
<select id="selectByFilter" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM tb_sys_user
|
||||
<where>
|
||||
deleted = 0
|
||||
SELECT DISTINCT u.*
|
||||
FROM tb_sys_user u
|
||||
<include refid="Permission_Filter"/>
|
||||
AND u.deleted = 0
|
||||
<if test="filter.id != null and filter.id != ''">
|
||||
AND id = #{filter.id}
|
||||
AND u.id = #{filter.id}
|
||||
</if>
|
||||
<if test="filter.username != null and filter.username != ''">
|
||||
AND username = #{filter.username}
|
||||
AND u.username = #{filter.username}
|
||||
</if>
|
||||
<if test="filter.email != null and filter.email != ''">
|
||||
AND email = #{filter.email}
|
||||
AND u.email = #{filter.email}
|
||||
</if>
|
||||
<if test="filter.phone != null and filter.phone != ''">
|
||||
AND phone = #{filter.phone}
|
||||
AND u.phone = #{filter.phone}
|
||||
</if>
|
||||
<if test="filter.status != null">
|
||||
AND status = #{filter.status}
|
||||
AND u.status = #{filter.status}
|
||||
</if>
|
||||
<if test="filter.wechatID != null and filter.wechatID != ''">
|
||||
AND wechat_id = #{filter.wechatID}
|
||||
AND u.wechat_id = #{filter.wechatID}
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY u.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 查询用户列表 -->
|
||||
<!-- 根据过滤条件查询用户VO列表(包含userinfo和deptrole信息,包含权限过滤) -->
|
||||
<select id="selectUserVOByFilter" resultMap="UserInfoTotalResultMap">
|
||||
SELECT DISTINCT
|
||||
u.id,
|
||||
u.username,
|
||||
u.email,
|
||||
u.phone,
|
||||
u.wechat_id,
|
||||
u.status,
|
||||
ui.user_id,
|
||||
ui.avatar,
|
||||
ui.gender,
|
||||
ui.family_name,
|
||||
ui.given_name,
|
||||
ui.full_name,
|
||||
ui.level,
|
||||
ui.id_card,
|
||||
ui.address,
|
||||
udr.dept_id,
|
||||
d.parent_id,
|
||||
GROUP_CONCAT(DISTINCT d.name ORDER BY d.name SEPARATOR ', ') as dept_name,
|
||||
GROUP_CONCAT(DISTINCT r.name ORDER BY r.name SEPARATOR ', ') as role_name,
|
||||
u.create_time,
|
||||
u.update_time
|
||||
FROM tb_sys_user u
|
||||
LEFT JOIN tb_sys_user_info ui ON u.id = ui.user_id AND ui.deleted = 0
|
||||
INNER JOIN tb_sys_user_dept_role udr ON u.id = udr.user_id AND udr.deleted = 0
|
||||
INNER JOIN tb_sys_dept d ON udr.dept_id = d.dept_id AND d.deleted = 0
|
||||
LEFT JOIN tb_sys_role r ON udr.role_id = r.role_id AND r.deleted = 0
|
||||
WHERE (
|
||||
EXISTS (
|
||||
SELECT 1 FROM (
|
||||
<foreach collection="userDeptRoles" item="currentRole" separator=" UNION ALL ">
|
||||
SELECT #{currentRole.deptID} AS dept_id, #{currentRole.roleID} AS role_id
|
||||
</foreach>
|
||||
) admin_check
|
||||
WHERE admin_check.dept_id = 'root_department'
|
||||
AND admin_check.role_id = 'superadmin'
|
||||
)
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM (
|
||||
<foreach collection="userDeptRoles" item="currentRole" separator=" UNION ALL ">
|
||||
SELECT #{currentRole.deptPath} AS user_dept_path
|
||||
</foreach>
|
||||
) user_roles
|
||||
WHERE d.dept_path LIKE CONCAT(user_roles.user_dept_path, '%')
|
||||
)
|
||||
)
|
||||
AND u.deleted = 0
|
||||
<if test="filter.id != null and filter.id != ''">
|
||||
AND u.id = #{filter.id}
|
||||
</if>
|
||||
<if test="filter.username != null and filter.username != ''">
|
||||
AND u.username LIKE CONCAT('%', #{filter.username}, '%')
|
||||
</if>
|
||||
<if test="filter.email != null and filter.email != ''">
|
||||
AND u.email LIKE CONCAT('%', #{filter.email}, '%')
|
||||
</if>
|
||||
<if test="filter.phone != null and filter.phone != ''">
|
||||
AND u.phone = #{filter.phone}
|
||||
</if>
|
||||
<if test="filter.status != null">
|
||||
AND u.status = #{filter.status}
|
||||
</if>
|
||||
<if test="filter.wechatID != null and filter.wechatID != ''">
|
||||
AND u.wechat_id = #{filter.wechatID}
|
||||
</if>
|
||||
GROUP BY u.id, u.username, u.email, u.phone, u.wechat_id, u.status,
|
||||
ui.user_id, ui.avatar, ui.gender, ui.family_name, ui.given_name,
|
||||
ui.full_name, ui.level, ui.id_card, ui.address, udr.dept_id, d.parent_id, u.create_time, u.update_time
|
||||
ORDER BY u.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 查询用户列表(包含权限过滤) -->
|
||||
<select id="selectUserList" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM tb_sys_user
|
||||
<include refid="Where_Clause"/>
|
||||
ORDER BY create_time DESC
|
||||
SELECT DISTINCT u.*
|
||||
FROM tb_sys_user u
|
||||
<include refid="Permission_Filter"/>
|
||||
AND u.deleted = 0
|
||||
<if test="username != null and username != ''">
|
||||
AND u.username LIKE CONCAT('%', #{username}, '%')
|
||||
</if>
|
||||
<if test="email != null and email != ''">
|
||||
AND u.email LIKE CONCAT('%', #{email}, '%')
|
||||
</if>
|
||||
<if test="status != null and status != ''">
|
||||
AND u.status = #{status}
|
||||
</if>
|
||||
ORDER BY u.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 查询用户列表(分页) -->
|
||||
<!-- 查询用户列表(分页,包含权限过滤) -->
|
||||
<select id="selectUserPage" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM tb_sys_user
|
||||
<include refid="Filter_Clause"/>
|
||||
ORDER BY create_time DESC
|
||||
SELECT DISTINCT u.*
|
||||
FROM tb_sys_user u
|
||||
<include refid="Permission_Filter"/>
|
||||
AND u.deleted = 0
|
||||
<if test="filter.id != null and filter.id != ''">
|
||||
AND u.id = #{filter.id}
|
||||
</if>
|
||||
<if test="filter.username != null and filter.username != ''">
|
||||
AND u.username = #{filter.username}
|
||||
</if>
|
||||
<if test="filter.email != null and filter.email != ''">
|
||||
AND u.email = #{filter.email}
|
||||
</if>
|
||||
<if test="filter.phone != null and filter.phone != ''">
|
||||
AND u.phone = #{filter.phone}
|
||||
</if>
|
||||
<if test="filter.status != null">
|
||||
AND u.status = #{filter.status}
|
||||
</if>
|
||||
<if test="filter.wechatID != null and filter.wechatID != ''">
|
||||
AND u.wechat_id = #{filter.wechatID}
|
||||
</if>
|
||||
ORDER BY u.create_time DESC
|
||||
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
||||
</select>
|
||||
|
||||
<!-- 查询用户VO列表(分页,包含userinfo和deptrole信息,包含权限过滤) -->
|
||||
<select id="selectUserVOPage" resultMap="UserInfoTotalResultMap">
|
||||
SELECT DISTINCT
|
||||
u.id,
|
||||
u.username,
|
||||
u.email,
|
||||
u.phone,
|
||||
u.wechat_id,
|
||||
u.status,
|
||||
ui.user_id,
|
||||
ui.avatar,
|
||||
ui.gender,
|
||||
ui.family_name,
|
||||
ui.given_name,
|
||||
ui.full_name,
|
||||
ui.level,
|
||||
ui.id_card,
|
||||
ui.address,
|
||||
udr.dept_id,
|
||||
d.parent_id,
|
||||
GROUP_CONCAT(DISTINCT d.name ORDER BY d.name SEPARATOR ', ') as dept_name,
|
||||
GROUP_CONCAT(DISTINCT r.name ORDER BY r.name SEPARATOR ', ') as role_name,
|
||||
u.create_time,
|
||||
u.update_time
|
||||
FROM tb_sys_user u
|
||||
LEFT JOIN tb_sys_user_info ui ON u.id = ui.user_id AND ui.deleted = 0
|
||||
INNER JOIN tb_sys_user_dept_role udr ON u.id = udr.user_id AND udr.deleted = 0
|
||||
INNER JOIN tb_sys_dept d ON udr.dept_id = d.dept_id AND d.deleted = 0
|
||||
LEFT JOIN tb_sys_role r ON udr.role_id = r.role_id AND r.deleted = 0
|
||||
WHERE (
|
||||
EXISTS (
|
||||
SELECT 1 FROM (
|
||||
<foreach collection="userDeptRoles" item="currentRole" separator=" UNION ALL ">
|
||||
SELECT #{currentRole.deptID} AS dept_id, #{currentRole.roleID} AS role_id
|
||||
</foreach>
|
||||
) admin_check
|
||||
WHERE admin_check.dept_id = 'root_department'
|
||||
AND admin_check.role_id = 'superadmin'
|
||||
)
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM (
|
||||
<foreach collection="userDeptRoles" item="currentRole" separator=" UNION ALL ">
|
||||
SELECT #{currentRole.deptPath} AS user_dept_path
|
||||
</foreach>
|
||||
) user_roles
|
||||
WHERE d.dept_path LIKE CONCAT(user_roles.user_dept_path, '%')
|
||||
)
|
||||
)
|
||||
AND u.deleted = 0
|
||||
<if test="filter.id != null and filter.id != ''">
|
||||
AND u.id = #{filter.id}
|
||||
</if>
|
||||
<if test="filter.username != null and filter.username != ''">
|
||||
AND u.username LIKE CONCAT('%', #{filter.username}, '%')
|
||||
</if>
|
||||
<if test="filter.email != null and filter.email != ''">
|
||||
AND u.email LIKE CONCAT('%', #{filter.email}, '%')
|
||||
</if>
|
||||
<if test="filter.phone != null and filter.phone != ''">
|
||||
AND u.phone = #{filter.phone}
|
||||
</if>
|
||||
<if test="filter.status != null">
|
||||
AND u.status = #{filter.status}
|
||||
</if>
|
||||
<if test="filter.wechatID != null and filter.wechatID != ''">
|
||||
AND u.wechat_id = #{filter.wechatID}
|
||||
</if>
|
||||
GROUP BY u.id, u.username, u.email, u.phone, u.wechat_id, u.status,
|
||||
ui.user_id, ui.avatar, ui.gender, ui.family_name, ui.given_name,
|
||||
ui.full_name, ui.level, ui.id_card, ui.address, udr.dept_id, d.parent_id, u.create_time, u.update_time
|
||||
ORDER BY u.create_time DESC
|
||||
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
||||
</select>
|
||||
|
||||
@@ -352,4 +559,26 @@
|
||||
WHERE tsui.user_id = #{userId}
|
||||
AND tsui.deleted = 0
|
||||
</select>
|
||||
|
||||
<!-- countDeptUser - 递归统计部门及其子部门的用户数量 -->
|
||||
<select id="countDeptUser" resultType="int">
|
||||
SELECT COUNT(DISTINCT tudr.user_id)
|
||||
FROM tb_sys_user_dept_role tudr
|
||||
INNER JOIN tb_sys_dept d ON tudr.dept_id = d.dept_id AND d.deleted = 0
|
||||
INNER JOIN tb_sys_user u ON tudr.user_id = u.id AND u.deleted = 0
|
||||
WHERE tudr.deleted = 0
|
||||
AND d.dept_path LIKE CONCAT(
|
||||
(SELECT dept_path FROM tb_sys_dept WHERE dept_id = #{deptId} AND deleted = 0),
|
||||
'%'
|
||||
)
|
||||
</select>
|
||||
|
||||
<!-- selectLoginUser -->
|
||||
|
||||
<select id="selectLoginUser">
|
||||
SELECT DISTINCT u.*
|
||||
FROM tb_sys_user u
|
||||
<include refid="Filter_Clause"/>
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -42,6 +42,11 @@ export const learningTaskApi = {
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async getTaskUsers(taskID: string): Promise<ResultDomain<TaskItemVO>> {
|
||||
const response = await api.get<TaskItemVO>(`${this.learningTaskPrefix}/${taskID}/users`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取任务分页列表
|
||||
* @param pageParam 分页参数
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import { api } from '@/apis/index';
|
||||
import type { SysDept, SysRole, DeptRoleVO, SysDeptRole, ResultDomain } from '@/types';
|
||||
import type { SysDept, SysRole, SysDeptRole, ResultDomain, UserDeptRoleVO } from '@/types';
|
||||
|
||||
/**
|
||||
* 部门API服务
|
||||
@@ -89,8 +89,8 @@ export const deptApi = {
|
||||
* @author yslg
|
||||
* @ since 2025-10-06
|
||||
*/
|
||||
async getDeptByRole(dept: SysDept): Promise<ResultDomain<SysRole>> {
|
||||
const response = await api.post<SysRole>('/depts/role', dept);
|
||||
async getDeptByRole(dept: SysDept): Promise<ResultDomain<UserDeptRoleVO>> {
|
||||
const response = await api.post<UserDeptRoleVO>('/depts/role', dept);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
@@ -113,7 +113,7 @@ export const deptApi = {
|
||||
* @author yslg
|
||||
* @ since 2025-10-06
|
||||
*/
|
||||
async bindDeptRole(deptRole: DeptRoleVO): Promise<ResultDomain<SysDeptRole>> {
|
||||
async bindDeptRole(deptRole: UserDeptRoleVO): Promise<ResultDomain<SysDeptRole>> {
|
||||
const response = await api.post<SysDeptRole>('/depts/bind/role', deptRole);
|
||||
return response.data;
|
||||
},
|
||||
@@ -125,7 +125,7 @@ export const deptApi = {
|
||||
* @author yslg
|
||||
* @ since 2025-10-06
|
||||
*/
|
||||
async unbindDeptRole(deptRole: DeptRoleVO): Promise<ResultDomain<SysDeptRole>> {
|
||||
async unbindDeptRole(deptRole: UserDeptRoleVO): Promise<ResultDomain<SysDeptRole>> {
|
||||
const response = await api.post<SysDeptRole>('/depts/unbind/role', deptRole);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
@@ -13,4 +13,5 @@ export { permissionApi } from './permission';
|
||||
export { authApi } from './auth';
|
||||
export { fileApi } from './file';
|
||||
export { moduleApi } from './module';
|
||||
export { logApi } from './log';
|
||||
|
||||
|
||||
33
schoolNewsWeb/src/apis/system/log.ts
Normal file
33
schoolNewsWeb/src/apis/system/log.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* @description 系统日志API
|
||||
* @author yslg
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
|
||||
import { api } from '../index';
|
||||
import type { LoginLog, OperationLog } from '@/types/log';
|
||||
import type { PageParam, ResultDomain } from '@/types';
|
||||
|
||||
/**
|
||||
* 日志API接口
|
||||
*/
|
||||
export const logApi = {
|
||||
baseUrl: '/sys/log',
|
||||
|
||||
async getLoginLogPage(pageParam: PageParam, filter: LoginLog): Promise<ResultDomain<LoginLog>> {
|
||||
const response = await api.post<LoginLog>(`${this.baseUrl}/login/page`, {
|
||||
pageParam,
|
||||
filter,
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async getOperationLogPage(pageParam: PageParam, filter: OperationLog): Promise<ResultDomain<OperationLog>> {
|
||||
const response = await api.post<OperationLog>(`${this.baseUrl}/operation/page`, {
|
||||
pageParam,
|
||||
filter,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -43,8 +43,8 @@ export const userApi = {
|
||||
* @param filter 过滤条件
|
||||
* @returns Promise<ResultDomain<SysUser>>
|
||||
*/
|
||||
async getUserList(filter: SysUser): Promise<ResultDomain<SysUser>> {
|
||||
const response = await api.post<SysUser>('/users/list', filter);
|
||||
async getUserList(filter: SysUser): Promise<ResultDomain<UserVO>> {
|
||||
const response = await api.post<UserVO>('/users/list', filter);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
|
||||
@@ -625,12 +625,6 @@ function toggleAvailable(itemId: string) {
|
||||
// 树形模式下的级联选择
|
||||
const node = findNodeInTree(itemId, treeData.value);
|
||||
if (node) {
|
||||
// 如果只允许选择叶子节点,检查是否为叶子节点
|
||||
if (props.onlyLeafSelectable && !isLeafNode(node)) {
|
||||
// 非叶子节点,不允许选择
|
||||
return;
|
||||
}
|
||||
|
||||
const childrenIds = getAllChildrenIds(node);
|
||||
|
||||
if (index > -1) {
|
||||
@@ -701,6 +695,11 @@ function moveSelectedToTarget() {
|
||||
itemsToMove = flatItems.filter(item =>
|
||||
selectedAvailable.value.includes(getNodeId(item))
|
||||
);
|
||||
|
||||
// 如果只允许选择叶子节点,过滤掉非叶子节点
|
||||
if (props.onlyLeafSelectable) {
|
||||
itemsToMove = itemsToMove.filter(item => isLeafNode(item));
|
||||
}
|
||||
} else {
|
||||
// 列表模式:从可选列表中查找
|
||||
itemsToMove = availableList.value.filter(item =>
|
||||
@@ -712,8 +711,10 @@ function moveSelectedToTarget() {
|
||||
|
||||
if (props.useTree && treeData.value.length > 0) {
|
||||
// 树形模式:需要重新构建树(移除已选项)
|
||||
// 只移除叶子节点
|
||||
const idsToRemove = new Set(itemsToMove.map(item => getNodeId(item)));
|
||||
availableList.value = availableList.value.filter(item =>
|
||||
!selectedAvailable.value.includes(getItemId(item))
|
||||
!idsToRemove.has(getItemId(item))
|
||||
);
|
||||
// 重新转换为树形结构
|
||||
if (props.treeTransform) {
|
||||
@@ -782,10 +783,27 @@ function moveToTarget(itemId: string) {
|
||||
function moveAllToTarget() {
|
||||
if (props.useTree && treeData.value.length > 0) {
|
||||
// 树形模式:扁平化所有节点
|
||||
const flatItems = flattenTree(treeData.value);
|
||||
let flatItems = flattenTree(treeData.value);
|
||||
|
||||
// 如果只允许选择叶子节点,过滤掉非叶子节点
|
||||
if (props.onlyLeafSelectable) {
|
||||
flatItems = flatItems.filter(item => isLeafNode(item));
|
||||
}
|
||||
|
||||
targetList.value.push(...flatItems);
|
||||
availableList.value = [];
|
||||
|
||||
// 从availableList中移除已移动的项
|
||||
const idsToRemove = new Set(flatItems.map(item => getNodeId(item)));
|
||||
availableList.value = availableList.value.filter(item =>
|
||||
!idsToRemove.has(getItemId(item))
|
||||
);
|
||||
|
||||
// 重新构建树(如果还有非叶子节点)
|
||||
if (props.onlyLeafSelectable && props.treeTransform) {
|
||||
treeData.value = props.treeTransform(availableList.value);
|
||||
} else {
|
||||
treeData.value = [];
|
||||
}
|
||||
} else {
|
||||
// 列表模式
|
||||
targetList.value.push(...availableList.value);
|
||||
|
||||
@@ -3,10 +3,7 @@
|
||||
<div
|
||||
class="tree-node-content"
|
||||
:style="{ paddingLeft: `${level * 20}px` }"
|
||||
:class="{
|
||||
selected: selectedIds.includes(nodeId),
|
||||
disabled: isCheckboxDisabled
|
||||
}"
|
||||
:class="{ selected: selectedIds.includes(nodeId) }"
|
||||
@click.stop="handleClick"
|
||||
@dblclick.stop="handleDblClick"
|
||||
>
|
||||
@@ -25,7 +22,6 @@
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="selectedIds.includes(nodeId)"
|
||||
:disabled="isCheckboxDisabled"
|
||||
@click.stop="handleCheckboxClick"
|
||||
/>
|
||||
|
||||
@@ -107,36 +103,28 @@ const hasChildren = computed(() => children.value && children.value.length > 0);
|
||||
|
||||
const expanded = computed(() => props.expandedKeys.has(nodeId.value));
|
||||
|
||||
// 判断复选框是否应该被禁用
|
||||
const isCheckboxDisabled = computed(() => {
|
||||
// 如果只允许选择叶子节点,且当前节点有子节点,则禁用复选框
|
||||
return props.onlyLeafSelectable && hasChildren.value;
|
||||
});
|
||||
|
||||
function getChildId(child: any): string {
|
||||
const idProp = props.treeProps?.id || 'id';
|
||||
return String(child[idProp] || '');
|
||||
}
|
||||
|
||||
function handleClick() {
|
||||
// 点击节点主体时切换选中状态(如果复选框未禁用)
|
||||
if (!isCheckboxDisabled.value) {
|
||||
// 点击节点主体时切换选中状态
|
||||
emit('toggle-select', nodeId.value);
|
||||
}
|
||||
}
|
||||
|
||||
function handleCheckboxClick() {
|
||||
// 复选框点击事件(如果未禁用)
|
||||
if (!isCheckboxDisabled.value) {
|
||||
// 复选框点击事件
|
||||
emit('toggle-select', nodeId.value);
|
||||
}
|
||||
}
|
||||
|
||||
function handleDblClick() {
|
||||
// 双击时触发(如果复选框未禁用)
|
||||
if (!isCheckboxDisabled.value) {
|
||||
emit('dblclick', nodeId.value);
|
||||
// 双击时触发
|
||||
// 如果只允许选择叶子节点,且当前是非叶子节点,则不触发双击
|
||||
if (props.onlyLeafSelectable && hasChildren.value) {
|
||||
return;
|
||||
}
|
||||
emit('dblclick', nodeId.value);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -191,11 +179,6 @@ function handleDblClick() {
|
||||
input[type="checkbox"] {
|
||||
margin: 0 8px;
|
||||
cursor: pointer;
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
.node-label {
|
||||
@@ -203,14 +186,6 @@ function handleDblClick() {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
|
||||
.node-label {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-children {
|
||||
|
||||
@@ -48,6 +48,9 @@ export * from './usercenter';
|
||||
// 定时任务相关
|
||||
export * from './crontab';
|
||||
|
||||
// 日志相关
|
||||
export * from './log';
|
||||
|
||||
// 枚举类型
|
||||
export * from './enums';
|
||||
export * from './enums/achievement-enums';
|
||||
|
||||
84
schoolNewsWeb/src/types/log/index.ts
Normal file
84
schoolNewsWeb/src/types/log/index.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* @description 系统日志类型定义
|
||||
* @author yslg
|
||||
* @since 2025-10-30
|
||||
*/
|
||||
|
||||
import { BaseDTO } from '../base';
|
||||
|
||||
/**
|
||||
* 登录日志
|
||||
*/
|
||||
export interface LoginLog extends BaseDTO {
|
||||
/** 用户名 */
|
||||
username?: string;
|
||||
/** 用户ID */
|
||||
userId?: string;
|
||||
/** 登录IP */
|
||||
ipAddress?: string;
|
||||
/** 登录地点 */
|
||||
location?: string;
|
||||
/** 浏览器 */
|
||||
browser?: string;
|
||||
/** 操作系统 */
|
||||
os?: string;
|
||||
/** 登录状态: success-成功, failed-失败 */
|
||||
status?: number;
|
||||
/** 登录信息 */
|
||||
message?: string;
|
||||
/** 登录时间 */
|
||||
loginTime?: string;
|
||||
/** 部门ID */
|
||||
deptId?: string;
|
||||
/** 部门名称 */
|
||||
deptName?: string;
|
||||
/** 开始时间 (查询条件) */
|
||||
startTime?: string;
|
||||
/** 结束时间 (查询条件) */
|
||||
endTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作日志
|
||||
*/
|
||||
export interface OperationLog extends BaseDTO {
|
||||
/** 操作人用户名 */
|
||||
username?: string;
|
||||
/** 操作人ID */
|
||||
userId?: string;
|
||||
/** 操作模块 */
|
||||
module?: string;
|
||||
/** 操作类型: create-新增, update-修改, delete-删除, read-查询 */
|
||||
operation?: 'create' | 'update' | 'delete' | 'read' | string;
|
||||
/** 操作描述 */
|
||||
description?: string;
|
||||
/** 请求方法 */
|
||||
method?: string;
|
||||
/** 请求路径 */
|
||||
requestUrl?: string;
|
||||
/** 请求参数 */
|
||||
requestParams?: string;
|
||||
/** 响应结果 */
|
||||
responseData?: string;
|
||||
/** IP地址 */
|
||||
ipAddress?: string;
|
||||
/** 操作地点 */
|
||||
location?: string;
|
||||
/** 操作耗时(毫秒) */
|
||||
duration?: number;
|
||||
/** 操作状态: success-成功, failed-失败 */
|
||||
status?: 'success' | 'failed' | string;
|
||||
/** 错误信息 */
|
||||
errorMessage?: string;
|
||||
/** 操作时间 */
|
||||
operationTime?: string;
|
||||
/** 部门ID */
|
||||
deptId?: string;
|
||||
/** 部门名称 */
|
||||
deptName?: string;
|
||||
/** 开始时间 (查询条件) */
|
||||
startTime?: string;
|
||||
/** 结束时间 (查询条件) */
|
||||
endTime?: string;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,15 @@ export interface SysPermission extends BaseDTO {
|
||||
/** 权限ID */
|
||||
permissionID?: string;
|
||||
moduleID?: string;
|
||||
moduleName?: string;
|
||||
moduleCode?: string;
|
||||
moduleDescription?: string;
|
||||
menuID?: string;
|
||||
menuName?: string;
|
||||
menuUrl?: string;
|
||||
roleID?: string;
|
||||
roleName?: string;
|
||||
roleDescription?: string;
|
||||
/** 权限名称 */
|
||||
name?: string;
|
||||
/** 权限描述 */
|
||||
|
||||
@@ -317,6 +317,9 @@ export interface TaskItemVO extends LearningTask {
|
||||
userID?: string;
|
||||
/** 用户名 */
|
||||
username?: string;
|
||||
deptID?: string;
|
||||
deptName?: string;
|
||||
parentDeptID?: string;
|
||||
/** 是否必修 */
|
||||
required?: boolean;
|
||||
/** 排序号 */
|
||||
|
||||
@@ -61,16 +61,19 @@ export interface UserVO extends BaseDTO {
|
||||
wechatID?: string;
|
||||
/** 用户状态 0-正常 1-禁用 */
|
||||
status?: number;
|
||||
/** 真实姓名 */
|
||||
realName?: string;
|
||||
familyName?: string;
|
||||
/** 名 */
|
||||
givenName?: string;
|
||||
/** 昵称 */
|
||||
nickname?: string;
|
||||
fullName?: string;
|
||||
/** 头像URL */
|
||||
avatar?: string;
|
||||
/** 性别 0-未知 1-男 2-女 */
|
||||
gender?: number;
|
||||
/** 学习等级 */
|
||||
level?: number;
|
||||
deptID?: string;
|
||||
parentDeptID?: string;
|
||||
/** 部门名称 */
|
||||
deptName?: string;
|
||||
/** 角色名称 */
|
||||
|
||||
@@ -1,110 +1,312 @@
|
||||
<template>
|
||||
<AdminLayout title="登录日志" subtitle="登录日志管理">
|
||||
<div class="login-logs">
|
||||
<div class="filter-bar">
|
||||
<el-input
|
||||
<!-- 搜索栏 -->
|
||||
<div class="search-bar">
|
||||
<div class="search-group">
|
||||
<label class="search-label">用户名</label>
|
||||
<input
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索用户名..."
|
||||
style="width: 200px"
|
||||
clearable
|
||||
type="text"
|
||||
class="search-input"
|
||||
placeholder="输入用户名搜索"
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
<el-select v-model="loginStatus" placeholder="登录状态" style="width: 150px" clearable>
|
||||
<el-option label="成功" value="success" />
|
||||
<el-option label="失败" value="failed" />
|
||||
</el-select>
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
/>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="handleExport">导出</el-button>
|
||||
<el-button type="danger" @click="handleClear">清空日志</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="logs" style="width: 100%">
|
||||
<el-table-column prop="username" label="用户名" width="120" />
|
||||
<el-table-column prop="ipAddress" label="IP地址" width="140" />
|
||||
<el-table-column prop="location" label="登录地点" width="150" />
|
||||
<el-table-column prop="browser" label="浏览器" width="120" />
|
||||
<el-table-column prop="os" label="操作系统" width="120" />
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 'success' ? 'success' : 'danger'">
|
||||
{{ row.status === 'success' ? '成功' : '失败' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="message" label="信息" min-width="150" />
|
||||
<el-table-column prop="loginTime" label="登录时间" width="180" />
|
||||
</el-table>
|
||||
<div class="search-group">
|
||||
<label class="search-label">登录状态</label>
|
||||
<select v-model="loginStatus" class="search-select">
|
||||
<option value="">全部</option>
|
||||
<option value="1">成功</option>
|
||||
<option value="0">失败</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
<div class="search-group">
|
||||
<label class="search-label">开始日期</label>
|
||||
<input
|
||||
v-model="startDate"
|
||||
type="date"
|
||||
class="search-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="search-group">
|
||||
<label class="search-label">结束日期</label>
|
||||
<input
|
||||
v-model="endDate"
|
||||
type="date"
|
||||
class="search-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="search-actions">
|
||||
<button class="btn-search" @click="handleSearch">搜索</button>
|
||||
<button class="btn-reset" @click="handleReset">重置</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<!-- <div class="toolbar">
|
||||
<button class="btn-danger" @click="handleClear">清空日志</button>
|
||||
</div> -->
|
||||
|
||||
<!-- 日志表格 -->
|
||||
<div class="log-table-wrapper">
|
||||
<table class="log-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="120">用户名</th>
|
||||
<th width="140">IP地址</th>
|
||||
<th width="150">登录地点</th>
|
||||
<th width="120">浏览器</th>
|
||||
<th width="120">操作系统</th>
|
||||
<th width="100">状态</th>
|
||||
<th>信息</th>
|
||||
<th width="180">登录时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody v-if="loading">
|
||||
<tr>
|
||||
<td colspan="8" class="loading-cell">
|
||||
<div class="loading-spinner"></div>
|
||||
<span>加载中...</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody v-else-if="logs.length === 0">
|
||||
<tr>
|
||||
<td colspan="8" class="empty-cell">
|
||||
<div class="empty-icon">📋</div>
|
||||
<p>暂无登录日志</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody v-else>
|
||||
<tr v-for="log in logs" :key="log.id" class="table-row">
|
||||
<td>{{ log.username || '-' }}</td>
|
||||
<td>{{ log.ipAddress || '-' }}</td>
|
||||
<td>{{ log.location || '-' }}</td>
|
||||
<td>{{ log.browser || '-' }}</td>
|
||||
<td>{{ log.os || '-' }}</td>
|
||||
<td>
|
||||
<span class="status-tag" :class="log.status === 1 ? 'status-success' : 'status-failed'">
|
||||
{{ log.status === 1 ? '成功' : '失败' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="log-message">
|
||||
<div class="message-text">{{ log.message || '-' }}</div>
|
||||
</td>
|
||||
<td>{{ log.loginTime || '-' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div v-if="!loading && logs.length > 0" class="pagination">
|
||||
<div class="pagination-info">
|
||||
共 {{ total }} 条数据,每页 {{ pageSize }} 条
|
||||
</div>
|
||||
|
||||
<div class="pagination-controls">
|
||||
<button
|
||||
class="page-btn"
|
||||
:disabled="currentPage === 1"
|
||||
@click="handlePageChange(1)"
|
||||
>
|
||||
首页
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="page-btn"
|
||||
:disabled="currentPage === 1"
|
||||
@click="handlePageChange(currentPage - 1)"
|
||||
>
|
||||
上一页
|
||||
</button>
|
||||
|
||||
<div class="page-numbers">
|
||||
<button
|
||||
v-for="page in displayPages"
|
||||
:key="page"
|
||||
class="page-number"
|
||||
:class="{ active: page === currentPage }"
|
||||
@click="handlePageChange(page)"
|
||||
:disabled="page === -1"
|
||||
>
|
||||
{{ page === -1 ? '...' : page }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="page-btn"
|
||||
:disabled="currentPage === totalPages"
|
||||
@click="handlePageChange(currentPage + 1)"
|
||||
>
|
||||
下一页
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="page-btn"
|
||||
:disabled="currentPage === totalPages"
|
||||
@click="handlePageChange(totalPages)"
|
||||
>
|
||||
末页
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="pagination-jump">
|
||||
<span>跳转到</span>
|
||||
<input
|
||||
v-model.number="jumpPage"
|
||||
type="number"
|
||||
class="jump-input"
|
||||
@keyup.enter="handleJumpPage"
|
||||
/>
|
||||
<span>页</span>
|
||||
<button class="jump-btn" @click="handleJumpPage">跳转</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ElInput, ElSelect, ElOption, ElDatePicker, ElButton, ElTable, ElTableColumn, ElTag, ElPagination, ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { AdminLayout } from '@/views/admin';
|
||||
import { logApi } from '@/apis/system';
|
||||
import type { LoginLog } from '@/types/log';
|
||||
|
||||
defineOptions({
|
||||
name: 'LoginLogsView'
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
const searchKeyword = ref('');
|
||||
const loginStatus = ref('');
|
||||
const dateRange = ref<[Date, Date] | null>(null);
|
||||
const startDate = ref('');
|
||||
const endDate = ref('');
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(10);
|
||||
const pageSize = ref(20);
|
||||
const total = ref(0);
|
||||
const logs = ref<any[]>([]);
|
||||
const logs = ref<LoginLog[]>([]);
|
||||
const jumpPage = ref<number>();
|
||||
|
||||
// 计算总页数
|
||||
const totalPages = computed(() => Math.ceil(total.value / pageSize.value) || 1);
|
||||
|
||||
// 计算显示的页码
|
||||
const displayPages = computed(() => {
|
||||
const pages: number[] = [];
|
||||
const maxDisplay = 7;
|
||||
|
||||
if (totalPages.value <= maxDisplay) {
|
||||
for (let i = 1; i <= totalPages.value; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
} else {
|
||||
if (currentPage.value <= 4) {
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
pages.push(-1);
|
||||
pages.push(totalPages.value);
|
||||
} else if (currentPage.value >= totalPages.value - 3) {
|
||||
pages.push(1);
|
||||
pages.push(-1);
|
||||
for (let i = totalPages.value - 4; i <= totalPages.value; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
} else {
|
||||
pages.push(1);
|
||||
pages.push(-1);
|
||||
for (let i = currentPage.value - 1; i <= currentPage.value + 1; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
pages.push(-1);
|
||||
pages.push(totalPages.value);
|
||||
}
|
||||
}
|
||||
|
||||
return pages;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
loadLogs();
|
||||
});
|
||||
|
||||
function loadLogs() {
|
||||
// TODO: 加载登录日志
|
||||
/**
|
||||
* 加载登录日志
|
||||
*/
|
||||
async function loadLogs() {
|
||||
try {
|
||||
loading.value = true;
|
||||
|
||||
// 构建查询条件
|
||||
const query: LoginLog = {
|
||||
username: searchKeyword.value || undefined,
|
||||
status: loginStatus.value ? Number(loginStatus.value) : undefined,
|
||||
startTime: startDate.value || undefined,
|
||||
endTime: endDate.value || undefined,
|
||||
};
|
||||
|
||||
const result = await logApi.getLoginLogPage(
|
||||
{pageNumber: currentPage.value, pageSize: pageSize.value},
|
||||
query
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
logs.value = result.pageDomain?.dataList || [];
|
||||
total.value = result.pageDomain?.pageParam.totalElements || 0;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载登录日志失败:', error);
|
||||
alert('加载登录日志失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询
|
||||
*/
|
||||
function handleSearch() {
|
||||
currentPage.value = 1;
|
||||
loadLogs();
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
// TODO: 导出日志
|
||||
ElMessage.info('导出功能开发中');
|
||||
}
|
||||
|
||||
async function handleClear() {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要清空所有登录日志吗?此操作不可恢复!', '警告', {
|
||||
type: 'warning'
|
||||
});
|
||||
// TODO: 清空日志
|
||||
ElMessage.success('日志已清空');
|
||||
} catch {
|
||||
// 取消操作
|
||||
}
|
||||
}
|
||||
|
||||
function handleSizeChange(val: number) {
|
||||
pageSize.value = val;
|
||||
/**
|
||||
* 重置查询条件
|
||||
*/
|
||||
function handleReset() {
|
||||
searchKeyword.value = '';
|
||||
loginStatus.value = '';
|
||||
startDate.value = '';
|
||||
endDate.value = '';
|
||||
currentPage.value = 1;
|
||||
loadLogs();
|
||||
}
|
||||
|
||||
function handleCurrentChange(val: number) {
|
||||
currentPage.value = val;
|
||||
/**
|
||||
* 页码改变
|
||||
*/
|
||||
function handlePageChange(page: number) {
|
||||
if (page < 1 || page > totalPages.value || page === -1) return;
|
||||
currentPage.value = page;
|
||||
loadLogs();
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转页面
|
||||
*/
|
||||
function handleJumpPage() {
|
||||
if (!jumpPage.value || jumpPage.value < 1 || jumpPage.value > totalPages.value) {
|
||||
alert('请输入有效的页码');
|
||||
return;
|
||||
}
|
||||
currentPage.value = jumpPage.value;
|
||||
loadLogs();
|
||||
}
|
||||
</script>
|
||||
@@ -114,15 +316,310 @@ function handleCurrentChange(val: number) {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
.search-bar {
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.search-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.search-label {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.search-input,
|
||||
.search-select {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
transition: border-color 0.3s;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.search-select {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.search-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.btn-search,
|
||||
.btn-reset {
|
||||
padding: 8px 20px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-search {
|
||||
background: #409eff;
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
background: #66b1ff;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-reset {
|
||||
background: #fff;
|
||||
color: #606266;
|
||||
border: 1px solid #dcdfe6;
|
||||
|
||||
&:hover {
|
||||
color: #409eff;
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
padding: 8px 20px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
border: none;
|
||||
background: #f56c6c;
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
background: #f78989;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
padding: 8px 20px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
border: none;
|
||||
background: #409eff;
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
background: #66b1ff;
|
||||
}
|
||||
}
|
||||
|
||||
.log-table-wrapper {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.log-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
th {
|
||||
background: #f5f7fa;
|
||||
color: #606266;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
padding: 12px 16px;
|
||||
text-align: left;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
transition: background-color 0.3s;
|
||||
|
||||
&:hover {
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
&:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.log-message {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.message-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
|
||||
&.status-success {
|
||||
background: #e1f3d8;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
&.status-failed {
|
||||
background: #fde2e2;
|
||||
color: #f56c6c;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-cell,
|
||||
.empty-cell {
|
||||
text-align: center;
|
||||
padding: 60px 20px !important;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #409eff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.pagination-controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.el-table {
|
||||
margin-bottom: 20px;
|
||||
.page-btn,
|
||||
.page-number {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #dcdfe6;
|
||||
background: #fff;
|
||||
color: #606266;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
color: #409eff;
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #409eff;
|
||||
color: #fff;
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.page-numbers {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.pagination-jump {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
|
||||
.jump-input {
|
||||
width: 60px;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.jump-btn {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #dcdfe6;
|
||||
background: #fff;
|
||||
color: #606266;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
color: #409eff;
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,46 +1,968 @@
|
||||
<template>
|
||||
<AdminLayout title="系统日志" subtitle="系统日志管理">
|
||||
<div class="system-logs">
|
||||
<h1 class="page-title">系统日志</h1>
|
||||
<AdminLayout title="操作日志" subtitle="操作日志管理">
|
||||
<div class="operation-logs">
|
||||
<!-- 搜索栏 -->
|
||||
<div class="search-bar">
|
||||
<div class="search-group">
|
||||
<label class="search-label">用户名</label>
|
||||
<input
|
||||
v-model="searchKeyword"
|
||||
type="text"
|
||||
class="search-input"
|
||||
placeholder="输入用户名搜索"
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="登录日志" name="login">
|
||||
<LoginLogs />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="操作日志" name="operation">
|
||||
<OperationLogs />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="系统配置" name="config">
|
||||
<SystemConfig />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<div class="search-group">
|
||||
<label class="search-label">操作模块</label>
|
||||
<input
|
||||
v-model="searchModule"
|
||||
type="text"
|
||||
class="search-input"
|
||||
placeholder="输入模块名搜索"
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- <div class="search-group">
|
||||
<label class="search-label">操作类型</label>
|
||||
<select v-model="operationType" class="search-select">
|
||||
<option value="">全部</option>
|
||||
<option value="create">新增</option>
|
||||
<option value="update">修改</option>
|
||||
<option value="delete">删除</option>
|
||||
<option value="read">查询</option>
|
||||
</select>
|
||||
</div> -->
|
||||
<!--
|
||||
<div class="search-group">
|
||||
<label class="search-label">操作状态</label>
|
||||
<select v-model="operationStatus" class="search-select">
|
||||
<option value="">全部</option>
|
||||
<option value="success">成功</option>
|
||||
<option value="failed">失败</option>
|
||||
</select>
|
||||
</div> -->
|
||||
|
||||
<div class="search-group">
|
||||
<label class="search-label">开始日期</label>
|
||||
<input
|
||||
v-model="startDate"
|
||||
type="date"
|
||||
class="search-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="search-group">
|
||||
<label class="search-label">结束日期</label>
|
||||
<input
|
||||
v-model="endDate"
|
||||
type="date"
|
||||
class="search-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="search-actions">
|
||||
<button class="btn-search" @click="handleSearch">搜索</button>
|
||||
<button class="btn-reset" @click="handleReset">重置</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 日志表格 -->
|
||||
<div class="log-table-wrapper">
|
||||
<table class="log-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="10%">操作人</th>
|
||||
<th width="10%">操作模块</th>
|
||||
<th width="10%">操作类型</th>
|
||||
<th width="10%">请求链接</th>
|
||||
<th width="10%">IP地址</th>
|
||||
<th width="10%">操作描述</th>
|
||||
<!-- <th width="100">耗时(ms)</th> -->
|
||||
<!-- <th width="100">状态</th> -->
|
||||
<th width="10%">操作时间</th>
|
||||
<th width="10%">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody v-if="loading">
|
||||
<tr>
|
||||
<td colspan="9" class="loading-cell">
|
||||
<div class="loading-spinner"></div>
|
||||
<span>加载中...</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody v-else-if="logs.length === 0">
|
||||
<tr>
|
||||
<td colspan="9" class="empty-cell">
|
||||
<div class="empty-icon">📋</div>
|
||||
<p>暂无操作日志</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody v-else>
|
||||
<tr v-for="log in logs" :key="log.id" class="table-row">
|
||||
<td>{{ log.username || '-' }}</td>
|
||||
<td>{{ log.module || '-' }}</td>
|
||||
<td>
|
||||
<span class="status-tag" :class="getOperationClass(log.operation)">
|
||||
{{ getOperationText(log.operation) }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="log-desc">
|
||||
<div class="desc-text">{{ log.requestUrl || '-' }}</div>
|
||||
</td>
|
||||
<td>{{ log.ipAddress || '-' }}</td>
|
||||
<td class="log-desc">
|
||||
<div class="desc-text">{{ truncateText(log.responseData, 50) }}</div>
|
||||
</td>
|
||||
<!-- <td>
|
||||
<span class="status-tag" :class="log.status === 'success' ? 'status-success' : 'status-failed'">
|
||||
{{ log.status === 'success' ? '成功' : '失败' }}
|
||||
</span>
|
||||
</td> -->
|
||||
<td>{{ formatTime(log?.createTime || '') || '-' }}</td>
|
||||
|
||||
<td class="action-cell">
|
||||
<button class="btn-link btn-primary" @click="viewDetail(log)">详情</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div v-if="!loading && logs.length > 0" class="pagination">
|
||||
<div class="pagination-info">
|
||||
共 {{ total }} 条数据,每页 {{ pageSize }} 条
|
||||
</div>
|
||||
|
||||
<div class="pagination-controls">
|
||||
<button
|
||||
class="page-btn"
|
||||
:disabled="currentPage === 1"
|
||||
@click="handlePageChange(1)"
|
||||
>
|
||||
首页
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="page-btn"
|
||||
:disabled="currentPage === 1"
|
||||
@click="handlePageChange(currentPage - 1)"
|
||||
>
|
||||
上一页
|
||||
</button>
|
||||
|
||||
<div class="page-numbers">
|
||||
<button
|
||||
v-for="page in displayPages"
|
||||
:key="page"
|
||||
class="page-number"
|
||||
:class="{ active: page === currentPage }"
|
||||
@click="handlePageChange(page)"
|
||||
:disabled="page === -1"
|
||||
>
|
||||
{{ page === -1 ? '...' : page }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="page-btn"
|
||||
:disabled="currentPage === totalPages"
|
||||
@click="handlePageChange(currentPage + 1)"
|
||||
>
|
||||
下一页
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="page-btn"
|
||||
:disabled="currentPage === totalPages"
|
||||
@click="handlePageChange(totalPages)"
|
||||
>
|
||||
末页
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="pagination-jump">
|
||||
<span>跳转到</span>
|
||||
<input
|
||||
v-model.number="jumpPage"
|
||||
type="number"
|
||||
class="jump-input"
|
||||
@keyup.enter="handleJumpPage"
|
||||
/>
|
||||
<span>页</span>
|
||||
<button class="jump-btn" @click="handleJumpPage">跳转</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 详情对话框 -->
|
||||
<div v-if="detailVisible" class="modal-overlay" @click.self="detailVisible = false">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title">操作日志详情</h3>
|
||||
<button class="modal-close" @click="detailVisible = false">✕</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div v-if="currentLog" class="log-detail">
|
||||
<div class="detail-section">
|
||||
<h4 class="detail-title">基本信息</h4>
|
||||
<div class="detail-content">
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">操作人:</span>
|
||||
<span class="detail-value">{{ currentLog.username || '-' }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">操作模块:</span>
|
||||
<span class="detail-value">{{ currentLog.module || '-' }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">操作类型:</span>
|
||||
<span class="detail-value">
|
||||
<span class="status-tag" :class="getOperationClass(currentLog.operation)">
|
||||
{{ getOperationText(currentLog.operation) }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<!-- <div class="detail-row">
|
||||
<span class="detail-label">操作状态:</span>
|
||||
<span class="detail-value">
|
||||
<span class="status-tag" :class="currentLog.status === 'success' ? 'status-success' : 'status-failed'">
|
||||
{{ currentLog.status === 'success' ? '成功' : '失败' }}
|
||||
</span>
|
||||
</span>
|
||||
</div> -->
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">请求链接:</span>
|
||||
<span class="detail-value">{{ currentLog.requestUrl || '-' }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">IP地址:</span>
|
||||
<span class="detail-value">{{ currentLog.ipAddress || '-' }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">日志内容:</span>
|
||||
<span class="detail-value">{{ currentLog.responseData || '-' }}</span>
|
||||
</div>
|
||||
<!-- <div class="detail-row">
|
||||
<span class="detail-label">耗时:</span>
|
||||
<span class="detail-value">{{ currentLog.duration }}ms</span>
|
||||
</div> -->
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">操作时间:</span>
|
||||
<span class="detail-value">{{ formatTime(currentLog?.createTime || '') || '-' }}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section" v-if="currentLog.requestParams">
|
||||
<h4 class="detail-title">请求参数</h4>
|
||||
<div class="detail-content">
|
||||
<pre class="code-block">{{ currentLog.requestParams }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section" v-if="currentLog.responseData">
|
||||
<h4 class="detail-title">响应结果</h4>
|
||||
<div class="detail-content">
|
||||
<pre class="code-block">{{ currentLog.responseData }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section" v-if="currentLog.errorMessage">
|
||||
<h4 class="detail-title">错误信息</h4>
|
||||
<div class="detail-content">
|
||||
<div class="error-message">{{ currentLog.errorMessage }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="btn-primary" @click="detailVisible = false">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { ElTabs, ElTabPane } from 'element-plus';
|
||||
import LoginLogs from './components/LoginLogs.vue';
|
||||
import OperationLogs from './components/OperationLogs.vue';
|
||||
import SystemConfig from './components/SystemConfig.vue';
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { AdminLayout } from '@/views/admin';
|
||||
import { logApi } from '@/apis/system';
|
||||
import type { OperationLog } from '@/types/log';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
defineOptions({
|
||||
name: 'SystemLogsView'
|
||||
});
|
||||
const activeTab = ref('login');
|
||||
|
||||
const loading = ref(false);
|
||||
const searchKeyword = ref('');
|
||||
const searchModule = ref('');
|
||||
const operationType = ref('');
|
||||
const operationStatus = ref('');
|
||||
const startDate = ref('');
|
||||
const endDate = ref('');
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(20);
|
||||
const total = ref(0);
|
||||
const logs = ref<OperationLog[]>([]);
|
||||
const jumpPage = ref<number>();
|
||||
const detailVisible = ref(false);
|
||||
const currentLog = ref<OperationLog | null>(null);
|
||||
|
||||
// 计算总页数
|
||||
const totalPages = computed(() => Math.ceil(total.value / pageSize.value) || 1);
|
||||
|
||||
// 计算显示的页码
|
||||
const displayPages = computed(() => {
|
||||
const pages: number[] = [];
|
||||
const maxDisplay = 7;
|
||||
|
||||
if (totalPages.value <= maxDisplay) {
|
||||
for (let i = 1; i <= totalPages.value; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
} else {
|
||||
if (currentPage.value <= 4) {
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
pages.push(-1);
|
||||
pages.push(totalPages.value);
|
||||
} else if (currentPage.value >= totalPages.value - 3) {
|
||||
pages.push(1);
|
||||
pages.push(-1);
|
||||
for (let i = totalPages.value - 4; i <= totalPages.value; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
} else {
|
||||
pages.push(1);
|
||||
pages.push(-1);
|
||||
for (let i = currentPage.value - 1; i <= currentPage.value + 1; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
pages.push(-1);
|
||||
pages.push(totalPages.value);
|
||||
}
|
||||
}
|
||||
|
||||
return pages;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
loadLogs();
|
||||
});
|
||||
|
||||
function formatTime(time: string) {
|
||||
return dayjs(time).format('YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取文本并添加省略号
|
||||
*/
|
||||
function truncateText(text?: string, maxLength = 20): string {
|
||||
if (!text) return '-';
|
||||
if (text.length <= maxLength) return text;
|
||||
return text.substring(0, maxLength) + '...';
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载操作日志
|
||||
*/
|
||||
async function loadLogs() {
|
||||
try {
|
||||
loading.value = true;
|
||||
|
||||
// 构建查询条件
|
||||
const query: OperationLog = {
|
||||
username: searchKeyword.value || undefined,
|
||||
module: searchModule.value || undefined,
|
||||
operation: operationType.value || undefined,
|
||||
status: operationStatus.value || undefined,
|
||||
startTime: startDate.value || undefined,
|
||||
endTime: endDate.value || undefined,
|
||||
};
|
||||
|
||||
const result = await logApi.getOperationLogPage(
|
||||
{pageNumber: currentPage.value, pageSize: pageSize.value},
|
||||
query
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
logs.value = result.pageDomain?.dataList || [];
|
||||
total.value = result.pageDomain?.pageParam.totalElements || 0;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载操作日志失败:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询
|
||||
*/
|
||||
function handleSearch() {
|
||||
currentPage.value = 1;
|
||||
loadLogs();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置查询条件
|
||||
*/
|
||||
function handleReset() {
|
||||
searchKeyword.value = '';
|
||||
searchModule.value = '';
|
||||
operationType.value = '';
|
||||
operationStatus.value = '';
|
||||
startDate.value = '';
|
||||
endDate.value = '';
|
||||
currentPage.value = 1;
|
||||
loadLogs();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取操作类型样式类
|
||||
*/
|
||||
function getOperationClass(type?: string) {
|
||||
const classMap: Record<string, string> = {
|
||||
'create': 'operation-create',
|
||||
'update': 'operation-update',
|
||||
'delete': 'operation-delete',
|
||||
'read': 'operation-read'
|
||||
};
|
||||
return classMap[type || ''] || 'operation-read';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取操作类型文本
|
||||
*/
|
||||
function getOperationText(type?: string) {
|
||||
const textMap: Record<string, string> = {
|
||||
'create': '新增',
|
||||
'update': '修改',
|
||||
'delete': '删除',
|
||||
'read': '查询'
|
||||
};
|
||||
return textMap[type || ''] || (type || '-');
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看详情
|
||||
*/
|
||||
function viewDetail(log: OperationLog) {
|
||||
currentLog.value = { ...log };
|
||||
detailVisible.value = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 页码改变
|
||||
*/
|
||||
function handlePageChange(page: number) {
|
||||
if (page < 1 || page > totalPages.value || page === -1) return;
|
||||
currentPage.value = page;
|
||||
loadLogs();
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转页面
|
||||
*/
|
||||
function handleJumpPage() {
|
||||
if (!jumpPage.value || jumpPage.value < 1 || jumpPage.value > totalPages.value) {
|
||||
alert('请输入有效的页码');
|
||||
return;
|
||||
}
|
||||
currentPage.value = jumpPage.value;
|
||||
loadLogs();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.system-logs {
|
||||
.operation-logs {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 28px;
|
||||
.search-bar {
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.search-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.search-label {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.search-input,
|
||||
.search-select {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
transition: border-color 0.3s;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.search-select {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.search-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.btn-search,
|
||||
.btn-reset {
|
||||
padding: 8px 20px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-search {
|
||||
background: #409eff;
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
background: #66b1ff;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-reset {
|
||||
background: #fff;
|
||||
color: #606266;
|
||||
border: 1px solid #dcdfe6;
|
||||
|
||||
&:hover {
|
||||
color: #409eff;
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
padding: 8px 20px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
border: none;
|
||||
background: #409eff;
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
background: #66b1ff;
|
||||
}
|
||||
}
|
||||
|
||||
.log-table-wrapper {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.log-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
th {
|
||||
background: #f5f7fa;
|
||||
color: #606266;
|
||||
font-weight: 600;
|
||||
color: #141F38;
|
||||
margin-bottom: 24px;
|
||||
font-size: 14px;
|
||||
padding: 12px 16px;
|
||||
text-align: left;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
transition: background-color 0.3s;
|
||||
|
||||
&:hover {
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
&:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.log-desc {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.desc-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
|
||||
&.status-success {
|
||||
background: #e1f3d8;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
&.status-failed {
|
||||
background: #fde2e2;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
&.operation-create {
|
||||
background: #e1f3d8;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
&.operation-update {
|
||||
background: #fdf6ec;
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
&.operation-delete {
|
||||
background: #fde2e2;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
&.operation-read {
|
||||
background: #ecf5ff;
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.action-cell {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn-link {
|
||||
border: none;
|
||||
padding: 6px 12px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
border-radius: 4px;
|
||||
min-width: 64px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&.btn-primary {
|
||||
background: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-cell,
|
||||
.empty-cell {
|
||||
text-align: center;
|
||||
padding: 60px 20px !important;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #409eff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.pagination-controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.page-btn,
|
||||
.page-number {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #dcdfe6;
|
||||
background: #fff;
|
||||
color: #606266;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
color: #409eff;
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #409eff;
|
||||
color: #fff;
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.page-numbers {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.pagination-jump {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
|
||||
.jump-input {
|
||||
width: 60px;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.jump-btn {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #dcdfe6;
|
||||
background: #fff;
|
||||
color: #606266;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
color: #409eff;
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 模态框样式
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 2000;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
max-height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
font-size: 24px;
|
||||
color: #909399;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: #f5f7fa;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.log-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
background: #fafafa;
|
||||
border-radius: 6px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin: 0 0 16px 0;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
color: #909399;
|
||||
min-width: 100px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
color: #606266;
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
background: #fff;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
font-size: 13px;
|
||||
font-family: 'Courier New', monospace;
|
||||
color: #303133;
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background: #fef0f0;
|
||||
border: 1px solid #fde2e2;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
font-size: 14px;
|
||||
color: #f56c6c;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -424,7 +424,19 @@ async function fetchDeptRoles() {
|
||||
auth: true
|
||||
};
|
||||
}
|
||||
return await deptApi.getDeptByRole(currentDept.value);
|
||||
|
||||
const result = await deptApi.getDeptByRole(currentDept.value);
|
||||
|
||||
// 转换数据格式,将 UserDeptRoleVO 转换为与 SysRole 一致的格式
|
||||
if (result.success && result.dataList) {
|
||||
result.dataList = result.dataList.map(item => ({
|
||||
roleID: item.roleID,
|
||||
name: item.roleName, // roleName -> name
|
||||
description: item.roleDescription // roleDescription -> description
|
||||
}));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 查看绑定角色
|
||||
|
||||
@@ -166,7 +166,12 @@
|
||||
right-title="已选权限"
|
||||
:fetch-available-api="fetchAllPermissions"
|
||||
:fetch-selected-api="fetchMenuPermissions"
|
||||
:filter-selected="filterPermissions"
|
||||
:item-config="{ id: 'permissionID', label: 'name', sublabel: 'code' }"
|
||||
:use-tree="true"
|
||||
:tree-transform="transformPermissionsToTree"
|
||||
:tree-props="{ children: 'children', label: 'displayName', id: 'permissionID' }"
|
||||
:only-leaf-selectable="true"
|
||||
unit-name="个"
|
||||
search-placeholder="搜索权限名称或编码..."
|
||||
@confirm="handlePermissionConfirm"
|
||||
@@ -505,11 +510,80 @@ async function fetchAllPermissions() {
|
||||
// 获取菜单已绑定权限的接口
|
||||
async function fetchMenuPermissions() {
|
||||
if (!currentMenu.value?.menuID) {
|
||||
return { success: true, dataList: [] };
|
||||
return {
|
||||
success: true,
|
||||
dataList: [],
|
||||
code: 200,
|
||||
message: '',
|
||||
login: true,
|
||||
auth: true
|
||||
};
|
||||
}
|
||||
return await menuApi.getMenuPermission(currentMenu.value.menuID);
|
||||
}
|
||||
|
||||
// 过滤已选项
|
||||
function filterPermissions(available: any[], selected: any[]) {
|
||||
const selectedIds = new Set(selected.map(item => item.permissionID));
|
||||
return available.filter(item => !selectedIds.has(item.permissionID));
|
||||
}
|
||||
|
||||
// 将权限按模块ID构建树形结构
|
||||
function transformPermissionsToTree(flatData: any[]) {
|
||||
if (!flatData || flatData.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 按模块分组
|
||||
const moduleMap = new Map<string, any>();
|
||||
const tree: any[] = [];
|
||||
|
||||
flatData.forEach(item => {
|
||||
const moduleID = item.moduleID;
|
||||
|
||||
if (!moduleID) {
|
||||
// 没有模块ID的权限作为根节点
|
||||
tree.push({
|
||||
...item,
|
||||
displayName: item.name,
|
||||
isModule: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!moduleMap.has(moduleID)) {
|
||||
// 创建模块节点
|
||||
moduleMap.set(moduleID, {
|
||||
permissionID: moduleID, // 使用moduleID作为ID,确保唯一性
|
||||
displayName: item.moduleName || moduleID,
|
||||
moduleID: moduleID,
|
||||
moduleName: item.moduleName,
|
||||
moduleCode: item.moduleCode,
|
||||
moduleDescription: item.moduleDescription,
|
||||
children: [],
|
||||
isModule: true // 标记这是模块节点
|
||||
});
|
||||
}
|
||||
|
||||
// 添加权限到模块的children中
|
||||
const moduleNode = moduleMap.get(moduleID);
|
||||
if (moduleNode) {
|
||||
moduleNode.children.push({
|
||||
...item,
|
||||
displayName: item.name,
|
||||
isModule: false // 标记这是权限节点
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 将所有模块添加到树中
|
||||
moduleMap.forEach(moduleNode => {
|
||||
tree.push(moduleNode);
|
||||
});
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
// 查看绑定权限
|
||||
async function handleBindPermission(row: SysMenu) {
|
||||
currentMenu.value = row;
|
||||
@@ -535,8 +609,11 @@ async function handlePermissionConfirm(items: SysPermission[]) {
|
||||
const currentBoundResult = await menuApi.getMenuPermission(currentMenu.value.menuID);
|
||||
const currentBoundIds = (currentBoundResult.dataList || []).map(p => p.permissionID).filter((id): id is string => !!id);
|
||||
|
||||
// 新选择的权限ID
|
||||
const newSelectedIds = items.map(p => p.permissionID).filter((id): id is string => !!id);
|
||||
// 新选择的权限ID(过滤掉模块节点,只保留权限节点)
|
||||
const newSelectedIds = items
|
||||
.filter((item: any) => !item.isModule) // 只保留权限节点,过滤模块节点
|
||||
.map(p => p.permissionID)
|
||||
.filter((id): id is string => !!id);
|
||||
|
||||
// 找出需要绑定的权限(新增的)
|
||||
const permissionsToBind = newSelectedIds.filter(id => !currentBoundIds.includes(id));
|
||||
@@ -546,7 +623,8 @@ async function handlePermissionConfirm(items: SysPermission[]) {
|
||||
|
||||
// 构建需要绑定的权限对象数组
|
||||
if (permissionsToBind.length > 0) {
|
||||
const permissionsToBindObjects = items.filter(p => p.permissionID && permissionsToBind.includes(p.permissionID));
|
||||
const permissionsToBindObjects = items
|
||||
.filter((item: any) => !item.isModule && item.permissionID && permissionsToBind.includes(item.permissionID));
|
||||
|
||||
const bindMenu = {
|
||||
...currentMenu.value,
|
||||
@@ -569,6 +647,8 @@ async function handlePermissionConfirm(items: SysPermission[]) {
|
||||
}
|
||||
|
||||
ElMessage.success('权限绑定保存成功');
|
||||
bindPermissionDialogVisible.value = false;
|
||||
resetBindList();
|
||||
|
||||
// 刷新菜单列表
|
||||
await loadMenuList();
|
||||
|
||||
@@ -286,7 +286,12 @@
|
||||
right-title="已选菜单"
|
||||
:fetch-available-api="fetchAllMenus"
|
||||
:fetch-selected-api="fetchPermissionMenus"
|
||||
:filter-selected="filterMenus"
|
||||
:item-config="{ id: 'menuID', label: 'name', sublabel: 'url' }"
|
||||
:use-tree="true"
|
||||
:tree-transform="transformMenusToTree"
|
||||
:tree-props="{ children: 'children', label: 'name', id: 'menuID' }"
|
||||
:only-leaf-selectable="true"
|
||||
unit-name="个"
|
||||
search-placeholder="搜索菜单名称..."
|
||||
@confirm="handleMenuConfirm"
|
||||
@@ -301,6 +306,7 @@
|
||||
right-title="已选角色"
|
||||
:fetch-available-api="fetchAllRoles"
|
||||
:fetch-selected-api="fetchPermissionRoles"
|
||||
:filter-selected="filterRoles"
|
||||
:item-config="{ id: 'roleID', label: 'name', sublabel: 'description' }"
|
||||
unit-name="个"
|
||||
search-placeholder="搜索角色名称..."
|
||||
@@ -622,7 +628,11 @@ async function fetchPermissionMenus() {
|
||||
success: result.success,
|
||||
login: result.login ?? true,
|
||||
auth: result.auth ?? true,
|
||||
dataList: result.data?.menus || []
|
||||
dataList: result.dataList?.map(item => ({
|
||||
...item,
|
||||
name: item.menuName,
|
||||
url: item.menuUrl
|
||||
})) || []
|
||||
};
|
||||
}
|
||||
|
||||
@@ -651,10 +661,115 @@ async function fetchPermissionRoles() {
|
||||
success: result.success,
|
||||
login: result.login ?? true,
|
||||
auth: result.auth ?? true,
|
||||
dataList: result.data?.roles || []
|
||||
dataList: result.dataList?.map(item => ({
|
||||
...item,
|
||||
name: item.roleName,
|
||||
description: item.roleDescription
|
||||
})) || []
|
||||
};
|
||||
}
|
||||
|
||||
// 过滤已选菜单项
|
||||
function filterMenus(available: any[], selected: any[]) {
|
||||
const selectedIds = new Set(selected.map(item => item.menuID));
|
||||
return available.filter(item => !selectedIds.has(item.menuID));
|
||||
}
|
||||
|
||||
// 过滤已选角色项
|
||||
function filterRoles(available: any[], selected: any[]) {
|
||||
const selectedIds = new Set(selected.map(item => item.roleID));
|
||||
return available.filter(item => !selectedIds.has(item.roleID));
|
||||
}
|
||||
|
||||
// 将菜单扁平数据转换为树形结构(根据parentID构建)
|
||||
function transformMenusToTree(flatData: any[]): any[] {
|
||||
if (!flatData || flatData.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const tree: any[] = [];
|
||||
const map = new Map<string, any>();
|
||||
const maxDepth = flatData.length; // 最多遍历len层
|
||||
|
||||
// 初始化所有节点
|
||||
flatData.forEach(item => {
|
||||
if (item.menuID) {
|
||||
map.set(item.menuID, { ...item, children: [] });
|
||||
}
|
||||
});
|
||||
|
||||
// 循环构建树结构,最多遍历maxDepth次
|
||||
for (let depth = 0; depth < maxDepth; depth++) {
|
||||
let hasChanges = false;
|
||||
|
||||
flatData.forEach(item => {
|
||||
if (!item.menuID) return;
|
||||
|
||||
const node = map.get(item.menuID);
|
||||
if (!node) return;
|
||||
|
||||
// 如果节点已经在树中,跳过
|
||||
if (isMenuNodeInTree(node, tree)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!item.parentID || item.parentID === '0' || item.parentID === '') {
|
||||
// 根节点
|
||||
if (!isMenuNodeInTree(node, tree)) {
|
||||
tree.push(node);
|
||||
hasChanges = true;
|
||||
}
|
||||
} else {
|
||||
// 查找父节点
|
||||
const parent = map.get(item.parentID);
|
||||
if (parent && isMenuNodeInTree(parent, tree)) {
|
||||
if (!parent.children) {
|
||||
parent.children = [];
|
||||
}
|
||||
if (!parent.children.includes(node)) {
|
||||
parent.children.push(node);
|
||||
hasChanges = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 如果没有变化,说明树构建完成
|
||||
if (!hasChanges) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 清理空的children数组
|
||||
function cleanEmptyChildren(nodes: any[]) {
|
||||
nodes.forEach(node => {
|
||||
if (node.children && node.children.length === 0) {
|
||||
delete node.children;
|
||||
} else if (node.children && node.children.length > 0) {
|
||||
cleanEmptyChildren(node.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cleanEmptyChildren(tree);
|
||||
return tree;
|
||||
}
|
||||
|
||||
// 检查菜单节点是否已经在树中
|
||||
function isMenuNodeInTree(node: any, tree: any[]): boolean {
|
||||
for (const treeNode of tree) {
|
||||
if (treeNode.menuID === node.menuID) {
|
||||
return true;
|
||||
}
|
||||
if (treeNode.children && treeNode.children.length > 0) {
|
||||
if (isMenuNodeInTree(node, treeNode.children)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 绑定菜单
|
||||
async function handleBindMenu(permission: SysPermission) {
|
||||
currentPermission.value = permission;
|
||||
@@ -708,6 +823,8 @@ async function handleMenuConfirm(items: SysMenu[]) {
|
||||
}
|
||||
|
||||
ElMessage.success('菜单绑定保存成功');
|
||||
bindMenuDialogVisible.value = false;
|
||||
resetBindList();
|
||||
} catch (error) {
|
||||
console.error('保存菜单绑定失败:', error);
|
||||
ElMessage.error('保存菜单绑定失败');
|
||||
@@ -757,6 +874,8 @@ async function handleRoleConfirm(items: SysRole[]) {
|
||||
}
|
||||
|
||||
ElMessage.success('角色绑定保存成功');
|
||||
bindRoleDialogVisible.value = false;
|
||||
resetBindList();
|
||||
} catch (error) {
|
||||
console.error('保存角色绑定失败:', error);
|
||||
ElMessage.error('保存角色绑定失败');
|
||||
|
||||
@@ -102,9 +102,14 @@
|
||||
:title="`绑定权限 - ${currentRole?.name || ''}`"
|
||||
left-title="可选权限"
|
||||
right-title="已选权限"
|
||||
:available-items="availablePermissions"
|
||||
:initial-target-items="initialBoundPermissions"
|
||||
:fetch-available-api="fetchAllPermissions"
|
||||
:fetch-selected-api="fetchRolePermissions"
|
||||
:filter-selected="filterPermissions"
|
||||
:item-config="{ id: 'permissionID', label: 'name', sublabel: 'code' }"
|
||||
:use-tree="true"
|
||||
:tree-transform="transformPermissionsToTree"
|
||||
:tree-props="{ children: 'children', label: 'displayName', id: 'permissionID' }"
|
||||
:only-leaf-selectable="true"
|
||||
unit-name="个"
|
||||
search-placeholder="搜索权限名称或编码..."
|
||||
@confirm="handlePermissionConfirm"
|
||||
@@ -124,7 +129,7 @@ import { GenericSelector } from '@/components/base';
|
||||
defineOptions({
|
||||
name: 'RoleManageView'
|
||||
});
|
||||
import { ref, onMounted, reactive, computed } from 'vue';
|
||||
import { ref, onMounted, reactive } from 'vue';
|
||||
import { ElMessage, ElMessageBox, FormInstance, FormRules } from 'element-plus';
|
||||
import { Plus } from '@element-plus/icons-vue';
|
||||
|
||||
@@ -134,9 +139,10 @@ const loading = ref(false);
|
||||
const submitting = ref(false);
|
||||
|
||||
// 权限绑定相关数据
|
||||
const permissionList = ref<SysPermission[]>([]);
|
||||
// 移除:改为由 GenericSelector 通过 API 加载
|
||||
// const permissionList = ref<SysPermission[]>([]);
|
||||
// const initialBoundPermissions = ref<SysPermission[]>([]);
|
||||
const currentRole = ref<SysRole | null>(null);
|
||||
const initialBoundPermissions = ref<SysPermission[]>([]);
|
||||
|
||||
// 对话框状态
|
||||
const dialogVisible = ref(false);
|
||||
@@ -251,43 +257,105 @@ function resetForm() {
|
||||
});
|
||||
}
|
||||
|
||||
// 计算可选权限(过滤掉已绑定的)
|
||||
const availablePermissions = computed(() => {
|
||||
const boundIds = new Set(initialBoundPermissions.value.map(p => p.permissionID));
|
||||
return permissionList.value.filter(p => !boundIds.has(p.permissionID));
|
||||
});
|
||||
|
||||
// 查看绑定权限
|
||||
async function handleBindPermission(row: SysRole) {
|
||||
currentRole.value = row;
|
||||
|
||||
try {
|
||||
// 获取所有权限
|
||||
let permission: SysPermission = {
|
||||
// 获取所有权限的API函数
|
||||
async function fetchAllPermissions() {
|
||||
const permission: SysPermission = {
|
||||
permissionID: undefined,
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
description: undefined,
|
||||
};
|
||||
const permissionResult = await permissionApi.getPermissionList(permission);
|
||||
permissionList.value = permissionResult.dataList || [];
|
||||
|
||||
// 获取已绑定的权限
|
||||
const bindingResult = await roleApi.getRolePermission({
|
||||
roleID: row.roleID
|
||||
});
|
||||
initialBoundPermissions.value = bindingResult.dataList || [];
|
||||
|
||||
bindPermissionDialogVisible.value = true;
|
||||
} catch (error) {
|
||||
console.error('获取权限绑定信息失败:', error);
|
||||
ElMessage.error('获取权限绑定信息失败');
|
||||
return await permissionApi.getPermissionList(permission);
|
||||
}
|
||||
|
||||
// 获取角色已绑定权限的API函数
|
||||
async function fetchRolePermissions() {
|
||||
if (!currentRole.value || !currentRole.value.roleID) {
|
||||
return {
|
||||
success: true,
|
||||
dataList: [],
|
||||
code: 200,
|
||||
message: '',
|
||||
login: true,
|
||||
auth: true
|
||||
};
|
||||
}
|
||||
return await roleApi.getRolePermission({
|
||||
roleID: currentRole.value.roleID
|
||||
});
|
||||
}
|
||||
|
||||
// 过滤已选项
|
||||
function filterPermissions(available: any[], selected: any[]) {
|
||||
const selectedIds = new Set(selected.map(item => item.permissionID));
|
||||
return available.filter(item => !selectedIds.has(item.permissionID));
|
||||
}
|
||||
|
||||
// 将权限按模块ID构建树形结构
|
||||
function transformPermissionsToTree(flatData: any[]) {
|
||||
if (!flatData || flatData.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 按模块分组
|
||||
const moduleMap = new Map<string, any>();
|
||||
const tree: any[] = [];
|
||||
|
||||
flatData.forEach(item => {
|
||||
const moduleID = item.moduleID;
|
||||
|
||||
if (!moduleID) {
|
||||
// 没有模块ID的权限作为根节点
|
||||
tree.push({
|
||||
...item,
|
||||
displayName: item.name,
|
||||
isModule: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!moduleMap.has(moduleID)) {
|
||||
// 创建模块节点
|
||||
moduleMap.set(moduleID, {
|
||||
permissionID: moduleID, // 使用moduleID作为ID,确保唯一性
|
||||
displayName: item.moduleName || moduleID,
|
||||
moduleID: moduleID,
|
||||
moduleName: item.moduleName,
|
||||
moduleCode: item.moduleCode,
|
||||
moduleDescription: item.moduleDescription,
|
||||
children: [],
|
||||
isModule: true // 标记这是模块节点
|
||||
});
|
||||
}
|
||||
|
||||
// 添加权限到模块的children中
|
||||
const moduleNode = moduleMap.get(moduleID);
|
||||
if (moduleNode) {
|
||||
moduleNode.children.push({
|
||||
...item,
|
||||
displayName: item.name,
|
||||
isModule: false // 标记这是权限节点
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 将所有模块添加到树中
|
||||
moduleMap.forEach(moduleNode => {
|
||||
tree.push(moduleNode);
|
||||
});
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
// 查看绑定权限
|
||||
async function handleBindPermission(row: SysRole) {
|
||||
currentRole.value = row;
|
||||
bindPermissionDialogVisible.value = true;
|
||||
// 不再需要预加载数据,由 GenericSelector 在打开时自动调用 API 加载
|
||||
}
|
||||
|
||||
// 重置绑定列表
|
||||
function resetBindList() {
|
||||
initialBoundPermissions.value = [];
|
||||
currentRole.value = null;
|
||||
}
|
||||
|
||||
@@ -301,11 +369,20 @@ async function handlePermissionConfirm(items: SysPermission[]) {
|
||||
try {
|
||||
submitting.value = true;
|
||||
|
||||
// 获取当前已绑定的权限ID
|
||||
const currentBoundIds = initialBoundPermissions.value.map(p => p.permissionID).filter((id): id is string => !!id);
|
||||
// 重新获取当前已绑定的权限
|
||||
const bindingResult = await roleApi.getRolePermission({
|
||||
roleID: currentRole.value.roleID
|
||||
});
|
||||
const currentBoundPermissions = bindingResult.dataList || [];
|
||||
|
||||
// 新选择的权限ID
|
||||
const newSelectedIds = items.map(p => p.permissionID).filter((id): id is string => !!id);
|
||||
// 获取当前已绑定的权限ID
|
||||
const currentBoundIds = currentBoundPermissions.map(p => p.permissionID).filter((id): id is string => !!id);
|
||||
|
||||
// 新选择的权限ID(过滤掉模块节点,只保留权限节点)
|
||||
const newSelectedIds = items
|
||||
.filter((item: any) => !item.isModule) // 只保留权限节点,过滤模块节点
|
||||
.map(p => p.permissionID)
|
||||
.filter((id): id is string => !!id);
|
||||
|
||||
// 找出需要绑定的权限(新增的)
|
||||
const permissionsToBind = newSelectedIds.filter(id => !currentBoundIds.includes(id));
|
||||
@@ -316,7 +393,7 @@ async function handlePermissionConfirm(items: SysPermission[]) {
|
||||
// 构建需要绑定的权限对象数组
|
||||
if (permissionsToBind.length > 0) {
|
||||
const permissionsToBindObjects = permissionsToBind.map(permissionID => {
|
||||
const permission = permissionList.value.find(p => p.permissionID === permissionID);
|
||||
const permission = items.find(p => p.permissionID === permissionID);
|
||||
return permission || { permissionID: permissionID };
|
||||
});
|
||||
|
||||
@@ -331,7 +408,7 @@ async function handlePermissionConfirm(items: SysPermission[]) {
|
||||
// 构建需要解绑的权限对象数组
|
||||
if (permissionsToUnbind.length > 0) {
|
||||
const permissionsToUnbindObjects = permissionsToUnbind.map(permissionID => {
|
||||
const permission = permissionList.value.find(p => p.permissionID === permissionID);
|
||||
const permission = currentBoundPermissions.find(p => p.permissionID === permissionID);
|
||||
return permission || { permissionID: permissionID };
|
||||
});
|
||||
|
||||
@@ -344,6 +421,8 @@ async function handlePermissionConfirm(items: SysPermission[]) {
|
||||
}
|
||||
|
||||
ElMessage.success('权限绑定保存成功');
|
||||
bindPermissionDialogVisible.value = false;
|
||||
resetBindList();
|
||||
|
||||
// 刷新角色列表
|
||||
await loadRoleList();
|
||||
|
||||
@@ -319,16 +319,17 @@
|
||||
:title="selectorMode === 'add' ? '添加人员' : '删除人员'"
|
||||
:left-title="selectorMode === 'add' ? '可添加人员' : '当前人员'"
|
||||
:right-title="selectorMode === 'add' ? '待添加人员' : '待删除人员'"
|
||||
:available-items="selectorMode === 'remove' ? availableUsers : []"
|
||||
:initial-target-items="[]"
|
||||
:fetch-available-api="selectorMode === 'add' ? fetchAllUsers : fetchCurrentUsers"
|
||||
:fetch-selected-api="selectorMode === 'add' ? fetchTaskUsers : undefined"
|
||||
:filter-selected="filterUsers"
|
||||
:loading="saving"
|
||||
:item-config="{ id: 'id', label: 'username', sublabel: 'deptName' }"
|
||||
:use-tree="true"
|
||||
:tree-transform="transformUsersToTree"
|
||||
:tree-props="{ children: 'children', label: 'displayName', id: 'id' }"
|
||||
:only-leaf-selectable="true"
|
||||
unit-name="人"
|
||||
search-placeholder="搜索人员..."
|
||||
:use-pagination="selectorMode === 'add'"
|
||||
:fetch-api="selectorMode === 'add' ? userApi.getUserPage : undefined"
|
||||
:filter-params="userFilterParams"
|
||||
:page-size="20"
|
||||
@confirm="handleUserSelectConfirm"
|
||||
@cancel="closeSelectorModal"
|
||||
/>
|
||||
@@ -388,9 +389,7 @@ const deleting = ref(false);
|
||||
const managingTask = ref<LearningTask | null>(null);
|
||||
const selectorMode = ref<'add' | 'remove'>('add');
|
||||
const currentUsers = ref<UserVO[]>([]);
|
||||
const availableUsers = ref<UserVO[]>([]);
|
||||
const saving = ref(false);
|
||||
const userFilterParams = ref<any>({});
|
||||
|
||||
// 计算显示的页码
|
||||
const displayPages = computed(() => {
|
||||
@@ -523,7 +522,9 @@ function handleEdit(task: LearningTask) {
|
||||
emit('edit', task);
|
||||
}
|
||||
|
||||
function handleStatistics(task: LearningTask) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function handleStatistics(_task: LearningTask) {
|
||||
// TODO: 实现统计功能
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -565,30 +566,171 @@ function closeUserList() {
|
||||
currentUsers.value = [];
|
||||
}
|
||||
|
||||
// 获取所有用户的API函数
|
||||
async function fetchAllUsers() {
|
||||
const filter: any = { username: undefined };
|
||||
return await userApi.getUserList(filter);
|
||||
}
|
||||
|
||||
// 获取当前任务用户的API函数(用于删除模式)
|
||||
async function fetchCurrentUsers() {
|
||||
// 删除模式时,左侧显示当前任务的用户列表
|
||||
return {
|
||||
success: true,
|
||||
dataList: currentUsers.value,
|
||||
code: 200,
|
||||
message: '',
|
||||
login: true,
|
||||
auth: true
|
||||
};
|
||||
}
|
||||
|
||||
// 获取任务已分配用户的API函数
|
||||
async function fetchTaskUsers() {
|
||||
if (!managingTask.value || !managingTask.value.taskID) {
|
||||
return {
|
||||
success: true,
|
||||
dataList: [],
|
||||
code: 200,
|
||||
message: '',
|
||||
login: true,
|
||||
auth: true
|
||||
};
|
||||
}
|
||||
|
||||
// 从任务详情中获取用户列表
|
||||
const result = await learningTaskApi.getTaskUsers(managingTask.value.taskID);
|
||||
if (result.success && result.dataList) {
|
||||
// 将返回的用户列表转换为UserVO格式
|
||||
const users = (result.dataList || []).map((item: any) => ({
|
||||
id: item.userID,
|
||||
username: item.username,
|
||||
deptID: item.deptID,
|
||||
deptName: item.deptName,
|
||||
parentDeptID: item.parentDeptID
|
||||
}));
|
||||
return {
|
||||
success: true,
|
||||
dataList: users,
|
||||
code: 200,
|
||||
message: '',
|
||||
login: true,
|
||||
auth: true
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
dataList: [],
|
||||
code: result.code || 500,
|
||||
message: result.message || '获取任务用户失败',
|
||||
login: true,
|
||||
auth: true
|
||||
};
|
||||
}
|
||||
|
||||
// 过滤已选用户
|
||||
function filterUsers(available: any[], selected: any[]) {
|
||||
const selectedIds = new Set(selected.map(item => item.id));
|
||||
return available.filter(item => !selectedIds.has(item.id));
|
||||
}
|
||||
|
||||
// 将用户扁平数据转换为树形结构(按部门分组)
|
||||
function transformUsersToTree(flatData: any[]): any[] {
|
||||
if (!flatData || flatData.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 按部门分组
|
||||
const deptMap = new Map<string, any>();
|
||||
const tree: any[] = [];
|
||||
|
||||
flatData.forEach(item => {
|
||||
const deptID = item.deptID || 'unknown';
|
||||
const deptName = item.deptName || '未分配部门';
|
||||
|
||||
if (!deptMap.has(deptID)) {
|
||||
// 创建部门节点
|
||||
deptMap.set(deptID, {
|
||||
id: `dept_${deptID}`, // 使用特殊前缀避免与用户ID冲突
|
||||
displayName: deptName,
|
||||
deptID: deptID,
|
||||
deptName: deptName,
|
||||
parentDeptID: item.parentDeptID,
|
||||
children: [],
|
||||
isDept: true // 标记这是部门节点
|
||||
});
|
||||
}
|
||||
|
||||
// 添加用户到部门的children中
|
||||
const deptNode = deptMap.get(deptID);
|
||||
if (deptNode) {
|
||||
deptNode.children.push({
|
||||
...item,
|
||||
displayName: item.username || item.id,
|
||||
isDept: false // 标记这是用户节点
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 构建部门层级关系
|
||||
const deptNodes = Array.from(deptMap.values());
|
||||
const deptTreeMap = new Map<string, any>();
|
||||
|
||||
// 初始化所有部门节点
|
||||
deptNodes.forEach(dept => {
|
||||
deptTreeMap.set(dept.deptID, { ...dept });
|
||||
});
|
||||
|
||||
// 构建部门树
|
||||
deptNodes.forEach(dept => {
|
||||
const node = deptTreeMap.get(dept.deptID);
|
||||
if (!node) return;
|
||||
|
||||
if (!dept.parentDeptID || dept.parentDeptID === '0' || dept.parentDeptID === '') {
|
||||
// 根部门
|
||||
tree.push(node);
|
||||
} else {
|
||||
// 子部门
|
||||
const parent = deptTreeMap.get(dept.parentDeptID);
|
||||
if (parent) {
|
||||
// 将用户节点暂存
|
||||
const users = node.children || [];
|
||||
node.children = [];
|
||||
|
||||
// 添加部门到父部门
|
||||
if (!parent.children) {
|
||||
parent.children = [];
|
||||
}
|
||||
parent.children.push(node);
|
||||
|
||||
// 将用户添加回来(放在部门子节点之后)
|
||||
node.children = users;
|
||||
} else {
|
||||
// 找不到父节点,作为根节点
|
||||
tree.push(node);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
// 显示添加人员选择器
|
||||
function showAddUserSelector() {
|
||||
selectorMode.value = 'add';
|
||||
|
||||
// 设置过滤参数(可以过滤掉已分配的用户,但在组件内部会自动过滤)
|
||||
userFilterParams.value = {};
|
||||
|
||||
showUserSelector.value = true;
|
||||
}
|
||||
|
||||
// 显示删除人员选择器
|
||||
function showRemoveUserSelector() {
|
||||
selectorMode.value = 'remove';
|
||||
|
||||
// 左侧显示当前用户
|
||||
availableUsers.value = [...currentUsers.value];
|
||||
|
||||
showUserSelector.value = true;
|
||||
}
|
||||
|
||||
// 关闭选择器弹窗
|
||||
function closeSelectorModal() {
|
||||
showUserSelector.value = false;
|
||||
availableUsers.value = [];
|
||||
}
|
||||
|
||||
// 处理用户选择器确认事件
|
||||
|
||||
Reference in New Issue
Block a user