首页 / 资讯中心 / 文章详情

UReport2报表设计器集成实践:在线表格与Spring Boot的融合

UReport2报表设计器集成实践:在线表格与Spring Boot的融合 ★ FEATURED ARTICLE
我需要先说明一点这篇内容来自我实际做过的一个内部运营数据平台项目不是纯理论整理。当时我们团队规模不大要在不到一个月的时间里交付几十张报表还要满足业务侧“表格样式能自己调”的诉求。调研了一圈之后我们最终选定了 UReport2 报表设计器作为在线表格方案的底座。如果你也在考虑“报表设计器选型”或者“怎么把在线表格能力嵌进自己的系统”这篇经验应该能帮你少走不少弯路。1. 我为什么在报表项目里选 UReport2在线表格带来的产品价值1.1 业务方的真实需求是“能自己改样式”不只是“能打印表格”项目启动时运营侧提的需求很直接每个月要看区域销售对账表格式经常变这个月要加一列同比下个月又想把某些行标黄。以前的做法是运营提需求、开发排期、改代码、重新发布一张表改三次一周就过去了。我们内部统计过一张纯前端的表格页面从拿到需求到上线至少需要两天如果是带分组、动态列、合并单元格的复杂报表三天起步。所以当时真正需要的不是一个“导出 Excel 的工具”而是一个在线表格设计器让业务人员能在浏览器里拖拖拽拽自己调表头、调汇总行、调整字体颜色改完直接保存发布。UReport2 刚好提供了这个能力它的设计器打开就是一个类 Excel 的界面使用者不需要学习新的设计语言业务侧接受成本很低。1.2 对比了三条路线为什么没有自己写、没有用 JasperReports、也没有选商用报表在定方案之前我认真比较过四类做法方案是否能在线改样式开发成本授权成本适合场景自研在线表格能但要做成能用成本极高很高无有专门前端团队做复杂表格产品JasperReports不能Studio 是桌面端改样式要重新出 jrxml中高开源版免费固定格式的 PDF 报表FineReport 等商用报表能低高私有化授权费用不便宜预算充足、要完整 BI 能力的项目UReport2能设计器在浏览器里中低开源免费中后台业务报表、内部管理系统自研这条路我直接否掉了。在线表格不是画一个table就行它需要格子模型、行列拖拽、合并单元格、公式引擎、分组渲染、打印分页一套下来工程量远超业务报表本身。我们项目周期不允许也不该把资源砸在这里。JasperReports 我也熟以前用过一段时间。它在 PDF 生成上很稳但设计器是桌面端业务人员想自己调样式很困难而且复杂中国式报表斜线表头、动态合并、交叉表在 jrxml 里手工维护 XML能把人写到崩溃。商用报表工具功能确实强但我们的场景是给内部运营用预算有限也不想被厂商绑定。开源的 UReport2 就成了综合成本最低的选择。1.3 对 UReport2 先建立一个理性预期UReport2 不是一个“新项目”代码更新频率不高社区也不算活跃。它的核心组件是ureport2-core和ureport2-console提供了设计器、预览、导出、图表等能力整体定位是“开源的中国式报表引擎”。你可以把 UReport2 理解成一个半成品能力底座它把最复杂的在线设计器和报表渲染引擎做好了但集成到具体业务系统时你需要自己处理权限、存储、数据源、部署细节。这个预期很重要——如果以为引入依赖就能直接给业务用上线后一定会在集成环节吃亏。2. 从部署到打开设计器Spring Boot 集成与第一个在线表格2.1 依赖引入与版本选择我们项目用的是 Spring Boot 2.7JDK 8。UReport2 在这套环境下跑得很稳如果你用的是 JDK 17 或者 Spring Boot 3.x需要额外评估兼容性因为它内部一些 Servlet 相关的 API 依赖的是 javax 包Spring Boot 3 改成了 jakarta直接引入大概率会有麻烦。Maven 坐标一般这样加dependency groupIdcom.bstek.ureport/groupId artifactIdureport2-console/artifactId version2.2.9/version /dependency网上也能找到一些第三方维护的坐标但我建议优先用这个官方 GAV。ureport2-console本身会传递依赖ureport2-core不需要重复引入。2.2 注册 UReportServlet否则设计器打不开UReport2 的设计器、预览、导出功能都是通过一个 Servlet 提供的路径是/ureport/*。在传统 SSM 项目里通过 web.xml 配置就行但 Spring Boot 没有 web.xml需要手动注册。我项目里的配置类大致长这样Configuration public class UReportConfig { Bean public ServletRegistrationBeanUReportServlet ureportServlet() { ServletRegistrationBeanUReportServlet registration new ServletRegistrationBean(); registration.setServlet(new UReportServlet()); registration.addUrlMappings(/ureport/*); registration.setLoadOnStartup(1); return registration; } }这里有一个我踩过的细节addUrlMappings的路径必须是/ureport/*尾部的/*不能省略。只写/ureport会导致匹配不到后续路径设计器页面 404。2.3 配置文件存储目录UReport2 的报表文件默认是以 XML 形式保存在文件系统里的。需要指定一个目录它提供了配置项ureport.fileStoreDir。在 Spring Boot 的application.properties里加上ureport.fileStoreDir/data/ureport/files这个目录建议提前创建并确保运行账号有读写权限。如果服务器本地目录不方便可以在后面通过自定义 Provider 改成数据库存储这一步后面会详细说。目录配错最典型的症状是保存报表时报 “file store provider error” 或者文件看起来保存了但重启后消失。2.4 打开设计器创建第一个在线表格启动项目后浏览器访问http://localhost:8080/ureport/designer第一次打开可能需要几秒加载资源看到的是一个类 Excel 的界面上面是菜单栏和工具栏中间是空白表格区域左下角有 sheet 标签右侧一般是数据源和数据集面板。新建报表时设计器会让你选择单元格行数和列数。这里不用太纠结后续随时可以插入行和列。我习惯先创建一个 6 行 6 列的空白报表把标题、表头、数据区域大致摆出来再逐步调样式。在 A1 单元格里输入“区域销售对账表”选中后设置字体加粗、字号 14、背景色这就是一个最简单的单元格样式操作。在表格区域右键可以插入行、删除行、合并单元格操作逻辑和 Excel 基本一致。保存按钮在左上角命名后文件就落到了刚才配置的目录下。3. 在线设计器里真正要掌握的三个概念单元格、数据集、表达式3.1 在线表格不等于 Excel单元格是有“关系”的用 UReport2 设计器的时候最容易产生的误区是拿它当 Excel 用合并几个格子、填上数字、加个 sum以为就完了。但报表引擎要处理的是“数据 样式 分组”单元格不是孤立的格子它是有上下左右“父格”关系的。具体来说一个单元格可以绑定一个数据集绑定之后这个单元格会在渲染时根据数据集合自动扩展成多行或多列。这就是 UReport2 做分组报表的基础。举个例子我在设计器里创建了一个数据集ds_sales它查出各区域的销售额。如果我把 B2 单元格的数据集属性设为ds_sales并且把它的上父格设为 A1表头区域那么 B2 会自动按照数据集里的行数向下扩展每一行显示一条记录。这就是在线表格背后和 Excel 最大的区别——Excel 里你得手动拖公式UReport2 里单元格本身就能“复制”自己。3.2 数据集是报表的数据入口建议先在 SQL 层把数据准备好UReport2 支持多种数据源最常用的是 SQL 数据源和 Spring Bean 数据源。SQL 数据源的配置方式很简单在设计器右侧数据集面板新增数据集选择数据源填写 SQL。比如select region, sum(amount) as total_amount from sales_order where month ${month} group by region这里的${month}是参数占位符运行时从请求参数里获取。如果报表需要接收日期范围、用户ID、部门ID都可以用同样的方式传给 SQL。这样做的最大好处是能下推到数据库的聚合逻辑绝不要放在报表单元格里算。报表引擎做数据计算的能力远不如数据库硬算大数据量会导致内存飙升。Bean 数据源适合从 Java 内存中取数比如从别的服务 RPC 拿数据、从缓存取数据。你只需要定义一个方法返回ListMapString, Object然后在设计器的数据源管理里选 Bean 数据源填写 beanName 和方法名。这个方式在集成内部系统时很灵活但需要注意返回的数据结构必须字段稳定否则报表设计阶段能预览上线后字段一变就全盘报错。在设计器里配置好数据集后建议先用“数据集预览”按钮看一遍返回数据。我见过很多同事在代码里调试半天结果发现是 SQL 写错了字段名。这个预览功能能省掉大量来回重启服务的时间。3.3 常用表达式求和、取数、格式化UReport2 的表达式基于 Groovy支持在单元格里写开头的公式也可以用${...}取值。最常见的几个用法场景写法示例说明引用单元格值A1引用 A1 的值求和sum(B2:B9)单列求和条件求和sumif(A2:A9,华东,B2:B9)按条件汇总单元格坐标A2得到 A2 单元格的行坐标常用于动态样式格式化日期format(C2, yyyy-MM-dd)把日期字段格式化拼接字符串合计 D2动态生成文本需要特别注意的是UReport2 的单元格引用和 Excel 的引用语义略有区别。在表达式里写B2拿到的是该单元格计算后的值写B2拿到的才是这个单元格在报表中的位置信息。如果你需要在动态扩展的数据行里做行号、序号这类东西经常要配合来取坐标。3.4 分组和汇总活用“左父格”和“上父格”我做区域对账表的时候第一个版本只实现了明细展示业务说不行需要按大区汇总还要能展开看明细。在 UReport2 里做分组汇总不需要写代码。我把一个单元格绑定到数据集之后在“单元格属性”里可以设置它的上父格和左父格。简单理解上父格决定一个数据集单元格纵向扩展时的层级关系左父格决定横向扩展时的层级关系。以我的对账表为例第一列显示“大区”第二列显示“省份”第三列显示“金额”。我把 B2 的上父格设为 A2C2 的上父格设为 B2这样 A2 先按大区分组B2 在大区下按省份分组C2 在省份下返回金额。渲染出来的表格就自动形成了“大区 - 省份 - 金额”的三级分组结构。汇总行也不用手写直接在分组单元格后面插入一个汇总单元格表达式写sum(C2:C9)引擎会自动按当前分组范围计算。这一块是整个设计器里最需要花时间理解的部分。如果你之前只写过 HTML 表格或者用过 Excel 公式第一次接触“父格”会有点绕但一旦理解它就是 UReport2 做复杂中国式报表最值钱的能力。4. 把报表接入业务系统预览、参数传递、导出与自定义Provider4.1 先用内置 URL 快速跑通预览如果只是让业务人员在浏览器里看报表、导出报表UReport2 内置的 URL 就能满足。预览一个报表文件的 URL 格式是http://localhost:8080/ureport/preview?_ufile:区域销售对账表.ureport.xmlmonth2024-01其中_u参数指定报表文件file:是文件系统的前缀后面跟文件名。如果有参数直接跟在 URL 后面格式和 HTTP GET 参数一样。这个 URL 可以直接发给业务人员也可以嵌在系统的 iframe 里。我在项目中就是做一个“报表中心”菜单前端用 iframe 嵌套这个预览地址业务人员点菜单就能看到最新报表。4.2 用 ExportManager 在后端代码里导出有些场景需要后端定时自动导出或者要对导出做额外的权限控制这时候就得在 Java 代码里调导出 API。我封装了一个简单的导出接口核心逻辑是注入com.bstek.ureport.export.ExportManager然后调用它的导出方法RestController RequestMapping(/report) public class ReportExportController { private final ExportManager exportManager; public ReportExportController(ExportManager exportManager) { this.exportManager exportManager; } GetMapping(/export) public void export(HttpServletRequest request, HttpServletResponse response, RequestParam String title, RequestParam String month) throws Exception { String fileName 区域销售对账表_ month .xlsx; response.setContentType(application/octet-stream); response.setHeader(Content-Disposition, attachment;filename URLEncoder.encode(fileName, UTF-8)); MapString, Object params new HashMap(); params.put(month, month); exportManager.exportExcel(request, response, params, file:区域销售对账表.ureport.xml); } }这样业务系统里的“导出”按钮就不再直接暴露 UReport2 的原始 URL而是走我们自己的接口方便做权限、审计、日志。4.3 参数传递的两种方式要分清UReport2 的参数有两种来源一种是报表文件里定义好的报表参数一种是运行时通过请求带过去的参数。在设计器里新增数据集时写了${month}这个month就是一个参数。预览和导出时只要在 URL 或参数 Map 里提供同名参数即可。另一种情况是从HttpServletRequest里直接取参数比如取当前登录用户的部门编码。我在报表里定义一个数据集其中某个参数的默认值不写死而是在代码里从 request 里拿String deptId request.getHeader(X-Dept-Id); params.put(deptId, deptId);这种方式在做行级数据权限时非常有用用户只能看到他所在部门的数据。4.4 自定义 Provider把报表文件从本地磁盘挪到数据库前面说ureport.fileStoreDir是文件系统存储但在多实例部署环境下会遇到问题报表设计器在 A 机器上保存的文件B 机器读不到。所以我建议直接实现 UReport2 的ReportProvider接口把报表文件内容存到数据库表里。接口核心方法就几个public class DbReportProvider implements ReportProvider { Override public InputStream loadReport(String file) { // 从数据库读取报表 XML返回输入流 } Override public void saveReport(String file, String content) { // 把报表 XML 保存到数据库 } Override public void deleteReport(String file) { // 删除报表 } Override public ListReportDefinition getReportFiles() { // 返回报表列表设计器文件列表会调用这里 } Override public String getPrefix() { return mydb:; // 自定义前缀代替 file: } }实现的时候注意getPrefix()要返回一个自定义前缀比如mydb:这样在 URL 里写_umydb:区域销售对账表.ureport.xml就会走你的 Provider。同时saveReport里我额外做了一个历史版本记录保存前先把旧内容 insert 到一张历史表这样业务人员改坏了报表开发还能从后台把上一个版本捞回来。这个功能在正式环境帮我们挽回了不少麻烦。5. 生产环境踩坑清单中文、字体、并发、权限一个都不能漏5.1 PDF 导出中文变成方块项目上线第一周运营反馈说“导出 PDF 后中文全是方块”。排查下来不是报表文件的问题而是 Linux 服务器上缺少中文字体。UReport2 导出 PDF 时依赖 Java 字体渲染服务器没有中文字体渲染出来的就是空心方块。处理方式很简单在服务器上安装中文字体包或者把 Windows 下的simsun.ttc、simhei.ttf等字体文件上传到服务器的/usr/share/fonts目录然后执行fc-cache -f刷新字体缓存重启应用。核心是保证 JVM 能找到中文字体。这个坑很经典建议在部署清单里提前加上不要等业务来报。5.2 大数据量导出时内存告警UReport2 渲染报表是在 JVM 内存里完成的如果一个数据集查出几百万行内存会直接被打满。我们的区域销售表按月过滤后一般只有几千行问题不大但有个月底对账场景需要导出全年明细单次导出接近 20 万行导致老年代内存暴涨。我的规避策略有三个在数据集 SQL 里做分页或汇总减少明细行数导出的数据量超过阈值时提示用户分批导出不硬扛给报表导出接口单独配置一个 JVM 参数较大的实例避免影响主业务。根本原则是别让报表引擎做全量明细导出能聚合的聚合能分页的分页不能痼疾到报表层再解决。5.3 设计器没有鉴权必须自己加一道门UReport2 的设计器 URL 默认是裸奔的任何人只要知道地址就能打开。生产环境里必须把/ureport/designer保护起来。我用的方案是加一个拦截器判断当前会话是否有管理员权限如果没有直接返回 403。预览和导出接口也需要控制不能允许未登录用户传一个_u参数就导出敏感数据。这部分我们是在网关层统一做的鉴权比在报表应用层做更稳妥。5.4 多人同时编辑同一张报表UReport2 的保存机制是覆盖式的两个同事同时打开同一张报表后保存的人会直接覆盖先保存的人没有任何冲突提示。这个问题要靠外部手段解决。我做的比较简单已打开的报表在系统里记录“编辑人”和“编辑时间”另一个人打开同一张报表时提示该报表正在被编辑只能以只读模式打开数据库 Provider 层面保存前校验版本号版本不一致就拒绝保存。如果你们团队内部用报表设计器的场景频繁一定要考虑这个并发编辑问题否则一定会出现“改了半天的报表突然没了”的投诉。5.5 浏览器兼容性和老旧 UI 的适应问题UReport2 设计器界面偏旧依赖的 jQuery 版本不高在低版本浏览器和 IE 里表现很差。我们明确要求内部用户使用 Chrome 或 Edge 访问不做兼容。另外设计器里做一些高级设置时交互不够直观。比如“单元格属性”弹窗里选项很密新用户容易找不到。我的经验是不要指望业务人员自己摸索设计器而是由开发先做 2-3 个标准模板业务人员只需要在模板基础上改改文案和颜色。在线表格解决的是“微调表达”的问题不是“从零设计”的问题。6. 如果重来一次我会调整的几个设计决策现在回头看UReport2 这套方案在我们项目里是成功的但有几个决策我会在项目初期就按下调整。第一报表模板一定要纳入版本管理。UReport2 报表文件本质上是 XML团队内部应该有一个独立的 git 仓库来管理这些 XML 文件。我们一开始没做后来出现业务人员改坏报表开发只能靠数据库历史表恢复流程很被动。改进后报表发布走 CI配合数据库 Provider 的历史版本兼顾了“开发可控”和“业务灵活”两头。第二给业务开放设计器要分阶段。先让核心运营同学试用收集反馈再逐步扩大范围。一上来给所有人开设计器权限只会造成报表风格五花八门最后还得开发去收拾。第三在线表格适合“格式灵活但结构不复杂”的报表。如果一张表要做多数据集关联、写大量后端逻辑、频繁从远程服务取数不要硬塞给 UReport2它不是你唯一的选择。报表引擎的价值是把“看得见的表格”快速做出来复杂的业务计算应该放在后端服务或数据准备层完成。第四重要报表的定时导出一定要留监控日志。我们后来给每天凌晨的“日报表自动导出”任务加了健康检查和导出记录表。不是因为 UReport2 容易出错而是任何报表体系上线久了都会遇到数据源变更、表结构调整、参数缺失等外部变化有日志才能快速定位是谁导致的。做报表是个苦活但把 UReport2 这类在线表格设计器用好了确实能把研发从“改表头颜色”这类重复劳动里解放出来。我的体会是它不是一个完美的工具但只要提前摸清它的边界——存储、权限、并发、字体、大数据量——它完全可以成为中后台系统里一个很稳的报表底座。
阅读完成 · 觉得有帮助?
咨询建站