feat(risk): 实现风险管理模块功能

- 新增RiskMapper,定义风险相关SQL映射和查询功能
- 添加CreateRiskRequest、CreateWorkOrderRequest和ProcessWorkOrderRequest请求DTO
- 实现RiskController,支持风险的创建、更新、删除、详细查询及列表分页查询
- 提供风险统计接口,统计风险总数、状态分布和等级分布
- 增加风险分配工单及批量更新状态的接口
- 实现RiskService及其实现类,包含风险的增删改查及业务逻辑
- 计算风险得分和风险等级,并支持标签和工单关联管理
- 定义RiskVO和RiskStatisticsVO用于前端数据展示和统计
- 实现风险工单的创建和管理,关联风险状态同步更新
This commit is contained in:
2026-03-30 11:55:49 +08:00
parent cd56e2e987
commit 4e1415a033
17 changed files with 2554 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
package cn.yinlihupo.service.risk;
import cn.yinlihupo.common.page.TableDataInfo;
import cn.yinlihupo.domain.dto.CreateRiskRequest;
import cn.yinlihupo.domain.dto.CreateWorkOrderRequest;
import cn.yinlihupo.domain.vo.RiskStatisticsVO;
import cn.yinlihupo.domain.vo.RiskVO;
/**
* 风险服务接口
*/
public interface RiskService {
/**
* 创建风险评估
*
* @param request 创建请求
* @return 风险ID
*/
Long createRisk(CreateRiskRequest request);
/**
* 更新风险
*
* @param riskId 风险ID
* @param request 更新请求
* @return 是否成功
*/
Boolean updateRisk(Long riskId, CreateRiskRequest request);
/**
* 删除风险
*
* @param riskId 风险ID
* @return 是否成功
*/
Boolean deleteRisk(Long riskId);
/**
* 获取风险详情
*
* @param riskId 风险ID
* @return 风险详情
*/
RiskVO getRiskDetail(Long riskId);
/**
* 分页查询风险列表
*
* @param projectId 项目ID
* @param pageNum 页码
* @param pageSize 每页大小
* @param category 分类筛选
* @param riskLevel 风险等级筛选
* @param status 状态筛选
* @param keyword 关键词搜索
* @return 分页风险列表
*/
TableDataInfo<RiskVO> getRiskList(Long projectId, Integer pageNum, Integer pageSize,
String category, String riskLevel, String status, String keyword);
/**
* 获取风险统计信息
*
* @param projectId 项目ID
* @return 统计信息
*/
RiskStatisticsVO getRiskStatistics(Long projectId);
/**
* 为风险分配工单
*
* @param riskId 风险ID
* @param request 工单创建请求
* @return 工单ID
*/
Long assignWorkOrder(Long riskId, CreateWorkOrderRequest request);
/**
* 批量更新风险状态
*
* @param riskIds 风险ID列表
* @param status 新状态
* @return 是否成功
*/
Boolean batchUpdateStatus(java.util.List<Long> riskIds, String status);
}

View File

@@ -0,0 +1,105 @@
package cn.yinlihupo.service.risk;
import cn.yinlihupo.common.page.TableDataInfo;
import cn.yinlihupo.domain.dto.CreateWorkOrderRequest;
import cn.yinlihupo.domain.dto.ProcessWorkOrderRequest;
import cn.yinlihupo.domain.vo.WorkOrderStatisticsVO;
import cn.yinlihupo.domain.vo.WorkOrderVO;
/**
* 工单服务接口
*/
public interface WorkOrderService {
/**
* 创建工单
*
* @param request 创建请求
* @return 工单ID
*/
Long createWorkOrder(CreateWorkOrderRequest request);
/**
* 更新工单
*
* @param workOrderId 工单ID
* @param request 更新请求
* @return 是否成功
*/
Boolean updateWorkOrder(Long workOrderId, CreateWorkOrderRequest request);
/**
* 删除工单
*
* @param workOrderId 工单ID
* @return 是否成功
*/
Boolean deleteWorkOrder(Long workOrderId);
/**
* 获取工单详情
*
* @param workOrderId 工单ID
* @return 工单详情
*/
WorkOrderVO getWorkOrderDetail(Long workOrderId);
/**
* 分页查询工单列表
*
* @param projectId 项目ID
* @param pageNum 页码
* @param pageSize 每页大小
* @param orderType 类型筛选
* @param status 状态筛选
* @param priority 优先级筛选
* @param keyword 关键词搜索
* @return 分页工单列表
*/
TableDataInfo<WorkOrderVO> getWorkOrderList(Long projectId, Integer pageNum, Integer pageSize,
String orderType, String status, String priority, String keyword);
/**
* 获取我的工单列表
*
* @param pageNum 页码
* @param pageSize 每页大小
* @param status 状态筛选
* @param orderType 类型筛选
* @return 分页工单列表
*/
TableDataInfo<WorkOrderVO> getMyWorkOrders(Integer pageNum, Integer pageSize,
String status, String orderType);
/**
* 处理工单
*
* @param request 处理请求
* @return 是否成功
*/
Boolean processWorkOrder(ProcessWorkOrderRequest request);
/**
* 分配工单给处理人
*
* @param workOrderId 工单ID
* @param handlerId 处理人ID
* @return 是否成功
*/
Boolean assignWorkOrder(Long workOrderId, Long handlerId);
/**
* 获取工单统计信息
*
* @param projectId 项目ID可选
* @return 统计信息
*/
WorkOrderStatisticsVO getWorkOrderStatistics(Long projectId);
/**
* 获取我的工单统计信息
*
* @return 统计信息
*/
WorkOrderStatisticsVO getMyWorkOrderStatistics();
}

