目录
- 电商平台商品管理与库存系统API设计
-
- [1. 引言](#1. 引言)
- [2. 系统需求分析](#2. 系统需求分析)
-
- [2.1 功能需求](#2.1 功能需求)
- [2.2 非功能需求](#2.2 非功能需求)
- [3. 系统架构设计](#3. 系统架构设计)
-
- [3.1 整体架构图](#3.1 整体架构图)
- [3.2 技术栈选择](#3.2 技术栈选择)
- [4. 数据模型设计](#4. 数据模型设计)
-
- [4.1 实体关系图](#4.1 实体关系图)
- [4.2 核心表设计](#4.2 核心表设计)
-
- [4.2.1 商品表 (products)](#4.2.1 商品表 (products))
- [4.2.2 SKU表 (skus)](#4.2.2 SKU表 (skus))
- [4.2.3 库存表 (inventory)](#4.2.3 库存表 (inventory))
- [5. 核心算法与公式](#5. 核心算法与公式)
-
- [5.1 库存计算](#5.1 库存计算)
- [5.2 库存预警](#5.2 库存预警)
- [6. 系统详细设计](#6. 系统详细设计)
-
- [6.1 库存扣减流程](#6.1 库存扣减流程)
- [6.2 库存数据一致性保障](#6.2 库存数据一致性保障)
- [7. API接口设计](#7. API接口设计)
-
- [7.1 RESTful API设计](#7.1 RESTful API设计)
-
- [7.1.1 商品管理API](#7.1.1 商品管理API)
- [7.1.2 库存管理API](#7.1.2 库存管理API)
- [7.1.3 分类管理API](#7.1.3 分类管理API)
- [8. 代码实现](#8. 代码实现)
-
- [8.1 项目结构](#8.1 项目结构)
- [8.2 核心代码实现](#8.2 核心代码实现)
-
- [8.2.1 数据模型 (app/models/inventory.py)](#8.2.1 数据模型 (app/models/inventory.py))
- [8.2.2 Pydantic模型 (app/schemas/inventory.py)](#8.2.2 Pydantic模型 (app/schemas/inventory.py))
- [8.2.3 库存服务 (app/core/inventory_service.py)](#8.2.3 库存服务 (app/core/inventory_service.py))
- [8.2.4 分布式锁 (app/core/redis_lock.py)](#8.2.4 分布式锁 (app/core/redis_lock.py))
- [8.2.5 库存API路由 (app/api/v1/inventory.py)](#8.2.5 库存API路由 (app/api/v1/inventory.py))
- [8.2.6 商品API路由 (app/api/v1/products.py)](#8.2.6 商品API路由 (app/api/v1/products.py))
- [9. 性能优化策略](#9. 性能优化策略)
-
- [9.1 缓存策略](#9.1 缓存策略)
- [9.2 数据库优化](#9.2 数据库优化)
- [10. 测试策略](#10. 测试策略)
-
- [10.1 单元测试](#10.1 单元测试)
- [10.2 压力测试](#10.2 压力测试)
- [11. 部署与监控](#11. 部署与监控)
-
- [11.1 Docker部署配置](#11.1 Docker部署配置)
- [11.2 监控指标](#11.2 监控指标)
- [12. 总结与展望](#12. 总结与展望)
-
- [12.1 核心亮点](#12.1 核心亮点)
- [12.2 未来扩展方向](#12.2 未来扩展方向)
- [12.3 注意事项](#12.3 注意事项)
『宝藏代码胶囊开张啦!』------ 我的 CodeCapsule 来咯!✨写代码不再头疼!我的新站点 CodeCapsule 主打一个 "白菜价"+"量身定制 "!无论是卡脖子的毕设/课设/文献复现 ,需要灵光一现的算法改进 ,还是想给项目加个"外挂",这里都有便宜又好用的代码方案等你发现!低成本,高适配,助你轻松通关!速来围观 👉 CodeCapsule官网
电商平台商品管理与库存系统API设计
1. 引言
商品管理与库存系统是电商平台的核心组件,直接关系到平台的运营效率、用户体验和盈利能力。一个高效的库存管理系统不仅需要实时跟踪库存变化,还需要支持复杂的商品管理、多仓库调度、库存预警等功能。本文将从需求分析、架构设计到代码实现,全面介绍如何构建一个高可用、高性能的电商商品管理与库存系统。
2. 系统需求分析
2.1 功能需求
-
商品管理
- 商品分类管理(多级分类)
- 商品属性管理(规格、参数等)
- 商品信息管理(增删改查)
- 商品SKU管理(库存量单位)
- 商品图片/视频管理
-
库存管理
- 多仓库库存管理
- 实时库存查询与更新
- 库存预警与补货提醒
- 库存锁定与释放
- 库存流水记录
-
价格管理
- 商品定价策略
- 促销价格管理
- 会员价格体系
-
数据统计与分析
- 商品销售统计
- 库存周转率分析
- 滞销商品预警
2.2 非功能需求
-
性能需求
- 库存查询响应时间 < 50ms
- 库存更新操作 < 100ms
- 支持每秒1000+的库存查询请求
- 支持每秒100+的库存更新操作
-
可用性需求
- 系统可用性达到99.99%
- 支持7×24小时不间断服务
-
数据一致性需求
- 保证库存数据的最终一致性
- 防止超卖问题
3. 系统架构设计
3.1 整体架构图
基础设施
数据层
服务层
API网关层
客户端层
Web前端
移动端
商家后台
第三方系统
API网关
负载均衡
认证鉴权
商品服务
库存服务
价格服务
搜索服务
商品数据库
库存数据库
缓存集群
搜索引擎
消息队列
分布式锁
配置中心
监控告警
3.2 技术栈选择
- 后端框架: FastAPI + SQLAlchemy + Pydantic
- 数据库: PostgreSQL (主数据) + Redis (缓存) + Elasticsearch (搜索)
- 消息队列: RabbitMQ / Kafka (异步处理)
- 分布式锁: Redis Redlock
- 监控: Prometheus + Grafana
- 容器化: Docker + Kubernetes
4. 数据模型设计
4.1 实体关系图
contains
has
tracks
stores
has
defines
displays
records
CATEGORIES
uuid
id
PK
string
name
uuid
parent_id
FK
integer
level
string
path
integer
sort_order
boolean
is_active
PRODUCTS
uuid
id
PK
string
product_code
UK
string
name
uuid
category_id
FK
uuid
brand_id
FK
decimal
market_price
decimal
cost_price
string
description
json
specifications
integer
status
integer
sales_count
integer
view_count
timestamp
created_at
timestamp
updated_at
SKUS
uuid
id
PK
uuid
product_id
FK
string
sku_code
UK
string
sku_name
json
specifications
decimal
price
decimal
cost_price
string
barcode
string
unit
integer
weight
string
thumbnail
boolean
is_active
INVENTORY
uuid
id
PK
uuid
sku_id
FK
uuid
warehouse_id
FK
integer
quantity
integer
locked_quantity
integer
available_quantity
integer
safety_stock
integer
warning_threshold
timestamp
last_updated
WAREHOUSES
uuid
id
PK
string
code
UK
string
name
string
address
string
contact
string
phone
integer
type
integer
status
boolean
is_default
PRODUCT_ATTRIBUTES
ATTRIBUTES
PRODUCT_IMAGES
INVENTORY_TRANSACTIONS
4.2 核心表设计
4.2.1 商品表 (products)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | UUID | 主键 |
| product_code | VARCHAR(50) | 商品编码,唯一 |
| name | VARCHAR(200) | 商品名称 |
| category_id | UUID | 分类ID |
| brand_id | UUID | 品牌ID |
| market_price | DECIMAL(10,2) | 市场价 |
| cost_price | DECIMAL(10,2) | 成本价 |
| description | TEXT | 商品描述 |
| specifications | JSON | 商品规格参数 |
| status | INTEGER | 状态(0:下架,1:上架,2:待审核) |
| sales_count | INTEGER | 销售数量 |
| view_count | INTEGER | 浏览量 |
| created_at | TIMESTAMP | 创建时间 |
| updated_at | TIMESTAMP | 更新时间 |
4.2.2 SKU表 (skus)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | UUID | 主键 |
| product_id | UUID | 商品ID |
| sku_code | VARCHAR(50) | SKU编码,唯一 |
| sku_name | VARCHAR(200) | SKU名称 |
| specifications | JSON | SKU规格属性 |
| price | DECIMAL(10,2) | 销售价格 |
| cost_price | DECIMAL(10,2) | 成本价格 |
| barcode | VARCHAR(50) | 条形码 |
| unit | VARCHAR(20) | 单位 |
| weight | INTEGER | 重量(克) |
| thumbnail | VARCHAR(500) | 缩略图 |
| is_active | BOOLEAN | 是否启用 |
4.2.3 库存表 (inventory)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | UUID | 主键 |
| sku_id | UUID | SKU ID |
| warehouse_id | UUID | 仓库ID |
| quantity | INTEGER | 总库存数量 |
| locked_quantity | INTEGER | 锁定库存数量 |
| available_quantity | INTEGER | 可用库存数量 |
| safety_stock | INTEGER | 安全库存 |
| warning_threshold | INTEGER | 库存预警阈值 |
| last_updated | TIMESTAMP | 最后更新时间 |
5. 核心算法与公式
5.1 库存计算
可用库存计算公式:
available_quantity = quantity − locked_quantity \text{available\_quantity} = \text{quantity} - \text{locked\_quantity} available_quantity=quantity−locked_quantity
库存周转率计算公式:
inventory_turnover = cost_of_goods_sold average_inventory \text{inventory\_turnover} = \frac{\text{cost\_of\_goods\_sold}}{\text{average\_inventory}} inventory_turnover=average_inventorycost_of_goods_sold
其中平均库存为:
average_inventory = beginning_inventory + ending_inventory 2 \text{average\_inventory} = \frac{\text{beginning\_inventory} + \text{ending\_inventory}}{2} average_inventory=2beginning_inventory+ending_inventory
5.2 库存预警
库存预警条件:
{ warning = true , if available_quantity ≤ safety_stock warning = false , otherwise \begin{cases} \text{warning} = \text{true}, & \text{if } \text{available\_quantity} \leq \text{safety\_stock} \\ \text{warning} = \text{false}, & \text{otherwise} \end{cases} {warning=true,warning=false,if available_quantity≤safety_stockotherwise
补货数量建议:
reorder_quantity = max ( safety_stock × 2 − available_quantity , minimum_order_quantity ) \text{reorder\_quantity} = \text{max}( \text{safety\_stock} \times 2 - \text{available\_quantity}, \text{minimum\_order\_quantity} ) reorder_quantity=max(safety_stock×2−available_quantity,minimum_order_quantity)
6. 系统详细设计
6.1 库存扣减流程
消息队列 数据库 Redis 库存服务 API网关 客户端 消息队列 数据库 Redis 库存服务 API网关 客户端 alt [库存充足] [库存不足] alt [锁获取成功] [锁获取失败] 下单请求 库存预扣请求 获取分布式锁 锁获取成功 查询库存缓存 返回库存信息 预扣库存 记录库存流水(预扣) 更新数据库库存 发送库存变更消息 释放分布式锁 返回预扣成功 下单成功 释放分布式锁 返回库存不足 下单失败 返回系统繁忙 请重试
6.2 库存数据一致性保障
为保证库存数据一致性,我们采用以下策略:
- 分布式锁机制: 使用Redis Redlock算法实现分布式锁
- 乐观锁机制: 数据库层面使用版本号控制
- 最终一致性: 通过消息队列保证数据最终一致性
- 补偿机制: 实现库存操作的回滚机制
7. API接口设计
7.1 RESTful API设计
7.1.1 商品管理API
GET /api/v1/products # 获取商品列表
POST /api/v1/products # 创建商品
GET /api/v1/products/{product_id} # 获取商品详情
PUT /api/v1/products/{product_id} # 更新商品
DELETE /api/v1/products/{product_id} # 删除商品
POST /api/v1/products/{product_id}/skus # 添加SKU
GET /api/v1/products/search # 搜索商品
7.1.2 库存管理API
GET /api/v1/inventory/{sku_id} # 查询库存
POST /api/v1/inventory/deduct # 扣减库存
POST /api/v1/inventory/revert # 回滚库存
POST /api/v1/inventory/adjust # 调整库存
GET /api/v1/inventory/transactions # 查询库存流水
POST /api/v1/inventory/warning/settings # 设置库存预警
GET /api/v1/inventory/warnings # 获取库存预警
7.1.3 分类管理API
GET /api/v1/categories # 获取分类树
POST /api/v1/categories # 创建分类
PUT /api/v1/categories/{category_id} # 更新分类
DELETE /api/v1/categories/{category_id} # 删除分类
GET /api/v1/categories/{category_id}/products # 获取分类下商品
8. 代码实现
8.1 项目结构
ecommerce-product-inventory/
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── config.py
│ ├── database.py
│ ├── models/
│ │ ├── __init__.py
│ │ ├── product.py
│ │ ├── sku.py
│ │ ├── category.py
│ │ ├── inventory.py
│ │ └── warehouse.py
│ ├── schemas/
│ │ ├── __init__.py
│ │ ├── product.py
│ │ ├── inventory.py
│ │ └── category.py
│ ├── api/
│ │ ├── __init__.py
│ │ ├── v1/
│ │ │ ├── __init__.py
│ │ │ ├── products.py
│ │ │ ├── inventory.py
│ │ │ └── categories.py
│ ├── core/
│ │ ├── __init__.py
│ │ ├── inventory_service.py
│ │ ├── cache.py
│ │ ├── redis_lock.py
│ │ └── mq.py
│ ├── crud/
│ │ ├── __init__.py
│ │ ├── product.py
│ │ └── inventory.py
│ └── utils/
│ ├── __init__.py
│ ├── validators.py
│ └── helpers.py
├── tests/
├── requirements.txt
└── docker-compose.yml
8.2 核心代码实现
8.2.1 数据模型 (app/models/inventory.py)
python
import uuid
from datetime import datetime
from decimal import Decimal
from typing import Optional
from sqlalchemy import Column, String, Integer, Boolean, DateTime, DECIMAL, ForeignKey, JSON, Text
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from app.database import Base
class Product(Base):
"""商品模型"""
__tablename__ = "products"
# 主键
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
# 基本信息
product_code = Column(String(50), unique=True, index=True, nullable=False)
name = Column(String(200), nullable=False)
category_id = Column(UUID(as_uuid=True), ForeignKey("categories.id"), index=True, nullable=False)
brand_id = Column(UUID(as_uuid=True), ForeignKey("brands.id"), index=True, nullable=True)
# 价格信息
market_price = Column(DECIMAL(10, 2), nullable=False, default=0)
cost_price = Column(DECIMAL(10, 2), nullable=False, default=0)
# 描述信息
description = Column(Text, nullable=True)
specifications = Column(JSON, nullable=True) # 规格参数
features = Column(Text, nullable=True) # 商品特色
# 状态信息
status = Column(Integer, nullable=False, default=0) # 0:下架, 1:上架, 2:待审核
is_hot = Column(Boolean, default=False)
is_new = Column(Boolean, default=False)
is_recommended = Column(Boolean, default=False)
# 统计信息
sales_count = Column(Integer, default=0)
view_count = Column(Integer, default=0)
favorite_count = Column(Integer, default=0)
# 时间戳
created_at = Column(DateTime, default=func.now())
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
# 关系
category = relationship("Category", back_populates="products")
brand = relationship("Brand", back_populates="products")
skus = relationship("SKU", back_populates="product", cascade="all, delete-orphan")
images = relationship("ProductImage", back_populates="product", cascade="all, delete-orphan")
inventory_records = relationship("Inventory", back_populates="product", cascade="all, delete-orphan")
def __repr__(self) -> str:
return f"<Product(id={self.id}, name={self.name}, code={self.product_code})>"
@property
def min_price(self) -> Decimal:
"""获取最低价格"""
if not self.skus:
return self.market_price
return min(sku.price for sku in self.skus if sku.is_active)
@property
def max_price(self) -> Decimal:
"""获取最高价格"""
if not self.skus:
return self.market_price
return max(sku.price for sku in self.skus if sku.is_active)
@property
def total_stock(self) -> int:
"""获取总库存"""
return sum(inv.available_quantity for inv in self.inventory_records)
class SKU(Base):
"""SKU模型"""
__tablename__ = "skus"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
product_id = Column(UUID(as_uuid=True), ForeignKey("products.id"), index=True, nullable=False)
# SKU信息
sku_code = Column(String(50), unique=True, index=True, nullable=False)
sku_name = Column(String(200), nullable=False)
specifications = Column(JSON, nullable=True) # SKU规格属性
# 价格信息
price = Column(DECIMAL(10, 2), nullable=False)
cost_price = Column(DECIMAL(10, 2), nullable=False)
promotion_price = Column(DECIMAL(10, 2), nullable=True) # 促销价
# 物理属性
barcode = Column(String(50), unique=True, nullable=True)
unit = Column(String(20), nullable=False, default="件")
weight = Column(Integer, nullable=False, default=0) # 重量(克)
volume = Column(DECIMAL(10, 2), nullable=True) # 体积(立方厘米)
# 显示信息
thumbnail = Column(String(500), nullable=True)
sort_order = Column(Integer, default=0)
# 状态
is_active = Column(Boolean, default=True)
is_default = Column(Boolean, default=False) # 是否默认SKU
# 时间戳
created_at = Column(DateTime, default=func.now())
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
# 关系
product = relationship("Product", back_populates="skus")
inventory_records = relationship("Inventory", back_populates="sku", cascade="all, delete-orphan")
def __repr__(self) -> str:
return f"<SKU(id={self.id}, sku_code={self.sku_code}, product_id={self.product_id})>"
@property
def actual_price(self) -> Decimal:
"""获取实际价格(优先使用促销价)"""
return self.promotion_price if self.promotion_price else self.price
class Category(Base):
"""商品分类模型"""
__tablename__ = "categories"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
# 分类信息
name = Column(String(100), nullable=False)
parent_id = Column(UUID(as_uuid=True), ForeignKey("categories.id"), index=True, nullable=True)
level = Column(Integer, nullable=False, default=1)
path = Column(String(500), nullable=True) # 分类路径,如"1/2/3"
# 显示信息
description = Column(Text, nullable=True)
image_url = Column(String(500), nullable=True)
icon = Column(String(100), nullable=True)
# 排序与状态
sort_order = Column(Integer, default=0)
is_active = Column(Boolean, default=True)
is_show = Column(Boolean, default=True)
# SEO信息
seo_title = Column(String(200), nullable=True)
seo_keywords = Column(String(500), nullable=True)
seo_description = Column(Text, nullable=True)
# 时间戳
created_at = Column(DateTime, default=func.now())
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
# 关系
parent = relationship("Category", remote_side=[id], backref="children")
products = relationship("Product", back_populates="category")
def __repr__(self) -> str:
return f"<Category(id={self.id}, name={self.name}, level={self.level})>"
def update_path(self) -> None:
"""更新分类路径"""
if self.parent_id:
parent = self.parent
self.path = f"{parent.path}/{self.id}" if parent.path else str(self.id)
self.level = parent.level + 1
else:
self.path = str(self.id)
self.level = 1
class Warehouse(Base):
"""仓库模型"""
__tablename__ = "warehouses"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
# 仓库信息
code = Column(String(50), unique=True, index=True, nullable=False)
name = Column(String(100), nullable=False)
type = Column(Integer, nullable=False, default=1) # 1:自营仓, 2:供应商仓, 3:虚拟仓
status = Column(Integer, nullable=False, default=1) # 1:启用, 0:停用
# 联系信息
address = Column(String(500), nullable=False)
contact_person = Column(String(50), nullable=True)
phone = Column(String(20), nullable=True)
email = Column(String(100), nullable=True)
# 配置信息
is_default = Column(Boolean, default=False)
priority = Column(Integer, default=0) # 优先级,数字越大优先级越高
capacity = Column(Integer, nullable=True) # 仓库容量
# 时间戳
created_at = Column(DateTime, default=func.now())
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
# 关系
inventory_records = relationship("Inventory", back_populates="warehouse")
def __repr__(self) -> str:
return f"<Warehouse(id={self.id}, name={self.name}, code={self.code})>"
class Inventory(Base):
"""库存模型"""
__tablename__ = "inventory"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
sku_id = Column(UUID(as_uuid=True), ForeignKey("skus.id"), index=True, nullable=False)
warehouse_id = Column(UUID(as_uuid=True), ForeignKey("warehouses.id"), index=True, nullable=False)
# 库存数量
quantity = Column(Integer, nullable=False, default=0) # 总库存
locked_quantity = Column(Integer, nullable=False, default=0) # 锁定库存
available_quantity = Column(Integer, nullable=False, default=0) # 可用库存
# 库存配置
safety_stock = Column(Integer, nullable=False, default=0) # 安全库存
warning_threshold = Column(Integer, nullable=True) # 预警阈值
max_stock = Column(Integer, nullable=True) # 最大库存
# 库存状态
status = Column(Integer, nullable=False, default=1) # 1:正常, 0:停用
last_check_date = Column(DateTime, nullable=True) # 最后盘点日期
# 时间戳
created_at = Column(DateTime, default=func.now())
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
last_updated = Column(DateTime, default=func.now(), onupdate=func.now())
# 关系
sku = relationship("SKU", back_populates="inventory_records")
warehouse = relationship("Warehouse", back_populates="inventory_records")
product = relationship("Product", secondary="skus", primaryjoin="Inventory.sku_id == SKU.id",
secondaryjoin="SKU.product_id == Product.id", viewonly=True)
def __repr__(self) -> str:
return f"<Inventory(id={self.id}, sku_id={self.sku_id}, warehouse_id={self.warehouse_id}, available={self.available_quantity})>"
def update_available_quantity(self) -> None:
"""更新可用库存"""
self.available_quantity = self.quantity - self.locked_quantity
self.last_updated = func.now()
def is_low_stock(self) -> bool:
"""判断是否低库存"""
if self.warning_threshold is not None:
return self.available_quantity <= self.warning_threshold
return self.available_quantity <= self.safety_stock
def get_reorder_quantity(self, minimum_order: int = 10) -> int:
"""获取建议补货数量
参数:
- minimum_order: 最小订购量
返回:
- 建议补货数量
"""
if self.max_stock:
reorder_qty = self.max_stock - self.available_quantity
else:
reorder_qty = max(self.safety_stock * 2 - self.available_quantity, 0)
return max(reorder_qty, minimum_order)
class InventoryTransaction(Base):
"""库存流水模型"""
__tablename__ = "inventory_transactions"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
sku_id = Column(UUID(as_uuid=True), ForeignKey("skus.id"), index=True, nullable=False)
warehouse_id = Column(UUID(as_uuid=True), ForeignKey("warehouses.id"), index=True, nullable=False)
# 流水信息
transaction_type = Column(Integer, nullable=False) # 1:入库, 2:出库, 3:调整, 4:锁定, 5:解锁
change_quantity = Column(Integer, nullable=False) # 变化数量(正数增加,负数减少)
before_quantity = Column(Integer, nullable=False) # 变化前数量
after_quantity = Column(Integer, nullable=False) # 变化后数量
# 关联信息
reference_type = Column(Integer, nullable=True) # 关联类型(1:订单, 2:采购单, 3:调拨单)
reference_id = Column(String(100), nullable=True) # 关联ID
reference_no = Column(String(100), nullable=True) # 关联单号
# 操作信息
operator_id = Column(UUID(as_uuid=True), nullable=True)
operator_name = Column(String(100), nullable=True)
remark = Column(Text, nullable=True)
# 时间戳
created_at = Column(DateTime, default=func.now())
# 关系
sku = relationship("SKU")
warehouse = relationship("Warehouse")
def __repr__(self) -> str:
return f"<InventoryTransaction(id={self.id}, sku_id={self.sku_id}, type={self.transaction_type}, change={self.change_quantity})>"
class ProductImage(Base):
"""商品图片模型"""
__tablename__ = "product_images"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
product_id = Column(UUID(as_uuid=True), ForeignKey("products.id"), index=True, nullable=False)
sku_id = Column(UUID(as_uuid=True), ForeignKey("skus.id"), index=True, nullable=True)
# 图片信息
url = Column(String(500), nullable=False)
thumbnail_url = Column(String(500), nullable=True)
alt_text = Column(String(200), nullable=True)
# 显示信息
sort_order = Column(Integer, default=0)
is_main = Column(Boolean, default=False) # 是否主图
# 时间戳
created_at = Column(DateTime, default=func.now())
# 关系
product = relationship("Product", back_populates="images")
sku = relationship("SKU")
def __repr__(self) -> str:
return f"<ProductImage(id={self.id}, product_id={self.product_id}, url={self.url})>"
8.2.2 Pydantic模型 (app/schemas/inventory.py)
python
from typing import Optional, List, Dict, Any
from datetime import datetime
from decimal import Decimal
from pydantic import BaseModel, Field, validator, condecimal, conint
import re
class CategoryBase(BaseModel):
"""分类基础模型"""
name: str = Field(..., max_length=100, description="分类名称")
parent_id: Optional[str] = Field(None, description="父分类ID")
description: Optional[str] = Field(None, description="分类描述")
image_url: Optional[str] = Field(None, description="分类图片")
sort_order: int = Field(0, ge=0, description="排序")
is_active: bool = Field(True, description="是否启用")
class CategoryCreate(CategoryBase):
"""分类创建模型"""
pass
class CategoryUpdate(BaseModel):
"""分类更新模型"""
name: Optional[str] = Field(None, max_length=100)
description: Optional[str] = None
image_url: Optional[str] = None
sort_order: Optional[int] = Field(None, ge=0)
is_active: Optional[bool] = None
class CategoryInDB(CategoryBase):
"""数据库中的分类模型"""
id: str
level: int
path: Optional[str]
created_at: datetime
updated_at: datetime
class Config:
orm_mode = True
class CategoryTree(CategoryInDB):
"""分类树模型"""
children: List["CategoryTree"] = []
class Config:
orm_mode = True
class ProductBase(BaseModel):
"""商品基础模型"""
product_code: str = Field(..., max_length=50, description="商品编码")
name: str = Field(..., max_length=200, description="商品名称")
category_id: str = Field(..., description="分类ID")
brand_id: Optional[str] = Field(None, description="品牌ID")
market_price: Decimal = Field(..., ge=0, description="市场价")
cost_price: Decimal = Field(..., ge=0, description="成本价")
description: Optional[str] = Field(None, description="商品描述")
specifications: Optional[Dict[str, Any]] = Field(None, description="规格参数")
features: Optional[str] = Field(None, description="商品特色")
status: int = Field(1, ge=0, le=2, description="状态(0:下架,1:上架,2:待审核)")
is_hot: bool = Field(False, description="是否热销")
is_new: bool = Field(False, description="是否新品")
is_recommended: bool = Field(False, description="是否推荐")
@validator('product_code')
def validate_product_code(cls, v):
"""验证商品编码格式"""
if not re.match(r'^[A-Za-z0-9_-]+$', v):
raise ValueError('商品编码只能包含字母、数字、下划线和连字符')
return v
class ProductCreate(ProductBase):
"""商品创建模型"""
pass
class ProductUpdate(BaseModel):
"""商品更新模型"""
name: Optional[str] = Field(None, max_length=200)
category_id: Optional[str] = None
brand_id: Optional[str] = None
market_price: Optional[Decimal] = Field(None, ge=0)
cost_price: Optional[Decimal] = Field(None, ge=0)
description: Optional[str] = None
specifications: Optional[Dict[str, Any]] = None
status: Optional[int] = Field(None, ge=0, le=2)
is_hot: Optional[bool] = None
is_new: Optional[bool] = None
is_recommended: Optional[bool] = None
class ProductInDB(ProductBase):
"""数据库中的商品模型"""
id: str
sales_count: int
view_count: int
favorite_count: int
created_at: datetime
updated_at: datetime
class Config:
orm_mode = True
class ProductDetail(ProductInDB):
"""商品详情模型"""
category: Optional[CategoryInDB] = None
skus: List["SKUInDB"] = []
images: List["ProductImageInDB"] = []
inventory_summary: Optional[Dict[str, int]] = None
class Config:
orm_mode = True
class SKUBase(BaseModel):
"""SKU基础模型"""
sku_code: str = Field(..., max_length=50, description="SKU编码")
sku_name: str = Field(..., max_length=200, description="SKU名称")
specifications: Optional[Dict[str, Any]] = Field(None, description="SKU规格")
price: Decimal = Field(..., ge=0, description="销售价")
cost_price: Decimal = Field(..., ge=0, description="成本价")
promotion_price: Optional[Decimal] = Field(None, ge=0, description="促销价")
barcode: Optional[str] = Field(None, max_length=50, description="条形码")
unit: str = Field("件", max_length=20, description="单位")
weight: int = Field(0, ge=0, description="重量(克)")
thumbnail: Optional[str] = Field(None, description="缩略图")
is_active: bool = Field(True, description="是否启用")
is_default: bool = Field(False, description="是否默认SKU")
@validator('sku_code')
def validate_sku_code(cls, v):
"""验证SKU编码格式"""
if not re.match(r'^[A-Za-z0-9_-]+$', v):
raise ValueError('SKU编码只能包含字母、数字、下划线和连字符')
return v
class SKUCreate(SKUBase):
"""SKU创建模型"""
product_id: str = Field(..., description="商品ID")
class SKUUpdate(BaseModel):
"""SKU更新模型"""
sku_name: Optional[str] = Field(None, max_length=200)
specifications: Optional[Dict[str, Any]] = None
price: Optional[Decimal] = Field(None, ge=0)
cost_price: Optional[Decimal] = Field(None, ge=0)
promotion_price: Optional[Decimal] = Field(None, ge=0)
barcode: Optional[str] = Field(None, max_length=50)
unit: Optional[str] = Field(None, max_length=20)
weight: Optional[int] = Field(None, ge=0)
thumbnail: Optional[str] = None
is_active: Optional[bool] = None
is_default: Optional[bool] = None
class SKUInDB(SKUBase):
"""数据库中的SKU模型"""
id: str
product_id: str
created_at: datetime
updated_at: datetime
class Config:
orm_mode = True
class SKUDetail(SKUInDB):
"""SKU详情模型"""
product: Optional[ProductInDB] = None
inventory: List["InventoryInDB"] = []
class Config:
orm_mode = True
class InventoryBase(BaseModel):
"""库存基础模型"""
sku_id: str = Field(..., description="SKU ID")
warehouse_id: str = Field(..., description="仓库ID")
quantity: int = Field(0, ge=0, description="总库存")
safety_stock: int = Field(0, ge=0, description="安全库存")
warning_threshold: Optional[int] = Field(None, ge=0, description="预警阈值")
max_stock: Optional[int] = Field(None, ge=0, description="最大库存")
class InventoryCreate(InventoryBase):
"""库存创建模型"""
pass
class InventoryUpdate(BaseModel):
"""库存更新模型"""
quantity: Optional[int] = Field(None, ge=0)
safety_stock: Optional[int] = Field(None, ge=0)
warning_threshold: Optional[int] = Field(None, ge=0)
max_stock: Optional[int] = Field(None, ge=0)
status: Optional[int] = Field(None, ge=0, le=1)
class InventoryInDB(InventoryBase):
"""数据库中的库存模型"""
id: str
locked_quantity: int
available_quantity: int
status: int
last_updated: datetime
created_at: datetime
updated_at: datetime
class Config:
orm_mode = True
class InventoryDetail(InventoryInDB):
"""库存详情模型"""
sku: Optional[SKUInDB] = None
warehouse: Optional["WarehouseInDB"] = None
class Config:
orm_mode = True
class WarehouseBase(BaseModel):
"""仓库基础模型"""
code: str = Field(..., max_length=50, description="仓库编码")
name: str = Field(..., max_length=100, description="仓库名称")
type: int = Field(1, ge=1, le=3, description="仓库类型(1:自营,2:供应商,3:虚拟)")
address: str = Field(..., max_length=500, description="仓库地址")
contact_person: Optional[str] = Field(None, max_length=50, description="联系人")
phone: Optional[str] = Field(None, max_length=20, description="联系电话")
is_default: bool = Field(False, description="是否默认仓库")
priority: int = Field(0, description="优先级")
class WarehouseCreate(WarehouseBase):
"""仓库创建模型"""
pass
class WarehouseUpdate(BaseModel):
"""仓库更新模型"""
name: Optional[str] = Field(None, max_length=100)
type: Optional[int] = Field(None, ge=1, le=3)
address: Optional[str] = Field(None, max_length=500)
contact_person: Optional[str] = Field(None, max_length=50)
phone: Optional[str] = Field(None, max_length=20)
is_default: Optional[bool] = None
priority: Optional[int] = None
status: Optional[int] = Field(None, ge=0, le=1)
class WarehouseInDB(WarehouseBase):
"""数据库中的仓库模型"""
id: str
status: int
created_at: datetime
updated_at: datetime
class Config:
orm_mode = True
class ProductImageInDB(BaseModel):
"""商品图片模型"""
id: str
product_id: str
sku_id: Optional[str]
url: str
thumbnail_url: Optional[str]
alt_text: Optional[str]
sort_order: int
is_main: bool
created_at: datetime
class Config:
orm_mode = True
# 库存操作相关模型
class InventoryDeductRequest(BaseModel):
"""库存扣减请求模型"""
sku_id: str = Field(..., description="SKU ID")
warehouse_id: Optional[str] = Field(None, description="仓库ID,不指定时使用默认仓库")
quantity: int = Field(..., gt=0, description="扣减数量")
order_id: str = Field(..., description="订单ID")
order_item_id: str = Field(..., description="订单项ID")
deduct_type: int = Field(1, ge=1, le=2, description="扣减类型(1:预扣,2:实扣)")
class InventoryDeductResponse(BaseModel):
"""库存扣减响应模型"""
success: bool
message: str
transaction_id: Optional[str] = None
available_quantity: Optional[int] = None
deducted_quantity: Optional[int] = None
class InventoryRevertRequest(BaseModel):
"""库存回滚请求模型"""
sku_id: str = Field(..., description="SKU ID")
warehouse_id: Optional[str] = Field(None, description="仓库ID")
quantity: int = Field(..., gt=0, description="回滚数量")
transaction_id: str = Field(..., description="原交易ID")
reason: str = Field(..., description="回滚原因")
class InventoryAdjustRequest(BaseModel):
"""库存调整请求模型"""
sku_id: str = Field(..., description="SKU ID")
warehouse_id: str = Field(..., description="仓库ID")
quantity: int = Field(..., description="调整数量(正数增加,负数减少)")
reason: str = Field(..., description="调整原因")
reference_type: int = Field(1, ge=1, le=3, description="关联类型(1:盘点,2:报损,3:其他)")
reference_no: Optional[str] = Field(None, description="关联单号")
# 分页和查询模型
class ProductQueryParams(BaseModel):
"""商品查询参数"""
keyword: Optional[str] = Field(None, description="关键词")
category_id: Optional[str] = Field(None, description="分类ID")
brand_id: Optional[str] = Field(None, description="品牌ID")
status: Optional[int] = Field(None, ge=0, le=2, description="状态")
min_price: Optional[Decimal] = Field(None, ge=0, description="最低价")
max_price: Optional[Decimal] = Field(None, ge=0, description="最高价")
is_hot: Optional[bool] = Field(None, description="是否热销")
is_new: Optional[bool] = Field(None, description="是否新品")
is_recommended: Optional[bool] = Field(None, description="是否推荐")
page: int = Field(1, ge=1, description="页码")
size: int = Field(20, ge=1, le=100, description="每页数量")
sort_by: str = Field("created_at", description="排序字段")
sort_order: str = Field("desc", description="排序方向")
class ProductListResponse(BaseModel):
"""商品列表响应"""
success: bool = True
message: str = "查询成功"
data: List[ProductInDB]
total: int
page: int
size: int
total_pages: int
# 更新前向引用
CategoryTree.update_forward_refs()
8.2.3 库存服务 (app/core/inventory_service.py)
python
import uuid
from datetime import datetime
from decimal import Decimal
from typing import Optional, List, Dict, Any, Tuple
import asyncio
import json
from sqlalchemy.orm import Session
from sqlalchemy import and_, or_, func
import redis
from app import models, schemas
from app.core.redis_lock import RedisDistributedLock
from app.core.cache import get_redis_client
from app.core.mq import MessageQueue
from app.config import settings
class InventoryService:
"""库存服务"""
def __init__(self, db: Session):
self.db = db
self.redis = get_redis_client()
self.mq = MessageQueue()
self.lock_timeout = 10 # 锁超时时间(秒)
self.lock_retry_delay = 0.1 # 锁重试延迟(秒)
async def deduct_inventory(self, request: schemas.InventoryDeductRequest) -> schemas.InventoryDeductResponse:
"""扣减库存
参数:
- request: 库存扣减请求
返回:
- 库存扣减响应
"""
# 获取仓库ID
warehouse_id = request.warehouse_id
if not warehouse_id:
warehouse_id = await self._get_default_warehouse_id()
# 构建锁键
lock_key = f"inventory:lock:{request.sku_id}:{warehouse_id}"
try:
# 获取分布式锁
async with RedisDistributedLock(self.redis, lock_key, timeout=self.lock_timeout):
# 查询库存记录
inventory = self.db.query(models.Inventory).filter(
and_(
models.Inventory.sku_id == uuid.UUID(request.sku_id),
models.Inventory.warehouse_id == uuid.UUID(warehouse_id),
models.Inventory.status == 1
)
).with_for_update().first()
if not inventory:
return schemas.InventoryDeductResponse(
success=False,
message="库存记录不存在"
)
# 检查库存是否充足
if inventory.available_quantity < request.quantity:
return schemas.InventoryDeductResponse(
success=False,
message=f"库存不足,当前可用库存: {inventory.available_quantity}",
available_quantity=inventory.available_quantity
)
# 记录操作前的数量
before_quantity = inventory.quantity
before_locked = inventory.locked_quantity
# 更新库存
if request.deduct_type == 1:
# 预扣库存:锁定库存
inventory.locked_quantity += request.quantity
else:
# 实扣库存:直接减少总库存
inventory.quantity -= request.quantity
# 更新可用库存
inventory.update_available_quantity()
inventory.last_updated = datetime.utcnow()
# 创建库存流水记录
transaction = models.InventoryTransaction(
sku_id=uuid.UUID(request.sku_id),
warehouse_id=uuid.UUID(warehouse_id),
transaction_type=4 if request.deduct_type == 1 else 2, # 4:锁定, 2:出库
change_quantity=-request.quantity,
before_quantity=before_quantity,
after_quantity=inventory.quantity,
reference_type=1, # 订单
reference_id=request.order_id,
reference_no=request.order_id,
operator_name="system",
remark=f"订单扣减: {request.order_item_id}"
)
self.db.add(transaction)
self.db.commit()
# 更新缓存
await self._update_inventory_cache(inventory)
# 发送库存变更消息
await self.mq.publish_inventory_change({
"sku_id": request.sku_id,
"warehouse_id": warehouse_id,
"change_type": "deduct",
"quantity": request.quantity,
"available_quantity": inventory.available_quantity,
"order_id": request.order_id,
"timestamp": datetime.utcnow().isoformat()
})
# 检查库存预警
if inventory.is_low_stock():
await self._send_low_stock_warning(inventory)
return schemas.InventoryDeductResponse(
success=True,
message="库存扣减成功",
transaction_id=str(transaction.id),
available_quantity=inventory.available_quantity,
deducted_quantity=request.quantity
)
except Exception as e:
self.db.rollback()
return schemas.InventoryDeductResponse(
success=False,
message=f"库存扣减失败: {str(e)}"
)
async def revert_inventory(self, request: schemas.InventoryRevertRequest) -> schemas.InventoryDeductResponse:
"""回滚库存(释放锁定库存或恢复库存)
参数:
- request: 库存回滚请求
返回:
- 库存回滚响应
"""
# 获取仓库ID
warehouse_id = request.warehouse_id
if not warehouse_id:
warehouse_id = await self._get_default_warehouse_id()
# 构建锁键
lock_key = f"inventory:lock:{request.sku_id}:{warehouse_id}"
try:
# 获取分布式锁
async with RedisDistributedLock(self.redis, lock_key, timeout=self.lock_timeout):
# 查询原交易记录
original_transaction = self.db.query(models.InventoryTransaction).filter(
models.InventoryTransaction.id == uuid.UUID(request.transaction_id)
).first()
if not original_transaction:
return schemas.InventoryDeductResponse(
success=False,
message="原交易记录不存在"
)
# 查询库存记录
inventory = self.db.query(models.Inventory).filter(
and_(
models.Inventory.sku_id == uuid.UUID(request.sku_id),
models.Inventory.warehouse_id == uuid.UUID(warehouse_id),
models.Inventory.status == 1
)
).with_for_update().first()
if not inventory:
return schemas.InventoryDeductResponse(
success=False,
message="库存记录不存在"
)
# 记录操作前的数量
before_quantity = inventory.quantity
before_locked = inventory.locked_quantity
# 根据原交易类型进行回滚
if original_transaction.transaction_type == 4: # 锁定
# 释放锁定库存
if inventory.locked_quantity < request.quantity:
request.quantity = inventory.locked_quantity # 只能释放已有的锁定库存
inventory.locked_quantity -= request.quantity
revert_type = 5 # 解锁
else: # 出库
# 恢复总库存
inventory.quantity += request.quantity
revert_type = 1 # 入库
# 更新可用库存
inventory.update_available_quantity()
inventory.last_updated = datetime.utcnow()
# 创建库存流水记录
transaction = models.InventoryTransaction(
sku_id=uuid.UUID(request.sku_id),
warehouse_id=uuid.UUID(warehouse_id),
transaction_type=revert_type,
change_quantity=request.quantity,
before_quantity=before_quantity,
after_quantity=inventory.quantity,
reference_type=1, # 订单
reference_id=original_transaction.reference_id,
reference_no=original_transaction.reference_no,
operator_name="system",
remark=f"库存回滚: {request.reason}, 原交易: {request.transaction_id}"
)
self.db.add(transaction)
self.db.commit()
# 更新缓存
await self._update_inventory_cache(inventory)
# 发送库存变更消息
await self.mq.publish_inventory_change({
"sku_id": request.sku_id,
"warehouse_id": warehouse_id,
"change_type": "revert",
"quantity": request.quantity,
"available_quantity": inventory.available_quantity,
"reason": request.reason,
"timestamp": datetime.utcnow().isoformat()
})
return schemas.InventoryDeductResponse(
success=True,
message="库存回滚成功",
transaction_id=str(transaction.id),
available_quantity=inventory.available_quantity
)
except Exception as e:
self.db.rollback()
return schemas.InventoryDeductResponse(
success=False,
message=f"库存回滚失败: {str(e)}"
)
async def adjust_inventory(self, request: schemas.InventoryAdjustRequest) -> schemas.InventoryDeductResponse:
"""调整库存
参数:
- request: 库存调整请求
返回:
- 库存调整响应
"""
lock_key = f"inventory:lock:{request.sku_id}:{request.warehouse_id}"
try:
# 获取分布式锁
async with RedisDistributedLock(self.redis, lock_key, timeout=self.lock_timeout):
# 查询库存记录
inventory = self.db.query(models.Inventory).filter(
and_(
models.Inventory.sku_id == uuid.UUID(request.sku_id),
models.Inventory.warehouse_id == uuid.UUID(request.warehouse_id),
models.Inventory.status == 1
)
).with_for_update().first()
if not inventory:
# 如果库存记录不存在,创建新的记录
inventory = models.Inventory(
sku_id=uuid.UUID(request.sku_id),
warehouse_id=uuid.UUID(request.warehouse_id),
quantity=max(request.quantity, 0),
locked_quantity=0,
safety_stock=0,
status=1
)
inventory.update_available_quantity()
self.db.add(inventory)
before_quantity = 0
else:
before_quantity = inventory.quantity
# 更新库存
inventory.quantity += request.quantity
if inventory.quantity < 0:
inventory.quantity = 0
# 更新可用库存
inventory.update_available_quantity()
inventory.last_updated = datetime.utcnow()
# 创建库存流水记录
transaction = models.InventoryTransaction(
sku_id=uuid.UUID(request.sku_id),
warehouse_id=uuid.UUID(request.warehouse_id),
transaction_type=3, # 调整
change_quantity=request.quantity,
before_quantity=before_quantity,
after_quantity=inventory.quantity,
reference_type=request.reference_type,
reference_no=request.reference_no,
operator_name="system",
remark=request.reason
)
self.db.add(transaction)
self.db.commit()
# 更新缓存
await self._update_inventory_cache(inventory)
# 发送库存变更消息
await self.mq.publish_inventory_change({
"sku_id": request.sku_id,
"warehouse_id": request.warehouse_id,
"change_type": "adjust",
"quantity": request.quantity,
"available_quantity": inventory.available_quantity,
"reason": request.reason,
"timestamp": datetime.utcnow().isoformat()
})
# 检查库存预警
if request.quantity < 0 and inventory.is_low_stock():
await self._send_low_stock_warning(inventory)
return schemas.InventoryDeductResponse(
success=True,
message="库存调整成功",
transaction_id=str(transaction.id),
available_quantity=inventory.available_quantity
)
except Exception as e:
self.db.rollback()
return schemas.InventoryDeductResponse(
success=False,
message=f"库存调整失败: {str(e)}"
)
async def get_inventory(self, sku_id: str, warehouse_id: Optional[str] = None) -> Optional[models.Inventory]:
"""获取库存信息
参数:
- sku_id: SKU ID
- warehouse_id: 仓库ID,不指定时返回默认仓库库存
返回:
- 库存记录
"""
# 优先从缓存获取
cache_key = f"inventory:{sku_id}"
if warehouse_id:
cache_key = f"{cache_key}:{warehouse_id}"
cached_data = await self.redis.get(cache_key)
if cached_data:
# 这里简化处理,实际应该反序列化为模型对象
return json.loads(cached_data)
# 查询数据库
query = self.db.query(models.Inventory).filter(
models.Inventory.sku_id == uuid.UUID(sku_id),
models.Inventory.status == 1
)
if warehouse_id:
query = query.filter(models.Inventory.warehouse_id == uuid.UUID(warehouse_id))
else:
# 获取默认仓库
default_warehouse = await self._get_default_warehouse_id()
query = query.filter(models.Inventory.warehouse_id == uuid.UUID(default_warehouse))
inventory = query.first()
if inventory:
# 更新缓存
await self._update_inventory_cache(inventory)
return inventory
async def batch_deduct_inventory(self, requests: List[schemas.InventoryDeductRequest]) -> List[schemas.InventoryDeductResponse]:
"""批量扣减库存
参数:
- requests: 库存扣减请求列表
返回:
- 库存扣减响应列表
"""
results = []
for request in requests:
result = await self.deduct_inventory(request)
results.append(result)
# 如果某个扣减失败,可以决定是否继续
if not result.success:
# 这里可以根据业务需求决定是否中断
pass
return results
async def get_inventory_summary(self, sku_id: str) -> Dict[str, Any]:
"""获取库存汇总信息
参数:
- sku_id: SKU ID
返回:
- 库存汇总信息
"""
# 查询所有仓库的库存
inventories = self.db.query(models.Inventory).filter(
models.Inventory.sku_id == uuid.UUID(sku_id),
models.Inventory.status == 1
).all()
total_quantity = sum(inv.quantity for inv in inventories)
total_locked = sum(inv.locked_quantity for inv in inventories)
total_available = sum(inv.available_quantity for inv in inventories)
# 检查是否有低库存预警
low_stock_warehouses = [
{
"warehouse_id": str(inv.warehouse_id),
"warehouse_name": inv.warehouse.name if inv.warehouse else "",
"available_quantity": inv.available_quantity,
"safety_stock": inv.safety_stock
}
for inv in inventories if inv.is_low_stock()
]
return {
"sku_id": sku_id,
"total_quantity": total_quantity,
"total_locked": total_locked,
"total_available": total_available,
"warehouse_count": len(inventories),
"low_stock_warehouses": low_stock_warehouses,
"has_low_stock": len(low_stock_warehouses) > 0
}
async def get_low_stock_products(self, threshold: Optional[int] = None) -> List[Dict[str, Any]]:
"""获取低库存商品
参数:
- threshold: 预警阈值,不指定时使用库存记录中的设置
返回:
- 低库存商品列表
"""
query = self.db.query(
models.Inventory,
models.SKU,
models.Product,
models.Warehouse
).join(
models.SKU, models.Inventory.sku_id == models.SKU.id
).join(
models.Product, models.SKU.product_id == models.Product.id
).join(
models.Warehouse, models.Inventory.warehouse_id == models.Warehouse.id
).filter(
models.Inventory.status == 1,
models.Product.status == 1,
models.SKU.is_active == True
)
if threshold:
# 使用指定的阈值
query = query.filter(models.Inventory.available_quantity <= threshold)
else:
# 使用库存记录中的预警阈值或安全库存
query = query.filter(
or_(
models.Inventory.available_quantity <= models.Inventory.warning_threshold,
and_(
models.Inventory.warning_threshold.is_(None),
models.Inventory.available_quantity <= models.Inventory.safety_stock
)
)
)
results = query.all()
low_stock_items = []
for inventory, sku, product, warehouse in results:
low_stock_items.append({
"product_id": str(product.id),
"product_name": product.name,
"product_code": product.product_code,
"sku_id": str(sku.id),
"sku_code": sku.sku_code,
"sku_name": sku.sku_name,
"warehouse_id": str(warehouse.id),
"warehouse_name": warehouse.name,
"available_quantity": inventory.available_quantity,
"safety_stock": inventory.safety_stock,
"warning_threshold": inventory.warning_threshold,
"reorder_quantity": inventory.get_reorder_quantity(),
"last_updated": inventory.last_updated.isoformat() if inventory.last_updated else None
})
return low_stock_items
async def _get_default_warehouse_id(self) -> str:
"""获取默认仓库ID"""
cache_key = "warehouse:default"
cached_id = await self.redis.get(cache_key)
if cached_id:
return cached_id.decode()
# 查询数据库
warehouse = self.db.query(models.Warehouse).filter(
models.Warehouse.is_default == True,
models.Warehouse.status == 1
).first()
if not warehouse:
# 如果没有默认仓库,使用第一个启用的仓库
warehouse = self.db.query(models.Warehouse).filter(
models.Warehouse.status == 1
).first()
if not warehouse:
raise Exception("没有可用的仓库")
warehouse_id = str(warehouse.id)
# 更新缓存
await self.redis.setex(cache_key, 3600, warehouse_id) # 缓存1小时
return warehouse_id
async def _update_inventory_cache(self, inventory: models.Inventory) -> None:
"""更新库存缓存"""
cache_key = f"inventory:{inventory.sku_id}:{inventory.warehouse_id}"
inventory_data = {
"id": str(inventory.id),
"sku_id": str(inventory.sku_id),
"warehouse_id": str(inventory.warehouse_id),
"quantity": inventory.quantity,
"locked_quantity": inventory.locked_quantity,
"available_quantity": inventory.available_quantity,
"safety_stock": inventory.safety_stock,
"warning_threshold": inventory.warning_threshold,
"last_updated": inventory.last_updated.isoformat() if inventory.last_updated else None
}
# 缓存5分钟
await self.redis.setex(cache_key, 300, json.dumps(inventory_data))
# 同时更新SKU维度的汇总缓存
summary_key = f"inventory:summary:{inventory.sku_id}"
await self.redis.delete(summary_key) # 删除汇总缓存,下次查询时重新计算
async def _send_low_stock_warning(self, inventory: models.Inventory) -> None:
"""发送低库存预警"""
# 获取SKU和商品信息
sku = self.db.query(models.SKU).filter(models.SKU.id == inventory.sku_id).first()
if not sku:
return
product = self.db.query(models.Product).filter(models.Product.id == sku.product_id).first()
warehouse = self.db.query(models.Warehouse).filter(models.Warehouse.id == inventory.warehouse_id).first()
warning_data = {
"type": "low_stock_warning",
"sku_id": str(inventory.sku_id),
"sku_code": sku.sku_code if sku else "",
"sku_name": sku.sku_name if sku else "",
"product_id": str(product.id) if product else "",
"product_name": product.name if product else "",
"product_code": product.product_code if product else "",
"warehouse_id": str(inventory.warehouse_id),
"warehouse_name": warehouse.name if warehouse else "",
"available_quantity": inventory.available_quantity,
"safety_stock": inventory.safety_stock,
"warning_threshold": inventory.warning_threshold,
"reorder_suggestion": inventory.get_reorder_quantity(),
"timestamp": datetime.utcnow().isoformat()
}
# 发送到消息队列
await self.mq.publish_warning(warning_data)
# 也可以发送邮件、短信等通知
# await self._send_notification(warning_data)
async def calculate_inventory_turnover(self, sku_id: str, period_days: int = 30) -> Dict[str, Any]:
"""计算库存周转率
参数:
- sku_id: SKU ID
- period_days: 统计周期(天)
返回:
- 库存周转率信息
"""
from datetime import timedelta
end_date = datetime.utcnow()
start_date = end_date - timedelta(days=period_days)
# 计算销售成本(这里简化处理,实际应该从订单系统获取)
# 假设通过库存流水计算出库数量
outbound_transactions = self.db.query(models.InventoryTransaction).filter(
models.InventoryTransaction.sku_id == uuid.UUID(sku_id),
models.InventoryTransaction.transaction_type == 2, # 出库
models.InventoryTransaction.created_at >= start_date,
models.InventoryTransaction.created_at <= end_date
).all()
total_outbound = sum(abs(tx.change_quantity) for tx in outbound_transactions)
# 获取SKU成本价
sku = self.db.query(models.SKU).filter(models.SKU.id == uuid.UUID(sku_id)).first()
if not sku:
return {"error": "SKU不存在"}
cost_of_goods_sold = total_outbound * float(sku.cost_price)
# 计算平均库存
# 这里简化处理,实际应该计算周期内的每日平均库存
inventory = self.db.query(models.Inventory).filter(
models.Inventory.sku_id == uuid.UUID(sku_id)
).first()
if not inventory:
average_inventory = 0
else:
# 这里简化计算,实际应该查询历史库存记录
average_inventory = float(inventory.quantity)
# 计算库存周转率
if average_inventory > 0:
turnover_rate = cost_of_goods_sold / average_inventory
turnover_days = period_days / turnover_rate if turnover_rate > 0 else float('inf')
else:
turnover_rate = float('inf')
turnover_days = 0
return {
"sku_id": sku_id,
"period_days": period_days,
"start_date": start_date.isoformat(),
"end_date": end_date.isoformat(),
"total_outbound": total_outbound,
"cost_of_goods_sold": cost_of_goods_sold,
"average_inventory": average_inventory,
"turnover_rate": turnover_rate,
"turnover_days": turnover_days,
"interpretation": self._interpret_turnover_rate(turnover_rate)
}
def _interpret_turnover_rate(self, rate: float) -> str:
"""解释库存周转率"""
if rate == float('inf'):
return "库存为0,周转率为无限大"
elif rate > 12: # 年周转12次以上
return "周转率很高,库存管理优秀"
elif rate > 6: # 年周转6-12次
return "周转率良好"
elif rate > 3: # 年周转3-6次
return "周转率一般"
elif rate > 1: # 年周转1-3次
return "周转率较低,库存可能积压"
else: # 年周转1次以下
return "周转率很低,库存严重积压"
8.2.4 分布式锁 (app/core/redis_lock.py)
python
import asyncio
import time
import uuid
from typing import Optional
import redis.asyncio as redis
from contextlib import asynccontextmanager
class RedisDistributedLock:
"""Redis分布式锁"""
def __init__(self, redis_client: redis.Redis, lock_key: str, timeout: int = 10):
"""
初始化分布式锁
参数:
- redis_client: Redis客户端
- lock_key: 锁的键名
- timeout: 锁超时时间(秒)
"""
self.redis = redis_client
self.lock_key = lock_key
self.timeout = timeout
self.identifier = str(uuid.uuid4())
async def acquire(self, retry_count: int = 3, retry_delay: float = 0.1) -> bool:
"""获取锁
参数:
- retry_count: 重试次数
- retry_delay: 重试延迟(秒)
返回:
- 是否获取成功
"""
for attempt in range(retry_count):
# 尝试获取锁
acquired = await self.redis.set(
self.lock_key,
self.identifier,
ex=self.timeout,
nx=True # 只在键不存在时设置
)
if acquired:
return True
if attempt < retry_count - 1:
await asyncio.sleep(retry_delay)
return False
async def release(self) -> bool:
"""释放锁
使用Lua脚本确保原子性操作
返回:
- 是否释放成功
"""
lua_script = """
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
"""
try:
result = await self.redis.eval(lua_script, 1, self.lock_key, self.identifier)
return bool(result)
except Exception:
return False
async def __aenter__(self):
"""上下文管理器入口"""
acquired = await self.acquire()
if not acquired:
raise Exception(f"Failed to acquire lock for key: {self.lock_key}")
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
"""上下文管理器出口"""
await self.release()
async def renew(self, additional_time: int = None) -> bool:
"""续期锁
参数:
- additional_time: 续期时间,不指定时使用原超时时间
返回:
- 是否续期成功
"""
if additional_time is None:
additional_time = self.timeout
lua_script = """
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("expire", KEYS[1], ARGV[2])
else
return 0
end
"""
try:
result = await self.redis.eval(lua_script, 1, self.lock_key, self.identifier, additional_time)
return bool(result)
except Exception:
return False
class InventoryLockManager:
"""库存锁管理器"""
def __init__(self, redis_client: redis.Redis):
self.redis = redis_client
self.locks = {}
async def lock_inventory(self, sku_id: str, warehouse_id: str, timeout: int = 10) -> Optional[RedisDistributedLock]:
"""锁定库存记录
参数:
- sku_id: SKU ID
- warehouse_id: 仓库ID
- timeout: 锁超时时间
返回:
- 锁对象
"""
lock_key = f"inventory:lock:{sku_id}:{warehouse_id}"
lock = RedisDistributedLock(self.redis, lock_key, timeout)
if await lock.acquire():
# 记录锁,用于后续管理
lock_id = f"{sku_id}:{warehouse_id}"
self.locks[lock_id] = lock
return lock
return None
async def unlock_inventory(self, sku_id: str, warehouse_id: str) -> bool:
"""解锁库存记录
参数:
- sku_id: SKU ID
- warehouse_id: 仓库ID
返回:
- 是否解锁成功
"""
lock_id = f"{sku_id}:{warehouse_id}"
if lock_id in self.locks:
lock = self.locks[lock_id]
result = await lock.release()
del self.locks[lock_id]
return result
# 如果内存中没有记录,尝试直接释放
lock_key = f"inventory:lock:{sku_id}:{warehouse_id}"
try:
await self.redis.delete(lock_key)
return True
except Exception:
return False
async def unlock_all(self) -> None:
"""释放所有锁"""
for lock_id, lock in list(self.locks.items()):
try:
await lock.release()
except Exception:
pass
finally:
del self.locks[lock_id]
@asynccontextmanager
async def inventory_lock(self, sku_id: str, warehouse_id: str, timeout: int = 10):
"""库存锁上下文管理器
参数:
- sku_id: SKU ID
- warehouse_id: 仓库ID
- timeout: 锁超时时间
"""
lock = await self.lock_inventory(sku_id, warehouse_id, timeout)
if not lock:
raise Exception(f"Failed to acquire lock for {sku_id}:{warehouse_id}")
try:
yield lock
finally:
await self.unlock_inventory(sku_id, warehouse_id)
8.2.5 库存API路由 (app/api/v1/inventory.py)
python
from typing import List, Optional, Dict, Any
from fastapi import APIRouter, Depends, HTTPException, status, Query, BackgroundTasks
from sqlalchemy.orm import Session
import uuid
from app import schemas, models
from app.api import deps
from app.core.inventory_service import InventoryService
from app.crud import crud_product, crud_inventory, crud_sku
router = APIRouter()
@router.get("/{sku_id}", response_model=schemas.InventoryDetail)
async def get_inventory(
sku_id: str,
warehouse_id: Optional[str] = Query(None, description="仓库ID"),
db: Session = Depends(deps.get_db),
current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
"""获取库存信息"""
try:
uuid.UUID(sku_id)
except ValueError:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="无效的SKU ID格式"
)
# 验证SKU是否存在
sku = crud_sku.get(db, id=sku_id)
if not sku:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="SKU不存在"
)
# 创建库存服务实例
inventory_service = InventoryService(db)
# 获取库存信息
inventory = await inventory_service.get_inventory(sku_id, warehouse_id)
if not inventory:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="库存记录不存在"
)
return inventory
@router.post("/deduct", response_model=schemas.InventoryDeductResponse)
async def deduct_inventory(
*,
db: Session = Depends(deps.get_db),
deduct_request: schemas.InventoryDeductRequest,
current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
"""扣减库存"""
# 验证SKU是否存在
sku = crud_sku.get(db, id=deduct_request.sku_id)
if not sku:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="SKU不存在"
)
# 验证仓库是否存在(如果指定了仓库)
if deduct_request.warehouse_id:
warehouse = crud_inventory.get_warehouse(db, id=deduct_request.warehouse_id)
if not warehouse:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="仓库不存在"
)
# 创建库存服务实例
inventory_service = InventoryService(db)
# 执行库存扣减
result = await inventory_service.deduct_inventory(deduct_request)
if not result.success:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=result.message
)
return result
@router.post("/revert", response_model=schemas.InventoryDeductResponse)
async def revert_inventory(
*,
db: Session = Depends(deps.get_db),
revert_request: schemas.InventoryRevertRequest,
current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
"""回滚库存"""
# 验证SKU是否存在
sku = crud_sku.get(db, id=revert_request.sku_id)
if not sku:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="SKU不存在"
)
# 创建库存服务实例
inventory_service = InventoryService(db)
# 执行库存回滚
result = await inventory_service.revert_inventory(revert_request)
if not result.success:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=result.message
)
return result
@router.post("/adjust", response_model=schemas.InventoryDeductResponse)
async def adjust_inventory(
*,
db: Session = Depends(deps.get_db),
adjust_request: schemas.InventoryAdjustRequest,
current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
"""调整库存"""
# 验证SKU是否存在
sku = crud_sku.get(db, id=adjust_request.sku_id)
if not sku:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="SKU不存在"
)
# 验证仓库是否存在
warehouse = crud_inventory.get_warehouse(db, id=adjust_request.warehouse_id)
if not warehouse:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="仓库不存在"
)
# 创建库存服务实例
inventory_service = InventoryService(db)
# 执行库存调整
result = await inventory_service.adjust_inventory(adjust_request)
if not result.success:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=result.message
)
return result
@router.post("/batch-deduct", response_model=List[schemas.InventoryDeductResponse])
async def batch_deduct_inventory(
*,
db: Session = Depends(deps.get_db),
deduct_requests: List[schemas.InventoryDeductRequest],
current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
"""批量扣减库存"""
# 验证所有SKU是否存在
for request in deduct_requests:
sku = crud_sku.get(db, id=request.sku_id)
if not sku:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"SKU不存在: {request.sku_id}"
)
# 验证仓库是否存在(如果指定了仓库)
if request.warehouse_id:
warehouse = crud_inventory.get_warehouse(db, id=request.warehouse_id)
if not warehouse:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"仓库不存在: {request.warehouse_id}"
)
# 创建库存服务实例
inventory_service = InventoryService(db)
# 执行批量库存扣减
results = await inventory_service.batch_deduct_inventory(deduct_requests)
return results
@router.get("/{sku_id}/summary")
async def get_inventory_summary(
sku_id: str,
db: Session = Depends(deps.get_db),
current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
"""获取库存汇总信息"""
try:
uuid.UUID(sku_id)
except ValueError:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="无效的SKU ID格式"
)
# 验证SKU是否存在
sku = crud_sku.get(db, id=sku_id)
if not sku:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="SKU不存在"
)
# 创建库存服务实例
inventory_service = InventoryService(db)
# 获取库存汇总
summary = await inventory_service.get_inventory_summary(sku_id)
return {
"success": True,
"data": summary
}
@router.get("/warnings/low-stock")
async def get_low_stock_warnings(
threshold: Optional[int] = Query(None, ge=0, description="预警阈值"),
db: Session = Depends(deps.get_db),
current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
"""获取低库存预警列表"""
# 检查权限(通常只有管理员可以查看)
if not current_user.is_superuser and not current_user.is_staff:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="权限不足"
)
# 创建库存服务实例
inventory_service = InventoryService(db)
# 获取低库存商品
low_stock_items = await inventory_service.get_low_stock_products(threshold)
return {
"success": True,
"data": low_stock_items,
"total": len(low_stock_items),
"timestamp": datetime.utcnow().isoformat()
}
@router.get("/{sku_id}/turnover")
async def get_inventory_turnover(
sku_id: str,
period_days: int = Query(30, ge=1, le=365, description="统计周期(天)"),
db: Session = Depends(deps.get_db),
current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
"""获取库存周转率"""
try:
uuid.UUID(sku_id)
except ValueError:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="无效的SKU ID格式"
)
# 验证SKU是否存在
sku = crud_sku.get(db, id=sku_id)
if not sku:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="SKU不存在"
)
# 创建库存服务实例
inventory_service = InventoryService(db)
# 计算库存周转率
turnover_data = await inventory_service.calculate_inventory_turnover(sku_id, period_days)
if "error" in turnover_data:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=turnover_data["error"]
)
return {
"success": True,
"data": turnover_data
}
@router.get("/transactions")
async def get_inventory_transactions(
sku_id: Optional[str] = Query(None, description="SKU ID"),
warehouse_id: Optional[str] = Query(None, description="仓库ID"),
transaction_type: Optional[int] = Query(None, ge=1, le=5, description="交易类型"),
start_date: Optional[str] = Query(None, description="开始日期"),
end_date: Optional[str] = Query(None, description="结束日期"),
page: int = Query(1, ge=1, description="页码"),
size: int = Query(20, ge=1, le=100, description="每页数量"),
db: Session = Depends(deps.get_db),
current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
"""查询库存流水"""
# 检查权限(通常只有管理员可以查看)
if not current_user.is_superuser and not current_user.is_staff:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="权限不足"
)
# 构建查询
query = db.query(models.InventoryTransaction)
# 添加过滤条件
if sku_id:
try:
query = query.filter(models.InventoryTransaction.sku_id == uuid.UUID(sku_id))
except ValueError:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="无效的SKU ID格式"
)
if warehouse_id:
try:
query = query.filter(models.InventoryTransaction.warehouse_id == uuid.UUID(warehouse_id))
except ValueError:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="无效的仓库ID格式"
)
if transaction_type:
query = query.filter(models.InventoryTransaction.transaction_type == transaction_type)
if start_date:
query = query.filter(models.InventoryTransaction.created_at >= start_date)
if end_date:
query = query.filter(models.InventoryTransaction.created_at <= end_date)
# 计算总数
total = query.count()
# 分页查询
transactions = query.order_by(
models.InventoryTransaction.created_at.desc()
).offset((page - 1) * size).limit(size).all()
# 格式化结果
transaction_list = []
for tx in transactions:
transaction_list.append({
"id": str(tx.id),
"sku_id": str(tx.sku_id),
"warehouse_id": str(tx.warehouse_id),
"transaction_type": tx.transaction_type,
"transaction_type_name": self._get_transaction_type_name(tx.transaction_type),
"change_quantity": tx.change_quantity,
"before_quantity": tx.before_quantity,
"after_quantity": tx.after_quantity,
"reference_type": tx.reference_type,
"reference_no": tx.reference_no,
"operator_name": tx.operator_name,
"remark": tx.remark,
"created_at": tx.created_at.isoformat()
})
return {
"success": True,
"data": transaction_list,
"total": total,
"page": page,
"size": size,
"total_pages": (total + size - 1) // size
}
def _get_transaction_type_name(self, transaction_type: int) -> str:
"""获取交易类型名称"""
type_names = {
1: "入库",
2: "出库",
3: "调整",
4: "锁定",
5: "解锁"
}
return type_names.get(transaction_type, "未知")
@router.post("/warehouses", response_model=Dict[str, Any])
async def create_warehouse(
*,
db: Session = Depends(deps.get_db),
warehouse_in: schemas.WarehouseCreate,
current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
"""创建仓库"""
# 检查权限
if not current_user.is_superuser:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="只有管理员可以创建仓库"
)
# 检查仓库编码是否已存在
existing = crud_inventory.get_warehouse_by_code(db, code=warehouse_in.code)
if existing:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="仓库编码已存在"
)
# 如果设置为默认仓库,需要先取消其他仓库的默认设置
if warehouse_in.is_default:
db.query(models.Warehouse).filter(
models.Warehouse.is_default == True
).update({"is_default": False})
# 创建仓库
warehouse = crud_inventory.create_warehouse(db, obj_in=warehouse_in)
# 清除缓存
redis_client = get_redis_client()
await redis_client.delete("warehouse:default")
await redis_client.delete("warehouse:list")
return {
"success": True,
"message": "仓库创建成功",
"data": warehouse
}
@router.get("/warehouses", response_model=List[schemas.WarehouseInDB])
async def list_warehouses(
db: Session = Depends(deps.get_db),
current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
"""获取仓库列表"""
# 检查缓存
redis_client = get_redis_client()
cache_key = "warehouse:list"
cached_data = await redis_client.get(cache_key)
if cached_data:
import json
return json.loads(cached_data)
# 查询数据库
warehouses = crud_inventory.get_warehouses(db)
# 更新缓存
warehouse_list = [warehouse.to_dict() for warehouse in warehouses]
await redis_client.setex(cache_key, 3600, json.dumps(warehouse_list)) # 缓存1小时
return warehouse_list
@router.post("/inventory/settings/{inventory_id}")
async def update_inventory_settings(
*,
db: Session = Depends(deps.get_db),
inventory_id: str,
settings_in: Dict[str, Any],
current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
"""更新库存设置"""
# 检查权限
if not current_user.is_superuser and not current_user.is_staff:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="权限不足"
)
try:
inventory_uuid = uuid.UUID(inventory_id)
except ValueError:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="无效的库存ID格式"
)
# 查询库存记录
inventory = db.query(models.Inventory).filter(models.Inventory.id == inventory_uuid).first()
if not inventory:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="库存记录不存在"
)
# 更新设置
if "safety_stock" in settings_in:
inventory.safety_stock = settings_in["safety_stock"]
if "warning_threshold" in settings_in:
inventory.warning_threshold = settings_in["warning_threshold"]
if "max_stock" in settings_in:
inventory.max_stock = settings_in["max_stock"]
# 更新可用库存
inventory.update_available_quantity()
inventory.last_updated = datetime.utcnow()
db.commit()
# 清除缓存
cache_key = f"inventory:{inventory.sku_id}:{inventory.warehouse_id}"
await redis_client.delete(cache_key)
return {
"success": True,
"message": "库存设置更新成功",
"data": inventory
}
8.2.6 商品API路由 (app/api/v1/products.py)
python
from typing import List, Optional, Dict, Any
from fastapi import APIRouter, Depends, HTTPException, status, Query, File, UploadFile
from sqlalchemy.orm import Session
import uuid
from datetime import datetime
from app import schemas, models
from app.api import deps
from app.crud import crud_product, crud_sku, crud_category
from app.core.inventory_service import InventoryService
router = APIRouter()
@router.get("/", response_model=schemas.ProductListResponse)
async def list_products(
keyword: Optional[str] = Query(None, description="关键词"),
category_id: Optional[str] = Query(None, description="分类ID"),
brand_id: Optional[str] = Query(None, description="品牌ID"),
status: Optional[int] = Query(None, ge=0, le=2, description="状态"),
min_price: Optional[float] = Query(None, ge=0, description="最低价"),
max_price: Optional[float] = Query(None, ge=0, description="最高价"),
is_hot: Optional[bool] = Query(None, description="是否热销"),
is_new: Optional[bool] = Query(None, description="是否新品"),
is_recommended: Optional[bool] = Query(None, description="是否推荐"),
page: int = Query(1, ge=1, description="页码"),
size: int = Query(20, ge=1, le=100, description="每页数量"),
sort_by: str = Query("created_at", description="排序字段"),
sort_order: str = Query("desc", description="排序方向"),
db: Session = Depends(deps.get_db),
current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
"""获取商品列表"""
# 构建查询参数
query_params = schemas.ProductQueryParams(
keyword=keyword,
category_id=category_id,
brand_id=brand_id,
status=status,
min_price=min_price,
max_price=max_price,
is_hot=is_hot,
is_new=is_new,
is_recommended=is_recommended,
page=page,
size=size,
sort_by=sort_by,
sort_order=sort_order
)
# 查询商品
products, total = crud_product.get_multi(db, query_params=query_params)
# 获取每个商品的库存汇总
for product in products:
inventory_service = InventoryService(db)
summary = await inventory_service.get_inventory_summary(str(product.id))
product.inventory_summary = summary
return {
"success": True,
"data": products,
"total": total,
"page": page,
"size": size,
"total_pages": (total + size - 1) // size
}
@router.post("/", response_model=schemas.ProductDetail, status_code=status.HTTP_201_CREATED)
async def create_product(
*,
db: Session = Depends(deps.get_db),
product_in: schemas.ProductCreate,
current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
"""创建商品"""
# 检查权限
if not current_user.is_superuser and not current_user.is_staff:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="只有管理员可以创建商品"
)
# 检查商品编码是否已存在
existing = crud_product.get_by_code(db, code=product_in.product_code)
if existing:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="商品编码已存在"
)
# 检查分类是否存在
if product_in.category_id:
category = crud_category.get(db, id=product_in.category_id)
if not category:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="分类不存在"
)
# 创建商品
product = crud_product.create(db, obj_in=product_in)
# 创建默认SKU
default_sku = schemas.SKUCreate(
product_id=str(product.id),
sku_code=f"{product_in.product_code}-DEFAULT",
sku_name=product_in.name,
price=product_in.market_price,
cost_price=product_in.cost_price,
is_default=True
)
sku = crud_sku.create(db, obj_in=default_sku)
# 获取默认仓库
inventory_service = InventoryService(db)
try:
warehouse_id = await inventory_service._get_default_warehouse_id()
# 创建初始库存记录
inventory = models.Inventory(
sku_id=sku.id,
warehouse_id=uuid.UUID(warehouse_id),
quantity=0,
locked_quantity=0,
safety_stock=0,
status=1
)
inventory.update_available_quantity()
db.add(inventory)
db.commit()
except Exception as e:
# 如果创建库存失败,回滚商品创建
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"创建库存失败: {str(e)}"
)
return product
@router.get("/{product_id}", response_model=schemas.ProductDetail)
async def get_product(
product_id: str,
db: Session = Depends(deps.get_db),
current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
"""获取商品详情"""
try:
uuid.UUID(product_id)
except ValueError:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="无效的商品ID格式"
)
product = crud_product.get(db, id=product_id)
if not product:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="商品不存在"
)
# 增加浏览量
product.view_count += 1
db.commit()
# 获取库存汇总
inventory_service = InventoryService(db)
summary = await inventory_service.get_inventory_summary(product_id)
product.inventory_summary = summary
return product
@router.put("/{product_id}", response_model=schemas.ProductDetail)
async def update_product(
*,
db: Session = Depends(deps.get_db),
product_id: str,
product_in: schemas.ProductUpdate,
current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
"""更新商品"""
# 检查权限
if not current_user.is_superuser and not current_user.is_staff:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="只有管理员可以更新商品"
)
try:
uuid.UUID(product_id)
except ValueError:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="无效的商品ID格式"
)
product = crud_product.get(db, id=product_id)
if not product:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="商品不存在"
)
# 如果更新分类,检查分类是否存在
if product_in.category_id:
category = crud_category.get(db, id=product_in.category_id)
if not category:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="分类不存在"
)
# 更新商品
product = crud_product.update(db, db_obj=product, obj_in=product_in)
# 清除商品缓存
redis_client = get_redis_client()
await redis_client.delete(f"product:{product_id}")
return product
@router.delete("/{product_id}")
async def delete_product(
*,
db: Session = Depends(deps.get_db),
product_id: str,
current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
"""删除商品(软删除)"""
# 检查权限
if not current_user.is_superuser:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="只有超级管理员可以删除商品"
)
try:
uuid.UUID(product_id)
except ValueError:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="无效的商品ID格式"
)
product = crud_product.get(db, id=product_id)
if not product:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="商品不存在"
)
# 软删除:将状态改为下架
product.status = 0
product.updated_at = datetime.utcnow()
db.commit()
# 清除缓存
redis_client = get_redis_client()
await redis_client.delete(f"product:{product_id}")
return {
"success": True,
"message": "商品已下架"
}
@router.post("/{product_id}/skus", response_model=schemas.SKUDetail)
async def create_sku(
*,
db: Session = Depends(deps.get_db),
product_id: str,
sku_in: schemas.SKUCreate,
current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
"""为商品创建SKU"""
# 检查权限
if not current_user.is_superuser and not current_user.is_staff:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="只有管理员可以创建SKU"
)
try:
uuid.UUID(product_id)
except ValueError:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="无效的商品ID格式"
)
# 检查商品是否存在
product = crud_product.get(db, id=product_id)
if not product:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="商品不存在"
)
# 检查SKU编码是否已存在
existing = crud_sku.get_by_code(db, code=sku_in.sku_code)
if existing:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="SKU编码已存在"
)
# 如果设置为默认SKU,需要先取消其他SKU的默认设置
if sku_in.is_default:
db.query(models.SKU).filter(
models.SKU.product_id == uuid.UUID(product_id),
models.SKU.is_default == True
).update({"is_default": False})
# 创建SKU
sku = crud_sku.create(db, obj_in=sku_in)
# 获取默认仓库并创建库存记录
inventory_service = InventoryService(db)
try:
warehouse_id = await inventory_service._get_default_warehouse_id()
# 创建初始库存记录
inventory = models.Inventory(
sku_id=sku.id,
warehouse_id=uuid.UUID(warehouse_id),
quantity=0,
locked_quantity=0,
safety_stock=0,
status=1
)
inventory.update_available_quantity()
db.add(inventory)
db.commit()
except Exception as e:
# 如果创建库存失败,回滚SKU创建
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"创建库存失败: {str(e)}"
)
return sku
@router.get("/{product_id}/skus", response_model=List[schemas.SKUDetail])
async def list_product_skus(
product_id: str,
db: Session = Depends(deps.get_db),
current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
"""获取商品下的所有SKU"""
try:
uuid.UUID(product_id)
except ValueError:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="无效的商品ID格式"
)
# 检查商品是否存在
product = crud_product.get(db, id=product_id)
if not product:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="商品不存在"
)
# 获取SKU列表
skus = crud_sku.get_by_product(db, product_id=product_id)
# 为每个SKU获取库存信息
inventory_service = InventoryService(db)
for sku in skus:
inventory = await inventory_service.get_inventory(str(sku.id))
sku.inventory = inventory
return skus
@router.post("/{product_id}/images")
async def upload_product_image(
*,
db: Session = Depends(deps.get_db),
product_id: str,
image: UploadFile = File(...),
is_main: bool = Query(False, description="是否主图"),
sort_order: int = Query(0, description="排序"),
current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
"""上传商品图片"""
# 检查权限
if not current_user.is_superuser and not current_user.is_staff:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="只有管理员可以上传图片"
)
try:
uuid.UUID(product_id)
except ValueError:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="无效的商品ID格式"
)
# 检查商品是否存在
product = crud_product.get(db, id=product_id)
if not product:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="商品不存在"
)
# 这里简化处理,实际应该上传到云存储
# 生成唯一的文件名
import os
from pathlib import Path
# 获取文件扩展名
file_ext = os.path.splitext(image.filename)[1]
new_filename = f"{uuid.uuid4()}{file_ext}"
# 保存文件(实际项目中应该使用云存储)
upload_dir = Path("uploads/products")
upload_dir.mkdir(parents=True, exist_ok=True)
file_path = upload_dir / new_filename
# 读取文件内容并保存
contents = await image.read()
with open(file_path, "wb") as f:
f.write(contents)
# 如果设置为新主图,需要先取消其他图片的主图设置
if is_main:
db.query(models.ProductImage).filter(
models.ProductImage.product_id == uuid.UUID(product_id),
models.ProductImage.is_main == True
).update({"is_main": False})
# 创建图片记录
image_record = models.ProductImage(
product_id=uuid.UUID(product_id),
url=f"/uploads/products/{new_filename}",
is_main=is_main,
sort_order=sort_order
)
db.add(image_record)
db.commit()
return {
"success": True,
"message": "图片上传成功",
"data": {
"id": str(image_record.id),
"url": image_record.url,
"is_main": image_record.is_main,
"sort_order": image_record.sort_order
}
}
@router.get("/search/suggestions")
async def search_suggestions(
keyword: str = Query(..., min_length=1, description="搜索关键词"),
limit: int = Query(10, ge=1, le=50, description="返回数量"),
db: Session = Depends(deps.get_db),
) -> Any:
"""搜索建议"""
# 简单实现,实际应该使用Elasticsearch等搜索引擎
suggestions = []
# 搜索商品名称
products = db.query(models.Product).filter(
models.Product.name.ilike(f"%{keyword}%"),
models.Product.status == 1
).limit(limit).all()
for product in products:
suggestions.append({
"type": "product",
"id": str(product.id),
"name": product.name,
"code": product.product_code,
"price": float(product.market_price)
})
# 搜索SKU编码和名称
skus = db.query(models.SKU).join(
models.Product, models.SKU.product_id == models.Product.id
).filter(
and_(
or_(
models.SKU.sku_code.ilike(f"%{keyword}%"),
models.SKU.sku_name.ilike(f"%{keyword}%")
),
models.Product.status == 1,
models.SKU.is_active == True
)
).limit(limit).all()
for sku in skus:
suggestions.append({
"type": "sku",
"id": str(sku.id),
"name": sku.sku_name,
"code": sku.sku_code,
"product_id": str(sku.product_id),
"product_name": sku.product.name if sku.product else "",
"price": float(sku.price)
})
return {
"success": True,
"keyword": keyword,
"suggestions": suggestions
}
9. 性能优化策略
9.1 缓存策略
缓存命中
缓存未命中
库存查询请求
缓存检查
返回缓存数据
查询数据库
写入缓存
库存更新操作
更新数据库
删除缓存
发送变更消息
9.2 数据库优化
-
索引策略:
- SKU表:sku_code, product_id, is_active
- 库存表:sku_id, warehouse_id, (sku_id, warehouse_id)复合索引
- 库存流水表:sku_id, created_at, (sku_id, transaction_type)
-
查询优化:
- 使用分页避免全表扫描
- 避免N+1查询问题
- 使用EXPLAIN分析查询计划
10. 测试策略
10.1 单元测试
python
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
import asyncio
from app.main import app
from app.database import Base, get_db
from app.core.inventory_service import InventoryService
# 测试数据库
SQLALCHEMY_TEST_DATABASE_URL = "sqlite:///./test_inventory.db"
engine = create_engine(SQLALCHEMY_TEST_DATABASE_URL, connect_args={"check_same_thread": False})
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def override_get_db():
try:
db = TestingSessionLocal()
yield db
finally:
db.close()
app.dependency_overrides[get_db] = override_get_db
client = TestClient(app)
def setup_module():
Base.metadata.create_all(bind=engine)
def teardown_module():
Base.metadata.drop_all(bind=engine)
def test_create_product():
"""测试创建商品"""
product_data = {
"product_code": "TEST001",
"name": "测试商品",
"category_id": "test-category-id",
"market_price": 100.0,
"cost_price": 50.0,
"status": 1
}
response = client.post("/api/v1/products", json=product_data)
assert response.status_code == 201
data = response.json()
assert data["success"] == True
assert data["data"]["product_code"] == "TEST001"
def test_deduct_inventory():
"""测试库存扣减"""
# 先创建测试数据
product_data = {
"product_code": "TEST002",
"name": "测试扣减商品",
"category_id": "test-category-id",
"market_price": 200.0,
"cost_price": 100.0,
"status": 1
}
product_response = client.post("/api/v1/products", json=product_data)
product_id = product_response.json()["data"]["id"]
# 获取SKU ID
skus_response = client.get(f"/api/v1/products/{product_id}/skus")
sku_id = skus_response.json()[0]["id"]
# 调整库存到有库存的状态
adjust_data = {
"sku_id": sku_id,
"warehouse_id": "default-warehouse-id",
"quantity": 100,
"reason": "测试调整",
"reference_type": 3
}
client.post("/api/v1/inventory/adjust", json=adjust_data)
# 测试扣减库存
deduct_data = {
"sku_id": sku_id,
"quantity": 10,
"order_id": "test-order-001",
"order_item_id": "test-order-item-001",
"deduct_type": 1
}
response = client.post("/api/v1/inventory/deduct", json=deduct_data)
assert response.status_code == 200
data = response.json()
assert data["success"] == True
assert data["deducted_quantity"] == 10
def test_inventory_low_stock_warning():
"""测试低库存预警"""
# 创建库存服务实例
db = TestingSessionLocal()
inventory_service = InventoryService(db)
# 测试低库存判断逻辑
class MockInventory:
def __init__(self):
self.available_quantity = 5
self.safety_stock = 10
self.warning_threshold = None
def is_low_stock(self):
return self.available_quantity <= self.safety_stock
mock_inventory = MockInventory()
assert mock_inventory.is_low_stock() == True
mock_inventory.available_quantity = 15
assert mock_inventory.is_low_stock() == False
def test_concurrent_inventory_deduction():
"""测试并发库存扣减"""
import threading
results = []
def deduct_thread(sku_id, quantity, order_id):
"""扣减线程"""
deduct_data = {
"sku_id": sku_id,
"quantity": quantity,
"order_id": order_id,
"order_item_id": f"{order_id}-item",
"deduct_type": 1
}
response = client.post("/api/v1/inventory/deduct", json=deduct_data)
results.append(response.json())
# 创建测试商品和库存
product_data = {
"product_code": "CONCURRENT_TEST",
"name": "并发测试商品",
"category_id": "test-category-id",
"market_price": 300.0,
"cost_price": 150.0,
"status": 1
}
product_response = client.post("/api/v1/products", json=product_data)
product_id = product_response.json()["data"]["id"]
skus_response = client.get(f"/api/v1/products/{product_id}/skus")
sku_id = skus_response.json()[0]["id"]
# 设置库存为50
adjust_data = {
"sku_id": sku_id,
"warehouse_id": "default-warehouse-id",
"quantity": 50,
"reason": "并发测试准备",
"reference_type": 3
}
client.post("/api/v1/inventory/adjust", json=adjust_data)
# 创建多个线程同时扣减库存
threads = []
for i in range(10):
thread = threading.Thread(
target=deduct_thread,
args=(sku_id, 10, f"order-concurrent-{i}")
)
threads.append(thread)
thread.start()
# 等待所有线程完成
for thread in threads:
thread.join()
# 检查结果:最多只有5个成功(库存50,每次扣10)
success_count = sum(1 for r in results if r.get("success") == True)
assert success_count == 5 # 50/10 = 5
# 检查失败的都是因为库存不足
for r in results:
if not r.get("success"):
assert "库存不足" in r.get("message", "")
10.2 压力测试
python
import asyncio
import time
import statistics
from typing import List
import aiohttp
import asyncpg
class InventoryStressTest:
"""库存压力测试"""
def __init__(self, base_url: str, concurrency: int = 100):
self.base_url = base_url
self.concurrency = concurrency
self.results = []
async def test_concurrent_deductions(self, sku_id: str, total_requests: int = 1000) -> dict:
"""测试并发扣减"""
semaphore = asyncio.Semaphore(self.concurrency)
async def make_request(session, request_id: int):
async with semaphore:
start_time = time.time()
try:
deduct_data = {
"sku_id": sku_id,
"quantity": 1,
"order_id": f"stress-test-order-{request_id}",
"order_item_id": f"stress-test-item-{request_id}",
"deduct_type": 1
}
async with session.post(
f"{self.base_url}/inventory/deduct",
json=deduct_data
) as response:
end_time = time.time()
elapsed = end_time - start_time
result = {
"request_id": request_id,
"status": response.status,
"elapsed": elapsed,
"success": response.status == 200
}
if response.status == 200:
data = await response.json()
result["data"] = data
return result
except Exception as e:
return {
"request_id": request_id,
"status": 0,
"elapsed": time.time() - start_time,
"success": False,
"error": str(e)
}
async with aiohttp.ClientSession() as session:
tasks = [make_request(session, i) for i in range(total_requests)]
results = await asyncio.gather(*tasks)
# 分析结果
successful = [r for r in results if r["success"]]
failed = [r for r in results if not r["success"]]
response_times = [r["elapsed"] for r in results]
return {
"total_requests": total_requests,
"successful": len(successful),
"failed": len(failed),
"success_rate": len(successful) / total_requests,
"avg_response_time": statistics.mean(response_times),
"p95_response_time": sorted(response_times)[int(0.95 * len(response_times))],
"max_response_time": max(response_times),
"min_response_time": min(response_times)
}
async def test_inventory_accuracy(self, sku_id: str, warehouse_id: str, db_config: dict) -> dict:
"""测试库存准确性"""
# 连接数据库
conn = await asyncpg.connect(**db_config)
try:
# 查询数据库中的实际库存
db_result = await conn.fetchrow(
"SELECT quantity, locked_quantity, available_quantity FROM inventory WHERE sku_id = $1 AND warehouse_id = $2",
sku_id, warehouse_id
)
# 查询库存流水计算的理论库存
tx_result = await conn.fetch(
"SELECT SUM(change_quantity) as total_change FROM inventory_transactions WHERE sku_id = $1 AND warehouse_id = $2",
sku_id, warehouse_id
)
total_change = tx_result[0]["total_change"] or 0
# 查询API返回的库存
async with aiohttp.ClientSession() as session:
async with session.get(
f"{self.base_url}/inventory/{sku_id}?warehouse_id={warehouse_id}"
) as response:
api_data = await response.json()
api_quantity = api_data["data"]["available_quantity"] if api_data["success"] else None
return {
"database_quantity": db_result["available_quantity"] if db_result else None,
"calculated_from_transactions": total_change,
"api_quantity": api_quantity,
"consistent": (
db_result and
api_quantity is not None and
db_result["available_quantity"] == api_quantity
)
}
finally:
await conn.close()
11. 部署与监控
11.1 Docker部署配置
dockerfile
# Dockerfile
FROM python:3.9-slim
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV PYTHONPATH=/app
# 安装系统依赖
RUN apt-get update \
&& apt-get install -y --no-install-recommends gcc libpq-dev curl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# 安装Python依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY . .
# 创建非root用户
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# 暴露端口
EXPOSE 8000
# 启动命令
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
11.2 监控指标
关键监控指标:
-
性能指标:
- 库存查询响应时间(P50, P95, P99)
- 库存更新操作耗时
- API调用成功率
- 系统吞吐量(QPS)
-
业务指标:
- 商品总数和SKU总数
- 低库存商品数量
- 库存周转率
- 超卖发生率
-
系统指标:
- 数据库连接池使用率
- Redis内存使用率
- 消息队列积压情况
- 错误率和异常数量
12. 总结与展望
本文详细介绍了电商平台商品管理与库存系统的设计与实现。我们设计了一个完整的系统架构,包括商品管理、SKU管理、多仓库库存管理、库存流水记录等核心功能。通过采用分布式锁、缓存策略、消息队列等技术,保证了系统的高性能和强一致性。
12.1 核心亮点
- 强一致性保障:通过分布式锁和数据库事务,有效防止超卖问题
- 高性能设计:采用多级缓存策略,优化查询性能
- 可扩展架构:支持多仓库、多SKU的复杂场景
- 完善的监控:提供全面的业务和系统监控指标
12.2 未来扩展方向
- 智能补货系统:基于销售预测和库存周转率的智能补货建议
- 供应链协同:与供应商系统集成,实现自动补货
- 库存调拨优化:基于地理位置和销售数据的智能调拨策略
- 库存金融:支持库存质押、供应链金融等增值服务
12.3 注意事项
- 数据一致性:在分布式环境下,需要持续关注和优化数据一致性策略
- 系统监控:生产环境需要完善的监控和告警机制
- 容灾备份:定期备份关键数据,设计容灾恢复方案
- 安全防护:加强API安全防护,防止恶意攻击
本系统为电商平台提供了一个坚实的基础,可以根据具体业务需求进行扩展和优化,满足不同规模电商平台的库存管理需求。