ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Python库存管理系统源码解析:从建表到Flask接口实战

Python库存管理系统源码解析:从建表到Flask接口实战 简介一份基于 Python 的库存管理系统完整源码定位为高校计算机相关专业学生的课程设计/毕业设计参考项目也适合 Python 学习者做综合练习。项目采用前后端分离结构核心逻辑由 Python 实现前端基于 JavaScript/CSS/HTML 构建并附带 Docker 部署配置与依赖清单便于快速部署和二次开发。压缩包共 26 个文件以 py、js、css 为主还包括配置文件、图片和说明文档整体仅 1.35MB轻量易用目录组织清晰含主程序、前端静态资源、服务端模块与说明文档。资源上传前已通过功能测试可直接运行也可在此基础上扩展出入库、统计报表等模块适合作为课设演示或项目立项初稿。该项目尤其适合需要快速搭建项目原型、理解前端与后端协同工作的学习者已有 993 人学习下载是一个经过验证、上手门槛较低的实战样例。1. 基于 Python 的库存管理系统源码先认清 zip 里真正值钱的东西凡是挂着“基于 Python 的库存管理系统源码.zip”的课程设计下载量都高得离谱。原因很现实库存管理这个题目业务边界清楚答辩时好演示老师又常追问数据库设计所以几乎每个班级都有人选。可真正把 zip 解开之后大多数人会发现里面不是一个能直接跑的项目而是一个“源码笔记”的混合素材包有 Flask 工程、SQLite 建表脚本、几个.py文件还夹着十几张截图。这篇文章就按我拿到这种源码包时的处理顺序把它拆成建模、接口、验收功能和交付排错四部分帮你在一小时内跑通而不是把时间浪费在猜测“到底该先运行哪个文件”上。2. 库存管理系统的业务建模与表结构设计任何库存管理系统本质都不是“商品表”和“进出货表”而是一组保证账实一致的数据约束。很多免费 python 源码大全里的库存项目只写两个表然后每次出入库直接UPDATE库存字段这样演示时看似流畅但一旦需要追溯某一次变动对账就全乱。我的习惯是先把核心表和流水表分开宁可代码多写几行也要让老师调库存记录时能看到每次变动的来龙去脉。2.1 从业务对象反推实体关系先列业务对象商品、分类、供应商、入库单、出库单、库存流水、盘点单。课程设计不需要把每个对象都做成表但要保证“一张商品编码对应多条流水”的关系成立。常见做法是product表和stock_transaction表通过product_id关联库存余额不直接写在商品表里而是由流水汇总得到。实际建库时我不建议把category、supplier全拆成外键表除非题目明确要求规范化设计。课程设计体型下一张product表里冗余category字段比三张表 join 更容易在答辩时解释。以下建表语句以 SQLite 为例MySQL 也基本通用。-- product 商品主表 CREATE TABLE IF NOT EXISTS product ( id INTEGER PRIMARY KEY AUTOINCREMENT, sku TEXT NOT NULL UNIQUE, -- 商品编码唯一 name TEXT NOT NULL, -- 商品名称 category TEXT NOT NULL DEFAULT 未分类, safety_stock INTEGER NOT NULL DEFAULT 0, -- 安全库存阈值 expired_at TEXT, -- 过期日期可空 created_at TEXT NOT NULL DEFAULT (datetime(now, localtime)) ); -- stock_transaction 库存流水表每一条记录代表一次库内变化 CREATE TABLE IF NOT EXISTS stock_transaction ( id INTEGER PRIMARY KEY AUTOINCREMENT, product_id INTEGER NOT NULL REFERENCES product(id), change_qty INTEGER NOT NULL, -- 有符号正入库负出库 before_qty INTEGER NOT NULL, -- 操作前库存 after_qty INTEGER NOT NULL, -- 操作后库存 type TEXT NOT NULL CHECK (type IN (IN,OUT,ADJUST)), ref_no TEXT, -- 关联单号如入库单 PO2024001 note TEXT, -- 备注 created_at TEXT NOT NULL DEFAULT (datetime(now, localtime)) );before_qty和after_qty是容易被忽略的字段。很多初版源码只记录change_qty出库时把商品表的库存减掉这导致流水无法校验“操作前的库存是不是真的等于当前库存”。把前后值都写进流水等于给每笔操作拍了快照后续写对账脚本、做回滚、向老师解释并发问题都更有底气。2.2 当前库存视图不用单独维护库存余额表库存余额如果单独建表就必须在每次出入库时同时更新流水和余额表两个写操作放在一个事务里出问题的概率成倍增加。对于课程设计的数据量更稳的是直接用视图实时汇总。CREATE VIEW IF NOT EXISTS stock_current AS SELECT product_id, SUM(change_qty) AS current_stock FROM stock_transaction GROUP BY product_id;这个视图永远和流水表一致不会出现“流水有记录但余额表忘更新”的情况。查询当前库存时LEFT JOIN这个视图没有流水的商品会自动得到NULL再用COALESCE转成 0。注意一个 SQLite 的坑SQLite 的CHECK约束对已经存在的数据和某些依赖子查询的条件不友好别指望用CHECK (current_stock 0)来卡住库存负数必须在 Python 应用层先做判断。2.3 预警参数设计库存系统的核心参数不是数据库配置而是下面这几个业务字段字段类型默认值用途safety_stockINTEGER0当前库存低于该值触发补货提醒expired_atTEXT空商品效期按天计算临期预警categoryTEXT未分类分类汇总报表的统计依据expired_at在部分源码里被设计成expiry_days表示保质期多少天但那样需要知道生产日期才能算过期时间更麻烦。直接用expired_at存一个具体的过期日期查询时用julianday()做日期差省一层换算。3. 用 Python Flask 实现库存出入库的最小读写闭环选 Flask 而不是 Django是为了让源码短、可读性好答辩时能把每一行都讲清楚如果题目要求前后端分离后端也可以换成 FastAPI但 SQLite 设计完全不变。下面的文件结构是最小可运行方案。inventory_system/ ├── app.py # Flask 路由 ├── init_db.py # 初始化表结构 ├── requirements.txt └── inventory.db # 由 init_db.py 自动生成requirements.txt只需要两行Flask2.0 python-dotenv3.1 初始化数据库把建表脚本做成可重复执行init_db.py不需要复杂功能核心是“幂等”两个字。# init_db.py import sqlite3 from pathlib import Path DB_PATH Path(__file__).resolve().parent / inventory.db def get_conn(): 获取数据库连接并启用外键约束 conn sqlite3.connect(DB_PATH) conn.row_factory sqlite3.Row conn.execute(PRAGMA foreign_keys ON) return conn def init_db(): conn get_conn() try: conn.executescript( CREATE TABLE IF NOT EXISTS product (...); CREATE TABLE IF NOT EXISTS stock_transaction (...); CREATE VIEW IF NOT EXISTS stock_current AS ...; ) conn.commit() finally: conn.close() if __name__ __main__: init_db() print(数据库初始化完成)这里的DB_PATH用Path(__file__).resolve().parent定位到当前文件目录而不是用相对路径inventory.db。因为 flask 的运行目录不一定是项目根目录直接用相对路径可能出现“数据库建在别处程序找不到表”的诡异问题。3.2 出入库接口一场事务里完成流水写入核心逻辑在两个 POST 接口里入库、出库。出库必须做库存充足性检查并且这两个操作都要保证“查当前库存”和“写流水”在同一个事务里。# app.py 节选 from flask import Flask, request, jsonify from init_db import get_conn app Flask(__name__) def current_stock(conn, product_id): row conn.execute( SELECT current_stock FROM stock_current WHERE product_id ?, (product_id,) ).fetchone() return row[current_stock] if row else 0 app.post(/stock/in) def stock_in(): data request.get_json(forceTrue) product_id int(data[product_id]) qty int(data[qty]) if qty 0: return jsonify({error: qty must be positive}), 400 conn get_conn() try: conn.execute(BEGIN) before current_stock(conn, product_id) after before qty conn.execute( INSERT INTO stock_transaction (product_id, change_qty, before_qty, after_qty, type, note) VALUES (?,?,?,?,?,?), (product_id, qty, before, after, IN, data.get(note, )) ) conn.commit() except Exception: conn.rollback() raise finally: conn.close() return jsonify({product_id: product_id, before: before, after: after})出库接口结构相同但要在before qty时直接返回 400。app.post(/stock/out) def stock_out(): data request.get_json(forceTrue) product_id int(data[product_id]) qty int(data[qty]) if qty 0: return jsonify({error: qty must be positive}), 400 conn get_conn() try: conn.execute(BEGIN) before current_stock(conn, product_id) if before qty: return jsonify({error: insufficient stock}), 400 after before - qty conn.execute( INSERT INTO stock_transaction (product_id, change_qty, before_qty, after_qty, type, note) VALUES (?,?,?,?,?,?), (product_id, -qty, before, after, OUT, data.get(note, )) ) conn.commit() finally: conn.close() return jsonify({product_id: product_id, before: before, after: after})显式conn.execute(BEGIN)比直接依赖with conn更直观也更容易让答辩老师理解“事务从哪里开始”。change_qty入库传正数出库传负数这样SUM(change_qty)能直接算出当前库存不需要分别统计进出再相减。3.3 查询接口与路由说明app.get(/stock) def list_stock(): conn get_conn() rows conn.execute( SELECT p.id, p.sku, p.name, p.category, p.safety_stock, COALESCE(s.current_stock, 0) AS current_stock FROM product p LEFT JOIN stock_current s ON s.product_id p.id ORDER BY p.id ).fetchall() conn.close() return jsonify([dict(r) for r in rows])LEFT JOIN是关键刚创建还没有流水的商品也要出现在列表里。COALESCE把NULL转成 0避免前端拿到的字段是null还要额外判断。路由方法请求参数说明/stock/inPOSTproduct_id,qty,note入库qty为正数/stock/outPOSTproduct_id,qty,note出库qty为正数内部转负/stockGET无返回当前全部商品库存代码写完后先用 curl 验证一次curl -X POST http://127.0.0.1:5000/stock/in \ -H Content-Type: application/json \ -d {product_id:1,qty:50,note:初盘入库}如果返回{before:0,after:50}说明事务和流水写入都正常。课程设计演示时这一条响应比任何截图都有说服力。4. 课程设计验收视角盘点、临期预警与报表统计老师常问的问题很固定盘点差异怎么处理临期提醒阈值设多少每个分类库存金额怎么算如果源码里只有简单的增删改查这几个问题会暴露短板。下面三个功能是同规模源码包里性价比最高的补强。4.1 盘点差异用 ADJUST 流水冲销盘点时不能直接改任意一条库存记录而是再生成一笔ADJUST类型流水。举例账面库存 45 件实物盘点 42 件少了 3 件就插入一条change_qty -3的调整记录。INSERT INTO stock_transaction (product_id, change_qty, before_qty, after_qty, type, ref_no, note) VALUES (1001, -3, 45, 42, ADJUST, PD20240001, 盘点短少 3 件);这种设计和出入库共用同一张流水表好处立竿见影视图stock_current不需要改盘点后库存自动变成 42报表对账时只要筛选typeADJUST就能看到所有人为调整记录。盘盈则插入正数含义统一为“有符号变动量”。4.2 临期预警阈值设置临期预警的前提是商品有expired_at。如果建表时没加先执行一句ALTER TABLE product ADD COLUMN expired_at TEXT;然后按剩余天数扫描。SELECT id, sku, name, expired_at, CAST(julianday(expired_at) - julianday(now, localtime) AS INTEGER) AS days_to_expire FROM product WHERE expired_at IS NOT NULL AND days_to_expire ? -- ? 传预警天数例如 30 ORDER BY days_to_expire;julianday()把日期转成天数再相减SQLite 能直接算出整数差。这里注意时区julianday(now, localtime)取的是本地时间入库时expired_at也必须存本地日期否则会出现“明明明天到期算出来还有 8 小时”的偏差。预警级别提前天数提示文案普通提醒30黄色显示“临期”紧急提醒7橙色显示“即将过期”已过期0红色销售流程禁用参数不要写死可以做成一张alert_rule表让用户在界面上填 30、7、0。课程设计讲到这里老师会认为你真的考虑过业务配置化的问题。4.3 用 pandas 生成分类库存报表报表部分建议用 pandas 直接读 SQLite只做统计不改数据。import pandas as pd import sqlite3 conn sqlite3.connect(inventory.db) df pd.read_sql_query( SELECT p.category, COUNT(p.id) AS sku_count, COALESCE(SUM(s.current_stock), 0) AS stock_qty, COALESCE(SUM(s.current_stock * p.unit_price), 0) AS stock_amount FROM product p LEFT JOIN stock_current s ON s.product_id p.id GROUP BY p.category , conn) print(df)注意p.unit_price需要在product表里提前准备好否则直接报错。这个查询把分类、SKU 数、总库存件数、库存金额一次算完导出 CSV 或者再用matplotlib画柱状图都很方便。如果画图Windows 下记得设plt.rcParams[font.sans-serif] [Microsoft YaHei]否则输出图表全是方块。5. 源码打包与排错从 zip 解压到环境复现这一部分是课程设计交付时最容易被扣分的地方也是“基于 Python 的库存管理系统源码.zip”这个名字里最需要看清的部分。很多源码不是跑不动而是压根没说明白怎么跑。zip 包拿到手第一件事不是解压而是看压缩包结构unzip -l 库存管理系统.zip重点看第一层路径。如果所有.py文件直接铺在压缩包根目录解压后会和你的其他文件混在一起连requirements.txt都找不到。我一般会重新打个带根目录的包zip -r 库存管理系统.zip inventory_system/ -x */__pycache__/* *.db-x排除缓存和数据库文件避免把__pycache__和本地调试数据发给老师。inventory_system/作为根目录解压后所有文件都待在同一文件夹里后续用 vscode 打开才方便。环境复现的顺序可以固定在 README 里python -m venv venv source venv/bin/activate # Windows 使用 venv\Scripts\activate pip install -r requirements.txt python init_db.py python app.py如果启动失败按三个检查点排查。先看python --version课程设计环境建议保持 Python 3.8 以上低版本对app.post这种写法不兼容再看虚拟环境是否激活pip list里如果没有 Flask就是激活步骤漏了最后看init_db.py是否生成inventory.db没有表说明建表脚本根本还没执行。常见错误原因快速修复sqlite3.OperationalError: no such table: product没运行init_db.py在项目根目录执行python init_db.pyModuleNotFoundError: No module named flask依赖没装进当前环境激活虚拟环境后重装requirements.txtPort 5000 is already in use端口被占用app.run(port5001)或关掉旧进程POST 请求返回 307/404路由写法与 Flask 版本不匹配统一改用app.post或app.route(..., methods[POST])最后一个技巧路由里返回 JSON 时不要直接返回字符串用jsonify包一层并在接口开头检查qty是否为正数。这样老师测试负数入库时你给出的不是一声毫无信息的 500而是一段能解释的 400 错误提示。细节做到这一步课程设计的代码质量分基本就稳了。本文还有配套的精品资源点击获取
返回列表