feat(core): 新增项目及相关功能的数据访问层和权限控制切面

- 添加多个Mapper接口及XML文件支持项目、成员、里程碑、任务、风险、资源、
  文件附件等模块的数据操作和查询功能,支持复杂查询与统计
- 新增Sa-Token权限配置,集成统一认证管理
- 引入权限常量类,定义系统角色、项目角色及权限编码标准
- 新增项目权限校验切面,实现基于注解的项目权限和角色校验逻辑
- 更新配置文件和依赖,集成MyBatis Plus、MinIO、Spring AI及文档解析相关库
- 调整MyBatis配置的类型别名包路径,统一领域实体引用路径
This commit is contained in:
2026-03-27 16:01:00 +08:00
parent a5e62e6885
commit 15b0013cd0
38 changed files with 2424 additions and 2 deletions

View File

@@ -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 "无权访问该项目资源";
}

View File

@@ -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 "无权执行该操作";
}

View File

@@ -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;
}
}

View File

@@ -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();
});
}
}

View File

@@ -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<String> getPermissionList(Object loginId, String loginType) {
// 项目权限通过 ProjectPermissionService 动态校验
// 这里返回系统级别的权限
try {
Long userId = Long.valueOf(loginId.toString());
List<String> 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<String> 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<>();
}
}
}

View File

@@ -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() {
}
}

View File

@@ -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<String> getCurrentUserRoles() {
try {
SaSession session = StpUtil.getSession();
Object roles = session.get("roles");
if (roles instanceof Set) {
return (Set<String>) roles;
}
if (roles instanceof List) {
return Set.copyOf((List<String>) 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<String> 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() {
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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<FileAttachment> {
/**
* 查询某业务实体的附件列表(含上传者信息)
*/
List<Map<String, Object>> selectAttachmentsByRelated(@Param("relatedType") String relatedType,
@Param("relatedId") Long relatedId);
/**
* 分页查询项目下所有附件(支持文件类型筛选)
*/
List<Map<String, Object>> selectAttachmentsByProject(@Param("projectId") Long projectId,
@Param("fileType") String fileType);
/**
* 统计用户上传文件数量和总大小
*/
List<Map<String, Object>> selectUploaderStats(@Param("projectId") Long projectId);
}

View File

@@ -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<Project> {
/**
* 分页查询项目列表(支持多条件筛选)
*/
List<Project> 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<Project> selectProjectsByUserId(@Param("userId") Long userId);
/**
* 查询项目详情
*/
Project selectDetailById(@Param("projectId") Long projectId);
/**
* 统计各状态项目数量
*/
List<Map<String, Object>> countByStatus();
/**
* 查询即将超期的项目N天内
*/
List<Project> selectAboutToExpire(@Param("days") int days);
}

View File

@@ -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<ProjectMember> {
/**
* 查询项目成员列表(含用户详情)
*/
List<Map<String, Object>> selectMembersWithUser(@Param("projectId") Long projectId);
/**
* 查询用户在某项目中的角色
*/
String selectRoleByUserAndProject(@Param("projectId") Long projectId,
@Param("userId") Long userId);
/**
* 统计某用户参与的项目数量(按角色分组)
*/
List<Map<String, Object>> countUserProjects(@Param("userId") Long userId);
}

View File

@@ -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<ProjectMilestone> {
/**
* 查询项目里程碑列表(含任务统计)
*/
List<Map<String, Object>> selectMilestoneListWithStats(@Param("projectId") Long projectId,
@Param("status") String status);
/**
* 查询已延期的关键里程碑
*/
List<ProjectMilestone> selectDelayedKeyMilestones(@Param("projectId") Long projectId);
/**
* 查询即将到期的里程碑N天内
*/
List<ProjectMilestone> selectUpcomingMilestones(@Param("projectId") Long projectId,
@Param("days") int days);
/**
* 查询里程碑完成进度统计
*/
Map<String, Object> selectMilestoneProgressSummary(@Param("projectId") Long projectId);
}

View File

@@ -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<Resource> {
/**
* 分页查询资源列表(含负责人信息,支持多条件筛选)
*/
List<Map<String, Object>> selectResourcePageWithResponsible(@Param("projectId") Long projectId,
@Param("resourceType") String resourceType,
@Param("status") String status,
@Param("keyword") String keyword);
/**
* 查询资源预算汇总(按类型统计)
*/
List<Map<String, Object>> selectResourceBudgetSummary(@Param("projectId") Long projectId);
/**
* 查询即将到位但尚未到位的资源N天内
*/
List<Resource> selectPendingArrivalResources(@Param("projectId") Long projectId,
@Param("days") int days);
/**
* 查询待审批的资源申请
*/
List<Map<String, Object>> selectPendingApprovalResources(@Param("projectId") Long projectId);
}

View File

@@ -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<Risk> {
/**
* 分页查询风险列表(含负责人信息,支持多条件筛选)
*/
List<Map<String, Object>> selectRiskPageWithOwner(@Param("projectId") Long projectId,
@Param("category") String category,
@Param("riskLevel") String riskLevel,
@Param("status") String status,
@Param("keyword") String keyword);
/**
* 查询未解决的高/严重风险(用于预警)
*/
List<Map<String, Object>> selectHighRisksUnresolved(@Param("projectId") Long projectId);
/**
* 按风险等级统计各分类数量
*/
List<Map<String, Object>> countRiskByCategoryAndLevel(@Param("projectId") Long projectId);
/**
* 查询风险处理趋势近6个月
*/
List<Map<String, Object>> selectRiskTrend(@Param("projectId") Long projectId);
}

View File

@@ -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<SysPermission> {
/**
* 根据角色编码列表查询权限编码列表
*/
List<String> selectPermissionCodesByRoleCodes(@Param("roleCodes") List<String> roleCodes);
/**
* 查询菜单树(含按钮权限)
*/
List<SysPermission> selectMenuTree();
/**
* 根据角色ID查询权限列表
*/
List<SysPermission> selectPermsByRoleId(@Param("roleId") Long roleId);
}

View File

@@ -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<SysRole> {
}

View File

@@ -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<SysRolePermission> {
}

View File

@@ -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<SysUser> {
/**
* 根据用户ID查询角色编码列表
*/
List<String> selectRoleCodesByUserId(@Param("userId") Long userId);
/**
* 根据用户名查询用户
*/
SysUser selectByUsername(@Param("username") String username);
/**
* 分页查询用户列表(支持按部门、状态、关键字筛选)
*/
List<SysUser> selectPageList(@Param("deptId") Long deptId,
@Param("status") Integer status,
@Param("keyword") String keyword);
/**
* 查询项目成员用户详情列表
*/
List<SysUser> selectUsersByProjectId(@Param("projectId") Long projectId);
}

View File

@@ -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<SysUserRole> {
}

View File

@@ -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<Task> {
/**
* 分页查询任务列表(含负责人信息,支持多条件筛选)
*/
List<Map<String, Object>> 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<Task> selectMyTasks(@Param("userId") Long userId,
@Param("projectId") Long projectId);
/**
* 查询超期未完成的任务
*/
List<Map<String, Object>> selectOverdueTasks(@Param("projectId") Long projectId);
/**
* 查询任务的前置依赖关系
*/
List<Map<String, Object>> selectDependencies(@Param("taskId") Long taskId);
/**
* 统计项目任务状态分布
*/
List<Map<String, Object>> countTasksByStatus(@Param("projectId") Long projectId);
}

View File

@@ -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<String> 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);
}

View File

@@ -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<String> 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<ProjectMember> 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<String> getUserProjectPermissions(Long userId, Long projectId) {
Set<String> 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<String> 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);
}
}