2026/8/9 3:40:20

食堂智能称重进销存系统技术架构与四端协同实践

食堂智能称重进销存系统技术架构与四端协同实践 关键词AI预测 进销存 智能验收 自动化 供应商管理 溯源 四端协同 食堂数字化去年帮一家三甲医院部署食堂管理系统的时候踩过一个让我印象深刻的坑。当时系统已经跑了一个多月财务对账的时候发现某供应商连续三个月的结算金额和实际送货量对不上——差了将近8%。翻遍了入库记录才发现仓库师傅每次手工登记重量的时候经常漏记小数点有时候忙起来甚至直接凭记忆估个数填上去。这个问题让我意识到一件事进销存系统的数据准确性决定性因素不是后台逻辑写得有多严谨而是前端数据采集环节有多可靠。人工录入天然有四个短板——延迟、遗漏、错误、主观。这四个短板加起来基本等于Garbage in, garbage out。后来我们团队基于好伙狮数字食堂的整体方案重新设计了验收和出入库环节。核心思路就一条把数据采集从人录入转变为设备自动采集。这篇文章把整个技术架构和实践经验整理出来给有类似需求的同行做个参考。好伙狮这套系统的架构本质上是一个四端协同的物联网进销存平台。四端分别是智能秤终端数据采集层、手持终端移动操作层、采购助手/供应商助手业务协同层、电脑管理后台数据分析与管理层。四端通过统一的服务端API进行数据交互数据存储层负责持久化和查询。架构分层text ┌─────────────────────────────────────────────────┐ │ 表现层 (Presentation) │ │ 管理后台(Web) 采购助手(Mobile) 供应商助手(Mobile) │ ├─────────────────────────────────────────────────┤ │ API网关 (API Gateway) │ │ RESTful WebSocket 实时推送 │ ├─────────────────────────────────────────────────┤ │ 业务服务层 │ │ 订单服务 验收服务 库存服务 供应商服务 报表服务 │ ├─────────────────────────────────────────────────┤ │ 数据采集层 │ │ 智能秤SDK(称重拍照上传) 手持终端SDK(扫码拍照) │ ├─────────────────────────────────────────────────┤ │ 基础设施层 │ │ MySQL(业务库) MongoDB(影像库) Redis(缓存) │ │ MinIO(图片存储) RabbitMQ(消息队列) │ └─────────────────────────────────────────────────┘ 数据采集层是整个系统的关键。智能秤通过串口通信读取称重传感器数据同时触发摄像头拍照两者数据打包后通过HTTP/HTTPS上传至服务端。手持终端基于Android系统集成Zxing扫码库和CameraX拍照模块扫码和拍照完成后通过统一的RestAPI提交出入库记录。3.1 智能秤数据采集模块智能秤终端采用ARM Linux系统核心流程是重量稳定检测→触发拍照→数据打包→上传服务端。重量稳定检测的阈值设置在±20g以内避免运输过程中的震动触发误采集。大公斤量程的传感器选型需要注意温漂补偿食堂后厨环境温差大不做好补偿的话早晚误差能差出好几十克。下面是一个简化版的重量采集和稳定判断逻辑Python实现运行在终端设备的边缘计算模块上python # -*- coding: utf-8 -*- 智能秤数据采集模块 v2.3.1 功能重量采集、稳定检测、触发拍照、数据打包上传 import time import threading from dataclasses import dataclass from typing import Callable, Optional # 稳定检测阈值单位克 STABILITY_THRESHOLD 20 # 稳定持续时间单位秒 STABILITY_DURATION 1.5 # 最小触发重量低于此值忽略 MIN_TRIGGER_WEIGHT 100 dataclass class WeighingRecord: 称重记录数据结构 weight_kg: float photo_path: Optional[str] timestamp: int device_id: str batch_no: str supplier_code: str order_id: str operator_id: str class WeighingController: 称重采集控制器 def __init__(self, serial_port: str /dev/ttyS1, camera_device: int 0): self.serial_port serial_port self.camera_device camera_device self.stable_count 0 self.last_weight 0.0 self.current_weight 0.0 self.is_collecting False def read_weight(self) - float: 从串口读取重量传感器原始数据 # 实际部署时通过pyserial读取 # ser serial.Serial(self.serial_port, 9600, timeout1) # raw ser.readline() # return self._parse_weight(raw) return 0.0 # 伪代码占位 def stability_check(self, weight: float) - bool: 检测重量是否已稳定 if abs(weight - self.last_weight) STABILITY_THRESHOLD: self.stable_count 1 else: self.stable_count 0 self.last_weight weight check_interval 0.1 # 采样间隔100ms required_samples int(STABILITY_DURATION / check_interval) return self.stable_count required_samples def capture_photo(self) - str: 触发摄像头拍照返回图片路径 # 实际部署时使用OpenCV或设备SDK # cap cv2.VideoCapture(self.camera_device) # ret, frame cap.read() # path f/data/photos/{int(time.time())}.jpg # cv2.imwrite(path, frame) return # 伪代码占位 def on_weight_stable(self, callback: Callable): 重量稳定后的回调打包数据并上传 if self.is_collecting or self.current_weight MIN_TRIGGER_WEIGHT: return self.is_collecting True photo_path self.capture_photo() record WeighingRecord( weight_kground(self.current_weight / 1000, 2), photo_pathphoto_path, timestampint(time.time()), device_idSCALE_DEVICE_ID, batch_no, supplier_code, order_id, operator_id ) self.is_collecting False callback(record) def loop(self): 主循环——轮询重量并检测稳定 while True: self.current_weight self.read_weight() if self.stability_check(self.current_weight): self.on_weight_stable(self._upload_record) time.sleep(0.1) def _upload_record(self, record: WeighingRecord): 上传称重记录到服务端 import requests api_url https://api.haohuoshi.cn/v1/weighing/receive try: files {} if record.photo_path: files[photo] open(record.photo_path, rb) data { weight: record.weight_kg, timestamp: record.timestamp, device_id: record.device_id, batch_no: record.batch_no, supplier_code: record.supplier_code, order_id: record.order_id, operator_id: record.operator_id } requests.post(api_url, datadata, filesfiles, timeout10) except Exception as e: # 网络异常时写入本地缓存队列待恢复后重传 pass 3.2 手持终端扫码出库模块手持终端运行的是定制Android系统出库流程的核心是条形码扫描识别和出库记录提交流程。扫码模块基于Zxing库支持EAN-13、CODE-128、QR码等常见编码格式。拍照功能使用CameraX API在扫码成功后自动触发拍照记录物资状态。实际部署中踩过的一个坑是连续扫码的速度问题。食堂仓库高峰期出库频次很高如果每次扫码都要等拍照上传完成才能扫下一个效率会大打折扣。我们的解法是把上传操作异步化——扫码识别后立即提交出库请求拍照上传放到后台线程处理不阻塞主流程。java /** * 手持终端出库控制器 v1.8.2 * 异步拍照上传 同步主流程保证连续扫码效率 */ public class OutboundController { private static final String API_BASE https://api.haohuoshi.cn/v1; private final ExecutorService photoExecutor Executors.newSingleThreadExecutor(); /** * 处理扫码出库——主流程同步、拍照异步 */ public void handleScanResult(String barcode, String operatorId) { // Step 1: 同步——查询物资信息 MaterialInfo material queryMaterialByCode(barcode); if (material null) { showError(未找到该条码对应的物资信息); return; } // Step 2: 同步——提交出库记录核心业务 OutboundRecord record new OutboundRecord(); record.barcode barcode; record.materialId material.id; record.materialName material.name; record.operatorId operatorId; record.outboundTime System.currentTimeMillis(); record.warehouseId getCurrentWarehouseId(); boolean success submitOutbound(record); if (!success) { showError(出库提交失败请重试); return; } // Step 3: 异步——拍照留痕不阻塞主流程 photoExecutor.submit(() - { String photoPath capturePhoto(); if (photoPath ! null) { uploadOutboundPhoto(record.id, photoPath); } }); // Step 4: 播放成功提示音准备下一次扫码 playSuccessBeep(); showBriefInfo(material.name 出库成功); } private boolean submitOutbound(OutboundRecord record) { // 调用服务端API提交出库记录 // Retrofit POST 请求到 /outbound/submit return true; // 伪代码占位 } private String capturePhoto() { // 使用CameraX API拍照 // ImageCapture.takePicture() return null; // 伪代码占位 } private void uploadOutboundPhoto(String recordId, String photoPath) { // Multipart上传照片到 MinIO 对象存储 } } 3.3 核心数据库设计验收记录表是整个进销存系统的核心表之一。设计上需要同时存储结构化重量数据和非结构化的图片路径外键关联到采购订单和供应商。以下是简化后的表结构sql -- 好伙狮进销存系统 v3.1.0 核心表结构 -- 数据库MySQL 8.0 -- 验收记录表核心表 CREATE TABLE receive_record ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, order_id BIGINT UNSIGNED NOT NULL COMMENT 关联采购订单ID, supplier_id INT UNSIGNED NOT NULL COMMENT 供应商ID, material_id INT UNSIGNED NOT NULL COMMENT 物资ID, weight_kg DECIMAL(10,3) NOT NULL COMMENT 称重重量(kg), photo_url VARCHAR(512) DEFAULT COMMENT 验收照片URL(MinIO), photo_thumbnail_url VARCHAR(512) DEFAULT COMMENT 缩略图URL, batch_no VARCHAR(64) DEFAULT COMMENT 批次号, device_id VARCHAR(64) NOT NULL COMMENT 称重设备ID, operator_id INT UNSIGNED NOT NULL COMMENT 操作员ID, warehouse_id INT UNSIGNED NOT NULL COMMENT 仓库ID, quality_status TINYINT DEFAULT 1 COMMENT 品质状态:1合格 2待确认 3不合格, remark VARCHAR(255) DEFAULT COMMENT 备注, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), INDEX idx_order_id (order_id), INDEX idx_supplier_id (supplier_id), INDEX idx_created_at (created_at), INDEX idx_batch_no (batch_no), INDEX idx_device_date (device_id, created_at) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT验收记录表; -- 出库记录表 CREATE TABLE outbound_record ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, material_id INT UNSIGNED NOT NULL COMMENT 物资ID, barcode VARCHAR(64) NOT NULL COMMENT 条码, quantity DECIMAL(10,2) NOT NULL COMMENT 出库数量, unit VARCHAR(16) NOT NULL COMMENT 单位, photo_url VARCHAR(512) DEFAULT COMMENT 出库照片URL, operator_id INT UNSIGNED NOT NULL COMMENT 操作员ID, device_id VARCHAR(64) NOT NULL COMMENT 手持终端ID, warehouse_id INT UNSIGNED NOT NULL COMMENT 仓库ID, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), INDEX idx_material_date (material_id, created_at), INDEX idx_barcode (barcode), INDEX idx_device_date (device_id, created_at) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT出库记录表; 影像数据验收照片、出库照片存储采用MinIO对象存储而非数据库直接存Binary原因是食堂日均验收记录可能达到几十到上百条每张照片按照2MB计算一年下来TB级的存储量放在MinIO里无论是扩容还是CDN分发都比直接放数据库里灵活得多。数据库只存URL和缩略图URL。3.4 采购协同与消息推送采购助手和供应商助手之间的订单协同采用的是一套基于消息队列的异步通知机制。采购方下单后订单服务将订单写入数据库的同时向RabbitMQ投递一条订单通知消息。消息包含订单ID、供应商ID等关键信息供应商助手通过WebSocket长连接或FCM推送接收到通知后引导供应商进入接单确认界面。yaml # 订单协同消息路由配置 v2.1.0 # RabbitMQ Exchange Queue 绑定关系 exchanges: - name: order.exchange type: topic durable: true queues: - name: order.notify.supplier.{supplier_id} durable: true arguments: x-message-ttl: 3600000 # 消息过期时间1小时 x-dead-letter-exchange: order.dlx - name: order.notify.admin durable: true bindings: - exchange: order.exchange queue: order.notify.supplier.{supplier_id} routing_key: order.created.supplier.{supplier_id} - exchange: order.exchange queue: order.notify.admin routing_key: order.*.admin # 死信队列——超时未处理的订单自动标记 - exchange: order.dlx queue: order.timeout routing_key: order.timeout 供应商助手收到订单通知后调用API完成订单确认同时触发标签打印服务。标签打印使用标准的ZPL指令集通过TCP/IP发送到供应商端的标签打印机zpl ^XA ^FO50,50^A0N,32,32^FD订单号: {order_id}^FS ^FO50,90^A0N,28,28^FD品类: {category}^FS ^FO50,130^A0N,28,28^FD数量: {quantity}^FS ^FO50,170^A0N,24,24^FD送货日期: {delivery_date}^FS ^FO50,210^BY2,2,80^BCN,80,Y,N,N^FD{order_code}^FS ^FO50,300^A0N,24,24^FD好伙狮数字食堂^FS ^XZ 坑1后厨网络环境比想象中差食堂后厨的设备区往往是WiFi信号的死角不锈钢设备又多信号衰减严重。智能秤上传图片一旦失败如果直接丢弃数据验收记录就成了无图状态。解决方案是在终端侧加一个本地SQLite缓存队列上传失败的数据先写本地网络恢复后按时间顺序重传。重传机制加指数退避策略避免恢复瞬间的请求风暴。坑2照片不是越多越好最早版本设计是每次称重拍三张照片全景、特写、俯拍结果发现食堂日均几百次称重存储成本和上传带宽压力都不小。跟实际使用的采购员聊了之后精简为拍一张——能看清货物全貌的高角度照片就够了。溯源的核心是有图不是图多。这个调整让存储和带宽成本降了六成不影响实用性。坑3手持终端的续航第一版手持终端频繁扫码拍照上传电池撑不过一个完整的上午班次。后来做了两个优化一是降低CameraX的拍照分辨率出库照片用1080p就够了二是增加了一个低功耗模式——屏幕熄灭期间暂停后台心跳只有扫码才唤醒。优化后续航从不到4小时提升到接近8小时基本覆盖一个全天班次。好伙狮这套系统跑了一段时间之后积攒了相当体量的历史采购数据——每天什么品类采购多少、什么时段消耗量大、什么季节哪些食材需求波动明显。这些数据为AI预测模型的训练提供了基础。我们在v3.2版本中上线了一个实验性的AI采购建议功能基于Prophet时间序列模型对历史采购数据进行拟合结合节假日、天气、用餐人数等外生变量输出未来一周的采购量预测。目前这个功能还处于持续优化阶段预测准确率在常规工作日可以达到85%以上节假日和特殊活动日还有待提升。AI预测的上限取决于数据质量而数据质量的上限取决于前端采集的准确性。这也是为什么我们在智能秤和手持终端的数据采集上花了大量精力——入口数据不准后面的预测模型再复杂也是白搭。好伙狮数字食堂这套以智能称重拍照溯源和四端协同为核心的进销存方案本质上解决的是食堂采购管理中的两个根本问题数据可信度和协同效率。数据可信度靠的是设备替代人工——称重数据由传感器采集而不是人手写照片由摄像头拍摄而不是人用手机拍杜绝了人为偏差和主观造假的空间。协同效率靠的是四端联动——采购下单、供应商接单、仓库验收、后台管理四个环节在系统内完成信息流转不再依赖电话和即时通讯。技术上没有特别高深的东西难的是把硬件设备、移动端应用、Web后台和消息系统有机地串在一起让每一个终端的操作都服务于同一个业务闭环。这也是做toB系统最有挑战的地方——不是造轮子是把轮子装到正确的轴上。版本信息好伙狮进销存系统 v3.2.1本文技术架构基于该版本撰写。文中涉及的代码示例为简化版伪代码实际生产环境实现更为复杂。