Python全栈项目实战:校园活动管理与推荐平台

项目概述

校园活动管理与推荐平台是一个面向高校师生的综合性活动管理系统,旨在解决校园活动信息分散、参与度低、管理效率不高等问题。系统通过智能推荐算法,为学生精准推送感兴趣的活动,提升校园活动的参与率和管理效率。

核心功能

  • 🎯 活动管理:活动发布、审核、编辑、取消
  • 👥 用户系统:学生、社团、管理员多角色管理
  • 🔔 消息通知:活动提醒、报名通知、实时推送
  • 智能推荐:基于用户行为的个性化活动推荐
  • 📊 数据分析:活动数据统计、用户画像分析
  • 💬 互动评价:活动评论、点赞、评分

技术栈选型

后端技术

# 核心框架
- Django 4.2          # Web框架
- Django REST Framework  # RESTful API
- Celery 5.3          # 异步任务队列
- Redis 7.0           # 缓存与消息队列

# 数据存储
- PostgreSQL 15       # 关系型数据库
- MongoDB 6.0         # 用户行为数据
- Elasticsearch 8.0   # 全文搜索

# 推荐系统
- Pandas              # 数据处理
- Scikit-learn        # 机器学习
- Surprise            # 协同过滤

前端技术

// 核心框架
- Vue.js 3.3          // 前端框架
- Vite 4.0            // 构建工具
- Pinia               // 状态管理
- Vue Router          // 路由管理

// UI组件库
- Element Plus        // PC端组件库
- Vant 4              // 移动端组件库

// 工具库
- Axios               // HTTP请求
- Day.js              // 日期处理
- ECharts             // 数据可视化

系统架构设计

整体架构

┌─────────────────────────────────────────────┐
│              前端层(Vue3)                    │
├──────────────┬──────────────┬────────────────┤
│   PC端界面    │   移动端界面   │   管理后台      │
└──────────────┴──────────────┴────────────────┘
                      ↓ HTTP/WebSocket
┌─────────────────────────────────────────────┐
│          Nginx (反向代理 + 负载均衡)           │
└─────────────────────────────────────────────┘
                      ↓
┌─────────────────────────────────────────────┐
│         Django REST Framework (API层)        │
├──────────────┬──────────────┬────────────────┤
│   用户模块    │   活动模块     │   推荐模块      │
└──────────────┴──────────────┴────────────────┘
                      ↓
┌──────────────┬──────────────┬────────────────┐
│  PostgreSQL  │   MongoDB    │      Redis      │
│  (主数据库)   │  (行为数据)   │   (缓存/队列)    │
└──────────────┴──────────────┴────────────────┘

推荐系统架构

# 推荐引擎流程
用户行为收集 → 特征工程 → 模型训练 → 候选生成 → 排序 → 结果返回
     ↓           ↓          ↓         ↓        ↓        ↓
  MongoDB    Pandas    Scikit-learn  Redis   排序算法  缓存

核心功能实现

1. 用户认证与权限管理

# users/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models

class User(AbstractUser):
    """用户模型"""
    USER_TYPE_CHOICES = [
        ('student', '学生'),
        ('club', '社团'),
        ('admin', '管理员'),
    ]
    
    user_type = models.CharField(
        max_length=20,
        choices=USER_TYPE_CHOICES,
        default='student'
    )
    student_id = models.CharField(max_length=20, unique=True, null=True)
    major = models.CharField(max_length=100, blank=True)
    grade = models.IntegerField(null=True)
    avatar = models.ImageField(upload_to='avatars/', null=True)
    interests = models.JSONField(default=list)  # 兴趣标签
    
    class Meta:
        db_table = 'users'
        verbose_name = '用户'
        verbose_name_plural = '用户'

# users/serializers.py
from rest_framework import serializers

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'username', 'email', 'user_type', 
                  'student_id', 'major', 'grade', 'avatar', 'interests']
        read_only_fields = ['id']

# users/views.py
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework_simplejwt.tokens import RefreshToken

class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = [IsAuthenticated]
    
    @action(detail=False, methods=['post'])
    def register(self, request):
        """用户注册"""
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = serializer.save()
        
        # 生成JWT Token
        refresh = RefreshToken.for_user(user)
        return Response({
            'user': UserSerializer(user).data,
            'token': str(refresh.access_token),
            'refresh': str(refresh)
        }, status=status.HTTP_201_CREATED)
    
    @action(detail=False, methods=['get'])
    def profile(self, request):
        """获取当前用户信息"""
        serializer = self.get_serializer(request.user)
        return Response(serializer.data)

2. 活动管理模块

# activities/models.py
from django.db import models
from django.contrib.postgres.fields import ArrayField

class Activity(models.Model):
    """活动模型"""
    STATUS_CHOICES = [
        ('draft', '草稿'),
        ('pending', '待审核'),
        ('approved', '已通过'),
        ('rejected', '已拒绝'),
        ('ongoing', '进行中'),
        ('completed', '已结束'),
        ('cancelled', '已取消'),
    ]
    
    CATEGORY_CHOICES = [
        ('academic', '学术讲座'),
        ('sports', '体育竞赛'),
        ('culture', '文艺演出'),
        ('volunteer', '志愿服务'),
        ('competition', '技能竞赛'),
        ('social', '社交联谊'),
    ]
    
    title = models.CharField(max_length=200, verbose_name='活动标题')
    description = models.TextField(verbose_name='活动描述')
    category = models.CharField(
        max_length=20,
        choices=CATEGORY_CHOICES,
        verbose_name='活动类别'
    )
    tags = ArrayField(
        models.CharField(max_length=50),
        default=list,
        verbose_name='标签'
    )
    
    organizer = models.ForeignKey(
        'users.User',
        on_delete=models.CASCADE,
        related_name='organized_activities',
        verbose_name='主办方'
    )
    
    location = models.CharField(max_length=200, verbose_name='活动地点')
    start_time = models.DateTimeField(verbose_name='开始时间')
    end_time = models.DateTimeField(verbose_name='结束时间')
    
    capacity = models.IntegerField(verbose_name='活动容量')
    current_participants = models.IntegerField(default=0)
    
    status = models.CharField(
        max_length=20,
        choices=STATUS_CHOICES,
        default='draft',
        verbose_name='活动状态'
    )
    
    cover_image = models.ImageField(
        upload_to='activities/',
        null=True,
        verbose_name='封面图片'
    )
    
    registration_deadline = models.DateTimeField(
        verbose_name='报名截止时间'
    )
    
    views_count = models.IntegerField(default=0, verbose_name='浏览量')
    likes_count = models.IntegerField(default=0, verbose_name='点赞数')
    
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        db_table = 'activities'
        ordering = ['-created_at']
        indexes = [
            models.Index(fields=['category', 'status']),
            models.Index(fields=['start_time']),
        ]

class Registration(models.Model):
    """报名记录"""
    activity = models.ForeignKey(
        Activity,
        on_delete=models.CASCADE,
        related_name='registrations'
    )
    user = models.ForeignKey(
        'users.User',
        on_delete=models.CASCADE,
        related_name='registrations'
    )
    status = models.CharField(
        max_length=20,
        choices=[
            ('registered', '已报名'),
            ('attended', '已参加'),
            ('absent', '缺席'),
            ('cancelled', '已取消'),
        ],
        default='registered'
    )
    registered_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        db_table = 'registrations'
        unique_together = ['activity', 'user']

# activities/views.py
from rest_framework import viewsets, filters
from django_filters.rest_framework import DjangoFilterBackend
from .tasks import send_activity_notification

