尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Django与Flask双框架无人超市管理系统实战解析

发布时间:2026/9/26 13:14:37

资讯中心
01
ARTICLE

Django与Flask双框架无人超市管理系统实战解析

Django与Flask双框架无人超市管理系统实战解析
前阵子我把一个基于 Python django flask 的无人超市管理系统从头到尾做了一遍。老实说这类项目听起来像是“毕业设计专属题库”但真落地起来业务边界、数据建模、订单库存联动、设备接口对接每一块都有值得抠的细节。这套系统到底解决什么问题无人超市的“无人”是靠硬件实现的——门禁、自助收银台、摄像头但门店的运转需要一个数字大脑商品怎么上架、库存还剩多少、订单怎么结算、会员余额够不够、一天的销售额是多少。管理系统管的就是这些。它适合三类人一是要做课设或毕设想找一个既有完整后台又有接口实践的题目二是做无人零售或便利店信息化想要一套能跑起来的轻量后台三是想搞明白 Django 和 Flask 在一个项目里到底能不能“同框”——标题里这两个框架并列写不少人觉得是写错了其实有合理的打开方式后面我会单独讲。我会按一条真实的开发主线来写需求拆解、数据库设计、环境搭建、订单与库存联动、设备接入和实时推送最后是排坑实录。如果你照着做拿一套能演示的超市管理系统问题不大。1. 无人超市管理系统到底要管什么——需求设计与模块拆解1.1 业务场景与系统边界无人超市的典型场景用户扫码进门自己挑商品到自助收银台结算门口的闸机核验后放行。从软件系统角度看这套流程涉及四件事硬件识别拿了什么、订单结算要付多少钱、库存联动卖出去一个少一个、会员体系余额和优惠。但真把“识别”做成完全靠视觉算法识别商品那是另一套复杂系统对大多数演示型项目来说投入产出比太低。我的建议是先划清边界系统侧重管理端和订单端硬件端通过预留接口接入。演示时可以不做真实识别而是用“扫码条码 手动加减购物车”的方式模拟整个购物链路。这样既避免了铺开太多技术点又能把超市业务的核心闭环跑通对方一看就懂。另一个容易忽略的是权限问题。后台给店长、收银员、系统管理员开账号不同角色能看能操作的东西不一样。Django 自带了用户、组和 Permission 体系本身就是一套可用的 RBAC 实现不用重复造轮子直接继承重写就行。1.2 核心功能模块清单我把需求整理成一张表格按优先级排模块核心功能优先级商品管理商品增删改查、条码、分类、上下架、图片P0库存管理入库、出库、实时库存、库存预警P0订单管理购物车、下单、结算、退款、订单详情P0会员管理注册、登录、余额、积分、消费记录P1设备对接门禁/收银机事件接入、设备状态上报P1数据看板销售趋势、TOP10、库存预警、客流记录P1系统权限后台账号、角色、操作日志P1这里的 P0 是系统能跑起来的最低要求P1 是让系统更像一个真正产品的加分项。我实际开发时也是这个顺序先把商品、库存、订单串起来再补会员和看板最后做设备模拟。这样每个阶段都有可演示的成果不会出现“开发两周还停留在一个空壳登录页”的情况。1.3 为什么标题里会出现“django flask”技术选型的关键思考这是很多人看到标题时最疑惑的地方。按理说 Django 和 Flask 是两个独立 Web 框架写管理系统二选一就够了为什么并列出现我在项目里遇到的情况是需求文档里写的是候选技术栈开发时二选一。但如果你想在这个项目里把两个框架都用上有一种拆分方式是合理的Django 做主后台Flask 做设备接口的轻量微服务。拆分的逻辑在于管理后台需要模型管理、Admin 界面、模板渲染、权限控制这些正是 Django 的强项而门禁回调、收银机事件推送这类接口逻辑简单、请求频率高、希望启动快用 Flask 一小段代码就能搞定。两个服务共用同一个数据库文件各自只操作自己负责的表。缺点是维护两套框架的成本更高团队不熟悉时容易踩坑所以这种方案适合学习型项目或者确实有“边缘小接口”需求的场景而不是所有项目都该这么搞。如果只让我推荐一套新手或做课设的同学就选 Django 全栈一个项目里用模板渲染加 DRF 写 API全部搞定。Flask 更像一把小刀适合需要快速搭 API 的场景。2. 技术架构与数据库设计先把地基画清楚2.1 架构分层与协作方式整个系统我分了四层展示层、应用层、数据层和设备层。展示层是后台的 Django 模板页面和自助终端的静态页面应用层是 Django 与 Flask 两个服务数据层是数据库设备层是扫码门禁、自助收银机这些硬件它们通过 HTTP 上报事件到 Flask 接口。这样的好处是职责清楚后台界面坏了不影响设备上报设备接口繁忙也不会拖垮后台渲染。两个服务共用数据库这件事很多人在设计时会有疑虑。我的做法是规定 Django 负责业务表和后台操作Flask 只负责“设备事件表”和“商品基础信息的只读查询”。为了不产生模型冲突我不会让两边同时 migrate 同一张表而是把表归属写清楚Flask 侧用 SQLAlchemy 的__tablename__指向 Django 建好的表只做读和写事件不做删除和改结构。实际开发中还有一条经验设备层与系统层的接口协议要最先定。哪怕后期代码改来改去只要 JSON 字段定好了硬件端的同事或者你自己写的模拟器就不用反复改。我一般先写一个docs/api.md列出每个接口的请求、响应、错误码示例然后再动手写代码效率会高很多。2.2 核心表结构设计数据库设计是整个系统最重要的一步表没建好后面全返工。我的核心表大概这么几张商品表、库存表、订单表、订单明细表、会员表、设备事件表。下面是用 Django 模型表达的简化结构class Category(models.Model): name models.CharField(max_length50) class Product(models.Model): name models.CharField(max_length100) barcode models.CharField(max_length64, uniqueTrue) category models.ForeignKey(Category, on_deletemodels.PROTECT) price models.DecimalField(max_digits10, decimal_places2) image models.ImageField(upload_toproducts/, blankTrue) status models.BooleanField(defaultTrue) class Stock(models.Model): product models.OneToOneField(Product, on_deletemodels.CASCADE) quantity models.IntegerField(default0) updated_at models.DateTimeField(auto_nowTrue) class Order(models.Model): order_no models.CharField(max_length32, uniqueTrue) member models.ForeignKey(Member, nullTrue, on_deletemodels.SET_NULL) total_amount models.DecimalField(max_digits10, decimal_places2) status models.CharField(max_length16, defaultpending) created_at models.DateTimeField(auto_now_addTrue) class OrderItem(models.Model): order models.ForeignKey(Order, related_nameitems, on_deletemodels.CASCADE) product models.ForeignKey(Product, on_deletemodels.PROTECT) product_name models.CharField(max_length100) price models.DecimalField(max_digits10, decimal_places2) quantity models.IntegerField()有几个设计点我要专门解释。第一订单明细里必须冗余一份商品名称和价格因为商品价格以后会变历史订单不能被价格调整影响这是“快照”思想第二库存单独一张表而不是在商品表上加字段是为了以后好扩展多仓库和盘点一对一关系在逻辑上也更清晰第三订单号自己做生成器比如20250601 6 位随机字母数字避免用数据库自增 id 直接暴露订单量第四商品表的外键用了on_deletePROTECT防止删分类时连带把商品删了。2.3 SQLite还是MySQL数据库选型与统一配置我给这个项目用了 SQLite原因很直接演示和课程设计场景下单文件零配置Django 和 Flask-SQLAlchemy 都天然支持而且这套模型将来切 MySQL 只是改一行连接串的事。如果你要做真门店或者多人同时访问再换成 MySQL字符集记得设utf8mb4否则中文会乱码。Django 侧的配置放在settings.pyFlask 侧用一个相同的连接串# Django settings.py DATABASES { default: { ENGINE: django.db.backends.sqlite3, NAME: BASE_DIR / db.sqlite3, } } # Flask app.py app.config[SQLALCHEMY_DATABASE_URI] sqlite:///db.sqlite3 app.config[SQLALCHEMY_TRACK_MODIFICATIONS] False注意这里存在一个细节如果 Django 和 Flask 都跑在同一目录下sqlite:///db.sqlite3的路径就是相对当前文件的位置部署到服务器时很容易因为工作目录不同找不到文件。所以我建议两边都用绝对路径拼接用Path(__file__).resolve().parent来定位项目根目录再去拼数据库文件路径省掉一大类“本地好好的服务器上就报错”的问题。3. 从零搭建Django管理端 Flask接口服务的实操记录3.1 环境准备与项目初始化如果你还没装 Python去官网下载安装包Windows 安装时记得勾选 “Add Python to PATH”这一步跳过的话后面命令行里输python会没反应。装好之后进项目目录先建虚拟环境再装依赖这是避免污染全局环境的最基本操作python -m venv venv # Windows venv\Scripts\activate # Linux / macOS source venv/bin/activate pip install django djangorestframework pillow pip install flask flask-sqlalchemy flask-cors用国内镜像装会快很多加个-i https://pypi.tuna.tsinghua.edu.cn/simple即可。然后创建项目和应用django-admin startproject supermarket cd supermarket python manage.py startapp products python manage.py startapp orders项目结构建议按业务拆 app别把商品、订单、会员全塞进一个views.py。我一般拆成products、orders、members、dashboard几个 app每个 app 只管自己的模型和视图后面维护和答辩讲解都轻松。3.2 Django侧模型、迁移和Admin后台模型定义好之后先做迁移再建后台python manage.py makemigrations python manage.py migrate python manage.py createsuperuserDjango Admin 是这类管理系统里最省事的功能简单注册就能得到一套可用的增删改查界面。但直接用默认配置会显得很业余我会在admin.py里做点定制from django.contrib import admin from .models import Product, Stock admin.register(Product) class ProductAdmin(admin.ModelAdmin): list_display (name, barcode, price, category, status, stock_quantity) search_fields (name, barcode) list_filter (category, status) list_per_page 20 def stock_quantity(self, obj): return getattr(obj.stock, quantity, 0) stock_quantity.short_description 库存这样后台列表能直接看到价格、分类、上下架状态和当前库存搜索框可以按名称或者条码搜管理几十上百个商品时体验完全不一样。商品图片的上传路径开发阶段我放在media/products/本地用 Django 自带的serve提供访问生产环境再交给 Nginx。提示Django 的on_deletemodels.PROTECT一定要想清楚再用。如果分类下还有商品直接删分类会报错这其实是保护数据的好设计别在界面上感觉“怎么删不掉”而是先处理下级数据。3.3 Flask侧轻量设备接口与数据查询Flask 服务我单独放在device_server/目录不塞进 Django 项目里当作一个独立进程跑。它的职责是接收设备上报的事件、按条码查商品、提供自助终端需要的接口。最小代码如下from flask import Flask, jsonify, request from flask_sqlalchemy import SQLAlchemy from flask_cors import CORS app Flask(__name__) app.config[SQLALCHEMY_DATABASE_URI] sqlite:///../db.sqlite3 CORS(app) db SQLAlchemy(app) class Product(db.Model): __tablename__ products_product id db.Column(db.Integer, primary_keyTrue) name db.Column(db.String(100)) barcode db.Column(db.String(64), uniqueTrue) price db.Column(db.Numeric(10, 2)) app.route(/api/product/barcode, methods[GET]) def get_product(barcode): product Product.query.filter_by(barcodebarcode).first() if not product: return jsonify({code: 404, msg: 商品不存在}), 404 return jsonify({code: 0, data: { id: product.id, name: product.name, price: float(product.price), }}) if __name__ __main__: app.run(host0.0.0.0, port5001, debugTrue)这里有个关键细节Django 建表时默认表名是appname_modelname比如products_product。Flask 侧要用__tablename__指明同一张表否则 SQLAlchemy 会去查一个不存在的product表运行时报 “no such table”。这是双框架共库最容易踩的坑。另外Flask 接口返回的 JSON 里如果有 Decimal 类型直接用jsonify会报错我上面的代码里用float(product.price)做了转换。更严谨的做法是写一个to_dict方法统一处理所有序列化逻辑。3.4 页面衔接模板渲染、静态资源和AJAX自助终端页面和后台页面是两种形态。后台用 Django 模板渲染特点是服务端直接输出 HTML数据不暴露细节管理体验直接自助终端更接近“大屏触摸机”我用一个单独静态页面 AJAX 请求 Flask 接口来渲染商品和购物车。Django 模板里引用静态文件的标准写法是这样{% load static %} img src{% static img/logo.png %} altlogo自助终端页面里扫码枪扫到条码后前端发 GET 请求到 Flask 的/api/product/barcode拿到商品信息渲染到列表同时维护一个本地购物车数组提交订单时再 POST 到 Django 的订单接口。这样购物车的前端操作和后端结算解耦就算页面刷新了只要订单还没提交就不会有脏数据。很多人在这一步纠结“页面到底该放 Django 还是 Flask”。我的经验是管理后台的页面必须放 Django自助终端页面可以放 Django 静态目录也可以放 Flask。放 Flask 的好处是终端页面的接口全在一个源里CORS 都不需要坏处是模板继承和静态资源管理不如 Django 成熟。二选一即可别弄出三五个服务同时跑演示时会非常被动。4. 无人结算的关键模拟订单流程与库存联动4.1 结算流程怎么串无人超市的结算流程比传统收银多了一个“自动核销”的环节。我按演示项目的方式把流程串起来用户扫码或手动添加商品进购物车前端维护商品列表和数量。用户点击“去结算”前端把购物车数据 POST 到 Django 订单接口。后端在校验商品、数量、价格之后检查库存是否足够。库存充足则在事务内扣减库存并创建订单订单状态为待支付。用户点“模拟支付成功”后端更新订单状态为已支付并向设备接口发送“放行”事件。库存不足或商品已下架时订单创建失败前端提示具体原因。第 5 步里“模拟支付成功”很关键。真实系统支付流程复杂但演示项目里可以做一个按钮方便展示整个闭环上线前再替换成真实支付 SDK流程骨架不变。这样既不影响讲解“业务理解”又省掉了支付资质和账号申请这些外部依赖。4.2 库存扣减的并发与幂等别在应用层“先查后改”库存扣减是无人超市管理系统里最容易出 bug 的地方。如果写成“先查库存再判断够不够再减库存”两个人同时下单最后一个商品时两个请求都可能读到库存为 1都判断可以扣结果库存变成 -1。这就是典型的竞态条件。正确做法是在数据库层面加锁把“判断 扣减”放进同一个事务。Django 里用select_for_update()from django.db import transaction from django.db.models import F transaction.atomic def create_order(user_id, items): try: for item in items: stock Stock.objects.select_for_update().get(product_iditem[product_id]) if stock.quantity item[quantity]: raise ValueError(库存不足) stock.quantity F(quantity) - item[quantity] stock.save() # 创建订单和订单明细 except Exception: # 事务回滚会自动释放锁 raise这里用了两个机制select_for_update()对选中的行加锁事务提交前其他事务必须等待F(quantity)让减库存操作在数据库内部完成避免先取回 Python 再赋值导致的旧值覆盖。这两件事合在一起才是一个可靠的扣减方案。Flask-SQLAlchemy 里对应的方法叫with_for_update()用法类似stock Stock.query.filter_by(product_idpid).with_for_update().first()如果用的是 MySQL数据库引擎一定要选 InnoDBMyISAM 不支持行级锁。这是个老生常谈但总有人踩的问题项目里默认建表引擎就是 InnoDB但如果曾经手工建过表最好检查一下。4.3 支付对接和回调处理演示项目的模拟支付足够应付大多数场合但如果你要接真实支付流程需要调整第一步后端生成订单后调用支付接口创建预付单返回支付参数给前端第二步前端拉起微信或支付宝收银台用户完成支付第三步支付平台异步回调你的接口通知支付结果第四步后端回调里验签、更新订单状态、确认扣减库存。回调处理里最怕的是重复通知——支付平台出于送达可靠性会回调多次。所以回调接口必须做幂等进入回调后先查订单状态如果已经是已支付就直接返回成功不再重复处理。演示版本里“模拟支付成功”按钮其实也要顺手做这个判断否则连点两次会出现两次回调处理把库存多扣一遍。注意订单状态机要设计清楚。我的订单状态是 pending待支付、paid已支付、refunded已退款、cancelled已取消。所有状态流转都只在后端做前端永远只传“动作”而不是“状态”这样不容易被绕过去。5. 把“无人”做得更真实识别、消息推送与数据看板5.1 设备接入的思路真正的无人超市里设备层是绕不开的。扫码门禁、自助收银机、摄像头、重力货架都要和后台通信。我在项目里做了一套“设备事件上报”接口设备通过 HTTP POST 上报事件后台落库。比如闸机上报{device_code: gate-01, event: user_enter, user: wx_xxx, ts: ...}系统就把进门记录存到设备事件表同时可以联动会员积分。演示项目没有硬件怎么办我建议写一个设备模拟器脚本每隔几秒随机生成进店、扫码、购买事件往 Flask 接口里发。这样做有两个好处一是开发后端时有稳定的数据源不必等硬件到位二是答辩或给老板演示时屏幕上“实时滚动的进店记录和订单”看起来非常真实效果远超手动点按钮。摄像头识别商品属于另一套系统一般用 OpenCV 或深度学习模型做检测和分类。这块不建议融进 Web 系统里一起开发复杂度太高。合理做法是识别服务把结果转成“商品条码”或“商品 id”再调用现有接口完成结算两个系统之间只有一个薄薄的协议层。5.2 后台实时推送WebSocket怎么做后台页面如果靠定时刷新查数据库来感知新订单和库存预警体验会非常差。更现代的做法是用 WebSocket 做服务端推送。Django 里推荐 ChannelsFlask 里推荐 Flask-SocketIO。以 Flask-SocketIO 为例最小实现是from flask_socketio import SocketIO, emit socketio SocketIO(app, cors_allowed_origins*) def check_low_stock(): # 查询库存低于阈值商品 low_stock_products [] if low_stock_products: socketio.emit(stock_warning, {data: low_stock_products}, broadcastTrue)服务端在检测到低库存、新订单时广播事件后台页面用 Socket.IO 客户端 JS 库监听对应事件然后更新 DOM整个过程不需要刷新页面延迟在毫秒级。Django Channels 的思路差不多用AsyncWebsocketConsumer写一个消费者在group_send里向在线页面推送消息。这里有一个实际经验WebSocket 虽然好用但本地调试时要注意浏览器对http://和ws://的混用没有限制部署到 HTTPS 环境后必须用wss://否则页面会报连接失败。我见过不少项目部署上去后“页面没数据”最后发现是 WebSocket 地址写死了ws://。5.3 数据可视化与商品搜索推荐管理后台的数据看板是无人超市管理系统的门面。我用 ECharts 画销售趋势、商品 TOP10、时段客流数据来源由 Django 接口聚合返回。做销售趋势的接口核心是按时段分组统计订单金额比如按天from django.db.models.functions import TruncDate from django.db.models import Sum from orders.models import Order daily_data ( Order.objects.filter(statuspaid) .annotate(dayTruncDate(created_at)) .values(day) .annotate(totalSum(total_amount)) .order_by(day) )聚合查询写好后前端用 fetch 拉 JSON 再扔给 ECharts几分钟就能出一张能看的趋势图。这里有个优化点日期分组前先确认数据库时区配置否则“今天”的数据可能被分到昨天。商品搜索方面无人超市的商品名往往带品牌词和规格词比如“可口可乐330ml”和“可乐500ml”。如果要做一个“搜可乐能把相关规格都带出来”的搜索不能只做数据库LIKE可以用 jieba 分词后计算文本相似度返回一个相似商品列表。这个思路和很多项目里用“中文关键词精准匹配”做推荐是同一套原理本质就是词频向量算相似度。对演示项目来说做个简单的分词加余弦相似度就够用了数据量大了再考虑 Elasticsearch。6. 常见问题与排查技巧实录6.1 Django静态文件加载不出来这是 Django 新手最高频的问题现象是模板里写了img src{% static img/logo.png %}页面却显示图片裂开。排查顺序我建议固定先看 settings 里有没有STATIC_URL和STATICFILES_DIRS再看模板第一行有没有{% load static %}再看文件路径是否与目录大小写一致。开发阶段 Django 自带 static 处理不用额外配置但部署到生产环境且DEBUGFalse后必须执行python manage.py collectstatic把静态文件集中收集到STATIC_ROOT再交给 Nginx 托管否则样式和图片全会 404。6.2 Flask部署后附件路径错误Windows 上开发、Linux 上部署时附件和上传文件路径最容易出问题。Windows 路径分隔符是\Linux 是/如果代码里写死uploads\imagesWindows 能跑 Linux 就报错。建议统一用pathlibfrom pathlib import Path BASE_DIR Path(__file__).resolve().parent UPLOAD_DIR BASE_DIR / uploads UPLOAD_DIR.mkdir(exist_okTrue)这样代码在不同系统上行为一致也不用再去处理字符串拼接。另一个相关问题是附件路径错误往往不是不存在而是“相对路径 进程工作目录”导致的。服务启动时的工作目录和项目根目录不一致相对路径就会指向错误位置所以只要涉及文件读写一律从BASE_DIR拼绝对路径。6.3 中文乱码与编码问题中文乱码的坑分散在几个地方。数据库层面MySQL 库表字符集要用utf8mb4SQLite 一般没这个问题但写入字符串时注意是 str 不是 bytes接口返回层面Flask 的jsonify默认会把中文转成\uXXXX这种 Unicode 转义序列浏览器里看着是\u554a想直接显示中文可以设置app.json.ensure_ascii False代码文件层面所有源文件统一用 UTF-8 保存Windows 上有些编辑器默认 GBK一不小心就把中文注释写坏了。6.4 ORM查询慢和索引优化小项目数据量不大查询问题不明显但到了几千订单或几万商品时任何一处 N1 查询都会暴露。典型场景是订单列表页循环里查商品信息一次页面请求发出几百条 SQL。解决方案是用select_related和prefetch_related提前取关联数据这是一定要掌握的优化手段。索引方面常用查询字段要建索引商品表的条码字段已经有 unique 索引订单表的order_no建 unique 索引订单表created_at建普通索引因为看板要按时间统计订单明细表的order_id和product_id建联合索引。Django 里在模型字段加db_indexTrue即可迁移后生效。6.5 常见问题速查表问题现象原因解决办法Django 图片不显示img 标签路径 404静态文件目录未配置检查 STATICFILES_DIRS模板加 load staticFlask 部署后附件报错路径不存在系统分隔符/工作目录差异用 pathlib 拼绝对路径接口返回 \uXXXX中文被转义Flask JSON 默认转义设置 ensure_asciiFalse订单库存超卖库存变负数先查后改导致竞态select_for_update 事务后台列表打开很慢多几百条 SQLORM N1 查询select_related / prefetch_relatedWebSocket 连不上页面控制台报错地址写死 ws://HTTPS 下换 wss://这张表是我自己在开发这个项目时不断补充出来的建议你也建一个自己的问题笔记很多坑不是记不住而是遇到时找不到上下文。到这里整个系统的核心链路就完整了后台能管商品库存终端能下单结算接口能接设备上报看板能展示销售数据再配合一套问题排查清单无论你是做课程设计还是实际落地都算有一套能打的方案。我个人实践下来最深的体会是这类系统最花时间的往往不是写代码而是把业务边界想清楚比如订单状态怎么流转、库存锁怎么加、双框架的表归属怎么划。把这些“为什么”想明白了代码量其实不大。最后分享一个比较实用的小技巧项目里准备一个mock_device.py造一批假数据和模拟事件到演示或联调时它就是你的“万能硬件”比临时手填数据高效得多后续真要接真实门禁或收银机也只是改协议层不用动任何核心代码。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。