feat(project): 新增里程碑与任务管理控制器

- 创建ProjectMilestoneController,实现里程碑的增删改查及进度状态更新
- 创建TaskController,实现任务的增删改查及进度状态更新
- 添加里程碑关键任务的延期及即将到期查询接口
- 添加任务我的待办及依赖关系查询接口
- 在SysUserMapper新增根据真实姓名查询用户接口
- 在ProjectServiceImpl中根据成员真实姓名匹配用户ID,完善成员信息关联
- 更新SysUserMapper.xml添加根据真实姓名查询SQL语句
This commit is contained in:
2026-03-31 16:24:11 +08:00
parent 88c9fe5e06
commit cf065dea74
5 changed files with 451 additions and 1 deletions

View File

@@ -0,0 +1,212 @@
package cn.yinlihupo.controller.project;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.yinlihupo.common.core.BaseResponse;
import cn.yinlihupo.common.util.ResultUtils;
import cn.yinlihupo.domain.entity.ProjectMilestone;
import cn.yinlihupo.mapper.ProjectMilestoneMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
/**
* 项目里程碑管理控制器
*/
@Slf4j
@RestController
@RequestMapping("/api/v1/milestone")
@RequiredArgsConstructor
public class ProjectMilestoneController {
private final ProjectMilestoneMapper milestoneMapper;
/**
* 分页查询里程碑列表
*/
@SaCheckPermission("project:milestone:list")
@GetMapping("/list")
public BaseResponse<Page<Map<String, Object>>> list(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) Long projectId,
@RequestParam(required = false) String status) {
Page<Map<String, Object>> page = new Page<>(pageNum, pageSize);
List<Map<String, Object>> records = milestoneMapper.selectMilestoneListWithStats(projectId, status);
page.setRecords(records);
page.setTotal(records.size());
return ResultUtils.success("查询成功", page);
}
/**
* 根据ID查询里程碑详情
*/
@SaCheckPermission("project:milestone:view")
@GetMapping("/{id}")
public BaseResponse<ProjectMilestone> getById(@PathVariable Long id) {
ProjectMilestone milestone = milestoneMapper.selectById(id);
if (milestone == null || milestone.getDeleted() == 1) {
return ResultUtils.error("里程碑不存在");
}
return ResultUtils.success("查询成功", milestone);
}
/**
* 新增里程碑
*/
@SaCheckPermission("project:milestone:create")
@PostMapping
public BaseResponse<Long> create(@RequestBody ProjectMilestone milestone) {
if (milestone.getProjectId() == null) {
return ResultUtils.error("项目ID不能为空");
}
if (!StringUtils.hasText(milestone.getMilestoneName())) {
return ResultUtils.error("里程碑名称不能为空");
}
// 设置默认值
if (milestone.getStatus() == null) {
milestone.setStatus("pending");
}
if (milestone.getProgress() == null) {
milestone.setProgress(0);
}
if (milestone.getIsKey() == null) {
milestone.setIsKey(0);
}
milestoneMapper.insert(milestone);
log.info("新增里程碑成功, id: {}, milestoneName: {}", milestone.getId(), milestone.getMilestoneName());
return ResultUtils.success("新增成功", milestone.getId());
}
/**
* 修改里程碑
*/
@SaCheckPermission("project:milestone:update")
@PutMapping
public BaseResponse<Void> update(@RequestBody ProjectMilestone milestone) {
if (milestone.getId() == null) {
return ResultUtils.error("里程碑ID不能为空");
}
ProjectMilestone exist = milestoneMapper.selectById(milestone.getId());
if (exist == null || exist.getDeleted() == 1) {
return ResultUtils.error("里程碑不存在");
}
// 不更新敏感字段
milestone.setCreateTime(null);
milestone.setDeleted(null);
milestoneMapper.updateById(milestone);
log.info("修改里程碑成功, id: {}", milestone.getId());
return ResultUtils.success("修改成功", null);
}
/**
* 删除里程碑
*/
@SaCheckPermission("project:milestone:delete")
@DeleteMapping("/{id}")
public BaseResponse<Void> delete(@PathVariable Long id) {
ProjectMilestone milestone = milestoneMapper.selectById(id);
if (milestone == null || milestone.getDeleted() == 1) {
return ResultUtils.error("里程碑不存在");
}
milestoneMapper.deleteById(id);
log.info("删除里程碑成功, id: {}", id);
return ResultUtils.success("删除成功", null);
}
/**
* 更新里程碑进度
*/
@SaCheckPermission("project:milestone:update")
@PutMapping("/{id}/progress")
public BaseResponse<Void> updateProgress(@PathVariable Long id, @RequestParam Integer progress) {
if (progress < 0 || progress > 100) {
return ResultUtils.error("进度值必须在0-100之间");
}
ProjectMilestone milestone = milestoneMapper.selectById(id);
if (milestone == null || milestone.getDeleted() == 1) {
return ResultUtils.error("里程碑不存在");
}
milestone.setProgress(progress);
if (progress == 100) {
milestone.setStatus("completed");
milestone.setActualDate(LocalDate.now());
} else if (progress > 0) {
milestone.setStatus("in_progress");
}
milestoneMapper.updateById(milestone);
log.info("更新里程碑进度成功, id: {}, progress: {}", id, progress);
return ResultUtils.success("更新成功", null);
}
/**
* 更新里程碑状态
*/
@SaCheckPermission("project:milestone:update")
@PutMapping("/{id}/status")
public BaseResponse<Void> updateStatus(@PathVariable Long id, @RequestParam String status) {
ProjectMilestone milestone = milestoneMapper.selectById(id);
if (milestone == null || milestone.getDeleted() == 1) {
return ResultUtils.error("里程碑不存在");
}
milestone.setStatus(status);
if ("completed".equals(status)) {
milestone.setProgress(100);
milestone.setActualDate(LocalDate.now());
}
milestoneMapper.updateById(milestone);
log.info("更新里程碑状态成功, id: {}, status: {}", id, status);
return ResultUtils.success("更新成功", null);
}
/**
* 查询已延期的关键里程碑
*/
@SaCheckPermission("project:milestone:view")
@GetMapping("/delayed-key")
public BaseResponse<List<ProjectMilestone>> getDelayedKeyMilestones(@RequestParam Long projectId) {
List<ProjectMilestone> milestones = milestoneMapper.selectDelayedKeyMilestones(projectId);
return ResultUtils.success("查询成功", milestones);
}
/**
* 查询即将到期的里程碑
*/
@SaCheckPermission("project:milestone:view")
@GetMapping("/upcoming")
public BaseResponse<List<ProjectMilestone>> getUpcomingMilestones(
@RequestParam Long projectId,
@RequestParam(defaultValue = "7") int days) {
List<ProjectMilestone> milestones = milestoneMapper.selectUpcomingMilestones(projectId, days);
return ResultUtils.success("查询成功", milestones);
}
/**
* 查询里程碑完成进度统计
*/
@SaCheckPermission("project:milestone:stats")
@GetMapping("/stats/progress")
public BaseResponse<Map<String, Object>> getProgressSummary(@RequestParam Long projectId) {
Map<String, Object> stats = milestoneMapper.selectMilestoneProgressSummary(projectId);
return ResultUtils.success("查询成功", stats);
}
}

