From 15b0013cd072ca6ce35117b636ac1bffe2e5b065 Mon Sep 17 00:00:00 2001 From: JiaoTianBo Date: Fri, 27 Mar 2026 16:01:00 +0800 Subject: [PATCH] =?UTF-8?q?feat(core):=20=E6=96=B0=E5=A2=9E=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E5=8F=8A=E7=9B=B8=E5=85=B3=E5=8A=9F=E8=83=BD=E7=9A=84?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E8=AE=BF=E9=97=AE=E5=B1=82=E5=92=8C=E6=9D=83?= =?UTF-8?q?=E9=99=90=E6=8E=A7=E5=88=B6=E5=88=87=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加多个Mapper接口及XML文件支持项目、成员、里程碑、任务、风险、资源、 文件附件等模块的数据操作和查询功能,支持复杂查询与统计 - 新增Sa-Token权限配置,集成统一认证管理 - 引入权限常量类,定义系统角色、项目角色及权限编码标准 - 新增项目权限校验切面,实现基于注解的项目权限和角色校验逻辑 - 更新配置文件和依赖,集成MyBatis Plus、MinIO、Spring AI及文档解析相关库 - 调整MyBatis配置的类型别名包路径,统一领域实体引用路径 --- pom.xml | 14 + .../annotation/RequireProjectPermission.java | 30 ++ .../common/annotation/RequireProjectRole.java | 36 +++ .../aspect/ProjectPermissionAspect.java | 162 ++++++++++ .../common/config/SaTokenConfig.java | 71 +++++ .../config/SaTokenPermissionExtension.java | 67 +++++ .../common/constant/PermissionConstants.java | 79 +++++ .../yinlihupo/common/util/SecurityUtils.java | 125 ++++++++ .../domain/entity/SysPermission.java | 66 +++++ .../cn/yinlihupo/domain/entity/SysRole.java | 57 ++++ .../domain/entity/SysRolePermission.java | 29 ++ .../cn/yinlihupo/domain/entity/SysUser.java | 82 ++++++ .../yinlihupo/domain/entity/SysUserRole.java | 29 ++ .../mapper/FileAttachmentMapper.java | 21 ++ .../cn/yinlihupo/mapper/ProjectMapper.java | 36 +++ .../yinlihupo/mapper/ProjectMemberMapper.java | 20 ++ .../mapper/ProjectMilestoneMapper.java | 26 ++ .../cn/yinlihupo/mapper/ResourceMapper.java | 28 ++ .../java/cn/yinlihupo/mapper/RiskMapper.java | 28 ++ .../yinlihupo/mapper/SysPermissionMapper.java | 30 ++ .../cn/yinlihupo/mapper/SysRoleMapper.java | 12 + .../mapper/SysRolePermissionMapper.java | 12 + .../cn/yinlihupo/mapper/SysUserMapper.java | 37 +++ .../yinlihupo/mapper/SysUserRoleMapper.java | 12 + .../java/cn/yinlihupo/mapper/TaskMapper.java | 35 +++ .../system/ProjectPermissionService.java | 160 ++++++++++ .../impl/ProjectPermissionServiceImpl.java | 278 ++++++++++++++++++ src/main/resources/application-dev.yaml | 2 +- src/main/resources/application.yaml | 25 +- .../resources/mapper/FileAttachmentMapper.xml | 75 +++++ src/main/resources/mapper/ProjectMapper.xml | 110 +++++++ .../resources/mapper/ProjectMemberMapper.xml | 92 ++++++ .../mapper/ProjectMilestoneMapper.xml | 85 ++++++ src/main/resources/mapper/ResourceMapper.xml | 96 ++++++ src/main/resources/mapper/RiskMapper.xml | 110 +++++++ .../resources/mapper/SysPermissionMapper.xml | 41 +++ src/main/resources/mapper/SysUserMapper.xml | 83 ++++++ src/main/resources/mapper/TaskMapper.xml | 125 ++++++++ 38 files changed, 2424 insertions(+), 2 deletions(-) create mode 100644 src/main/java/cn/yinlihupo/common/annotation/RequireProjectPermission.java create mode 100644 src/main/java/cn/yinlihupo/common/annotation/RequireProjectRole.java create mode 100644 src/main/java/cn/yinlihupo/common/aspect/ProjectPermissionAspect.java create mode 100644 src/main/java/cn/yinlihupo/common/config/SaTokenConfig.java create mode 100644 src/main/java/cn/yinlihupo/common/config/SaTokenPermissionExtension.java create mode 100644 src/main/java/cn/yinlihupo/common/constant/PermissionConstants.java create mode 100644 src/main/java/cn/yinlihupo/common/util/SecurityUtils.java create mode 100644 src/main/java/cn/yinlihupo/domain/entity/SysPermission.java create mode 100644 src/main/java/cn/yinlihupo/domain/entity/SysRole.java create mode 100644 src/main/java/cn/yinlihupo/domain/entity/SysRolePermission.java create mode 100644 src/main/java/cn/yinlihupo/domain/entity/SysUser.java create mode 100644 src/main/java/cn/yinlihupo/domain/entity/SysUserRole.java create mode 100644 src/main/java/cn/yinlihupo/mapper/SysPermissionMapper.java create mode 100644 src/main/java/cn/yinlihupo/mapper/SysRoleMapper.java create mode 100644 src/main/java/cn/yinlihupo/mapper/SysRolePermissionMapper.java create mode 100644 src/main/java/cn/yinlihupo/mapper/SysUserMapper.java create mode 100644 src/main/java/cn/yinlihupo/mapper/SysUserRoleMapper.java create mode 100644 src/main/java/cn/yinlihupo/service/system/ProjectPermissionService.java create mode 100644 src/main/java/cn/yinlihupo/service/system/impl/ProjectPermissionServiceImpl.java create mode 100644 src/main/resources/mapper/FileAttachmentMapper.xml create mode 100644 src/main/resources/mapper/ProjectMapper.xml create mode 100644 src/main/resources/mapper/ProjectMemberMapper.xml create mode 100644 src/main/resources/mapper/ProjectMilestoneMapper.xml create mode 100644 src/main/resources/mapper/ResourceMapper.xml create mode 100644 src/main/resources/mapper/RiskMapper.xml create mode 100644 src/main/resources/mapper/SysPermissionMapper.xml create mode 100644 src/main/resources/mapper/SysUserMapper.xml create mode 100644 src/main/resources/mapper/TaskMapper.xml diff --git a/pom.xml b/pom.xml index 793045e..0b73e85 100644 --- a/pom.xml +++ b/pom.xml @@ -125,6 +125,20 @@ 2.9.1 + + + cn.dev33 + sa-token-spring-boot3-starter + 1.39.0 + + + + + cn.dev33 + sa-token-redis-jackson + 1.39.0 + + org.springframework.boot spring-boot-starter-test diff --git a/src/main/java/cn/yinlihupo/common/annotation/RequireProjectPermission.java b/src/main/java/cn/yinlihupo/common/annotation/RequireProjectPermission.java new file mode 100644 index 0000000..6855323 --- /dev/null +++ b/src/main/java/cn/yinlihupo/common/annotation/RequireProjectPermission.java @@ -0,0 +1,30 @@ +package cn.yinlihupo.common.annotation; + +import java.lang.annotation.*; + +/** + * 项目权限校验注解 + * 用于标注需要校验项目权限的方法 + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RequireProjectPermission { + + /** + * 项目ID参数名 + * 默认从方法参数中获取名为"projectId"的参数值 + */ + String projectIdParam() default "projectId"; + + /** + * 需要的权限编码 + * 例如: "risk:manage", "task:assign" + */ + String value(); + + /** + * 错误提示信息 + */ + String message() default "无权访问该项目资源"; +} diff --git a/src/main/java/cn/yinlihupo/common/annotation/RequireProjectRole.java b/src/main/java/cn/yinlihupo/common/annotation/RequireProjectRole.java new file mode 100644 index 0000000..0ef095c --- /dev/null +++ b/src/main/java/cn/yinlihupo/common/annotation/RequireProjectRole.java @@ -0,0 +1,36 @@ +package cn.yinlihupo.common.annotation; + +import java.lang.annotation.*; + +/** + * 项目角色校验注解 + * 用于标注需要校验项目角色的方法 + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RequireProjectRole { + + /** + * 项目ID参数名 + * 默认从方法参数中获取名为"projectId"的参数值 + */ + String projectIdParam() default "projectId"; + + /** + * 需要的项目角色 + * 可选值: manager, leader, member, observer + */ + String[] value(); + + /** + * 是否允许系统管理员绕过角色校验 + * 默认true,管理员拥有所有权限 + */ + boolean allowAdmin() default true; + + /** + * 错误提示信息 + */ + String message() default "无权执行该操作"; +} diff --git a/src/main/java/cn/yinlihupo/common/aspect/ProjectPermissionAspect.java b/src/main/java/cn/yinlihupo/common/aspect/ProjectPermissionAspect.java new file mode 100644 index 0000000..ccc7297 --- /dev/null +++ b/src/main/java/cn/yinlihupo/common/aspect/ProjectPermissionAspect.java @@ -0,0 +1,162 @@ +package cn.yinlihupo.common.aspect; + +import cn.yinlihupo.common.annotation.RequireProjectPermission; +import cn.yinlihupo.common.annotation.RequireProjectRole; +import cn.yinlihupo.common.exception.BusinessException; +import cn.yinlihupo.common.util.SecurityUtils; +import cn.yinlihupo.service.system.ProjectPermissionService; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; + +/** + * 项目权限校验切面 + */ +@Slf4j +@Aspect +@Component +@RequiredArgsConstructor +public class ProjectPermissionAspect { + + private final ProjectPermissionService projectPermissionService; + + /** + * 校验项目权限 + */ + @Before("@annotation(cn.yinlihupo.common.annotation.RequireProjectPermission)") + public void checkProjectPermission(JoinPoint joinPoint) { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + RequireProjectPermission annotation = method.getAnnotation(RequireProjectPermission.class); + + Long userId = SecurityUtils.getCurrentUserId(); + if (userId == null) { + throw new BusinessException(403, "用户未登录"); + } + + // 管理员直接放行 + if (projectPermissionService.isAdmin(userId)) { + return; + } + + // 获取项目ID + Long projectId = extractProjectId(joinPoint, annotation.projectIdParam()); + if (projectId == null) { + throw new BusinessException(400, "无法获取项目ID"); + } + + // 校验权限 + String requiredPermission = annotation.value(); + boolean hasPermission = projectPermissionService.hasPermission(userId, projectId, requiredPermission); + + if (!hasPermission) { + log.warn("用户 [{}] 没有项目 [{}] 的权限 [{}]", userId, projectId, requiredPermission); + throw new BusinessException(403, annotation.message()); + } + } + + /** + * 校验项目角色 + */ + @Before("@annotation(cn.yinlihupo.common.annotation.RequireProjectRole)") + public void checkProjectRole(JoinPoint joinPoint) { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + RequireProjectRole annotation = method.getAnnotation(RequireProjectRole.class); + + Long userId = SecurityUtils.getCurrentUserId(); + if (userId == null) { + throw new BusinessException(403, "用户未登录"); + } + + // 管理员直接放行(如果允许) + if (annotation.allowAdmin() && projectPermissionService.isAdmin(userId)) { + return; + } + + // 获取项目ID + Long projectId = extractProjectId(joinPoint, annotation.projectIdParam()); + if (projectId == null) { + throw new BusinessException(400, "无法获取项目ID"); + } + + // 获取用户项目角色 + String userRole = projectPermissionService.getUserProjectRole(userId, projectId); + if (userRole == null) { + throw new BusinessException(403, "您不是该项目的成员"); + } + + // 校验角色 + String[] requiredRoles = annotation.value(); + boolean hasRole = false; + for (String requiredRole : requiredRoles) { + if (userRole.equals(requiredRole)) { + hasRole = true; + break; + } + } + + if (!hasRole) { + log.warn("用户 [{}] 在项目 [{}] 中的角色 [{}] 不符合要求 {}", + userId, projectId, userRole, requiredRoles); + throw new BusinessException(403, annotation.message()); + } + } + + /** + * 从方法参数中提取项目ID + */ + private Long extractProjectId(JoinPoint joinPoint, String paramName) { + Object[] args = joinPoint.getArgs(); + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Parameter[] parameters = signature.getMethod().getParameters(); + + // 1. 先从方法参数中查找 + for (int i = 0; i < parameters.length; i++) { + if (parameters[i].getName().equals(paramName) && args[i] instanceof Long) { + return (Long) args[i]; + } + } + + // 2. 尝试从请求参数中获取 + try { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attributes != null) { + HttpServletRequest request = attributes.getRequest(); + String projectIdStr = request.getParameter(paramName); + if (projectIdStr != null && !projectIdStr.isEmpty()) { + return Long.valueOf(projectIdStr); + } + + // 尝试从路径变量中获取 + Object pathVar = request.getAttribute(paramName); + if (pathVar instanceof Long) { + return (Long) pathVar; + } + } + } catch (Exception e) { + log.debug("从请求中获取项目ID失败: {}", e.getMessage()); + } + + // 3. 查找任何名为projectId或包含projectId的参数 + for (int i = 0; i < parameters.length; i++) { + String paramNameLower = parameters[i].getName().toLowerCase(); + if ((paramNameLower.contains("projectid") || paramNameLower.contains("project_id")) + && args[i] instanceof Long) { + return (Long) args[i]; + } + } + + return null; + } +} diff --git a/src/main/java/cn/yinlihupo/common/config/SaTokenConfig.java b/src/main/java/cn/yinlihupo/common/config/SaTokenConfig.java new file mode 100644 index 0000000..f0b712b --- /dev/null +++ b/src/main/java/cn/yinlihupo/common/config/SaTokenConfig.java @@ -0,0 +1,71 @@ +package cn.yinlihupo.common.config; + +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.filter.SaServletFilter; +import cn.dev33.satoken.interceptor.SaInterceptor; +import cn.dev33.satoken.router.SaRouter; +import cn.dev33.satoken.stp.StpUtil; +import cn.yinlihupo.common.core.BaseResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * Sa-Token 配置类 + */ +@Slf4j +@Configuration +public class SaTokenConfig implements WebMvcConfigurer { + + /** + * 注册 Sa-Token 拦截器,打开注解式鉴权功能 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + // 注册 Sa-Token 拦截器,校验规则为 StpUtil.checkLogin() 登录校验 + registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin())) + .addPathPatterns("/**") + .excludePathPatterns( + "/auth/login", + "/auth/register", + "/error", + "/swagger-ui/**", + "/v3/api-docs/**" + ); + } + + /** + * 注册 [Sa-Token 全局过滤器] + */ + @Bean + public SaServletFilter getSaServletFilter() { + return new SaServletFilter() + // 指定 [拦截路由] 与 [放行路由] + .addInclude("/**") + .addExclude("/favicon.ico", "/swagger-ui/**", "/v3/api-docs/**") + // 认证函数: 每次请求执行 + .setAuth(obj -> { + log.debug("---------- 进入 Sa-Token 全局认证 -----------"); + }) + // 异常处理函数:每次认证函数发生异常时执行 + .setError(e -> { + log.error("Sa-Token 认证异常: {}", e.getMessage()); + return new BaseResponse<>(403, e.getMessage(), null); + }) + // 前置函数:在每次认证函数之前执行 + .setBeforeAuth(obj -> { + // 设置跨域响应头 + SaHolder.getResponse() + .setHeader("Access-Control-Allow-Origin", "*") + .setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + .setHeader("Access-Control-Allow-Headers", "*") + .setHeader("Access-Control-Max-Age", "3600"); + // 如果是预检请求,则立即返回到前端 + SaRouter.match("OPTIONS") + .free(r -> log.debug("--------OPTIONS预检请求,不做处理")) + .back(); + }); + } +} diff --git a/src/main/java/cn/yinlihupo/common/config/SaTokenPermissionExtension.java b/src/main/java/cn/yinlihupo/common/config/SaTokenPermissionExtension.java new file mode 100644 index 0000000..a5a35bd --- /dev/null +++ b/src/main/java/cn/yinlihupo/common/config/SaTokenPermissionExtension.java @@ -0,0 +1,67 @@ +package cn.yinlihupo.common.config; + +import cn.dev33.satoken.stp.StpInterface; +import cn.yinlihupo.mapper.SysPermissionMapper; +import cn.yinlihupo.mapper.SysUserMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +/** + * Sa-Token 权限认证接口扩展 + * 实现 StpInterface 接口,自定义权限和角色获取逻辑 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class SaTokenPermissionExtension implements StpInterface { + + private final SysUserMapper sysUserMapper; + private final SysPermissionMapper sysPermissionMapper; + + /** + * 返回指定账号id所拥有的权限码集合 + * + * @param loginId 账号id + * @param loginType 账号类型 + * @return 权限码集合 + */ + @Override + public List getPermissionList(Object loginId, String loginType) { + // 项目权限通过 ProjectPermissionService 动态校验 + // 这里返回系统级别的权限 + try { + Long userId = Long.valueOf(loginId.toString()); + List roleCodes = sysUserMapper.selectRoleCodesByUserId(userId); + if (roleCodes.isEmpty()) { + return new ArrayList<>(); + } + // 根据角色查询权限 + return sysPermissionMapper.selectPermissionCodesByRoleCodes(roleCodes); + } catch (Exception e) { + log.error("获取用户权限列表失败: {}", e.getMessage()); + return new ArrayList<>(); + } + } + + /** + * 返回指定账号id所拥有的角色标识集合 + * + * @param loginId 账号id + * @param loginType 账号类型 + * @return 角色标识集合 + */ + @Override + public List getRoleList(Object loginId, String loginType) { + try { + Long userId = Long.valueOf(loginId.toString()); + return sysUserMapper.selectRoleCodesByUserId(userId); + } catch (Exception e) { + log.error("获取用户角色列表失败: {}", e.getMessage()); + return new ArrayList<>(); + } + } +} diff --git a/src/main/java/cn/yinlihupo/common/constant/PermissionConstants.java b/src/main/java/cn/yinlihupo/common/constant/PermissionConstants.java new file mode 100644 index 0000000..f7dedb5 --- /dev/null +++ b/src/main/java/cn/yinlihupo/common/constant/PermissionConstants.java @@ -0,0 +1,79 @@ +package cn.yinlihupo.common.constant; + +/** + * 权限常量类 + * 定义系统角色、项目角色和权限编码 + */ +public class PermissionConstants { + + // ==================== 系统角色 ==================== + /** 系统管理员 */ + public static final String ROLE_ADMIN = "admin"; + /** 项目经理 */ + public static final String ROLE_PROJECT_MANAGER = "project_manager"; + /** 团队负责人 */ + public static final String ROLE_TEAM_LEADER = "team_leader"; + /** 普通成员 */ + public static final String ROLE_MEMBER = "member"; + + // ==================== 项目角色 ==================== + /** 项目经理 - 拥有项目全部权限 */ + public static final String PROJECT_ROLE_MANAGER = "manager"; + /** 负责人 - 协助管理项目 */ + public static final String PROJECT_ROLE_LEADER = "leader"; + /** 普通成员 */ + public static final String PROJECT_ROLE_MEMBER = "member"; + /** 观察者 - 只读权限 */ + public static final String PROJECT_ROLE_OBSERVER = "observer"; + + // ==================== 项目权限编码 ==================== + // 项目查看权限 + public static final String PERM_PROJECT_VIEW = "project:view"; + // 项目管理权限(编辑、删除) + public static final String PERM_PROJECT_MANAGE = "project:manage"; + + // 风险查看权限 + public static final String PERM_RISK_VIEW = "risk:view"; + // 风险管控权限(创建、编辑、删除) + public static final String PERM_RISK_MANAGE = "risk:manage"; + + // 资源查看权限 + public static final String PERM_RESOURCE_VIEW = "resource:view"; + // 资源管控权限(申请、审批、分配) + public static final String PERM_RESOURCE_MANAGE = "resource:manage"; + + // 里程碑查看权限 + public static final String PERM_MILESTONE_VIEW = "milestone:view"; + // 里程碑推进权限 + public static final String PERM_MILESTONE_PUSH = "milestone:push"; + + // 任务工单查看权限 + public static final String PERM_TASK_VIEW = "task:view"; + // 任务工单分配权限 + public static final String PERM_TASK_ASSIGN = "task:assign"; + // 任务工单处理权限 + public static final String PERM_TASK_PROCESS = "task:process"; + + // 日报查看权限 + public static final String PERM_REPORT_VIEW = "report:view"; + // 日报上传权限 + public static final String PERM_REPORT_CREATE = "report:create"; + + // 知识库查看权限 + public static final String PERM_KB_VIEW = "kb:view"; + // 知识库上传权限 + public static final String PERM_KB_CREATE = "kb:create"; + + // ==================== 数据权限范围 ==================== + /** 全部数据 */ + public static final int DATA_SCOPE_ALL = 1; + /** 本部门数据 */ + public static final int DATA_SCOPE_DEPT = 2; + /** 本人数据 */ + public static final int DATA_SCOPE_SELF = 3; + /** 本项目数据 */ + public static final int DATA_SCOPE_PROJECT = 4; + + private PermissionConstants() { + } +} diff --git a/src/main/java/cn/yinlihupo/common/util/SecurityUtils.java b/src/main/java/cn/yinlihupo/common/util/SecurityUtils.java new file mode 100644 index 0000000..6873fb7 --- /dev/null +++ b/src/main/java/cn/yinlihupo/common/util/SecurityUtils.java @@ -0,0 +1,125 @@ +package cn.yinlihupo.common.util; + +import cn.dev33.satoken.session.SaSession; +import cn.dev33.satoken.stp.StpUtil; +import cn.yinlihupo.common.constant.PermissionConstants; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * 安全工具类 (Sa-Token 版本) + * 提供获取当前用户、权限校验等方法 + */ +public class SecurityUtils { + + /** + * 获取当前登录用户ID + */ + public static Long getCurrentUserId() { + try { + Object loginId = StpUtil.getLoginIdDefaultNull(); + if (loginId == null) { + return null; + } + return Long.valueOf(loginId.toString()); + } catch (Exception e) { + return null; + } + } + + /** + * 获取当前用户角色列表 + */ + @SuppressWarnings("unchecked") + public static Set getCurrentUserRoles() { + try { + SaSession session = StpUtil.getSession(); + Object roles = session.get("roles"); + if (roles instanceof Set) { + return (Set) roles; + } + if (roles instanceof List) { + return Set.copyOf((List) roles); + } + } catch (Exception e) { + // 未登录或会话不存在 + } + return Collections.emptySet(); + } + + /** + * 判断当前用户是否为系统管理员 + */ + public static boolean isAdmin() { + return hasRole(PermissionConstants.ROLE_ADMIN); + } + + /** + * 判断当前用户是否拥有指定角色 + */ + public static boolean hasRole(String role) { + return StpUtil.hasRole(role); + } + + /** + * 判断当前用户是否拥有任意一个指定角色 + */ + public static boolean hasAnyRole(String... roles) { + for (String role : roles) { + if (StpUtil.hasRole(role)) { + return true; + } + } + return false; + } + + /** + * 判断当前用户是否拥有指定权限 + */ + public static boolean hasPermission(String permission) { + return StpUtil.hasPermission(permission); + } + + /** + * 判断当前用户是否拥有任意一个指定权限 + */ + public static boolean hasAnyPermission(String... permissions) { + for (String permission : permissions) { + if (StpUtil.hasPermission(permission)) { + return true; + } + } + return false; + } + + /** + * 登录并缓存用户角色 + * + * @param userId 用户ID + * @param roles 角色列表 + */ + public static void login(Long userId, Set roles) { + StpUtil.login(userId); + SaSession session = StpUtil.getSession(); + session.set("roles", roles); + } + + /** + * 登出 + */ + public static void logout() { + StpUtil.logout(); + } + + /** + * 获取当前登录Token + */ + public static String getTokenValue() { + return StpUtil.getTokenValue(); + } + + private SecurityUtils() { + } +} diff --git a/src/main/java/cn/yinlihupo/domain/entity/SysPermission.java b/src/main/java/cn/yinlihupo/domain/entity/SysPermission.java new file mode 100644 index 0000000..41ed096 --- /dev/null +++ b/src/main/java/cn/yinlihupo/domain/entity/SysPermission.java @@ -0,0 +1,66 @@ +package cn.yinlihupo.domain.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 系统权限实体类 + * 对应数据库表: sys_permission + */ +@Data +@TableName("sys_permission") +public class SysPermission { + + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** 父级权限ID */ + private Long parentId; + + /** 权限编码 */ + private String permissionCode; + + /** 权限名称 */ + private String permissionName; + + /** 权限类型: 1-菜单, 2-按钮, 3-接口 */ + private Integer permissionType; + + /** 路由路径 */ + private String path; + + /** 组件路径 */ + private String component; + + /** 图标 */ + private String icon; + + /** 接口URL */ + private String apiUrl; + + /** 接口方法 */ + private String apiMethod; + + /** 排序 */ + private Integer sortOrder; + + /** 是否可见: 1-是, 0-否 */ + private Integer visible; + + /** 状态: 1-正常, 0-禁用 */ + private Integer status; + + /** 创建时间 */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** 更新时间 */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + /** 删除标记 */ + @TableLogic + private Integer deleted; +} diff --git a/src/main/java/cn/yinlihupo/domain/entity/SysRole.java b/src/main/java/cn/yinlihupo/domain/entity/SysRole.java new file mode 100644 index 0000000..53ddf8b --- /dev/null +++ b/src/main/java/cn/yinlihupo/domain/entity/SysRole.java @@ -0,0 +1,57 @@ +package cn.yinlihupo.domain.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 系统角色实体类 + * 对应数据库表: sys_role + */ +@Data +@TableName("sys_role") +public class SysRole { + + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** 角色编码 */ + private String roleCode; + + /** 角色名称 */ + private String roleName; + + /** 角色类型: system-系统角色, custom-自定义角色 */ + private String roleType; + + /** 角色描述 */ + private String description; + + /** 数据权限: 1-全部, 2-本部门, 3-本人 */ + private Integer dataScope; + + /** 排序 */ + private Integer sortOrder; + + /** 状态: 1-正常, 0-禁用 */ + private Integer status; + + /** 创建人 */ + private Long createBy; + + /** 创建时间 */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** 更新人 */ + private Long updateBy; + + /** 更新时间 */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + /** 删除标记 */ + @TableLogic + private Integer deleted; +} diff --git a/src/main/java/cn/yinlihupo/domain/entity/SysRolePermission.java b/src/main/java/cn/yinlihupo/domain/entity/SysRolePermission.java new file mode 100644 index 0000000..631ddc2 --- /dev/null +++ b/src/main/java/cn/yinlihupo/domain/entity/SysRolePermission.java @@ -0,0 +1,29 @@ +package cn.yinlihupo.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 角色权限关联实体类 + * 对应数据库表: sys_role_permission + */ +@Data +@TableName("sys_role_permission") +public class SysRolePermission { + + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** 角色ID */ + private Long roleId; + + /** 权限ID */ + private Long permissionId; + + /** 创建时间 */ + private LocalDateTime createTime; +} diff --git a/src/main/java/cn/yinlihupo/domain/entity/SysUser.java b/src/main/java/cn/yinlihupo/domain/entity/SysUser.java new file mode 100644 index 0000000..8dc2099 --- /dev/null +++ b/src/main/java/cn/yinlihupo/domain/entity/SysUser.java @@ -0,0 +1,82 @@ +package cn.yinlihupo.domain.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** + * 系统用户实体类 + * 对应数据库表: sys_user + */ +@Data +@TableName("sys_user") +public class SysUser { + + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** 用户名 */ + private String username; + + /** 密码(加密) */ + private String password; + + /** 真实姓名 */ + private String realName; + + /** 昵称 */ + private String nickname; + + /** 头像URL */ + private String avatar; + + /** 性别: 0-未知, 1-男, 2-女 */ + private Integer gender; + + /** 手机号 */ + private String phone; + + /** 邮箱 */ + private String email; + + /** 所属部门ID */ + private Long deptId; + + /** 职位 */ + private String position; + + /** 工号 */ + private String employeeNo; + + /** 入职日期 */ + private LocalDate entryDate; + + /** 状态: 1-正常, 0-禁用, 2-锁定 */ + private Integer status; + + /** 最后登录时间 */ + private LocalDateTime lastLoginTime; + + /** 最后登录IP */ + private String lastLoginIp; + + /** 创建人 */ + private Long createBy; + + /** 创建时间 */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** 更新人 */ + private Long updateBy; + + /** 更新时间 */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + /** 删除标记 */ + @TableLogic + private Integer deleted; +} diff --git a/src/main/java/cn/yinlihupo/domain/entity/SysUserRole.java b/src/main/java/cn/yinlihupo/domain/entity/SysUserRole.java new file mode 100644 index 0000000..4282dce --- /dev/null +++ b/src/main/java/cn/yinlihupo/domain/entity/SysUserRole.java @@ -0,0 +1,29 @@ +package cn.yinlihupo.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 用户角色关联实体类 + * 对应数据库表: sys_user_role + */ +@Data +@TableName("sys_user_role") +public class SysUserRole { + + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** 用户ID */ + private Long userId; + + /** 角色ID */ + private Long roleId; + + /** 创建时间 */ + private LocalDateTime createTime; +} diff --git a/src/main/java/cn/yinlihupo/mapper/FileAttachmentMapper.java b/src/main/java/cn/yinlihupo/mapper/FileAttachmentMapper.java index bc5b25b..69f9ab3 100644 --- a/src/main/java/cn/yinlihupo/mapper/FileAttachmentMapper.java +++ b/src/main/java/cn/yinlihupo/mapper/FileAttachmentMapper.java @@ -3,10 +3,31 @@ package cn.yinlihupo.mapper; import cn.yinlihupo.domain.entity.FileAttachment; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; /** * 文件附件Mapper接口 */ @Mapper public interface FileAttachmentMapper extends BaseMapper { + + /** + * 查询某业务实体的附件列表(含上传者信息) + */ + List> selectAttachmentsByRelated(@Param("relatedType") String relatedType, + @Param("relatedId") Long relatedId); + + /** + * 分页查询项目下所有附件(支持文件类型筛选) + */ + List> selectAttachmentsByProject(@Param("projectId") Long projectId, + @Param("fileType") String fileType); + + /** + * 统计用户上传文件数量和总大小 + */ + List> selectUploaderStats(@Param("projectId") Long projectId); } diff --git a/src/main/java/cn/yinlihupo/mapper/ProjectMapper.java b/src/main/java/cn/yinlihupo/mapper/ProjectMapper.java index d316b26..6f68a21 100644 --- a/src/main/java/cn/yinlihupo/mapper/ProjectMapper.java +++ b/src/main/java/cn/yinlihupo/mapper/ProjectMapper.java @@ -3,10 +3,46 @@ package cn.yinlihupo.mapper; import cn.yinlihupo.domain.entity.Project; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; /** * 项目Mapper接口 */ @Mapper public interface ProjectMapper extends BaseMapper { + + /** + * 分页查询项目列表(支持多条件筛选) + */ + List selectPageList(@Param("status") String status, + @Param("priority") String priority, + @Param("riskLevel") String riskLevel, + @Param("managerId") Long managerId, + @Param("keyword") String keyword, + @Param("planStartDateFrom") LocalDate planStartDateFrom, + @Param("planStartDateTo") LocalDate planStartDateTo); + + /** + * 查询指定用户有权限的项目列表(仅自己管理或参与的) + */ + List selectProjectsByUserId(@Param("userId") Long userId); + + /** + * 查询项目详情 + */ + Project selectDetailById(@Param("projectId") Long projectId); + + /** + * 统计各状态项目数量 + */ + List> countByStatus(); + + /** + * 查询即将超期的项目(N天内) + */ + List selectAboutToExpire(@Param("days") int days); } diff --git a/src/main/java/cn/yinlihupo/mapper/ProjectMemberMapper.java b/src/main/java/cn/yinlihupo/mapper/ProjectMemberMapper.java index 3b8cc91..fe99ac2 100644 --- a/src/main/java/cn/yinlihupo/mapper/ProjectMemberMapper.java +++ b/src/main/java/cn/yinlihupo/mapper/ProjectMemberMapper.java @@ -3,10 +3,30 @@ package cn.yinlihupo.mapper; import cn.yinlihupo.domain.entity.ProjectMember; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; /** * 项目成员Mapper接口 */ @Mapper public interface ProjectMemberMapper extends BaseMapper { + + /** + * 查询项目成员列表(含用户详情) + */ + List> selectMembersWithUser(@Param("projectId") Long projectId); + + /** + * 查询用户在某项目中的角色 + */ + String selectRoleByUserAndProject(@Param("projectId") Long projectId, + @Param("userId") Long userId); + + /** + * 统计某用户参与的项目数量(按角色分组) + */ + List> countUserProjects(@Param("userId") Long userId); } diff --git a/src/main/java/cn/yinlihupo/mapper/ProjectMilestoneMapper.java b/src/main/java/cn/yinlihupo/mapper/ProjectMilestoneMapper.java index 0148368..cec9e44 100644 --- a/src/main/java/cn/yinlihupo/mapper/ProjectMilestoneMapper.java +++ b/src/main/java/cn/yinlihupo/mapper/ProjectMilestoneMapper.java @@ -3,10 +3,36 @@ package cn.yinlihupo.mapper; import cn.yinlihupo.domain.entity.ProjectMilestone; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; /** * 项目里程碑Mapper接口 */ @Mapper public interface ProjectMilestoneMapper extends BaseMapper { + + /** + * 查询项目里程碑列表(含任务统计) + */ + List> selectMilestoneListWithStats(@Param("projectId") Long projectId, + @Param("status") String status); + + /** + * 查询已延期的关键里程碑 + */ + List selectDelayedKeyMilestones(@Param("projectId") Long projectId); + + /** + * 查询即将到期的里程碑(N天内) + */ + List selectUpcomingMilestones(@Param("projectId") Long projectId, + @Param("days") int days); + + /** + * 查询里程碑完成进度统计 + */ + Map selectMilestoneProgressSummary(@Param("projectId") Long projectId); } diff --git a/src/main/java/cn/yinlihupo/mapper/ResourceMapper.java b/src/main/java/cn/yinlihupo/mapper/ResourceMapper.java index c61941e..b286374 100644 --- a/src/main/java/cn/yinlihupo/mapper/ResourceMapper.java +++ b/src/main/java/cn/yinlihupo/mapper/ResourceMapper.java @@ -3,10 +3,38 @@ package cn.yinlihupo.mapper; import cn.yinlihupo.domain.entity.Resource; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; /** * 资源Mapper接口 */ @Mapper public interface ResourceMapper extends BaseMapper { + + /** + * 分页查询资源列表(含负责人信息,支持多条件筛选) + */ + List> selectResourcePageWithResponsible(@Param("projectId") Long projectId, + @Param("resourceType") String resourceType, + @Param("status") String status, + @Param("keyword") String keyword); + + /** + * 查询资源预算汇总(按类型统计) + */ + List> selectResourceBudgetSummary(@Param("projectId") Long projectId); + + /** + * 查询即将到位但尚未到位的资源(N天内) + */ + List selectPendingArrivalResources(@Param("projectId") Long projectId, + @Param("days") int days); + + /** + * 查询待审批的资源申请 + */ + List> selectPendingApprovalResources(@Param("projectId") Long projectId); } diff --git a/src/main/java/cn/yinlihupo/mapper/RiskMapper.java b/src/main/java/cn/yinlihupo/mapper/RiskMapper.java index eed53da..a11c2b7 100644 --- a/src/main/java/cn/yinlihupo/mapper/RiskMapper.java +++ b/src/main/java/cn/yinlihupo/mapper/RiskMapper.java @@ -3,10 +3,38 @@ package cn.yinlihupo.mapper; import cn.yinlihupo.domain.entity.Risk; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; /** * 风险Mapper接口 */ @Mapper public interface RiskMapper extends BaseMapper { + + /** + * 分页查询风险列表(含负责人信息,支持多条件筛选) + */ + List> selectRiskPageWithOwner(@Param("projectId") Long projectId, + @Param("category") String category, + @Param("riskLevel") String riskLevel, + @Param("status") String status, + @Param("keyword") String keyword); + + /** + * 查询未解决的高/严重风险(用于预警) + */ + List> selectHighRisksUnresolved(@Param("projectId") Long projectId); + + /** + * 按风险等级统计各分类数量 + */ + List> countRiskByCategoryAndLevel(@Param("projectId") Long projectId); + + /** + * 查询风险处理趋势(近6个月) + */ + List> selectRiskTrend(@Param("projectId") Long projectId); } diff --git a/src/main/java/cn/yinlihupo/mapper/SysPermissionMapper.java b/src/main/java/cn/yinlihupo/mapper/SysPermissionMapper.java new file mode 100644 index 0000000..8073f8b --- /dev/null +++ b/src/main/java/cn/yinlihupo/mapper/SysPermissionMapper.java @@ -0,0 +1,30 @@ +package cn.yinlihupo.mapper; + +import cn.yinlihupo.domain.entity.SysPermission; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 系统权限Mapper接口 + */ +@Mapper +public interface SysPermissionMapper extends BaseMapper { + + /** + * 根据角色编码列表查询权限编码列表 + */ + List selectPermissionCodesByRoleCodes(@Param("roleCodes") List roleCodes); + + /** + * 查询菜单树(含按钮权限) + */ + List selectMenuTree(); + + /** + * 根据角色ID查询权限列表 + */ + List selectPermsByRoleId(@Param("roleId") Long roleId); +} diff --git a/src/main/java/cn/yinlihupo/mapper/SysRoleMapper.java b/src/main/java/cn/yinlihupo/mapper/SysRoleMapper.java new file mode 100644 index 0000000..807c1ce --- /dev/null +++ b/src/main/java/cn/yinlihupo/mapper/SysRoleMapper.java @@ -0,0 +1,12 @@ +package cn.yinlihupo.mapper; + +import cn.yinlihupo.domain.entity.SysRole; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * 系统角色Mapper接口 + */ +@Mapper +public interface SysRoleMapper extends BaseMapper { +} diff --git a/src/main/java/cn/yinlihupo/mapper/SysRolePermissionMapper.java b/src/main/java/cn/yinlihupo/mapper/SysRolePermissionMapper.java new file mode 100644 index 0000000..ec9d21a --- /dev/null +++ b/src/main/java/cn/yinlihupo/mapper/SysRolePermissionMapper.java @@ -0,0 +1,12 @@ +package cn.yinlihupo.mapper; + +import cn.yinlihupo.domain.entity.SysRolePermission; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * 角色权限关联Mapper接口 + */ +@Mapper +public interface SysRolePermissionMapper extends BaseMapper { +} diff --git a/src/main/java/cn/yinlihupo/mapper/SysUserMapper.java b/src/main/java/cn/yinlihupo/mapper/SysUserMapper.java new file mode 100644 index 0000000..806245d --- /dev/null +++ b/src/main/java/cn/yinlihupo/mapper/SysUserMapper.java @@ -0,0 +1,37 @@ +package cn.yinlihupo.mapper; + +import cn.yinlihupo.domain.entity.SysUser; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 系统用户Mapper接口 + */ +@Mapper +public interface SysUserMapper extends BaseMapper { + + /** + * 根据用户ID查询角色编码列表 + */ + List selectRoleCodesByUserId(@Param("userId") Long userId); + + /** + * 根据用户名查询用户 + */ + SysUser selectByUsername(@Param("username") String username); + + /** + * 分页查询用户列表(支持按部门、状态、关键字筛选) + */ + List selectPageList(@Param("deptId") Long deptId, + @Param("status") Integer status, + @Param("keyword") String keyword); + + /** + * 查询项目成员用户详情列表 + */ + List selectUsersByProjectId(@Param("projectId") Long projectId); +} diff --git a/src/main/java/cn/yinlihupo/mapper/SysUserRoleMapper.java b/src/main/java/cn/yinlihupo/mapper/SysUserRoleMapper.java new file mode 100644 index 0000000..94ed900 --- /dev/null +++ b/src/main/java/cn/yinlihupo/mapper/SysUserRoleMapper.java @@ -0,0 +1,12 @@ +package cn.yinlihupo.mapper; + +import cn.yinlihupo.domain.entity.SysUserRole; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * 用户角色关联Mapper接口 + */ +@Mapper +public interface SysUserRoleMapper extends BaseMapper { +} diff --git a/src/main/java/cn/yinlihupo/mapper/TaskMapper.java b/src/main/java/cn/yinlihupo/mapper/TaskMapper.java index 2c9043a..5f062b0 100644 --- a/src/main/java/cn/yinlihupo/mapper/TaskMapper.java +++ b/src/main/java/cn/yinlihupo/mapper/TaskMapper.java @@ -3,10 +3,45 @@ package cn.yinlihupo.mapper; import cn.yinlihupo.domain.entity.Task; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; /** * 任务Mapper接口 */ @Mapper public interface TaskMapper extends BaseMapper { + + /** + * 分页查询任务列表(含负责人信息,支持多条件筛选) + */ + List> selectTaskPageWithAssignee(@Param("projectId") Long projectId, + @Param("milestoneId") Long milestoneId, + @Param("assigneeId") Long assigneeId, + @Param("status") String status, + @Param("priority") String priority, + @Param("keyword") String keyword); + + /** + * 查询我的待办任务(按用户ID) + */ + List selectMyTasks(@Param("userId") Long userId, + @Param("projectId") Long projectId); + + /** + * 查询超期未完成的任务 + */ + List> selectOverdueTasks(@Param("projectId") Long projectId); + + /** + * 查询任务的前置依赖关系 + */ + List> selectDependencies(@Param("taskId") Long taskId); + + /** + * 统计项目任务状态分布 + */ + List> countTasksByStatus(@Param("projectId") Long projectId); } diff --git a/src/main/java/cn/yinlihupo/service/system/ProjectPermissionService.java b/src/main/java/cn/yinlihupo/service/system/ProjectPermissionService.java new file mode 100644 index 0000000..0785908 --- /dev/null +++ b/src/main/java/cn/yinlihupo/service/system/ProjectPermissionService.java @@ -0,0 +1,160 @@ +package cn.yinlihupo.service.system; + +import java.util.Set; + +/** + * 项目权限服务接口 + * 提供项目级别的权限校验功能 + */ +public interface ProjectPermissionService { + + /** + * 判断用户是否为系统管理员 + * + * @param userId 用户ID + * @return true-是管理员 + */ + boolean isAdmin(Long userId); + + /** + * 获取用户在项目中的角色 + * + * @param userId 用户ID + * @param projectId 项目ID + * @return 项目角色编码(manager/leader/member/observer),未参与项目返回null + */ + String getUserProjectRole(Long userId, Long projectId); + + /** + * 判断用户是否参与项目 + * + * @param userId 用户ID + * @param projectId 项目ID + * @return true-是项目成员 + */ + boolean isProjectMember(Long userId, Long projectId); + + /** + * 判断用户是否为项目经理 + * + * @param userId 用户ID + * @param projectId 项目ID + * @return true-是项目经理 + */ + boolean isProjectManager(Long userId, Long projectId); + + /** + * 判断用户是否为项目负责人或经理 + * + * @param userId 用户ID + * @param projectId 项目ID + * @return true-是负责人或经理 + */ + boolean isProjectManagerOrLeader(Long userId, Long projectId); + + /** + * 获取用户在项目中拥有的所有权限编码 + * + * @param userId 用户ID + * @param projectId 项目ID + * @return 权限编码集合 + */ + Set getUserProjectPermissions(Long userId, Long projectId); + + /** + * 校验用户是否拥有指定项目权限 + * + * @param userId 用户ID + * @param projectId 项目ID + * @param permission 权限编码 + * @return true-有权限 + */ + boolean hasPermission(Long userId, Long projectId, String permission); + + /** + * 校验用户是否拥有任意一个指定权限 + * + * @param userId 用户ID + * @param projectId 项目ID + * @param permissions 权限编码数组 + * @return true-有权限 + */ + boolean hasAnyPermission(Long userId, Long projectId, String... permissions); + + // ==================== 便捷权限校验方法 ==================== + + /** + * 是否有查看项目的权限 + */ + boolean canViewProject(Long userId, Long projectId); + + /** + * 是否有管理项目的权限(编辑、删除) + */ + boolean canManageProject(Long userId, Long projectId); + + /** + * 是否有查看风险的权限 + */ + boolean canViewRisk(Long userId, Long projectId); + + /** + * 是否有管控风险的权限(创建、编辑、删除) + */ + boolean canManageRisk(Long userId, Long projectId); + + /** + * 是否有查看资源的权限 + */ + boolean canViewResource(Long userId, Long projectId); + + /** + * 是否有管控资源的权限(申请、审批、分配) + */ + boolean canManageResource(Long userId, Long projectId); + + /** + * 是否有查看里程碑的权限 + */ + boolean canViewMilestone(Long userId, Long projectId); + + /** + * 是否有推进里程碑的权限 + */ + boolean canPushMilestone(Long userId, Long projectId); + + /** + * 是否有查看任务工单的权限 + */ + boolean canViewTask(Long userId, Long projectId); + + /** + * 是否有分配任务工单的权限 + */ + boolean canAssignTask(Long userId, Long projectId); + + /** + * 是否有处理任务工单的权限 + */ + boolean canProcessTask(Long userId, Long projectId); + + /** + * 是否有查看日报的权限 + */ + boolean canViewReport(Long userId, Long projectId); + + /** + * 是否有上传日报的权限 + */ + boolean canCreateReport(Long userId, Long projectId); + + /** + * 是否有查看知识库的权限 + */ + boolean canViewKnowledgeBase(Long userId, Long projectId); + + /** + * 是否有上传知识库的权限 + */ + boolean canCreateKnowledgeBase(Long userId, Long projectId); +} diff --git a/src/main/java/cn/yinlihupo/service/system/impl/ProjectPermissionServiceImpl.java b/src/main/java/cn/yinlihupo/service/system/impl/ProjectPermissionServiceImpl.java new file mode 100644 index 0000000..79c8e82 --- /dev/null +++ b/src/main/java/cn/yinlihupo/service/system/impl/ProjectPermissionServiceImpl.java @@ -0,0 +1,278 @@ +package cn.yinlihupo.service.system.impl; + +import cn.yinlihupo.common.constant.PermissionConstants; +import cn.yinlihupo.domain.entity.Project; +import cn.yinlihupo.domain.entity.ProjectMember; +import cn.yinlihupo.mapper.ProjectMapper; +import cn.yinlihupo.mapper.ProjectMemberMapper; +import cn.yinlihupo.mapper.SysUserMapper; +import cn.yinlihupo.service.system.ProjectPermissionService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * 项目权限服务实现类 + */ +@Service +@RequiredArgsConstructor +public class ProjectPermissionServiceImpl implements ProjectPermissionService { + + private final SysUserMapper sysUserMapper; + private final ProjectMemberMapper projectMemberMapper; + private final ProjectMapper projectMapper; + + @Override + public boolean isAdmin(Long userId) { + if (userId == null) { + return false; + } + List roleCodes = sysUserMapper.selectRoleCodesByUserId(userId); + return roleCodes.contains(PermissionConstants.ROLE_ADMIN); + } + + @Override + public String getUserProjectRole(Long userId, Long projectId) { + if (userId == null || projectId == null) { + return null; + } + + // 先检查是否为项目经理(project.manager_id) + Project project = projectMapper.selectById(projectId); + if (project != null && userId.equals(project.getManagerId())) { + return PermissionConstants.PROJECT_ROLE_MANAGER; + } + + // 查询项目成员表 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ProjectMember::getProjectId, projectId) + .eq(ProjectMember::getUserId, userId) + .eq(ProjectMember::getStatus, 1) + .eq(ProjectMember::getDeleted, 0); + ProjectMember member = projectMemberMapper.selectOne(wrapper); + + return member != null ? member.getRoleCode() : null; + } + + @Override + public boolean isProjectMember(Long userId, Long projectId) { + return getUserProjectRole(userId, projectId) != null; + } + + @Override + public boolean isProjectManager(Long userId, Long projectId) { + String role = getUserProjectRole(userId, projectId); + return PermissionConstants.PROJECT_ROLE_MANAGER.equals(role); + } + + @Override + public boolean isProjectManagerOrLeader(Long userId, Long projectId) { + String role = getUserProjectRole(userId, projectId); + return PermissionConstants.PROJECT_ROLE_MANAGER.equals(role) + || PermissionConstants.PROJECT_ROLE_LEADER.equals(role); + } + + @Override + public Set getUserProjectPermissions(Long userId, Long projectId) { + Set permissions = new HashSet<>(); + + if (userId == null || projectId == null) { + return permissions; + } + + // 系统管理员拥有所有权限 + if (isAdmin(userId)) { + permissions.add(PermissionConstants.PERM_PROJECT_VIEW); + permissions.add(PermissionConstants.PERM_PROJECT_MANAGE); + permissions.add(PermissionConstants.PERM_RISK_VIEW); + permissions.add(PermissionConstants.PERM_RISK_MANAGE); + permissions.add(PermissionConstants.PERM_RESOURCE_VIEW); + permissions.add(PermissionConstants.PERM_RESOURCE_MANAGE); + permissions.add(PermissionConstants.PERM_MILESTONE_VIEW); + permissions.add(PermissionConstants.PERM_MILESTONE_PUSH); + permissions.add(PermissionConstants.PERM_TASK_VIEW); + permissions.add(PermissionConstants.PERM_TASK_ASSIGN); + permissions.add(PermissionConstants.PERM_TASK_PROCESS); + permissions.add(PermissionConstants.PERM_REPORT_VIEW); + permissions.add(PermissionConstants.PERM_REPORT_CREATE); + permissions.add(PermissionConstants.PERM_KB_VIEW); + permissions.add(PermissionConstants.PERM_KB_CREATE); + return permissions; + } + + String projectRole = getUserProjectRole(userId, projectId); + if (projectRole == null) { + return permissions; + } + + // 根据项目角色分配权限 + switch (projectRole) { + case PermissionConstants.PROJECT_ROLE_MANAGER: + // 项目经理拥有项目全部管理权限 + permissions.add(PermissionConstants.PERM_PROJECT_VIEW); + permissions.add(PermissionConstants.PERM_PROJECT_MANAGE); + permissions.add(PermissionConstants.PERM_RISK_VIEW); + permissions.add(PermissionConstants.PERM_RISK_MANAGE); + permissions.add(PermissionConstants.PERM_RESOURCE_VIEW); + permissions.add(PermissionConstants.PERM_RESOURCE_MANAGE); + permissions.add(PermissionConstants.PERM_MILESTONE_VIEW); + permissions.add(PermissionConstants.PERM_MILESTONE_PUSH); + permissions.add(PermissionConstants.PERM_TASK_VIEW); + permissions.add(PermissionConstants.PERM_TASK_ASSIGN); + permissions.add(PermissionConstants.PERM_TASK_PROCESS); + permissions.add(PermissionConstants.PERM_REPORT_VIEW); + permissions.add(PermissionConstants.PERM_REPORT_CREATE); + permissions.add(PermissionConstants.PERM_KB_VIEW); + permissions.add(PermissionConstants.PERM_KB_CREATE); + break; + + case PermissionConstants.PROJECT_ROLE_LEADER: + // 负责人拥有大部分管理权限,但不能删除项目 + permissions.add(PermissionConstants.PERM_PROJECT_VIEW); + permissions.add(PermissionConstants.PERM_RISK_VIEW); + permissions.add(PermissionConstants.PERM_RISK_MANAGE); + permissions.add(PermissionConstants.PERM_RESOURCE_VIEW); + permissions.add(PermissionConstants.PERM_RESOURCE_MANAGE); + permissions.add(PermissionConstants.PERM_MILESTONE_VIEW); + permissions.add(PermissionConstants.PERM_MILESTONE_PUSH); + permissions.add(PermissionConstants.PERM_TASK_VIEW); + permissions.add(PermissionConstants.PERM_TASK_ASSIGN); + permissions.add(PermissionConstants.PERM_TASK_PROCESS); + permissions.add(PermissionConstants.PERM_REPORT_VIEW); + permissions.add(PermissionConstants.PERM_REPORT_CREATE); + permissions.add(PermissionConstants.PERM_KB_VIEW); + permissions.add(PermissionConstants.PERM_KB_CREATE); + break; + + case PermissionConstants.PROJECT_ROLE_MEMBER: + // 成员拥有基本操作权限 + permissions.add(PermissionConstants.PERM_PROJECT_VIEW); + permissions.add(PermissionConstants.PERM_RISK_VIEW); + permissions.add(PermissionConstants.PERM_RESOURCE_VIEW); + permissions.add(PermissionConstants.PERM_MILESTONE_VIEW); + permissions.add(PermissionConstants.PERM_TASK_VIEW); + permissions.add(PermissionConstants.PERM_TASK_PROCESS); + permissions.add(PermissionConstants.PERM_REPORT_VIEW); + permissions.add(PermissionConstants.PERM_REPORT_CREATE); + permissions.add(PermissionConstants.PERM_KB_VIEW); + permissions.add(PermissionConstants.PERM_KB_CREATE); + break; + + case PermissionConstants.PROJECT_ROLE_OBSERVER: + // 观察者只有查看权限 + permissions.add(PermissionConstants.PERM_PROJECT_VIEW); + permissions.add(PermissionConstants.PERM_RISK_VIEW); + permissions.add(PermissionConstants.PERM_RESOURCE_VIEW); + permissions.add(PermissionConstants.PERM_MILESTONE_VIEW); + permissions.add(PermissionConstants.PERM_TASK_VIEW); + permissions.add(PermissionConstants.PERM_REPORT_VIEW); + permissions.add(PermissionConstants.PERM_KB_VIEW); + break; + } + + return permissions; + } + + @Override + public boolean hasPermission(Long userId, Long projectId, String permission) { + if (permission == null) { + return false; + } + return getUserProjectPermissions(userId, projectId).contains(permission); + } + + @Override + public boolean hasAnyPermission(Long userId, Long projectId, String... permissions) { + if (permissions == null || permissions.length == 0) { + return false; + } + Set userPermissions = getUserProjectPermissions(userId, projectId); + for (String permission : permissions) { + if (userPermissions.contains(permission)) { + return true; + } + } + return false; + } + + // ==================== 便捷权限校验方法实现 ==================== + + @Override + public boolean canViewProject(Long userId, Long projectId) { + return hasPermission(userId, projectId, PermissionConstants.PERM_PROJECT_VIEW); + } + + @Override + public boolean canManageProject(Long userId, Long projectId) { + return hasPermission(userId, projectId, PermissionConstants.PERM_PROJECT_MANAGE); + } + + @Override + public boolean canViewRisk(Long userId, Long projectId) { + return hasPermission(userId, projectId, PermissionConstants.PERM_RISK_VIEW); + } + + @Override + public boolean canManageRisk(Long userId, Long projectId) { + return hasPermission(userId, projectId, PermissionConstants.PERM_RISK_MANAGE); + } + + @Override + public boolean canViewResource(Long userId, Long projectId) { + return hasPermission(userId, projectId, PermissionConstants.PERM_RESOURCE_VIEW); + } + + @Override + public boolean canManageResource(Long userId, Long projectId) { + return hasPermission(userId, projectId, PermissionConstants.PERM_RESOURCE_MANAGE); + } + + @Override + public boolean canViewMilestone(Long userId, Long projectId) { + return hasPermission(userId, projectId, PermissionConstants.PERM_MILESTONE_VIEW); + } + + @Override + public boolean canPushMilestone(Long userId, Long projectId) { + return hasPermission(userId, projectId, PermissionConstants.PERM_MILESTONE_PUSH); + } + + @Override + public boolean canViewTask(Long userId, Long projectId) { + return hasPermission(userId, projectId, PermissionConstants.PERM_TASK_VIEW); + } + + @Override + public boolean canAssignTask(Long userId, Long projectId) { + return hasPermission(userId, projectId, PermissionConstants.PERM_TASK_ASSIGN); + } + + @Override + public boolean canProcessTask(Long userId, Long projectId) { + return hasPermission(userId, projectId, PermissionConstants.PERM_TASK_PROCESS); + } + + @Override + public boolean canViewReport(Long userId, Long projectId) { + return hasPermission(userId, projectId, PermissionConstants.PERM_REPORT_VIEW); + } + + @Override + public boolean canCreateReport(Long userId, Long projectId) { + return hasPermission(userId, projectId, PermissionConstants.PERM_REPORT_CREATE); + } + + @Override + public boolean canViewKnowledgeBase(Long userId, Long projectId) { + return hasPermission(userId, projectId, PermissionConstants.PERM_KB_VIEW); + } + + @Override + public boolean canCreateKnowledgeBase(Long userId, Long projectId) { + return hasPermission(userId, projectId, PermissionConstants.PERM_KB_CREATE); + } +} diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml index 9d7f755..3b5e3bf 100644 --- a/src/main/resources/application-dev.yaml +++ b/src/main/resources/application-dev.yaml @@ -19,7 +19,7 @@ spring: # MyBatis Plus 配置 mybatis-plus: mapper-locations: classpath*:/mapper/**/*.xml - type-aliases-package: cn.yinlihupo.ylhpaiprojectmanager.entity + type-aliases-package: cn.yinlihupo.domain.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index db3fe60..dce7a6d 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -10,4 +10,27 @@ spring: # 公共配置 server: - port: 8080 \ No newline at end of file + port: 8080 + +# Sa-Token 配置 +sa-token: + # Token 名称 + token-name: Authorization + # Token 有效期(单位:秒)默认30天,-1代表永不过期 + timeout: 2592000 + # Token 临时有效期(指定时间内无操作就视为token过期)单位:秒 + activity-timeout: -1 + # 是否允许同一账号并发登录(为true时允许一起登录,为false时新登录挤掉旧登录) + is-concurrent: true + # 在多人登录同一账号时,是否共用一个token(为true时所有登录共用一个token,为false时每次登录新建一个token) + is-share: false + # Token 风格 + token-style: uuid + # 是否输出操作日志 + is-log: true + # 是否从cookie中读取token + is-read-cookie: false + # 是否从请求体中读取token + is-read-body: false + # 是否从header中读取token + is-read-header: true \ No newline at end of file diff --git a/src/main/resources/mapper/FileAttachmentMapper.xml b/src/main/resources/mapper/FileAttachmentMapper.xml new file mode 100644 index 0000000..6ee1a5a --- /dev/null +++ b/src/main/resources/mapper/FileAttachmentMapper.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/mapper/ProjectMapper.xml b/src/main/resources/mapper/ProjectMapper.xml new file mode 100644 index 0000000..ab05829 --- /dev/null +++ b/src/main/resources/mapper/ProjectMapper.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/mapper/ProjectMemberMapper.xml b/src/main/resources/mapper/ProjectMemberMapper.xml new file mode 100644 index 0000000..d904a71 --- /dev/null +++ b/src/main/resources/mapper/ProjectMemberMapper.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/mapper/ProjectMilestoneMapper.xml b/src/main/resources/mapper/ProjectMilestoneMapper.xml new file mode 100644 index 0000000..a58bc35 --- /dev/null +++ b/src/main/resources/mapper/ProjectMilestoneMapper.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/mapper/ResourceMapper.xml b/src/main/resources/mapper/ResourceMapper.xml new file mode 100644 index 0000000..ed9677b --- /dev/null +++ b/src/main/resources/mapper/ResourceMapper.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/mapper/RiskMapper.xml b/src/main/resources/mapper/RiskMapper.xml new file mode 100644 index 0000000..abbe876 --- /dev/null +++ b/src/main/resources/mapper/RiskMapper.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/mapper/SysPermissionMapper.xml b/src/main/resources/mapper/SysPermissionMapper.xml new file mode 100644 index 0000000..6ed805d --- /dev/null +++ b/src/main/resources/mapper/SysPermissionMapper.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/mapper/SysUserMapper.xml b/src/main/resources/mapper/SysUserMapper.xml new file mode 100644 index 0000000..aa8dd33 --- /dev/null +++ b/src/main/resources/mapper/SysUserMapper.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/mapper/TaskMapper.xml b/src/main/resources/mapper/TaskMapper.xml new file mode 100644 index 0000000..09214cb --- /dev/null +++ b/src/main/resources/mapper/TaskMapper.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +