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 类进行接收处理的,使用方式也很简单。

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

最近要用到,收藏 + 点赞

热门分享

扫码入社