工业数据服务后端项目复盘
C++ 工业数据服务后端项目复盘:从高频数据写入到实时报警推送
1. 项目背景
这个项目是一个面向工业设备和上位机系统的数据服务后端,主要负责接收电解槽、MEA、CCM 等生产和测试环节产生的数据,并将这些数据稳定写入 PostgreSQL 数据库。
系统除了基础的数据入库,还提供了动态查询、更新、删除、用户登录认证、设备写入权限控制、实时报警推送等能力。整体目标是让前端大屏、上位机、生产管理系统能够通过统一的 HTTP API 访问后端数据。
2. 技术栈
| 模块 | 技术 |
|---|---|
| 开发语言 | C++17 |
| HTTP 框架 | Pistache |
| 数据库 | PostgreSQL |
| 数据库访问 | libpq 原生 C API |
| JSON 处理 | nlohmann/json |
| 并发处理 | 自定义 ThreadPool、后台 Worker |
| 数据写入 | 批量队列、事务提交、失败降级 |
| 认证 | JWT、libsodium 密码校验 |
| 日志 | spdlog |
| 实时推送 | SSE |
| 外部通知 | libcurl、飞书机器人 Webhook |
| 构建系统 | CMake |
| 配置管理 | JSON 配置文件 |
| 扩展依赖 | OpenCV |
这套技术栈的特点是比较偏底层,尤其是数据库访问没有使用 ORM,而是直接基于 libpq 封装,这样可以更明确地控制 SQL、连接生命周期和性能开销。
3. 系统整体架构
系统可以分成四层:
HTTP 层负责接收请求和路由分发,Handler 层负责业务处理。查询类接口通过数据库连接池访问 PostgreSQL,写入类接口则先进入内存队列,再由后台 Worker 批量写入数据库。
这样设计的好处是:上位机上报数据时不需要等待数据库完整写入,可以快速返回;后台再统一进行批处理,提高吞吐能力。
4. 核心模块设计
4.1 配置管理
项目通过 config/config.json 管理运行参数,包括服务端口、线程数、数据库连接串、JWT 参数、写入队列大小、批量写入大小、飞书机器人地址等。
这样可以避免把环境参数写死在代码里,方便在开发、测试、部署环境中切换。
4.2 元数据驱动的数据映射
项目中有一个比较关键的配置文件:metadata.json。
它定义了逻辑表名、真实数据库表名、主键、字段映射和写入白名单。例如前端或上位机传入的是中文字段名,后端会根据元数据映射到数据库字段。
这种设计带来了几个好处:
- 前端字段和数据库字段解耦;
- 动态 CRUD 可以通过白名单限制表和字段;
- 不同业务表可以复用同一套写入逻辑;
- 设备写入权限可以在配置层控制。
4.3 批量写入引擎
数据写入没有直接在 HTTP 请求线程里执行,而是进入 BatchIngestor 队列。
写入流程大致是:
- 上位机调用 /api/test_data;
- 后端校验 JWT,并提取 client_id;
- 根据 metadata.json 校验表名和写入权限;
- 将数据转换成 RowTask 放入队列;
- 后台 Worker 从队列中批量取数据;
- 按表分组;
- 使用事务批量写入 PostgreSQL;
- 如果批量写入失败,则降级为逐条写入。
数据写入流程图:
这个设计解决了高频数据上报时 HTTP 请求被数据库阻塞的问题,也避免了单条插入带来的性能浪费。
4.4 数据库连接管理
查询接口使用连接池,连接通过 RAII 的方式自动归还。每次获取连接时会检查连接是否有效,如果连接断开,会尝试重连或重建连接。
写入 Worker 则使用独立长期连接,避免后台写入线程和前台查询线程争抢同一个连接池。
4.5 动态 CRUD
系统提供了通用接口:
- POST /api/query
- PUT /api/update
- POST /api/delete
这些接口不是直接拼接任意 SQL,而是先通过 metadata.json 校验逻辑表和字段,再由后端生成参数化 SQL。
这样既保留了动态查询的灵活性,也降低了 SQL 注入风险。
4.6 JWT 认证与设备权限
登录接口 /api/login 支持普通用户和上位机设备两种模式。
普通用户 Token 使用配置文件中的过期时间;上位机 Token 会额外带上 client_id,有效期默认一年。
设备上报数据时,后端会读取 Token 中的 client_id,再和 metadata.json 中的 write_permission 进行匹配。这样可以限制不同设备只能写入自己被授权的数据表。
4.7 实时报警推送
报警模块不是直接在数据写入逻辑里硬编码判断,而是通过 PostgreSQL 的 LISTEN / NOTIFY 机制接收数据库侧产生的报警事件。
后端的 PgListener 会监听 electrolyzer_issue_channel。当数据库发出通知时,后端解析报警 JSON,然后交给 AlarmManager 处理。
AlarmManager 做了三件事:
- 通过 SSE 推送给前端大屏;
- 通过飞书机器人发送外部通知;
- 对相同 rate_limit_key 做 20 秒限流,避免重复报警刷屏。
报警链路图:
5. 项目中的工程思考
这个项目里比较有价值的点,不是单独某个接口,而是整体工程结构:
第一,写入链路做了异步化。HTTP 请求只负责接收和入队,数据库写入交给后台 Worker,降低了上位机请求超时的风险。
第二,使用元数据驱动表结构。新增业务表时,可以更多地通过配置扩展,而不是复制一套新的 Handler。
第三,数据库操作尽量做了安全边界。动态查询、更新、删除都要求表名和字段经过白名单校验,避免完全开放 SQL 能力。
第四,报警链路和业务写入链路解耦。报警由数据库通知触发,后端负责转发和限流,这样后续扩展短信、邮件、企业微信等渠道也比较自然。
6. 当前不足与后续优化
这个项目目前已经具备核心能力,但仍然有一些可以继续优化的地方:
- 部分接口目前还没有统一接入 JWT 鉴权,例如 Flex CRUD 接口;
- 写入队列是内存队列,服务异常退出时可能丢失未落库数据;
- 日志配置中有滚动日志参数,但当前代码还没有真正接入 rotating sink;
- server.host 配置目前被读取,但实际服务绑定的是 0.0.0.0;
- OPTIONS 预检路由还不是全路由自动处理;
- 后续可以考虑增加单元测试、接口测试和部署脚本。
7. 总结
这个后端项目主要完成了工业数据服务中的几个关键问题:高频数据接入、批量写入、动态查询、权限控制、实时报警和外部通知。
对我来说,这个项目的价值不仅在于实现了一组 API,更重要的是完整经历了一个后端服务从路由设计、配置管理、数据库封装、并发写入、安全控制到报警推送的工程化过程。
它也让我更清楚地理解了:在实际业务系统里,稳定性、可维护性和可扩展性往往比单个功能点更重要。一个好的后端服务,不只是能把数据写进去,还要能在高频请求、异常数据、数据库波动、权限隔离和报警通知这些场景下持续工作。
