资讯详情

资讯详情

OpenStock自建指南:Docker+PostgreSQL搭建开源库存管理系统

1. OpenStock 到底是什么为什么值得动手搭一套OpenStock 这个词最近在技术圈里被反复提起很多人第一次听到会以为是某个股票行情软件或者量化交易平台。实际上它更像是一类开源的库存与物资管理系统的统称——你可以把它理解成一个“自己说了算”的进销存中枢用来管理商品、物料、设备、耗材等一切需要记录“有多少、在哪里、谁在用”的东西。它解决的问题非常具体中小团队用不起昂贵的商业 ERP用 Excel 又管不住多人协作和实时同步于是需要一个轻量、可自建、可二次开发的方案。我最早接触这类系统是因为帮一个做硬件的小团队整理物料。他们有几百种元器件采购、领用、归还全靠一张共享表格结果经常出现“账上有、柜子里没有”的情况。后来我们决定自己搭一套 OpenStock 类的系统核心诉求就三个数据存在自己手里、多人能同时操作、能按自己的业务改字段。这套东西适合谁呢适合有一定 Linux 基础、懂点 Docker、愿意折腾的开发者或运维也适合小团队里负责内部工具的那个人。如果你完全没碰过命令行跟着做也能跑起来但遇到问题排查会吃力一些。需要先说明的是OpenStock 并不是一个官方唯一指定的软件名市面上叫这个名字或类似名字的开源项目有好几个方向。我下面讲的这套搭建思路是基于“自建库存管理服务”这个最常见、最实用的场景来展开的选型上会偏向成熟、社区活跃、文档齐全的方案。你完全可以根据自己团队的技术栈替换其中的组件核心逻辑是相通的。2. 搭建前的整体设计与选型思路2.1 为什么优先选“自建”而不是买 SaaS很多人第一反应是找个在线的库存管理工具注册就能用为什么还要自己搭我踩过的坑是这样的早期用某在线表格管物料免费版有行数限制团队超过五个人就要升级更麻烦的是有些物料的供应商信息、成本价属于敏感数据放在别人的服务器上总归不踏实。自建 OpenStock 的最大好处是数据主权——数据库在你自己的机器上备份策略你说了算想导出什么格式就导出什么格式。另一个关键点是可定制。标准 SaaS 的字段是固定的比如“商品名称、数量、价格”但实际业务里你可能需要“封装形式、耐压值、存放货架号”这些字段。自建系统可以直接改数据库表结构或者通过配置扩展不用等产品经理排期。当然代价是你得自己负责运维服务器挂了要自己修这也是为什么我建议用 Docker 来降低维护成本。2.2 技术栈选型为什么是 Docker PostgreSQL 轻量后端整套方案我推荐的技术组合是Docker Compose 编排、PostgreSQL 做数据库、后端用 Python FastAPI 或 Node.js、前端用现成的管理面板。逐个说下理由。Docker 的价值在于环境隔离和一键迁移。库存系统通常不是天天改代码但依赖的数据库、缓存、Web 服务版本容易冲突。用 Docker 把每个组件打包成容器换服务器时只需要把docker-compose.yml和数据库卷拷过去几分钟就能恢复。我实测过从一台云主机迁移到另一台停机时间不到十分钟。数据库选 PostgreSQL 而不是 MySQL主要看中它对 JSON 字段和复杂查询的支持更好。库存场景里经常要存“扩展属性”比如某个物料的规格参数是变动的用 JSONB 字段存就很灵活查询时还能建索引。MySQL 也能做但 PostgreSQL 在这块更顺手。如果团队已经有 MySQL 运维经验换成 MySQL 也完全可行逻辑不变。后端语言看团队熟悉度。FastAPI 写起来快自带接口文档适合快速迭代Node.js 生态里库存管理的轮子更多有些现成的开源项目可以直接拿来改。我下面以 FastAPI 为例因为它的代码可读性强方便你后续自己加功能。2.3 部署架构单机还是多节点对于大多数中小团队单机 Docker 部署就够了。一台 2 核 4G 的云主机跑数据库、后端、前端三个容器支撑几十个人同时操作毫无压力。库存系统的并发量通常很低瓶颈不在 CPU 而在数据库的写入锁。如果团队规模上百人或者需要高可用再考虑把数据库单独拆出来加一个只读副本做报表查询。网络方面如果只在公司内网使用直接绑定内网 IP 即可如果需要外网访问建议加一层反向代理并配置 HTTPS。这里不展开具体工具原则是不要直接把数据库端口暴露到公网这是最基本的安全底线。3. 核心细节解析与实操要点3.1 数据库表结构怎么设计才不返工库存系统的表结构是地基设计不好后面改起来非常痛苦。我建议至少包含这几张核心表物料表、库存表、出入库记录表、用户表、操作日志表。物料表存静态信息名称、编码、规格、单位库存表存动态数量当前数量、锁定数量、货架位置出入库记录表存每一次变动的流水。这里有个关键设计点库存数量不要直接存在物料表里。我见过有人图省事在物料表加一个quantity字段每次出入库直接加减。这样做的问题是一旦出现并发操作或者程序 bug数量对不上时你根本查不出是哪一笔出的错。正确做法是库存表只存“当前快照”所有变动都写流水需要核对时用流水累加来验证快照。这多写一张表的成本换来的是可追溯性。另一个细节是物料编码的唯一性。建议用“类别前缀 自增序号”的方式生成比如ELEC-0001表示电子类第一个物料。不要用随机字符串人工核对时太痛苦。编码一旦生成就不要改因为很多地方会引用它。3.2 出入库逻辑的并发处理库存系统最怕的就是两个人同时领用同一个物料导致超卖。比如仓库里只剩 1 个A 和 B 同时提交领用申请如果程序先查再改两个请求都查到“有 1 个”然后都扣减结果变成 -1。解决办法是在数据库层面加行锁用SELECT ... FOR UPDATE把那一行锁住等第一个事务提交后再让第二个事务读取。具体到代码里出入库操作必须放在一个数据库事务里先锁库存行检查数量是否足够足够则更新快照并插入流水不足则回滚并返回错误。这个逻辑听起来简单但很多开源项目为了性能会省略锁导致数据不准。你自己搭的时候一定要确认这一点或者干脆在应用层加一个队列把同一物料的出入库请求串行化。注意行锁会带来等待如果某个物料的出入库非常频繁可能成为瓶颈。实际业务里这种极端情况很少真遇到了再考虑用消息队列削峰。3.3 权限与操作日志不能省小团队容易觉得“都是自己人不用搞权限”但库存数据一旦被误删或误改恢复起来很麻烦。建议至少分三种角色管理员可增删改所有数据、操作员只能出入库和查询、只读用户只能看报表。权限控制可以在后端接口层做每个请求校验用户角色。操作日志要记录“谁、什么时间、对哪个物料、做了什么操作、改前改后的值”。这张表只增不改定期归档。我遇到过有人误把某个物料的数量从 100 改成 1因为有日志五分钟就定位并恢复了。没有日志的话只能全量回滚数据库损失更大。4. 手把手实操从零跑起一套 OpenStock4.1 环境准备与依赖安装先准备一台 Linux 服务器Ubuntu 22.04 或 Debian 12 都可以。最低配置 2 核 4G硬盘 40G 起步因为日志和备份会占空间。用 root 或 sudo 权限执行以下步骤。第一步安装 Docker 和 Docker Compose。官方脚本最省事curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh sudo apt-get install docker-compose-plugin -y装完后验证版本docker --version docker compose version如果显示版本号就说明成功了。这里有个坑有些云主机的默认源里 Docker 版本很老用官方脚本能避免这个问题。另外如果服务器在国内拉取镜像可能较慢可以配置镜像加速具体方法各云厂商文档里都有这里不展开。4.2 编写 docker-compose 编排文件新建一个目录openstock在里面创建docker-compose.yml。下面是我实际用的一份配置你可以直接抄version: 3.9 services: db: image: postgres:15 restart: always environment: POSTGRES_USER: openstock POSTGRES_PASSWORD: 换成你的强密码 POSTGRES_DB: openstock volumes: - ./pgdata:/var/lib/postgresql/data ports: - 127.0.0.1:5432:5432 api: build: ./api restart: always depends_on: - db environment: DATABASE_URL: postgresql://openstock:换成你的强密码db:5432/openstock ports: - 127.0.0.1:8000:8000 web: image: nginx:alpine restart: always volumes: - ./web:/usr/share/nginx/html ports: - 80:80几个关键点解释下。数据库端口绑定127.0.0.1而不是0.0.0.0意思是只允许本机访问外网连不上这是安全底线。pgdata目录挂载到宿主机容器删了数据还在。api服务用build而不是现成镜像因为你要自己写业务代码。4.3 后端接口的最小实现在api目录下创建Dockerfile和main.py。Dockerfile内容FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . CMD [uvicorn, main:app, --host, 0.0.0.0, --port, 8000]requirements.txt里写fastapi uvicorn psycopg2-binary sqlalchemymain.py先实现一个最核心的出入库接口。为了让你看清逻辑我写得直白一些from fastapi import FastAPI, HTTPException from sqlalchemy import create_engine, text import os app FastAPI() engine create_engine(os.environ[DATABASE_URL]) app.post(/stock/in) def stock_in(item_id: int, qty: int, user: str): with engine.begin() as conn: row conn.execute( text(SELECT quantity FROM stock WHERE item_id:id FOR UPDATE), {id: item_id} ).fetchone() if not row: raise HTTPException(404, 物料不存在) new_qty row[0] qty conn.execute( text(UPDATE stock SET quantity:q WHERE item_id:id), {q: new_qty, id: item_id} ) conn.execute( text(INSERT INTO stock_log(item_id, change, user) VALUES(:id, :c, :u)), {id: item_id, c: qty, u: user} ) return {item_id: item_id, quantity: new_qty}这段代码的关键是FOR UPDATE它会在事务期间锁住那一行防止并发扣减。engine.begin()自动管理事务出错会回滚。实际项目里还要加权限校验、参数验证、错误处理但核心逻辑就是这些。4.4 启动与验证在openstock目录下执行docker compose up -d第一次会拉取镜像并构建等几分钟。然后用docker compose ps看三个容器是否都是running状态。接着测试接口curl -X POST http://127.0.0.1:8000/stock/in?item_id1qty10usertest如果返回{item_id:1,quantity:10}说明链路通了。你可以再发一次同样的请求数量应该变成 20。这时候去数据库里查stock_log表应该有两行记录。到这一步最核心的出入库功能就跑起来了。提示生产环境一定要把user参数换成从登录态里取不能让前端随便传否则日志就失去意义了。5. 常见问题与排查技巧实录5.1 容器启动失败怎么快速定位最常见的报错是数据库连不上。先看docker compose logs db如果显示database system is ready to accept connections就说明数据库没问题。再看docker compose logs api如果报connection refused通常是DATABASE_URL里的主机名写错了。在 Compose 网络里服务名就是主机名所以应该写db而不是localhost。另一个高频问题是端口被占用。比如 80 端口已经被宿主机上的 Nginx 占了web容器就起不来。用ss -tlnp | grep 80查一下换个端口映射即可。我习惯把外部端口设成 8080 之类不常用的避免冲突。5.2 数据对不上时的排查顺序如果发现库存数量和实际不符按这个顺序查先看stock_log表里该物料的流水总和和stock表里的快照对比。如果流水总和等于快照说明数据没丢只是实物和账面对不上那是流程问题如果流水总和不等于快照说明有程序 bug 或者有人直接改了数据库。这时候用git log看最近有没有改过出入库代码重点检查事务是否完整。我遇到过一次快照比流水多 5 个的情况查了半天发现是有人手动执行了UPDATE stock SET quantityquantity5没写日志。所以再次强调所有库存变动必须走接口禁止直接操作数据库。5.3 性能问题的常见表现与对策库存系统一般不会遇到性能瓶颈但如果物料数量上万、流水上百万查询会变慢。对策是给stock_log表的item_id和created_at建联合索引报表查询走索引扫描。另外历史流水可以按月归档到单独的表主表只保留最近三个月的数据。这些优化等真正遇到慢查询再做也不迟过早优化反而增加复杂度。下面这张表整理了我踩过的坑和对应解法你可以存下来备用问题现象可能原因解决方向容器反复重启数据库密码不匹配检查环境变量和 pgdata 是否残留旧数据接口返回 500数据库连接池耗尽调大 SQLAlchemy 的 pool_size数量出现负数缺少行锁或事务不完整确认出入库用了 FOR UPDATE日志表暴涨没有归档策略按月分区或定期删除旧数据外网无法访问端口只绑定了 127.0.0.1加反向代理不要直接暴露后端5.4 备份与恢复的实操建议数据库备份用pg_dump最稳。在宿主机加一个定时任务每天凌晨执行docker compose exec -T db pg_dump -U openstock openstock backup_$(date %F).sql保留最近 30 天的备份旧的自动删除。恢复的时候先停掉api容器把 SQL 文件导入再启动。我实测过整个恢复过程不到两分钟。注意备份文件不要放在同一块硬盘上最好同步到另一台机器或对象存储防止硬盘故障导致数据全丢。6. 后续扩展与个人经验分享这套基础版跑通之后可以按需加功能。比如加一个低库存预警当某个物料数量低于阈值时发通知加一个扫码出入库用手机摄像头扫条码减少手工输入错误加一个报表导出按时间段统计领用最多的物料。这些都是在现有接口上扩展不用动核心架构。我自己在实际操作中的体会是库存系统的难点从来不在技术而在流程约束。工具再好如果团队成员图省事绕过系统直接改数据库数据迟早会乱。所以搭建的同时一定要定规矩所有变动走接口、所有操作留日志、定期对账。技术方案只是把规矩落地的载体。最后再分享一个小技巧初期可以把物料编码打印成二维码贴在货架上出入库时扫码操作比手工输入快得多也几乎不会出错。这个投入很小但效果立竿见影。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →