社区资源媒体管理系统设计与实现

社区资源媒体管理系统设计与实现

1. 系统概述

社区资源媒体管理系统是一个专为社区户外广告打造的高效、专业化平台,旨在实现社区媒体的数字化管理、智能投放和便捷交易。该系统将整合社区各类广告资源,为广告主、物业公司和社区居民提供一站式服务。

1.1 系统目标

  1. 实现社区户外广告资源的数字化管理
  2. 提供精准广告投放功能
  3. 建立广告交易平台
  4. 优化广告资源利用率
  5. 提升广告投放效果分析能力

1.2 系统特点

  • 专业化:针对社区户外广告场景定制
  • 智能化:利用算法实现精准投放
  • 可视化:直观展示广告资源分布和效果
  • 安全可靠:完善的权限管理和数据保护机制

2. 系统架构设计

2.1 技术栈选择

  • 后端:Python + Django/Django REST framework
  • 前端:Vue.js/React + Element UI/Ant Design
  • 数据库:PostgreSQL/MySQL
  • 缓存:Redis
  • 搜索引擎:Elasticsearch
  • 文件存储:阿里云OSS/七牛云
  • 消息队列:RabbitMQ/Celery
  • GIS支持:PostGIS/GeoDjango

2.2 系统架构图

复制代码
┌───────────────────────────────────────────────────────────────┐
│                       客户端层                                 │
│  ┌───────────┐  ┌───────────┐  ┌───────────┐  ┌───────────┐   │
│  │   Web端   │  │ 移动端APP │  │ 管理后台  │  │ 第三方接入│   │
│  └───────────┘  └───────────┘  └───────────┘  └───────────┘   │
└───────────────────────────────────────────────────────────────┘
                               │
                               ▼
┌───────────────────────────────────────────────────────────────┐
│                       应用服务层                               │
│  ┌───────────┐  ┌───────────┐  ┌───────────┐  ┌───────────┐   │
│  │  API网关  │  │ 用户服务  │  │ 广告服务  │  │ 支付服务  │   │
│  └───────────┘  └───────────┘  └───────────┘  └───────────┘   │
│  ┌───────────┐  ┌───────────┐  ┌───────────┐  ┌───────────┐   │
│  │ 数据服务  │  │ 文件服务  │  │ 消息服务  │  │ 定时任务  │   │
│  └───────────┘  └───────────┘  └───────────┘  └───────────┘   │
└───────────────────────────────────────────────────────────────┘
                               │
                               ▼
┌───────────────────────────────────────────────────────────────┐
│                       数据存储层                               │
│  ┌───────────┐  ┌───────────┐  ┌───────────┐  ┌───────────┐   │
│  │ 关系数据库 │  │  缓存系统  │  │ 搜索引擎  │  │ 文件存储  │   │
│  └───────────┘  └───────────┘  └───────────┘  └───────────┘   │
└───────────────────────────────────────────────────────────────┘

2.3 微服务划分

  1. 用户服务:处理用户注册、登录、权限管理
  2. 广告服务:广告资源管理、投放策略
  3. 交易服务:订单管理、支付处理
  4. 数据服务:数据分析、报表生成
  5. 消息服务:通知、站内信
  6. 文件服务:图片、视频等资源管理

3. 数据库设计

3.1 主要数据表结构

用户相关表
python 复制代码
class User(AbstractUser):
    USER_TYPE_CHOICES = (
        ('admin', '管理员'),
        ('advertiser', '广告主'),
        ('property', '物业'),
        ('resident', '居民'),
    )
    user_type = models.CharField(max_length=20, choices=USER_TYPE_CHOICES)
    phone = models.CharField(max_length=20, unique=True)
    company = models.CharField(max_length=100, blank=True)
    avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
    verified = models.BooleanField(default=False)
    
class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    id_card = models.CharField(max_length=20, blank=True)
    address = models.TextField(blank=True)
    credit_score = models.IntegerField(default=100)
社区相关表
python 复制代码
class Community(models.Model):
    name = models.CharField(max_length=100)
    address = models.TextField()
    location = models.PointField()  # 使用GeoDjango
    total_buildings = models.IntegerField()
    total_households = models.IntegerField()
    property_company = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, limit_choices_to={'user_type': 'property'})
    created_at = models.DateTimeField(auto_now_add=True)
    
class Building(models.Model):
    community = models.ForeignKey(Community, on_delete=models.CASCADE)
    name = models.CharField(max_length=50)
    floor_count = models.IntegerField()
    household_count = models.IntegerField()
    location = models.PointField()
广告资源相关表
python 复制代码
class AdSpace(models.Model):
    SPACE_TYPE_CHOICES = (
        ('elevator', '电梯广告'),
        ('gate', '大门广告'),
        ('billboard', '广告牌'),
        ('parking', '停车场广告'),
        ('other', '其他'),
    )
    community = models.ForeignKey(Community, on_delete=models.CASCADE)
    space_type = models.CharField(max_length=20, choices=SPACE_TYPE_CHOICES)
    location = models.PointField()
    description = models.TextField()
    size = models.CharField(max_length=50)  # 如"60cm×90cm"
    price_per_day = models.DecimalField(max_digits=10, decimal_places=2)
    is_available = models.BooleanField(default=True)
    images = models.ManyToManyField('FileResource', blank=True)
    
class AdSpaceImage(models.Model):
    ad_space = models.ForeignKey(AdSpace, on_delete=models.CASCADE)
    image = models.ImageField(upload_to='ad_space_images/')
    is_primary = models.BooleanField(default=False)
    uploaded_at = models.DateTimeField(auto_now_add=True)
广告内容相关表
python 复制代码
class AdContent(models.Model):
    AD_TYPE_CHOICES = (
        ('image', '图片广告'),
        ('video', '视频广告'),
        ('text', '文字广告'),
        ('interactive', '互动广告'),
    )
    advertiser = models.ForeignKey(User, on_delete=models.CASCADE, limit_choices_to={'user_type': 'advertiser'})
    title = models.CharField(max_length=100)
    ad_type = models.CharField(max_length=20, choices=AD_TYPE_CHOICES)
    content = models.TextField()  # 或JSONField存储结构化内容
    target_audience = models.JSONField(default=dict)  # 目标受众筛选条件
    start_date = models.DateField()
    end_date = models.DateField()
    budget = models.DecimalField(max_digits=12, decimal_places=2)
    status = models.CharField(max_length=20, default='draft')  # draft, pending, approved, rejected, running, completed
    created_at = models.DateTimeField(auto_now_add=True)
    
class AdMaterial(models.Model):
    ad_content = models.ForeignKey(AdContent, on_delete=models.CASCADE)
    file = models.ForeignKey('FileResource', on_delete=models.CASCADE)
    material_type = models.CharField(max_length=20)  # image, video, etc.
    display_order = models.IntegerField(default=0)
订单交易相关表
python 复制代码
class AdOrder(models.Model):
    ORDER_STATUS_CHOICES = (
        ('pending', '待支付'),
        ('paid', '已支付'),
        ('deployed', '已投放'),
        ('completed', '已完成'),
        ('cancelled', '已取消'),
        ('refunded', '已退款'),
    )
    order_no = models.CharField(max_length=50, unique=True)
    advertiser = models.ForeignKey(User, on_delete=models.CASCADE, limit_choices_to={'user_type': 'advertiser'})
    ad_content = models.ForeignKey(AdContent, on_delete=models.CASCADE)
    total_amount = models.DecimalField(max_digits=12, decimal_places=2)
    actual_amount = models.DecimalField(max_digits=12, decimal_places=2)
    discount = models.DecimalField(max_digits=5, decimal_places=2, default=0)
    status = models.CharField(max_length=20, choices=ORDER_STATUS_CHOICES, default='pending')
    created_at = models.DateTimeField(auto_now_add=True)
    paid_at = models.DateTimeField(null=True, blank=True)
    
