第三部分:高级特性与性能优化
3.1 缓存策略设计
电商系统需要高性能的缓存策略来应对高并发访问。
infrastructure/caching/product_cache.py:
python
import redis
from django.conf import settings
from django.core.cache import cache
import json
import hashlib
from datetime import datetime, timedelta
from product.models import Product, SKU
class ProductCacheService:
"""商品缓存服务"""
def __init__(self):
self.redis_client = redis.Redis(
host=settings.REDIS_HOST,
port=settings.REDIS_PORT,
db=settings.REDIS_DB,
decode_responses=True
)
self.product_cache_prefix = "product:"
self.category_cache_prefix = "category:products:"
self.hot_cache_prefix = "hot:products"
self.cache_ttl = 3600 # 1小时
def get_product_detail(self, product_id):
"""获取商品详情缓存"""
cache_key = f"{self.product_cache_prefix}{product_id}"
# 尝试从缓存获取
cached_data = self.redis_client.get(cache_key)
if cached_data:
return json.loads(cached_data)
# 缓存未命中,从数据库获取
try:
product = Product.objects.select_related('category').get(
id=product_id,
status='approved'
)
# 获取SKU列表
skus = product.skus.filter(is_active=True).values(
'id', 'sku_code', 'name', 'price', 'stock'
)
# 获取商品图片
images = product.images.all().values('image', 'alt_text', 'sort_order')
# 构建缓存数据
cache_data = {
'id': product.id,
'name': product.name,
'category': {
'id': product.category.id,
'name': product.category.name
},
'brand': product.brand,
'main_image': product.main_image.url if product.main_image else None,
'description': product.description,
'base_price': str(product.base_price),
'total_stock': product.total_stock,
'total_sales': product.total_sales,
'is_recommended': product.is_recommended,
'is_hot': product.is_hot,
'is_new': product.is_new,
'skus': list(skus),
'images': list(images),
'cached_at': datetime.now().isoformat()
}
# 设置缓存
self.redis_client.setex(
cache_key,
self.cache_ttl,
json.dumps(cache_data, ensure_ascii=False)
)
return cache_data
except Product.DoesNotExist:
return None
def get_category_products(self, category_id, page=1, page_size=20):
"""获取分类商品列表缓存"""
cache_key = f"{self.category_cache_prefix}{category_id}:page{page}:size{page_size}"
# 尝试从缓存获取
cached_data = self.redis_client.get(cache_key)
if cached_data:
return json.loads(cached_data)
# 缓存未命中,从数据库获取
from django.core.paginator import Paginator
products = Product.objects.filter(
category_id=category_id,
status='approved'
).select_related('category').order_by('-created_at')
paginator = Paginator(products, page_size)
page_obj = paginator.get_page(page)
# 构建缓存数据
cache_data = {
'items': [
{
'id': p.id,
'name': p.name,
'category': p.category.name,
'main_image': p.main_image.url if p.main_image else None,
'base_price': str(p.base_price),
'total_stock': p.total_stock,
'total_sales': p.total_sales,
'is_recommended': p.is_recommended,
'is_hot': p.is_hot,
'is_new': p.is_new
}
for p in page_obj.object_list
],
'total': paginator.count,
'page': page,
'page_size': page_size,
'has_next': page_obj.has_next(),
'has_previous': page_obj.has_previous(),
'cached_at': datetime.now().isoformat()
}
# 设置缓存
self.redis_client.setex(
cache_key,
self.cache_ttl,
json.dumps(cache_data, ensure_ascii=False)
)
return cache_data
def get_hot_products(self, limit=10):
"""获取热销商品缓存"""
cache_key = f"{self.hot_cache_prefix}:{limit}"
# 尝试从缓存获取
cached_data = self.redis_client.get(cache_key)
if cached_data:
return json.loads(cached_data)
# 缓存未命中,从数据库获取
products = Product.objects.filter(
status='approved',
is_hot=True
).select_related('category').order_by('-total_sales')[:limit]
# 构建缓存数据
cache_data = {
'items': [
{
'id': p.id,
'name': p.name,
'category': p.category.name,
'main_image': p.main_image.url if p.main_image else None,
'base_price': str(p.base_price),
'total_sales': p.total_sales,
'price_range': self._get_price_range(p.id)
}
for p in products
],
'cached_at': datetime.now().isoformat()
}
# 设置缓存
self.redis_client.setex(
cache_key,
self.cache_ttl,
json.dumps(cache_data, ensure_ascii=False)
)
return cache_data
def _get_price_range(self, product_id):
"""获取商品价格区间"""
skus = SKU.objects.filter(
product_id=product_id,
is_active=True
).aggregate(
min_price=models.Min('price'),
max_price=models.Max('price')
)
if skus['min_price'] and skus['max_price']:
if skus['min_price'] == skus['max_price']:
return str(skus['min_price'])
return f"{skus['min_price']} - {skus['max_price']}"
return None
def invalidate_product_cache(self, product_id):
"""失效商品缓存"""
cache_key = f"{self.product_cache_prefix}{product_id}"
self.redis_client.delete(cache_key)
# 同时失效相关的分类缓存
product = Product.objects.get(id=product_id)
pattern = f"{self.category_cache_prefix}{product.category_id}:*"
keys = self.redis_client.keys(pattern)
if keys:
self.redis_client.delete(*keys)
# 失效热销商品缓存
hot_keys = self.redis_client.keys(f"{self.hot_cache_prefix}:*")
if hot_keys:
self.redis_client.delete(*hot_keys)
def warm_up_cache(self, product_ids=None):
"""预热缓存"""
if product_ids is None:
# 预热最近100个商品
products = Product.objects.filter(
status='approved'
).order_by('-created_at')[:100]
product_ids = [p.id for p in products]
for product_id in product_ids:
self.get_product_detail(product_id)
# 预热热销商品
self.get_hot_products()
self.get_hot_products(20)
3.2 订单状态机实现
为了管理复杂的订单状态流转,我们需要一个健壮的状态机。
apps/order/state_machine.py:
from enum import Enum
from typing import Dict, List, Set, Optional
from django.core.exceptions import ValidationError
import logging
logger = logging.getLogger(__name__)
class OrderState(str, Enum):
"""订单状态枚举"""
PENDING = 'pending' # 待支付
PAID = 'paid' # 已支付
SHIPPED = 'shipped' # 已发货
DELIVERED = 'delivered' # 已收货
COMPLETED = 'completed' # 已完成
CANCELLED = 'cancelled' # 已取消
REFUNDED = 'refunded' # 已退款
@classmethod
def choices(cls):
"""Django choices格式"""
return [(key.value, key.name) for key in cls]
class OrderTransition:
"""订单状态转换"""
def __init__(
self,
from_state: OrderState,
to_state: OrderState,
condition: Optional[callable] = None,
action: Optional[callable] = None
):
self.from_state = from_state
self.to_state = to_state
self.condition = condition
self.action = action
def can_transition(self, order, **kwargs) -> bool:
"""检查是否可以转换"""
if self.condition:
return self.condition(order, **kwargs)
return True
def execute(self, order, **kwargs):
"""执行转换"""
if self.action:
self.action(order, **kwargs)
class OrderStateMachine:
"""订单状态机"""
def __init__(self):
self.transitions: Dict[OrderState, List[OrderTransition]] = {}
self._initialize_transitions()
def _initialize_transitions(self):
"""初始化状态转换规则"""
# 待支付 -> 已支付
self.add_transition(
OrderState.PENDING,
OrderState.PAID,
condition=self._can_pay,
action=self._on_pay
)
# 待支付 -> 已取消
self.add_transition(
OrderState.PENDING,
OrderState.CANCELLED,
condition=self._can_cancel,
action=self._on_cancel
)
# 已支付 -> 已发货
self.add_transition(
OrderState.PAID,
OrderState.SHIPPED,
condition=self._can_ship,
action=self._on_ship
)
# 已支付 -> 已退款
self.add_transition(
OrderState.PAID,
OrderState.REFUNDED,
condition=self._can_refund,
action=self._on_refund
)
# 已发货 -> 已收货
self.add_transition(
OrderState.SHIPPED,
OrderState.DELIVERED,
condition=self._can_deliver,
action=self._on_deliver
)
# 已收货 -> 已完成
self.add_transition(
OrderState.DELIVERED,
OrderState.COMPLETED,
condition=self._can_complete,
action=self._on_complete
)
# 已发货 -> 已退款
self.add_transition(
OrderState.SHIPPED,
OrderState.REFUNDED,
condition=self._can_refund_after_ship,
action=self._on_refund_after_ship
)
def add_transition(
self,
from_state: OrderState,
to_state: OrderState,
condition: Optional[callable] = None,
action: Optional[callable] = None
):
"""添加状态转换"""
transition = OrderTransition(from_state, to_state, condition, action)
if from_state not in self.transitions:
self.transitions[from_state] = []
self.transitions[from_state].append(transition)
def get_available_transitions(
self,
current_state: OrderState,
order=None,
**kwargs
) -> List[OrderState]:
"""获取可用的状态转换"""
available = []
if current_state in self.transitions:
for transition in self.transitions[current_state]:
if transition.can_transition(order, **kwargs):
available.append(transition.to_state)
return available
def can_transition(
self,
from_state: OrderState,
to_state: OrderState,
order=None,
**kwargs
) -> bool:
"""检查是否可以转换"""
if from_state not in self.transitions:
return False
for transition in self.transitions[from_state]:
if transition.to_state == to_state:
return transition.can_transition(order, **kwargs)
return False
def transition(
self,
order,
to_state: OrderState,
**kwargs
):
"""执行状态转换"""
current_state = OrderState(order.status)
if not self.can_transition(current_state, to_state, order, **kwargs):
raise ValidationError(
f"无法从 {current_state.value} 转换到 {to_state.value}"
)
# 执行转换
transitions = self.transitions.get(current_state, [])
for transition in transitions:
if transition.to_state == to_state:
transition.execute(order, **kwargs)
break
# 更新订单状态
order.status = to_state.value
order.save()
logger.info(
f"订单 {order.order_number} 状态从 {current_state.value} 转换为 {to_state.value}"
)
# 条件检查方法
def _can_pay(self, order, **kwargs) -> bool:
"""是否可以支付"""
# 检查订单是否已超时
from django.utils import timezone
from datetime import timedelta
if order.created_at < timezone.now() - timedelta(hours=24):
return False
# 检查库存
from .services import InventoryService
for item in order.items.all():
success, _ = InventoryService.check_stock(item.sku.id, item.quantity)
if not success:
return False
return True
def _can_cancel(self, order, **kwargs) -> bool:
"""是否可以取消"""
# 只有待支付订单可以取消
return order.status == OrderState.PENDING.value
def _can_ship(self, order, **kwargs) -> bool:
"""是否可以发货"""
# 只有已支付订单可以发货
return order.status == OrderState.PAID.value
def _can_deliver(self, order, **kwargs) -> bool:
"""是否可以收货"""
# 只有已发货订单可以收货
return order.status == OrderState.SHIPPED.value
def _can_complete(self, order, **kwargs) -> bool:
"""是否可以完成"""
# 只有已收货订单可以完成
return order.status == OrderState.DELIVERED.value
def _can_refund(self, order, **kwargs) -> bool:
"""是否可以退款"""
# 已支付订单可以退款
if order.status != OrderState.PAID.value:
return False
# 检查退款原因
reason = kwargs.get('reason')
if not reason:
return False
return True
def _can_refund_after_ship(self, order, **kwargs) -> bool:
"""发货后是否可以退款"""
# 已发货订单可以退款
if order.status != OrderState.SHIPPED.value:
return False
# 检查是否符合退款条件
reason = kwargs.get('reason')
if not reason:
return False
# 可以添加更多检查逻辑,如发货时间等
return True
# 动作执行方法
def _on_pay(self, order, **kwargs):
"""支付动作"""
payment_method = kwargs.get('payment_method')
order.payment_method = payment_method
order.payment_status = 'paid'
order.paid_at = timezone.now()
# 扣减库存
from .services import InventoryService
for item in order.items.all():
InventoryService.confirm_stock(item.sku.id, item.quantity)
def _on_cancel(self, order, **kwargs):
"""取消动作"""
reason = kwargs.get('reason', '用户取消')
order.cancelled_at = timezone.now()
# 恢复库存
from .services import InventoryService
for item in order.items.all():
InventoryService.release_stock(item.sku.id, item.quantity)
def _on_ship(self, order, **kwargs):
"""发货动作"""
shipping_company = kwargs.get('shipping_company')
tracking_number = kwargs.get('tracking_number')
order.shipping_company = shipping_company
order.tracking_number = tracking_number
order.shipped_at = timezone.now()
def _on_deliver(self, order, **kwargs):
"""收货动作"""
order.delivered_at = timezone.now()
def _on_complete(self, order, **kwargs):
"""完成动作"""
order.completed_at = timezone.now()
def _on_refund(self, order, **kwargs):
"""退款动作"""
reason = kwargs.get('reason')
refund_amount = kwargs.get('refund_amount', order.final_amount)
order.refund_amount = refund_amount
order.refund_reason = reason
order.refunded_at = timezone.now()
order.payment_status = 'refunded'
def _on_refund_after_ship(self, order, **kwargs):
"""发货后退款动作"""
self._on_refund(order, **kwargs)
# 全局状态机实例
order_state_machine = OrderStateMachine()
第四部分:部署与运维
4.1 生产环境配置
config/settings/production.py:
import os
from .base import *
# 安全配置
DEBUG = False
SECRET_KEY = os.getenv('DJANGO_SECRET_KEY')
ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', '').split(',')
# 数据库配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.getenv('DB_NAME'),
'USER': os.getenv('DB_USER'),
'PASSWORD': os.getenv('DB_PASSWORD'),
'HOST': os.getenv('DB_HOST', 'localhost'),
'PORT': os.getenv('DB_PORT', '5432'),
'CONN_MAX_AGE': 600, # 连接池超时时间
'OPTIONS': {
'sslmode': 'require',
},
}
}
# Redis配置
REDIS_HOST = os.getenv('REDIS_HOST', 'localhost')
REDIS_PORT = int(os.getenv('REDIS_PORT', 6379))
REDIS_DB = int(os.getenv('REDIS_DB', 0))
REDIS_PASSWORD = os.getenv('REDIS_PASSWORD', None)
# 缓存配置
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': f'redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
'PASSWORD': REDIS_PASSWORD,
'SOCKET_CONNECT_TIMEOUT': 5,
'SOCKET_TIMEOUT': 5,
},
'KEY_PREFIX': 'ecommerce',
}
}
# 静态文件配置
STATIC_ROOT = '/var/www/ecommerce/static'
MEDIA_ROOT = '/var/www/ecommerce/media'
# 安全头配置
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_HSTS_SECONDS = 31536000 # 1年
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
# 日志配置
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
'style': '{',
},
'simple': {
'format': '{levelname} {message}',
'style': '{',
},
},
'handlers': {
'file': {
'level': 'ERROR',
'class': 'logging.handlers.RotatingFileHandler',
'filename': '/var/log/ecommerce/error.log',
'maxBytes': 10485760, # 10MB
'backupCount': 10,
'formatter': 'verbose',
},
'console': {
'level': 'INFO',
'class': 'logging.StreamHandler',
'formatter': 'simple',
},
},
'loggers': {
'django': {
'handlers': ['file', 'console'],
'level': 'ERROR',
'propagate': True,
},
'ecommerce': {
'handlers': ['file', 'console'],
'level': 'INFO',
'propagate': True,
},
},
}
# Celery配置
CELERY_BROKER_URL = f'redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB + 1}'
CELERY_RESULT_BACKEND = f'redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB + 1}'
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'Asia/Shanghai'
4.2 Docker部署配置
docker-compose.yml:
version: '3.8'
services:
# PostgreSQL数据库
postgres:
image: postgres:15-alpine
container_name: ecommerce_postgres
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
networks:
- ecommerce_network
restart: unless-stopped
# Redis缓存
redis:
image: redis:7-alpine
container_name: ecommerce_redis
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redis_data:/data
ports:
- "6379:6379"
networks:
- ecommerce_network
restart: unless-stopped
# Django应用
django:
build:
context: .
dockerfile: Dockerfile
container_name: ecommerce_django
environment:
DJANGO_SETTINGS_MODULE: config.settings.production
DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY}
DB_NAME: ${DB_NAME}
DB_USER: ${DB_USER}
DB_PASSWORD: ${DB_PASSWORD}
DB_HOST: postgres
DB_PORT: 5432
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_DB: 0
REDIS_PASSWORD: ${REDIS_PASSWORD}
volumes:
- static_volume:/app/staticfiles
- media_volume:/app/media
depends_on:
- postgres
- redis
networks:
- ecommerce_network
restart: unless-stopped
# Nginx反向代理
nginx:
image: nginx:alpine
container_name: ecommerce_nginx
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- static_volume:/app/staticfiles
- media_volume:/app/media
ports:
- "80:80"
- "443:443"
depends_on:
- django
networks:
- ecommerce_network
restart: unless-stopped
# Celery Worker
celery_worker:
build:
context: .
dockerfile: Dockerfile
container_name: ecommerce_celery_worker
command: celery -A config worker --loglevel=info
environment:
DJANGO_SETTINGS_MODULE: config.settings.production
DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY}
DB_NAME: ${DB_NAME}
DB_USER: ${DB_USER}
DB_PASSWORD: ${DB_PASSWORD}
DB_HOST: postgres
DB_PORT: 5432
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_DB: 1
REDIS_PASSWORD: ${REDIS_PASSWORD}
depends_on:
- postgres
- redis
networks:
- ecommerce_network
restart: unless-stopped
# Celery Beat调度器
celery_beat:
build:
context: .
dockerfile: Dockerfile
container_name: ecommerce_celery_beat
command: celery -A config beat --loglevel=info
environment:
DJANGO_SETTINGS_MODULE: config.settings.production
DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY}
DB_NAME: ${DB_NAME}
DB_USER: ${DB_USER}
DB_PASSWORD: ${DB_PASSWORD}
DB_HOST: postgres
DB_PORT: 5432
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_DB: 1
REDIS_PASSWORD: ${REDIS_PASSWORD}
depends_on:
- postgres
- redis
networks:
- ecommerce_network
restart: unless-stopped
volumes:
postgres_data:
redis_data:
static_volume:
media_volume:
networks:
ecommerce_network:
driver: bridge
Dockerfile:
# 使用Python 3.11作为基础镜像
FROM python:3.11-slim
# 设置工作目录
WORKDIR /app
# 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV PIP_NO_CACHE_DIR=1
# 安装系统依赖
RUN apt-get update && apt-get install -y \
gcc \
libpq-dev \
curl \
&& rm -rf /var/lib/apt/lists/*
# 复制依赖文件
COPY requirements.txt .
# 安装Python依赖
RUN pip install --upgrade pip && \
pip install -r requirements.txt --no-cache-dir
# 复制项目文件
COPY . .
# 收集静态文件
RUN python manage.py collectstatic --noinput
# 创建非root用户
RUN adduser --disabled-password --gecos '' appuser && \
chown -R appuser:appuser /app
USER appuser
# 暴露端口
EXPOSE 8000
# 启动命令
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "4", "config.wsgi:application"]
第五部分:项目总结与进阶学习
5.1 项目技术亮点总结
通过本项目的学习,你掌握了以下核心技能:
- 企业级架构设计:六边形架构、清晰的分层设计、模块化开发
- 复杂数据模型设计:SPU/SKU分离、多级分类、属性系统
- 高并发解决方案:Redis分布式锁、数据库乐观锁、缓存策略
- 业务逻辑复杂系统设计:订单状态机、库存管理、支付集成
- 生产环境部署:Docker容器化、Nginx反向代理、监控运维
5.2 常见问题与解决方案
Q1: 如何应对双十一级别的高并发?
- 前端:CDN加速、静态资源缓存、限流降级
- 后端:微服务拆分、数据库读写分离、Redis集群
- 运维:自动扩缩容、负载均衡、监控告警
Q2: 库存超卖如何彻底解决?
- 应用层:Redis分布式锁+原子操作
- 数据库层:乐观锁(版本号)、悲观锁(SELECT FOR UPDATE)
- 业务层:预扣库存、异步扣减、库存预警
Q3: 如何保证支付安全?
- 接口签名:防止数据篡改
- 异步通知:支付结果回调验证
- 对账系统:每日自动对账,发现异常及时处理
5.3 进阶学习路径
- 微服务架构:学习Spring Cloud、Dubbo,理解服务治理
- 大数据分析:学习Hadoop、Spark,构建用户画像系统
- 人工智能应用:学习推荐算法、智能客服
- DevOps实践:CI/CD流水线、监控系统、容器编排
5.4 项目扩展建议
- 移动端适配:开发React Native或Flutter移动应用
- 国际化支持:多语言、多货币、本地化运营
- 社交电商:结合直播、短视频、社交分享
- 供应链管理:仓储管理、物流跟踪、供应商协同
行动号召
通过本教程的学习,你已经掌握了企业级电商后台系统的核心开发技能。但这只是开始,真正的挑战在于如何将这些知识应用到实际项目中。
下一步建议:
- 动手实践:在本项目基础上,添加优惠券、秒杀等营销功能
- 源码研究:深入阅读Django、DRF源码,理解框架设计思想
- 性能优化:使用性能测试工具,找出系统瓶颈并优化
- 架构演进:尝试将单体应用拆分为微服务架构
记住,技术学习没有捷径,只有不断实践、总结、优化,才能真正成长为优秀的高级开发者。
现在,打开你的IDE,开始编码吧!如果在实践过程中遇到问题,欢迎在评论区交流讨论。我们一起成长,共同进步!