feat(project): 添加项目查询相关接口与功能
- 新增ProjectQueryController,提供项目列表、甘特图和统计接口 - 支持管理员和普通用户分别查询对应项目数据 - 新增GanttTaskVO、ProjectGanttVO、ProjectListVO和ProjectStatisticsVO数据模型 - ProjectService接口扩展项目查询相关方法定义 - 实现ProjectServiceImpl中项目列表、甘特图、统计信息的业务逻辑 - 项目查询支持分页、关键词和状态筛选 - 甘特图数据包含任务和里程碑详细信息 - 项目统计包括总数、状态分布、本月新增、即将到期和风险统计 - FeishuAuthServiceImpl中新增新用户自动分配“普通成员”角色功能 - 修改开发环境配置,更新Chat模型为google/gemini-3.1-pro-preview
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
package cn.yinlihupo.controller.project;
|
||||
|
||||
import cn.yinlihupo.common.core.BaseResponse;
|
||||
import cn.yinlihupo.common.page.TableDataInfo;
|
||||
import cn.yinlihupo.common.util.ResultUtils;
|
||||
import cn.yinlihupo.common.util.SecurityUtils;
|
||||
import cn.yinlihupo.domain.vo.ProjectGanttVO;
|
||||
import cn.yinlihupo.domain.vo.ProjectListVO;
|
||||
import cn.yinlihupo.domain.vo.ProjectStatisticsVO;
|
||||
import cn.yinlihupo.service.project.ProjectService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 项目查询控制器
|
||||
* 提供项目列表、甘特图、统计信息等查询接口
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/project")
|
||||
@RequiredArgsConstructor
|
||||
public class ProjectQueryController {
|
||||
|
||||
private final ProjectService projectService;
|
||||
|
||||
/**
|
||||
* 获取项目列表
|
||||
* 管理员返回全部项目,普通用户返回自己管理或参与的项目
|
||||
*
|
||||
* @param pageNum 页码,默认1
|
||||
* @param pageSize 每页大小,默认10
|
||||
* @param keyword 关键词搜索(项目名称/编号)
|
||||
* @param status 状态筛选
|
||||
* @return 项目列表分页数据
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public BaseResponse<TableDataInfo<ProjectListVO>> getProjectList(
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) String status) {
|
||||
|
||||
Long userId = SecurityUtils.getCurrentUserId();
|
||||
if (userId == null) {
|
||||
return ResultUtils.error("用户未登录");
|
||||
}
|
||||
|
||||
// 判断是否为管理员
|
||||
boolean isAdmin = SecurityUtils.isAdmin();
|
||||
log.info("获取项目列表, userId: {}, isAdmin: {}, pageNum: {}, pageSize: {}", userId, isAdmin, pageNum, pageSize);
|
||||
|
||||
try {
|
||||
TableDataInfo<ProjectListVO> result;
|
||||
if (isAdmin) {
|
||||
// 管理员返回全部项目
|
||||
result = projectService.getAllProjectList(pageNum, pageSize, keyword, status);
|
||||
} else {
|
||||
// 普通用户返回自己的项目
|
||||
result = projectService.getMyProjectList(userId, pageNum, pageSize, keyword, status);
|
||||
}
|
||||
return ResultUtils.success("查询成功", result);
|
||||
} catch (Exception e) {
|
||||
log.error("获取项目列表失败: {}", e.getMessage(), e);
|
||||
return ResultUtils.error("查询失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目甘特图数据
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @return 甘特图数据
|
||||
*/
|
||||
@GetMapping("/{projectId}/gantt")
|
||||
public BaseResponse<ProjectGanttVO> getProjectGantt(@PathVariable Long projectId) {
|
||||
log.info("获取项目甘特图数据, projectId: {}", projectId);
|
||||
|
||||
if (projectId == null || projectId <= 0) {
|
||||
return ResultUtils.error("项目ID不能为空");
|
||||
}
|
||||
|
||||
try {
|
||||
ProjectGanttVO result = projectService.getProjectGantt(projectId);
|
||||
return ResultUtils.success("查询成功", result);
|
||||
} catch (Exception e) {
|
||||
log.error("获取项目甘特图失败: {}", e.getMessage(), e);
|
||||
return ResultUtils.error("查询失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目统计信息
|
||||
* 管理员返回全局统计,普通用户返回自己的项目统计
|
||||
*
|
||||
* @return 统计信息
|
||||
*/
|
||||
@GetMapping("/statistics")
|
||||
public BaseResponse<ProjectStatisticsVO> getProjectStatistics() {
|
||||
Long userId = SecurityUtils.getCurrentUserId();
|
||||
if (userId == null) {
|
||||
return ResultUtils.error("用户未登录");
|
||||
}
|
||||
|
||||
// 判断是否为管理员
|
||||
boolean isAdmin = SecurityUtils.isAdmin();
|
||||
log.info("获取项目统计信息, userId: {}, isAdmin: {}", userId, isAdmin);
|
||||
|
||||
try {
|
||||
ProjectStatisticsVO result;
|
||||
if (isAdmin) {
|
||||
// 管理员返回全局统计
|
||||
result = projectService.getProjectStatistics(null);
|
||||
} else {
|
||||
// 普通用户返回自己的项目统计
|
||||
result = projectService.getProjectStatistics(userId);
|
||||
}
|
||||
return ResultUtils.success("查询成功", result);
|
||||
} catch (Exception e) {
|
||||
log.error("获取项目统计信息失败: {}", e.getMessage(), e);
|
||||
return ResultUtils.error("查询失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
84
src/main/java/cn/yinlihupo/domain/vo/GanttTaskVO.java
Normal file
84
src/main/java/cn/yinlihupo/domain/vo/GanttTaskVO.java
Normal file
@@ -0,0 +1,84 @@
|
||||
package cn.yinlihupo.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 甘特图任务VO
|
||||
*/
|
||||
@Data
|
||||
public class GanttTaskVO {
|
||||
|
||||
/**
|
||||
* 任务ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 任务名称
|
||||
*/
|
||||
private String taskName;
|
||||
|
||||
/**
|
||||
* 任务类型: task-任务, milestone-里程碑
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 计划开始日期
|
||||
*/
|
||||
private LocalDate startDate;
|
||||
|
||||
/**
|
||||
* 计划结束日期
|
||||
*/
|
||||
private LocalDate endDate;
|
||||
|
||||
/**
|
||||
* 实际开始日期
|
||||
*/
|
||||
private LocalDate actualStartDate;
|
||||
|
||||
/**
|
||||
* 实际结束日期
|
||||
*/
|
||||
private LocalDate actualEndDate;
|
||||
|
||||
/**
|
||||
* 进度百分比
|
||||
*/
|
||||
private Integer progress;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 优先级
|
||||
*/
|
||||
private String priority;
|
||||
|
||||
/**
|
||||
* 父任务ID
|
||||
*/
|
||||
private Long parentId;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private Integer sortOrder;
|
||||
|
||||
/**
|
||||
* 执行人名称
|
||||
*/
|
||||
private String assigneeName;
|
||||
|
||||
/**
|
||||
* 依赖任务ID列表
|
||||
*/
|
||||
private List<Long> dependencies;
|
||||
}
|
||||
53
src/main/java/cn/yinlihupo/domain/vo/ProjectGanttVO.java
Normal file
53
src/main/java/cn/yinlihupo/domain/vo/ProjectGanttVO.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package cn.yinlihupo.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 项目甘特图数据VO
|
||||
*/
|
||||
@Data
|
||||
public class ProjectGanttVO {
|
||||
|
||||
/**
|
||||
* 项目ID
|
||||
*/
|
||||
private Long projectId;
|
||||
|
||||
/**
|
||||
* 项目名称
|
||||
*/
|
||||
private String projectName;
|
||||
|
||||
/**
|
||||
* 项目状态
|
||||
*/
|
||||
private String projectStatus;
|
||||
|
||||
/**
|
||||
* 项目计划开始日期
|
||||
*/
|
||||
private LocalDate projectStartDate;
|
||||
|
||||
/**
|
||||
* 项目计划结束日期
|
||||
*/
|
||||
private LocalDate projectEndDate;
|
||||
|
||||
/**
|
||||
* 项目整体进度
|
||||
*/
|
||||
private Integer projectProgress;
|
||||
|
||||
/**
|
||||
* 甘特图任务列表(包含里程碑和任务)
|
||||
*/
|
||||
private List<GanttTaskVO> tasks;
|
||||
|
||||
/**
|
||||
* 里程碑列表
|
||||
*/
|
||||
private List<GanttTaskVO> milestones;
|
||||
}
|
||||
100
src/main/java/cn/yinlihupo/domain/vo/ProjectListVO.java
Normal file
100
src/main/java/cn/yinlihupo/domain/vo/ProjectListVO.java
Normal file
@@ -0,0 +1,100 @@
|
||||
package cn.yinlihupo.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 项目列表VO
|
||||
*/
|
||||
@Data
|
||||
public class ProjectListVO {
|
||||
|
||||
/**
|
||||
* 项目ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 项目编号
|
||||
*/
|
||||
private String projectCode;
|
||||
|
||||
/**
|
||||
* 项目名称
|
||||
*/
|
||||
private String projectName;
|
||||
|
||||
/**
|
||||
* 项目类型
|
||||
*/
|
||||
private String projectType;
|
||||
|
||||
/**
|
||||
* 项目经理ID
|
||||
*/
|
||||
private Long managerId;
|
||||
|
||||
/**
|
||||
* 项目经理名称
|
||||
*/
|
||||
private String managerName;
|
||||
|
||||
/**
|
||||
* 计划开始日期
|
||||
*/
|
||||
private LocalDate planStartDate;
|
||||
|
||||
/**
|
||||
* 计划结束日期
|
||||
*/
|
||||
private LocalDate planEndDate;
|
||||
|
||||
/**
|
||||
* 进度百分比
|
||||
*/
|
||||
private Integer progress;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 优先级
|
||||
*/
|
||||
private String priority;
|
||||
|
||||
/**
|
||||
* 风险等级
|
||||
*/
|
||||
private String riskLevel;
|
||||
|
||||
/**
|
||||
* 标签列表
|
||||
*/
|
||||
private List<String> tags;
|
||||
|
||||
/**
|
||||
* 预算
|
||||
*/
|
||||
private BigDecimal budget;
|
||||
|
||||
/**
|
||||
* 已花费金额
|
||||
*/
|
||||
private BigDecimal cost;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 我在项目中的角色
|
||||
*/
|
||||
private String myRole;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package cn.yinlihupo.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 项目统计信息VO
|
||||
*/
|
||||
@Data
|
||||
public class ProjectStatisticsVO {
|
||||
|
||||
/**
|
||||
* 项目总数
|
||||
*/
|
||||
private Integer totalCount;
|
||||
|
||||
/**
|
||||
* 进行中项目数
|
||||
*/
|
||||
private Integer ongoingCount;
|
||||
|
||||
/**
|
||||
* 已完成项目数
|
||||
*/
|
||||
private Integer completedCount;
|
||||
|
||||
/**
|
||||
* 已暂停项目数
|
||||
*/
|
||||
private Integer pausedCount;
|
||||
|
||||
/**
|
||||
* 规划中项目数
|
||||
*/
|
||||
private Integer planningCount;
|
||||
|
||||
/**
|
||||
* 已取消项目数
|
||||
*/
|
||||
private Integer cancelledCount;
|
||||
|
||||
/**
|
||||
* 草稿项目数
|
||||
*/
|
||||
private Integer draftCount;
|
||||
|
||||
/**
|
||||
* 各状态项目数量统计
|
||||
*/
|
||||
private Map<String, Integer> statusCountMap;
|
||||
|
||||
/**
|
||||
* 本月新增项目数
|
||||
*/
|
||||
private Integer newThisMonth;
|
||||
|
||||
/**
|
||||
* 即将到期项目数(7天内)
|
||||
*/
|
||||
private Integer aboutToExpireCount;
|
||||
|
||||
/**
|
||||
* 平均项目进度
|
||||
*/
|
||||
private BigDecimal averageProgress;
|
||||
|
||||
/**
|
||||
* 高风险项目数
|
||||
*/
|
||||
private Integer highRiskCount;
|
||||
}
|
||||
@@ -1,8 +1,14 @@
|
||||
package cn.yinlihupo.service.project;
|
||||
|
||||
import cn.yinlihupo.common.page.TableDataInfo;
|
||||
import cn.yinlihupo.domain.vo.ProjectGanttVO;
|
||||
import cn.yinlihupo.domain.vo.ProjectInitResult;
|
||||
import cn.yinlihupo.domain.vo.ProjectListVO;
|
||||
import cn.yinlihupo.domain.vo.ProjectStatisticsVO;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI项目初始化服务接口
|
||||
* 使用Spring AI结构化输出能力,从项目文档中提取结构化信息
|
||||
@@ -41,4 +47,47 @@ public interface ProjectService {
|
||||
* @return 保存后的项目初始化结果(包含生成的ID等信息)
|
||||
*/
|
||||
ProjectInitResult saveProjectData(ProjectInitResult result);
|
||||
|
||||
// ==================== 项目查询相关接口 ====================
|
||||
|
||||
/**
|
||||
* 获取我的项目列表(我管理或参与的项目)
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页大小
|
||||
* @param keyword 关键词搜索
|
||||
* @param status 状态筛选
|
||||
* @return 分页项目列表
|
||||
*/
|
||||
TableDataInfo<ProjectListVO> getMyProjectList(Long userId, Integer pageNum, Integer pageSize,
|
||||
String keyword, String status);
|
||||
|
||||
/**
|
||||
* 获取项目甘特图数据
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @return 甘特图数据
|
||||
*/
|
||||
ProjectGanttVO getProjectGantt(Long projectId);
|
||||
|
||||
/**
|
||||
* 获取项目统计信息
|
||||
*
|
||||
* @param userId 用户ID(为null则统计全部)
|
||||
* @return 统计信息
|
||||
*/
|
||||
ProjectStatisticsVO getProjectStatistics(Long userId);
|
||||
|
||||
/**
|
||||
* 获取所有项目列表(管理员用)
|
||||
*
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页大小
|
||||
* @param keyword 关键词搜索
|
||||
* @param status 状态筛选
|
||||
* @return 分页项目列表
|
||||
*/
|
||||
TableDataInfo<ProjectListVO> getAllProjectList(Integer pageNum, Integer pageSize,
|
||||
String keyword, String status);
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
package cn.yinlihupo.service.project.impl;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.yinlihupo.common.page.TableDataInfo;
|
||||
import cn.yinlihupo.domain.entity.*;
|
||||
import cn.yinlihupo.domain.vo.ProjectInitResult;
|
||||
import cn.yinlihupo.domain.vo.*;
|
||||
import cn.yinlihupo.mapper.*;
|
||||
import cn.yinlihupo.service.oss.OssService;
|
||||
import cn.yinlihupo.service.project.ProjectService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.metadata.Usage;
|
||||
import org.springframework.ai.converter.BeanOutputConverter;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.time.YearMonth;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* AI项目初始化服务实现类
|
||||
@@ -551,4 +551,278 @@ public class ProjectServiceImpl implements ProjectService {
|
||||
timeline.setKbScope(info.getKbScope());
|
||||
return timeline;
|
||||
}
|
||||
|
||||
// ==================== 项目查询相关实现 ====================
|
||||
|
||||
@Override
|
||||
public TableDataInfo<ProjectListVO> getMyProjectList(Long userId, Integer pageNum, Integer pageSize,
|
||||
String keyword, String status) {
|
||||
log.info("查询我的项目列表, userId: {}, pageNum: {}, pageSize: {}", userId, pageNum, pageSize);
|
||||
|
||||
// 使用分页插件
|
||||
Page<Project> page = new Page<>(pageNum, pageSize);
|
||||
|
||||
// 构建查询条件
|
||||
LambdaQueryWrapper<Project> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(Project::getDeleted, 0);
|
||||
|
||||
// 关键词搜索
|
||||
if (keyword != null && !keyword.isEmpty()) {
|
||||
wrapper.and(w -> w.like(Project::getProjectName, keyword)
|
||||
.or()
|
||||
.like(Project::getProjectCode, keyword));
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (status != null && !status.isEmpty()) {
|
||||
wrapper.eq(Project::getStatus, status);
|
||||
}
|
||||
|
||||
// 查询我管理或参与的项目
|
||||
// 先查询我参与的项目ID列表
|
||||
List<Long> myProjectIds = projectMemberMapper.selectList(
|
||||
new LambdaQueryWrapper<ProjectMember>()
|
||||
.eq(ProjectMember::getUserId, userId)
|
||||
.eq(ProjectMember::getDeleted, 0)
|
||||
.eq(ProjectMember::getStatus, 1)
|
||||
).stream().map(ProjectMember::getProjectId).collect(Collectors.toList());
|
||||
|
||||
// 查询条件:我管理的项目 或 我参与的项目
|
||||
wrapper.and(w -> w.eq(Project::getManagerId, userId)
|
||||
.or()
|
||||
.in(!myProjectIds.isEmpty(), Project::getId, myProjectIds));
|
||||
|
||||
wrapper.orderByDesc(Project::getCreateTime);
|
||||
|
||||
Page<Project> projectPage = projectMapper.selectPage(page, wrapper);
|
||||
|
||||
// 转换为VO
|
||||
List<ProjectListVO> voList = projectPage.getRecords().stream()
|
||||
.map(this::convertToProjectListVO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return TableDataInfo.build(new Page<ProjectListVO>(projectPage.getCurrent(), projectPage.getSize(), projectPage.getTotal())
|
||||
.setRecords(voList));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectGanttVO getProjectGantt(Long projectId) {
|
||||
log.info("查询项目甘特图数据, projectId: {}", projectId);
|
||||
|
||||
// 1. 查询项目基本信息
|
||||
Project project = projectMapper.selectById(projectId);
|
||||
if (project == null || project.getDeleted() == 1) {
|
||||
throw new RuntimeException("项目不存在");
|
||||
}
|
||||
|
||||
ProjectGanttVO ganttVO = new ProjectGanttVO();
|
||||
ganttVO.setProjectId(project.getId());
|
||||
ganttVO.setProjectName(project.getProjectName());
|
||||
ganttVO.setProjectStatus(project.getStatus());
|
||||
ganttVO.setProjectStartDate(project.getPlanStartDate());
|
||||
ganttVO.setProjectEndDate(project.getPlanEndDate());
|
||||
ganttVO.setProjectProgress(project.getProgress());
|
||||
|
||||
// 2. 查询里程碑
|
||||
List<ProjectMilestone> milestones = projectMilestoneMapper.selectList(
|
||||
new LambdaQueryWrapper<ProjectMilestone>()
|
||||
.eq(ProjectMilestone::getProjectId, projectId)
|
||||
.eq(ProjectMilestone::getDeleted, 0)
|
||||
.orderByAsc(ProjectMilestone::getSortOrder)
|
||||
);
|
||||
|
||||
List<GanttTaskVO> milestoneVOList = milestones.stream()
|
||||
.map(this::convertMilestoneToGanttTask)
|
||||
.collect(Collectors.toList());
|
||||
ganttVO.setMilestones(milestoneVOList);
|
||||
|
||||
// 3. 查询任务
|
||||
List<Task> tasks = taskMapper.selectList(
|
||||
new LambdaQueryWrapper<Task>()
|
||||
.eq(Task::getProjectId, projectId)
|
||||
.eq(Task::getDeleted, 0)
|
||||
.orderByAsc(Task::getSortOrder)
|
||||
);
|
||||
|
||||
List<GanttTaskVO> taskVOList = tasks.stream()
|
||||
.map(this::convertTaskToGanttTask)
|
||||
.collect(Collectors.toList());
|
||||
ganttVO.setTasks(taskVOList);
|
||||
|
||||
return ganttVO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectStatisticsVO getProjectStatistics(Long userId) {
|
||||
log.info("查询项目统计信息, userId: {}", userId);
|
||||
|
||||
ProjectStatisticsVO statistics = new ProjectStatisticsVO();
|
||||
|
||||
// 构建基础查询条件
|
||||
LambdaQueryWrapper<Project> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(Project::getDeleted, 0);
|
||||
|
||||
// 如果指定了用户ID,只统计该用户的项目
|
||||
if (userId != null) {
|
||||
List<Long> myProjectIds = projectMemberMapper.selectList(
|
||||
new LambdaQueryWrapper<ProjectMember>()
|
||||
.eq(ProjectMember::getUserId, userId)
|
||||
.eq(ProjectMember::getDeleted, 0)
|
||||
.eq(ProjectMember::getStatus, 1)
|
||||
).stream().map(ProjectMember::getProjectId).collect(Collectors.toList());
|
||||
|
||||
wrapper.and(w -> w.eq(Project::getManagerId, userId)
|
||||
.or()
|
||||
.in(!myProjectIds.isEmpty(), Project::getId, myProjectIds));
|
||||
}
|
||||
|
||||
// 1. 查询所有符合条件的项目
|
||||
List<Project> projects = projectMapper.selectList(wrapper);
|
||||
|
||||
// 2. 统计总数
|
||||
statistics.setTotalCount(projects.size());
|
||||
|
||||
// 3. 按状态统计
|
||||
Map<String, Integer> statusCountMap = projects.stream()
|
||||
.collect(Collectors.groupingBy(
|
||||
Project::getStatus,
|
||||
Collectors.collectingAndThen(Collectors.counting(), Long::intValue)
|
||||
));
|
||||
statistics.setStatusCountMap(statusCountMap);
|
||||
|
||||
// 设置各状态数量
|
||||
statistics.setDraftCount(statusCountMap.getOrDefault("draft", 0));
|
||||
statistics.setPlanningCount(statusCountMap.getOrDefault("planning", 0));
|
||||
statistics.setOngoingCount(statusCountMap.getOrDefault("ongoing", 0));
|
||||
statistics.setPausedCount(statusCountMap.getOrDefault("paused", 0));
|
||||
statistics.setCompletedCount(statusCountMap.getOrDefault("completed", 0));
|
||||
statistics.setCancelledCount(statusCountMap.getOrDefault("cancelled", 0));
|
||||
|
||||
// 4. 统计本月新增
|
||||
YearMonth thisMonth = YearMonth.now();
|
||||
int newThisMonth = (int) projects.stream()
|
||||
.filter(p -> p.getCreateTime() != null &&
|
||||
YearMonth.from(p.getCreateTime()).equals(thisMonth))
|
||||
.count();
|
||||
statistics.setNewThisMonth(newThisMonth);
|
||||
|
||||
// 5. 统计即将到期(7天内)
|
||||
LocalDate now = LocalDate.now();
|
||||
LocalDate sevenDaysLater = now.plusDays(7);
|
||||
int aboutToExpire = (int) projects.stream()
|
||||
.filter(p -> !"completed".equals(p.getStatus()) && !"cancelled".equals(p.getStatus()))
|
||||
.filter(p -> p.getPlanEndDate() != null &&
|
||||
!p.getPlanEndDate().isBefore(now) &&
|
||||
!p.getPlanEndDate().isAfter(sevenDaysLater))
|
||||
.count();
|
||||
statistics.setAboutToExpireCount(aboutToExpire);
|
||||
|
||||
// 6. 计算平均进度
|
||||
double avgProgress = projects.stream()
|
||||
.filter(p -> p.getProgress() != null)
|
||||
.mapToInt(Project::getProgress)
|
||||
.average()
|
||||
.orElse(0.0);
|
||||
statistics.setAverageProgress(BigDecimal.valueOf(avgProgress).setScale(2, RoundingMode.HALF_UP));
|
||||
|
||||
// 7. 统计高风险项目
|
||||
int highRiskCount = (int) projects.stream()
|
||||
.filter(p -> "high".equals(p.getRiskLevel()) || "critical".equals(p.getRiskLevel()))
|
||||
.count();
|
||||
statistics.setHighRiskCount(highRiskCount);
|
||||
|
||||
return statistics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableDataInfo<ProjectListVO> getAllProjectList(Integer pageNum, Integer pageSize,
|
||||
String keyword, String status) {
|
||||
log.info("查询所有项目列表, pageNum: {}, pageSize: {}", pageNum, pageSize);
|
||||
|
||||
Page<Project> page = new Page<>(pageNum, pageSize);
|
||||
|
||||
LambdaQueryWrapper<Project> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(Project::getDeleted, 0);
|
||||
|
||||
if (keyword != null && !keyword.isEmpty()) {
|
||||
wrapper.and(w -> w.like(Project::getProjectName, keyword)
|
||||
.or()
|
||||
.like(Project::getProjectCode, keyword));
|
||||
}
|
||||
|
||||
if (status != null && !status.isEmpty()) {
|
||||
wrapper.eq(Project::getStatus, status);
|
||||
}
|
||||
|
||||
wrapper.orderByDesc(Project::getCreateTime);
|
||||
|
||||
Page<Project> projectPage = projectMapper.selectPage(page, wrapper);
|
||||
|
||||
List<ProjectListVO> voList = projectPage.getRecords().stream()
|
||||
.map(this::convertToProjectListVO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return TableDataInfo.build(new Page<ProjectListVO>(projectPage.getCurrent(), projectPage.getSize(), projectPage.getTotal())
|
||||
.setRecords(voList));
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为项目列表VO
|
||||
*/
|
||||
private ProjectListVO convertToProjectListVO(Project project) {
|
||||
ProjectListVO vo = new ProjectListVO();
|
||||
vo.setId(project.getId());
|
||||
vo.setProjectCode(project.getProjectCode());
|
||||
vo.setProjectName(project.getProjectName());
|
||||
vo.setProjectType(project.getProjectType());
|
||||
vo.setManagerId(project.getManagerId());
|
||||
vo.setPlanStartDate(project.getPlanStartDate());
|
||||
vo.setPlanEndDate(project.getPlanEndDate());
|
||||
vo.setProgress(project.getProgress());
|
||||
vo.setStatus(project.getStatus());
|
||||
vo.setPriority(project.getPriority());
|
||||
vo.setRiskLevel(project.getRiskLevel());
|
||||
vo.setTags(project.getTags());
|
||||
vo.setBudget(project.getBudget());
|
||||
vo.setCost(project.getCost());
|
||||
vo.setCreateTime(project.getCreateTime());
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为甘特图任务VO(里程碑)
|
||||
*/
|
||||
private GanttTaskVO convertMilestoneToGanttTask(ProjectMilestone milestone) {
|
||||
GanttTaskVO vo = new GanttTaskVO();
|
||||
vo.setId(milestone.getId());
|
||||
vo.setTaskName(milestone.getMilestoneName());
|
||||
vo.setType("milestone");
|
||||
vo.setStartDate(milestone.getPlanDate());
|
||||
vo.setEndDate(milestone.getPlanDate());
|
||||
vo.setActualStartDate(milestone.getActualDate());
|
||||
vo.setActualEndDate(milestone.getActualDate());
|
||||
vo.setProgress(milestone.getProgress());
|
||||
vo.setStatus(milestone.getStatus());
|
||||
vo.setSortOrder(milestone.getSortOrder());
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为甘特图任务VO(任务)
|
||||
*/
|
||||
private GanttTaskVO convertTaskToGanttTask(Task task) {
|
||||
GanttTaskVO vo = new GanttTaskVO();
|
||||
vo.setId(task.getId());
|
||||
vo.setTaskName(task.getTaskName());
|
||||
vo.setType("task");
|
||||
vo.setStartDate(task.getPlanStartDate());
|
||||
vo.setEndDate(task.getPlanEndDate());
|
||||
vo.setActualStartDate(task.getActualStartDate());
|
||||
vo.setActualEndDate(task.getActualEndDate());
|
||||
vo.setProgress(task.getProgress());
|
||||
vo.setStatus(task.getStatus());
|
||||
vo.setPriority(task.getPriority());
|
||||
vo.setSortOrder(task.getSortOrder());
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,15 @@ import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.yinlihupo.common.config.FeishuConfig;
|
||||
import cn.yinlihupo.common.constant.PermissionConstants;
|
||||
import cn.yinlihupo.common.util.PhoneUtils;
|
||||
import cn.yinlihupo.domain.entity.SysRole;
|
||||
import cn.yinlihupo.domain.entity.SysUser;
|
||||
import cn.yinlihupo.domain.entity.SysUserRole;
|
||||
import cn.yinlihupo.mapper.SysPermissionMapper;
|
||||
import cn.yinlihupo.mapper.SysRoleMapper;
|
||||
import cn.yinlihupo.mapper.SysUserMapper;
|
||||
import cn.yinlihupo.mapper.SysUserRoleMapper;
|
||||
import cn.yinlihupo.service.system.FeishuAuthService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -33,6 +38,8 @@ public class FeishuAuthServiceImpl implements FeishuAuthService {
|
||||
private final FeishuConfig feishuConfig;
|
||||
private final SysUserMapper sysUserMapper;
|
||||
private final SysPermissionMapper sysPermissionMapper;
|
||||
private final SysRoleMapper sysRoleMapper;
|
||||
private final SysUserRoleMapper sysUserRoleMapper;
|
||||
|
||||
/**
|
||||
* 飞书OAuth授权端点
|
||||
@@ -207,11 +214,47 @@ public class FeishuAuthServiceImpl implements FeishuAuthService {
|
||||
// 飞书登录用户不需要密码,设置一个随机密码
|
||||
user.setPassword("FEISHU_OAUTH_USER");
|
||||
sysUserMapper.insert(user);
|
||||
|
||||
// 新用户注册时,自动分配"普通成员"角色
|
||||
assignDefaultRole(user.getId(), now);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为新用户分配默认角色(普通成员)
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param now 当前时间
|
||||
*/
|
||||
private void assignDefaultRole(Long userId, LocalDateTime now) {
|
||||
try {
|
||||
// 查询"普通成员"角色
|
||||
LambdaQueryWrapper<SysRole> roleWrapper = new LambdaQueryWrapper<>();
|
||||
roleWrapper.eq(SysRole::getRoleCode, PermissionConstants.ROLE_MEMBER)
|
||||
.eq(SysRole::getDeleted, 0);
|
||||
SysRole memberRole = sysRoleMapper.selectOne(roleWrapper);
|
||||
|
||||
if (memberRole == null) {
|
||||
log.warn("未找到'普通成员'角色,跳过角色分配");
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建用户角色关联
|
||||
SysUserRole userRole = new SysUserRole();
|
||||
userRole.setUserId(userId);
|
||||
userRole.setRoleId(memberRole.getId());
|
||||
userRole.setCreateTime(now);
|
||||
sysUserRoleMapper.insert(userRole);
|
||||
|
||||
log.info("为新用户 {} 分配默认角色 '普通成员' 成功", userId);
|
||||
} catch (Exception e) {
|
||||
log.error("为新用户 {} 分配默认角色失败", userId, e);
|
||||
// 角色分配失败不影响用户创建,只记录日志
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SysUser getUserById(Long userId) {
|
||||
if (userId == null) {
|
||||
|
||||
@@ -37,7 +37,7 @@ spring:
|
||||
base-url: https://sg1.proxy.yinlihupo.cc/proxy/https://openrouter.ai/api
|
||||
chat:
|
||||
options:
|
||||
model: moonshotai/kimi-k2-thinking
|
||||
model: google/gemini-3.1-pro-preview
|
||||
|
||||
# MinIO 对象存储配置
|
||||
minio:
|
||||
|
||||
Reference in New Issue
Block a user