class OrderItem(models.Model):
    order = models.ForeignKey(AdOrder, on_delete=models.CASCADE)
    ad_space = models.ForeignKey(AdSpace, on_delete=models.CASCADE)
    start_date = models.DateField()
    end_date = models.DateField()
    price_per_day = models.DecimalField(max_digits=10, decimal_places=2)
    total_days = models.IntegerField()
    subtotal = models.DecimalField(max_digits=12, decimal_places=2)
    deployed_at = models.DateTimeField(null=True, blank=True)
    completed_at = models.DateTimeField(null=True, blank=True)
效果统计相关表
python 复制代码
class AdImpression(models.Model):
    ad_content = models.ForeignKey(AdContent, on_delete=models.CASCADE)
    ad_space = models.ForeignKey(AdSpace, on_delete=models.CASCADE)
    date = models.DateField()
    view_count = models.IntegerField(default=0)
    interaction_count = models.IntegerField(default=0)
    
class AdInteraction(models.Model):
    INTERACTION_TYPE_CHOICES = (
        ('click', '点击'),
        ('scan', '扫码'),
        ('call', '电话'),
        ('share', '分享'),
    )
    ad_content = models.ForeignKey(AdContent, on_delete=models.CASCADE)
    ad_space = models.ForeignKey(AdSpace, on_delete=models.CASCADE)
    user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
    interaction_type = models.CharField(max_length=20, choices=INTERACTION_TYPE_CHOICES)
    interaction_data = models.JSONField(default=dict)  # 额外数据如扫码内容等
    created_at = models.DateTimeField(auto_now_add=True)
    ip_address = models.GenericIPAddressField(null=True, blank=True)
    device_info = models.CharField(max_length=200, blank=True)
系统管理相关表
python 复制代码
class SystemConfig(models.Model):
    key = models.CharField(max_length=50, unique=True)
    value = models.JSONField()
    description = models.TextField(blank=True)
    is_public = models.BooleanField(default=False)
    
class OperationLog(models.Model):
    ACTION_CHOICES = (
        ('create', '创建'),
        ('update', '更新'),
        ('delete', '删除'),
        ('login', '登录'),
        ('logout', '登出'),
        ('approve', '审批'),
        ('reject', '拒绝'),
    )
    user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
    action = models.CharField(max_length=20, choices=ACTION_CHOICES)
    model = models.CharField(max_length=50)
    object_id = models.CharField(max_length=50, blank=True)
    data_before = models.JSONField(null=True, blank=True)
    data_after = models.JSONField(null=True, blank=True)
    ip_address = models.GenericIPAddressField()
    created_at = models.DateTimeField(auto_now_add=True)

3.2 数据库关系图

复制代码
┌───────────┐       ┌───────────┐       ┌──────────────┐
│   User    │───────│ Community │───────│   Building   │
└───────────┘       └───────────┘       └──────────────┘
     |                     |
     |                     |
     ▼                     ▼
┌───────────┐       ┌───────────┐       ┌──────────────┐
│AdContent  │───────│  AdSpace  │───────│ AdSpaceImage │
└───────────┘       └───────────┘       └──────────────┘
     |                     |
     |                     |
     ▼                     ▼
┌───────────┐       ┌──────────────┐     ┌──────────────┐
│ AdOrder   │───────│  OrderItem   │─────│ AdImpression │
└───────────┘       └──────────────┘     └──────────────┘
                                               |
                                               |
                                               ▼
                                      ┌──────────────────┐
                                      │ AdInteraction   │
                                      └──────────────────┘

4. 核心功能模块实现

4.1 用户认证与权限管理

python 复制代码
# authentication/backends.py
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
from .models import User

class MultiFieldModelBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        try:
            user = User.objects.get(Q(username=username) | Q(phone=username) | Q(email=username))
            if user.check_password(password):
                return user
        except User.DoesNotExist:
            return None

# authentication/permissions.py
from rest_framework.permissions import BasePermission

class IsAdvertiser(BasePermission):
    def has_permission(self, request, view):
        return request.user.is_authenticated and request.user.user_type == 'advertiser'

class IsPropertyManager(BasePermission):
    def has_permission(self, request, view):
        return request.user.is_authenticated and request.user.user_type == 'property'

# authentication/serializers.py
from rest_framework import serializers
from django.contrib.auth import authenticate
from .models import User, UserProfile

class UserLoginSerializer(serializers.Serializer):
    username = serializers.CharField()
    password = serializers.CharField(write_only=True)
    
    def validate(self, data):
        user = authenticate(username=data['username'], password=data['password'])
        if not user:
            raise serializers.ValidationError("Invalid credentials")
        if not user.is_active:
            raise serializers.ValidationError("User account is disabled")
        return user

class UserProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model = UserProfile
        fields = ['id_card', 'address', 'credit_score']

class UserSerializer(serializers.ModelSerializer):
    profile = UserProfileSerializer()
    
    class Meta:
        model = User
        fields = ['id', 'username', 'email', 'phone', 'user_type', 'company', 'verified', 'profile']
        read_only_fields = ['verified']
        
    def update(self, instance, validated_data):
        profile_data = validated_data.pop('profile', {})
        profile = instance.profile
        
        for attr, value in validated_data.items():
            setattr(instance, attr, value)
        instance.save()
        
        for attr, value in profile_data.items():
            setattr(profile, attr, value)
        profile.save()
        
        return instance

4.2 广告资源管理模块

python 复制代码
# advertisements/views.py
from rest_framework import viewsets, permissions, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
from .models import AdSpace, AdSpaceImage
from .serializers import AdSpaceSerializer, AdSpaceImageSerializer
from .filters import AdSpaceFilter

class AdSpaceViewSet(viewsets.ModelViewSet):
    queryset = AdSpace.objects.all()
    serializer_class = AdSpaceSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_class = AdSpaceFilter
    
    def get_permissions(self):
        if self.action in ['create', 'update', 'partial_update', 'destroy']:
            permission_classes = [permissions.IsAuthenticated, IsPropertyManager]
        else:
            permission_classes = [permissions.IsAuthenticatedOrReadOnly]
        return [permission() for permission in permission_classes]
    
    def get_queryset(self):
        queryset = super().get_queryset()
        # 物业用户只能看到自己社区的广告位
        if self.request.user.user_type == 'property':
            queryset = queryset.filter(community__property_company=self.request.user)
        # 广告主可以看到所有可用的广告位
        elif self.request.user.user_type == 'advertiser':
            queryset = queryset.filter(is_available=True)
        return queryset
    
    @action(detail=True, methods=['post'], serializer_class=AdSpaceImageSerializer)
    def upload_image(self, request, pk=None):
        ad_space = self.get_object()
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save(ad_space=ad_space)
        return Response(serializer.data, status=status.HTTP_201_CREATED)
    
    @action(detail=True, methods=['get'])
    def stats(self, request, pk=None):
        ad_space = self.get_object()
        # 获取广告位的统计数据
        data = {
            'total_orders': ad_space.order_items.count(),
            'current_orders': ad_space.order_items.filter(
                order__status__in=['paid', 'deployed']
            ).count(),
            'revenue': sum(
                [item.subtotal for item in ad_space.order_items.filter(
                    order__status__in=['paid', 'deployed', 'completed']
                )]
            ),
        }
        return Response(data)