View File

@@ -0,0 +1,416 @@
package cn.yinlihupo.service.risk.impl;
import cn.hutool.core.util.IdUtil;
import cn.yinlihupo.common.page.TableDataInfo;
import cn.yinlihupo.common.util.SecurityUtils;
import cn.yinlihupo.domain.dto.CreateRiskRequest;
import cn.yinlihupo.domain.dto.CreateWorkOrderRequest;
import cn.yinlihupo.domain.entity.Project;
import cn.yinlihupo.domain.entity.Risk;
import cn.yinlihupo.domain.entity.SysUser;
import cn.yinlihupo.domain.entity.WorkOrder;
import cn.yinlihupo.domain.vo.RiskStatisticsVO;
import cn.yinlihupo.domain.vo.RiskVO;
import cn.yinlihupo.mapper.ProjectMapper;
import cn.yinlihupo.mapper.RiskMapper;
import cn.yinlihupo.mapper.SysUserMapper;
import cn.yinlihupo.mapper.WorkOrderMapper;
import cn.yinlihupo.service.risk.RiskService;
import cn.yinlihupo.service.risk.WorkOrderService;
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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
/**
* 风险服务实现类
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class RiskServiceImpl implements RiskService {
private final RiskMapper riskMapper;
private final ProjectMapper projectMapper;
private final SysUserMapper sysUserMapper;
private final WorkOrderMapper workOrderMapper;
private final WorkOrderService workOrderService;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createRisk(CreateRiskRequest request) {
log.info("创建风险评估, projectId: {}, riskName: {}", request.getProjectId(), request.getRiskName());
// 验证项目存在
Project project = projectMapper.selectById(request.getProjectId());
if (project == null || project.getDeleted() == 1) {
throw new RuntimeException("项目不存在");
}
Risk risk = new Risk();
risk.setProjectId(request.getProjectId());
risk.setRiskCode(generateRiskCode());
risk.setCategory(request.getCategory());
risk.setRiskName(request.getRiskName());
risk.setDescription(request.getDescription());
risk.setRiskSource(request.getRiskSource() != null ? request.getRiskSource() : "manual");
// 计算风险得分和等级
if (request.getProbability() != null && request.getImpact() != null) {
risk.setProbability(BigDecimal.valueOf(request.getProbability()));
risk.setImpact(BigDecimal.valueOf(request.getImpact()));
BigDecimal score = risk.getProbability()
.multiply(risk.getImpact())
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
risk.setRiskScore(score);
risk.setRiskLevel(calculateRiskLevel(request.getProbability(), request.getImpact()));
}
risk.setOwnerId(request.getOwnerId());
risk.setMitigationPlan(request.getMitigationPlan());
risk.setContingencyPlan(request.getContingencyPlan());
risk.setTriggerCondition(request.getTriggerCondition());
risk.setDueDate(request.getDueDate());
risk.setTags(request.getTags());
risk.setStatus("identified");
risk.setDiscoverTime(LocalDateTime.now());
riskMapper.insert(risk);
log.info("风险评估创建成功, riskId: {}", risk.getId());
return risk.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateRisk(Long riskId, CreateRiskRequest request) {
log.info("更新风险, riskId: {}", riskId);
Risk risk = riskMapper.selectById(riskId);
if (risk == null || risk.getDeleted() == 1) {
throw new RuntimeException("风险不存在");
}
if (request.getCategory() != null) {
risk.setCategory(request.getCategory());
}
if (request.getRiskName() != null) {
risk.setRiskName(request.getRiskName());
}
if (request.getDescription() != null) {
risk.setDescription(request.getDescription());
}
if (request.getProbability() != null && request.getImpact() != null) {
risk.setProbability(BigDecimal.valueOf(request.getProbability()));
risk.setImpact(BigDecimal.valueOf(request.getImpact()));
BigDecimal score = risk.getProbability()
.multiply(risk.getImpact())
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
risk.setRiskScore(score);
risk.setRiskLevel(calculateRiskLevel(request.getProbability(), request.getImpact()));
}
if (request.getOwnerId() != null) {
risk.setOwnerId(request.getOwnerId());
}
if (request.getMitigationPlan() != null) {
risk.setMitigationPlan(request.getMitigationPlan());
}
if (request.getContingencyPlan() != null) {
risk.setContingencyPlan(request.getContingencyPlan());
}
if (request.getTriggerCondition() != null) {
risk.setTriggerCondition(request.getTriggerCondition());
}
if (request.getDueDate() != null) {
risk.setDueDate(request.getDueDate());
}
if (request.getTags() != null) {
risk.setTags(request.getTags());
}
riskMapper.updateById(risk);
log.info("风险更新成功, riskId: {}", riskId);
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteRisk(Long riskId) {
log.info("删除风险, riskId: {}", riskId);
Risk risk = riskMapper.selectById(riskId);
if (risk == null || risk.getDeleted() == 1) {
throw new RuntimeException("风险不存在");
}
riskMapper.deleteById(riskId);
log.info("风险删除成功, riskId: {}", riskId);
return true;
}
@Override
public RiskVO getRiskDetail(Long riskId) {
log.info("获取风险详情, riskId: {}", riskId);
Risk risk = riskMapper.selectById(riskId);
if (risk == null || risk.getDeleted() == 1) {
throw new RuntimeException("风险不存在");
}
return convertToRiskVO(risk);
}
@Override
public TableDataInfo<RiskVO> getRiskList(Long projectId, Integer pageNum, Integer pageSize,
String category, String riskLevel, String status, String keyword) {
log.info("分页查询风险列表, projectId: {}, pageNum: {}, pageSize: {}", projectId, pageNum, pageSize);
Page<Risk> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<Risk> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Risk::getDeleted, 0);
if (projectId != null) {
wrapper.eq(Risk::getProjectId, projectId);
}
if (category != null && !category.isEmpty()) {
wrapper.eq(Risk::getCategory, category);
}
if (riskLevel != null && !riskLevel.isEmpty()) {
wrapper.eq(Risk::getRiskLevel, riskLevel);
}
if (status != null && !status.isEmpty()) {
wrapper.eq(Risk::getStatus, status);
}
if (keyword != null && !keyword.isEmpty()) {
wrapper.and(w -> w.like(Risk::getRiskName, keyword)
.or()
.like(Risk::getDescription, keyword));
}
// 按风险等级和得分排序
wrapper.orderByAsc(
r -> "critical".equals(r.getRiskLevel()) ? 1 :
"high".equals(r.getRiskLevel()) ? 2 :
"medium".equals(r.getRiskLevel()) ? 3 : 4
).orderByDesc(Risk::getRiskScore);
Page<Risk> riskPage = riskMapper.selectPage(page, wrapper);
List<RiskVO> voList = riskPage.getRecords().stream()
.map(this::convertToRiskVO)
.collect(Collectors.toList());
return TableDataInfo.build(new Page<RiskVO>(riskPage.getCurrent(), riskPage.getSize(), riskPage.getTotal())
.setRecords(voList));
}
@Override
public RiskStatisticsVO getRiskStatistics(Long projectId) {
log.info("获取风险统计信息, projectId: {}", projectId);
RiskStatisticsVO statistics = new RiskStatisticsVO();
// 构建查询条件
LambdaQueryWrapper<Risk> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Risk::getDeleted, 0);
if (projectId != null) {
wrapper.eq(Risk::getProjectId, projectId);
}
List<Risk> risks = riskMapper.selectList(wrapper);
// 统计总数
statistics.setTotalCount(risks.size());
// 按状态统计
Map<String, Long> statusCount = risks.stream()
.collect(Collectors.groupingBy(Risk::getStatus, Collectors.counting()));
statistics.setIdentifiedCount(statusCount.getOrDefault("identified", 0L).intValue());
statistics.setAssignedCount(statusCount.getOrDefault("assigned", 0L).intValue());
statistics.setMitigatingCount(statusCount.getOrDefault("mitigating", 0L).intValue());
statistics.setResolvedCount(statusCount.getOrDefault("resolved", 0L).intValue());
statistics.setClosedCount(statusCount.getOrDefault("closed", 0L).intValue());
// 按等级统计
Map<String, Long> levelCount = risks.stream()
.collect(Collectors.groupingBy(Risk::getRiskLevel, Collectors.counting()));
statistics.setCriticalCount(levelCount.getOrDefault("critical", 0L).intValue());
statistics.setHighCount(levelCount.getOrDefault("high", 0L).intValue());
statistics.setMediumCount(levelCount.getOrDefault("medium", 0L).intValue());
statistics.setLowCount(levelCount.getOrDefault("low", 0L).intValue());
// 计算平均风险得分
double avgScore = risks.stream()
.filter(r -> r.getRiskScore() != null)
.mapToDouble(r -> r.getRiskScore().doubleValue())
.average()
.orElse(0.0);
statistics.setAverageRiskScore(BigDecimal.valueOf(avgScore).setScale(2, RoundingMode.HALF_UP));
// 统计未解决的高风险数量
int unresolvedHigh = (int) risks.stream()
.filter(r -> !"resolved".equals(r.getStatus()) && !"closed".equals(r.getStatus()))
.filter(r -> "critical".equals(r.getRiskLevel()) || "high".equals(r.getRiskLevel()))
.count();
statistics.setUnresolvedHighCount(unresolvedHigh);
// 按分类统计
Map<String, Long> categoryStats = risks.stream()
.collect(Collectors.groupingBy(
r -> r.getCategory() != null ? r.getCategory() : "other",
Collectors.counting()
));
statistics.setCategoryStats(categoryStats);
// 按等级统计
statistics.setLevelStats(levelCount);
return statistics;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long assignWorkOrder(Long riskId, CreateWorkOrderRequest request) {
log.info("为风险分配工单, riskId: {}", riskId);
Risk risk = riskMapper.selectById(riskId);
if (risk == null || risk.getDeleted() == 1) {
throw new RuntimeException("风险不存在");
}
// 设置工单关联
request.setRiskId(riskId);
request.setProjectId(risk.getProjectId());
request.setOrderType("risk_handle");
request.setSource("risk");
// 创建工单
Long workOrderId = workOrderService.createWorkOrder(request);
// 更新风险的工单关联
List<Long> workOrderIds = risk.getWorkOrderIds();
if (workOrderIds == null) {
workOrderIds = new ArrayList<>();
}
workOrderIds.add(workOrderId);
risk.setWorkOrderIds(workOrderIds);
risk.setStatus("assigned");
riskMapper.updateById(risk);
log.info("风险工单分配成功, riskId: {}, workOrderId: {}", riskId, workOrderId);
return workOrderId;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean batchUpdateStatus(List<Long> riskIds, String status) {
log.info("批量更新风险状态, riskIds: {}, status: {}", riskIds, status);
for (Long riskId : riskIds) {
Risk risk = riskMapper.selectById(riskId);
if (risk != null && risk.getDeleted() == 0) {
risk.setStatus(status);
if ("resolved".equals(status)) {
risk.setResolvedTime(LocalDateTime.now());
}
riskMapper.updateById(risk);
}
}
log.info("批量更新风险状态完成, 更新数量: {}", riskIds.size());
return true;
}
/**
* 生成风险编号
*/
private String generateRiskCode() {
return "RSK" + IdUtil.fastSimpleUUID().substring(0, 12).toUpperCase();
}
/**
* 计算风险等级
*/
private String calculateRiskLevel(Integer probability, Integer impact) {
if (probability == null || impact == null) {
return "low";
}
int score = probability * impact;
if (score >= 300) {
return "critical";
} else if (score >= 200) {
return "high";
} else if (score >= 100) {
return "medium";
} else {
return "low";
}
}
/**
* 转换为VO
*/
private RiskVO convertToRiskVO(Risk risk) {
RiskVO vo = new RiskVO();
vo.setId(risk.getId());
vo.setRiskCode(risk.getRiskCode());
vo.setProjectId(risk.getProjectId());
vo.setCategory(risk.getCategory());
vo.setRiskName(risk.getRiskName());
vo.setDescription(risk.getDescription());
vo.setRiskSource(risk.getRiskSource());
vo.setProbability(risk.getProbability());
vo.setImpact(risk.getImpact());
vo.setRiskScore(risk.getRiskScore());
vo.setRiskLevel(risk.getRiskLevel());
vo.setStatus(risk.getStatus());
vo.setOwnerId(risk.getOwnerId());
vo.setMitigationPlan(risk.getMitigationPlan());
vo.setContingencyPlan(risk.getContingencyPlan());
vo.setTriggerCondition(risk.getTriggerCondition());
vo.setDiscoverTime(risk.getDiscoverTime());
vo.setDueDate(risk.getDueDate());
vo.setResolvedTime(risk.getResolvedTime());
vo.setTags(risk.getTags());
vo.setCreateTime(risk.getCreateTime());
vo.setUpdateTime(risk.getUpdateTime());
// 查询项目名称
if (risk.getProjectId() != null) {
Project project = projectMapper.selectById(risk.getProjectId());
if (project != null) {
vo.setProjectName(project.getProjectName());
}
}
// 查询负责人信息
if (risk.getOwnerId() != null) {
SysUser owner = sysUserMapper.selectById(risk.getOwnerId());
if (owner != null) {
vo.setOwnerName(owner.getRealName());
vo.setOwnerAvatar(owner.getAvatar());
}
}
// 计算关联工单数量
if (risk.getWorkOrderIds() != null) {
vo.setWorkOrderCount(risk.getWorkOrderIds().size());
} else {
vo.setWorkOrderCount(0);
}
return vo;
}
}

View File

@@ -0,0 +1,515 @@
package cn.yinlihupo.service.risk.impl;
import cn.hutool.core.util.IdUtil;
import cn.yinlihupo.common.page.TableDataInfo;
import cn.yinlihupo.common.util.SecurityUtils;
import cn.yinlihupo.domain.dto.CreateWorkOrderRequest;
import cn.yinlihupo.domain.dto.ProcessWorkOrderRequest;
import cn.yinlihupo.domain.entity.*;
import cn.yinlihupo.domain.vo.WorkOrderStatisticsVO;
import cn.yinlihupo.domain.vo.WorkOrderVO;
import cn.yinlihupo.mapper.*;
import cn.yinlihupo.service.risk.WorkOrderService;
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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
/**
* 工单服务实现类
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class WorkOrderServiceImpl implements WorkOrderService {
private final WorkOrderMapper workOrderMapper;
private final ProjectMapper projectMapper;
private final RiskMapper riskMapper;
private final SysUserMapper sysUserMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createWorkOrder(CreateWorkOrderRequest request) {
log.info("创建工单, projectId: {}, title: {}", request.getProjectId(), request.getTitle());
// 验证项目存在
Project project = projectMapper.selectById(request.getProjectId());
if (project == null || project.getDeleted() == 1) {
throw new RuntimeException("项目不存在");
}
WorkOrder workOrder = new WorkOrder();
workOrder.setProjectId(request.getProjectId());
workOrder.setOrderCode(generateOrderCode());
workOrder.setRiskId(request.getRiskId());
workOrder.setOrderType(request.getOrderType() != null ? request.getOrderType() : "other");
workOrder.setTitle(request.getTitle());
workOrder.setDescription(request.getDescription());
workOrder.setPriority(request.getPriority() != null ? request.getPriority() : "medium");
workOrder.setHandlerId(request.getHandlerId());
workOrder.setHandlerGroupId(request.getHandlerGroupId());
workOrder.setDeadline(request.getDeadline());
workOrder.setSource(request.getSource() != null ? request.getSource() : "api");
workOrder.setTags(request.getTags());
// 设置创建人和状态
workOrder.setCreatorId(SecurityUtils.getCurrentUserId());
if (request.getHandlerId() != null) {
workOrder.setStatus("assigned");
workOrder.setAssignedTime(LocalDateTime.now());
} else {
workOrder.setStatus("pending");
}
workOrderMapper.insert(workOrder);
log.info("工单创建成功, workOrderId: {}", workOrder.getId());
return workOrder.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateWorkOrder(Long workOrderId, CreateWorkOrderRequest request) {
log.info("更新工单, workOrderId: {}", workOrderId);
WorkOrder workOrder = workOrderMapper.selectById(workOrderId);
if (workOrder == null || workOrder.getDeleted() == 1) {
throw new RuntimeException("工单不存在");
}
if (request.getTitle() != null) {
workOrder.setTitle(request.getTitle());
}
if (request.getDescription() != null) {
workOrder.setDescription(request.getDescription());
}
if (request.getPriority() != null) {
workOrder.setPriority(request.getPriority());
}
if (request.getHandlerId() != null) {
workOrder.setHandlerId(request.getHandlerId());
if ("pending".equals(workOrder.getStatus())) {
workOrder.setStatus("assigned");
workOrder.setAssignedTime(LocalDateTime.now());
}
}
if (request.getHandlerGroupId() != null) {
workOrder.setHandlerGroupId(request.getHandlerGroupId());
}
if (request.getDeadline() != null) {
workOrder.setDeadline(request.getDeadline());
}
if (request.getTags() != null) {
workOrder.setTags(request.getTags());
}
workOrderMapper.updateById(workOrder);
log.info("工单更新成功, workOrderId: {}", workOrderId);
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWorkOrder(Long workOrderId) {
log.info("删除工单, workOrderId: {}", workOrderId);
WorkOrder workOrder = workOrderMapper.selectById(workOrderId);
if (workOrder == null || workOrder.getDeleted() == 1) {
throw new RuntimeException("工单不存在");
}
workOrderMapper.deleteById(workOrderId);
log.info("工单删除成功, workOrderId: {}", workOrderId);
return true;
}
@Override
public WorkOrderVO getWorkOrderDetail(Long workOrderId) {
log.info("获取工单详情, workOrderId: {}", workOrderId);
WorkOrder workOrder = workOrderMapper.selectById(workOrderId);
if (workOrder == null || workOrder.getDeleted() == 1) {
throw new RuntimeException("工单不存在");
}
return convertToWorkOrderVO(workOrder);
}
@Override
public TableDataInfo<WorkOrderVO> getWorkOrderList(Long projectId, Integer pageNum, Integer pageSize,
String orderType, String status, String priority, String keyword) {
log.info("分页查询工单列表, projectId: {}, pageNum: {}, pageSize: {}", projectId, pageNum, pageSize);
Page<WorkOrder> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<WorkOrder> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(WorkOrder::getDeleted, 0);
if (projectId != null) {
wrapper.eq(WorkOrder::getProjectId, projectId);
}
if (orderType != null && !orderType.isEmpty()) {
wrapper.eq(WorkOrder::getOrderType, orderType);
}
if (status != null && !status.isEmpty()) {
wrapper.eq(WorkOrder::getStatus, status);
}
if (priority != null && !priority.isEmpty()) {
wrapper.eq(WorkOrder::getPriority, priority);
}
if (keyword != null && !keyword.isEmpty()) {
wrapper.and(w -> w.like(WorkOrder::getTitle, keyword)
.or()
.like(WorkOrder::getDescription, keyword));
}
// 按优先级和创建时间排序
wrapper.orderByAsc(
w -> "critical".equals(w.getPriority()) ? 1 :
"high".equals(w.getPriority()) ? 2 :
"medium".equals(w.getPriority()) ? 3 : 4
).orderByDesc(WorkOrder::getCreateTime);
Page<WorkOrder> orderPage = workOrderMapper.selectPage(page, wrapper);
List<WorkOrderVO> voList = orderPage.getRecords().stream()
.map(this::convertToWorkOrderVO)
.collect(Collectors.toList());
return TableDataInfo.build(new Page<WorkOrderVO>(orderPage.getCurrent(), orderPage.getSize(), orderPage.getTotal())
.setRecords(voList));
}
@Override
public TableDataInfo<WorkOrderVO> getMyWorkOrders(Integer pageNum, Integer pageSize,
String status, String orderType) {
Long userId = SecurityUtils.getCurrentUserId();
log.info("查询我的工单列表, userId: {}, pageNum: {}, pageSize: {}", userId, pageNum, pageSize);
Page<WorkOrder> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<WorkOrder> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(WorkOrder::getDeleted, 0)
.eq(WorkOrder::getHandlerId, userId);
if (status != null && !status.isEmpty()) {
wrapper.eq(WorkOrder::getStatus, status);
}
if (orderType != null && !orderType.isEmpty()) {
wrapper.eq(WorkOrder::getOrderType, orderType);
}
// 按优先级和创建时间排序
wrapper.orderByAsc(
w -> "critical".equals(w.getPriority()) ? 1 :
"high".equals(w.getPriority()) ? 2 :
"medium".equals(w.getPriority()) ? 3 : 4
).orderByDesc(WorkOrder::getCreateTime);
Page<WorkOrder> orderPage = workOrderMapper.selectPage(page, wrapper);
List<WorkOrderVO> voList = orderPage.getRecords().stream()
.map(this::convertToWorkOrderVO)
.collect(Collectors.toList());
return TableDataInfo.build(new Page<WorkOrderVO>(orderPage.getCurrent(), orderPage.getSize(), orderPage.getTotal())
.setRecords(voList));
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean processWorkOrder(ProcessWorkOrderRequest request) {
Long userId = SecurityUtils.getCurrentUserId();
log.info("处理工单, workOrderId: {}, userId: {}", request.getWorkOrderId(), userId);
WorkOrder workOrder = workOrderMapper.selectById(request.getWorkOrderId());
if (workOrder == null || workOrder.getDeleted() == 1) {
throw new RuntimeException("工单不存在");
}
// 验证是否为处理人
if (!userId.equals(workOrder.getHandlerId())) {
throw new RuntimeException("您不是该工单的处理人");
}
String oldStatus = workOrder.getStatus();
String newStatus = request.getStatus();
// 更新状态
if (newStatus != null) {
workOrder.setStatus(newStatus);
// 记录首次响应时间
if ("processing".equals(newStatus) && workOrder.getFirstResponseTime() == null) {
workOrder.setFirstResponseTime(LocalDateTime.now());
}
// 记录解决时间
if ("resolved".equals(newStatus)) {
workOrder.setResolvedTime(LocalDateTime.now());
}
// 记录关闭时间
if ("closed".equals(newStatus)) {
workOrder.setClosedTime(LocalDateTime.now());
}
}
// 更新满意度评分
if (request.getSatisfactionScore() != null) {
workOrder.setSatisfactionScore(request.getSatisfactionScore());
}
workOrderMapper.updateById(workOrder);
// 如果是风险工单,更新风险状态
if ("resolved".equals(newStatus) && workOrder.getRiskId() != null) {
Risk risk = riskMapper.selectById(workOrder.getRiskId());
if (risk != null && "assigned".equals(risk.getStatus())) {
risk.setStatus("mitigating");
riskMapper.updateById(risk);
}
}
log.info("工单处理成功, workOrderId: {}, status: {} -> {}", request.getWorkOrderId(), oldStatus, newStatus);
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean assignWorkOrder(Long workOrderId, Long handlerId) {
log.info("分配工单, workOrderId: {}, handlerId: {}", workOrderId, handlerId);
WorkOrder workOrder = workOrderMapper.selectById(workOrderId);
if (workOrder == null || workOrder.getDeleted() == 1) {
throw new RuntimeException("工单不存在");
}
workOrder.setHandlerId(handlerId);
workOrder.setStatus("assigned");
workOrder.setAssignedTime(LocalDateTime.now());
workOrderMapper.updateById(workOrder);
log.info("工单分配成功, workOrderId: {}", workOrderId);
return true;
}
@Override
public WorkOrderStatisticsVO getWorkOrderStatistics(Long projectId) {
log.info("获取工单统计信息, projectId: {}", projectId);
WorkOrderStatisticsVO statistics = new WorkOrderStatisticsVO();
// 构建查询条件
LambdaQueryWrapper<WorkOrder> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(WorkOrder::getDeleted, 0);
if (projectId != null) {
wrapper.eq(WorkOrder::getProjectId, projectId);
}
List<WorkOrder> orders = workOrderMapper.selectList(wrapper);
// 统计总数
statistics.setTotalCount(orders.size());
// 按状态统计
Map<String, Long> statusCount = orders.stream()
.collect(Collectors.groupingBy(WorkOrder::getStatus, Collectors.counting()));
statistics.setPendingCount(statusCount.getOrDefault("pending", 0L).intValue());
statistics.setAssignedCount(statusCount.getOrDefault("assigned", 0L).intValue());
statistics.setProcessingCount(statusCount.getOrDefault("processing", 0L).intValue());
statistics.setCompletedCount(statusCount.getOrDefault("resolved", 0L).intValue());
statistics.setClosedCount(statusCount.getOrDefault("closed", 0L).intValue());
statistics.setRejectedCount(0); // 新结构没有 rejected 状态
// 统计超期未完成
LocalDateTime now = LocalDateTime.now();
int overdueCount = (int) orders.stream()
.filter(o -> !"resolved".equals(o.getStatus()) && !"closed".equals(o.getStatus()))
.filter(o -> o.getDeadline() != null && o.getDeadline().isBefore(now))
.count();
statistics.setOverdueCount(overdueCount);
// 统计即将超期7天内
LocalDateTime sevenDaysLater = now.plusDays(7);
int aboutToExpireCount = (int) orders.stream()
.filter(o -> !"resolved".equals(o.getStatus()) && !"closed".equals(o.getStatus()))
.filter(o -> o.getDeadline() != null
&& !o.getDeadline().isBefore(now)
&& !o.getDeadline().isAfter(sevenDaysLater))
.count();
statistics.setAboutToExpireCount(aboutToExpireCount);
// 按类型统计
Map<String, Long> typeStats = orders.stream()
.collect(Collectors.groupingBy(
o -> o.getOrderType() != null ? o.getOrderType() : "other",
Collectors.counting()
));
statistics.setTypeStats(typeStats);
// 按优先级统计
Map<String, Long> priorityStats = orders.stream()
.collect(Collectors.groupingBy(
o -> o.getPriority() != null ? o.getPriority() : "medium",
Collectors.counting()
));
statistics.setPriorityStats(priorityStats);
return statistics;
}
@Override
public WorkOrderStatisticsVO getMyWorkOrderStatistics() {
Long userId = SecurityUtils.getCurrentUserId();
log.info("获取我的工单统计信息, userId: {}", userId);
WorkOrderStatisticsVO statistics = new WorkOrderStatisticsVO();
// 构建查询条件
LambdaQueryWrapper<WorkOrder> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(WorkOrder::getDeleted, 0)
.eq(WorkOrder::getHandlerId, userId);
List<WorkOrder> orders = workOrderMapper.selectList(wrapper);
// 统计总数
statistics.setTotalCount(orders.size());
// 按状态统计
Map<String, Long> statusCount = orders.stream()
.collect(Collectors.groupingBy(WorkOrder::getStatus, Collectors.counting()));
statistics.setPendingCount(statusCount.getOrDefault("pending", 0L).intValue());
statistics.setAssignedCount(statusCount.getOrDefault("assigned", 0L).intValue());
statistics.setProcessingCount(statusCount.getOrDefault("processing", 0L).intValue());
statistics.setCompletedCount(statusCount.getOrDefault("resolved", 0L).intValue());
statistics.setClosedCount(statusCount.getOrDefault("closed", 0L).intValue());
statistics.setRejectedCount(0);
// 统计超期未完成
LocalDateTime now = LocalDateTime.now();
int overdueCount = (int) orders.stream()
.filter(o -> !"resolved".equals(o.getStatus()) && !"closed".equals(o.getStatus()))
.filter(o -> o.getDeadline() != null && o.getDeadline().isBefore(now))
.count();
statistics.setOverdueCount(overdueCount);
// 统计即将超期7天内
LocalDateTime sevenDaysLater = now.plusDays(7);
int aboutToExpireCount = (int) orders.stream()
.filter(o -> !"resolved".equals(o.getStatus()) && !"closed".equals(o.getStatus()))
.filter(o -> o.getDeadline() != null
&& !o.getDeadline().isBefore(now)
&& !o.getDeadline().isAfter(sevenDaysLater))
.count();
statistics.setAboutToExpireCount(aboutToExpireCount);
// 按类型统计
Map<String, Long> typeStats = orders.stream()
.collect(Collectors.groupingBy(
o -> o.getOrderType() != null ? o.getOrderType() : "other",
Collectors.counting()
));
statistics.setTypeStats(typeStats);
// 按优先级统计
Map<String, Long> priorityStats = orders.stream()
.collect(Collectors.groupingBy(
o -> o.getPriority() != null ? o.getPriority() : "medium",
Collectors.counting()
));
statistics.setPriorityStats(priorityStats);
return statistics;
}
/**
* 生成工单编号
*/
private String generateOrderCode() {
return "WO" + IdUtil.fastSimpleUUID().substring(0, 12).toUpperCase();
}
/**
* 转换为VO
*/
private WorkOrderVO convertToWorkOrderVO(WorkOrder workOrder) {
WorkOrderVO vo = new WorkOrderVO();
vo.setId(workOrder.getId());
vo.setOrderCode(workOrder.getOrderCode());
vo.setOrderType(workOrder.getOrderType());
vo.setProjectId(workOrder.getProjectId());
vo.setRiskId(workOrder.getRiskId());
vo.setTitle(workOrder.getTitle());
vo.setDescription(workOrder.getDescription());
vo.setCreatorId(workOrder.getCreatorId());
vo.setHandlerId(workOrder.getHandlerId());
vo.setHandlerGroupId(workOrder.getHandlerGroupId());
vo.setPriority(workOrder.getPriority());
vo.setStatus(workOrder.getStatus());
vo.setSource(workOrder.getSource());
vo.setDeadline(workOrder.getDeadline());
vo.setAssignedTime(workOrder.getAssignedTime());
vo.setFirstResponseTime(workOrder.getFirstResponseTime());
vo.setResolvedTime(workOrder.getResolvedTime());
vo.setClosedTime(workOrder.getClosedTime());
vo.setSatisfactionScore(workOrder.getSatisfactionScore());
vo.setTags(workOrder.getTags());
vo.setCreateTime(workOrder.getCreateTime());
vo.setUpdateTime(workOrder.getUpdateTime());
// 判断是否超期
if (workOrder.getDeadline() != null
&& !"resolved".equals(workOrder.getStatus())
&& !"closed".equals(workOrder.getStatus())) {
vo.setIsOverdue(workOrder.getDeadline().isBefore(LocalDateTime.now()));
} else {
vo.setIsOverdue(false);
}
// 查询项目名称
if (workOrder.getProjectId() != null) {
Project project = projectMapper.selectById(workOrder.getProjectId());
if (project != null) {
vo.setProjectName(project.getProjectName());
}
}
// 查询风险名称
if (workOrder.getRiskId() != null) {
Risk risk = riskMapper.selectById(workOrder.getRiskId());
if (risk != null) {
vo.setRiskName(risk.getRiskName());
}
}
// 查询创建人信息
if (workOrder.getCreatorId() != null) {
SysUser creator = sysUserMapper.selectById(workOrder.getCreatorId());
if (creator != null) {
vo.setCreatorName(creator.getRealName());
}
}
// 查询处理人信息
if (workOrder.getHandlerId() != null) {
SysUser handler = sysUserMapper.selectById(workOrder.getHandlerId());
if (handler != null) {
vo.setHandlerName(handler.getRealName());
vo.setHandlerAvatar(handler.getAvatar());
}
}
return vo;
}
}