1. 项目背景与需求分析
超市零售行业正面临数字化转型的关键时期。传统的手工记账和Excel管理方式已经无法满足现代零售对实时数据、精准库存和高效运营的需求。我在为本地一家连锁超市做技术咨询时,发现他们存在以下典型痛点:
- 库存数据滞后:手工盘点导致库存更新延迟,经常出现"系统有库存但货架没货"的情况
- 销售分析困难:依赖月度报表,无法实时掌握热销商品和滞销品
- 订单处理低效:收银台高峰期经常排队,退换货流程繁琐
- 多店协同薄弱:各分店数据孤立,无法实现库存调拨和统一促销
针对这些问题,我们决定开发一套基于Django的超市管理系统,核心目标包括:
- 实现商品信息的数字化管理
- 建立实时库存监控机制
- 优化订单处理流程
- 提供数据驱动的经营决策支持
2. 技术架构设计
2.1 整体架构方案
系统采用B/S架构,分为以下三个层次:
code复制[浏览器客户端] ←HTTP→ [Django应用服务器] ←ORM→ [MySQL数据库]
↑
│
[Redis缓存] [Celery Worker]
前端采用Bootstrap 5实现响应式布局,确保在收银台PC、经理平板和老板手机上都能够良好展示。后端选择Django 4.0 LTS版本,主要考虑因素包括:
- 自带Admin后台,快速实现基础CRUD
- ORM支持多种数据库,便于后期扩展
- 完善的中间件和信号机制
- 活跃的社区和丰富的第三方包
2.2 数据库设计要点
商品信息表的设计尤为关键,我们采用"宽表+垂直分表"的策略:
python复制class Product(models.Model):
barcode = models.CharField(max_length=20, unique=True) # 国际条码
name = models.CharField(max_length=100)
category = models.ForeignKey(Category, on_delete=models.PROTECT)
purchase_price = models.DecimalField(max_digits=10, decimal_places=2)
retail_price = models.DecimalField(max_digits=10, decimal_places=2)
spec = models.CharField(max_length=50) # 规格如"500ml/瓶"
class ProductStock(models.Model):
product = models.OneToOneField(Product, on_delete=models.CASCADE)
current = models.IntegerField(default=0) # 当前库存
safety = models.IntegerField(default=10) # 安全库存
updated = models.DateTimeField(auto_now=True)
重要提示:商品表与库存表分离的设计可以避免高频库存更新导致的商品信息锁表问题
3. 核心功能实现
3.1 实时库存管理
库存变动的原子操作实现:
python复制@transaction.atomic
def update_stock(product_id, delta):
"""更新库存的原子操作
Args:
delta: 正数表示入库,负数表示出库
"""
stock = ProductStock.objects.select_for_update().get(product_id=product_id)
if stock.current + delta < 0:
raise ValueError("库存不足")
stock.current += delta
stock.save()
# 触发安全库存检查
if stock.current < stock.safety:
send_stock_alert.delay(product_id)
配合Redis实现库存缓存,减轻数据库压力:
python复制def get_cached_stock(product_id):
cache_key = f"stock:{product_id}"
stock = cache.get(cache_key)
if stock is None:
stock = ProductStock.objects.get(product_id=product_id).current
cache.set(cache_key, stock, timeout=60) # 60秒缓存
return stock
3.2 智能补货算法
基于ABC分类和安全库存的计算:
python复制def calculate_safety_stock(product):
"""计算安全库存量"""
# 获取过去30天销售数据
sales = SaleRecord.objects.filter(
product=product,
date__gte=timezone.now()-timedelta(days=30)
).aggregate(
avg_sales=Avg('quantity'),
std_sales=StdDev('quantity')
)
lead_time = get_supplier_lead_time(product.supplier) # 供应商交货周期
# 安全库存 = Z值 × 销售标准差 × √交货周期
z_score = 1.65 # 对应95%的服务水平
safety_stock = z_score * sales['std_sales'] * math.sqrt(lead_time)
return max(round(safety_stock), 5) # 最小安全库存为5
4. 性能优化实践
4.1 高并发订单处理
使用Django Channels实现WebSocket实时订单通知:
python复制# consumers.py
class OrderConsumer(AsyncWebsocketConsumer):
async def connect(self):
await self.channel_layer.group_add("cashiers", self.channel_name)
await self.accept()
async def new_order(self, event):
await self.send(text_data=json.dumps({
"type": "order.new",
"order_id": event["order_id"]
}))
# views.py
@csrf_exempt
def create_order(request):
# ...订单创建逻辑...
async_to_sync(channel_layer.group_send)(
"cashiers",
{"type": "new_order", "order_id": order.id}
)
4.2 报表查询优化
针对大数据量销售报表,采用以下优化措施:
- 建立物化视图
sql复制CREATE MATERIALIZED VIEW daily_sales_mv AS
SELECT
product_id,
DATE(created_at) AS sale_date,
SUM(quantity) AS total_quantity,
SUM(amount) AS total_amount
FROM sales_record
GROUP BY product_id, DATE(created_at);
- 使用Django的annotate和Prefetch优化查询:
python复制def get_daily_report(date):
return Product.objects.annotate(
sale_count=Coalesce(
Sum('sale_records__quantity',
filter=Q(sale_records__created_at__date=date)),
0
),
sale_amount=Coalesce(
Sum('sale_records__amount',
filter=Q(sale_records__created_at__date=date)),
0
)
).prefetch_related(
Prefetch('sale_records',
queryset=SaleRecord.objects.filter(created_at__date=date))
)
5. 部署架构与监控
5.1 生产环境部署方案
使用Docker Compose编排服务:
yaml复制version: '3.8'
services:
web:
build: .
command: gunicorn config.wsgi:application --bind 0.0.0.0:8000
volumes:
- static:/app/static
depends_on:
- redis
- db
environment:
- DJANGO_SETTINGS_MODULE=config.settings.production
db:
image: mysql:8.0
volumes:
- db_data:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=${DB_PASSWORD}
- MYSQL_DATABASE=${DB_NAME}
redis:
image: redis:6-alpine
celery:
build: .
command: celery -A config worker -l info
depends_on:
- redis
- db
volumes:
db_data:
static:
5.2 关键监控指标
配置Prometheus监控以下核心指标:
- 订单处理延迟(histogram)
- 库存同步延迟(gauge)
- API响应时间(summary)
- 数据库连接池使用率(gauge)
- Celery任务队列长度(gauge)
6. 踩坑经验分享
6.1 库存超卖问题
初期直接使用Django ORM更新库存时出现并发超卖,解决方案:
- 使用select_for_update()实现行级锁
- 数据库设置事务隔离级别为REPEATABLE READ
- 添加乐观锁版本控制字段
python复制class ProductStock(models.Model):
version = models.IntegerField(default=0)
def save(self, *args, **kwargs):
self.version += 1
super().save(*args, **kwargs)
@transaction.atomic
def deduct_stock(product_id, quantity):
stock = ProductStock.objects.select_for_update().get(product_id=product_id)
if stock.current < quantity:
raise ValueError("库存不足")
stock.current -= quantity
stock.save()
6.2 扫码枪集成问题
不同品牌扫码枪的输出格式差异导致入库异常,最终解决方案:
- 开发统一的输入解析中间件
- 支持自动识别以下格式:
- 纯条码模式(如"690123456789")
- 带前缀模式(如"EN13:690123456789")
- 带校验位模式(如"690123456789\n")
python复制class BarcodeMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.method == 'POST' and 'barcode' in request.POST:
raw = request.POST['barcode']
request.parsed_barcode = self.clean_barcode(raw)
return self.get_response(request)
def clean_barcode(self, raw):
# 移除前后空白字符
cleaned = raw.strip()
# 处理带前缀的情况
if ':' in cleaned:
_, cleaned = cleaned.split(':', 1)
# 移除校验位
if len(cleaned) > 13:
cleaned = cleaned[:13]
return cleaned
7. 扩展功能开发
7.1 会员积分系统
实现会员消费积分和抵扣功能:
python复制class Member(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
points = models.IntegerField(default=0)
level = models.IntegerField(default=1) # 会员等级
@transaction.atomic
def process_payment(order, use_points=False):
# ...正常支付逻辑...
if use_points:
points_used = min(order.member.points, order.total * 100) # 100积分抵1元
order.points_used = points_used
order.total -= points_used / 100
order.member.points -= points_used
order.member.save()
# 新增积分 = 实付金额 × 积分系数
points_added = int(order.total * get_point_rate(order.member.level))
order.member.points += points_added
order.member.save()
order.points_added = points_added
order.save()
7.2 供应商协同接口
开发供应商API对接功能:
python复制class SupplierAPIView(APIView):
authentication_classes = [TokenAuthentication]
def get(self, request, format=None):
# 供应商查询库存接口
products = Product.objects.filter(
supplier=request.user.supplier
).annotate(
need_order=Case(
When(stock__current__lt=F('stock__safety'), then=Value(1)),
default=Value(0),
output_field=IntegerField()
)
)
serializer = SupplierProductSerializer(products, many=True)
return Response(serializer.data)
def post(self, request, format=None):
# 供应商确认发货接口
shipment = ShipmentSerializer(data=request.data)
if shipment.is_valid():
shipment.save()
return Response({"status": "confirmed"})
return Response(shipment.errors, status=400)