在大屏项目中,数据往往需要“实时、动态、自动”推送给前端页面。常见方式包括 WebSocket、SSE(Server-Sent Events)、长轮询等。大多数项目会手动写 push 逻辑,但随着模块增多,会出现:代码分散、推送逻辑耦合、模板化代码重复等问题。

本文介绍一种优雅的方式 → 使用注解(Annotation)实现大屏数据的动态推送:业务方法只需要标注注解,返回值即可自动推送到大屏。支持模块化、多通道、自动序列化、动态监听、多大屏 ID 推送。


一、为什么用注解?

传统写法:

public void queryOrder() {
    Result r = service.getOrderCount();
    webSocket.send("order", r);
}

问题:

  • 所有模块都要手动调用 webSocket

  • 大屏越多,入口越多,代码越乱

  • 推送逻辑与业务逻辑强耦合

改用注解后:

@ScreenPush("order")
public Result queryOrder() {
    return service.getOrderCount();
}

业务方只写业务逻辑,推送框架自动完成:

  • 采集数据

  • 序列化成 JSON

  • 根据不同 screenId 群发

  • 异步推送

你甚至可以定时执行,自动刷新大屏。


二、整体架构设计

核心组件:

  1. 注解定义:@ScreenPush

  2. 切面(AOP)自动拦截标注方法

  3. 推送服务(WebSocket / SSE)

  4. 序列化组件(Jackson/fastjson)

  5. 大屏 ID 管理(多大屏推送)

架构图:

业务方法 → 注解 → AOP → 推送组件 → 大屏前端(WebSocket/SSE)

三、注解定义

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ScreenPush {
    /** 推送的事件名,比如 order/user/chart1 */
    String value();

    /** 是否异步推送 */
    boolean async() default true;
}

四、切面(AOP)拦截标注方法

核心逻辑:

  • 拦截方法执行

  • 执行原始业务逻辑,获得返回值

  • 自动序列化成 JSON

  • 自动推送到指定的大屏事件通道

@Aspect
@Component
public class ScreenPushAspect {

    @Autowired
    private ScreenPusher screenPusher; // 推送核心组件

    @Around("@annotation(screenPush)")
    public Object around(ProceedingJoinPoint pjp, ScreenPush screenPush) throws Throwable {
        // 执行业务方法
        Object result = pjp.proceed();

        // 将结果推送到大屏
        if (screenPush.async()) {
            CompletableFuture.runAsync(() -> screenPusher.push(screenPush.value(), result));
        } else {
            screenPusher.push(screenPush.value(), result);
        }

        return result;
    }
}

五、推送组件(以 WebSocket 为示例)

支持广播给所有大屏,也支持按 screenId 推送。

@Component
public class ScreenPusher {

    @Autowired
    private WebSocketServer webSocketServer; // 业务自己的 WS 组件

    public void push(String event, Object data) {
        String json = JSON.toJSONString(data);
        webSocketServer.broadcast(event, json);
    }
}

六、WebSocket 服务端示例

@Component
@ServerEndpoint("/ws/screen/{screenId}")
public class WebSocketServer {

    private static final Map<String, Session> SESSION_MAP = new ConcurrentHashMap<>();

    @OnOpen
    public void onOpen(Session session, @PathParam("screenId") String screenId) {
        SESSION_MAP.put(screenId, session);
    }

    @OnClose
    public void onClose(@PathParam("screenId") String screenId) {
        SESSION_MAP.remove(screenId);
    }

    public void broadcast(String event, String json) {
        SESSION_MAP.values().forEach(session -> {
            try {
                session.getBasicRemote().sendText(buildMessage(event, json));
            } catch (Exception ignored) {}
        });
    }

    private String buildMessage(String event, String json) {
        return "{" + "\"event\":\"" + event + "\"," + "\"data\":" + json + "}";
    }
}

七、业务层如何使用?(最简单的体验)

只需要加注解即可!

@ScreenPush("order")
public OrderDTO loadOrderInfo() {
    return orderService.getOrderSummary();
}

调用此方法时,大屏自动收到:

{
  "event": "order",
  "data": {
    "todayCount": 123,
    "totalAmount": 88888
  }
}

八、支持定时任务 + 注解自动推送(大屏常用)

@Scheduled(cron = "0/10 * * * * ?")
@ScreenPush("pv")
public PvDTO calcPvUv() {
    return pvService.statPvUv();
}

每 10 秒自动推送数据,无需任何额外代码。


九、支持多大屏 ID 推送(可选增强版)

注解增强:

String[] screenIds() default {};  // 空默认推所有

切面根据 screenIds 精准推送。


十、优点总结

✔ 业务与推送解耦

业务不再需要接触 WebSocket / SSE。

✔ 推送逻辑统一维护

减少重复代码。

✔ 易扩展,多推送方式可切换

WebSocket → SSE → Kafka → RedisPubSub。

✔ 支持多大屏 ID

适合多大屏同时在线。

✔ 支持定时任务自动推

非常适合大屏定时刷新。

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