开篇:为什么改造开源 WMS 而不是从零自研

第 1 / 10 章
开篇:为什么改造开源 WMS 而不是从零自研

我是 sunrise314,一名后端开发。这个系列记录我把一套开源仓储管理系统(GreaterWMS)改造成生产级系统的完整过程:从技术选型、领域模型、出入库全链路,到并发治理、金额精度、批次效期、外部集成、设备对接,最后是报表看板与部署复盘。全部内容基于真实代码,每一章都有可以直接对照的源码。这一章先回答一个最根本的问题:为什么是改造,而不是自研。

一、接到的需求:一个多货主仓库的数字化

事情的开端是一个很典型的需求:客户有一个多货主的仓储场景——同一个仓库里放着好几个货主的货,进出库都要按货主隔离,还要对接他们的 ERP 出入库单据。仓库里有 PDA 扫码枪,未来可能上 AGV 搬运车。预算不支持买一套商用 WMS 的 license,也不想为了"万一以后要改"把命运交到别人的闭源代码手里。

摆在面前的是三条路:

路线优点缺点
从零自研完全可控、技术栈随意仓储业务模型极其庞杂,从库存账到波次拣货全是细节坑,周期以年计
商业 WMS开箱即用、有实施团队价格高,深度定制受制于厂商,二开黑盒
开源改造业务底座现成、代码全透明上游代码质量参差,需要一次"生产化体检"

仓储业务的复杂度经常被低估。表面上就是"进货、出货、记库存",但真正做起来会发现:收货有预装载、预分拣、分拣、上架四个动作;发货有订单下发、波次、拣货、调度、回单;库存不是一个数字,而是在手、在途、待检、冻结、待拣、已拣、缺货等十几个状态并存的账户体系;再加多货主隔离、库位管理、批次效期、盘点流水——这些东西没有实际踩过坑的人写不出来。

自研最大的风险不是写不出代码,而是业务模型的缺口。你可以在三个月内搭出一个能跑的 CRUD 系统,但要花一年以上才能补齐一个成熟 WMS 在十年里沉淀的状态机和多维库存设计。所以我的结论很早就倾向了第三条路:找一个业务模型完整的开源 WMS 做底座,把精力花在生产化改造上。

二、GreaterWMS 是什么:福特亚太售后物流的基因

我选的是 GreaterWMS V2.1.54,Apache 2.0 协议,GitHub 上星标最高的开源 WMS 之一。选它有四个理由。

第一,业务基因正统。 它的业务模型源自福特亚太的售后物流仓储供应链流程,覆盖多仓、波次发货、合并拣货、Milk-Run 等模型。售后物流是供应链里最琐碎的场景之一——零件种类极多、单量碎片化、时效要求高——在这种场景里打磨出来的状态机和流程设计,比"演示级"开源项目扎实得多。

第二,架构干净。 前后端彻底分离:后端 Django 只出 RESTful API,前端是独立的 Quasar(Vue)工程,编译产物由 Django 根路由托管。按业务领域拆成 32 个 Django app,每个 app 自带 models / views / serializers / urls,职责单一。我数了一下,全项目 34 个数据模型、158 个 API 端点(path),规模适中——大到业务模型完整,又没有膨胀到读不完。

第三,多租户是原生设计。 几乎所有业务表都带 openid 字段,作为租户/货主隔离键,配合自定义认证实现多货主数据隔离。这正是我那个多货主仓库需求的核心,省掉了自己设计租户模型的一大块工作。

第四,多端统一(OneAPP)。 同一套后端 API 服务 Web、PDA、手机 App:前端基于 Quasar 可以编译成 Web、Android 甚至小程序。仓库现场 PDA 扫码作业是刚需,这一点直接命中。

技术栈盘点如下:

