JFinal使用技巧-表格数据SSE流式加载

最近比较忙,一直没分享什么有意思的内容。刚好前两天在老项目中,遇到一个问题,今天分享一下解决方案:)

是一个业务统计汇总记录功能,由于使用年限长了,系统数据也比较多了,导致该页面功能打开特别卡(加载计算数据太久了)。
并且有大量java计算逻辑和计算使用的上下文数据在后台,并不能简单拆分为多个http请求。再加上老系统大改造的话工期也不划算。

于是就想到了使用SSE进行后台逐行推送。由于老项目JF也是老版本,里面并没有 JF 的 SseEmitter.java 类,好在该类是独立的,可以从JF高版本中拷贝过来直接用。


1、老项目的渲染方式是数据与模板在同一个Controller的Action方法中完成的,这样就导致数据在计算时,浏览器是空白加载状态。这个问题 就把代码拆分一下,增加一个event参数,先直接渲染html,把表格数据使用SSE二次推送过来。代码简例:

Controller{

    myAction {
        String event = get("event", "view");
        if ("data".equals(event)) {
            MyService.ME.data(new SseEmitter(getResponse()));
            renderNull();
            return;
        }
        render("xxx.html");
    }

}

2、Service代码中每处理一块数据时,就向前端发送一段数据, 并且可以增加进度条字段。代码简例:

MyService{
    data(SseEmitter sse){
        //查出需要处理的数据量
        List<Record> recordList = Db.find(...);
        HandlerMap handlerMap = xxx;//业务逻辑处理器
        int count = 0;
        int totalCount = recordList.size();
        for (Record r : recordList) {
            count ++;
            r.set("COUNT", count);
            
            //业务处理器
            handlerFn(handlerMap, r);
            
            //向浏览器发送数据(一行一渲染)
            sse.sendMessage(Kv.by("event", "record").set("data", r)
                    .set("totalCount", count + "/" + totalCount));
    
        }
        //发送汇总数据
        sse.sendMessage(Kv.by("event", "sum").set("data", handlerMap.sumData())
            .set("totalCount", totalCount));
        //结束
        sse.complete();
    }
}

3、html之前是在后端渲染的数据,现在我改为了使用前端JS模板+返回json进行渲染(项目是layui)。当然也可以让后端渲染html片段进行推送到前端,进行充填也是可以的。代码简例:

html...
<查询项代码省略。。。>
<table>
    <thead>
        <tr>
            <th>标题代码省略了</th>
            。。。
        </tr>
    </thead>
    <tbody id="table_body">
        <tr><td colspan="N" onclick="event_data_fn()">点击查询按钮后显示列表数据、导出按钮</td></tr>
    </tbody>
</table>
<script type="text/html" id="tpl_table_tr">
    <tr>
        <td>{{d.COUNT}}</td>
        字段内容代码省略。。。
    </tr>
</script>
<script>
    function event_data_fn() {
        layui.use('laytpl', function(){
            var tpl_table_tr = layui.laytpl($('#tpl_table_tr').html());
            var table_body = $("#table_body");
            table_body.html("");
            省略进度条和汇总等字段内容的清理代码...
            // 重点看 SSE 接收数据的代码:
            // 流式加载表格数据
            var param = $("#searchForm").serialize();
            
            const eventSource = new EventSource('/xxx/myAction?event=data&' + param);
            
            eventSource.addEventListener('message', function (event) {
                var kv = JSON.parse(event.data);
                console.log('kv=', kv);
                if(kv.event === 'record'){
                    table_body.append(tpl_table_tr.render(kv.data));
                    // 进度条
                    totalCount.text(kv.totalCount);
                }else if(kv.event === 'sum'){
                    省略进度条汇总数据字段的展示处理。。。
                    // 主动关闭事件源
                    eventSource.close();
                }
            });
            eventSource.addEventListener('error', function (event) {
                eventSource.close();
            });
        });
    } 
</script>

可以看到前端是使用 EventSource 类进行接收处理的,使用方式也很简单。
PS:也可以使用 @microsoft/fetch-event-source 是微软开发的 JavaScript 库,用于在浏览器和 Node.js 环境中实现 Server-Sent Events (SSE) 通信。它基于 Fetch API 构建,提供了比原生 EventSource更强大的功能,适用于需要自定义请求头、POST 方法、复杂错误处理等场景。(@bensharp 反馈image.png


4、线上系统是使用nginx的。所以路径需要配置一下设置为 http_version 1.1 以及关闭缓存功能。让java推一次,浏览器就立刻收到一次JS执行起来。不然nginx给缓存起来,攒一起后再给浏览器,就显得卡卡的失去了流式加载的效果。代码简例:

# 和自己项目功能路径对应上
location /xxx/myAction {
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_read_timeout 60s;
    proxy_buffering off;
    proxy_cache off;
    # 更换为对应容器端口
    proxy_pass http://127.0.0.1:1011;
}


好了分享完毕~

 

可以作为一个参考,适合各种流式加载的业务处理方案。

评论区

JFinal

2025-10-12 16:43

最近要用到,收藏 + 点赞

zzutligang

2025-10-15 11:16

收藏 + 点赞

bensharp

2025-10-16 22:14

浏览器自带的EventSource只支持GET方法,可以试试@microsoft/fetch-event-source这个库,让前端处理的功能更强一些

杜福忠

2025-10-17 00:24

@bensharp 👍 ,查阅了一下资料:
@microsoft/fetch-event-source 是微软开发的 JavaScript 库,用于在浏览器和 Node.js 环境中实现 ​​Server-Sent Events (SSE)​​ 通信。它基于 Fetch API 构建,提供了比原生 EventSource更强大的功能,适用于需要自定义请求头、POST 方法、复杂错误处理等场景。

这个工具在前后分离项目中确实非常有必要,令牌之类的在Authorization自定义头里面,EventSource就没法搞了。
我加到帖子中有需要的可以少绕弯路

jfinal009

2025-10-17 14:31

@杜福忠 handlerFn(handlerMap, r); 这块精华,分享一下呗😁

杜福忠

2025-10-17 15:36

@jfinal009 这里面是业务逻辑的处理,handlerMap 是一个键值的业务bean,在循环业务处理中临时存储需要的上下文数据。
比如:
(JVM优化 建议一个方法是50行内)。
一些简单的业务逻辑处理比如200行内是能阅读的。
当超过500行的业务时,阅读就很费劲了烧脑。一般是需要拆分为多个方法的,基本在一个Service类里面就可以,需要的数据就用 方法的参数相互传递,就能很好的阅读了。
但是业务需要上千行代码处理时,就需要业务bean对象承载业务数据,创建业务对象类,各个类负责相应的小业务块处理。

bensharp

2025-10-17 16:55

@杜福忠 @microsoft/fetch-event-source官方只提供了esm和cjs两种方式使用,我的项目也是用layui,不想使用module的方式来用fetch-event-source,我还去折腾了一下把官方git下载的源码通过webpack打包成UMD的形式,直接通过script标签导入页面中使用。

热门分享

扫码入社