ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

从零搭建BrewUI:前后端分离的智能酿造管理平台实战

从零搭建BrewUI:前后端分离的智能酿造管理平台实战 1. 项目背景:为什么需要BrewUI我最初接触到BrewUI是在帮朋友搭一套家庭智能酿造系统的时候。他在家里做精酿啤酒已经有两年多配方、温控、发酵记录全散落在不同的App和Excel表格里每次酿酒都要手动翻记录。当时市面上不是没有酿造管理软件但要么只支持单一设备要么界面停留在上个时代数据导出和配方管理做得一塌糊涂。找了一圈之后我决定自己动手把酿造管理这件事做成一个真正好用的Web应用这就是BrewUI的起点。BrewUI本质上是一个面向自酿爱好者和小型酿造工作室的酿造流程管理工具。它解决的核心问题很直接把配方设计、酿造过程记录、设备温度监控、发酵进度追踪、批次管理和数据统计全部收敛到一个界面里不再需要你在不同工具之间来回切换。对于刚入门的玩家它比Paper和Excel直观得多对于已经跑了不少批次的进阶玩家它能帮你积累数据、复盘配方、沉淀自己的酿造经验。这个项目的目标用户非常明确一是自酿爱好者二是在小规模测试配方的工作室三是偶尔接单做定制酿造的兼职酿友。这三个群体的共同点是——他们不需要工业化酿造管理软件里那些复杂到吓人的功能但需要一套足够灵活、足够清晰的工具把酿造这件充满变量的手艺活变得可追踪、可复制、可优化。我选择做Web应用而不是原生App原因也很实际第一酿造过程跨端使用频率极高配方设计可能在电脑上完成发酵监控需要在手机上看第二Web应用的部署和维护成本远低于同时维护iOS和Android两套客户端第三容器化部署之后整个工具可以轻松跑在家里NAS或者一台小型服务器上数据完全自持不依赖第三方云服务。对于一个长期使用率很高的工具来说数据在自己的掌控之中是底线.2. 整体架构与核心功能拆解2.1 前后端分离的模块化设计BrewUI采用前后端分离架构这算是当前Web应用的主流选择但具体到酿造管理场景这个选择有它特别的理由。酿造数据的特点是写入频率低但单次数据量大查询模式复杂而且涉及大量状态变化。比如一次酿造过程包含投料、糖化、煮沸、加酒花、冷却、接种、发酵、装瓶等环节每个环节都有一批参数需要记录。如果采用传统的服务端渲染每次页面跳转都要重新加载大量表单和数据体验会非常割裂。后端我用了Go语言主要看重它的并发性能和部署便利性。酿造工作室有时会同时跑多个批次每个批次挂多个温度探头后端需要同时处理这些数据流。Go的goroutine模型在这种场景下比Python的GIL更省心而且编译出来的单二进制文件部署非常简单。前端选择Vue 3因为它的响应式数据流非常适合做实时刷新场景——温度曲线、pH变化、比重趋势这些图表数据需要实时更新Vue的响应式机制让这个实现变得非常直接。数据存储层用了PostgreSQL和Redis组合。PostgreSQL负责所有结构化数据包括配方、批次、设备配置、用户信息Redis承担缓存和临时数据存储比如当前活动的温度监控数据以及频繁读取的设备状态缓存。这套组合的好处是清晰、可控、容易打理对于自酿规模的数据量来说绰绰有余即使数据增长到几十万条记录PostgreSQL也完全扛得住。提示如果你的数据量很小比如只有几十个批次记录完全没必要上Redis。直接全部走PostgreSQL反而更简单。我一开始就上了Redis后来发现初期大部分接口根本不需要缓存白白增加了一层复杂度。2.2 配方管理模块配方管理是BrewUI的核心模块也是整个应用里最有价值的部分。设计一个可靠的酿造配方需要考虑麦芽配比、酒花添加计划、酵母选择、糖化温度、发酵温度等多个维度而且这些维度之间互相影响。传统做法是拿纸笔或者Excel记录问题在于配方之间的横向对比非常困难想从历史配方中迭代优化出更好的版本更是要翻遍所有记录。BrewUI的配方模块采用版本化配方设计。每个配方可以创建多个版本每次调整都自动生成新版本并保留历史版本。这个设计来自代码版本管理的灵感——你改配方参数的时候系统会记录下这次改动之后随时可以回溯、对比、甚至恢复旧版本。对于酿酒来说这一点极其重要配方调整的变量太多如果改坏了没有回溯能力整个批次就废了有版本化之后你可以放心大胆地试验新方案。配方创建页面里集成了一个简易的配方计算器它可以基于麦芽出糖率和糖化效率预估原麦汁浓度基于酒花α酸含量和添加时间计算IBU苦度值。底层的计算逻辑参考了多本酿酒经典教材的公式虽然做不到商业软件那样精确但对于自酿场景已经足够可靠。对于批次量一致的配方计算误差能控制在3%以内这个精度足够支撑日常决策了。2.3 酿造记录与过程追踪酿造过程中的记录模块是我个人在实际使用中最常用也最受益的部分。自酿最怕的不是配方不好而是不知道自己在哪一步做得好、哪一步做得差。BrewUI把酿造过程拆成标准化阶段设备准备、投料、糖化、过滤洗糟、煮沸、冷却、发酵、成熟、装瓶每个阶段都有对应的参数录入模板。记录的方式我做成了两步实时记录和分批补录。实时记录适合在酿造现场用手机操作每个阶段有独立的表单提交后自动带时间戳分批补录适合结束后统一整理系统会按时间顺序排列各个数据点。这两种模式覆盖了绝大多数酿造场景——有时间专注记录的时候用实时模式手忙脚乱顾不上记录的时候用补录模式保证数据不会因为现场太乱而缺失。发酵追踪是这个模块里我投入精力最多的部分。自酿的发酵阶段通常持续一到三周期间需要记录比重的变化趋势、温度波动、以及观察到的发酵现象。BrewUI允许用户为每个批次设置自定义的测量时间点和提醒到时间自动生成待办事项。同时支持接入蓝牙比重计和温度探头自动采集数据生成曲线图。实测下来自动采集和手动记录相比不仅仅省去了每天抄录的烦恼更重要的是数据密度高了之后能明显看出发酵停滞或者异常波动的时间点.3. 核心功能实现与关键参数计算3.1 配方计算引擎的实现配方计算引擎是BrewUI的技术核心这个模块的计算准确性直接决定了用户对工具的信任度。我之前做第一版的时候只是简单地把麦芽重量乘以平均出糖率结果算出来的浓度和实际测量值差了接近20%后来仔细研究才发现问题出在单位换算和效率系数的处理上。最终的计算引擎分三层。第一层是把用户输入的原料按类别归类麦芽需要处理其潜在出糖量以每公斤麦芽可产生的糖度·升数表示、色度、出糖效率酒花需要处理α酸含量、添加时间、煮沸利用率辅料需要处理可发酵性比例和添加方式。第二层是基础计算核心公式如下预估原麦汁浓度单位Plato (总可提取糖量(克) ÷ 最终麦汁体积(升) ÷ 1000) × 100 × 效率系数总可提取糖量 Σ(每种麦芽重量(kg) × 该麦芽潜在出糖量(克/公斤))效率系数是这套计算里最需要经验的参数。麦芽的潜在出糖量标注值是在实验室条件下获得的极限值实际酿造中不可能达到100%。家用设备的效率通常在65%到75%之间商用设备可以做到80%到85%。BrewUI默认给出70%的经验值用户可以在批次结束后根据实测数据修正效率系数系统会自动把这个修正值应用到后续同设备的配方估算中。这个学习式修正机制是实测后最有效的优化——设备不同、操作习惯不同恒定参数永远不可能精确但动态修正可以越用越准。IBU苦度值的计算也有几个版本勃灵公式、火花公式等都有各自的适用场景。BrewUI默认采用Rager公式它在煮沸时间处于30到90分钟范围内时精度表现最好而这个范围恰好覆盖了大多数自酿配方的酒花添加策略。Rager公式IBU (酒花重量(克) × α酸含量(%) × 利用率(%) × 1000) ÷ (麦汁体积(升) × 1.34)其中利用率基于煮沸时间查表获得煮沸5分钟约5%15分钟约12%30分钟约18%60分钟约27%90分钟约30%。建议新用户第一次使用配方计算器时可以先用一个以前做过的配方做验证对比计算结果和实测数据修正效率系数后再开始设计新配方。这样能最快建立对工具的信任感。3.2 温度监控与数据采集链路温度监控功能需要端到端的数据链路设计从传感器到展示界面的每一步都会影响数据的可靠性和实时性。目前BrewUI支持两类温度数据来源一类是支持HTTP API的智能温度计比如iSpindel通过WiFi上报数据另一类是通过MQTT协议接入的树莓派加DS18B20传感器方案。MQTT方案更灵活成本也更低适合DIY玩家。数据采集流程是这样的温度传感器每30秒采样一次通过MQTT推送到BrewUI后端的MQTT broker后端服务订阅主题后把数据写入Redis缓存同时异步写入PostgreSQL做持久化。前端通过WebSocket订阅实时数据流温度变化可以在1秒内反映在页面上。数据写入Redis是为了处理高频查询和短期曲线展示存PostgreSQL是为了长期存档和批次复盘。DS18B20传感器的接线虽然不算复杂但我在实测中踩过一个坑传感器线缆过长时会出现信号衰减和读数漂移。后来在传感器两端并联了一个4.7kΩ的上拉电阻到VCC问题才彻底解决。另外DS18B20的防水探头必须做好密封如果探头长时间浸泡在液体中密封不好会导致读数永久性偏移而且不会自行恢复。MQTT消息体我设计得尽量精简只包含传感器ID、时间戳和温度值三个字段避免网络传输带来的时序抖动{ sensor_id: ferm1, timestamp: 1736150400, temperature: 20.5 }3.3 批次状态机与阶段流转酿造批次的状态流转是整个业务逻辑里最容易被忽略、但最容易出bug的部分。不同阶段之间的转换关系如果不明确就会出现发酵还没开始就能直接进入装瓶这种逻辑错误或者冷却完成后发酵阶段居然还能修改糖化温度这类数据矛盾。BrewUI用状态机来约束批次的生命周期。每个批次实例的状态只有七个准备、糖化中、煮沸中、冷却中、发酵中、熟化中、已完成。状态之间的跳转只有固定的邻接关系准备可以进入糖化中糖化中可以进入煮沸中以此类推不允许跳级更不允许回退到先前已完成的状态。这个设计看起来约束很多但实际上恰好符合酿造流程本身的线性特征也减少了用户误操作的概率。状态流转的同时会触发对应的数据校验规则。比如从糖化中进入煮沸中之前系统会校验糖化记录是否完整——麦芽投料时间、糖化温度、糖化持续时间、洗糟水量这几项是必填项缺一不可。同理从冷却中进入发酵中之前必须记录冷却后的麦汁温度和比重值。这套校验规则是从几十个真实批次的数据完整性分析中总结出来的缺失率最高的字段恰恰就是这些影响后续判断的关键参数。4. 实操过程:从零搭起一套可用的BrewUI4.1 开发环境与部署方案整个BrewUI项目我用了Docker Compose做本地开发和部署编排这也是我最推荐的方案。开发环境和生产环境保持一致的容器编排可以省掉大量环境不一致带来的麻烦。建议的目录结构如下brewui/ ├── backend/ │ ├── cmd/ │ ├── internal/ │ ├── go.mod │ └── Dockerfile ├── frontend/ │ ├── src/ │ ├── package.json │ └── Dockerfile ├── deploy/ │ ├── docker-compose.yml │ ├── nginx.conf │ └── .env └── data/ └── postgres/Docker Compose编排里包含四个服务前端Nginx容器、后端Go容器、PostgreSQL容器、Redis容器。后端容器里同时开启MQTT broker子进程用systemd管理生命周期确保温度采集链路和Web服务同时可用。生产环境部署在NAS或者小型Linux服务器上总内存要求不超过4GB对于大多数家庭设备来说完全没有压力。编排文件的关键部分version: 3.8 services: backend: build: ../backend ports: - 8080:8080 environment: - DB_HOSTpostgres - REDIS_HOSTredis - MQTT_BROKER_PORT1883 depends_on: - postgres - redis postgres: image: postgres:16-alpine volumes: - ../data/postgres:/var/lib/postgresql/data environment: - POSTGRES_DBbrewui - POSTGRES_USERbrewui - POSTGRES_PASSWORDchange_me redis: image: redis:7-alpine4.2 数据库表设计与关键SQL数据库设计这块我花了不少时间建模核心表包括用户表、设备表、配方表、配方版本表、批次表、批次阶段记录表、传感器数据表。这里我只挑最关键的三张表展开。配方表和配方版本表采用一对多的关系一个配方可以对应多个版本。版本表里包含了配方的完整快照包括麦芽列表、酒花列表、酵母信息、糖化参数、发酵参数和计算得到的预估浓度与苦度值。快照式存储是我刻意选择的方案——如果只存修改差异回溯老版本时需要反向计算既费时又容易出错快照方式虽然占用空间多一点但换来的是百分之百可靠的版本回溯。批次表关联配方版本同时记录设备ID、酿造日期、实际的初始比重、最终比重、发酵温度区间、装瓶数量等字段。酿造过程中产生的大量阶段测量数据单独存放在批次测量记录表里每一条记录都包含时间戳、阶段标识、测量类型和数值。传感器数据表采用按时间分区的策略每天一个分区查询某段时期的曲线时只扫描对应分区性能好得多。实测下来即使积累了超过一百万条温度记录按分区查询的响应时间依然能保持在100毫秒以内。4.3 前端核心页面与交互逻辑前端页面我重点打磨了三个视图配方编辑器、批次工作台、数据统计面板。配方编辑器的交互设计目标是所见即所得。左侧是原料清单右侧是实时计算面板每次增删原料或修改参数右侧的预估浓度、苦度值、色度立即刷新。这个实时反馈机制极大降低了配方设计的试错成本传统做法是改完所有参数再统一计算看到结果后如果不符合预期又得从头调整实时计算把整个设计过程变成了即时调参。批次工作台是酿造现场的主界面按阶段展示当前批次的进度和参数。顶部是阶段进度条中间是当前阶段的参数表单和实时测量数据底部是历史测量记录的时间线。发酵阶段时工作台会展示温度曲线和比重曲线两条曲线放在同一张图上可以直观看出温度变化对照比重变化的影响关系。这个联动视图对诊断发酵异常非常有帮助比如温度骤降导致发酵迟滞在图上会表现为温度曲线下跌的同时比重曲线趋于平缓。数据统计面板聚合了所有批次的历史数据支持按配方、酵母、麦芽品牌等维度筛选对比。最实用的是批次对比功能可以把三到五个批次放在同一张图上对比温度曲线、比重曲线和最终评价方便复盘哪些调整带来了实际改善。5. 我踩过的坑:BrewUI实战问题排查5.1 温度传感器数据漂移问题第一次接入DS18B20传感器时我遇到了非常头疼的数据漂移问题。温度显示少量波动时还正常但连续运行两天后读数会逐渐偏离实际温度最大偏差达到了3摄氏度。排查过程花了两天最后定位到两个根因。第一个根因是传感器供电不稳定。单片机通过USB口供电时电流波动会导致DS18B20内部电压基准漂移进而影响温度转换精度。解决方法是给传感器单独使用稳压模块供电隔离数字电路和模拟电路的供电回路。第二个根因是数据线受到的电磁干扰。传感器线缆和220V电源线捆在一起走线时50Hz的工频干扰会叠加到信号线上导致偶尔出现跳变和读数偏移。把传感器线缆和电源线分开走线并在信号线外包了一层屏蔽层做好接地之后读数稳定在正负0.3摄氏度以内。5.2 前端实时数据卡顿温度监控页面上线初期WebSocket推送频率一旦超过每秒一次页面的温度曲线就会出现明显卡顿尤其当历史数据加载完成后交互响应变得迟钝。排查后发现主要瓶颈在浏览器DOM更新频率上每次收到传感器数据就重新绘制整条温度曲线而曲线包含了一千多个点重绘开销太大。解决办法是把曲线重绘改成节流模式每3秒最多重绘一次数据点先缓存在内存数组中重绘时一次性追加。同时开启了Canvas accelerator加速曲线绘制性能提升了约五倍。另外对历史数据做了降采样处理展示一分钟内的曲线时用全量数据展示一天内的曲线时每隔30个点取一个代表点这样既保证了细节精度又不会让浏览器崩溃。5.3 MQTT消息重放导致的数据重复MQTT broker在断线重连后偶尔会重发旧消息导致温度数据出现重复记录。这个问题在传感器端和broker端都有可能发生传感器断网后本地积累的数据重新发送broker在恢复会话后从最后确认的消息位置继续投递。最终我在消息消费端做了幂等处理判断依据是传感器ID加时间戳如果同一传感器在同一时间戳已有记录直接丢弃新到达的重复消息。5.4 数据库连接被耗尽酿酒旺季时多个批次同时运行温度采集和设备状态检查导致数据库连接数峰值飙升出现过几次too many connections错误。排查后发现连接池配置默认的最大连接数是100应用本身并不需要这么高的并发。把连接池最大值调低到30同时开启了连接复用和空闲连接回收问题彻底解决。这件事给我的经验是连接池配置不是越大越好设置合理的上限反而能避免雪崩关键是让监控数据告诉你合理的阈值在哪里。6. 常见问题速查表问题现象可能原因排查思路与解决方案温度读数整体偏高传感器探头未完全浸没确认探头位置处于液体中心避免接触容器壁温度曲线出现尖刺电磁干扰或供电不稳分离信号线与电源线布线检查稳压模块浓度计算值与实测偏差大出糖效率系数设置不当用实测数据反推效率系数更新至配方配置批次无法进入下一阶段必填参数缺失查看阶段校验提示补全记录后再试WebSocket频繁断开网络不稳定或Nginx超时调整Nginx的proxy_read_timeout和心跳间隔发酵曲线长时间无更新传感器离线或消息堆积检查传感器网络连接查看broker消息队列长度前端页面加载缓慢历史数据量过大开启列表分页和图表降采样数据按批次隔离7. 后续扩展方向与真实体会BrewUI目前的版本已经在我自己的酿造工作室跑了二十多个批次稳定性达到预期但这只是起点。后续计划加入移动端深度适配的PWA功能让手机在锁屏状态下也能接收发酵报警通知另外考虑实现配方分享社区让用户之间可以交换和评估配方把个人的配方库变成群体的知识库。还有几个待优化的方向自动生成批次报告用PDF或HTML格式输出一份完整的酿酒记录可以直接分享给朋友或者存档增加AI辅助的配方推荐基于已有的批次数据和用户评分推荐口感风格相似的配方配方变体接入更多主流传感器设备降低硬件门槛。最后分享几个我个人的使用习惯希望能帮你更快上手BrewUI。第一每次批次结束后务必花两分钟填写最终评价和口感描述这是后续配方迭代最宝贵的参考数据。第二不要同时运行太多批次BrewUI虽然后台并发能力足够强但批次太多反而会让你的注意力分散降低每次酿造的质量控制水平。第三定期备份PostgreSQL数据库虽然服务器一般不会出事但数据是无价的——我自己的备份策略是每天凌晨自动导出一次同步到另一个存储设备同时保留最近三十天的备份文件。BrewUI这个项目的价值不只是把酿造记录搬到了电脑上。它真正改变了我的酿酒习惯——从凭感觉调整方案变成看数据做决策从碎片化记录变成完整可追溯的知识体系。如果你也在认真对待每一次酿造不妨试着用类似工具把自己的工艺沉淀下来。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进