库存管理系统教程:FastAPI + SQLite 出入库与 Excel 导入导出

仓库里几百种物料,产品编码、供应商、批次、存放地点、剩余数量,靠 Excel 表格传来传去,入一次库、出一次库都要手动改数,改错了还没记录可查。inventory-management 就是一个轻量级的库存管理系统:FastAPI + SQLite 后端、单页 Web 前端,库存台账、出入库操作、出入库流水、Excel 导入导出、JWT 登录,一个容器就能跑起来。这篇教程按源码把它的功能、数据模型、接口和部署完整讲一遍。

先说明一个情况:这个仓库的 README.md 是空的(0 字节),下面的内容全部以仓库源码为准——backend/models.py(数据模型)、backend/routers.py(全部接口)、backend/database.py、backend/requirements.txt 和 backend/docker-compose.yml 都逐一核对过;唯一没能确认的是 backend/main.py 的具体内容(拉取时网络失败),所以直接运行的入口命令以仓库内 main.py 为准,Docker 部署方式是完整验证过的。

一、项目简介

inventory-management 是 git.cnbugs.com 上 AI-Agent 组织的一个开源项目(仓库地址 https://git.cnbugs.com/AI-Agent/inventory-management),定位是小型仓库/备件库的库存管理系统。它不像 WMS(仓储管理系统)那样管库位、波次和拣货,而是聚焦最核心的三件事:库存台账、出入库流水、Excel 导入导出。

从提交记录看,项目 2026 年 5 月 29 日初版,6 月 1 日加上了登录功能(JWT 认证、注册、登录、前端登录页面)。数据模型是围绕“产品编码 + 批次”设计的:每种物料按产品编码和批次建台账,记录供应商、外壳与标签备注、当前剩余数量和存放地点;每次出入库都写一条流水,数量不够出库时直接拒绝。

二、功能特性

  • 库存台账:产品编码、供应商、外壳与标签备注、批次、当前剩余数量、存放地点,支持分页和多字段搜索。
  • 出入库操作:入库增加数量、出库扣减数量,出库时校验库存是否充足,不足直接拒绝并返回当前剩余数。
  • 出入库流水:每次操作都记一条记录(产品编码、类型、数量、备注、操作时间),可查可清空。
  • Excel 导入:上传 Excel 批量导入,按“序号”或“产品编码+批次”去重更新,导入结果返回新增/更新条数和错误行。
  • Excel 导出:库存表和出入库记录都能导出带样式的 Excel(蓝底白字表头、列宽已调好)。
  • JWT 登录认证:注册、登录、查看当前用户,Token 有效期 24 小时,所有业务接口都要带 Token。
  • 单页前端:backend/static/index.html 一个页面完成登录、台账、出入库和导入导出。

三、技术架构与数据模型

组件技术选型说明
Web 框架FastAPI 0.115.0REST API + 自动生成的 /docs 文档
ORMSQLAlchemy 2.0.35模型在 backend/models.py
数据库SQLite文件 backend/data/inventory.db,无外部依赖
认证JWT(HS256,24 小时)python-jose 签发,OAuth2 Bearer 携带
Excelopenpyxl 3.1.5导入解析 + 导出生成
前端静态单页 static/index.htmlHTML 占仓库代码量的 59.2%,随后端一起部署
部署Docker宿主 8011 端口映射容器 8000 端口

数据库里有三张表,结构如下(以 models.py 为准):

表字段说明
user(用户表)id、username(唯一)、password_hash、nickname、created_at密码存 SHA256 哈希,登录时比对哈希值
inventory(库存表)id、cInvCode(产品编码)、supplier(供应商)、casing_label_remark(现外壳&标签&备注)、batch(批次)、current_remaining(当前剩余数量)、storage_location(存货地点)、created_at、updated_at核心台账,产品编码 + 批次是业务上的唯一组合
transaction_log(出入库记录表)id、inventory_id、cInvCode、type(in/out)、quantity、remark、created_at每次出入库一条流水,数量变动可追溯

四、适用场景

  • 备件库/耗材库:电子元器件、维修备件、办公耗材,几百到几千种物料,Excel 管不过来。
  • 小型仓库:不需要 WMS 的库位和波次管理,只要台账清楚、出入库有记录。
  • 实验室物料管理:试剂、样品按批次管理,剩余数量实时可见。
  • Excel 迁移过渡:已有 Excel 台账的,直接导入就能用,不用重新录入。

五、环境准备与部署

部署只需要 Docker。仓库里的 backend/docker-compose.yml 已经写好:容器名 inventory-management,宿主 8011 端口映射容器 8000 端口,./data 和 ./static 挂载进容器,时区 Asia/Shanghai,restart: unless-stopped。

git clone https://git.cnbugs.com/AI-Agent/inventory-management.git
cd inventory-management/backend

# Docker 部署(推荐)
docker compose up -d --build

# 查看容器状态
docker ps --filter name=inventory-management
docker logs inventory-management --tail 50

# 访问:http://<服务器IP>:8011
# API 文档:http://<服务器IP>:8011/docs

本地直接运行也可以(入口以仓库内 main.py 为准):

cd inventory-management/backend

# 建虚拟环境并装依赖
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

# 初始化数据库表(只建表,不建任何账号)
python3 init_db.py
# 输出:数据库表创建成功!

# 启动服务(端口以 main.py / docker-compose 为准,容器内是 8000)
uvicorn main:app --host 0.0.0.0 --port 8000

数据持久化注意两点:SQLite 文件在 backend/data/inventory.db,Docker 部署时 ./data 目录已挂载出来,备份直接拷这个文件就行;./static 是前端页面,也挂载出来了,改前端不用重新构建镜像。

六、首次使用:注册第一个账号

这个系统没有默认管理员账号。init_db.py 只建表,不创建任何用户,所以第一次使用要自己注册一个账号——注册接口是开放的(不需要先登录),注册完直接用这个账号登录。

# 注册第一个账号(username 唯一,重复注册会报 400 用户名已存在)
curl -s -X POST "http://localhost:8011/api/auth/register?username=admin&password=Admin@2026&nickname=管理员"

# 返回:{"message":"注册成功"}

# 登录拿 Token(OAuth2 表单提交)
curl -s -X POST http://localhost:8011/api/auth/login \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "username=admin&password=Admin@2026"

# 返回示例
# {"access_token":"eyJhbGciOi...","token_type":"bearer","username":"admin","nickname":"管理员"}

export TOKEN="eyJhbGciOi..."

# 验证当前用户
curl -s http://localhost:8011/api/auth/me \
  -H "Authorization: Bearer $TOKEN"

Token 有效期 24 小时,过期重新登录。所有业务接口(库存、出入库、导入导出)都必须在请求头里带 Authorization: Bearer <Token>,没带会返回 401“无法验证凭据”。

七、使用示例:库存管理

# 新增一条库存
curl -s -X POST http://localhost:8011/api/inventory \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "cInvCode": "R1001-10K",
    "supplier": "风华高科",
    "casing_label_remark": "0805 贴片电阻,防静电袋装",
    "batch": "20260601",
    "current_remaining": 5000,
    "storage_location": "A区-3排-2层"
  }'

# 查库存列表(分页 + 搜索,搜产品编码/供应商/批次/存放地点/备注)
curl -s "http://localhost:8011/api/inventory?page=1&page_size=20&search=R1001" \
  -H "Authorization: Bearer $TOKEN" | python3 -m json.tool
# 返回 {"total": 1, "items": [...]}

# 更新一条库存(只传要改的字段)
curl -s -X PUT http://localhost:8011/api/inventory/1 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"storage_location": "A区-3排-5层"}'

# 删除一条库存
curl -s -X DELETE http://localhost:8011/api/inventory/1 \
  -H "Authorization: Bearer $TOKEN"

搜索是模糊匹配,search 一个关键词会同时在产品编码、供应商、批次、存放地点和备注五个字段里找,日常查料一个关键词就够了。分页参数 page 从 1 开始,page_size 最大 100。

八、使用示例:出入库操作

# 入库 200 个(current_remaining 增加)
curl -s -X POST http://localhost:8011/api/stock/operation \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"inventory_id": 1, "type": "in", "quantity": 200, "remark": "采购到货 PO20260601"}'
# 返回 {"message":"操作成功","current_remaining":5200}

# 出库 50 个(current_remaining 扣减)
curl -s -X POST http://localhost:8011/api/stock/operation \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"inventory_id": 1, "type": "out", "quantity": 50, "remark": "维修领用"}'

# 出库超库存会被拒绝(返回 400)
curl -s -X POST http://localhost:8011/api/stock/operation \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"inventory_id": 1, "type": "out", "quantity": 99999, "remark": "超量测试"}'
# 返回 {"detail":"库存不足,当前剩余: 5150"}

# 查出入库流水(分页 + 搜索产品编码/备注)
curl -s "http://localhost:8011/api/stock/logs?page=1&page_size=20" \
  -H "Authorization: Bearer $TOKEN" | python3 -m json.tool

# 清空全部出入库记录(谨慎操作,不可恢复)
curl -s -X DELETE http://localhost:8011/api/stock/logs \
  -H "Authorization: Bearer $TOKEN"

注意两个细节:出库校验是“当前剩余 < 出库数量就拒绝”,所以永远不会出现负库存;每次出入库成功都会写一条流水到 transaction_log,数量变动全程可追溯。但 DELETE /api/stock/logs 是清空全部流水且不可恢复,点之前先导出备份。

九、Excel 导入导出

已有 Excel 台账的直接导入,不用重新录入。导入逻辑是:文件必须是 .xlsx 或 .xls;程序在前 5 行里找表头,必须包含“产品编码”列;表头支持两个版本(“现外壳&标签&备注”和“外壳&标签&备注”都认);数据从第 2 行开始解析。

