feat(risk): 实现风险管理模块功能
- 新增RiskMapper,定义风险相关SQL映射和查询功能 - 添加CreateRiskRequest、CreateWorkOrderRequest和ProcessWorkOrderRequest请求DTO - 实现RiskController,支持风险的创建、更新、删除、详细查询及列表分页查询 - 提供风险统计接口,统计风险总数、状态分布和等级分布 - 增加风险分配工单及批量更新状态的接口 - 实现RiskService及其实现类,包含风险的增删改查及业务逻辑 - 计算风险得分和风险等级,并支持标签和工单关联管理 - 定义RiskVO和RiskStatisticsVO用于前端数据展示和统计 - 实现风险工单的创建和管理,关联风险状态同步更新
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user