View File

@@ -0,0 +1,213 @@
package cn.yinlihupo.controller.project;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.yinlihupo.common.core.BaseResponse;
import cn.yinlihupo.common.util.ResultUtils;
import cn.yinlihupo.domain.entity.Task;
import cn.yinlihupo.mapper.TaskMapper;
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.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 任务管理控制器
*/
@Slf4j
@RestController
@RequestMapping("/api/v1/task")
@RequiredArgsConstructor
public class TaskController {
private final TaskMapper taskMapper;
/**
* 分页查询任务列表
*/
@SaCheckPermission("project:task:list")
@GetMapping("/list")
public BaseResponse<Page<Map<String, Object>>> list(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) Long projectId,
@RequestParam(required = false) Long milestoneId,
@RequestParam(required = false) Long assigneeId,
@RequestParam(required = false) String status,
@RequestParam(required = false) String priority,
@RequestParam(required = false) String keyword) {
Page<Map<String, Object>> page = new Page<>(pageNum, pageSize);
List<Map<String, Object>> records = taskMapper.selectTaskPageWithAssignee(
projectId, milestoneId, assigneeId, status, priority, keyword);
page.setRecords(records);
page.setTotal(records.size());
return ResultUtils.success("查询成功", page);
}
/**
* 根据ID查询任务详情
*/
@SaCheckPermission("project:task:view")
@GetMapping("/{id}")
public BaseResponse<Task> getById(@PathVariable Long id) {
Task task = taskMapper.selectById(id);
if (task == null || task.getDeleted() == 1) {
return ResultUtils.error("任务不存在");
}
return ResultUtils.success("查询成功", task);
}
/**
* 新增任务
*/
@SaCheckPermission("project:task:create")
@PostMapping
public BaseResponse<Long> create(@RequestBody Task task) {
if (task.getProjectId() == null) {
return ResultUtils.error("项目ID不能为空");
}
if (!StringUtils.hasText(task.getTaskName())) {
return ResultUtils.error("任务名称不能为空");
}
// 设置默认值
if (task.getStatus() == null) {
task.setStatus("pending");
}
if (task.getProgress() == null) {
task.setProgress(0);
}
taskMapper.insert(task);
log.info("新增任务成功, id: {}, taskName: {}", task.getId(), task.getTaskName());
return ResultUtils.success("新增成功", task.getId());
}
/**
* 修改任务
*/
@SaCheckPermission("project:task:update")
@PutMapping
public BaseResponse<Void> update(@RequestBody Task task) {
if (task.getId() == null) {
return ResultUtils.error("任务ID不能为空");
}
Task exist = taskMapper.selectById(task.getId());
if (exist == null || exist.getDeleted() == 1) {
return ResultUtils.error("任务不存在");
}
// 不更新敏感字段
task.setCreateTime(null);
task.setDeleted(null);
task.setTaskCode(null);
taskMapper.updateById(task);
log.info("修改任务成功, id: {}", task.getId());
return ResultUtils.success("修改成功", null);
}
/**
* 删除任务
*/
@SaCheckPermission("project:task:delete")
@DeleteMapping("/{id}")
public BaseResponse<Void> delete(@PathVariable Long id) {
Task task = taskMapper.selectById(id);
if (task == null || task.getDeleted() == 1) {
return ResultUtils.error("任务不存在");
}
taskMapper.deleteById(id);
log.info("删除任务成功, id: {}", id);
return ResultUtils.success("删除成功", null);
}
/**
* 查询我的待办任务
*/
@SaCheckPermission("project:task:my")
@GetMapping("/my-tasks")
public BaseResponse<List<Task>> getMyTasks(
@RequestParam Long userId,
@RequestParam(required = false) Long projectId) {
List<Task> tasks = taskMapper.selectMyTasks(userId, projectId);
return ResultUtils.success("查询成功", tasks);
}
/**
* 更新任务进度
*/
@SaCheckPermission("project:task:update")
@PutMapping("/{id}/progress")
public BaseResponse<Void> updateProgress(@PathVariable Long id, @RequestParam Integer progress) {
if (progress < 0 || progress > 100) {
return ResultUtils.error("进度值必须在0-100之间");
}
Task task = taskMapper.selectById(id);
if (task == null || task.getDeleted() == 1) {
return ResultUtils.error("任务不存在");
}
task.setProgress(progress);
if (progress == 100) {
task.setStatus("completed");
} else if (progress > 0) {
task.setStatus("in_progress");
}
taskMapper.updateById(task);
log.info("更新任务进度成功, id: {}, progress: {}", id, progress);
return ResultUtils.success("更新成功", null);
}
/**
* 更新任务状态
*/
@SaCheckPermission("project:task:update")
@PutMapping("/{id}/status")
public BaseResponse<Void> updateStatus(@PathVariable Long id, @RequestParam String status) {
Task task = taskMapper.selectById(id);
if (task == null || task.getDeleted() == 1) {
return ResultUtils.error("任务不存在");
}
task.setStatus(status);
if ("completed".equals(status)) {
task.setProgress(100);
}
taskMapper.updateById(task);
log.info("更新任务状态成功, id: {}, status: {}", id, status);
return ResultUtils.success("更新成功", null);
}
/**
* 查询任务依赖关系
*/
@SaCheckPermission("project:task:view")
@GetMapping("/{id}/dependencies")
public BaseResponse<List<Map<String, Object>>> getDependencies(@PathVariable Long id) {
List<Map<String, Object>> dependencies = taskMapper.selectDependencies(id);
return ResultUtils.success("查询成功", dependencies);
}
/**
* 统计项目任务状态分布
*/
@SaCheckPermission("project:task:stats")
@GetMapping("/stats/status")
public BaseResponse<List<Map<String, Object>>> countByStatus(@RequestParam Long projectId) {
List<Map<String, Object>> stats = taskMapper.countTasksByStatus(projectId);
return ResultUtils.success("查询成功", stats);
}
}