# 导出库存为 Excel(表头蓝底白字,列宽已调好,文件名带时间戳)
curl -s -o 库存数据_20261109.xlsx \
  -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8011/api/inventory/export"

# 导出出入库记录为 Excel
curl -s -o 出入库记录_20261109.xlsx \
  -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8011/api/stock/export"

# 从 Excel 导入库存
curl -s -X POST http://localhost:8011/api/inventory/import \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@库存台账.xlsx"
# 返回示例:{"message":"导入完成:新增 120 条,更新 15 条","imported":120,"updated":15,"errors":[]}

导入的去重规则要特别注意:有“序号”列时按序号查,存在就更新整行,不存在就按该序号新增;没有序号时按“产品编码 + 批次”去重,存在就更新(空字段不覆盖原值),不存在就新增。导入结果会返回新增条数、更新条数和前 10 条错误行(带行号),失败的行不影响其他行。

十、API 端点总览

方法路径说明
POST/api/auth/register用户注册(开放注册)
POST/api/auth/login用户登录(OAuth2 表单,返回 JWT)
GET/api/auth/me获取当前用户信息
GET/api/inventory库存列表(分页 + 搜索)
POST/api/inventory新增库存
PUT/api/inventory/{id}更新库存(只传改动的字段)
DELETE/api/inventory/{id}删除库存
POST/api/stock/operation出入库操作(in/out)
GET/api/stock/logs出入库记录(分页 + 搜索)
DELETE/api/stock/logs清空全部出入库记录
GET/api/inventory/export导出库存为 Excel
POST/api/inventory/import从 Excel 导入库存
GET/api/stock/export导出出入库记录为 Excel

十一、安全注意事项(源码里看到的)

看源码发现两个生产部署前必须处理的安全问题,如实写出来:

  • JWT 密钥写死在源码里:routers.py 里 SECRET_KEY 是硬编码的固定值(原文值这里不贴),任何人拿到源码都能伪造任意用户的 Token。生产部署前必须改掉,改成环境变量读取(os.environ.get("SECRET_KEY")),改完之后已签发的 Token 全部失效,所有用户要重新登录。
  • 密码是 SHA256 哈希:models.py 里 set_password 用的是 hashlib.sha256(无盐),虽然 requirements 里有 passlib[bcrypt]、routers 里也 import 了 CryptContext,但实际存密码走的是 SHA256。SHA256 无盐哈希抗不住彩虹表,安全性弱于 bcrypt,生产使用建议改成 bcrypt(passlib 已经在依赖里了)。
  • 注册接口开放:任何人知道地址就能注册账号,部署到内网后如果还想控制,建议在反向代理层加访问控制,或者把注册改成仅管理员可操作。
  • 不要直接暴露公网:这个系统没有 HTTPS、没有限流、没有审计日志,放公网风险很大,内网用或者反向代理加认证。

十二、常见问题

现象原因与处理
登录返回 401 用户名或密码错误账号不存在或密码错;确认已注册过,密码区分大小写
调接口返回 401 无法验证凭据没带 Token 或 Token 过期(24 小时);重新登录拿新 Token
出库返回 400 库存不足当前剩余小于出库数量;先查 GET /api/inventory 核对剩余数
导入报“未找到有效的表头行”Excel 前 5 行里没有“产品编码”列;按导出文件的表头格式整理
导入后有的行没进来看返回的 errors 数组(带行号,最多 10 条);空产品编码的行会被跳过
容器起来了但打不开页面确认端口映射是 8011,docker logs inventory-management 看启动报错

十三、运维建议

  • 备份就是拷一个文件:SQLite 数据库只有一个文件 backend/data/inventory.db,每天定时 cp 一份到备份目录,或者用 cron + sqlite3 .backup 命令热备。恢复时停掉容器、把文件拷回去、再启动。
  • 先改 SECRET_KEY 再上线:见第十一节,改完要通知所有用户重新登录。
  • 清流水前先导出:DELETE /api/stock/logs 不可恢复,定期导出 Excel 归档后再清。
  • 导入先拿小文件试:第一次导入先用十几行的小表验证表头和去重规则,确认无误再导全量。
# 备份 SQLite(热备,不用停服务)
sqlite3 /opt/inventory-management/backend/data/inventory.db \
  ".backup '/backup/inventory-$(date +%Y%m%d).db'"

# 看一眼库里的数据量
sqlite3 /opt/inventory-management/backend/data/inventory.db \
  "SELECT COUNT(*) FROM inventory; SELECT COUNT(*) FROM transaction_log;"

总结一下:inventory-management 是个“小而完整”的库存系统——台账、出入库、流水、Excel 导入导出、JWT 登录,SQLite 单文件数据库,一个 Docker 容器就能跑。部署记住三点:Docker 映射的是 8011 端口;没有默认账号,第一个账号自己注册;上线前先把 routers.py 里写死的 JWT 密钥换掉。

发表回复

后才能评论