社区资源媒体管理系统设计与实现
1. 系统概述
社区资源媒体管理系统是一个专为社区户外广告打造的高效、专业化平台,旨在实现社区媒体的数字化管理、智能投放和便捷交易。该系统将整合社区各类广告资源,为广告主、物业公司和社区居民提供一站式服务。
1.1 系统目标
- 实现社区户外广告资源的数字化管理
- 提供精准广告投放功能
- 建立广告交易平台
- 优化广告资源利用率
- 提升广告投放效果分析能力
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 微服务划分
- 用户服务:处理用户注册、登录、权限管理
- 广告服务:广告资源管理、投放策略
- 交易服务:订单管理、支付处理
- 数据服务:数据分析、报表生成
- 消息服务:通知、站内信
- 文件服务:图片、视频等资源管理
3. 数据库设计
3.1 主要数据表结构
用户相关表
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)
社区相关表
class Community(models.Model):name = models.CharField(max_length=100)address = models.TextField()location = models.PointField() # 使用GeoDjangototal_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()
广告资源相关表
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)
广告内容相关表
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, completedcreated_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)
订单交易相关表
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)
效果统计相关表
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)
系统管理相关表
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 用户认证与权限管理
# authentication/backends.py
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
from .models import Userclass 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 userexcept User.DoesNotExist:return None# authentication/permissions.py
from rest_framework.permissions import BasePermissionclass 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, UserProfileclass 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 userclass UserProfileSerializer(serializers.ModelSerializer):class Meta:model = UserProfilefields = ['id_card', 'address', 'credit_score']class UserSerializer(serializers.ModelSerializer):profile = UserProfileSerializer()class Meta:model = Userfields = ['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.profilefor 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 广告资源管理模块
# 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 AdSpaceFilterclass AdSpaceViewSet(viewsets.ModelViewSet):queryset = AdSpace.objects.all()serializer_class = AdSpaceSerializerfilter_backends = [DjangoFilterBackend]filterset_class = AdSpaceFilterdef 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, AdSpaceImageclass PointField(serializers.Field):def to_representation(self, value):if value:return {'lng': value.x, 'lat': value.y}return Nonedef to_internal_value(self, data):if data and 'lng' in data and 'lat' in data:return Point(float(data['lng']), float(data['lat']))return Noneclass AdSpaceImageSerializer(serializers.ModelSerializer):class Meta:model = AdSpaceImagefields = ['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 = AdSpacefields = ['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 AdSpaceclass 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 = AdSpacefields = ['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 广告内容管理模块
# 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_contentclass AdContentViewSet(viewsets.ModelViewSet):queryset = AdContent.objects.all()serializer_class = AdContentSerializerfilter_backends = [DjangoFilterBackend]filterset_class = AdContentFilterdef 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 querysetdef 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 FileResourceSerializerclass AdMaterialSerializer(serializers.ModelSerializer):file_details = FileResourceSerializer(source='file', read_only=True)class Meta:model = AdMaterialfields = ['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 = AdContentfields = ['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 timetime.sleep(10) # 模拟审核耗时# 90%的概率通过审核import randomif 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 订单交易模块
# 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 AdContentclass AdOrderViewSet(viewsets.ModelViewSet):queryset = AdOrder.objects.all()serializer_class = AdOrderSerializerdef 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 querysetdef get_serializer_class(self):if self.action == 'create':return CreateOrderSerializerreturn super().get_serializer_class()@transaction.atomicdef 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 AdContentSerializerclass OrderItemSerializer(serializers.ModelSerializer):ad_space_details = AdSpaceSerializer(source='ad_space', read_only=True)class Meta:model = OrderItemfields = ['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 = AdOrderfields = ['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_spacedata['price_per_day'] = ad_space.price_per_dayreturn dataclass 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 valuedef 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, OrderItemclass OrderService:@staticmethod@transaction.atomicdef 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']).dayssubtotal = 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 数据统计与分析模块
# 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 StatsSerializerclass DashboardStatsAPIView(APIView):permission_classes = [permissions.IsAuthenticated]def get(self, request):user = request.usertime_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 serializersclass 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 智能推荐系统
# 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 mathclass AdRecommendationService:@staticmethoddef 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.1score += performance_score * 0.3scored_spaces.append((space, score))# 按得分排序scored_spaces.sort(key=lambda x: x[1], reverse=True)return [space for space, score in scored_spaces[:limit]]@staticmethoddef 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).daysscore += duration_score * 0.3# 广告主信用得分advertiser_score = ad.advertiser.profile.credit_score / 100score += advertiser_score * 0.3scored_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 AdSpaceSerializerclass 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 广告竞价系统
# 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 BidSerializerclass BidAPIView(APIView):permission_classes = [permissions.IsAuthenticated, IsAdvertiser]@transaction.atomicdef 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).dayssubtotal = total_days * min_priceorder = 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 AdContentclass 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 valuedef 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 valuedef 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 实时数据监控
# 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 OrderItemclass AdMonitoringConsumer(AsyncWebsocketConsumer):async def connect(self):self.user = self.scope['user']if not self.user.is_authenticated:await self.close()returnself.advertiser_group = Noneself.property_group = Noneif 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_asyncdef get_ad_content(self, ad_id):try:return AdContent.objects.get(id=ad_id)except AdContent.DoesNotExist:return None@database_sync_to_asyncdef 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部署配置
# docker-compose.yml
version: '3.8'services:web:build: .command: gunicorn config.wsgi:application --bind 0.0.0.0:8000volumes:- .:/codeports:- "8000:8000"env_file:- .envdepends_on:- redis- db- rabbitmqcelery:build: .command: celery -A config worker -l infovolumes:- .:/codeenv_file:- .envdepends_on:- redis- db- rabbitmqcelery-beat:build: .command: celery -A config beat -l infovolumes:- .:/codeenv_file:- .envdepends_on:- redis- db- rabbitmqdb:image: postgres:13volumes:- 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:6ports:- "6379:6379"rabbitmq:image: rabbitmq:3-managementports:- "5672:5672"- "15672:15672"volumes:- rabbitmq_data:/var/lib/rabbitmqvolumes:postgres_data:rabbitmq_data:
6.3 性能优化策略
-
数据库优化:
- 使用索引优化查询性能
- 配置数据库连接池
- 读写分离
-
缓存策略:
- 使用Redis缓存热点数据
- 实现多级缓存
- 缓存广告位和广告内容的列表
-
异步处理:
- 使用Celery处理耗时任务
- 异步生成报表
- 异步处理图片和视频
-
CDN加速:
- 静态资源使用CDN分发
- 广告素材使用CDN加速
-
负载均衡:
- 使用Nginx做负载均衡
- 配置多台应用服务器
7. 系统安全设计
7.1 安全措施
-
认证与授权:
- JWT认证
- 细粒度的权限控制
- 防止越权访问
-
数据安全:
- 敏感数据加密存储
- 数据库备份策略
- 防止SQL注入
-
API安全:
- 接口限流
- 防止CSRF攻击
- 参数校验
-
日志与监控:
- 操作日志记录
- 异常监控
- 安全审计
7.2 安全代码示例
# security/middleware.py
from django.utils.deprecation import MiddlewareMixin
from django.conf import settings
from django.core.exceptions import SuspiciousOperation
import reclass 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 responseclass 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 bodyif request.content_type == 'application/json' and request.body:try:import jsondata = json.loads(request.body)self._check_json(data)except ValueError:passdef _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 测试策略
- 单元测试:测试各个模块的功能
- 集成测试:测试模块间的交互
- 性能测试:测试系统在高负载下的表现
- 安全测试:测试系统的安全性
- UI测试:测试用户界面
8.2 测试代码示例
# 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 Userclass 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 扩展功能
- AI内容审核:使用机器学习自动审核广告内容
- 智能定价:根据历史数据和需求动态调整广告位价格
- AR预览:使用增强现实技术预览广告效果
- 区块链合约:使用智能合约管理广告交易
- 跨平台投放:整合线上和线下广告资源
9.2 技术演进
- 微服务化:将系统拆分为更小的微服务
- Serverless架构:部分功能使用无服务器架构
- 大数据分析:更深入的广告效果分析
- 边缘计算:在边缘节点处理部分计算任务
- 5G应用:利用5G网络实现更丰富的广告形式
10. 结论
本文详细设计并实现了一个基于Python的社区资源媒体管理系统,该系统专为社区户外广告打造,提供了从广告资源管理、广告内容制作、广告投放到效果分析的全流程解决方案。系统采用现代化的技术架构,具有良好的扩展性和性能表现,能够满足不同规模社区广告管理的需求。
系统的主要创新点包括:
- 专业化设计:针对社区户外广告场景的深度定制
- 智能化推荐:基于算法的广告位推荐系统
- 可视化操作:直观的广告资源管理和投放界面
- 安全可靠:多层次的安全防护机制
未来,系统可以进一步整合AI和大数据技术,提供更智能的广告投放服务和更精准的效果分析,成为社区户外广告领域的标杆解决方案。