# advertisements/serializers.py
from rest_framework import serializers
from django.contrib.gis.geos import Point
from .models import AdSpace, AdSpaceImage

class PointField(serializers.Field):
    def to_representation(self, value):
        if value:
            return {'lng': value.x, 'lat': value.y}
        return None
    
    def to_internal_value(self, data):
        if data and 'lng' in data and 'lat' in data:
            return Point(float(data['lng']), float(data['lat']))
        return None

class AdSpaceImageSerializer(serializers.ModelSerializer):
    class Meta:
        model = AdSpaceImage
        fields = ['id', 'image', 'is_primary', 'uploaded_at']
        read_only_fields = ['uploaded_at']

class AdSpaceSerializer(serializers.ModelSerializer):
    location = PointField()
    images = AdSpaceImageSerializer(many=True, read_only=True)
    community_name = serializers.CharField(source='community.name', read_only=True)
    
    class Meta:
        model = AdSpace
        fields = [
            'id', 'community', 'community_name', 'space_type', 
            'location', 'description', 'size', 'price_per_day', 
            'is_available', 'images'
        ]
        
    def validate(self, data):
        if self.instance and 'community' in data and data['community'] != self.instance.community:
            raise serializers.ValidationError("Cannot change community of an existing ad space")
        return data

# advertisements/filters.py
import django_filters
from django.contrib.gis.db.models.functions import Distance
from django.contrib.gis.geos import Point
from .models import AdSpace

class AdSpaceFilter(django_filters.FilterSet):
    space_type = django_filters.CharFilter(field_name='space_type')
    min_price = django_filters.NumberFilter(field_name='price_per_day', lookup_expr='gte')
    max_price = django_filters.NumberFilter(field_name='price_per_day', lookup_expr='lte')
    available = django_filters.BooleanFilter(field_name='is_available')
    community = django_filters.NumberFilter(field_name='community')
    near = django_filters.CharFilter(method='filter_near')
    
    class Meta:
        model = AdSpace
        fields = ['space_type', 'price_per_day', 'is_available', 'community']
    
    def filter_near(self, queryset, name, value):
        try:
            lng, lat, radius = map(float, value.split(','))
            point = Point(lng, lat, srid=4326)
            return queryset.filter(
                location__distance_lte=(point, radius)
            ).annotate(
                distance=Distance('location', point)
            ).order_by('distance')
        except (ValueError, TypeError):
            return queryset

4.3 广告内容管理模块

python 复制代码
# contents/views.py
from rest_framework import viewsets, permissions, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
from .models import AdContent, AdMaterial
from .serializers import AdContentSerializer, AdMaterialSerializer
from .filters import AdContentFilter
from .tasks import process_ad_content

class AdContentViewSet(viewsets.ModelViewSet):
    queryset = AdContent.objects.all()
    serializer_class = AdContentSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_class = AdContentFilter
    
    def get_permissions(self):
        if self.action in ['create', 'update', 'partial_update', 'destroy']:
            permission_classes = [permissions.IsAuthenticated, IsAdvertiser]
        else:
            permission_classes = [permissions.IsAuthenticatedOrReadOnly]
        return [permission() for permission in permission_classes]
    
    def get_queryset(self):
        queryset = super().get_queryset()
        # 广告主只能看到自己的广告
        if self.request.user.user_type == 'advertiser':
            queryset = queryset.filter(advertiser=self.request.user)
        # 物业可以看到自己社区的广告
        elif self.request.user.user_type == 'property':
            queryset = queryset.filter(
                order_items__order__status__in=['paid', 'deployed', 'completed'],
                order_items__ad_space__community__property_company=self.request.user
            ).distinct()
        return queryset
    
    def perform_create(self, serializer):
        serializer.save(advertiser=self.request.user)
    
    @action(detail=True, methods=['post'])
    def submit_for_review(self, request, pk=None):
        ad_content = self.get_object()
        if ad_content.status != 'draft':
            return Response(
                {'detail': 'Only draft ads can be submitted for review'},
                status=status.HTTP_400_BAD_REQUEST
            )
        ad_content.status = 'pending'
        ad_content.save()
        # 异步处理审核流程
        process_ad_content.delay(ad_content.id)
        return Response({'status': 'submitted for review'})
    
    @action(detail=True, methods=['get'])
    def materials(self, request, pk=None):
        ad_content = self.get_object()
        materials = ad_content.materials.all().order_by('display_order')
        serializer = AdMaterialSerializer(materials, many=True)
        return Response(serializer.data)

# contents/serializers.py
from rest_framework import serializers
from .models import AdContent, AdMaterial
from files.serializers import FileResourceSerializer

class AdMaterialSerializer(serializers.ModelSerializer):
    file_details = FileResourceSerializer(source='file', read_only=True)
    
    class Meta:
        model = AdMaterial
        fields = ['id', 'ad_content', 'file', 'material_type', 'display_order', 'file_details']
        extra_kwargs = {
            'ad_content': {'write_only': True}
        }

class AdContentSerializer(serializers.ModelSerializer):
    advertiser_name = serializers.CharField(source='advertiser.company', read_only=True)
    materials = AdMaterialSerializer(many=True, read_only=True)
    
    class Meta:
        model = AdContent
        fields = [
            'id', 'advertiser', 'advertiser_name', 'title', 'ad_type', 
            'content', 'target_audience', 'start_date', 'end_date', 
            'budget', 'status', 'created_at', 'materials'
        ]
        read_only_fields = ['status', 'created_at']
        
    def validate(self, data):
        if 'start_date' in data and 'end_date' in data:
            if data['start_date'] > data['end_date']:
                raise serializers.ValidationError("End date must be after start date")
        return data

# contents/tasks.py
from celery import shared_task
from django.core.mail import send_mail
from django.conf import settings
from .models import AdContent

@shared_task
def process_ad_content(ad_content_id):
    ad_content = AdContent.objects.get(id=ad_content_id)
    # 这里可以添加复杂的审核逻辑
    # 模拟审核过程
    import time
    time.sleep(10)  # 模拟审核耗时
    
    # 90%的概率通过审核
    import random
    if random.random() < 0.9:
        ad_content.status = 'approved'
        subject = '您的广告已通过审核'
        message = f'您的广告 "{ad_content.title}" 已通过审核,可以开始投放。'
    else:
        ad_content.status = 'rejected'
        subject = '您的广告未通过审核'
        message = f'您的广告 "{ad_content.title}" 未通过审核,请修改后重新提交。'
    
    ad_content.save()
    
    # 发送邮件通知
    send_mail(
        subject=subject,
        message=message,
        from_email=settings.DEFAULT_FROM_EMAIL,
        recipient_list=[ad_content.advertiser.email],
        fail_silently=True,
    )

4.4 订单交易模块

python 复制代码
# orders/views.py
from rest_framework import viewsets, permissions, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django.utils import timezone
from django.db import transaction
from .models import AdOrder, OrderItem
from .serializers import AdOrderSerializer, OrderItemSerializer, CreateOrderSerializer
from .services import OrderService
from advertisements.models import AdSpace
from contents.models import AdContent

