基于Python+Django的校园音乐平台:从数据可视化到分布式计算的毕设实战

发布时间:2026/9/30 3:11:13
基于Python+Django的校园音乐平台:从数据可视化到分布式计算的毕设实战 毕业设计选题季每年这个时候我都会收到一堆学弟学妹的私信学长Django的毕设到底该做什么、想用Python做毕设但不知道选什么题、有没有不太卷、但是答辩又拿得出手的项目。如果你也在纠结这个问题那这篇关于青听校园音乐平台的整理值得你耐心看完。先说清楚这是个什么东西这是一个基于Python Django开发的全栈校园音乐平台前端用HTML/CSS/JS配合 BootstraP 和 ECharts后端引入了分布式计算的思想用 Celery Redis 处理异步任务用 Channels WebSocket 做实时数据推送最后再通过数据可视化把用户行为、歌曲热度、播放趋势这些数据画成图表。换句话说它不是一个简单的 CRUD 音乐管理后台而是一个把简历里分布式、数据可视化、实时推送这几个关键词全部落到实处的毕业设计项目。强烈建议你先收藏后面写毕设或者做项目实训的时候肯定用得上。下面我不光讲这个平台怎么搭还会把每个技术点背后为什么这么做踩了哪些坑答辩时怎么讲一并给你拆开揉碎。1. 毕业设计选题的幕后逻辑为什么音乐平台值得做1.1 题目覆盖面分析一个题目对应简历上的三块拼图很多人在毕设选题的时候会走两个极端要么选纯理论、纯算法的题目写完连个能点开的界面都没有要么选一些烂大街的管理系统比如图书管理系统超市管理系统做完之后除了增删改查完全说不出技术亮点。选择音乐平台这个方向本质上是在业务复杂度和技术深度之间找一个平衡点。音乐平台的业务模型比普通管理系统要丰富得多。你要处理用户的注册登录、歌手专辑歌单的关联关系、播放记录的产生、评论的发布、音乐文件的上传与播放甚至还有搜索、推荐、排行榜这些衍生功能。这些功能落到数据库上就是多张表之间的外键关联、多对多关系、聚合统计查询Django 的 ORM 在这里能发挥出很大优势。更重要的是这个题目天然适配三个加分方向第一是数据可视化音乐平台每天会产生大量播放记录和用户行为数据画趋势图、柱状图、饼图都合情合理第二是分布式计算歌曲的播放计数、热门榜单统计、消息推送这些任务如果全部同步处理会占用大量请求线程用消息队列削峰填谷就成了一件顺理成章的事第三是实时通信你完全可以做一个 WebSocket 推送让后台一旦有新的播放数据前台页面马上更新。这三块拼图凑在一起在毕业答辩里具备了从功能演示升级到系统设计讲解的基础。1.2 技术栈定型的思路Django做主力框架的理由先解决一个最常见的问题为什么是 Django而不是 Flask 或者 Spring BootFlask 确实轻量配一个 ECharts 写数据可视化页面也很快但 Flask 默认不自带 ORM、Admin 后台、用户认证体系这些在毕设中都要自己拼装做到后面反而更耗时。Spring Boot 在企业里很主流但如果你主攻 Python再用 Java 系框架精力会被分散掉而且对零基础同学来说门槛偏高。Django 最吸引人的地方在于它的全家桶属性自带的 Admin 后台能直接看到用户、歌曲、评论的数据管理自带的 User 模型可以通过继承 AbstractUser 扩展头像、签名、会员字段自带的 ORM 让你不用写原生 SQL 就能完成复杂的连表查询和聚合统计。换句话说Django 帮你踩掉了大量重复性的坑你能把宝贵的时间省下来投入到分布式任务和数据可视化这两个最有答辩价值的点上。再说分布式计算这个点。严格来说一个毕业设计项目并不会真的去部署一套 Hadoop 或者 Spark 集群那属于分布式计算里的大数据分支。在 Web 项目语境下分布式的合理落地方式是把耗时任务拆出去交给独立进程去跑让 Web 服务器和任务执行器在逻辑上分离。青听平台采用的是Celery 分布式任务队列 Redis 消息代理的方案Web 进程收到请求后先把任务写入 Redis然后由多个 Worker 进程并行消费任务。这样你能在答辩现场演示并发越高、任务队列自动扩容的分布式特性比口头上说我了解分布式原理有说服力得多。2. 平台的底层骨架从数据模型到前后端交互2.1 数据库模型设计音乐平台的六个核心表数据模型是整个平台的根基。我第一次做这类项目的时候犯过一个错误一上来就写视图、写模板等做到播放记录和榜单统计时才发现表结构设计得乱七八糟返工重来了三天。所以如果你要复现这个项目建议先花半天时间把模型定下来。青听平台的模型我按业务域拆成了用户、音乐、歌单、记录、评论这几块核心表大概如下from django.db import models from django.contrib.auth.models import AbstractUser class User(AbstractUser): # 扩展Django自带用户头像、签名、会员标记 avatar models.ImageField(upload_toavatar/%Y/%m, defaultavatar/default.png) signature models.CharField(max_length255, blankTrue) is_vip models.BooleanField(defaultFalse) created_at models.DateTimeField(auto_now_addTrue) class Singer(models.Model): name models.CharField(max_length50, db_indexTrue) avatar models.ImageField(upload_tosinger/) region models.CharField(max_length20, blankTrue) # 地区用于可视化 intro models.TextField(blankTrue) class Album(models.Model): title models.CharField(max_length100) singer models.ForeignKey(Singer, on_deletemodels.CASCADE, related_namealbums) cover models.ImageField(upload_toalbum_cover/) publish_date models.DateField(nullTrue, blankTrue) class Song(models.Model): title models.CharField(max_length100) singer models.ForeignKey(Singer, on_deletemodels.CASCADE, related_namesongs) album models.ForeignKey(Album, on_deletemodels.SET_NULL, nullTrue, related_namesongs) audio models.FileField(upload_toaudio/) duration models.IntegerField(default0, help_text时长单位秒) play_count models.BigIntegerField(default0) # 冗余字段减少实时聚合压力 lyric models.TextField(blankTrue) created_at models.DateTimeField(auto_now_addTrue) class Playlist(models.Model): name models.CharField(max_length50) user models.ForeignKey(User, on_deletemodels.CASCADE, related_nameplaylists) cover models.ImageField(upload_toplaylist_cover/, nullTrue, blankTrue) songs models.ManyToManyField(Song, related_nameplaylists) created_at models.DateTimeField(auto_now_addTrue) class PlayRecord(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, related_nameplay_records) song models.ForeignKey(Song, on_deletemodels.CASCADE, related_nameplay_records) played_at models.DateTimeField(auto_now_addTrue) class Comment(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE) song models.ForeignKey(Song, on_deletemodels.CASCADE, related_namecomments) content models.TextField() created_at models.DateTimeField(auto_now_addTrue)这里有两个新手容易忽略的设计细节。第一个是Song.play_count字段我用的是一个冗余计数而不是在展示时去PlayRecord表里 Count。原因是播放记录表会随着用户操作无限膨胀每次访问榜单都聚合 Count 一次数据库压力会非常大。更好的做法是播放时通过 Celery 异步把计数写入这个字段展示时直接查这个字段就行了。第二个是related_name一定要写清楚比如singer.playlists和song.playlists如果都叫playlists就会冲突良好的命名习惯能避免后续 ORM 查询时一堆莫名其妙的报错。2.2 ORM查询优化select_related与prefetch_related音乐平台里最常见的查询是列表页展示歌曲同时显示歌手名和专辑封面。如果你用最原始的写法songs Song.objects.all() for song in songs: print(song.singer.name, song.album.title)这段代码触发的 SQL 查询次数是 1 2N。歌曲有 100 条就会额外发 200 次查询请求页面响应时间会肉眼可见地变慢。解决方式很简单在查询时用select_related把外键关系一次性 join 出来songs Song.objects.select_related(singer, album).all()而如果是查询歌单并且要取歌单里的所有歌曲这就是一个多对多关系select_related帮不上忙得用prefetch_relatedplaylists Playlist.objects.prefetch_related(songs__singer).all()这类优化在功能开发阶段看不出来区别因为测试数据只有几十条。但答辩的时候很可能会被评委问一句如果你的平台有十万首歌曲现在的查询方式还撑得住吗你只要能答出select_related、prefetch_related和普通查询的 N1 问题区别这个加分项基本就稳了。2.3 Django模板体系与路由组织Django 的路由组织有一个习惯我特别推荐每个 app 内部创建自己的 urls.py然后在项目根路由里用 include 挂载。青听平台我拆了user、music、analysis、api这几个 app根路由大概长这样from django.contrib import admin from django.urls import path, include from django.conf import settings from django.conf.urls.static import static urlpatterns [ path(admin/, admin.site.urls), path(user/, include(user.urls)), path(music/, include(music.urls)), path(analysis/, include(analysis.urls)), path(api/, include(api.urls)), ] if settings.DEBUG: urlpatterns static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)这个设计的直接好处是分析模块的可视化接口走/api/业务页面走/music/用户中心走/user/职责清晰后面加新功能基本不会动到根路由。模板方面Django 默认的模板语法虽然不如 Vue 那样灵活但应对这个项目已经足够了。我的建议是页面骨架用模板继承!-- base.html -- !DOCTYPE html html langzh-CN head meta charsetUTF-8 title{% block title %}青听校园音乐平台{% endblock %}/title link relstylesheet href{% static css/bootstrap.min.css %} {% block extra_head %}{% endblock %} /head body {% include header.html %} {% block content %}{% endblock %} {% include footer.html %} script src{% static js/jquery.min.js %}/script script src{% static js/bootstrap.min.js %}/script {% block extra_js %}{% endblock %} /body /html这样写的好处是每个页面只需要写自己的{% block content %}部分页面之间风格统一也不会出现同一个导航栏在不同页面重复复制粘贴的问题。3. 数据可视化落地ECharts在Django里的正确打开方式3.1 可视化数据从哪来ORM聚合查询与JsonResponse数据可视化最关键的并不是前端怎么画图而是后端怎么把数据喂给前端。我在这个项目里没有采用复杂的 ECharts 前后端分离方案而是走 DjangO 模板页面 Ajax 动态刷新这样既保住了 Django 模板开发的便利性又能让图表实时更新。后端我先写了一个提供 JSON 数据的视图放在analysis/views.py里。比如统计最近七天每天的播放量from django.db.models import Count from django.db.models.functions import TruncDate from django.http import JsonResponse from django.utils import timezone from datetime import timedelta def play_trend_data(request): days int(request.GET.get(days, 7)) start timezone.now() - timedelta(daysdays) records ( PlayRecord.objects .filter(played_at__gtestart) .annotate(dayTruncDate(played_at)) .values(day) .annotate(totalCount(id)) .order_by(day) ) dates [item[day].strftime(%m-%d) for item in records] counts [item[total] for item in records] return JsonResponse({dates: dates, counts: counts})这段代码的核心在于TruncDate它能把played_at这个DateTimeField截断成日期配合annotate就能按天分组计数。很多新手会试图用filter(played_at__contains2025-01-01)这种土办法来筛日期SQL 效率低不说代码还丑。用 TruncDate 才是 Django 官方推荐的做法。再比如歌手歌曲分布图和热门歌曲 Top10本质上就是把聚合条件换一下def singer_songs_data(request): data list( Singer.objects.annotate(song_countCount(songs)) .values(name, song_count) .order_by(-song_count)[:10] ) return JsonResponse({data: data}) def hot_rank_data(request): data list( Song.objects.order_by(-play_count)[:10] .values(title, play_count) ) return JsonResponse({data: data})这里有一个要点values(name, song_count)之后返回的是一个字典列表JsonResponse会自动帮你做 JSON 序列化不需要手动json.dumps也不需要ensure_asciiFalse因为 Django 已经处理好了中文编码。如果你在返回时遇到中文乱码记得检查是不是自己手动做了多余的序列化。3.2 ECharts图表在模板中的接入后端准备好了 JSON 接口前端页面的核心工作就是拿数据、渲染图表。在 DjangO 模板中存 ECharts我强烈建议在{% block extra_js %}里写图表初始化代码不要全部堆到页面底部。以首页的播放趋势折线图为例// 页面里的 script 标签 $(function () { $.getJSON(/api/play-trend/, function (res) { var chart echarts.init(document.getElementById(trendChart)); var option { title: { text: 最近7天播放量趋势 }, tooltip: { trigger: axis }, grid: { left: 3%, right: 4%, bottom: 3%, containLabel: true }, xAxis: { type: category, data: res.dates }, yAxis: { type: value }, series: [{ name: 播放量, type: line, smooth: true, areaStyle: {}, data: res.counts }] }; chart.setOption(option); // 浏览器窗口变化时自适应 window.addEventListener(resize, function () { chart.resize(); }); }); });这里最容易踩的一个坑是echarts.init时绑定的 DOM 元素必须是可见的。如果你把图标放在一个默认隐藏的 Tab 页里面等切换 Tab 的时候会发现图表宽高为 0。解决办法是在 Tab 切换事件里手动调用一次chart.resize()。另一个常见问题是直接把 ECharts 的echarts.min.js放在本地静态目录如果你是从 CDN 下载的旧版本可能会有兼容性问题建议直接到 ECharts 官网下载当前最新版本或者用官方 CDN。3.3 几个高清分可视化案例播放趋势、歌手分布、热榜Top10除了播放趋势折线图我还做了三个可视化页面分别对应不同图表类型答辩时展示效果会非常丰富歌手地区分布饼图数据从Singer.region字段聚合而来用饼图能直观看出校园音乐平台里本土歌手和国际歌手的占比。热门歌曲 Top10 横向柱状图最能体现榜单效果建议用横向柱状图把 yAxis 和 xAxis 的 type 互换歌名显示更完整。用户活跃时段热力图这个稍微进阶一点把一天 24 小时按小时分组统计每个小时段的播放次数用 ECharts 的 heatmap 展示能够看出用户在午休和晚上两个时间段使用最频繁。这三个可视化的数据接口全部通过上面的聚合查询方式获取前端模板结构基本一致只是option里的type不同。我建议你在开发时把每个图表封装成一个独立的renderXxxChart()函数维护起来会舒服很多。4. 分布式计算真实落地场景不是堆机器而是拆任务4.1 为什么Django项目需要Celery耗时代码的归宿聊到分布式计算之前我先说一个很现实的问题Django 的视图函数默认是同步阻塞的如果一个请求里要执行耗时的操作比如发送邮件、解析音乐文件的时长、批量更新播放计数、生成数据报表这个请求的线程就会被占住用户就要一直转圈等待并发稍高一点整个服务就容易被拖垮。分布式计算在这个项目里最扎实的落地方式就是把这类耗时不紧急的任务从请求链路中剥离出去交给消息队列和后台 Worker 去处理。用到的工具就是Celery。Celery 的基本模型可以类比成一个外卖平台Django 视图就是下订单的顾客Redis 是订单池Celery Worker 是骑手。顾客点单之后不需要自己跑到厨房等菜订单进入池子空闲的骑手取单配送。对应到系统里用户播放歌曲时视图函数不需要立刻去写PlayRecord、去累加play_count而是把任务发给 Redis然后立刻返回播放成功后台 Worker 再去慢慢处理这些任务。这里就体现出分布式的意味了你可以开多个 Worker 进程每个 Worker 独立消费任务队列任务多了就横向扩展 Worker 数量不依赖 Web 进程本身。4.2 Celery Redis集成全流程我把我实际搭通的配置步骤完整列出来照着做基本不会出错。首先安装依赖pip install celery redis然后在项目根目录创建qingting/celery.pyimport os from celery import Celery os.environ.setdefault(DJANGO_SETTINGS_MODULE, qingting.settings) app Celery(qingting) app.config_from_object(django.conf:settings, namespaceCELERY) app.autodiscover_tasks()接着在settings.py里补充配置CELERY_BROKER_URL redis://127.0.0.1:6379/0 CELERY_RESULT_BACKEND redis://127.0.0.1:6379/1 CELERY_TASK_SERIALIZER json CELERY_TIMEZONE Asia/Shanghai然后写一个异步任务放在music/tasks.pyfrom celery import shared_task from django.core.cache import cache shared_task def add_play_record(song_id, user_id): from .models import PlayRecord, Song PlayRecord.objects.create(song_idsong_id, user_iduser_id) Song.objects.filter(pksong_id).update(play_countF(play_count) 1) # 删除排行榜缓存让下次查询重新聚合 cache.delete(hot_rank)注意几个细节。第一任务内部建议延迟导入模型写在函数里面而不是写在文件顶部避免 Celery 加载任务时因为 Django 模型未初始化而报错。第二累加次数用F(play_count) 1而不是先取再写这样在并发情况下不会丢失计数。第三创建 PlayRecord 之前可以把这个用户是否已经播放过这首歌是否允许重复播放这类校验逻辑放在视图层任务层只做最简单的数据落库。启动的时候分别开两个终端# 终端1启动Django开发服务器 python manage.py runserver # 终端2启动Celery WorkerWindows下推荐加 -P eventlet celery -A qingting worker -l info -P eventletWindows 下不加-P eventlet经常会报ValueError: not enough values to unpack这是因为 Celery 默认的 prefork 模式在 Windows 上没有完整的 fork 机制换成 eventlet 协程模式就能正常跑。需要先执行pip install eventlet。4.3 WebSocket实时数据推送Channels实现前台实时更新热词里有一条很关键的django websocket实现后台有数据前端推送。这正好是青听平台的亮点功能。Django 原生只支持 HTTP要实现 WebSocket 需要引入Channels它把 Django 扩展成了支持 ASGI异步服务器网关接口的框架。安装和配置很简单pip install channels channels-redis# settings.py INSTALLED_APPS [ # ... daphne, channels, ] ASGI_APPLICATION qingting.asgi.application CHANNEL_LAYERS { default: { BACKEND: channels_redis.core.RedisChannelLayer, CONFIG: {hosts: [(127.0.0.1, 6379)]}, } }然后写一个消费者Consumer作用是统计当前在线人数并实时推送给所有连接的前端页面# analysis/consumers.py import json from channels.generic.websocket import AsyncWebsocketConsumer from channels.db import database_sync_to_async from django.contrib.auth.models import User class OnlineConsumer(AsyncWebsocketConsumer): async def connect(self): self.group_name online_users await self.channel_layer.group_add(self.group_name, self.channel_name) await self.accept() # 把当前在线人数推给所有连接的客户端 online_count await self.get_online_count() await self.channel_layer.group_send( self.group_name, {type: online_count, value: online_count} ) async def disconnect(self, code): await self.channel_layer.group_discard(self.group_name, self.channel_name) async def online_count(self, event): await self.send(text_datajson.dumps({type: online_count, value: event[value]})) database_sync_to_async def get_online_count(self): return User.objects.filter(last_login__gte...).count()路由文件也要跟上# analysis/routing.py from django.urls import path from .consumers import OnlineConsumer websocket_urlpatterns [ path(ws/online/, OnlineConsumer.as_asgi()), ]再把analysis/routing.py挂到项目的asgi.py里。前端页面只需要建立 WebSocket 连接var ws new WebSocket(ws:// window.location.host /ws/online/); ws.onmessage function (event) { var data JSON.parse(event.data); if (data.type online_count) { $(#onlineCount).text(data.value 人在线); } };做到这一步你在后台用另一个浏览器登录前台就能实时看到在线人数变化。这个功能演示起来非常直观也是整个项目里看起来最像企业级系统的一个点。4.4 数据库读写分离与缓存策略我在部署版本里还做了一个轻量级读写分离的尝试所有的写操作走主库所有的读操作走从库。这虽然是数据库层的垂直拆分但在 Django 里配置非常简单只需要在settings.py里定义多套数据库DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: qingting, USER: root, PASSWORD: 你的密码, HOST: 127.0.0.1, PORT: 3306, }, replica: { ENGINE: django.db.backends.mysql, NAME: qingting, USER: root, PASSWORD: 你的密码, HOST: 192.168.1.4, # 从库地址 PORT: 3306, } } DATABASE_ROUTERS [qingting.db_router.MasterSlaveRouter]然后写一个简单的路由类实现db_for_read和db_for_write两个方法。核心逻辑就一句话读操作给replica写操作给default。在真实生产环境里从库和主库之间是通过主从复制保持数据一致的毕设环境如果没有条件搭主从你可以用两个本机 MySQL 实例模拟或者在答辩时讲清楚设计思路即可。缓存方面排行榜、歌手列表这种不会实时变化的数据我用 Redis 做了缓存。查询时先读缓存缓存里没有才查数据库查完之后再写入缓存并设置过期时间。这就是典型的Cache Aside 模式写代码不超过十行但讲起来非常有体系感。5. 开发期踩坑记录每一个坑都可以是答辩加分项5.1 静态文件404的老问题Django 开发环境下静态文件 404 是出现频率最高的问题。很多人用python manage.py runserver启动后访问页面发现 CSS、JS 全部加载不出来控制台全是 404。原因通常是忘记在项目的urls.py里配置静态文件路由。开发环境下我建议直接在根路由里加一句from django.conf import settings from django.conf.urls.static import static urlpatterns static(settings.STATIC_URL, document_rootsettings.STATIC_ROOT)同时要检查settings.py里是否设置了STATICFILES_DIRS并且保证模板里是用{% static css/style.css %}而不是写死/static/css/style.css。写死路径在你本地开发可能没有问题一旦部署到服务器路径前缀发生变化就会出现全部引用失效的情况。5.2 ECharts图表渲染不出来宽高为零的问题ECharts 初始化时如果容器没有设置宽度和高度图表是画不出来的。我第一次遇到这个坑是在把图表放进 Bootstrap 的 Tab 页时只有一个 Tab 是默认显示的到了第二个 Tab聪明的折腾半天才发现不是数据的问题而是容器被隐藏时宽度为 0。解决办法有两个要么给每个图表的div写死height: 400px; width: 100%要么在 Tab 切换的回调里调用setTimeout(() chart.resize(), 200)。我最后选择的是给容器写死高度避免因为 resize 时机问题导致图表变形。还有一个细节$.getJSON请求后端接口时如果接口返回 500浏览器控制台只会显示一个红色的GET ... 500 (Internal Server Error)真正的报错信息在 Django 终端里。这时候要学会第一时间看终端堆栈而不是在前端反复调试。5.3 Celery在Windows下的兼容性问题Windows 下启动 Celery 的坑我在前面提过一次这里再展开讲。没有加-P eventlet时Celery worker 启动后能正常接收任务但一旦有任务真正执行Windows 上容易进程卡死或抛出ValueError。这个问题在 macOS 和 Linux 上不存在主要是 Windows 没有原生 fork 系统调用。解决方案就是安装eventlet并用协程模式启动pip install eventlet celery -A qingting worker -l info -P eventlet另外提醒一句加入 Channels 之后启动开发服务器不能再用python manage.py runserver而要改用daphne qingting.asgi:application或者使用 uvicorn。我实测下来用uvicorn qingting.asgi:application --reload更顺手重载功能和 runserver 类似。5.4 N1查询导致的响应变慢我在 2.2 节中已经讲解了select_related和prefetch_related。开发初期我没有太在意一直等到数据量增加到几百条发现歌曲列表页打开要将近两秒才意识到这个问题的严重性。后来我用 Django 自带的一个调试工具django-debug-toolbar查看 SQL 执行次数发现一个页面竟然发出去几百条 SQL这才彻底修改查询写法。在这里想多强调一句当你写完一个列表页养成用 debug-toolbar 看一眼 SQL 次数的习惯比什么都管用。5.5 媒体文件上传与访问路径音乐平台的用户头像、歌手封面、音乐文件都属于媒体文件。开发环境下要把MEDIA_ROOT和MEDIA_URL配置好并且在根路由中手工添加静态路由这样才能在页面里通过{{ song.audio.url }}访问到文件。这个问题的隐蔽性在于Admin 后台里能看到上传成功的文件但前台就是加载不出来。检查思路很简单先看MEDIA_URL是否被模板正确引用再看 Django 终端是否拦截了/media/开头的请求。部署到生产环境后媒体文件通常由 Nginx 直接托管不再经过 Django所以这些配置还要再调整一遍。6. 项目收尾与答辩经验从源码到优秀毕业设计的临门一脚6.1 部署结构Nginx Gunicorn Django毕设如果需要现场演示直接在本地用开发服务器跑完全没问题但如果你想在服务器上部署一份让评委通过公网域名访问推荐的部署结构是Nginx负责接收外部 HTTP 请求托管静态文件和媒体文件Gunicorn负责运行业务代码处理动态请求Django通过 WSGI 与 Gunicorn 对接Celery Worker独立进程处理异步任务Redis提供消息队列与缓存支撑Gunicorn 启动命令gunicorn qingting.wsgi:application -w 4 -b 127.0.0.1:8000Nginx 的核心配置大概如下server { listen 80; server_name your_domain.com; location /static/ { alias /your_project/static/; } location /media/ { alias /your_project/media/; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这个配置我在多个项目里验证过稳定性没问题。需要注意的是部署前一定要执行python manage.py collectstatic收集静态文件否则 Nginx 找不到任何 CSS 和 JS。6.2 答辩演示的操作顺序建议答辩的时候给评委演示项目顺序和节奏非常关键。我不建议一上来就演示功能而是先用一个数据可视化页面开场比如播放趋势折线图和热门 Top10 柱状图让评委在头三十秒就 get 到这个项目有数据分析能力而不是一个普通的管理系统。接着演示核心业务登录、站内搜索、听歌、创建歌单。这里不要贪多把一条主链路走通就好。然后切换到分布式与实时通信这个技术亮点打开后台手动模拟一个用户播放歌曲前台页面通过 WebSocket 立刻刷新在线人数和最新播放记录同时解释这一过程中 Redis 队列和 Celery Worker 各自做了什么。最后打开 Admin 后台展示数据模型设计和数据库表结构回答评委关于表与表之间关系的提问。这部分需要提前准备好一张系统架构图图中清楚标注浏览器、Nginx、Django、Redis、Celery Worker、MySQL 六个组件之间的关系。不需要史实图或者复杂工具用 PowerPoint 画一个简单的框图就足够了。6.3 这版源码还能往哪些方向延伸如果你做完上面这些功能之后还有富余时间我非常推荐再往下面几个方向扩展一两个能让项目含金量再上一个台阶第一个方向是推荐算法。基于用户播放记录构建协同过滤推荐给用户推荐可能喜欢的歌曲。不用做到大厂那样复杂的深度模型一个基于物品的协同过滤ItemCF算法就足以写清楚原理并实现。第二个方向是自动标签。从歌曲名和评论内容提取关键词生成词云图这是数据可视化里非常出效果的一个扩展。第三个方向是移动端适配。把前端页面用响应式布局重构或者做成一个简单的微信小程序通过后端 API 交互这样系统就横跨了 PC 端和移动端两个平台。我个人在这类项目里最感兴趣的其实是Celery 任务队列结合定时任务每天凌晨自动汇总前一天的播放数据生成日报并通过站内消息推送给管理员。这个功能实现难度不大但它把任务队列 定时调度 数据聚合串在了一条链路上讲解起来特别容易引起评委的兴趣。回到开头那个问题毕业设计的项目到底该怎么选我的结论很简单——选那些能把课本上几个重要概念落到真实代码里的题目。青听校园音乐平台这套组合Python Django HTML/CSS/JS 分布式计算 数据可视化具备功能完整、技术点密集、可演示性强三个特点这几年带学生复盘下来走这条路线的最终答辩效果普遍都不错。你照着上面的结构和踩坑记录动手做一遍等到答辩那天你会感谢自己当初没有为了省事去选一个图书管理系统。