

需求长什么样
万村乐数字乡村在 2024 年 4 月推出了可视化大数据平台 V1.0,前端用的是 Vue 3.0 配 Vite 3.0 和 ECharts 5.0,地图那一层挂了 Mars3D 3.6。平台本身是低代码的,数据源管理原生支持 MySQL 这类关系库,组件拖出来绑数据就能出图。
真正麻烦的是需求的形状。县里要的是「一张图看全县」,镇里要分村排名,村里要的是自己那几项指标,还有的村点名要把家庭积分排行放大屏上滚动。我们统计过一段时间的大屏工单,超过七成属于换指标、换维度、换图表类型,真正需要新写取数逻辑的不到两成。
这种分布决定了方案:报表引擎要把取数和展示彻底拆开,展示交给低代码画布,取数收敛成一套可配置的模型,绝不能让每个村都去改一次代码。
取数不给裸 SQL,只给数据集
第一版我们图省事,让实施人员在后台直接贴 SQL,组件绑 SQL 就出数。三个月之后这套东西变成灾难:有人写了不带village_id的全表扫描,有人在大屏上跑了一条五张表JOIN的语句,一次刷新拖垮整个从库。
第二版改成数据集。一个数据集是一段带占位符的 SQL 模板,加上参数定义和字段元数据,实施人员只能选数据集、填参数,不能自己拼语句。
villageIds不在参数列表里,这是刻意的。它由服务端从当前会话解析,前端传什么都不认。万村乐后台的权限按省、市、县、镇、村五级划分,大屏这一侧如果让前端决定查哪些村,改一个请求参数就能看别的县的数据。
参数注入这块只能用白名单
模板里除了值参数,还有需要动态替换的标识符,例如按月看还是按季看,排序字段是哪一列。这类东西没法用 JDBC 的?占位,只能拼字符串,也就成了注入的高发点。
slot.getAllowed()是配置时就固定下来的集合,例如粒度只允许DAY、MONTH、QUARTER三个值,排序列只允许字段元数据里声明过的那几列。这样拼出来的字符串永远来自后端常量,用户输入只用于挑选,不参与拼接。
max_rows也不是摆设。大屏组件展示能力有限,一条折线画 2000 个点已经看不清,超过上限直接截断并在响应里带一个标记位,前端提示按更粗的粒度看。

缓存分三层,各管各的事
大屏是典型的读多写少,同一个页面在会议室投屏时可能有十几个人同时开着,每 30 秒轮询一次,不做缓存等于自己打自己。
第一层是数据集结果缓存,键由数据集编码、参数值、区划范围三部分哈希得到,存 Redis,TTL 按数据集配置,收入趋势这种按月更新的给 300 秒,告警统计这类给 30 秒。
区划范围必须进缓存键。少了这一项,县级账号先查一次,村级账号后查就会命中县级的缓存,这是很容易出的越权,而且难以在测试环境复现,因为测试数据里区划往往只有一个。
第二层是接口合并。村级数据大屏原本一屏要拉 6 个接口,改成一次请求带多个数据集编码,服务端并行取数后合并返回,弱网环境下首屏时间明显好转。
第三层是预计算。跨村排名这类需要扫全县的查询不走实时,由定时任务在凌晨算好写进 ADS 层的结果表,大屏直接读结果表。查询从几秒降到几十毫秒,代价是数据延迟一天,对排名类指标可以接受。
导出和大屏用同一套引擎
报表这块除了看,还有导出需求。镇里报材料要 Excel,村里公开栏要 PDF。我们没有单独做一套导出逻辑,导出复用同一个数据集,只是换了渲染器。
导出的行数上限跟大屏不一样,放到 50000 行,超过就分 Sheet。生成走异步任务,前端拿任务号轮询,避免几万行数据在 HTTP 请求里跑超时。字段的label、unit、scale这些元数据在导出时同样用得上,表头和小数位不用再配一遍。
版本差异带来的工程约束
功能清单上,可视化大数据在标准版是不开放的,只有更高的版本档位才有。这意味着报表引擎必须能整块摘掉:接口、菜单、定时预计算任务全部挂在一个开关下,关掉之后系统照常跑,数据库里那些 ADS 表不建也不影响主流程。
私有化部署也逼着我们克制。客户机器常见配置是 4 核 8G,Druid 连接池那边我们给只读报表流量单独指到从库,池子上限设小一点,防止一个大屏把连接吃光导致村民在小程序上连办事大厅都打不开。业务优先级排在报表前面,这条在配置里写死,不给现场调整的余地。
几个容易忽略的实现细节
时间参数不要让前端算。本月、上月、近 30 天这类相对时间,如果由浏览器根据本地时钟生成,遇到客户机器时区设错或者时钟不准,查出来的数据会比服务端晚一天,村里对不上账就会来投诉。我们把相对时间做成后端解析的符号,前端只传符号名。
空数据要给默认结构。有的村刚上线,收入表里一条记录都没有,接口返回空数组,ECharts 5.0 那边直接渲染成一片空白,看起来像系统坏了。数据集配置里加了补零开关,按查询区间把缺失的月份补成 0,图形至少有一条贴着横轴的线。
数值格式统一在服务端处理。金额按元还是按万元、保留几位小数,这些交给前端各写一遍必然不一致,同一个数字在两块大屏上显示成 12.5 万和 125000 元,村干部会以为数据错了。字段元数据里的unit和scale就是为了把这件事收在一处。
回过头看
报表引擎做到现在,实施人员配一块新大屏的耗时从原来提需求排开发的几天,压到了自己动手一两个小时。真正省下来的不是开发量,是每次改指标都要走一遍发版流程的那段等待。数据集这层抽象加上区划强制注入,让越权这类高危问题在框架层就被堵住,比靠代码评审去发现要可靠得多。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。