层级技术版本
后端框架Django4.1.2(二开目标 4.2 LTS)
REST 框架Django REST Framework3.14
ASGI 服务器daphne / Twisted4.0
API 文档drf-spectacular(Swagger/Redoc)0.26
认证PyJWT(自定义 JWT)2.8
数据处理pandas / openpyxl / numpy—
数据库SQLite(默认)/ MySQL / PostgreSQL—
前端Quasar 1.7+ / Vue 2.6+—

我对 Django 不算狂热粉,但必须承认它在这个场景里是合理的:自带 ORM 和 admin,DRF 的序列化器和 ViewSet 让"一个主数据的 CRUD"几乎零成本,32 个 app 的组织方式也让模块边界非常清晰。对 Java 团队来说可以这样类比:Django app ≈ Spring 的 module,ViewSet ≈ Controller + Service 合体,DRF Serializer ≈ DTO + Converter,ORM 迁移 ≈ Flyway(只是生成方向相反——它从模型类生成迁移文件)。

三、先跑起来:Python 3.13 的适配实录

评估一个开源项目,我的习惯是先在本机把它完整跑起来,再谈架构。这一步就遇到了第一个坑:官方 requirements.txt 面向 Python 3.8,而我的环境是 Python 3.13——老项目的依赖锁在三年前,直接装会有一片兼容性报错。

我做了一版依赖适配(requirements-py313.txt),核心升级:

依赖原版适配后
Django4.1.24.2.19(LTS)
djangorestframework3.143.15.2
numpy1.232.1.3
pandas1.52.2.3
asgiref 等—补齐 `asgihandler` 等缺失项

然后按以下步骤初始化:

# 1) 创建虚拟环境并安装适配版依赖
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements-py313.txt

# 2) 生成业务表迁移(源码不含迁移文件,必须执行)
.\.venv\Scripts\python.exe manage.py makemigrations

# 3) 应用迁移建库
.\.venv\Scripts\python.exe manage.py migrate

# 4) 用 daphne(ASGI 服务器)启动
.\.venv\Scripts\python.exe -m daphne -b 0.0.0.0 -p 8008 greaterwms.asgi:application

有一个对老 Django 项目很关键的细节:源码仓库里不含 migrations 目录,作者预期你用他发布的某个数据库快照。这意味着如果你自己建库,必须先 makemigrations 生成全部 29 个 app 的 0001_initial,再 migrate。跳过第一步直接 migrate 会报"no migrations"。

跑通之后的验证结果:

检查项结果
`manage.py check`✅ 0 issues
makemigrations✅ 29 个 app 的 0001_initial
migrate✅ 26 个业务表 + Django 内置表全部建立
daphne 启动✅ 监听 8008
`GET /api/`✅ HTTP 200,OpenAPI Schema 365KB
`GET /api/docs/`(ReDoc)✅ HTTP 200
`POST /login/`✅ HTTP 200,错误场景返回 `{"code":"1011","msg":"User Name Or Password Error"}`

POST /login/ 故意用了错误密码来验证——返回的是业务层的错误码 JSON 而不是异常堆栈,说明认证、序列化、异常处理三条管线都活着。这个"先破坏再确认"的冒烟习惯,比单纯看到一个 200 可信得多。

另外 drf-spectacular 自动生成的 OpenAPI 文档开箱可用:/api/debug/ 是 Swagger UI 交互调试,/api/docs/ 是 ReDoc 阅读视图,/api/ 直接吐 365KB 的 Schema JSON。158 个端点全部有文档,这对二开来说是巨大利好——你不用通读代码就能摸清 API 全貌。

四、开源代码的真实成色:优点与生产风险清单

跑起来只是及格线。真正决定"能不能改造成生产系统"的是代码成色。我把 32 个 app 通读了一遍,结论是:业务模型优秀,工程化欠账不少。

