用过帆软的同学都懂,它的做表能力确实成熟,但每当工厂里冒出一个新需求——比如车间想看“按班组、按工单、按小时颗粒度”的产量趋势,或者要在一张大屏上同时塞下 OEE、废品率、设备状态——你大概率会经历一轮漫长的提需求、排期、改模板、再测试的循环,运气好一周上线,运气不好拖一个月。更现实的问题是授权费用,一个中小型工厂如果每个车间都上帆软,一年下来的预算足够再招一个初级开发了。
所以我花了大概一个月晚上和周末的时间,用 Flask + AI 辅助编程的方式,自己搭了一套面向工厂车间的 BI 平台,替代掉了原来挂在帆软上的大部分报表。这篇文章把我整个过程的思路、步骤、踩坑记录,以及我给 AI 用的提示词全部整理出来,给正在纠结“要不要脱帆软”“怎么低成本自研 BI”的朋友一个参考。这篇文章适合懂一点 Python、想用开源技术栈做企业数据可视化的开发,也适合正在评估自研方案的信息化负责人。
1. 先说结论:自研 BI 到底比帆软好在哪
1.1 帆软的三个痛点
帆软(FineReport 和 FineBI)在企业报表领域确实能打,尤其是财务三大表、复杂分组报表、填报流程,这些功能几行脚本就搞定了。但落到工厂这个场景里,我遇到的痛点特别具体,不是功能不够,而是功能和实际管理方式对不上。
第一个痛点是需求响应太慢。工厂车间的管理报表往往不是一成不变的,今天想看设备综合效率,明天想按产品线拆分不良率,后天想把晨会看板改成三班倒视图。帆软的模板是配置好的,想改动需要熟悉 FineReport 设计器的人来改,或者提交给服务商实施。对工厂内部的小需求来说,这个链路非常重。
第二个痛点是数据口径和维护成本。帆软连接工厂数据库之后,报表逻辑通常写死在模板里,多人维护时很容易出现同一张“产量表”在不同模板里面统计口径不一致。时间一长,管理层发现问题了,再去排查是哪张模板的过滤条件写错了,那是不折不扣的考古工作。
第三个痛点是钱。帆软的授权是按模块、按年、按用户数叠加的,另外如果需要技术支持、定制开发,成本还会更高。对于预算本身就不宽裕的中小工厂来说,这笔开销是很肉疼的。而且要扩展一个用户,还得联系商务加授权,这在信息化快速试错阶段是很别扭的。
1.2 自研适合什么工厂,不适合什么工厂
我不建议所有人都去照着这篇文章自研,因为自研是有前提条件的。先说适合的情况:你的报表主要是内部看板、日常管理报表、车间大屏,数据量在千万条以内,并且你手里至少有一个能写 Python、能折腾服务器的开发人员。这种情况下自研的投入产出比是很高的。
不适合自研的情况也很明显:如果你需要大量填报报表、复杂工作流审批、用户自助拖拽分析、或者给几十个业务部门每人开通不同的精细权限,那还是老老实实用商业 BI 或者像 Superset、Metabase 这类开源 BI 更适合。这里不是说 Flask 做不到,而是这些能力要做好做稳,研发成本远超预期。
有一点要清楚:自研 BI 的目标不是“全面对标帆软”,而是在你实际用得最多的几个场景里做到够用、好用、快速迭代。我做的时候砍掉了自助分析、数据填报、复杂权限矩阵,只保留了数据连接、指标计算、报表展示、定时刷新、大屏展示,这样开发的复杂度就一下子降下来了。
1.3 选型对比:Flask 不是唯一解,为什么我选它
如果你决定自研,技术选型其实很宽。开源 BI 方案里,Superset 和 Metabase 都是现成的,但定制起来还是绕不过去:改样式、改交互、嵌入现有系统,都得熟悉它本身的插件机制和后端架构,学习成本一点也不低。Power BI 免费版虽然好用,但数据源、刷新频率、云服务这些约束在工厂内网环境里也很麻烦。
最后我选了 Flask,理由很简单:团队对 Python 最熟,Flask 足够轻量,可以完全控制接口和页面,从数据库读取到 JSON 输出再到前端图表渲染,整条链路都是自己的。Flask 不像 Django 那样自带一堆东西,但做 BI 后端我们本来就不需要复杂的 ORM、Admin 后台,反而轻一点更好扩展。
这里也要顺便说一下,Flask 在并发方面有一些限制,如果你直接用它处理上万人的并发访问,那就得考虑加一层异步框架或者加服务实例。但工厂 BI 的使用场景是车间几十个看板、几个部门轮换看,并发往往只是个位数或几十,Flask 完全扛得住。部署上用 gunicorn 或 waitress 起多进程就行,我在后面会详细写。
2. 整体架构设计:一个工业车间的数据监控系统怎么搭
2.1 三层架构:数据库层、服务层、展示层
自研 BI 的核心不是代码多漂亮,而是架构清晰。我做的时候把系统拆成三层,层和层之间用 JSON 接口通信,这样可以保证以后任何一层替换掉都不影响另外两层。
数据库层负责存储源数据。对工厂来说,数据通常已经在 MES、ERP 或 PLC 采集系统里了,BI 平台不一定要重复存一份全量数据,只需要把报表需要的明细数据或者轻度汇总数据同步过来。我这边是直接从 MySQL 生产库里读取订单和报工表,为了不拖累生产库,特意加了一个只读账号,并限制它只能访问特定的几张业务表。
服务层就是 Flask 后端。它负责接收前端请求、调 SQL 查询、做二次计算、返回统一的 JSON 结构。对于 BI 来说,最好在服务层就把所有指标口径固定下来,而不是让每个前端页面自己重新写过滤逻辑。这样当管理员问“为什么两张图产量不一样”的时候,答案很简单:因为前后端算的是同一个聚合函数。
展示层是浏览器里的页面,用 ECharts 渲染图表,用原生 HTML/CSS 或轻量框架搭布局。大屏、看板、列表页都只是数据的最终呈现形式,核心逻辑全部在服务层,所以前端可以做得比较薄,以后就算换 Vue 或 React 重写前端,后端接口完全不用动。
2.2 数据库表设计:用生产订单表举个例
不管做什么 BI,表设计都是地基。我们以最常见的“订单产量统计”为例,在数据库中通常会有一张类似production_order的表,它的字段大概长这样:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| order_no | varchar | 工单号 |
| product_code | varchar | 产品编码 |
| line_code | varchar | 产线编码 |
| shift_code | varchar | 班次,比如 D / N |
| plan_qty | int | 计划数量 |
| actual_qty | int | 实际完成数量 |
| qualified_qty | int | 合格数量 |
| status | tinyint | 工单状态 |
| create_time | datetime | 开工时间 |
| finish_time | datetime | 完工时间 |
这张表其实已经能覆盖工厂 80% 的基础统计需求了。比如按天统计产量就用create_time字段做日期分组,按产线统计就用line_code,按班次统计就用shift_code。从这张表派生出来的日产量、班次产量、计划达成率、合格率,就是典型的 BI 指标。
在设计时有一个非常实用的建议:不需要一开始就把所有统计维度建模成雪花模型,直接把业务明细表同步过来,然后在查询时用聚合函数做维度分析就行。工厂的数据量级通常在几千万以下,几百万条记录做 GROUP BY 在现代数据库上毫秒级就出来了,完全不用上数据仓库那套复杂建模。
2.3 数据接入方式:MES/ERP 数据怎么进 BI
数据接入是自研 BI 最容易卡住的环节。工厂的数据源五花八门,有 MySQL 的,有 SQL Server 的,有直接读 PLC 存到 PostgreSQL 的,还有一部分数据是 Excel 导出再导进来的。
我这边统一采用的方式是“只读视图 + 定时拉取”,用 Flask 的定时任务每天凌晨把源库数据同步到 BI 库的明细表中。这样有两个好处,一是 BI 查询不会影响源系统性能,二是可以自主控制刷新频率,比如大屏需要每 5 分钟刷新一次,那就对某几张关键表单独做短周期同步。
如果你的工厂数据源比较复杂,可以在服务层加一个统一的数据接入模块,支持导入 CSV、调用第三方 API、直连 ERP 数据库。我自己没有做太复杂的适配,因为工厂的几套系统都支持 MySQL 只读账号,这已经足够了。如果你要接的是 REST API,实践上也不难,在服务层写一个定时拉取任务就行,主要注意幂等性和增量判断,避免每次全量覆盖。
3. 实操:从空目录到第一个看板(完整步骤)
3.1 环境准备与项目初始化
开发环境建议使用 Python 3.10 或 3.11 版本,我这边用的是 Python 3.11,Flask 2.3 以上版本。先建一个虚拟环境,再把依赖装上:
mkdir factory_bi cd factory_bi python -m venv venv source venv/bin/activate # Windows 上是 venv\Scripts\activate pip install flask flask-sqlalchemy flask-cors pymysql pandas gunicorn我一般习惯把项目结构分成app.py、models.py、api/和templates/四个部分。这样文件不会全部堆在一个文件里面,以后加接口、改样式都好找。
factory_bi/ ├── app.py # Flask 入口 ├── config.py # 配置,数据库连接等 ├── models.py # SQLAlchemy 模型 ├── api/ │ ├── __init__.py │ ├── report.py # 报表接口 │ └── auth.py # 登录权限 ├── templates/ │ └── dashboard.html # 看板页面 └── static/ ├── css/ └── js/这里有一个细节要提醒:数据库连接尽量用 SQLAlchemy 连接池,不要每次请求都新建连接。在生产环境,数据库连接频繁断开重连是导致接口偶发超时的最大原因之一。配置上设置pool_size=10、pool_recycle=3600就够用了。
3.2 用 AI 提示词生成 Flask 报表接口
我写代码不是一行行手打的,大部分重复性的接口代码都是让 AI 直接生成,我再做检查和修改。给 AI 的提示词最关键的一点是“给它足够的上下文”,要包含表结构、字段含义、想要的返回格式、甚至已经定义好的数据模型。
一个实用提示词模板长这样:
你是一名 Python Flask 后端工程师,请帮我写一个报表统计接口。 项目使用 Flask + SQLAlchemy + PyMySQL,连接 MySQL 数据库。 已有数据模型 production_order 的字段如下: - order_no: 工单号 - product_code: 产品编码 - line_code: 产线编码 - shift_code: 班次,D 表示白班,N 表示夜班 - actual_qty: 实际完成数量 - qualified_qty: 合格数量 - create_time: 开工时间 请实现 GET /api/report/production 接口,参数 start_date 和 end_date, 返回 JSON 格式如下: { "code": 0, "data": [ {"date": "2025-01-01", "total_qty": 100, "qualified_qty": 95, "line_code": "A线"} ] } 要求按日期、产线分组统计,支持日期范围过滤,按日期升序排列。把这个提示词交给 AI 之后,它基本能在几秒内生成一个可运行的接口代码。我拿到后再根据项目里的模型做一点点调整,比如字段名不一致、时区问题,几分钟就能跑通。
生成之后我通常会再追加一个提示词来做代码检查和防御性处理:
请再帮我优化这个接口: 1. 如果 start_date 大于 end_date,返回参数错误。 2. 查询时增加索引提示。 3. 避免把 null 字段直接传给 JSON,转成 0。 4. 增加简单的耗时日志输出。这里要特别提醒,AI 生成的代码不一定符合你的数据库规范,尤其是表名、列名、权限校验,一定要自己过一遍,不能直接上线。我在实际项目中把 AI 当作“高级代码生成器”,所有代码都要经过 review 和测试。
3.3 用 AI 提示词生成 ECharts 前端看板
后端接口跑通之后,前端做一个工厂看板其实不难。我的页面选择用 ECharts,通过 CDN 引入,不需要构建前端工程,保持简单直接。核心是一个 HTML 页面,里面有若干图表容器和一个定时刷新脚本。
前端最花时间的其实是 ECharts 配置项。如果只做一个柱状图,那确实没难度,但要做成大屏风格的“产量趋势 + 设备状态 + 班组达成率”组合,配色、图例、tooltip、自适应逻辑都挺烦的。我建议用 AI 来生成配置,提示词模板如下:
你是 ECharts 可视化专家。 我现在有一个 JSON 数据,格式如下: [ {"date": "2025-01-01", "total_qty": 100, "qualified_qty": 95}, {"date": "2025-01-02", "total_qty": 120, "qualified_qty": 113} ] 请生成一个 ECharts 配置,要求: 1. 使用柱状图显示 total_qty。 2. 使用折线图显示 qualified_qty,放在同一个 x 轴上。 3. 配色使用深色背景大屏风格,主色 #00d4ff,折线颜色 #ffd700。 4. 柱状图圆角 4,tooltip 显示日期、产量、合格数。 5. x 轴日期过长时倾斜 45 度。这样生成的配置基本就能直接用了。如果觉得某些细节不对,比如网格间距、字体大小、动画效果,可以继续追加描述让 AI 改。把 AI 当同事使唤,哪里不对改哪里,效率比自己在文档里查 API 高太多了。
3.4 权限控制怎么做
工厂 BI 必然涉及权限问题。车间主任看自己车间的数据,厂长看全厂数据,质检员只看不良统计,这些如果全靠一套通用查询是管不住的。我在自研版里把权限控制做成了一套非常轻量的 token 机制,没有上 Flask-Login 全家桶。
实现思路是:用户在登录页面提交账号密码,后端校验后生成一个包含用户角色和过期时间 token,前端每次请求在 Header 里带Authorization: Bearer <token>。后端用一个装饰器校验 token 并解析出用户角色,在查询数据时根据角色追加过滤条件。
提示词示例:
帮我用 Flask 写一个登录接口和权限校验装饰器。 用户表 user 有字段 id, username, password_hash, role。 role 有三种:admin 可以看全部数据,manager 可以看指定 line_code 的数据,viewer 只读。 token 使用 itsdangerous 生成,有效期 12 小时。 请写一个 @require_role('manager') 的装饰器,并将用户的 line_code 注入到请求上下文。对于中小工厂,这种程度已经够用。要说清楚的是,这不替代帆软那种精细到单元格级的数据权限,但如果只是做到“车间的人只能看见车间的数据”,百万级用户以下的场景完全没有问题。
3.5 大屏适配与定时刷新
工厂看板经常是挂在车间墙上的电视或者 LED 大屏上,所以前端必须解决两个问题:分辨率适配和自动刷新。
分辨率适配我用了 ECharts 的resize事件,加上 CSS 的百分比定位。核心逻辑是:监听窗口变化,所有图表实例统一调用chart.resize()。如果你对细节有要求,还可以让 AI 帮你写一个initChart工厂函数。
定时刷新用 JavaScript 的setInterval来做,每隔 30 秒或 5 分钟调一次后端接口,然后用setOption更新图表数据。注意在更新时设置notMerge: true,避免旧的配置项残留在图表里。
async function refreshDashboard() { const res = await fetch('/api/report/production?start_date=2025-01-01&end_date=2025-01-07'); const result = await res.json(); const dates = result.data.map(item => item.date); const qty = result.data.map(item => item.total_qty); myChart.setOption({ xAxis: { data: dates }, series: [{ data: qty }] }, true); } setInterval(refreshDashboard, 30000);这段逻辑很简单,就不需要 AI 提示词了,自己写更快。如果你要在这个基础上增加“首次加载动画”“加载失败重试”这些体验优化,再丢给 AI 补充也不迟。
4. 常见问题与排查技巧实录
4.1 接口返回慢?先查索引再想缓存
刚开始我遇到一个特别典型的问题:产量报表的查询参数跨度大一点,比如查一年的数据,接口要跑四五秒才返回。一开始我以为是 Flask 的问题,后来排查下来发现是数据库查询没走索引,create_time和line_code都没有索引,全表扫描了几百万行。
解决方式是给数据库表加联合索引:
ALTER TABLE production_order ADD INDEX idx_time_line (create_time, line_code); ALTER TABLE production_order ADD INDEX idx_status (status);加完索引后,接口直接从 4 秒多降到了 300 毫秒以内。这个教训给我提了个醒,自研 BI 的性能瓶颈几乎都在数据库层,不要在 Flask 代码里瞎优化缓存。如果加完索引还是很慢,再考虑加 Redis 缓存做 5 分钟级别的快照,而不是每次实时查库。
4.2 图表数据对不上?口径统一才是核心
做 BI 最容易翻车的问题就是两个图表数字对不上。比如“昨日产量”在晨会看板上是 9800,到了月度汇总表里变成 9600。问题基本上出在统计口径上,常见的原因有:
- 日期范围不统一,一个用
create_time,一个用finish_time。 - 过滤条件不一致,一个只看
status = 1(已完工),一个把所有工单都算进去。 - 时区问题,数据库存储的是 UTC 时间,前端展示用的是北京时间。
我的解决方案是在服务层把口径集中定义,不要在页面里乱加过滤条件。比如服务层定义一个dashboardService.py,里面所有查询都走统一的date_field参数,这样任何一个新报表只需要复用同一个服务函数,就不会出现口径分叉。另外,建议你在前端页面显著位置标注“统计口径:按开工日期,含已完工与生产中工单”,这样业务部门也能理解数据差异。
4.3 部署上线的坑:gunicorn + nginx + 进程守护
开发环境跑flask run没问题,真正上线用 Flask 自带的开发服务器是不行的,并发一高就卡住。我最终用的是 gunicorn 来跑 Flask 应用,nginx 做反向代理。部署步骤也很直接:
pip install gunicorn gunicorn -w 4 -b 127.0.0.1:8000 app:app --timeout 60nginx 配置里把/反向代理到127.0.0.1:8000,再设置几个静态文件的缓存策略。
server { listen 80; server_name your-server-ip; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /static/ { alias /opt/factory_bi/static/; expires 1d; } }还有一个坑是 Flask 里的if __name__ == '__main__': app.run()在 gunicorn 下不会生效,所以入口文件要写得干净,不要在里面带 9999 端口这种测试配置。最后用 systemd 或者 supervisor 把 gunicorn 守护起来,否则服务器一重启服务就没了,车间大屏直接雪花,这个责任你肯定不想背。
4.4 边缘情况:工单跨天、时区、扫码数据延迟
工厂数据还有一个特别常见又麻烦的情况——夜班跨天。假设夜班是晚上 20:00 到第二天早上 8:00,如果按自然日统计产量,会把 24 号夜班和 25 号日班混在一起,导致管理层永远看不到正确的“单日班次产量”。我的处理方式是在数据库里加一个shift_date字段,这个字段在数据同步时根据班次规则计算好,比如 20:00 到次日 8:00 的工单统一归到开工日。这个逻辑看似简单,却是工厂 BI 和通用 BI 最大的区别之一。
扫码数据延迟也是工厂的老大难。产线工人晚扫码、漏扫码,导致实时看板上的产量比实际偏低。我的做法是在看板页面显眼位置标注“数据采集时间”和“统计截止时间”,同时允许按工单维度做一次人工数据修正接口,这样管理层看到波动时会先判断是不是数据延迟,而不是误以为生产出了大问题。
4.5 快速排查速查表
| 现象 | 可能原因 | 优先排查方式 |
|---|---|---|
| 接口超时 | SQL 没走索引 / 数据量过大 | EXPLAIN分析执行计划,补索引 |
| 图表长时间空白 | 前端请求异常 / token 过期 | 打开浏览器 F12 看 Network 请求 |
| 数字对不上 | 日期口径不同 / 状态过滤条件不一致 | 对比两个查询条件,统一走服务层函数 |
| 大屏显示变形 | 缩放布局写死 | 用百分比定位 + ECharts resize |
| 部署后页面 502 | gunicorn 挂了 / nginx 没启动 | systemctl status gunicorn看日志 |
| 定时任务不执行 | 没有设置时区 / 进程未加载 | 检查 cron 或 APScheduler 的时区配置 |
我还建议大家写接口时统一返回结构{ "code": 0, "message": "success", "data": ... },前端统一判断code,这样排查接口问题时能快速定位是后端报错还是前端没调对。这个习惯在自研项目里越早统一越好,后面接口超过 10 个的时候体会会特别深。
写在后面
回到最初的问题,用 AI + Flask 自研替代帆软,到底值不值?我的答案是:看场景。对我们厂来说,日常生产看板、晨会日报、月度产量汇总这些需求占了 80%,过去依赖帆软的模板和外包实施,现在完全是内部团队当天提需求、当天上线,这个响应速度是商业 BI 给不了的。
我个人实际操作的体会是,一套轻量 BI 的代码量并没有想象中那么大,核心可能就几千行 Python 和几个 HTML 页面,真正的难点在于“你对业务指标的理解”以及“你敢不敢砍掉 80% 的伪需求,只保留最有价值的那几个报表”。AI 辅助编程在这个过程中把写代码的门槛压低了很多,但千万不要把 AI 当作万能工具,它生成的代码只能算初稿,每一个聚合字段、每一条过滤条件,都需要你来为数据的准确性兜底。
最后再分享一个我后来才体会到的小技巧:自研 BI 一定不要把“做报表”当成终点,而是要把“指标口径固定下来”这件事做好。当整个工厂都是同一个数据源、同一个计算函数、同一个权限模型的时候,你会发现不再需要出一本厚厚的报表说明文档,因为所有看板上的数字,天然就是一致且可追溯的。这才是我认为自研 BI 真正值钱的地方。