
做了几年工厂信息化系统今年接手的这套车辆挡泥板机器人工厂管理系统算是我第一次在同一个项目里同时把 Django 和 Flask 都推到生产环境。项目名字听着长其实核心目标很直接把一条“注塑—转运—喷涂—质检—装配”的自动化产线管理起来让计划员不用再拿着纸质工单跑到车间找人填表让设备管理员能实时看到每一台机器人的运行状态让质量人员能按批次追溯到具体是哪个工单、哪道工序、哪台设备干出来的活。这个项目适合正在做智能制造、工厂MES、设备数据采集的团队参考尤其是那些想用 Python 生态同时解决“业务平台”和“设备接入”两个问题的同学。如果你只是写 DemoDjango 和 Flask 二选一就够了但一旦真的上线两种框架各管一摊反而比硬塞在一个框架里舒服得多。下面我按实际开发顺序把设计思路、数据库结构、关键代码、部署流程和现场踩坑都整理出来给大家一份能直接照着改的底稿。1. 项目背景与需求拆解1.1 挡泥板产线里到底需要管什么汽车挡泥板属于典型的注塑外饰件常见的原材料是 PP 或者 ABS 改性料经过注塑机成型后还要做去浇口、表面处理、喷涂、烘烤、质检、装配附件等工序。这条线里真正意义上的“机器人”就有好几类注塑机旁的取件机械手、上下料六轴机器人、喷涂机器人、用于视觉检测的自动识别装置。再加上输送线、烘炉、温度传感器、扫码枪这些外围设备整条产线要协调动作单靠设备厂家自带的控制系统根本不够用。车间原来的管理方式比较原始计划员用 Excel 排产生产完成数量靠班长每天晚上手工汇总质量问题记录在一张张纸质单子上。一旦某个批次被客户投诉想追查是哪台机器人、哪班操作员干的要翻半天旧账。所以项目立项时需求写得很明确以生产工单为主线把机器人状态、产量数据、质量数据、设备报警全部拉到一个系统里做到从“计划下发”到“产品入库”的全过程可追溯。这套系统的定位不是去替代机器人的运动控制而是做生产执行层的数据中枢。机器人的轨迹、速度这些还是由 PLC 和控制器负责管理系统只关心三件事任务是什么、设备干得怎么样、产品质量合不合格。1.2 为什么一个项目同时用 Django 和 Flask团队一开始在技术栈上吵过一轮。一部分人认为 Django 自带 Admin、ORM、权限、迁移工具工厂管理系统这种重业务、多表单、多报表的场景Django 是绝对的主流选择。另一部分人认为 Flask 灵活设备接入只要开几个接口就行不用把整个 Django 项目拖到工控机上。最后我们用了个折中方案Django 管“人、单、账”Flask 管“机、信、控”。用人话说Django 是总部负责工单、权限、报表、配置Flask 是驻扎在产线旁的调度员负责跟机器人对话、传指令、收心跳、报状态。之所以不把它们合并是因为两者的生命周期不一样业务功能每周都可能变但设备协议和调用逻辑其实很稳定把设备接入放在 Flask 独立进程里改设备协议时不用动主系统风险小得多。用生活里的例子类比Django 像公司的 ERP权限严格、流程完整Flask 像车间门口的电子看板轻便直接、随改随用。两个框架可以在同一台服务器上跑也完全可以分开放中间用 HTTP 或消息队列通信。实际做下来这个边界守住以后团队协作也顺了后台开发专心写 Django 业务设备工程师盯 Flask 接口互不干扰。2. 系统整体架构与模块划分2.1 双框架协作的拓扑与通信方式整套系统的物理拓扑并不复杂核心是两条链路。第一条是管理端链路浏览器访问 Django 服务Django 连 MySQL 存业务数据Redis 做缓存和状态暂存。第二条是设备链路Django 把生产任务通过 HTTP 或 Redis 队列发给 Flask 服务Flask 解析后转成机器人控制器能识别的指令同时接收机器人上报的实时状态再回调 Django 落库。通信方式我们选的是 HTTP REST 为主Redis 队列为辅。车间内网环境流量不大每天几千个工单HTTP 完全扛得住。但对于设备状态心跳这种高频小数据直接写入 MySQL 太浪费先让 Flask 往 Redis 里刷新Django 展示看板时读 Redis只有需要历史报表时才批量落库。这样既保证了看板实时性又不会把数据库拖垮。模块划分方面Django 端包含用户权限、生产工单、物料管理、设备档案、质量记录、产量报表六个模块Flask 端只保留任务接收、指令下发、状态接收、报警推送、视觉结果上送五个轻量接口。两边各有一份接口文档字段命名统一用蛇形命名避免联调时来回扯皮。整体模块关系可以用下面这张表概括模块名称所在框架主要职责数据存储用户与权限Django登录、角色、操作权限MySQL生产工单Django工单创建、派工、状态流转MySQL设备档案Django机器人、PLC、传感器台账MySQL质量记录Django Flask质检数据展示、缺陷追溯MySQL Redis任务分发Django Flask工单下发、指令转换Redis HTTP状态上报Flask心跳、在线状态、报警Redis产量统计Django班次产量、工时报表MySQL2.2 核心数据模型设计要点数据模型我直接按“工单驱动”的思路设计。最核心的是 WorkOrder 表字段包括工单号、产品型号、计划数量、完成数量、不良数量、状态、计划开始时间、实际开始时间、结束时间。工单号必须唯一用时间戳加三位流水号生成比如20250121-001现场工人报工时喊起来也顺口。设备状态不直接存在 WorkOrder 里而是单独建一张 DeviceStatus 表只保留当前最新状态。这样设计是为了避免高频率的状态刷新把工单表的行锁占满。实时状态型数据走 Redis历史状态每天凌晨批量刷入 DeviceStatusHistory 表。生产报工记录 ProductionLog 是流水型数据每完成一件写一条记录字段包括工单ID、设备ID、操作员ID、时间、是否合格。下面是一个简化版的 Django models.py字段比实际项目略少但结构一致。要注意核心的一点外键全部逻辑外键不直接使用 DB ForeignKey 约束工厂系统里设备可能中途换编码物理外键后期改起来太痛苦。from django.db import models class WorkOrder(models.Model): STATUS_CHOICES ( (pending, 待执行), (running, 执行中), (paused, 已暂停), (completed, 已完成), (aborted, 已终止), ) order_no models.CharField(max_length32, uniqueTrue) product_model models.CharField(max_length64) plan_qty models.IntegerField() done_qty models.IntegerField(default0) defect_qty models.IntegerField(default0) status models.CharField(max_length16, choicesSTATUS_CHOICES, defaultpending) plan_start_time models.DateTimeField(nullTrue, blankTrue) actual_start_time models.DateTimeField(nullTrue, blankTrue) actual_end_time models.DateTimeField(nullTrue, blankTrue) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table work_order ordering [-created_at] class DeviceStatus(models.Model): device_code models.CharField(max_length64, uniqueTrue) online models.BooleanField(defaultFalse) current_status models.CharField(max_length32, defaultidle) current_order_no models.CharField(max_length32, blankTrue) updated_at models.DateTimeField(auto_nowTrue) class Meta: db_table device_status class ProductionLog(models.Model): work_order models.CharField(max_length32) device_code models.CharField(max_length64) operator models.CharField(max_length32) is_qualified models.BooleanField(defaultTrue) produce_time models.DateTimeField(auto_now_addTrue) class Meta: db_table production_log这套表结构跑了一年多没做过结构性大调整。经验是工单、报工、设备状态分开建表比全塞在一张大表里好维护得多。3. Django 端业务功能与工单闭环3.1 从零创建一个 Django App 的实操记录Django 项目我习惯用虚拟环境隔离依赖避免本机多个 Python 项目互相污染。创建项目命令很简单但有几个细节值得注意。mkdir factory_system cd factory_system python -m venv venv source venv/bin/activate # Windows 使用 venv\Scripts\activate pip install django mysqlclient redis django-admin startproject factory . python manage.py startapp production python manage.py startapp device创建完 app 后记得把production和device加到INSTALLED_APPS。有个新手容易踩的坑用django-admin startproject后面带了“.”项目文件会直接生成在当前目录而不是多套一层目录。有些人喜欢“先建外层项目目录再建内层同名目录”的默认结构这个看习惯但带“.”的结构在部署时路径更干净我推荐这种。另外很多同学问“在 PyCharm 里怎么导入一个已经建好的 Django 项目”。正确做法是打开 PyCharm 后选择 Open选中项目根目录然后配置 Python 解释器指向虚拟环境里的venv/bin/python。千万不要用 New Project 再手动复制文件那样 Interpreter 和项目结构很容易错乱。导入后如果 Django 控制台没有正确识别 manage.py检查一下 Settings 里的 Django Support 是否启用项目根目录和 settings.py 路径是不是指对了。后台管理直接用 Django Admin 注册模型两天就能搭出一个够用的内部后台。以下是 admin.py 的注册示例from django.contrib import admin from production.models import WorkOrder, ProductionLog admin.register(WorkOrder) class WorkOrderAdmin(admin.ModelAdmin): list_display (order_no, product_model, plan_qty, done_qty, status) list_filter (status,) search_fields (order_no, product_model)这类管理系统里80% 的增删改查页面靠 Django Admin 就够没必要额外写一堆视图函数。只有当操作逻辑复杂时比如工单下发涉及外部服务调用才单独写自定义视图。3.2 工单下发与状态机流转工单状态机是整个系统的核心。字段上我用的是pending - running - completed三条主路径旁边挂着paused和aborted两个异常分支。为什么要显式设计状态机因为现场操作员可能在同一时间看到多个状态如果枚举不清晰就会出现“前面还在运行后面直接没数据”这种奇怪现象。创建工单后页面只生成一条待执行记录。真正下发动作发生在计划员点击“开始生产”按钮时这时 Django 视图要做三件事把工单状态改为 running调用 Flask 接口下发任务同时记录实际开始时间。这三件事不能没有先后次序地乱写尤其不能先把状态改成 completed再调用外部接口否则机器人还没收到指令看板上已经显示完成了。我建议用一个独立的 service 函数处理下发逻辑保持视图精简。简化的核心代码如下def dispatch_order(order_no): try: with transaction.atomic(): order WorkOrder.objects.select_for_update().get(order_noorder_no) if order.status ! pending: raise ValueError(工单状态不允许下发) order.status running order.actual_start_time timezone.now() order.save() # 外部调用放在事务之外避免长时间占用数据库连接 resp requests.post( http://127.0.0.1:5001/api/task/dispatch, json{order_no: order_no}, timeout5 ) resp.raise_for_status() except Exception as exc: log.error(工单下发失败: %s, exc) raise这里特别注意requests.post不要放在transaction.atomic()里面。如果 Flask 服务响应慢数据库事务会一直开着锁住工单行其他操作全部排队。外部调用和数据库事务是两种不同资源混在一起最容易出线上故障。3.3 产量统计与质量追溯的报表实现报表模块用的是 Django ORM 的聚合查询。按班次统计产量核心代码一行就能搞定from django.db.models import Sum, Count from production.models import ProductionLog daily_report ( ProductionLog.objects .filter(produce_time__date2025-02-10) .values(device_code) .annotate(totalCount(id), qualifiedSum(is_qualified)) )但实际项目里不会只有一个日期筛选还有设备筛选、产品型号筛选、班次筛选。我的经验是先写一个筛选器类把所有参数集中处理再更新聚合查询。报表页面不需要太花哨直接渲染成 HTML 表格再导出一个 CSV 就行。现场管理者和领导更喜欢看 Excel所以我在 Django 视图里加了一个csv输出接口点按钮就能下载没人关心页面画得有多漂亮。质量追溯稍微复杂一点。每个挡泥板上会贴二维码扫码枪读到后通过 Flask 接口上报到 Redis 缓存Django 按时间批量落库。这样按产品序列号就能反查生产工单、设备、操作员、质检图片完全满足客户审核要求。4. Flask 设备接入层设计与实现4.1 Flask 和 Django 的边界怎么划Flask 服务在设计前我们定了一条纪律Flask 只做设备接入和指令转发不写任何业务逻辑不碰 MySQL 里的业务表最多用 SQLite 存一份本地任务缓存。这样 Flask 项目始终保持在几百行代码以内出问题容易排查。Flask 跑在边缘工控机上往往要和机器人控制器走同一网段。很多机器人系统对外提供 HTTP 接口或者 Modbus TCP 协议Flask 作为中间层负责把 Django 下发的 JSON 指令翻译成机器人控制器能接受的报文。具体协议各家不一样但中间层思路是一致的进口统一、出口适配。以后换机器人品牌只需要单独改适配器Django 端完全不用动。4.2 关键接口实现任务分发与状态上报Flask 服务我习惯用app Flask(__name__)起步再加一个线程锁保证任务状态不打架。下面是一个可运行的最小实现实际项目在此基础上扩充了指令适配。from flask import Flask, request, jsonify import threading app Flask(__name__) lock threading.Lock() task_cache {} app.route(/api/task/dispatch, methods[POST]) def dispatch(): data request.get_json() order_no data.get(order_no) with lock: if order_no in task_cache: return jsonify({code: 1, msg: 任务已存在请勿重复下发}) # 这里实际会调用机器人控制器的接口 robot_response send_to_robot(order_no, data) task_cache[order_no] { order_no: order_no, status: running, robot_response: robot_response } return jsonify({code: 0, msg: ok}) app.route(/api/robot/status, methods[POST]) def robot_status(): data request.get_json() device_code data.get(device_code) status data.get(status) # 写入 Redis供 Django 看板实时读取 redis_client.set(fdevice:{device_code}:status, status, ex30) return jsonify({code: 0}) if __name__ __main__: app.run(host0.0.0.0, port5001, threadedTrue)send_to_robot函数内部是品牌差异最大的地方也是最考验设备调试经验的部分。有的机器人控制器一次只能接收一个任务并发多了会直接拒绝有的必须按特定顺序发送使能、启动、暂停指令。所以我在 Flask 端设计了一个简单的阻塞式队列同一台设备的任务按顺序排队避免同时给机器人发多条指令。4.3 掉线重试与幂等处理车间网络偶尔会抽风Flask 或 Django 某一端可能瞬间连不上。开始我们只做了超时和异常捕获但出现过一个重复下发的问题Django 请求超时后重试Flask 把同一个工单号创建了两次。后来加了幂等判断也就是上面代码里的task_cache检查。对于机器人上报的状态数据我们要容忍乱序。机器人执行完一个动作后可能先上报“结束”再上报“开始”因为内部缓存刷新的时机不同。解决办法是每条上报数据都带一个时间戳Flask 接收时比较后丢弃旧数据不让历史状态覆盖新状态。这里用 Redis 的SETNX或者简单的乐观锁都可以关键是“以时间戳为准”这个原则要记住。5. 部署环境准备与生产优化5.1 Python、虚拟环境与依赖安装的那些坑部署环境是生产项目翻车最多的地方。首先是 Python 版本我建议统一用 3.8 或 3.10别在车间服务器上追逐最新版本。Django 和 Flask 的生态虽然兼容很快但现场旧设备上位机里的操作系统往往是 CentOS 7 或者 Ubuntu 16GLIBC 版本不够装新版 Python 会到处报错。安装依赖时如果你要跑视觉检测需要opencv-python如果数据库用 MySQL需要mysqlclient。很多人卡在pip install mysqlclient编译失败最简单粗暴的解决办法是提前装好系统依赖Ubuntu 上执行sudo apt-get install python3-dev default-libmysqlclient-dev build-essentialWindows 上更省事的是直接安装mysqlclient的预编译 wheel 包。另外别用全局 Python 环境一定要建虚拟环境。车间服务器可能同时跑着其他 Python 服务全局装依赖会把环境搞乱。5.2 用 gunicorn、uwsgi 和 supervisor 管理进程Django 端生产环境我习惯用 uWSGI 跑Flask 端用 gunicorn 跑两者统一交给 supervisor 管理。Django 启动命令示例source /opt/factory_system/venv/bin/activate uwsgi --http 0.0.0.0:8000 --module factory.wsgi \ --workers 4 --threads 2 --stats 127.0.0.1:9191Flask 端因为要处理并发回调用 gunicorn 加多 worker 模式gunicorn -w 2 -b 0.0.0.0:5001 app:app这两个服务都放在内网前端页面通过 Nginx 反向代理统一对外。Nginx 配置里把/路由到 Django 的 8000 端口静态文件直接交给 Nginx。Flask 的 5001 端口不需要对外暴露只有 Django 服务器能访问它等于加了一层简单的网络隔离。进程管理用 supervisor 的好处是进程崩了会自动重启还能把日志写到固定目录。这里要提醒一句生产环境下千万别用 Flask 自带的开发服务器也别用 Django 的runserver它们处理不了真实并发还可能因为代码热加载导致状态错乱。6. 常见问题与排查技巧实录6.1 排错速查表把这个项目上线后遇到最多的问题整理成了一张速查表发现故障时可以先对照排查问题现象可能原因解决办法Django migrate 时提示表不存在模型没加入 INSTALLED_APPS 或迁移文件没生成执行makemigrations app_name后再 migrateFlask 返回中文乱码响应头 Content-Type 没有指定 UTF-8使用jsonify并设置app.config[JSON_AS_ASCII] False看板上机器人状态不刷新Redis key 过期或客户端未刷新检查EX过期时间加长到 30 秒机器人频繁跳线网络超时时间设置太短超时统一调整为 5 秒并增加重试工单重复下发外部调用超时后前端重复请求在 Flask 端加task_cache幂等判断Django Admin 登录后无样式静态文件未收集执行collectstatic并配置 Nginx 静态路径PyCharm 导入项目后 manage.py 无法运行解释器未配置或者项目路径含中文删除.idea后重新 Open使用虚拟环境 Python这张表是血泪教训换来的。尤其是 Redis key 过期时间设短了看板闪断设长了故障恢复不及时最后折中设为 30 秒配合前端 10 秒拉取一次效果最稳。6.2 三个值得保留的实战设计最后分享几个我们在项目里坚持下来的设计。第一工单号和其他主键一律用字符串不用数据库自增 ID 暴露给外部。客户来审核时需要看工单号自增 ID 太容易猜也不利于跨系统同步。第二所有外部接口调用必须加超时、重试和幂等即使内网环境也一样。一套系统上线半年后最大的故障来源往往是某个服务假死而不是协议不兼容。第三日志格式统一加 trace_id从 Django 下发到 Flask 执行再到机器人上报整条链路的日志能串起来排障时间至少缩短一半。另外关于视觉检测和 OpenCV我们最初打算在 Flask 里直接跑缺陷识别后来发现服务器的 CPU 资源不够GIL 又把多线程卡得死死的。最后改成 Flask 收到机器人触发信号后把图像路径放进 Redis 队列由独立的 Python 多进程 CPU 程序处理结果再回调 Flask。这个改造让 Flask 服务一直保持轻盈视觉程序崩了也不影响工单流程。这套系统从开发到上线大概用了两个月中间返工最多的地方不是代码写不出来而是业务边界没谈清楚。搞工业软件就是这样技术选型可以很花哨但真正决定项目成败的是能不能理解车间现场的节奏。我个人最大的体会是Django 和 Flask 同时用并不是炫技而是用最小的成本把“管理”和“控制”这两个天然不同频的事情分开。如果你也正在做类似的工厂管理系统希望上面这些设计和踩坑记录能帮你少走几步弯路。