先说优点。asn(收货)和 dn(发货)两个核心 app 的状态机设计相当成熟:主单(List)+ 明细(Detail)结构,通过 *_status 字段驱动流转,配合独立的动作端点实现仓储作业流程。入库是 asnlist → asndetail → preload(预装载)→ presort(预分拣)→ sorted(分拣)→ movetobin(上架),出库是 dnlist → dndetail → neworder → orderrelease(波次下发)→ pickinglist(拣货单)→ picked → dispatch(车辆调度)→ pod(回单)。这条主链路完整覆盖了真实仓库的作业动作,是整个项目最值钱的部分。

库存设计也很见功力:stocklist 一张表维护 15 个维度的库存数量(在手、可订购、已订购、待检、冻结、破损、在途收、在途发、待装载、待分拣、已分拣、待拣、已拣、缺货……),所有出入库动作本质上是这些维度之间的转移。这套"库存账户"模型在第二章我会展开讲。

再说欠账。生产化体检之后,我列出了这张风险清单:

项现状风险
SECRET_KEY`get_random_secret_key()` 每次启动随机生成重启后所有 JWT 失效,用户全被登出
DEBUG`True`生产环境泄露敏感信息
ALLOWED_HOSTS`['*']`Host 头攻击风险
CSRF 中间件被注释纯 API 场景可接受,但需评估
CORS`CORS_ORIGIN_ALLOW_ALL = True`生产应收紧为白名单
JWT 有效期20 年会话过长,等同没有过期
数据一致性库存增减依赖应用层多次 save并发下超卖、部分更新脏数据
金额字段`FloatField`二进制浮点误差,财务不可接受
限流实现模块级全局变量存时间戳多请求并发串扰

这份清单基本就是我的二开路线图。逐条应对的方案会在后面章节展开,这里先给结论:

  • SECRET_KEY / DEBUG / ALLOWED_HOSTS / CORS / JWT 属于配置层加固,一次性解决(第十章部署复盘统一收口);
  • 数据一致性是硬骨头:入库 9 处、出库 5 处写操作要事务化,再加 12 处 select_for_update() 行锁(第三、四、五章);
  • 金额精度要做 Float → Decimal 的存量迁移,顺带修两个隐藏 bug(第六章);
  • 业务增强按客户场景补批次效期(第七章)、外部系统对接(第八章)、IoT 设备(第九章)、报表看板(第十章)。

五、为什么这个改造过程值得写成系列

市面上讲 Django 的教程很多,讲 WMS 的文章也不少,但"把一个真实开源项目从 demo 改造成生产系统"的完整记录很少。这两者之间的差距恰恰是最有信息量的部分:

  • 你会遇到别人的设计决策并被迫理解它——为什么库存要拆 15 个维度,为什么上架和分拣是两个动作;
  • 你会遇到真实的技术债——全局变量存限流时间戳、异常处理器弄丢 HTTP 状态码、SQLite 下 select_for_update 静默失效;
  • 你要做存量数据迁移——Float 转 Decimal 不只是改字段类型,要考虑已有数据怎么换算;
  • 你要做边界设计——外部系统重复推送同一张入库单怎么办,设备事件乱序到达怎么办。

这些都不是教程会教你的,但每一件都会在生产环境咬你一口。

本系列的章节安排如下,全部基于改造中的真实代码:

章节主题核心内容
02领域模型34 张表、openid 多租户、15 维库存账户、ASN/DN 状态机
03入库全链路preload → presort → sorted → movetobin,9 处事务化
04出库全链路波次下发、库位分配、拣货、回单、缺货挂单
05并发治理`select_for_update` 行锁、数据库方言的坑
06金额精度Float → Decimal 存量迁移,顺带修两个 bug
07批次效期医药冷链场景的到期预警设计
08集成层X-API-KEY 认证、幂等建单、ERP/OMS/TMS 对接
09IoT 设备适配器模式接入 AGV / 称重 / 立体库
10报表与部署四类经营报表、ECharts 仪表盘、安全加固清单

如果你也面对"要不要用开源 WMS 做底座"的决策,或者正在维护一个 Django 老项目,这个系列应该能给你一张相对完整的地图。下一章,我们从整个系统最核心的资产开始:那 34 张表到底怎么读。