
简介面向数据分析与可视化平台开发者的完整源码项目覆盖数据源管理、项目管理、数据集处理、图表与看板配置等核心模块支持数据接入、清洗、展示的一体化流程可直接用于构建或二次开发企业级数据展示系统降低从零搭建的复杂度。压缩包共1160个文件、约27.19MB以682个Java文件为核心辅以119个FTL页面模板、95个JS脚本、17个CSS样式、9个SQL脚本及XML/JSON等配置目录层次清晰前后端模块划分明确从数据接入、清洗转换到图表渲染与看板布局可循完整链路学习。已有389人学习浏览适合具备一定Java Web基础、希望掌握数据可视化平台实现思路的读者。通过源码不仅能梳理后端服务、前端模板与数据库脚本之间的协作关系还能借助SQL文件快速初始化本地环境并在此基础上扩展新的数据源类型或自定义图表组件灵活适配业务需求为后续二次开发提供清晰参考。1. 一个数据可视化分析平台源码最难的不是画图图表库永远是最好解决的部分ECharts一挂、数据一填五分钟能出十种图。真正难啃的是数据口径同一个指标在不同业务库里叫法不一致数据源分布在不同机房连接参数、账号密码、权限粒度全都不一样。大屏上线前一半时间都在核对口径而不是在调样式。免费数据可视化大屏模板能解决展示问题解决不了数据源切换、权限隔离和下钻联动这三个生产环境逃不开的环节。这套数据可视化分析平台的源码把数据源管理、数据集、图表、看板四层拆开配合MySQL等数据库做持久化正好补上这三环。它适合做企业内部BI平台二次开发也适合课程设计从源码直接拉起一个完整可演示的系统。下面按分层结构拆从静态资源一直拆到启动脚本。2. 源码结构与数据源管理primeflex、theme.css 背后挂着的连接池2.1 前端静态资源在平台里的分工这套平台运行时的CSS和JS清单不长但每一条都能对上具体功能。拿到源码包先别急着启动把这些资源在工程里的位置搞清楚后续改界面效率会高很多。资源文件承担的角色primeflex.min.css栅格与布局样式12列弹性网格看板里图表卡片的位置全靠它控制theme.cssPrimeFaces 组件主题控制按钮、输入框、弹窗的视觉统一primeicons.css图标字体菜单和操作按钮靠它避免散装图片datatables.min.css明细型表格样式用于数据集预览或详情页的数据表渲染jquery.datetimepicker.min.css日期时间选择器样式在数据集筛选条件和看板全局时间控件中使用analysis.css / style.css平台自定义覆盖样式用来对齐大屏配色与带单位的指标卡片从文件组合可以判断这个源码走的是 Java 服务端渲染路线PrimeFaces 是主框架jQuery 负责前端交互没有把图表全部交给前端工程单独构建。对做二次开发的人来说这意味着大部分页面改版直接在模板文件里就能改不需要额外启动一套 Node.js 前端链路。2.2 数据源注册与连接池管理数据源是所有分析的入口。平台在界面上维护数据源配置背后维护着一组连接池。常见做法是用一个注册表组件按数据源名称缓存 HikariDataSource避免每次查询都重新建连接。Component public class DataSourceRegistry { private final MapString, HikariDataSource poolMap new ConcurrentHashMap(); public HikariDataSource getPool(DataSourceConfig cfg) throws Exception { // 已注册过直接返回避免重复创建连接池 if (poolMap.containsKey(cfg.getSourceName())) { return poolMap.get(cfg.getSourceName()); } HikariConfig hikari new HikariConfig(); hikari.setJdbcUrl(buildJdbcUrl(cfg)); hikari.setUsername(cfg.getUsername()); hikari.setPassword(AESUtil.decrypt(cfg.getPassword())); hikari.setMinimumIdle(2); // 空闲连接保持 2 个 hikari.setMaximumPoolSize(8); // 单个数据源最大 8 个连接 hikari.setConnectionTimeout(3000L); hikari.setPoolName(pool- cfg.getSourceName()); HikariDataSource ds new HikariDataSource(hikari); poolMap.put(cfg.getSourceName(), ds); return ds; } private String buildJdbcUrl(DataSourceConfig cfg) { if (mysql.equalsIgnoreCase(cfg.getType())) { return jdbc:mysql:// cfg.getHost() : cfg.getPort() / cfg.getDbName() ?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai; } if (postgresql.equalsIgnoreCase(cfg.getType())) { return jdbc:postgresql:// cfg.getHost() : cfg.getPort() / cfg.getDbName(); } return jdbc:oracle:thin: cfg.getHost() : cfg.getPort() : cfg.getSid(); } }这段代码的关键在三个参数。minimumIdle控制空闲保活连接数避免频繁建连maximumPoolSize设为 8 而不是默认 10是因为平台面向多数据源场景每个源都建大连接池总连接数会成倍累积很容易把业务库的连接数打满connectionTimeout设为 3000 毫秒保证数据源不可用时看板能快速报错而不是一直转圈等待。密码字段入库前做 AES 加密解密放在注册连接池这一步数据库里不能出现明文密码。2.3 数据源配置表与工程迁移数据源配置持久化在平台自己的主库里建议单独建库与业务数据隔离。表结构可以按下面方式建字段不追求多但每个都有实际用途。CREATE TABLE datasource_config ( id INT AUTO_INCREMENT PRIMARY KEY, source_name VARCHAR(64) NOT NULL COMMENT 数据源名称全局唯一, type VARCHAR(16) NOT NULL COMMENT mysql/postgresql/oracle, host VARCHAR(128) NOT NULL, port INT NOT NULL, db_name VARCHAR(64) NOT NULL, username VARCHAR(64) NOT NULL, password VARCHAR(256) NOT NULL COMMENT AES密文, remark VARCHAR(255) DEFAULT NULL, created_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;source_name必须唯一数据集和图表通过这个名字引用数据源避免在几十个数据集里散落数据库 IP 和账号。做环境迁移时用 Python 脚本把配置一次性导出比较方便密码改由新环境配置后逐条校验连接。import pymysql import json conn pymysql.connect(host127.0.0.1, userroot, password123456, databaseviz_platform, charsetutf8mb4) with conn.cursor() as cursor: cursor.execute(SELECT source_name, type, host, port, db_name, username FROM datasource_config) for row in cursor.fetchall(): print(json.dumps(dict(zip( [source_name, type, host, port, db_name, username], row ), ensure_asciiFalse, defaultstr))) conn.close()这个脚本只导出配置不导出密码迁移后逐条验证连接即可。多数据源场景下平台本身的主库保存配置与看板元数据业务数据始终停留在各自数据源中两种角色不能混用。数据源的连接校验建议用Connection.getMetaData().getDatabaseProductName()反向核对页面选择的类型防止用户选了 MySQL 但地址指向 Oracle。数据集、图表、看板都带project_id字段新成员加入时按项目维度授权看板列表只展示当前用户有权限的项目内容越权问题先卡在项目层再靠后文提到的 SQL 条件过滤到行级。3. 数据集构建把 SQL 查询转换成图表可消费的列模型3.1 数据集查询的 SQL 规范与参数绑定数据集在平台里不是一个物理表而是一段查询加一组列定义、一组可替换参数的组合。用户先选数据源再写 SQL平台执行查询并把结果集包装成统一列模型图表层只读列名和类型不关心数据来自哪张表。保存和预览是配套动作保存时校验 SQL 语法预览时限制前 100 行。平台查询参数用#{}占位符绑定后端接收 Map 参数后预处理 SQL避免用户把参数拼进字符串。一个典型的数据集 SQL 长这样SELECT DATE_FORMAT(o.create_time, %Y-%m-%d) AS day, COUNT(DISTINCT o.user_id) AS active_users, ROUND(SUM(o.amount), 2) AS gmv, c.city_name AS city FROM order_table o LEFT JOIN dim_city c ON o.city_id c.id WHERE o.create_time BETWEEN #{startDate} AND #{endDate} AND (#{city} IS NULL OR c.city_name #{city}) GROUP BY DATE_FORMAT(o.create_time, %Y-%m-%d), c.city_name ORDER BY day DESC;这里有三个细节容易踩坑。#{city} IS NULL这种写法对应可选筛选条件当页面不传城市时平台自动注入 NULL避免动态拼 SQL 的注入风险。DATE_FORMAT是 MySQL 的日期格式化数据源换成 Oracle 就要改TO_CHAR(o.create_time, YYYY-MM-DD)平台在解析 SQL 时要做方言适配否则跨数据源迁移数据集时 WHERE 条件没问题GROUP BY 反而先报错。ROUND(SUM(...), 2)控制指标精度后端拿到的是 BigDecimalJSON 序列化时要避免转成科学计数法。参数注入的时机也很关键。数据集配置里的参数除了绑定看板全局时间控件还可以绑定当前登录用户。比如做区域经理看板时SQL 里写dept_id #{currentUser.deptId}后端在查询前解析以#{currentUser.开头的参数从登录上下文填充而不是等前端传值。这样同一个数据集配置可以被多个角色复用不需要每个角色复制一份数据集。3.2 日期时间控件与筛选条件联动看板右上角的全局时间筛选通常用 jquery.datetimepicker 对应的组件渲染。前端初始化的常见写法$(#globalTimeRange).datetimepicker({ format: Y-m-d, timepicker: false, startDate: new Date(new Date().setDate(new Date().getDate() - 30)), endDate: new Date(), onChangeDateTime: function (dp, $input) { // 触发看板全部图表重新加载两个时间参数注入数据集查询 window.VizDashboard.reloadAll({ startDate: dp.getStartDate(), endDate: dp.getEndDate() }); } });format必须和数据集 SQL 里 DATE_FORMAT 的模式一致页面传 2025-05-01SQL 里就按%Y-%m-%d解析两边不一致轻则查出空数据重则日期被隐式转换按天维度的数据直接少一天。快捷区间按钮放在插件外面点近7天时先算日期区间再回填输入框事件统一走onChangeDateTime保证所有图表走同一条参数通道。3.3 数据集预览与缓存策略数据集保存前平台会把用户 SQL 包一层 Limit 再执行防止手滑把整张千万级大表拖进预览SELECT * FROM ( 原始数据集SQL ) t LIMIT 100MySQL 和 PostgreSQL 都支持 LIMITOracle 要换成FETCH FIRST 100 ROWS ONLY。预览只影响前 100 行真实图表全量查询时才走缓存策略。每个数据集有refresh_type字段常用三种取值实时、定时、手动。实时适合小数据量定时用 Quartz 表达式。看板初始加载会并发触发十多个图表查询没有缓存时数据库连接池瞬间被打满有了缓存之后相同参数的数据集只查一次。缓存 key 建议拼成dataset:{datasetId}:{paramMd5}参数变化才重新查询参数没变直接命中。注意缓存只对查询结果生效数据集 SQL 里如果用了NOW()这类动态时间函数同一个缓存 key 下结果不会自动变需要把这类时间参数显式放入 paramMd5 参与计算。4. 图表配置与看板编排从图形参数到拖拽布局4.1 图表类型与数据字段映射平台内置的图表类型不必追求多把每类用透就够。数据集查询出的列经过配置界面做字段映射后交给 ECharts 渲染。常见映射关系如下chart_type渲染类型典型字段配置适用场景lineECharts linexday, yactive_users趋势与周期对比barECharts barxcity, ygmv排名与离散对比pieECharts pienamecity, valueamount占比结构scatterECharts scatterxday, yuv, sizeamount相关性分析tableDataTables全部列直接展示明细下钻图表配置落库时option_json存两份一份是平台维护的字段映射一份是最终给 ECharts 消费的配置。平台自动生成 Option 时覆盖标题、图例、坐标轴单位、颜色序列。用户自定义部分像 tooltip 函数不能直接 JSON 序列化这类场景预留一个custom_js扩展字段存函数字符串渲染时动态执行。一个折线图的最小 Option 结构{ tooltip: {trigger: axis}, legend: {data: [GMV]}, grid: {left: 3%, right: 4%, bottom: 3%, containLabel: true}, xAxis: {type: category, data: ds.columns[day]}, yAxis: {type: value, name: 金额(元)}, series: [{ name: GMV, type: line, smooth: true, data: ds.columns[gmv] }] }ds.columns[day]表示从数据集结果里按列名取数组图表层不做任何二次聚合所有聚合都推回数据集 SQL 完成。这是平台设计里的一个重要原则计算逻辑留在 SQL 层图表层只负责呈现。如果图表层反复做 reduce、filter口径会散落到几十个图表配置里后续对账非常痛苦。4.2 看板布局的持久化方案看板页面由多个图表卡片组成采用栅格布局一行为一组行内按 12 列跨度切分。布局保存成一个 JSON 字段结构如下{ rows: [ {cols: [{chartId: 12, span: 12, height: 360}]}, {cols: [{chartId: 13, span: 6, height: 320}, {chartId: 14, span: 6, height: 320}]} ] }rows数组对应页面从上到下的每一行cols内的span合计为 12 说明这一行排满。拖拽时用 PrimeFaces 的 Draggable 组件维护位置用 Sortable 维护顺序缩放结束后前端遍历所有卡片的偏移和尺寸重写 JSON 并 POST 到看板保存接口。布局保存后还要区分草稿态和发布态。编辑中的布局只影响预览点发布才把layout_json复制到线上版本确保大屏演示时不会因为其他人误操作导致布局抖动。版本记录表保存每次发布前后的 JSON diff回滚时取上一次发布记录即可。4.3 联动下钻与权限隔离看板里的图表不能只做静态展示页面需要一个全局事件总线。点击饼图的某个扇区触发事件并向同看板其他图表注入过滤参数window.VizEventBus new Vue(); // 在饼图渲染完成后绑定点击 chart.on(click, function (params) { // 把点击的扇区名称作为过滤条件广播出去 window.VizEventBus.$emit(global-filter, {city: params.name}); }); // 业务数据图表监听同一个事件 window.VizEventBus.$on(global-filter, function (filter) { loadChartData(filter); });VizEventBus是轻量的事件中心比在每张图表里硬编码关联干净得多。params.name是 ECharts 点击回调里数据项名称作为联动条件监听方拿到后刷新自身查询参数联动链路闭环。提示下钻联动的过滤条件不要直接拼进 SQL统一走数据集参数通道否则一套看板换数据源后联动就断了。企业级数据可视化平台和个人大屏项目最大的不同就是数据权限必须能下钻到行级。不同用户看同一张看板时SQL 注入的过滤条件不一样。仅看板层做按钮隐藏是不够的权限条件要下沉到数据集 SQL在数据集解析阶段统一追加条件子句展示层只是锦上添花。5. startup.bat 启动验证与二次开发技巧5.1 启动脚本里的关键参数源码包里的 startup.bat 是 Windows 下的启动入口。它不只是拉起服务还负责把字符集、JVM 堆、数据库连接参数一次性安排好。一份可改的脚本参考echo off chcp 65001 nul set JAVA_HOMED:\soft\jdk17 set CATALINA_HOMED:\soft\apache-tomcat-9.0.80 set APP_OPTS-Xms512m -Xmx2048m -Dfile.encodingUTF-8 set DB_URLjdbc:mysql://127.0.0.1:3306/viz_platform?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse call %CATALINA_HOME%\bin\startup.batchcp 65001改控制台代码页解决 Windows 下中文日志乱码。serverTimezoneAsia/Shanghai必须显式指定否则 MySQL 驱动拿服务器默认时区按天统计的数据集会差 8 小时甚至生成错误日期。useSSLfalse适用于内网部署避免证书校验增加连接延迟。DB_URL 用引号包裹是因为 URL 里的在批处理里是命令分隔符不加引号会被截断。平台打成可执行 jar 包时启动行换成java %APP_OPTS% -jar viz-platform.jar --spring.datasource.url%DB_URL%。5.2 启动中的常见异常和处理启动阶段出问题按顺序排查四个点。一是报Access denied for user先确认数据源配置表里的密码是 AES 密文而非明文AES 密钥与工程解密代码不一致时会报填充错误。二是报Unknown database viz_platform说明主库还没初始化先执行源码包里的建库脚本。三是启动成功但看板接口报连接池超时大概率是配置的 IP 是内网地址而当前机器不在这个网段先用telnet host port确认 TCP 通不通。四是登录后图表区空白打开浏览器控制台看请求返回多半是数据集 SQL 执行报错把后台日志里的完整 SQL 贴到数据源客户端里跑一遍比盲改配置更快定位。5.3 给图表加一段自定义扩展平台预留的custom_js字段可以把 ECharts 的高级配置注入到自动生成的 Option 上。比如给 GMV 折线图加渐变面积背景在图表配置里写function customize(option, data) { option.series[0].areaStyle { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ {offset: 0, color: rgba(56,132,255,0.25)}, {offset: 1, color: rgba(56,132,255,0.02)} ]) }; return option; }series[0]定位到第一个序列areaStyle是折线图的面积填充配置。平台渲染时先执行自动 Option 生成再执行自定义函数覆盖这样用户不需要动平台核心代码就能把个性化样式隔离在业务配置里后续升级源码时自定义逻辑不会被覆盖。本文还有配套的精品资源点击获取