个人开发4

  • 支持演练过程跟踪管理(步骤状态、操作记录、日志文件)

  • 强制阅读安全须知(弹窗+1分钟语音朗读)

  • 记录操作人、时间轴、安全确认状态

  • 实时展示演练进度
    // 安全确认API(JPA实体)
    @Entity
    @Table(name = "drill_execution")
    public class DrillExecution {
    @Column(name = "safety_confirmed", columnDefinition = "TINYINT(1) DEFAULT 0")
    private Boolean safetyConfirmed; // 确认状态

    @Column(name = "safety_confirm_time")
    private LocalDateTime safetyConfirmTime; // 确认时间
    }

// 安全校验拦截器
@Component
public class SafetyCheckInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
Integer executionId = getExecutionIdFromRequest(req);
DrillExecution exec = executionRepo.findById(executionId)
.orElseThrow(() -> new BizException("演练不存在"));

    if (!exec.getSafetyConfirmed()) {
        throw new SafetyCheckException("请先完成安全须知确认"); // 自定义异常
    }
    return true;
}

}
前端实现
vue

@Aspect
@Component
public class StepLogAspect {
@Pointcut("@annotation(com.emdrill.anno.LogStepAction)")
public void logPointcut() {}

@Around("logPointcut()")
public Object logStepAction(ProceedingJoinPoint pjp) throws Throwable {
    Method method = ((MethodSignature) pjp.getSignature()).getMethod();
    LogStepAction annotation = method.getAnnotation(LogStepAction.class);
    
    // 记录操作前状态
    StepLog preLog = buildPreLog(pjp.getArgs());
    logRepository.save(preLog);

    Object result = pjp.proceed();

    // 记录操作后状态
    StepLog postLog = buildPostLog(result);
    logRepository.save(postLog);
    
    return result;
}

}

// 使用注解标记需要日志记录的方法
@LogStepAction(actionType = "START_STEP")
public void startStep(Integer executionId) { ... }
WebSocket配置

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic/progress");
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws-drill")
                .setAllowedOrigins("*")
                .withSockJS();
    }
}

// 进度更新推送服务
@Service
@RequiredArgsConstructor
public class ProgressPushService {
    private final SimpMessagingTemplate messagingTemplate;

    public void pushProgressUpdate(Integer executionId, ProgressVO vo) {
        messagingTemplate.convertAndSend(
            "/topic/progress/" + executionId, 
            vo
        );
    }
<template>
  <GanttChart 
    :tasks="ganttTasks" 
    @task-updated="handleTaskUpdate"
  />
</template>

<script setup>
import { ref, onMounted } from 'vue'
import SockJS from 'sockjs-client'
import { Client } from '@stomp/stompjs'

const executionId = props.executionId
const ganttTasks = ref([])

// WebSocket连接初始化
const initWebSocket = () => {
  const client = new Client({
    brokerURL: 'ws://your-domain/ws-drill',
    reconnectDelay: 5000,
    onConnect: () => {
      client.subscribe(`/topic/progress/${executionId}`, (msg) => {
        const data = JSON.parse(msg.body)
        updateGantt(data)
      })
    }
  })
  client.activate()
}

const updateGantt = (progressData) => {
  ganttTasks.value = progressData.steps.map(step => ({
    id: step.stepId,
    name: step.stepName,
    start: new Date(step.startTime),
    end: new Date(step.endTime),
    progress: step.progressPercent
  }))
}
</script>
语音播放兼容性问题
     document.addEventListener('click', () => {
       audioElement.play().catch(() => {
         showManualPlayButton() // 显示手动播放按健
       })
     }, { once: true })
   
     @Entity
     public class DrillExecution {
         @Version
         private Integer version; 
     }

     
     @Transactional
     public void updateStep(Integer id, StepUpdateDTO dto) {
         DrillExecution exec = repository.findByIdWithLock(id);
         if (!exec.getVersion().equals(dto.getVersion())) {
             throw new OptimisticLockException("数据已被修改,请刷新重试");
         }
         
     }
   
| 功能测试 | 未确认安全须知时发起步骤操作 | 拦截请求并返回错误码SAFETY_CHECK_FAIL(1001) |
yaml
# Prometheus配置示例
scrape_configs:
  - job_name: 'drill-execution'
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: ['execution-service:8080']

# Grafana仪表盘指标
- 实时在线人数(WebSocket连接数)
- 步骤操作TPS(Transactions Per Second)
- 语音合成服务响应时长(P99延迟)
**容器化部署**  
```dockerfile
# 前端容器
FROM nginx:alpine
COPY dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 后端容器
FROM openjdk:17-jdk-slim
COPY target/execution-service.jar /app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
posted @ 2025-04-22 00:50  我嘞牛牛  阅读(17)  评论(0)    收藏  举报