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

14
pom.xml
View File

@@ -125,6 +125,20 @@
<version>2.9.1</version>
</dependency>
<!-- Sa-Token 权限认证 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>1.39.0</version>
</dependency>
<!-- Sa-Token Redis 集成(可选,用于分布式环境) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-jackson</artifactId>
<version>1.39.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

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

View File

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

View File

@@ -10,4 +10,27 @@ spring:
# 公共配置
server:
port: 8080
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

View File

@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.yinlihupo.mapper.FileAttachmentMapper">
<!-- 通用 ResultMap -->
<resultMap id="BaseResultMap" type="FileAttachment">
<id column="id" property="id"/>
<result column="file_name" property="fileName"/>
<result column="original_name" property="originalName"/>
<result column="file_path" property="filePath"/>
<result column="file_url" property="fileUrl"/>
<result column="file_type" property="fileType"/>
<result column="file_size" property="fileSize"/>
<result column="storage_type" property="storageType"/>
<result column="related_type" property="relatedType"/>
<result column="related_id" property="relatedId"/>
<result column="uploader_id" property="uploaderId"/>
<result column="exif_data" property="exifData" typeHandler="cn.yinlihupo.common.handler.JsonbTypeHandler"/>
<result column="ai_analysis" property="aiAnalysis" typeHandler="cn.yinlihupo.common.handler.JsonbTypeHandler"/>
<result column="extra_data" property="extraData" typeHandler="cn.yinlihupo.common.handler.JsonbTypeHandler"/>
<result column="create_time" property="createTime"/>
<result column="deleted" property="deleted"/>
</resultMap>
<!-- 查询某业务实体的附件列表(含上传者信息) -->
<select id="selectAttachmentsByRelated" resultType="map">
SELECT fa.*,
u.real_name AS uploader_name,
u.avatar AS uploader_avatar
FROM file_attachment fa
LEFT JOIN sys_user u ON fa.uploader_id = u.id AND u.deleted = 0
WHERE fa.related_type = #{relatedType}
AND fa.related_id = #{relatedId}
AND fa.deleted = 0
ORDER BY fa.create_time DESC
</select>
<!-- 分页查询项目下所有附件(支持文件类型筛选) -->
<select id="selectAttachmentsByProject" resultType="map">
SELECT fa.*,
u.real_name AS uploader_name
FROM file_attachment fa
LEFT JOIN sys_user u ON fa.uploader_id = u.id AND u.deleted = 0
WHERE fa.deleted = 0
AND fa.related_type IN ('project', 'task', 'risk', 'report')
AND fa.related_id IN (
SELECT id FROM project WHERE id = #{projectId} AND deleted = 0
UNION ALL
SELECT id FROM task WHERE project_id = #{projectId} AND deleted = 0
UNION ALL
SELECT id FROM risk WHERE project_id = #{projectId} AND deleted = 0
)
<if test="fileType != null and fileType != ''">
AND fa.file_type LIKE CONCAT(#{fileType}, '%')
</if>
ORDER BY fa.create_time DESC
</select>
<!-- 统计用户上传文件数量和总大小 -->
<select id="selectUploaderStats" resultType="map">
SELECT uploader_id,
COUNT(*) AS file_count,
SUM(file_size) AS total_size
FROM file_attachment
WHERE deleted = 0
<if test="projectId != null">
AND related_id = #{projectId}
AND related_type = 'project'
</if>
GROUP BY uploader_id
ORDER BY file_count DESC
</select>
</mapper>

View File

@@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.yinlihupo.mapper.ProjectMapper">
<!-- 通用 ResultMap -->
<resultMap id="BaseResultMap" type="Project">
<id column="id" property="id"/>
<result column="project_code" property="projectCode"/>
<result column="project_name" property="projectName"/>
<result column="project_type" property="projectType"/>
<result column="description" property="description"/>
<result column="objectives" property="objectives"/>
<result column="manager_id" property="managerId"/>
<result column="sponsor_id" property="sponsorId"/>
<result column="plan_start_date" property="planStartDate"/>
<result column="plan_end_date" property="planEndDate"/>
<result column="actual_start_date" property="actualStartDate"/>
<result column="actual_end_date" property="actualEndDate"/>
<result column="budget" property="budget"/>
<result column="cost" property="cost"/>
<result column="currency" property="currency"/>
<result column="progress" property="progress"/>
<result column="status" property="status"/>
<result column="priority" property="priority"/>
<result column="risk_level" property="riskLevel"/>
<result column="visibility" property="visibility"/>
<result column="tags" property="tags" typeHandler="cn.yinlihupo.common.handler.JsonbTypeHandler"/>
<result column="extra_data" property="extraData" typeHandler="cn.yinlihupo.common.handler.JsonbTypeHandler"/>
<result column="create_by" property="createBy"/>
<result column="create_time" property="createTime"/>
<result column="update_by" property="updateBy"/>
<result column="update_time" property="updateTime"/>
<result column="deleted" property="deleted"/>
</resultMap>
<!-- 分页查询项目列表(支持多条件筛选) -->
<select id="selectPageList" resultMap="BaseResultMap">
SELECT p.*
FROM project p
<where>
p.deleted = 0
<if test="status != null and status != ''">
AND p.status = #{status}
</if>
<if test="priority != null and priority != ''">
AND p.priority = #{priority}
</if>
<if test="riskLevel != null and riskLevel != ''">
AND p.risk_level = #{riskLevel}
</if>
<if test="managerId != null">
AND p.manager_id = #{managerId}
</if>
<if test="keyword != null and keyword != ''">
AND (
p.project_name LIKE CONCAT('%', #{keyword}, '%')
OR p.project_code LIKE CONCAT('%', #{keyword}, '%')
)
</if>
<if test="planStartDateFrom != null">
AND p.plan_start_date &gt;= #{planStartDateFrom}
</if>
<if test="planStartDateTo != null">
AND p.plan_start_date &lt;= #{planStartDateTo}
</if>
</where>
ORDER BY p.create_time DESC
</select>
<!-- 查询指定用户有权限的项目列表(超管查全部,其他查自己参与的) -->
<select id="selectProjectsByUserId" resultMap="BaseResultMap">
SELECT DISTINCT p.*
FROM project p
LEFT JOIN project_member pm ON p.id = pm.project_id AND pm.deleted = 0 AND pm.status = 1
WHERE p.deleted = 0
AND (
p.manager_id = #{userId}
OR pm.user_id = #{userId}
)
ORDER BY p.create_time DESC
</select>
<!-- 查询项目详情(含经理姓名) -->
<select id="selectDetailById" resultMap="BaseResultMap">
SELECT p.*
FROM project p
WHERE p.id = #{projectId}
AND p.deleted = 0
</select>
<!-- 统计各状态项目数量 -->
<select id="countByStatus" resultType="map">
SELECT status, COUNT(*) AS cnt
FROM project
WHERE deleted = 0
GROUP BY status
</select>
<!-- 查询即将超期的项目计划结束日期在今天到N天以内 -->
<select id="selectAboutToExpire" resultMap="BaseResultMap">
SELECT *
FROM project
WHERE deleted = 0
AND status NOT IN ('completed', 'cancelled')
AND plan_end_date BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL '1 day' * #{days}
ORDER BY plan_end_date ASC
</select>
</mapper>

View File

@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.yinlihupo.mapper.ProjectMemberMapper">
<!-- 通用 ResultMap -->
<resultMap id="BaseResultMap" type="ProjectMember">
<id column="id" property="id"/>
<result column="project_id" property="projectId"/>
<result column="user_id" property="userId"/>
<result column="role_code" property="roleCode"/>
<result column="join_date" property="joinDate"/>
<result column="leave_date" property="leaveDate"/>
<result column="responsibility" property="responsibility"/>
<result column="weekly_hours" property="weeklyHours"/>
<result column="status" property="status"/>
<result column="create_by" property="createBy"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
<result column="deleted" property="deleted"/>
</resultMap>
<!-- 带用户信息的项目成员 ResultMap -->
<resultMap id="MemberWithUserMap" type="map">
<result column="id" property="id"/>
<result column="project_id" property="projectId"/>
<result column="user_id" property="userId"/>
<result column="role_code" property="roleCode"/>
<result column="join_date" property="joinDate"/>
<result column="responsibility" property="responsibility"/>
<result column="weekly_hours" property="weeklyHours"/>
<result column="status" property="status"/>
<result column="real_name" property="realName"/>
<result column="nickname" property="nickname"/>
<result column="avatar" property="avatar"/>
<result column="phone" property="phone"/>
<result column="position" property="position"/>
</resultMap>
<!-- 查询项目成员列表(含用户详情) -->
<select id="selectMembersWithUser" resultMap="MemberWithUserMap">
SELECT pm.id,
pm.project_id,
pm.user_id,
pm.role_code,
pm.join_date,
pm.responsibility,
pm.weekly_hours,
pm.status,
u.real_name,
u.nickname,
u.avatar,
u.phone,
u.position
FROM project_member pm
INNER JOIN sys_user u ON pm.user_id = u.id AND u.deleted = 0
WHERE pm.project_id = #{projectId}
AND pm.deleted = 0
AND pm.status = 1
ORDER BY
CASE pm.role_code
WHEN 'manager' THEN 1
WHEN 'leader' THEN 2
WHEN 'member' THEN 3
ELSE 4
END,
pm.join_date ASC
</select>
<!-- 查询用户在某项目中的角色 -->
<select id="selectRoleByUserAndProject" resultType="String">
SELECT role_code
FROM project_member
WHERE project_id = #{projectId}
AND user_id = #{userId}
AND status = 1
AND deleted = 0
LIMIT 1
</select>
<!-- 统计某用户参与的项目数量(按角色分组) -->
<select id="countUserProjects" resultType="map">
SELECT pm.role_code, COUNT(*) AS cnt
FROM project_member pm
INNER JOIN project p ON pm.project_id = p.id AND p.deleted = 0
WHERE pm.user_id = #{userId}
AND pm.deleted = 0
AND pm.status = 1
GROUP BY pm.role_code
</select>
</mapper>

View File

@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.yinlihupo.mapper.ProjectMilestoneMapper">
<!-- 通用 ResultMap -->
<resultMap id="BaseResultMap" type="ProjectMilestone">
<id column="id" property="id"/>
<result column="project_id" property="projectId"/>
<result column="milestone_name" property="milestoneName"/>
<result column="description" property="description"/>
<result column="plan_date" property="planDate"/>
<result column="actual_date" property="actualDate"/>
<result column="status" property="status"/>
<result column="progress" property="progress"/>
<result column="sort_order" property="sortOrder"/>
<result column="is_key" property="isKey"/>
<result column="deliverables" property="deliverables" typeHandler="cn.yinlihupo.common.handler.JsonbTypeHandler"/>
<result column="extra_data" property="extraData" typeHandler="cn.yinlihupo.common.handler.JsonbTypeHandler"/>
<result column="create_by" property="createBy"/>
<result column="create_time" property="createTime"/>
<result column="update_by" property="updateBy"/>
<result column="update_time" property="updateTime"/>
<result column="deleted" property="deleted"/>
</resultMap>
<!-- 查询项目里程碑列表(支持状态筛选,包含任务统计) -->
<select id="selectMilestoneListWithStats" resultType="map">
SELECT m.*,
COUNT(t.id) AS task_total,
COUNT(CASE WHEN t.status = 'completed' THEN 1 END) AS task_completed,
COUNT(CASE WHEN t.status = 'in_progress' THEN 1 END) AS task_in_progress
FROM project_milestone m
LEFT JOIN task t ON m.id = t.milestone_id AND t.deleted = 0
WHERE m.project_id = #{projectId}
AND m.deleted = 0
<if test="status != null and status != ''">
AND m.status = #{status}
</if>
GROUP BY m.id
ORDER BY m.sort_order ASC, m.plan_date ASC
</select>
<!-- 查询延期或即将延期的关键里程碑 -->
<select id="selectDelayedKeyMilestones" resultMap="BaseResultMap">
SELECT *
FROM project_milestone
WHERE deleted = 0
AND is_key = 1
AND status NOT IN ('completed')
AND plan_date &lt; CURRENT_DATE
<if test="projectId != null">
AND project_id = #{projectId}
</if>
ORDER BY plan_date ASC
</select>
<!-- 查询即将到期的里程碑N天内 -->
<select id="selectUpcomingMilestones" resultMap="BaseResultMap">
SELECT *
FROM project_milestone
WHERE deleted = 0
AND status NOT IN ('completed')
AND plan_date BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL '1 day' * #{days}
<if test="projectId != null">
AND project_id = #{projectId}
</if>
ORDER BY plan_date ASC
</select>
<!-- 查询里程碑完成进度统计 -->
<select id="selectMilestoneProgressSummary" resultType="map">
SELECT
COUNT(*) AS total,
COUNT(CASE WHEN status = 'completed' THEN 1 END) AS completed,
COUNT(CASE WHEN status = 'in_progress' THEN 1 END) AS in_progress,
COUNT(CASE WHEN status = 'delayed' THEN 1 END) AS delayed,
COUNT(CASE WHEN status = 'pending' THEN 1 END) AS pending,
ROUND(AVG(progress), 1) AS avg_progress
FROM project_milestone
WHERE project_id = #{projectId}
AND deleted = 0
</select>
</mapper>

View File

@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.yinlihupo.mapper.ResourceMapper">
<!-- 通用 ResultMap -->
<resultMap id="BaseResultMap" type="Resource">
<id column="id" property="id"/>
<result column="resource_code" property="resourceCode"/>
<result column="project_id" property="projectId"/>
<result column="resource_type" property="resourceType"/>
<result column="resource_name" property="resourceName"/>
<result column="description" property="description"/>
<result column="specification" property="specification"/>
<result column="unit" property="unit"/>
<result column="plan_quantity" property="planQuantity"/>
<result column="actual_quantity" property="actualQuantity"/>
<result column="unit_price" property="unitPrice"/>
<result column="currency" property="currency"/>
<result column="supplier" property="supplier"/>
<result column="status" property="status"/>
<result column="plan_arrive_date" property="planArriveDate"/>
<result column="actual_arrive_date" property="actualArriveDate"/>
<result column="responsible_id" property="responsibleId"/>
<result column="location" property="location"/>
<result column="tags" property="tags" typeHandler="cn.yinlihupo.common.handler.JsonbTypeHandler"/>
<result column="extra_data" property="extraData" typeHandler="cn.yinlihupo.common.handler.JsonbTypeHandler"/>
<result column="create_by" property="createBy"/>
<result column="create_time" property="createTime"/>
<result column="update_by" property="updateBy"/>
<result column="update_time" property="updateTime"/>
<result column="deleted" property="deleted"/>
</resultMap>
<!-- 分页查询资源列表(含负责人信息,支持多条件筛选) -->
<select id="selectResourcePageWithResponsible" resultType="map">
SELECT r.*,
u.real_name AS responsible_name
FROM resource r
LEFT JOIN sys_user u ON r.responsible_id = u.id AND u.deleted = 0
WHERE r.project_id = #{projectId}
AND r.deleted = 0
<if test="resourceType != null and resourceType != ''">
AND r.resource_type = #{resourceType}
</if>
<if test="status != null and status != ''">
AND r.status = #{status}
</if>
<if test="keyword != null and keyword != ''">
AND r.resource_name LIKE CONCAT('%', #{keyword}, '%')
</if>
ORDER BY r.resource_type ASC, r.create_time DESC
</select>
<!-- 查询资源预算汇总(按类型统计计划金额和实际金额) -->
<select id="selectResourceBudgetSummary" resultType="map">
SELECT resource_type,
COUNT(*) AS item_count,
SUM(plan_quantity * unit_price) AS plan_amount,
SUM(actual_quantity * unit_price) AS actual_amount,
SUM((actual_quantity - plan_quantity) * unit_price) AS diff_amount
FROM resource
WHERE project_id = #{projectId}
AND deleted = 0
GROUP BY resource_type
ORDER BY plan_amount DESC NULLS LAST
</select>
<!-- 查询即将到位但尚未到位的资源 -->
<select id="selectPendingArrivalResources" resultMap="BaseResultMap">
SELECT *
FROM resource
WHERE project_id = #{projectId}
AND deleted = 0
AND status IN ('approved', 'procuring')
AND plan_arrive_date BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL '1 day' * #{days}
ORDER BY plan_arrive_date ASC
</select>
<!-- 查询资源申请(待审批状态) -->
<select id="selectPendingApprovalResources" resultType="map">
SELECT r.*,
u.real_name AS responsible_name,
p.project_name
FROM resource r
LEFT JOIN sys_user u ON r.responsible_id = u.id AND u.deleted = 0
LEFT JOIN project p ON r.project_id = p.id AND p.deleted = 0
WHERE r.deleted = 0
AND r.status = 'requested'
<if test="projectId != null">
AND r.project_id = #{projectId}
</if>
ORDER BY r.create_time ASC
</select>
</mapper>

View File

@@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.yinlihupo.mapper.RiskMapper">
<!-- 通用 ResultMap -->
<resultMap id="BaseResultMap" type="Risk">
<id column="id" property="id"/>
<result column="risk_code" property="riskCode"/>
<result column="project_id" property="projectId"/>
<result column="category" property="category"/>
<result column="risk_name" property="riskName"/>
<result column="description" property="description"/>
<result column="risk_source" property="riskSource"/>
<result column="risk_type" property="riskType"/>
<result column="probability" property="probability"/>
<result column="impact" property="impact"/>
<result column="risk_score" property="riskScore"/>
<result column="risk_level" property="riskLevel"/>
<result column="status" property="status"/>
<result column="owner_id" property="ownerId"/>
<result column="mitigation_plan" property="mitigationPlan"/>
<result column="contingency_plan" property="contingencyPlan"/>
<result column="trigger_condition" property="triggerCondition"/>
<result column="discover_time" property="discoverTime"/>
<result column="due_date" property="dueDate"/>
<result column="resolved_time" property="resolvedTime"/>
<result column="ai_analysis" property="aiAnalysis" typeHandler="cn.yinlihupo.common.handler.JsonbTypeHandler"/>
<result column="tags" property="tags" typeHandler="cn.yinlihupo.common.handler.JsonbTypeHandler"/>
<result column="extra_data" property="extraData" typeHandler="cn.yinlihupo.common.handler.JsonbTypeHandler"/>
<result column="create_by" property="createBy"/>
<result column="create_time" property="createTime"/>
<result column="update_by" property="updateBy"/>
<result column="update_time" property="updateTime"/>
<result column="deleted" property="deleted"/>
</resultMap>
<!-- 分页查询风险列表(含负责人信息,支持多条件筛选) -->
<select id="selectRiskPageWithOwner" resultType="map">
SELECT r.*,
u.real_name AS owner_name,
u.avatar AS owner_avatar
FROM risk r
LEFT JOIN sys_user u ON r.owner_id = u.id AND u.deleted = 0
WHERE r.project_id = #{projectId}
AND r.deleted = 0
<if test="category != null and category != ''">
AND r.category = #{category}
</if>
<if test="riskLevel != null and riskLevel != ''">
AND r.risk_level = #{riskLevel}
</if>
<if test="status != null and status != ''">
AND r.status = #{status}
</if>
<if test="keyword != null and keyword != ''">
AND r.risk_name LIKE CONCAT('%', #{keyword}, '%')
</if>
ORDER BY
CASE r.risk_level
WHEN 'critical' THEN 1
WHEN 'high' THEN 2
WHEN 'medium' THEN 3
ELSE 4
END,
r.create_time DESC
</select>
<!-- 查询未解决的高/严重风险(用于预警) -->
<select id="selectHighRisksUnresolved" resultType="map">
SELECT r.*,
u.real_name AS owner_name,
p.project_name
FROM risk r
LEFT JOIN sys_user u ON r.owner_id = u.id AND u.deleted = 0
LEFT JOIN project p ON r.project_id = p.id AND p.deleted = 0
WHERE r.deleted = 0
AND r.risk_level IN ('critical', 'high')
AND r.status NOT IN ('resolved', 'closed')
<if test="projectId != null">
AND r.project_id = #{projectId}
</if>
ORDER BY r.risk_score DESC NULLS LAST
</select>
<!-- 按风险等级统计各分类数量 -->
<select id="countRiskByCategoryAndLevel" resultType="map">
SELECT category, risk_level, COUNT(*) AS cnt
FROM risk
WHERE project_id = #{projectId}
AND deleted = 0
GROUP BY category, risk_level
ORDER BY category, risk_level
</select>
<!-- 查询风险处理趋势(按月统计新增/关闭数量) -->
<select id="selectRiskTrend" resultType="map">
SELECT
TO_CHAR(create_time, 'YYYY-MM') AS month,
COUNT(*) AS created_cnt,
COUNT(CASE WHEN status IN ('resolved','closed') THEN 1 END) AS closed_cnt
FROM risk
WHERE project_id = #{projectId}
AND deleted = 0
AND create_time &gt;= NOW() - INTERVAL '6 months'
GROUP BY month
ORDER BY month ASC
</select>
</mapper>

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.yinlihupo.mapper.SysPermissionMapper">
<!-- 根据角色编码列表查询权限编码列表 -->
<select id="selectPermissionCodesByRoleCodes" resultType="String">
SELECT DISTINCT p.permission_code
FROM sys_permission p
INNER JOIN sys_role_permission rp ON p.id = rp.permission_id
INNER JOIN sys_role r ON rp.role_id = r.id
WHERE r.role_code IN
<foreach collection="roleCodes" item="code" open="(" separator="," close=")">
#{code}
</foreach>
AND p.deleted = 0
AND r.deleted = 0
AND p.status = 1
</select>
<!-- 查询菜单树(含按钮权限) -->
<select id="selectMenuTree" resultType="SysPermission">
SELECT *
FROM sys_permission
WHERE deleted = 0
AND status = 1
AND permission_type IN (1, 2)
ORDER BY sort_order ASC, id ASC
</select>
<!-- 根据角色ID查询权限列表 -->
<select id="selectPermsByRoleId" resultType="SysPermission">
SELECT p.*
FROM sys_permission p
INNER JOIN sys_role_permission rp ON p.id = rp.permission_id
WHERE rp.role_id = #{roleId}
AND p.deleted = 0
ORDER BY p.sort_order ASC
</select>
</mapper>

View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.yinlihupo.mapper.SysUserMapper">
<!-- 通用ResultMap -->
<resultMap id="BaseResultMap" type="SysUser">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="real_name" property="realName"/>
<result column="nickname" property="nickname"/>
<result column="avatar" property="avatar"/>
<result column="gender" property="gender"/>
<result column="phone" property="phone"/>
<result column="email" property="email"/>
<result column="dept_id" property="deptId"/>
<result column="position" property="position"/>
<result column="employee_no" property="employeeNo"/>
<result column="entry_date" property="entryDate"/>
<result column="status" property="status"/>
<result column="last_login_time" property="lastLoginTime"/>
<result column="last_login_ip" property="lastLoginIp"/>
<result column="create_by" property="createBy"/>
<result column="create_time" property="createTime"/>
<result column="update_by" property="updateBy"/>
<result column="update_time" property="updateTime"/>
<result column="deleted" property="deleted"/>
</resultMap>
<!-- 根据用户ID查询角色编码列表 -->
<select id="selectRoleCodesByUserId" resultType="String">
SELECT r.role_code
FROM sys_role r
INNER JOIN sys_user_role ur ON r.id = ur.role_id
WHERE ur.user_id = #{userId}
AND r.deleted = 0
</select>
<!-- 根据用户名查询用户(含角色) -->
<select id="selectByUsername" resultMap="BaseResultMap">
SELECT *
FROM sys_user
WHERE username = #{username}
AND deleted = 0
</select>
<!-- 分页查询用户列表(支持按部门、状态、关键字筛选) -->
<select id="selectPageList" resultMap="BaseResultMap">
SELECT u.*
FROM sys_user u
<where>
u.deleted = 0
<if test="deptId != null">
AND u.dept_id = #{deptId}
</if>
<if test="status != null">
AND u.status = #{status}
</if>
<if test="keyword != null and keyword != ''">
AND (
u.username LIKE CONCAT('%', #{keyword}, '%')
OR u.real_name LIKE CONCAT('%', #{keyword}, '%')
OR u.phone LIKE CONCAT('%', #{keyword}, '%')
)
</if>
</where>
ORDER BY u.create_time DESC
</select>
<!-- 查询项目成员用户详情(关联 project_member -->
<select id="selectUsersByProjectId" resultMap="BaseResultMap">
SELECT u.*
FROM sys_user u
INNER JOIN project_member pm ON u.id = pm.user_id
WHERE pm.project_id = #{projectId}
AND pm.status = 1
AND pm.deleted = 0
AND u.deleted = 0
ORDER BY pm.create_time ASC
</select>
</mapper>

View File

@@ -0,0 +1,125 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.yinlihupo.mapper.TaskMapper">
<!-- 通用 ResultMap -->
<resultMap id="BaseResultMap" type="Task">
<id column="id" property="id"/>
<result column="task_code" property="taskCode"/>
<result column="project_id" property="projectId"/>
<result column="milestone_id" property="milestoneId"/>
<result column="task_name" property="taskName"/>
<result column="description" property="description"/>
<result column="task_type" property="taskType"/>
<result column="assignee_id" property="assigneeId"/>
<result column="plan_start_date" property="planStartDate"/>
<result column="plan_end_date" property="planEndDate"/>
<result column="actual_start_date" property="actualStartDate"/>
<result column="actual_end_date" property="actualEndDate"/>
<result column="plan_hours" property="planHours"/>
<result column="actual_hours" property="actualHours"/>
<result column="progress" property="progress"/>
<result column="priority" property="priority"/>
<result column="status" property="status"/>
<result column="sort_order" property="sortOrder"/>
<result column="tags" property="tags" typeHandler="cn.yinlihupo.common.handler.JsonbTypeHandler"/>
<result column="attachments" property="attachments" typeHandler="cn.yinlihupo.common.handler.JsonbTypeHandler"/>
<result column="extra_data" property="extraData" typeHandler="cn.yinlihupo.common.handler.JsonbTypeHandler"/>
<result column="create_by" property="createBy"/>
<result column="create_time" property="createTime"/>
<result column="update_by" property="updateBy"/>
<result column="update_time" property="updateTime"/>
<result column="deleted" property="deleted"/>
</resultMap>
<!-- 分页查询任务列表(含负责人信息,支持多条件筛选) -->
<select id="selectTaskPageWithAssignee" resultType="map">
SELECT t.*,
u.real_name AS assignee_name,
u.avatar AS assignee_avatar,
m.milestone_name
FROM task t
LEFT JOIN sys_user u ON t.assignee_id = u.id AND u.deleted = 0
LEFT JOIN project_milestone m ON t.milestone_id = m.id AND m.deleted = 0
WHERE t.project_id = #{projectId}
AND t.deleted = 0
<if test="milestoneId != null">
AND t.milestone_id = #{milestoneId}
</if>
<if test="assigneeId != null">
AND t.assignee_id = #{assigneeId}
</if>
<if test="status != null and status != ''">
AND t.status = #{status}
</if>
<if test="priority != null and priority != ''">
AND t.priority = #{priority}
</if>
<if test="keyword != null and keyword != ''">
AND t.task_name LIKE CONCAT('%', #{keyword}, '%')
</if>
ORDER BY t.sort_order ASC, t.plan_start_date ASC
</select>
<!-- 查询我的任务按用户ID -->
<select id="selectMyTasks" resultMap="BaseResultMap">
SELECT t.*
FROM task t
WHERE t.assignee_id = #{userId}
AND t.deleted = 0
AND t.status NOT IN ('completed', 'cancelled')
<if test="projectId != null">
AND t.project_id = #{projectId}
</if>
ORDER BY
CASE t.priority
WHEN 'critical' THEN 1
WHEN 'high' THEN 2
WHEN 'medium' THEN 3
ELSE 4
END,
t.plan_end_date ASC NULLS LAST
</select>
<!-- 查询超期未完成的任务 -->
<select id="selectOverdueTasks" resultType="map">
SELECT t.*,
u.real_name AS assignee_name,
p.project_name,
CURRENT_DATE - t.plan_end_date AS overdue_days
FROM task t
LEFT JOIN sys_user u ON t.assignee_id = u.id AND u.deleted = 0
LEFT JOIN project p ON t.project_id = p.id AND p.deleted = 0
WHERE t.deleted = 0
AND t.status NOT IN ('completed', 'cancelled')
AND t.plan_end_date &lt; CURRENT_DATE
<if test="projectId != null">
AND t.project_id = #{projectId}
</if>
ORDER BY overdue_days DESC
</select>
<!-- 查询任务依赖关系(前置任务) -->
<select id="selectDependencies" resultType="map">
SELECT t.id,
t.task_name,
t.status,
t.progress,
td.dependency_type,
td.lag_days
FROM task_dependency td
INNER JOIN task t ON td.depends_on_task_id = t.id AND t.deleted = 0
WHERE td.task_id = #{taskId}
</select>
<!-- 统计项目任务状态分布 -->
<select id="countTasksByStatus" resultType="map">
SELECT status, COUNT(*) AS cnt
FROM task
WHERE project_id = #{projectId}
AND deleted = 0
GROUP BY status
</select>
</mapper>