View File

@@ -39,4 +39,9 @@ public interface SysUserMapper extends BaseMapper<SysUser> {
* 查询项目成员用户详情列表
*/
List<SysUser> selectUsersByProjectId(@Param("projectId") Long projectId);
/**
* 根据真实姓名查询用户
*/
SysUser selectByRealName(@Param("realName") String realName);
}

View File

@@ -467,7 +467,18 @@ public class ProjectServiceImpl implements ProjectService {
member.setWeeklyHours(BigDecimal.valueOf(info.getWeeklyHours()));
}
member.setStatus(1);
// TODO: 需要根据name查找或创建用户暂时留空
// 根据成员名字匹配 sys_user 用户
if (info.getName() != null && !info.getName().isEmpty()) {
SysUser user = sysUserMapper.selectByRealName(info.getName());
if (user != null) {
member.setUserId(user.getId());
log.debug("成员 '{}' 匹配到用户 ID: {}", info.getName(), user.getId());
} else {
log.warn("成员 '{}' 未在系统中找到匹配的用户", info.getName());
}
}
return member;
}

View File

@@ -88,4 +88,13 @@
ORDER BY pm.create_time ASC
</select>
<!-- 根据真实姓名查询用户 -->
<select id="selectByRealName" resultMap="BaseResultMap">
SELECT *
FROM sys_user
WHERE real_name = #{realName}
AND deleted = 0
AND status = 1
</select>
</mapper>