class ActivityViewSet(viewsets.ModelViewSet):
    queryset = Activity.objects.all()
    serializer_class = ActivitySerializer
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    filterset_fields = ['category', 'status', 'organizer']
    search_fields = ['title', 'description', 'tags']
    ordering_fields = ['start_time', 'created_at', 'views_count']
    
    def perform_create(self, serializer):
        """创建活动"""
        activity = serializer.save(organizer=self.request.user)
        # 异步发送通知
        send_activity_notification.delay(activity.id)
    
    @action(detail=True, methods=['post'])
    def register(self, request, pk=None):
        """报名活动"""
        activity = self.get_object()
        
        # 检查活动状态
        if activity.status != 'approved':
            return Response(
                {'error': '活动暂不接受报名'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # 检查是否已满员
        if activity.current_participants >= activity.capacity:
            return Response(
                {'error': '活动已满员'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # 创建报名记录
        registration, created = Registration.objects.get_or_create(
            activity=activity,
            user=request.user
        )
        
        if not created:
            return Response(
                {'error': '您已报名此活动'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # 更新参与人数
        activity.current_participants += 1
        activity.save()
        
        return Response({'message': '报名成功'})
    
    @action(detail=True, methods=['post'])
    def like(self, request, pk=None):
        """点赞活动"""
        activity = self.get_object()
        activity.likes_count += 1
        activity.save()
        return Response({'likes_count': activity.likes_count})

3. 智能推荐系统

# recommendations/engine.py
import pandas as pd
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from surprise import SVD, Dataset, Reader
from collections import defaultdict

class RecommendationEngine:
    """推荐引擎"""
    
    def __init__(self):
        self.user_activity_matrix = None
        self.activity_features = None
        self.cf_model = SVD()  # 协同过滤模型
    
    def build_user_activity_matrix(self):
        """构建用户-活动交互矩阵"""
        from activities.models import Registration, Activity
        
        registrations = Registration.objects.values_list(
            'user_id', 'activity_id', 'status'
        )
        
        # 评分规则:已参加=5分,已报名=3分,缺席=1分
        score_map = {
            'attended': 5,
            'registered': 3,
            'absent': 1
        }
        
        data = []
        for user_id, activity_id, status in registrations:
            score = score_map.get(status, 0)
            data.append([user_id, activity_id, score])
        
        df = pd.DataFrame(data, columns=['user_id', 'activity_id', 'rating'])
        return df
    
    def extract_activity_features(self):
        """提取活动特征"""
        from activities.models import Activity
        
        activities = Activity.objects.filter(status='approved').values(
            'id', 'category', 'tags'
        )
        
        feature_dict = {}
        for activity in activities:
            # 独热编码类别
            category_vector = self._encode_category(activity['category'])
            # TF-IDF编码标签
            tag_vector = self._encode_tags(activity['tags'])
            # 合并特征
            feature_dict[activity['id']] = np.concatenate([
                category_vector, tag_vector
            ])
        
        return feature_dict
    
    def collaborative_filtering(self, user_id, top_n=10):
        """协同过滤推荐"""
        df = self.build_user_activity_matrix()
        
        # 构建Surprise数据集
        reader = Reader(rating_scale=(1, 5))
        data = Dataset.load_from_df(df, reader)
        
        # 训练模型
        trainset = data.build_full_trainset()
        self.cf_model.fit(trainset)
        
        # 获取用户未参与的活动
        user_activities = set(df[df['user_id'] == user_id]['activity_id'])
        all_activities = set(df['activity_id'].unique())
        unseen_activities = all_activities - user_activities
        
        # 预测评分
        predictions = []
        for activity_id in unseen_activities:
            pred = self.cf_model.predict(user_id, activity_id)
            predictions.append((activity_id, pred.est))
        
        # 返回Top N
        predictions.sort(key=lambda x: x[1], reverse=True)
        return [act_id for act_id, _ in predictions[:top_n]]
    
    def content_based_filtering(self, user_id, top_n=10):
        """基于内容的推荐"""
        from users.models import User
        from activities.models import Activity
        
        # 获取用户兴趣标签
        user = User.objects.get(id=user_id)
        user_interests = set(user.interests)
        
        # 获取活动特征
        activities = Activity.objects.filter(status='approved')
        
        # 计算相似度
        scores = []
        for activity in activities:
            activity_tags = set(activity.tags)
            # Jaccard相似度
            similarity = len(user_interests & activity_tags) / \
                        len(user_interests | activity_tags) if activity_tags else 0
            scores.append((activity.id, similarity))
        
        # 排序返回
        scores.sort(key=lambda x: x[1], reverse=True)
        return [act_id for act_id, _ in scores[:top_n]]
    
    def hybrid_recommendation(self, user_id, top_n=10):
        """混合推荐策略"""
        # 协同过滤推荐
        cf_results = self.collaborative_filtering(user_id, top_n=20)
        # 基于内容推荐
        cb_results = self.content_based_filtering(user_id, top_n=20)
        
        # 融合策略:加权求和
        cf_weight = 0.6
        cb_weight = 0.4
        
        combined_scores = defaultdict(float)
        
        for i, act_id in enumerate(cf_results):
            combined_scores[act_id] += cf_weight * (20 - i)
        
        for i, act_id in enumerate(cb_results):
            combined_scores[act_id] += cb_weight * (20 - i)
        
        # 排序返回Top N
        sorted_results = sorted(
            combined_scores.items(),
            key=lambda x: x[1],
            reverse=True
        )
        return [act_id for act_id, _ in sorted_results[:top_n]]

# recommendations/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .engine import RecommendationEngine

class RecommendationView(APIView):
    """推荐接口"""
    
    def get(self, request):
        user_id = request.user.id
        engine = RecommendationEngine()
        
        # 获取推荐活动ID
        recommended_ids = engine.hybrid_recommendation(user_id, top_n=10)
        
        # 查询活动详情
        activities = Activity.objects.filter(id__in=recommended_ids)
        serializer = ActivitySerializer(activities, many=True)
        
        return Response({
            'recommendations': serializer.data
        })

4. 异步任务处理

# tasks.py
from celery import shared_task
from django.core.mail import send_mail
from django.conf import settings

@shared_task
def send_activity_notification(activity_id):
    """发送活动通知"""
    from activities.models import Activity
    from users.models import User
    
    activity = Activity.objects.get(id=activity_id)
    
    # 获取订阅该类别的用户
    users = User.objects.filter(
        interests__contains=[activity.category]
    )
    
    for user in users:
        send_mail(
            subject=f'新活动推荐:{activity.title}',
            message=f'{activity.description[:100]}...',
            from_email=settings.DEFAULT_FROM_EMAIL,
            recipient_list=[user.email],
            fail_silently=True,
        )

@shared_task
def update_recommendation_cache():
    """更新推荐缓存"""
    from recommendations.engine import RecommendationEngine
    from users.models import User
    import redis
    
    r = redis.Redis.from_url(settings.REDIS_URL)
    engine = RecommendationEngine()
    
    for user in User.objects.filter(user_type='student'):
        recommendations = engine.hybrid_recommendation(user.id)
        cache_key = f'recommendations:{user.id}'
        r.setex(cache_key, 3600, str(recommendations))  # 缓存1小时

前端实现要点

Vue3 + Composition API

<!-- ActivityList.vue -->
<template>
  <div class="activity-list">
    <el-row :gutter="20">
      <!-- 筛选器 -->
      <el-col :span="6">
        <el-card>
          <h3>筛选条件</h3>
          <el-form>
            <el-form-item label="活动类别">
              <el-select v-model="filters.category" placeholder="请选择">
                <el-option label="全部" value=""></el-option>
                <el-option label="学术讲座" value="academic"></el-option>
                <el-option label="体育竞赛" value="sports"></el-option>
                <el-option label="文艺演出" value="culture"></el-option>
              </el-select>
            </el-form-item>
            
            <el-form-item label="活动时间">
              <el-date-picker
                v-model="filters.dateRange"
                type="daterange"
                range-separator="至"
              />
            </el-form-item>
            
            <el-button type="primary" @click="loadActivities">
              搜索
            </el-button>
          </el-form>
        </el-card>
      </el-col>
      
      <!-- 活动列表 -->
      <el-col :span="18">
        <div v-loading="loading">
          <el-card
            v-for="activity in activities"
            :key="activity.id"
            class="activity-card"
          >
            <div class="activity-content">
              <img :src="activity.cover_image" class="cover" />
              <div class="info">
                <h2>{{ activity.title }}</h2>
                <p class="description">{{ activity.description }}</p>
                <div class="meta">
                  <el-tag>{{ activity.category }}</el-tag>
                  <span>{{ formatDate(activity.start_time) }}</span>
                  <span>{{ activity.location }}</span>
                </div>
                <div class="stats">
                  <span>
                    <el-icon><View /></el-icon>
                    {{ activity.views_count }}
                  </span>
                  <span>
                    <el-icon><Star /></el-icon>
                    {{ activity.likes_count }}
                  </span>
                  <span>
                    报名:{{ activity.current_participants }}/{{ activity.capacity }}
                  </span>
                </div>
              </div>
              <div class="actions">
                <el-button type="primary" @click="registerActivity(activity.id)">
                  立即报名
                </el-button>
                <el-button @click="viewDetail(activity.id)">
                  查看详情
                </el-button>
              </div>
            </div>
          </el-card>
          
          <!-- 分页 -->
          <el-pagination
            v-model:current-page="pagination.page"
            v-model:page-size="pagination.pageSize"
            :total="pagination.total"
            @change="loadActivities"
          />
        </div>
      </el-col>
    </el-row>
  </div>
</template>

<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { getActivities, registerForActivity } from '@/api/activities'
import dayjs from 'dayjs'

// 状态管理
const activities = ref([])
const loading = ref(false)

const filters = reactive({
  category: '',
  dateRange: [],
  search: ''
})

const pagination = reactive({
  page: 1,
  pageSize: 10,
  total: 0
})

// 加载活动列表
const loadActivities = async () => {
  loading.value = true
  try {
    const params = {
      page: pagination.page,
      page_size: pagination.pageSize,
      category: filters.category,
      start_time_after: filters.dateRange[0],
      start_time_before: filters.dateRange[1]
    }
    
    const response = await getActivities(params)
    activities.value = response.results
    pagination.total = response.count
  } catch (error) {
    ElMessage.error('加载活动失败')
  } finally {
    loading.value = false
  }
}

// 报名活动
const registerActivity = async (activityId) => {
  try {
    await registerForActivity(activityId)
    ElMessage.success('报名成功')
    loadActivities()
  } catch (error) {
    ElMessage.error(error.response?.data?.error || '报名失败')
  }
}

// 格式化日期
const formatDate = (date) => {
  return dayjs(date).format('YYYY-MM-DD HH:mm')
}

// 查看详情
const viewDetail = (activityId) => {
  router.push(`/activities/${activityId}`)
}

onMounted(() => {
  loadActivities()
})
</script>

<style scoped>
.activity-list {
  padding: 20px;
}

.activity-card {
  margin-bottom: 20px;
}

.activity-content {
  display: flex;
  gap: 20px;
}

.cover {
  width: 200px;
  height: 150px;
  object-fit: cover;
  border-radius: 8px;
}

.info {
  flex: 1;
}

.description {
  color: #666;
  margin: 10px 0;
}

.meta {
  display: flex;
  gap: 15px;
  color: #999;
  font-size: 14px;
  margin: 10px 0;
}

.stats {
  display: flex;
  gap: 20px;
  color: #999;
  font-size: 14px;
}

.actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
  justify-content: center;
}
</style>

状态管理(Pinia)

// stores/user.js
import { defineStore } from 'pinia'
import { login, getUserProfile } from '@/api/auth'

export const useUserStore = defineStore('user', {
  state: () => ({
    token: localStorage.getItem('token') || '',
    userInfo: null
  }),
  
  getters: {
    isLoggedIn: (state) => !!state.token,
    isStudent: (state) => state.userInfo?.user_type === 'student',
    isClub: (state) => state.userInfo?.user_type === 'club'
  },
  
  actions: {
    async login(credentials) {
      const response = await login(credentials)
      this.token = response.token
      this.userInfo = response.user
      localStorage.setItem('token', response.token)
    },
    
    async fetchProfile() {
      const response = await getUserProfile()
      this.userInfo = response
    },
    
    logout() {
      this.token = ''
      this.userInfo = null
      localStorage.removeItem('token')
    }
  }
})

数据库设计

PostgreSQL表结构

-- 用户表
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(150) UNIQUE NOT NULL,
    email VARCHAR(254) UNIQUE NOT NULL,
    password VARCHAR(128) NOT NULL,
    user_type VARCHAR(20) NOT NULL,
    student_id VARCHAR(20) UNIQUE,
    major VARCHAR(100),
    grade INTEGER,
    avatar VARCHAR(200),
    interests JSONB DEFAULT '[]',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 活动表
CREATE TABLE activities (
    id SERIAL PRIMARY KEY,
    title VARCHAR(200) NOT NULL,
    description TEXT NOT NULL,
    category VARCHAR(20) NOT NULL,
    tags TEXT[],
    organizer_id INTEGER REFERENCES users(id),
    location VARCHAR(200) NOT NULL,
    start_time TIMESTAMP NOT NULL,
    end_time TIMESTAMP NOT NULL,
    capacity INTEGER NOT NULL,
    current_participants INTEGER DEFAULT 0,
    status VARCHAR(20) DEFAULT 'draft',
    cover_image VARCHAR(200),
    registration_deadline TIMESTAMP NOT NULL,
    views_count INTEGER DEFAULT 0,
    likes_count INTEGER DEFAULT 0,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 报名表
CREATE TABLE registrations (
    id SERIAL PRIMARY KEY,
    activity_id INTEGER REFERENCES activities(id),
    user_id INTEGER REFERENCES users(id),
    status VARCHAR(20) DEFAULT 'registered',
    registered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE(activity_id, user_id)
);

-- 评论表
CREATE TABLE comments (
    id SERIAL PRIMARY KEY,
    activity_id INTEGER REFERENCES activities(id),
    user_id INTEGER REFERENCES users(id),
    content TEXT NOT NULL,
    rating INTEGER CHECK (rating >= 1 AND rating <= 5),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 索引
CREATE INDEX idx_activities_category_status ON activities(category, status);
CREATE INDEX idx_activities_start_time ON activities(start_time);
CREATE INDEX idx_registrations_user ON registrations(user_id);

部署方案

Docker Compose配置

# docker-compose.yml
version: '3.8'

services:
  # Django后端
  web:
    build: ./backend
    command: gunicorn config.wsgi:application --bind 0.0.0.0:8000
    volumes:
      - ./backend:/app
      - static_volume:/app/staticfiles
      - media_volume:/app/media
    env_file:
      - .env
    depends_on:
      - db
      - redis
    ports:
      - "8000:8000"
  
  # PostgreSQL数据库
  db:
    image: postgres:15
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=campus_activity
      - POSTGRES_USER=admin
      - POSTGRES_PASSWORD=password123
  
  # Redis缓存
  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data
  
  # Celery Worker
  celery:
    build: ./backend
    command: celery -A config worker -l info
    volumes:
      - ./backend:/app
    env_file:
      - .env
    depends_on:
      - db
      - redis
  
  # Celery Beat
  celery-beat:
    build: ./backend
    command: celery -A config beat -l info
    volumes:
      - ./backend:/app
    env_file:
      - .env
    depends_on:
      - db
      - redis
  
  # Nginx
  nginx:
    image: nginx:alpine
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - static_volume:/app/staticfiles
      - media_volume:/app/media
      - ./frontend/dist:/usr/share/nginx/html
    ports:
      - "80:80"
    depends_on:
      - web

volumes:
  postgres_data:
  redis_data:
  static_volume:
  media_volume:

Nginx配置

# nginx/nginx.conf
upstream django {
    server web:8000;
}

server {
    listen 80;
    server_name campus-activity.com;
    
    # 前端静态文件
    location / {
        root /usr/share/nginx/html;
        try_files $uri $uri/ /index.html;
    }
    
    # API接口
    location /api/ {
        proxy_pass http://django;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    
    # 静态文件
    location /static/ {
        alias /app/staticfiles/;
    }
    
    # 媒体文件
    location /media/ {
        alias /app/media/;
    }
    
    # WebSocket支持
    location /ws/ {
        proxy_pass http://django;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

项目亮点与总结

技术亮点

  1. 前后端分离架构:Django REST Framework + Vue3,职责清晰
  2. 智能推荐算法:协同过滤 + 基于内容的混合推荐策略
  3. 异步任务处理:Celery实现消息推送、数据统计等耗时任务
  4. 缓存优化:Redis缓存热门活动和推荐结果,提升性能
  5. 实时通知:WebSocket实现活动提醒、报名通知
  6. 全文搜索:Elasticsearch提供快速准确的活动搜索
  7. 容器化部署:Docker Compose一键部署,易于扩展

性能优化

  • 数据库索引优化,提升查询速度
  • 分页加载,减少单次数据传输量
  • 图片CDN加速,优化静态资源加载
  • API接口缓存,减少数据库压力
  • 推荐结果预计算,提升用户体验

安全措施

  • JWT Token认证,保障接口安全
  • CORS跨域配置,防止恶意请求
  • SQL注入防护,参数化查询
  • XSS攻击防护,输入内容过滤
  • HTTPS加密传输,保护用户隐私

未来优化方向

  1. 引入深度学习模型,提升推荐精准度
  2. 增加实时聊天功能,促进用户互动
  3. 开发小程序版本,扩大用户覆盖面
  4. 接入第三方支付,支持付费活动
  5. 大数据分析,生成用户行为报告

总结

本项目是一个功能完善的全栈Web应用,涵盖了用户管理、活动管理、智能推荐、异步任务、实时通知等核心功能。通过Django和Vue3的结合,实现了高效的前后端分离开发。推荐系统的引入,使得活动信息能够精准触达目标用户,提升了平台价值。

源代码下载

https://download.youkuaiyun.com/download/exlink2012/92057822

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天天进步2015

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值