feat(ops): 新增安保工单核心服务与派单策略
包含安保工单 CRUD(创建/确认/完单)、区域人员绑定服务、 区域分配策略 SecurityAreaAssignStrategy、调度策略 SecurityScheduleStrategy,以及安保扩展查询处理器。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
package com.viewsh.module.ops.security.service.area;
|
||||
|
||||
import com.viewsh.module.ops.security.dal.dataobject.area.OpsAreaSecurityUserDO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 区域-安保人员绑定服务
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
public interface OpsAreaSecurityUserService {
|
||||
|
||||
/**
|
||||
* 查询区域绑定的启用安保人员
|
||||
*
|
||||
* @param areaId 区域ID
|
||||
* @return 安保人员列表
|
||||
*/
|
||||
List<OpsAreaSecurityUserDO> listByAreaId(Long areaId);
|
||||
|
||||
/**
|
||||
* 绑定安保人员到区域
|
||||
*
|
||||
* @param areaId 区域ID
|
||||
* @param userId 用户ID
|
||||
* @param userName 用户姓名
|
||||
* @param teamId 班组ID
|
||||
* @param sort 排序值
|
||||
* @return 绑定记录ID
|
||||
*/
|
||||
Long bindUser(Long areaId, Long userId, String userName, Long teamId, Integer sort);
|
||||
|
||||
/**
|
||||
* 更新绑定信息
|
||||
*
|
||||
* @param id 绑定记录ID
|
||||
* @param enabled 是否启用
|
||||
* @param sort 排序值
|
||||
* @param teamId 班组ID
|
||||
*/
|
||||
void updateBinding(Long id, Boolean enabled, Integer sort, Long teamId);
|
||||
|
||||
/**
|
||||
* 解除绑定
|
||||
*
|
||||
* @param id 绑定记录ID
|
||||
*/
|
||||
void unbindUser(Long id);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.viewsh.module.ops.security.service.area;
|
||||
|
||||
import com.viewsh.module.ops.security.dal.dataobject.area.OpsAreaSecurityUserDO;
|
||||
import com.viewsh.module.ops.security.dal.mysql.area.OpsAreaSecurityUserMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.viewsh.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static com.viewsh.module.ops.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 区域-安保人员绑定服务实现
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class OpsAreaSecurityUserServiceImpl implements OpsAreaSecurityUserService {
|
||||
|
||||
@Resource
|
||||
private OpsAreaSecurityUserMapper areaSecurityUserMapper;
|
||||
|
||||
@Override
|
||||
public List<OpsAreaSecurityUserDO> listByAreaId(Long areaId) {
|
||||
return areaSecurityUserMapper.selectListByAreaId(areaId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long bindUser(Long areaId, Long userId, String userName, Long teamId, Integer sort) {
|
||||
// 唯一性校验
|
||||
OpsAreaSecurityUserDO existing = areaSecurityUserMapper.selectByAreaIdAndUserId(areaId, userId);
|
||||
if (existing != null) {
|
||||
throw exception(SECURITY_AREA_USER_DUPLICATE);
|
||||
}
|
||||
|
||||
OpsAreaSecurityUserDO record = OpsAreaSecurityUserDO.builder()
|
||||
.areaId(areaId)
|
||||
.userId(userId)
|
||||
.userName(userName)
|
||||
.teamId(teamId)
|
||||
.enabled(true)
|
||||
.sort(sort != null ? sort : 0)
|
||||
.build();
|
||||
try {
|
||||
areaSecurityUserMapper.insert(record);
|
||||
} catch (DuplicateKeyException e) {
|
||||
throw exception(SECURITY_AREA_USER_DUPLICATE);
|
||||
}
|
||||
|
||||
log.info("绑定安保人员到区域: areaId={}, userId={}, userName={}", areaId, userId, userName);
|
||||
return record.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBinding(Long id, Boolean enabled, Integer sort, Long teamId) {
|
||||
OpsAreaSecurityUserDO existing = areaSecurityUserMapper.selectById(id);
|
||||
if (existing == null) {
|
||||
throw exception(SECURITY_AREA_USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
OpsAreaSecurityUserDO update = new OpsAreaSecurityUserDO();
|
||||
update.setId(id);
|
||||
if (enabled != null) {
|
||||
update.setEnabled(enabled);
|
||||
}
|
||||
if (sort != null) {
|
||||
update.setSort(sort);
|
||||
}
|
||||
if (teamId != null) {
|
||||
update.setTeamId(teamId);
|
||||
}
|
||||
areaSecurityUserMapper.updateById(update);
|
||||
|
||||
log.info("更新安保人员绑定: id={}, enabled={}, sort={}", id, enabled, sort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindUser(Long id) {
|
||||
OpsAreaSecurityUserDO existing = areaSecurityUserMapper.selectById(id);
|
||||
if (existing == null) {
|
||||
throw exception(SECURITY_AREA_USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
areaSecurityUserMapper.deleteById(id);
|
||||
log.info("解除安保人员绑定: id={}, areaId={}, userId={}", id, existing.getAreaId(), existing.getUserId());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.viewsh.module.ops.security.service.dispatch;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.viewsh.module.ops.core.dispatch.DispatchEngine;
|
||||
import com.viewsh.module.ops.core.dispatch.model.AssigneeRecommendation;
|
||||
import com.viewsh.module.ops.core.dispatch.model.OrderDispatchContext;
|
||||
import com.viewsh.module.ops.core.dispatch.strategy.AssignStrategy;
|
||||
import com.viewsh.module.ops.enums.WorkOrderTypeEnum;
|
||||
import com.viewsh.module.ops.security.dal.dataobject.area.OpsAreaSecurityUserDO;
|
||||
import com.viewsh.module.ops.security.dal.mysql.area.OpsAreaSecurityUserMapper;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 安保工单区域分配策略
|
||||
* <p>
|
||||
* 根据区域绑定的安保人员随机分配。
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class SecurityAreaAssignStrategy implements AssignStrategy {
|
||||
|
||||
private static final String STRATEGY_NAME = "security_area_user";
|
||||
private static final String BUSINESS_TYPE = WorkOrderTypeEnum.SECURITY.getType();
|
||||
|
||||
@Resource
|
||||
private DispatchEngine dispatchEngine;
|
||||
|
||||
@Resource
|
||||
private OpsAreaSecurityUserMapper areaSecurityUserMapper;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
dispatchEngine.registerAssignStrategy(BUSINESS_TYPE, this);
|
||||
log.info("注册安保分配策略: {}", STRATEGY_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return STRATEGY_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSupportedBusinessType() {
|
||||
return BUSINESS_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssigneeRecommendation recommend(OrderDispatchContext context) {
|
||||
Long areaId = context.getAreaId();
|
||||
if (areaId == null) {
|
||||
log.warn("安保派单缺少区域ID: orderId={}", context.getOrderId());
|
||||
return AssigneeRecommendation.none();
|
||||
}
|
||||
|
||||
List<OpsAreaSecurityUserDO> users = areaSecurityUserMapper.selectListByAreaId(areaId);
|
||||
if (CollUtil.isEmpty(users)) {
|
||||
log.info("区域 {} 无绑定安保人员,工单 {} 等待手动分配", areaId, context.getOrderId());
|
||||
return AssigneeRecommendation.none();
|
||||
}
|
||||
|
||||
// 选择 sort 值最小的人员(sort 越小优先级越高,由 Mapper 已按 sort ASC 排序)
|
||||
OpsAreaSecurityUserDO chosen = users.get(0);
|
||||
AssigneeRecommendation recommendation = AssigneeRecommendation.of(
|
||||
chosen.getUserId(), chosen.getUserName(), 50, "区域排序优先分配");
|
||||
recommendation.setAreaId(areaId);
|
||||
return recommendation;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.viewsh.module.ops.security.service.dispatch;
|
||||
|
||||
import com.viewsh.module.ops.core.dispatch.DispatchEngine;
|
||||
import com.viewsh.module.ops.core.dispatch.model.DispatchDecision;
|
||||
import com.viewsh.module.ops.core.dispatch.model.DispatchPath;
|
||||
import com.viewsh.module.ops.core.dispatch.model.OrderDispatchContext;
|
||||
import com.viewsh.module.ops.core.dispatch.strategy.ScheduleStrategy;
|
||||
import com.viewsh.module.ops.enums.WorkOrderTypeEnum;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 安保工单调度策略
|
||||
* <p>
|
||||
* 安保工单调度相对简单:
|
||||
* - 有可用人员 → 直接派单
|
||||
* - 人员忙碌 → 入队等待
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class SecurityScheduleStrategy implements ScheduleStrategy {
|
||||
|
||||
private static final String STRATEGY_NAME = "security_schedule";
|
||||
private static final String BUSINESS_TYPE = WorkOrderTypeEnum.SECURITY.getType();
|
||||
|
||||
@Resource
|
||||
private DispatchEngine dispatchEngine;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
dispatchEngine.registerScheduleStrategy(BUSINESS_TYPE, this);
|
||||
log.info("注册安保调度策略: {}", STRATEGY_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return STRATEGY_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSupportedBusinessType() {
|
||||
return BUSINESS_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DispatchDecision decide(OrderDispatchContext context) {
|
||||
// 安保工单默认直接派单
|
||||
// DispatchEngine 会根据执行人状态自动选择路径
|
||||
return DispatchDecision.builder()
|
||||
.path(DispatchPath.DIRECT_DISPATCH)
|
||||
.reason("安保工单直接派单")
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.viewsh.module.ops.security.service.securityorder;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 安保工单人工完单请求 DTO(Service 层内部使用)
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SecurityOrderCompleteReqDTO {
|
||||
|
||||
@NotNull(message = "工单ID不能为空")
|
||||
private Long orderId;
|
||||
|
||||
@NotBlank(message = "处理结果不能为空")
|
||||
private String result;
|
||||
|
||||
private List<String> resultImgUrls;
|
||||
|
||||
/**
|
||||
* 操作人ID(由 Controller 层填充)
|
||||
*/
|
||||
@NotNull(message = "操作人ID不能为空")
|
||||
private Long operatorId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.viewsh.module.ops.security.service.securityorder;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 安保工单创建请求 DTO(Service 层内部使用)
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SecurityOrderCreateReqDTO {
|
||||
|
||||
@NotBlank(message = "工单标题不能为空")
|
||||
private String title;
|
||||
|
||||
private String description;
|
||||
|
||||
private Integer priority;
|
||||
|
||||
@NotNull(message = "区域ID不能为空")
|
||||
private Long areaId;
|
||||
|
||||
private String location;
|
||||
|
||||
// ==================== 告警来源 ====================
|
||||
|
||||
private String alarmId;
|
||||
|
||||
private String alarmType;
|
||||
|
||||
private String cameraId;
|
||||
|
||||
private String roiId;
|
||||
|
||||
private String imageUrl;
|
||||
|
||||
private String sourceType;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.viewsh.module.ops.security.service.securityorder;
|
||||
|
||||
import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO;
|
||||
import com.viewsh.module.ops.security.dal.dataobject.workorder.OpsOrderSecurityExtDO;
|
||||
import com.viewsh.module.ops.security.dal.mysql.workorder.OpsOrderSecurityExtMapper;
|
||||
import com.viewsh.module.ops.service.OrderDetailVO;
|
||||
import com.viewsh.module.ops.service.OrderExtQueryHandler;
|
||||
import com.viewsh.module.ops.service.OrderSummaryVO;
|
||||
import com.viewsh.module.ops.enums.WorkOrderTypeEnum;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 安保工单扩展查询处理器
|
||||
* <p>
|
||||
* 实现 OrderExtQueryHandler 接口,为工单中心查询提供安保扩展信息加载能力
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class SecurityOrderExtQueryHandler implements OrderExtQueryHandler {
|
||||
|
||||
@Resource
|
||||
private OpsOrderSecurityExtMapper securityExtMapper;
|
||||
|
||||
private static final String ORDER_TYPE_SECURITY = WorkOrderTypeEnum.SECURITY.getType();
|
||||
|
||||
@Override
|
||||
public boolean supports(String orderType) {
|
||||
return ORDER_TYPE_SECURITY.equals(orderType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enrichWithExtInfo(OrderSummaryVO vo, Long orderId) {
|
||||
OpsOrderSecurityExtDO securityExt = securityExtMapper.selectByOpsOrderId(orderId);
|
||||
if (securityExt != null) {
|
||||
vo.setExtInfo(buildExtInfoMap(securityExt));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrderDetailVO buildDetailVO(OpsOrderDO order) {
|
||||
OpsOrderSecurityExtDO securityExt = securityExtMapper.selectByOpsOrderId(order.getId());
|
||||
|
||||
OrderDetailVO vo = OrderDetailVO.builder()
|
||||
.id(order.getId())
|
||||
.orderCode(order.getOrderCode())
|
||||
.orderType(order.getOrderType())
|
||||
.sourceType(order.getSourceType())
|
||||
.title(order.getTitle())
|
||||
.description(order.getDescription())
|
||||
.priority(order.getPriority())
|
||||
.status(order.getStatus())
|
||||
.areaId(order.getAreaId())
|
||||
.location(order.getLocation())
|
||||
.urgentReason(order.getUrgentReason())
|
||||
.assigneeId(order.getAssigneeId())
|
||||
.assigneeName(order.getAssigneeName())
|
||||
.inspectorId(order.getInspectorId())
|
||||
.startTime(order.getStartTime())
|
||||
.endTime(order.getEndTime())
|
||||
.qualityScore(order.getQualityScore())
|
||||
.qualityComment(order.getQualityComment())
|
||||
.responseSeconds(order.getResponseSeconds())
|
||||
.completionSeconds(order.getCompletionSeconds())
|
||||
.createTime(order.getCreateTime())
|
||||
.updateTime(order.getUpdateTime())
|
||||
.build();
|
||||
|
||||
if (securityExt != null) {
|
||||
vo.setExtInfo(buildExtInfoMap(securityExt));
|
||||
}
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建安保工单扩展信息 Map(列表/详情统一使用)
|
||||
*/
|
||||
private Map<String, Object> buildExtInfoMap(OpsOrderSecurityExtDO ext) {
|
||||
Map<String, Object> extInfo = new LinkedHashMap<>(16);
|
||||
extInfo.put("alarmId", ext.getAlarmId());
|
||||
extInfo.put("alarmType", ext.getAlarmType());
|
||||
extInfo.put("cameraId", ext.getCameraId());
|
||||
extInfo.put("roiId", ext.getRoiId());
|
||||
extInfo.put("imageUrl", ext.getImageUrl());
|
||||
extInfo.put("assignedUserId", ext.getAssignedUserId());
|
||||
extInfo.put("assignedUserName", ext.getAssignedUserName());
|
||||
extInfo.put("assignedTeamId", ext.getAssignedTeamId());
|
||||
extInfo.put("result", ext.getResult());
|
||||
extInfo.put("resultImgUrls", ext.getResultImgUrls());
|
||||
extInfo.put("dispatchedTime", ext.getDispatchedTime());
|
||||
extInfo.put("confirmedTime", ext.getConfirmedTime());
|
||||
extInfo.put("completedTime", ext.getCompletedTime());
|
||||
return extInfo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.viewsh.module.ops.security.service.securityorder;
|
||||
|
||||
import com.viewsh.module.ops.security.dal.dataobject.workorder.OpsOrderSecurityExtDO;
|
||||
|
||||
/**
|
||||
* 安保工单服务接口
|
||||
* <p>
|
||||
* 提供安保工单的创建、确认、自动完单、人工完单等能力
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
public interface SecurityOrderService {
|
||||
|
||||
/**
|
||||
* 创建安保工单(对外接口,由外部告警系统调用)
|
||||
* <p>
|
||||
* 流程:创建主表 + 扩展表 → 自动派单
|
||||
*
|
||||
* @param createReq 创建请求
|
||||
* @return 工单ID
|
||||
*/
|
||||
Long createSecurityOrder(SecurityOrderCreateReqDTO createReq);
|
||||
|
||||
/**
|
||||
* 确认工单(安保人员确认接单)
|
||||
*
|
||||
* @param orderId 工单ID
|
||||
* @param userId 安保人员user_id
|
||||
*/
|
||||
void confirmOrder(Long orderId, Long userId);
|
||||
|
||||
/**
|
||||
* 自动完单(对方系统调用,无需提交结果)
|
||||
*
|
||||
* @param orderId 工单ID
|
||||
* @param remark 备注
|
||||
*/
|
||||
void autoCompleteOrder(Long orderId, String remark);
|
||||
|
||||
/**
|
||||
* 人工完单(安保人员提交处理结果)
|
||||
*
|
||||
* @param req 完单请求(包含 result + resultImgUrls)
|
||||
*/
|
||||
void manualCompleteOrder(SecurityOrderCompleteReqDTO req);
|
||||
|
||||
/**
|
||||
* 根据工单ID查询安保扩展信息
|
||||
*
|
||||
* @param opsOrderId 工单ID
|
||||
* @return 扩展信息
|
||||
*/
|
||||
OpsOrderSecurityExtDO getSecurityExt(Long opsOrderId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
package com.viewsh.module.ops.security.service.securityorder;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.viewsh.module.ops.core.event.OrderEventPublisher;
|
||||
import com.viewsh.module.ops.core.event.OrderCreatedEvent;
|
||||
import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO;
|
||||
import com.viewsh.module.ops.dal.mysql.area.OpsBusAreaMapper;
|
||||
import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper;
|
||||
import com.viewsh.module.ops.enums.OperatorTypeEnum;
|
||||
import com.viewsh.module.ops.enums.PriorityEnum;
|
||||
import com.viewsh.module.ops.enums.SourceTypeEnum;
|
||||
import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
|
||||
import com.viewsh.module.ops.enums.WorkOrderTypeEnum;
|
||||
import com.viewsh.module.ops.infrastructure.code.OrderCodeGenerator;
|
||||
import com.viewsh.module.ops.infrastructure.log.enumeration.EventDomain;
|
||||
import com.viewsh.module.ops.infrastructure.log.enumeration.LogModule;
|
||||
import com.viewsh.module.ops.infrastructure.log.enumeration.LogType;
|
||||
import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecord;
|
||||
import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecorder;
|
||||
// 注意:confirm/complete 的业务日志由 SecurityOrderEventListener 统一记录
|
||||
// 本类仅记录 CREATE 日志(创建不经过状态变更事件)
|
||||
import static com.viewsh.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static com.viewsh.module.ops.enums.ErrorCodeConstants.*;
|
||||
import com.viewsh.module.ops.infrastructure.id.OrderIdGenerator;
|
||||
import com.viewsh.module.ops.security.dal.dataobject.workorder.OpsOrderSecurityExtDO;
|
||||
import com.viewsh.module.ops.security.dal.mysql.workorder.OpsOrderSecurityExtMapper;
|
||||
import com.viewsh.module.ops.service.fsm.OrderStateMachine;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 安保工单服务实现
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SecurityOrderServiceImpl implements SecurityOrderService {
|
||||
|
||||
@Resource
|
||||
private OpsOrderMapper opsOrderMapper;
|
||||
|
||||
@Resource
|
||||
private OpsOrderSecurityExtMapper securityExtMapper;
|
||||
|
||||
@Resource
|
||||
private OrderIdGenerator orderIdGenerator;
|
||||
|
||||
@Resource
|
||||
private OrderCodeGenerator orderCodeGenerator;
|
||||
|
||||
@Resource
|
||||
private OrderEventPublisher orderEventPublisher;
|
||||
|
||||
@Resource
|
||||
private OpsBusAreaMapper opsBusAreaMapper;
|
||||
|
||||
@Resource
|
||||
private OrderStateMachine orderStateMachine;
|
||||
|
||||
@Resource
|
||||
private EventLogRecorder eventLogRecorder;
|
||||
|
||||
private static final String BUSINESS_TYPE = WorkOrderTypeEnum.SECURITY.getType();
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long createSecurityOrder(SecurityOrderCreateReqDTO createReq) {
|
||||
// 0. 校验区域是否存在
|
||||
if (opsBusAreaMapper.selectById(createReq.getAreaId()) == null) {
|
||||
throw exception(AREA_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 1. 生成ID和编号
|
||||
Long orderId = orderIdGenerator.generate();
|
||||
String orderCode = orderCodeGenerator.generate(BUSINESS_TYPE);
|
||||
|
||||
// 2. 确定来源类型
|
||||
String sourceType = StrUtil.isNotBlank(createReq.getSourceType())
|
||||
? createReq.getSourceType()
|
||||
: (StrUtil.isNotBlank(createReq.getAlarmId()) ? SourceTypeEnum.ALARM.getType() : SourceTypeEnum.MANUAL.getType());
|
||||
|
||||
// 3. 构建主表记录
|
||||
OpsOrderDO order = OpsOrderDO.builder()
|
||||
.id(orderId)
|
||||
.orderCode(orderCode)
|
||||
.orderType(BUSINESS_TYPE)
|
||||
.sourceType(sourceType)
|
||||
.title(createReq.getTitle())
|
||||
.description(createReq.getDescription())
|
||||
.priority(createReq.getPriority() != null ? createReq.getPriority() : PriorityEnum.P2.getPriority())
|
||||
.status(WorkOrderStatusEnum.PENDING.getStatus())
|
||||
.areaId(createReq.getAreaId())
|
||||
.location(createReq.getLocation())
|
||||
.build();
|
||||
opsOrderMapper.insert(order);
|
||||
|
||||
// 4. 构建扩展表记录
|
||||
OpsOrderSecurityExtDO securityExt = OpsOrderSecurityExtDO.builder()
|
||||
.opsOrderId(orderId)
|
||||
.alarmId(createReq.getAlarmId())
|
||||
.alarmType(createReq.getAlarmType())
|
||||
.cameraId(createReq.getCameraId())
|
||||
.roiId(createReq.getRoiId())
|
||||
.imageUrl(createReq.getImageUrl())
|
||||
.build();
|
||||
securityExtMapper.insert(securityExt);
|
||||
|
||||
// 5. 发布工单创建事件(触发自动派单)
|
||||
OrderCreatedEvent event = OrderCreatedEvent.builder()
|
||||
.orderId(orderId)
|
||||
.orderType(BUSINESS_TYPE)
|
||||
.orderCode(orderCode)
|
||||
.title(createReq.getTitle())
|
||||
.areaId(createReq.getAreaId())
|
||||
.priority(order.getPriority())
|
||||
.createTime(order.getCreateTime())
|
||||
.build();
|
||||
orderEventPublisher.publishOrderCreated(event);
|
||||
|
||||
// 6. 记录业务日志
|
||||
eventLogRecorder.record(EventLogRecord.builder()
|
||||
.module(LogModule.SECURITY)
|
||||
.domain(EventDomain.DISPATCH)
|
||||
.eventType(LogType.ORDER_CREATED.getCode())
|
||||
.message("安保工单创建")
|
||||
.targetId(orderId)
|
||||
.targetType("order")
|
||||
.build());
|
||||
|
||||
log.info("创建安保工单成功: orderId={}, orderCode={}, alarmId={}, areaId={}",
|
||||
orderId, orderCode, createReq.getAlarmId(), createReq.getAreaId());
|
||||
|
||||
return orderId;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void confirmOrder(Long orderId, Long userId) {
|
||||
OpsOrderDO order = getOrderOrThrow(orderId);
|
||||
validateOrderType(order);
|
||||
|
||||
// 状态转换:DISPATCHED → CONFIRMED(扩展表时间 + 业务日志由 EventListener 统一记录)
|
||||
orderStateMachine.transition(order, WorkOrderStatusEnum.CONFIRMED,
|
||||
OperatorTypeEnum.SECURITY_GUARD, userId, "安保人员确认接单");
|
||||
|
||||
log.info("安保工单确认: orderId={}, userId={}", orderId, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void autoCompleteOrder(Long orderId, String remark) {
|
||||
OpsOrderDO order = getOrderOrThrow(orderId);
|
||||
validateOrderType(order);
|
||||
|
||||
// 状态转换 → COMPLETED(扩展表时间 + 业务日志由 EventListener 统一记录,主表 endTime 由状态机统一设置)
|
||||
orderStateMachine.transition(order, WorkOrderStatusEnum.COMPLETED,
|
||||
OperatorTypeEnum.SYSTEM, null,
|
||||
StrUtil.isNotBlank(remark) ? remark : "系统自动完单");
|
||||
|
||||
log.info("安保工单自动完单: orderId={}", orderId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void manualCompleteOrder(SecurityOrderCompleteReqDTO req) {
|
||||
OpsOrderDO order = getOrderOrThrow(req.getOrderId());
|
||||
validateOrderType(order);
|
||||
|
||||
// 状态转换 → COMPLETED(扩展表 completedTime + 业务日志由 EventListener 统一记录,主表 endTime 由状态机统一设置)
|
||||
orderStateMachine.transition(order, WorkOrderStatusEnum.COMPLETED,
|
||||
OperatorTypeEnum.SECURITY_GUARD, req.getOperatorId(), "安保人员提交处理结果");
|
||||
|
||||
// 更新扩展表:结果 + 图片
|
||||
OpsOrderSecurityExtDO extUpdate = new OpsOrderSecurityExtDO();
|
||||
extUpdate.setOpsOrderId(req.getOrderId());
|
||||
extUpdate.setResult(req.getResult());
|
||||
if (req.getResultImgUrls() != null && !req.getResultImgUrls().isEmpty()) {
|
||||
extUpdate.setResultImgUrls(cn.hutool.json.JSONUtil.toJsonStr(req.getResultImgUrls()));
|
||||
}
|
||||
securityExtMapper.insertOrUpdateSelective(extUpdate);
|
||||
|
||||
log.info("安保工单人工完单: orderId={}", req.getOrderId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpsOrderSecurityExtDO getSecurityExt(Long opsOrderId) {
|
||||
return securityExtMapper.selectByOpsOrderId(opsOrderId);
|
||||
}
|
||||
|
||||
// ==================== 内部方法 ====================
|
||||
|
||||
private OpsOrderDO getOrderOrThrow(Long orderId) {
|
||||
OpsOrderDO order = opsOrderMapper.selectById(orderId);
|
||||
if (order == null) {
|
||||
throw exception(SECURITY_ORDER_NOT_FOUND);
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
private void validateOrderType(OpsOrderDO order) {
|
||||
if (!BUSINESS_TYPE.equals(order.getOrderType())) {
|
||||
throw exception(SECURITY_ORDER_TYPE_MISMATCH);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user