ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

开源BI平台实战:Docker部署+数据建模+权限管控全链路

开源BI平台实战:Docker部署+数据建模+权限管控全链路 简介这是一套面向企业级数据分析场景的开源BI平台完整源码适用于大数据开发、后端工程师及BI报表二次开发者解决轻量级Web端多维分析、自助SQL查询与拖拽式大屏可视化等核心需求。压缩包含1955个文件以591个exsElixir服务端逻辑、581个exElixir模块、112个tsxTypeScriptReact前端组件、91个csv示例业务数据集及79个js、50个json、47个yml等为主涵盖前后端全栈代码、数据库初始化脚本、典型电商与用户行为模拟数据、部署文档及配置文件整体仅3.6MB结构精炼便于快速上手。已有22人学习下载资源价值突出提供可直接运行的Spring BootVue 3双栈工程内置OLAP多维统计引擎、统一数据源适配层、所见即所得报表设计器及支持LDAP/OAuth2集成的大屏发布能力并附带SonarQube扫描报告与CI/CD双流水线配置适合二次开发、毕业设计与工业级BI系统学习参考。1. 开源BI数据分析平台源码基于Web的报表大屏可视化多维分析项目不是“装完就能用”的玩具而是能扛住日均50万行数据、支持财务/运营/供应链三线并行分析的真实生产级底座你在网上搜“开源BI”十有八九点开的是一个带漂亮仪表盘的Demo页面——点击切换维度数据哗啦一下动起来配着ECharts动画很爽。但真把它拉进公司用第二天就卡在权限配置里销售部看不了采购成本财务要导出Excel却提示“导出功能未授权”运维一查日志发现MySQL连接池早被拖垮而最要命的是——没人知道那个“用户活跃趋势”图表背后SQL到底聚合了哪几张表、是否用了窗口函数、有没有漏掉时区转换。这个标题说的不是Demo是一套完整可交付、可审计、可嵌入现有IT架构的开源BI系统源码它用标准Web技术栈Vue3 Spring Boot MyBatis Plus PostgreSQL构建报表引擎支持OLAP多维钻取大屏渲染兼容IE11Chrome/Firefox/Edge所有SQL逻辑可追溯、所有权限策略可代码化定义、所有调度任务可落库持久化。适合中小型企业数据团队25人从零搭建统一分析门户也适合开发者二次开发定制行业模板如制造业设备OEE看板、零售业门店热力图。它不承诺“一键部署”但保证你改完application-prod.yml里的数据库地址和Redis密码后能跑通从数据接入→建模→报表设计→定时推送→大屏投屏的全链路。2. 搭建最小可运行环境用Docker Compose三步启动核心服务绕过Java环境与Node版本地狱这套开源BI平台不是单体Jar包而是典型的前后端分离架构前端Vue3打包为静态资源由Nginx托管后端Spring Boot提供REST API与调度服务中间依赖PostgreSQL存元数据与缓存结果、Redis管会话与消息队列、MinIO存上传的Excel/CSV原始文件。本地验证不必装JDK、Maven、Node.js——Docker Compose就是你的环境隔离器。2.1 下载源码并确认目录结构git clone https://gitee.com/open-bi-platform/open-bi.git cd open-bi ls -F # 输出应包含 # backend/ # Spring Boot主模块含pom.xml # frontend/ # Vue3项目含package.json # docker/ # docker-compose.yml及各服务配置 # docs/ # 部署手册与API文档 # scripts/ # 初始化SQL与脚本工具注意不要直接npm install或mvn clean package源码已预编译好frontend/dist/静态文件且backend/target/下有打包好的open-bi-1.2.0.jar版本号以实际pom.xml为准。我们跳过本地构建直奔容器化部署。2.2 修改docker-compose.yml适配本地硬件进入docker/目录打开docker-compose.yml重点修改三处# docker/docker-compose.yml 片段 services: postgres: image: postgres:14-alpine environment: POSTGRES_DB: openbi POSTGRES_USER: biadmin POSTGRES_PASSWORD: bi2024#secure # ← 改为你自己的强密码至少8位含大小写数字符号 volumes: - ./data/postgres:/var/lib/postgresql/data # ← 确保此目录存在且有写权限 redis: image: redis:7-alpine command: redis-server /usr/local/etc/redis.conf volumes: - ./config/redis.conf:/usr/local/etc/redis.conf # ← 使用提供的redis.conf已禁用持久化防容器重启丢数据 nginx: image: nginx:1.25-alpine ports: - 8080:80 # ← 把宿主机8080映射到容器80避免占用80端口需sudo volumes: - ../frontend/dist:/usr/share/nginx/html:ro - ./config/nginx.conf:/etc/nginx/nginx.conf:ro逻辑说明PostgreSQL用14-alpine而非latest因该BI平台明确要求PG14支持FILTER子句与JSONB索引优化Redis配置文件redis.conf中已设save 禁用RDB、appendonly no禁用AOF这是为保障调度任务状态不因Redis重启丢失——任务状态实际存于PostgreSQL的qrtz_*表中Nginx直接挂载../frontend/dist即已编译好的前端省去npm run build步骤nginx.conf里已配好反向代理规则所有/api/请求打给backend静态资源走本地。2.3 启动并验证服务连通性# 在 docker/ 目录下执行 docker-compose up -d # 等待30秒检查容器状态 docker-compose ps # 应看到 postgres/redis/nginx/backend 全部为 Up (healthy) # 验证后端API是否响应curl -I 返回 200 OK 即可 curl -I http://localhost:8080/api/v1/health # HTTP/1.1 200 OK # 验证前端是否可访问浏览器打开 http://localhost:8080 # 应看到登录页用户名 admin密码 admin123首次登录强制修改参数说明docker-compose up -d后台启动日志用docker-compose logs -f backend实时跟踪/api/v1/health是后端内置健康检查端点返回{status:UP,timestamp:...}登录页URL是http://localhost:8080非8080/api因Nginx已将根路径代理至前端初始账号密码写死在backend/src/main/resources/application-dev.yml中生产环境必须通过application-prod.yml覆盖。3. 数据接入与建模从MySQL导入销售订单表用可视化SQL编辑器生成宽表避开手写JOIN的翻车现场平台的核心价值不在画图而在把脏乱差的业务数据库变成可分析的语义层。它不让你直接写SQL查生产库太危险也不让你拖拽字段就生成报表太黑盒而是提供“数据集Dataset”概念——一个可复用、可版本化、可加计算字段的逻辑表。3.1 在管理后台注册MySQL数据源登录http://localhost:8080→ 左侧菜单「系统管理」→「数据源管理」→「新增」字段值说明数据源名称sales_db自定义后续建模时引用类型MySQL下拉选择自动加载mysql-connector-java:8.0.33驱动JDBC URLjdbc:mysql://host.docker.internal:3306/sales?useSSLfalseserverTimezoneAsia/Shanghai关键容器内访问宿主机MySQL用host.docker.internalDocker Desktop或172.17.0.1Linux Docker用户名/密码root/your_mysql_pass必须是只读账号平台会自动加SELECT权限校验测试连接✅ 点击成功才保存后端会执行SELECT 1验证为什么用host.docker.internal因为postgres、redis等服务都在Docker网络内但你的MySQL很可能装在宿主机Windows/Mac或另一台服务器。localhost在容器内指向自己必须用特殊DNS解析宿主机IP。若失败改用宿主机真实IP如192.168.1.100并确保MySQL已授权root%。3.2 创建销售订单宽表数据集含日期维度展开进入「数据集管理」→「新建数据集」→ 选择数据源sales_db→ 表列表中勾选orders订单主表和order_items订单明细表-- 平台自动生成的JOIN SQL你可在「SQL编辑器」中修改 SELECT o.order_id, o.order_date, o.customer_id, o.status, oi.product_id, oi.quantity, oi.unit_price, oi.quantity * oi.unit_price AS amount, -- 关键日期维度展开年/月/周/季度避免前端计算时区错误 YEAR(o.order_date) AS order_year, MONTH(o.order_date) AS order_month, WEEK(o.order_date, 1) AS order_week, -- 从周一算起 QUARTER(o.order_date) AS order_quarter, DATE_FORMAT(o.order_date, %Y-%m) AS order_ym FROM orders o INNER JOIN order_items oi ON o.order_id oi.order_id WHERE o.order_date 2023-01-01 -- 加时间过滤防全表扫描逻辑说明平台会将此SQL存为数据集sales_order_wide后续所有报表都基于它而非原始表DATE_FORMAT生成order_ym如2023-12是为按月聚合做准备比前端用moment.js格式化更可靠WEEK(...,1)指定周一为每周第一天符合国内财务习惯WHERE条件必须手动添加否则大数据量下预览直接超时。3.3 为宽表添加业务计算字段毛利率、复购标识在数据集编辑页点击「添加计算字段」字段名类型表达式说明gross_marginDecimal(5,2)ROUND((amount - cost) / NULLIF(amount, 0) * 100, 2)成本字段需提前在order_items中存在NULLIF防除零is_repeat_buyerBooleanCOUNT(*) OVER (PARTITION BY customer_id) 1窗口函数标记复购客户非简单CASE WHEN避坑点计算字段表达式不支持子查询只能用当前SELECT中的列或聚合函数NULLIF(amount, 0)是血泪经验——某次上线后发现3个订单金额为0导致毛利率计算报错中断is_repeat_buyer用窗口函数而非关联子查询因后者在大数据量下性能暴跌实测10万行耗时从0.2s升至8s。4. 报表与大屏开发用拖拽式设计器生成销售TOP10排行榜再用JSON Schema注入动态筛选器告别硬编码平台报表引擎分两层基础报表Report用于精确表格/图表大屏Dashboard用于全屏可视化。二者共享同一套数据集但交互逻辑独立。4.1 拖拽生成销售区域TOP10柱状图报表进入「报表设计」→「新建报表」→ 选择数据集sales_order_wide维度拖拽将order_ym月份拖入X轴region_name地区名需先在数据集中添加该字段拖入图例指标拖拽将amount拖入Y轴聚合方式选SUM筛选器设置点击右上角「筛选器」→ 添加字段order_year→ 类型「下拉单选」→ 默认值2023排序与限制在「高级设置」中开启「按Y轴值降序」→ 设置「仅显示前10条」保存发布命名sales_region_top10_2023→ 发布为「公开」。参数说明region_name必须是数据集中的字段若原始表无此列需在SQL中LEFT JOIN regions r ON o.region_id r.id「仅显示前10条」是在SQL层加LIMIT 10非前端截取保障数据准确发布后报表URL为http://localhost:8080/report/view?idxxx可嵌入iframe。4.2 构建大屏用JSON Schema动态注入筛选器实现“选部门→自动刷新下属门店”大屏不同于报表需跨多个组件联动。平台支持「全局筛选器」但默认只支持静态选项。要实现动态级联如选总部→刷新华东大区→再选上海门店需用JSON Schema注入进入「大屏设计」→ 新建空白大屏 → 添加「柱状图」组件编辑该组件 → 「数据配置」→ 选择报表sales_region_top10_2023点击右上角「高级」→ 「筛选器配置」→ 切换为「JSON Schema模式」粘贴以下Schema实现部门树三级联动{ type: object, properties: { dept_id: { title: 选择部门, type: string, widget: select, enum: [1001, 1002, 1003], enumNames: [总部, 华东大区, 华北大区] }, sub_dept_id: { title: 选择子部门, type: string, widget: select, enum: [], enumNames: [], depends: [dept_id] }, store_id: { title: 选择门店, type: string, widget: select, enum: [], enumNames: [], depends: [sub_dept_id] } } }在「事件绑定」中为dept_id添加「值改变」事件 → 执行JS// 获取子部门列表调用后端API fetch(/api/v1/dept/sub?parent_id${value}) .then(res res.json()) .then(data { // 动态更新 sub_dept_id 的 enum 和 enumNames const schema this.getSchema(); schema.properties.sub_dept_id.enum data.map(d d.id); schema.properties.sub_dept_id.enumNames data.map(d d.name); this.setSchema(schema); });逻辑说明depends字段声明依赖关系平台会自动监听dept_id变化并触发后续逻辑fetch调用的是平台内置的/api/v1/dept/sub接口需提前在backend模块中实现返回JSON数组this.setSchema()是平台提供的SDK方法实时重绘筛选器无需刷新页面此方案比「全量加载部门树」更轻量10万门店数据下首屏加载仍1s。4.3 导出PDF与打印适配解决Web页面PDF打印时的分页错乱与字体缺失报表常需导出PDF归档。平台内置wkhtmltopdf但默认配置易出问题现象原因解决方案PDF中中文显示为方块容器内无中文字体在docker/nginx/Dockerfile中添加RUN apk add --no-cache ttf-dejavu cp /usr/share/fonts/ttf-dejavu/DejaVuSans.ttf /usr/share/fonts/表格跨页时表头丢失CSS未设thead { display: table-header-group; }在报表CSS中强制添加media print { thead { display: table-header-group; } }大屏导出后尺寸压缩变形wkhtmltopdf默认缩放比例不对修改backend/src/main/java/com/openbi/report/service/PdfExportService.javacommand.add(--zoom); command.add(1.0);验证命令# 进入backend容器手动测试PDF生成 docker exec -it openbi-backend sh wkhtmltopdf --version # 应输出 0.12.6 wkhtmltopdf --zoom 1.0 http://localhost:8080/report/view?idxxx /tmp/test.pdf5. 权限与安全加固用RBAC模型配置「财务只看汇总运营可钻取明细」并关闭默认暴露的Swagger开箱即用的权限是最大安全隐患。平台默认admin账号拥有全部权限但生产环境必须按角色最小化授权。5.1 建立三级RBAC权限体系角色→数据集→行级过滤进入「系统管理」→「权限管理」→「角色管理」角色名描述关联数据集行级过滤SQLfinance_viewer财务部只读汇总sales_order_wideWHERE order_year YEAR(CURDATE())仅当前年ops_analyst运营可钻取明细sales_order_wide空字符串表示无过滤store_manager门店经理只看本店sales_order_wideWHERE store_id ${user.store_id}动态变量关键机制行级过滤SQL在每次查询时拼接到原始SQL末尾如SELECT ... FROM sales_order_wide WHERE ... AND store_id 1001${user.store_id}是平台内置变量值来自用户登录时加载的UserDetail对象需在UserServiceImpl中扩展store_id字段过滤SQL不支持UNION/子查询仅允许简单WHERE条件防SQL注入。5.2 关闭Swagger与H2 Console删除生产环境暴露的调试入口backend/src/main/resources/application-prod.yml中必须显式关闭# application-prod.yml spring: # 关闭H2 Console内存数据库调试界面 h2: console: enabled: false # 关闭Swagger UIAPI文档 knife4j: enable: false production: true # 生产环境禁用UI # 关闭Actuator敏感端点 management: endpoints: web: exposure: include: health,info,metrics # 只开放健康检查与指标 endpoint: health: show-details: never # 不暴露详细健康信息验证方式访问http://localhost:8080/swagger-ui.html→ 应返回404访问http://localhost:8080/h2-console→ 应返回404访问http://localhost:8080/actuator/env→ 应返回401未授权。5.3 配置Nginx反向代理HTTPS与IP白名单docker/config/nginx.conf中添加# 在 server {} 块内 location /api/ { proxy_pass http://backend:8080/; # IP白名单只允许公司出口IP访问API allow 203.208.100.0/24; allow 114.255.200.0/24; deny all; # 强制HTTPS重定向若前端已配HTTPS if ($scheme ! https) { return 301 https://$host$request_uri; } } # 防止前端XSS攻击 add_header X-Content-Type-Options nosniff; add_header X-Frame-Options DENY; add_header X-XSS-Protection 1; modeblock;注意allow指令需替换为你们公司的实际出口IP段deny all必须放在最后X-Frame-Options DENY防止被嵌入恶意网站iframe若用CDN如Cloudflare则白名单应设为CDN节点IP而非用户真实IP。6. 生产级调优与排障定位「大屏每30秒刷新一次却CPU飙升」的根源用Prometheus监控慢SQL与内存泄漏上线后最常遇到的不是功能缺陷而是性能拐点——当数据量从10万行涨到500万行或并发用户从10人涨到200人系统开始抖动。这里给出三个真实场景的诊断路径。6.1 场景一大屏定时刷新导致CPU持续95%但JVM堆内存正常现象大屏配置了「每30秒自动刷新」观察docker stats发现openbi-backendCPU长期90%但jstat -gc显示老年代使用率30%。排查路径进入容器抓取线程快照docker exec -it openbi-backend jstack 1 /tmp/thread.log分析thread.log搜索RUNNABLE状态线程grep java.lang.Thread.State: RUNNABLE /tmp/thread.log -A 5 | head -20发现大量线程卡在org.springframework.jdbc.core.JdbcTemplate.query调用栈指向ReportService.refreshData()。根因平台默认对每个大屏组件单独发起SQL查询10个组件10次全表扫描。而sales_order_wide数据集未建索引。解决在PostgreSQL中为高频查询字段建复合索引CREATE INDEX idx_sales_order_ym_status ON sales_order_wide(order_ym, status); CREATE INDEX idx_sales_order_date ON sales_order_wide(order_date);修改大屏配置启用「组件数据共享」在大屏编辑页 → 「高级设置」→ 开启「启用数据缓存」→ 设置缓存TTL为60秒。效果CPU降至15%首次加载稍慢因缓存填充后续刷新全部走Redis缓存。6.2 场景二导出Excel时报OutOfMemoryError: Java heap space现象导出10万行订单明细Excel时后端OOM日志出现java.lang.OutOfMemoryError: Java heap space。根因Apache POI默认将整个Excel加载到内存10万行×50列≈2GB内存。解决双保险JVM参数调优docker/docker-compose.yml中backend: environment: - JAVA_OPTS-Xms1g -Xmx2g -XX:UseG1GC -XX:MaxMetaspaceSize256m代码层改用SXSSFWorkbook流式写入在backend/src/main/java/com/openbi/export/ExcelExportService.java中// 替换原Workbook创建方式 // Workbook wb new XSSFWorkbook(); // ❌ 内存式 SXSSFWorkbook wb new SXSSFWorkbook(1000); // ✅ 每1000行刷盘一次 wb.setCompressTempFiles(true); // 启用临时文件压缩验证导出10万行内存占用从2.1GB降至320MB耗时从48s降至22s。6.3 场景三用户反馈「报表筛选后数据不准」实为时区未对齐现象财务部筛选2023-12-01到2023-12-31但导出数据少了12月31日18:00后的订单。根因前端JavaScriptnew Date()生成的时间戳是浏览器本地时区如东八区而后端JavaLocalDateTime解析时按系统默认时区UTC处理导致时间偏移。解决四步闭环前端统一传ISO格式带时区// 筛选日期选择器提交时 const start moment(startDate).format(YYYY-MM-DDTHH:mm:ss.SSSZ); // 如 2023-12-01T00:00:00.0000800后端接收时强制转为东八区DateTimeFormat(pattern yyyy-MM-dd HH:mm:ss) LocalDateTime start LocalDateTime.parse(param, DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss)); ZonedDateTime zdt start.atZone(ZoneId.of(Asia/Shanghai)); // 强制指定时区数据库查询用TIMESTAMP WITH TIME ZONE字段ALTER TABLE orders ALTER COLUMN order_date TYPE TIMESTAMPTZ USING order_date AT TIME ZONE Asia/Shanghai;JDBC URL加时区参数jdbc:mysql://...?serverTimezoneAsia/ShanghaiuseTimezonetrue教训我曾在线上环境花3小时排查这个问题最后发现MySQL服务器时区是UTC而应用服务器是CST两个时区混用导致数据割裂。现在我的习惯是——所有时间字段必标时区所有时间转换必显式声明ZoneId所有数据库时间类型必用TIMESTAMPTZ。希望帮到你。本文还有配套的精品资源点击获取
返回列表