You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java中如何在OrdemDeServico状态变更时通过WebSocket实时推送数据?

针对实时推送OrdemDeServico状态变更的优化建议

直接在update方法加推送的问题

你当前的写法能实现需求,但不符合最佳实践,主要问题有两个:

  • 职责混在一起:update方法的核心是更新业务实体,现在硬塞了消息推送逻辑,违反单一职责原则。以后要改推送规则或者换推送方式,都得动这个业务方法,维护起来麻烦。
  • 事务风险:推送逻辑放在事务里,如果推送出错会导致整个更新事务回滚;反过来,事务没提交就推送,前端拿到的数据可能和数据库最终状态不一致。

推荐的优化方案

方案1:用Spring事件驱动解耦

把推送逻辑拆到事件监听器里,步骤如下:

  1. 定义状态变更事件类:
public class OrdemStatusChangedEvent extends ApplicationEvent {
    private final OrdemDeServico ordem;

    public OrdemStatusChangedEvent(OrdemDeServico ordem) {
        super(ordem);
        this.ordem = ordem;
    }

    public OrdemDeServico getOrdem() {
        return ordem;
    }
}
  1. 修改update方法,事务提交后发布事件:
@Transactional
public OrdemDeServicoDTO update(Long id, OrdemDeServicoDTO dto){
    try{
        OrdemDeServico entity = repository.getReferenceById(id);
        // 先存旧状态,用来判断是否真的变了
        String oldStatus = entity.getStatus();
        copyDtoToEntity(dto, entity);
        
        // 处理技师关联逻辑
        if(dto.getTechnicianId() != null && dto.getTechnicianId() != 0){
            Tecnico tecnico = tecnicoRepository.findById(dto.getTechnicianId())
                    .orElseThrow(() -> new ResourceNotFoundException("Tecnico não encontrado"));
            entity.setTechnician(tecnico);
        } else {
            entity.setTechnician(null);
        }
        
        entity = repository.save(entity);
        
        // 只有状态真的变了才发事件
        if(!oldStatus.equals(entity.getStatus())){
            // 确保事务提交后再推送,避免前端拿到未持久化的数据
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                @Override
                public void afterCommit() {
                    applicationEventPublisher.publishEvent(new OrdemStatusChangedEvent(entity));
                }
            });
        }
        
        return new OrdemDeServicoDTO(entity);
    } catch (EntityNotFoundException e){
        throw new ResourceNotFoundException("Nenhuma Ordem de Serviço encontrada com o id: " + id);
    }
}
  1. 写事件监听器处理推送:
@Component
public class OrdemStatusChangeListener {
    private final SimpMessagingTemplate messagingTemplate;

    public OrdemStatusChangeListener(SimpMessagingTemplate messagingTemplate) {
        this.messagingTemplate = messagingTemplate;
    }

    @EventListener
    public void handleOrdemStatusChanged(OrdemStatusChangedEvent event) {
        OrdemDeServico ordem = event.getOrdem();
        if("Em Produção".equals(ordem.getStatus())){
            EmProducaoDTO emProducaoDto = convertToDto(ordem);
            messagingTemplate.convertAndSend("/topic/ordemDeServico", emProducaoDto);
        }
    }

    private EmProducaoDTO convertToDto(OrdemDeServico ordem) {
        // 你的实体转DTO逻辑
    }
}

方案2:用JPA实体监听器覆盖所有修改场景

如果除了update方法,还有其他地方(比如直接调用repository.save)会修改状态,可以用JPA的实体监听器:

  1. 在OrdemDeServico实体上加监听器和旧状态存储:
@Entity
@EntityListeners(OrdemDeServicoListener.class)
public class OrdemDeServico {
    // 实体原有字段...
    
    private String status;
    
    // 临时存旧状态,不用持久化到数据库
    @Transient
    private String oldStatus;

    @PreUpdate
    public void preUpdate() {
        this.oldStatus = this.status;
    }

    // getter和setter...
}
  1. 实现监听器:
@Component
public class OrdemDeServicoListener {
    private final ApplicationEventPublisher eventPublisher;

    public OrdemDeServicoListener(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    @PostUpdate
    public void postUpdate(OrdemDeServico ordem) {
        if(!ordem.getOldStatus().equals(ordem.getStatus())){
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                @Override
                public void afterCommit() {
                    eventPublisher.publishEvent(new OrdemStatusChangedEvent(ordem));
                }
            });
        }
    }
}

之后还是用方案1的事件监听器处理推送,这样不管哪种方式修改状态,都会触发推送。

额外提醒

  • 推送粒度:原来定时任务推的是全列表,现在推单条,前端要做增量更新(比如把新数据加到列表或者替换旧数据)。如果一定要推全量,可以在事件里触发一次全量查询,但性能会受影响,优先建议前端做增量处理。
  • 异常处理:推送可能出网络问题,要在监听器里加try-catch,别让推送异常搞挂业务逻辑。
  • 测试注意:测试时要留意事务提交时机,确保推送给前端的是已经存在数据库里的正确数据。

内容的提问来源于stack exchange,提问作者Pedro Paulo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 10:37:15