class AdOrderViewSet(viewsets.ModelViewSet):
    queryset = AdOrder.objects.all()
    serializer_class = AdOrderSerializer
    
    def get_permissions(self):
        if self.action in ['create', 'update', 'partial_update', 'destroy']:
            permission_classes = [permissions.IsAuthenticated, IsAdvertiser]
        else:
            permission_classes = [permissions.IsAuthenticated]
        return [permission() for permission in permission_classes]
    
    def get_queryset(self):
        queryset = super().get_queryset()
        # 广告主只能看到自己的订单
        if self.request.user.user_type == 'advertiser':
            queryset = queryset.filter(advertiser=self.request.user)
        # 物业可以看到自己社区的订单
        elif self.request.user.user_type == 'property':
            queryset = queryset.filter(
                items__ad_space__community__property_company=self.request.user
            ).distinct()
        return queryset
    
    def get_serializer_class(self):
        if self.action == 'create':
            return CreateOrderSerializer
        return super().get_serializer_class()
    
    @transaction.atomic
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        
        # 获取广告内容
        ad_content = AdContent.objects.get(
            id=serializer.validated_data['ad_content_id'],
            advertiser=request.user,
            status='approved'
        )
        
        # 创建订单
        order = OrderService.create_order(
            advertiser=request.user,
            ad_content=ad_content,
            items_data=serializer.validated_data['items']
        )
        
        headers = self.get_success_headers(serializer.data)
        return Response(
            AdOrderSerializer(order).data,
            status=status.HTTP_201_CREATED,
            headers=headers
        )
    
    @action(detail=True, methods=['post'])
    def pay(self, request, pk=None):
        order = self.get_object()
        if order.status != 'pending':
            return Response(
                {'detail': 'Only pending orders can be paid'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # 这里应该调用支付接口,简化处理直接标记为已支付
        order.status = 'paid'
        order.paid_at = timezone.now()
        order.save()
        
        # 更新订单项状态
        order.items.update(deployed_at=timezone.now())
        
        # 更新广告内容状态
        order.ad_content.status = 'running'
        order.ad_content.save()
        
        return Response({'status': 'paid'})

# orders/serializers.py
from rest_framework import serializers
from .models import AdOrder, OrderItem
from advertisements.models import AdSpace
from advertisements.serializers import AdSpaceSerializer
from contents.serializers import AdContentSerializer

class OrderItemSerializer(serializers.ModelSerializer):
    ad_space_details = AdSpaceSerializer(source='ad_space', read_only=True)
    
    class Meta:
        model = OrderItem
        fields = [
            'id', 'order', 'ad_space', 'ad_space_details', 
            'start_date', 'end_date', 'price_per_day', 
            'total_days', 'subtotal', 'deployed_at', 'completed_at'
        ]
        read_only_fields = [
            'total_days', 'subtotal', 'deployed_at', 'completed_at'
        ]

class AdOrderSerializer(serializers.ModelSerializer):
    items = OrderItemSerializer(many=True, read_only=True)
    ad_content_details = AdContentSerializer(source='ad_content', read_only=True)
    
    class Meta:
        model = AdOrder
        fields = [
            'id', 'order_no', 'advertiser', 'ad_content', 'ad_content_details',
            'total_amount', 'actual_amount', 'discount', 'status',
            'created_at', 'paid_at', 'items'
        ]
        read_only_fields = [
            'order_no', 'advertiser', 'total_amount', 'actual_amount',
            'discount', 'status', 'created_at', 'paid_at'
        ]

class CreateOrderItemSerializer(serializers.Serializer):
    ad_space_id = serializers.IntegerField()
    start_date = serializers.DateField()
    end_date = serializers.DateField()
    
    def validate(self, data):
        ad_space = AdSpace.objects.filter(id=data['ad_space_id'], is_available=True).first()
        if not ad_space:
            raise serializers.ValidationError("Ad space not available")
        
        # 检查广告位是否在选定日期内可用
        conflicting_items = OrderItem.objects.filter(
            ad_space=ad_space,
            start_date__lte=data['end_date'],
            end_date__gte=data['start_date'],
            order__status__in=['paid', 'deployed']
        )
        if conflicting_items.exists():
            raise serializers.ValidationError("Ad space is not available for the selected dates")
        
        data['ad_space'] = ad_space
        data['price_per_day'] = ad_space.price_per_day
        return data

class CreateOrderSerializer(serializers.Serializer):
    ad_content_id = serializers.IntegerField()
    items = CreateOrderItemSerializer(many=True, min_length=1)
    
    def validate_ad_content_id(self, value):
        if not AdContent.objects.filter(id=value, advertiser=self.context['request'].user, status='approved').exists():
            raise serializers.ValidationError("Invalid ad content")
        return value
    
    def validate(self, data):
        if not data['items']:
            raise serializers.ValidationError("At least one order item is required")
        return data

# orders/services.py
from django.utils import timezone
from django.db import transaction
import random
import string
from .models import AdOrder, OrderItem

class OrderService:
    @staticmethod
    @transaction.atomic
    def create_order(advertiser, ad_content, items_data):
        # 生成订单号
        order_no = f'ORD{timezone.now().strftime("%Y%m%d")}{"".join(random.choices(string.digits, k=6))}'
        
        # 计算总金额
        total_amount = sum(
            (item['end_date'] - item['start_date']).days * item['price_per_day']
            for item in items_data
        )
        
        # 创建订单
        order = AdOrder.objects.create(
            order_no=order_no,
            advertiser=advertiser,
            ad_content=ad_content,
            total_amount=total_amount,
            actual_amount=total_amount,  # 实际支付金额,这里简化处理
            status='pending'
        )
        
        # 创建订单项
        order_items = []
        for item_data in items_data:
            total_days = (item_data['end_date'] - item_data['start_date']).days
            subtotal = total_days * item_data['price_per_day']
            
            order_item = OrderItem(
                order=order,
                ad_space=item_data['ad_space'],
                start_date=item_data['start_date'],
                end_date=item_data['end_date'],
                price_per_day=item_data['price_per_day'],
                total_days=total_days,
                subtotal=subtotal
            )
            order_items.append(order_item)
        
        OrderItem.objects.bulk_create(order_items)
        
        return order

4.5 数据统计与分析模块

python 复制代码
# analytics/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import permissions
from django.db.models import Sum, Count, Q
from django.utils import timezone
from datetime import timedelta
from advertisements.models import AdSpace
from contents.models import AdContent
from orders.models import AdOrder, OrderItem
from .serializers import StatsSerializer

class DashboardStatsAPIView(APIView):
    permission_classes = [permissions.IsAuthenticated]
    
    def get(self, request):
        user = request.user
        time_threshold = timezone.now() - timedelta(days=30)
        
        if user.user_type == 'advertiser':
            # 广告主数据
            ads = AdContent.objects.filter(advertiser=user)
            orders = AdOrder.objects.filter(advertiser=user)
            
            stats = {
                'total_ads': ads.count(),
                'active_ads': ads.filter(status='running').count(),
                'total_orders': orders.count(),
                'total_spent': orders.aggregate(total=Sum('actual_amount'))['total'] or 0,
                'recent_orders': orders.filter(created_at__gte=time_threshold).count(),
            }
            
        elif user.user_type == 'property':
            # 物业数据
            communities = user.managed_communities.all()
            ad_spaces = AdSpace.objects.filter(community__in=communities)
            order_items = OrderItem.objects.filter(ad_space__in=ad_spaces)
            
            stats = {
                'total_spaces': ad_spaces.count(),
                'available_spaces': ad_spaces.filter(is_available=True).count(),
                'total_orders': order_items.count(),
                'total_income': order_items.aggregate(total=Sum('subtotal'))['total'] or 0,
                'recent_orders': order_items.filter(
                    order__created_at__gte=time_threshold
                ).count(),
            }
            
        else:
            stats = {}
        
        serializer = StatsSerializer(stats)
        return Response(serializer.data)

class AdPerformanceAPIView(APIView):
    permission_classes = [permissions.IsAuthenticated, IsAdvertiser]
    
    def get(self, request, ad_id):
        ad_content = AdContent.objects.filter(
            id=ad_id, advertiser=request.user
        ).first()
        if not ad_content:
            return Response(
                {'detail': 'Not found'}, 
                status=status.HTTP_404_NOT_FOUND
            )
        
        # 获取广告的订单和投放数据
        order_items = OrderItem.objects.filter(
            order__ad_content=ad_content
        ).select_related('ad_space', 'ad_space__community')
        
        # 计算基本统计
        total_spaces = order_items.count()
        total_days = sum(item.total_days for item in order_items)
        total_cost = sum(item.subtotal for item in order_items)
        
        # 按社区分组统计
        by_community = []
        communities = set(item.ad_space.community for item in order_items)
        for community in communities:
            items = order_items.filter(ad_space__community=community)
            by_community.append({
                'community_id': community.id,
                'community_name': community.name,
                'total_spaces': items.count(),
                'total_days': sum(item.total_days for item in items),
                'total_cost': sum(item.subtotal for item in items),
            })
        
        # 按时间分组统计(简化处理)
        time_data = []
        for i in range(30, -1, -1):
            date = timezone.now() - timedelta(days=i)
            items = order_items.filter(
                start_date__lte=date,
                end_date__gte=date
            )
            time_data.append({
                'date': date.date(),
                'active_spaces': items.count(),
            })
        
        data = {
            'ad_id': ad_content.id,
            'ad_title': ad_content.title,
            'total_spaces': total_spaces,
            'total_days': total_days,
            'total_cost': total_cost,
            'by_community': by_community,
            'time_series': time_data,
        }
        
        return Response(data)

# analytics/serializers.py
from rest_framework import serializers

class StatsSerializer(serializers.Serializer):
    total_ads = serializers.IntegerField(required=False)
    active_ads = serializers.IntegerField(required=False)
    total_orders = serializers.IntegerField(required=False)
    total_spent = serializers.DecimalField(max_digits=12, decimal_places=2, required=False)
    recent_orders = serializers.IntegerField(required=False)
    total_spaces = serializers.IntegerField(required=False)
    available_spaces = serializers.IntegerField(required=False)
    total_income = serializers.DecimalField(max_digits=12, decimal_places=2, required=False)

5. 高级功能实现

5.1 智能推荐系统

python 复制代码
# recommendations/services.py
from django.db.models import Q
from advertisements.models import AdSpace
from contents.models import AdContent
from orders.models import OrderItem
from datetime import date, timedelta
from collections import defaultdict
import math

class AdRecommendationService:
    @staticmethod
    def recommend_spaces_for_ad(ad_content, limit=10):
        """为广告内容推荐合适的广告位"""
        # 获取广告的目标受众特征
        target_audience = ad_content.target_audience or {}
        
        # 基础查询:可用的广告位
        queryset = AdSpace.objects.filter(is_available=True)
        
        # 根据广告类型筛选
        if ad_content.ad_type == 'video':
            queryset = queryset.filter(space_type__in=['elevator', 'parking'])
        elif ad_content.ad_type == 'image':
            queryset = queryset.exclude(space_type='other')
        
        # 根据目标社区筛选
        if 'communities' in target_audience:
            queryset = queryset.filter(community_id__in=target_audience['communities'])
        
        # 根据价格预算筛选
        if ad_content.budget:
            max_price = ad_content.budget / ((ad_content.end_date - ad_content.start_date).days or 1)
            queryset = queryset.filter(price_per_day__lte=max_price)
        
        # 排除已经预订的广告位
        booked_spaces = OrderItem.objects.filter(
            Q(start_date__lte=ad_content.end_date) & Q(end_date__gte=ad_content.start_date),
            order__status__in=['paid', 'deployed']
        ).values_list('ad_space_id', flat=True)
        queryset = queryset.exclude(id__in=booked_spaces)
        
        # 计算每个广告位的得分
        spaces = list(queryset)
        scored_spaces = []
        
        for space in spaces:
            score = 0
            
            # 价格得分(越便宜得分越高)
            price_score = 1 / (space.price_per_day or 1)
            score += price_score * 0.3
            
            # 社区规模得分
            community_score = math.log(space.community.total_households or 1)
            score += community_score * 0.4
            
            # 历史表现得分(简化处理)
            performance_score = OrderItem.objects.filter(
                ad_space=space,
                order__status='completed'
            ).count() * 0.1
            score += performance_score * 0.3
            
            scored_spaces.append((space, score))
        
        # 按得分排序
        scored_spaces.sort(key=lambda x: x[1], reverse=True)
        
        return [space for space, score in scored_spaces[:limit]]
    
    @staticmethod
    def recommend_ads_for_space(ad_space, limit=5):
        """为广告位推荐合适的广告内容"""
        # 基础查询:已批准的广告内容
        queryset = AdContent.objects.filter(status='approved')
        
        # 根据广告位类型筛选
        if ad_space.space_type == 'elevator':
            queryset = queryset.filter(ad_type__in=['image', 'video'])
        elif ad_space.space_type == 'billboard':
            queryset = queryset.filter(ad_type__in=['image', 'text'])
        
        # 排除已经预订的广告内容
        booked_ads = OrderItem.objects.filter(
            Q(start_date__lte=date.today() + timedelta(days=30)) &
            Q(end_date__gte=date.today()),
            order__status__in=['paid', 'deployed'],
            ad_space=ad_space
        ).values_list('order__ad_content_id', flat=True)
        queryset = queryset.exclude(id__in=booked_ads)
        
        # 计算每个广告的得分
        ads = list(queryset)
        scored_ads = []
        
        for ad in ads:
            score = 0
            
            # 预算得分(预算越高得分越高)
            budget_score = math.log(ad.budget or 1)
            score += budget_score * 0.4
            
            # 持续时间得分(持续时间越长得分越高)
            duration_score = (ad.end_date - ad.start_date).days
            score += duration_score * 0.3
            
            # 广告主信用得分
            advertiser_score = ad.advertiser.profile.credit_score / 100
            score += advertiser_score * 0.3
            
            scored_ads.append((ad, score))
        
        # 按得分排序
        scored_ads.sort(key=lambda x: x[1], reverse=True)
        
        return [ad for ad, score in scored_ads[:limit]]

# recommendations/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import permissions, status
from .services import AdRecommendationService
from advertisements.models import AdSpace
from contents.models import AdContent
from contents.serializers import AdContentSerializer
from advertisements.serializers import AdSpaceSerializer

class RecommendSpacesAPIView(APIView):
    permission_classes = [permissions.IsAuthenticated, IsAdvertiser]
    
    def get(self, request, ad_id):
        ad_content = AdContent.objects.filter(
            id=ad_id, advertiser=request.user
        ).first()
        if not ad_content:
            return Response(
                {'detail': 'Not found'}, 
                status=status.HTTP_404_NOT_FOUND
            )
        
        spaces = AdRecommendationService.recommend_spaces_for_ad(ad_content)
        serializer = AdSpaceSerializer(spaces, many=True)
        return Response(serializer.data)

class RecommendAdsAPIView(APIView):
    permission_classes = [permissions.IsAuthenticated, IsPropertyManager]
    
    def get(self, request, space_id):
        ad_space = AdSpace.objects.filter(
            id=space_id, 
            community__property_company=request.user
        ).first()
        if not ad_space:
            return Response(
                {'detail': 'Not found'}, 
                status=status.HTTP_404_NOT_FOUND
            )
        
        ads = AdRecommendationService.recommend_ads_for_space(ad_space)
        serializer = AdContentSerializer(ads, many=True)
        return Response(serializer.data)

5.2 广告竞价系统

python 复制代码
# bidding/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import permissions, status
from django.utils import timezone
from datetime import timedelta
from django.db import transaction
from advertisements.models import AdSpace
from contents.models import AdContent
from orders.models import AdOrder, OrderItem
from orders.services import OrderService
from .serializers import BidSerializer

class BidAPIView(APIView):
    permission_classes = [permissions.IsAuthenticated, IsAdvertiser]
    
    @transaction.atomic
    def post(self, request):
        serializer = BidSerializer(data=request.data, context={'request': request})
        serializer.is_valid(raise_exception=True)
        
        ad_content = AdContent.objects.get(
            id=serializer.validated_data['ad_content_id'],
            advertiser=request.user,
            status='approved'
        )
        ad_space = AdSpace.objects.get(
            id=serializer.validated_data['ad_space_id'],
            is_available=True
        )
        
        # 检查广告位是否可用
        start_date = serializer.validated_data['start_date']
        end_date = serializer.validated_data['end_date']
        
        conflicting_items = OrderItem.objects.filter(
            ad_space=ad_space,
            start_date__lte=end_date,
            end_date__gte=start_date,
            order__status__in=['paid', 'deployed']
        )
        if conflicting_items.exists():
            return Response(
                {'detail': 'Ad space is not available for the selected dates'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # 检查是否有更高价格的竞价
        min_price = serializer.validated_data['price_per_day']
        higher_bids = OrderItem.objects.filter(
            ad_space=ad_space,
            start_date__lte=end_date,
            end_date__gte=start_date,
            price_per_day__gt=min_price,
            order__status='pending'
        )
        if higher_bids.exists():
            return Response(
                {'detail': 'There are higher bids for this ad space'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # 创建竞价订单
        total_days = (end_date - start_date).days
        subtotal = total_days * min_price
        
        order = OrderService.create_order(
            advertiser=request.user,
            ad_content=ad_content,
            items_data=[{
                'ad_space': ad_space,
                'start_date': start_date,
                'end_date': end_date,
                'price_per_day': min_price
            }]
        )
        
        return Response(
            {'order_id': order.id, 'order_no': order.order_no},
            status=status.HTTP_201_CREATED
        )

# bidding/serializers.py
from rest_framework import serializers
from django.utils import timezone
from datetime import timedelta
from advertisements.models import AdSpace
from contents.models import AdContent

class BidSerializer(serializers.Serializer):
    ad_content_id = serializers.IntegerField()
    ad_space_id = serializers.IntegerField()
    start_date = serializers.DateField()
    end_date = serializers.DateField()
    price_per_day = serializers.DecimalField(max_digits=10, decimal_places=2)
    
    def validate_ad_content_id(self, value):
        if not AdContent.objects.filter(
            id=value, 
            advertiser=self.context['request'].user,
            status='approved'
        ).exists():
            raise serializers.ValidationError("Invalid ad content")
        return value
    
    def validate_ad_space_id(self, value):
        if not AdSpace.objects.filter(id=value, is_available=True).exists():
            raise serializers.ValidationError("Ad space not available")
        return value
    
    def validate(self, data):
        if data['start_date'] > data['end_date']:
            raise serializers.ValidationError("End date must be after start date")
        
        # 开始日期不能早于明天
        if data['start_date'] < timezone.now().date() + timedelta(days=1):
            raise serializers.ValidationError("Start date must be at least tomorrow")
        
        # 持续时间不能超过90天
        if (data['end_date'] - data['start_date']).days > 90:
            raise serializers.ValidationError("Duration cannot exceed 90 days")
        
        # 检查价格是否高于广告位的基础价格
        ad_space = AdSpace.objects.get(id=data['ad_space_id'])
        if data['price_per_day'] < ad_space.price_per_day:
            raise serializers.ValidationError(
                f"Bid price must be at least {ad_space.price_per_day}"
            )
        
        return data

5.3 实时数据监控

python 复制代码
# monitoring/consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer
from channels.db import database_sync_to_async
from advertisements.models import AdSpace
from contents.models import AdContent
from orders.models import OrderItem

class AdMonitoringConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.user = self.scope['user']
        if not self.user.is_authenticated:
            await self.close()
            return
        
        self.advertiser_group = None
        self.property_group = None
        
        if self.user.user_type == 'advertiser':
            self.advertiser_group = f'advertiser_{self.user.id}'
            await self.channel_layer.group_add(
                self.advertiser_group,
                self.channel_name
            )
        elif self.user.user_type == 'property':
            self.property_group = f'property_{self.user.id}'
            await self.channel_layer.group_add(
                self.property_group,
                self.channel_name
            )
        
        await self.accept()
    
    async def disconnect(self, close_code):
        if self.advertiser_group:
            await self.channel_layer.group_discard(
                self.advertiser_group,
                self.channel_name
            )
        if self.property_group:
            await self.channel_layer.group_discard(
                self.property_group,
                self.channel_name
            )
    
    async def receive(self, text_data):
        data = json.loads(text_data)
        action = data.get('action')
        
        if action == 'subscribe_ad':
            ad_id = data.get('ad_id')
            if ad_id and self.user.user_type == 'advertiser':
                ad = await self.get_ad_content(ad_id)
                if ad and ad.advertiser == self.user:
                    group = f'ad_{ad_id}'
                    await self.channel_layer.group_add(
                        group,
                        self.channel_name
                    )
                    await self.send(text_data=json.dumps({
                        'type': 'subscription',
                        'status': 'subscribed',
                        'ad_id': ad_id
                    }))
        
        elif action == 'subscribe_space':
            space_id = data.get('space_id')
            if space_id and self.user.user_type == 'property':
                space = await self.get_ad_space(space_id)
                if space and space.community.property_company == self.user:
                    group = f'space_{space_id}'
                    await self.channel_layer.group_add(
                        group,
                        self.channel_name
                    )
                    await self.send(text_data=json.dumps({
                        'type': 'subscription',
                        'status': 'subscribed',
                        'space_id': space_id
                    }))
    
    async def ad_update(self, event):
        await self.send(text_data=json.dumps(event))
    
    async def space_update(self, event):
        await self.send(text_data=json.dumps(event))
    
    async def order_update(self, event):
        await self.send(text_data=json.dumps(event))
    
    async def impression_update(self, event):
        await self.send(text_data=json.dumps(event))
    
    @database_sync_to_async
    def get_ad_content(self, ad_id):
        try:
            return AdContent.objects.get(id=ad_id)
        except AdContent.DoesNotExist:
            return None
    
    @database_sync_to_async
    def get_ad_space(self, space_id):
        try:
            return AdSpace.objects.get(id=space_id)
        except AdSpace.DoesNotExist:
            return None

# monitoring/signals.py
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
from contents.models import AdContent
from advertisements.models import AdSpace
from orders.models import AdOrder, OrderItem
from analytics.models import AdImpression, AdInteraction

@receiver(post_save, sender=AdContent)
def ad_content_updated(sender, instance, created, **kwargs):
    channel_layer = get_channel_layer()
    group_name = f'ad_{instance.id}'
    
    async_to_sync(channel_layer.group_send)(
        group_name,
        {
            'type': 'ad_update',
            'ad_id': instance.id,
            'status': instance.status,
            'updated': True,
            'created': created
        }
    )
    
    if instance.advertiser:
        advertiser_group = f'advertiser_{instance.advertiser.id}'
        async_to_sync(channel_layer.group_send)(
            advertiser_group,
            {
                'type': 'ad_update',
                'ad_id': instance.id,
                'status': instance.status,
                'updated': True,
                'created': created
            }
        )

@receiver(post_save, sender=AdSpace)
def ad_space_updated(sender, instance, created, **kwargs):
    channel_layer = get_channel_layer()
    group_name = f'space_{instance.id}'
    
    async_to_sync(channel_layer.group_send)(
        group_name,
        {
            'type': 'space_update',
            'space_id': instance.id,
            'is_available': instance.is_available,
            'updated': True,
            'created': created
        }
    )
    
    if instance.community and instance.community.property_company:
        property_group = f'property_{instance.community.property_company.id}'
        async_to_sync(channel_layer.group_send)(
            property_group,
            {
                'type': 'space_update',
                'space_id': instance.id,
                'is_available': instance.is_available,
                'updated': True,
                'created': created
            }
        )

@receiver(post_save, sender=AdOrder)
def order_updated(sender, instance, created, **kwargs):
    channel_layer = get_channel_layer()
    
    if instance.advertiser:
        advertiser_group = f'advertiser_{instance.advertiser.id}'
        async_to_sync(channel_layer.group_send)(
            advertiser_group,
            {
                'type': 'order_update',
                'order_id': instance.id,
                'status': instance.status,
                'updated': True,
                'created': created
            }
        )
    
    # 通知物业公司
    property_companies = set()
    for item in instance.items.all():
        if item.ad_space.community and item.ad_space.community.property_company:
            property_companies.add(item.ad_space.community.property_company)
    
    for company in property_companies:
        property_group = f'property_{company.id}'
        async_to_sync(channel_layer.group_send)(
            property_group,
            {
                'type': 'order_update',
                'order_id': instance.id,
                'status': instance.status,
                'updated': True,
                'created': created
            }
        )

@receiver(post_save, sender=AdImpression)
def impression_updated(sender, instance, created, **kwargs):
    channel_layer = get_channel_layer()
    group_name = f'ad_{instance.ad_content.id}'
    
    async_to_sync(channel_layer.group_send)(
        group_name,
        {
            'type': 'impression_update',
            'ad_id': instance.ad_content.id,
            'date': instance.date.isoformat(),
            'view_count': instance.view_count,
            'interaction_count': instance.interaction_count,
            'updated': True,
            'created': created
        }
    )

6. 系统部署与运维

6.1 部署架构

复制代码
┌───────────────────────────────────────────────────────────────┐
│                       负载均衡层 (Nginx)                       │
└───────────────────────────────────────────────────────────────┘
                               │
                     ┌─────────┴─────────┐
                     ▼                   ▼
┌─────────────────────────────────┐ ┌─────────────────────────────────┐
│          Web服务器1             │ │          Web服务器2             │
│  ┌───────────┐  ┌───────────┐  │ │  ┌───────────┐  ┌───────────┐  │
│  │   Django  │  │  Celery   │  │ │  │   Django  │  │  Celery   │  │
│  └───────────┘  └───────────┘  │ │  └───────────┘  └───────────┘  │
└─────────────────────────────────┘ └─────────────────────────────────┘
                     │                   │
                     └─────────┬─────────┘
                               ▼
┌───────────────────────────────────────────────────────────────┐
│                       数据存储层                              │
│  ┌───────────┐  ┌───────────┐  ┌───────────┐  ┌───────────┐  │
│  │ PostgreSQL│  │  Redis    │  │ RabbitMQ  │  │  OSS      │  │
│  └───────────┘  └───────────┘  └───────────┘  └───────────┘  │
└───────────────────────────────────────────────────────────────┘

6.2 Docker部署配置

dockerfile 复制代码
# docker-compose.yml
version: '3.8'

services:
  web:
    build: .
    command: gunicorn config.wsgi:application --bind 0.0.0.0:8000
    volumes:
      - .:/code
    ports:
      - "8000:8000"
    env_file:
      - .env
    depends_on:
      - redis
      - db
      - rabbitmq

  celery:
    build: .
    command: celery -A config worker -l info
    volumes:
      - .:/code
    env_file:
      - .env
    depends_on:
      - redis
      - db
      - rabbitmq

  celery-beat:
    build: .
    command: celery -A config beat -l info
    volumes:
      - .:/code
    env_file:
      - .env
    depends_on:
      - redis
      - db
      - rabbitmq

  db:
    image: postgres:13
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    environment:
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=${DB_NAME}
    ports:
      - "5432:5432"

  redis:
    image: redis:6
    ports:
      - "6379:6379"

  rabbitmq:
    image: rabbitmq:3-management
    ports:
      - "5672:5672"
      - "15672:15672"
    volumes:
      - rabbitmq_data:/var/lib/rabbitmq

volumes:
  postgres_data:
  rabbitmq_data:

6.3 性能优化策略

  1. 数据库优化

    • 使用索引优化查询性能
    • 配置数据库连接池
    • 读写分离
  2. 缓存策略

    • 使用Redis缓存热点数据
    • 实现多级缓存
    • 缓存广告位和广告内容的列表
  3. 异步处理

    • 使用Celery处理耗时任务
    • 异步生成报表
    • 异步处理图片和视频
  4. CDN加速

    • 静态资源使用CDN分发
    • 广告素材使用CDN加速
  5. 负载均衡

    • 使用Nginx做负载均衡
    • 配置多台应用服务器

7. 系统安全设计

7.1 安全措施

  1. 认证与授权

    • JWT认证
    • 细粒度的权限控制
    • 防止越权访问
  2. 数据安全

    • 敏感数据加密存储
    • 数据库备份策略
    • 防止SQL注入
  3. API安全

    • 接口限流
    • 防止CSRF攻击
    • 参数校验
  4. 日志与监控

    • 操作日志记录
    • 异常监控
    • 安全审计

7.2 安全代码示例

python 复制代码
# security/middleware.py
from django.utils.deprecation import MiddlewareMixin
from django.conf import settings
from django.core.exceptions import SuspiciousOperation
import re

class SecurityHeadersMiddleware(MiddlewareMixin):
    def process_response(self, request, response):
        # 设置安全相关的HTTP头
        response['X-Content-Type-Options'] = 'nosniff'
        response['X-Frame-Options'] = 'DENY'
        response['X-XSS-Protection'] = '1; mode=block'
        if settings.SECURE_SSL_REDIRECT:
            response['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
        return response

class InputValidationMiddleware(MiddlewareMixin):
    SQLI_PATTERNS = [
        re.compile(r'(\s*([\0\b\'\"\n\r\t\%\_\\]*\s*(((select\s*.+\s*from\s*.+)|(insert\s*.+\s*into\s*.+)|(update\s*.+\s*set\s*.+)|(delete\s*.+\s*from\s*.+)|(drop\s*.+)|(truncate\s*.+)|(alter\s*.+)|(exec\s*.+)|(\s*(all|any|not|and|between|in|like|or|some|contains|containsall|containskey)\s*.+[\=\>\<=\!\~]+.+)|(let\s+.+[\=]\s*.*)|(begin\s*.*\s*end)|(\s*[\/\*]+\s*.*\s*[\*\/]+)|(\s*(\-\-)\s*.*\s+)|(\s*(contains|containsall|containskey)\s+.*)))(\s*[\,)\;\s]*\s*)*)+', re.I),
    ]
    
    XSS_PATTERNS = [
        re.compile(r'<script.*?>.*?</script>', re.I),
        re.compile(r'on[a-z]+\s*=', re.I),
    ]
    
    def process_request(self, request):
        # 检查GET参数
        for key, value in request.GET.items():
            self._check_input(key, value)
        
        # 检查POST参数
        for key, value in request.POST.items():
            self._check_input(key, value)
        
        # 检查JSON body
        if request.content_type == 'application/json' and request.body:
            try:
                import json
                data = json.loads(request.body)
                self._check_json(data)
            except ValueError:
                pass
    
    def _check_input(self, key, value):
        if isinstance(value, str):
            for pattern in self.SQLI_PATTERNS:
                if pattern.search(value):
                    raise SuspiciousOperation(f'Potential SQL injection detected in parameter {key}')
            
            for pattern in self.XSS_PATTERNS:
                if pattern.search(value):
                    raise SuspiciousOperation(f'Potential XSS detected in parameter {key}')
    
    def _check_json(self, data):
        if isinstance(data, dict):
            for key, value in data.items():
                self._check_input(key, value)
                self._check_json(value)
        elif isinstance(data, list):
            for item in data:
                self._check_json(item)

8. 系统测试方案

8.1 测试策略

  1. 单元测试:测试各个模块的功能
  2. 集成测试:测试模块间的交互
  3. 性能测试:测试系统在高负载下的表现
  4. 安全测试:测试系统的安全性
  5. UI测试:测试用户界面

8.2 测试代码示例

python 复制代码
# tests/test_advertisements.py
from django.test import TestCase
from django.urls import reverse
from rest_framework.test import APIClient
from rest_framework import status
from advertisements.models import AdSpace
from users.models import User

class AdSpaceTests(TestCase):
    def setUp(self):
        self.client = APIClient()
        self.property_user = User.objects.create_user(
            username='property',
            password='password',
            user_type='property'
        )
        self.advertiser_user = User.objects.create_user(
            username='advertiser',
            password='password',
            user_type='advertiser'
        )
        self.community = Community.objects.create(
            name='Test Community',
            address='Test Address',
            property_company=self.property_user,
            total_buildings=10,
            total_households=1000
        )
        self.ad_space = AdSpace.objects.create(
            community=self.community,
            space_type='elevator',
            description='Test Ad Space',
            size='60x90cm',
            price_per_day=100.00,
            is_available=True
        )
    
    def test_create_ad_space_as_property(self):
        self.client.force_authenticate(user=self.property_user)
        url = reverse('adspace-list')
        data = {
            'community': self.community.id,
            'space_type': 'billboard',
            'description': 'New Ad Space',
            'size': '200x300cm',
            'price_per_day': '200.00',
            'is_available': True
        }
        response = self.client.post(url, data, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(AdSpace.objects.count(), 2)
    
    def test_create_ad_space_as_advertiser(self):
        self.client.force_authenticate(user=self.advertiser_user)
        url = reverse('adspace-list')
        data = {
            'community': self.community.id,
            'space_type': 'billboard',
            'description': 'New Ad Space',
            'size': '200x300cm',
            'price_per_day': '200.00',
            'is_available': True
        }
        response = self.client.post(url, data, format='json')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
    
    def test_list_ad_spaces(self):
        url = reverse('adspace-list')
        response = self.client.get(url, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data), 1)
    
    def test_filter_ad_spaces(self):
        # 创建另一个广告位
        AdSpace.objects.create(
            community=self.community,
            space_type='billboard',
            description='Billboard',
            size='200x300cm',
            price_per_day=200.00,
            is_available=True
        )
        
        url = reverse('adspace-list')
        # 按类型过滤
        response = self.client.get(url, {'space_type': 'elevator'}, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data), 1)
        self.assertEqual(response.data[0]['space_type'], 'elevator')
        
        # 按价格范围过滤
        response = self.client.get(url, {'min_price': 150, 'max_price': 250}, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data), 1)
        self.assertEqual(response.data[0]['space_type'], 'billboard')

9. 系统扩展与未来规划

9.1 扩展功能

  1. AI内容审核:使用机器学习自动审核广告内容
  2. 智能定价:根据历史数据和需求动态调整广告位价格
  3. AR预览:使用增强现实技术预览广告效果
  4. 区块链合约:使用智能合约管理广告交易
  5. 跨平台投放:整合线上和线下广告资源

9.2 技术演进

  1. 微服务化:将系统拆分为更小的微服务
  2. Serverless架构:部分功能使用无服务器架构
  3. 大数据分析:更深入的广告效果分析
  4. 边缘计算:在边缘节点处理部分计算任务
  5. 5G应用:利用5G网络实现更丰富的广告形式

10. 结论

本文详细设计并实现了一个基于Python的社区资源媒体管理系统,该系统专为社区户外广告打造,提供了从广告资源管理、广告内容制作、广告投放到效果分析的全流程解决方案。系统采用现代化的技术架构,具有良好的扩展性和性能表现,能够满足不同规模社区广告管理的需求。

系统的主要创新点包括:

  1. 专业化设计:针对社区户外广告场景的深度定制
  2. 智能化推荐:基于算法的广告位推荐系统
  3. 可视化操作:直观的广告资源管理和投放界面
  4. 安全可靠:多层次的安全防护机制

未来,系统可以进一步整合AI和大数据技术,提供更智能的广告投放服务和更精准的效果分析,成为社区户外广告领域的标杆解决方案。

相关推荐
Star在努力几秒前
15-C语言:第15~16天笔记
c语言·笔记·算法
CoovallyAIHub5 分钟前
工业质检新突破!YOLO-pdd多尺度PCB缺陷检测算法实现99%高精度
深度学习·算法·计算机视觉
gb42152876 分钟前
负载均衡算法中的加权随机算法
windows·算法·负载均衡
悟乙己8 分钟前
译|生存分析Survival Analysis案例入门讲解(一)
人工智能·机器学习·数据挖掘·生存分析·因果推荐
都给我9 分钟前
服务器中涉及节流(Throttle)的硬件组件及其应用注意事项
服务器·网络·express
ALe要立志成为web糕手12 分钟前
计算机网络基础
网络·安全·web安全·网络安全
都叫我大帅哥27 分钟前
决策树:从零开始的机器学习“算命大师”修炼手册
python·机器学习
这里有鱼汤27 分钟前
首个支持A股的AI多智能体金融系统,来了
前端·python
loopdeloop34 分钟前
机器学习、深度学习与数据挖掘:核心技术差异、应用场景与工程实践指南
深度学习·机器学习·数据挖掘
liulun35 分钟前
SkSurface---像素的容器:表面
网络·网络协议·rpc