
简介在Web开发中选择一个合适的框架并设计清晰的数据结构是构建完整业务系统的关键。Python作为易上手的语言搭配Django框架可快速实现用户认证、文件上传、数据处理、权限控制等核心功能。本文以在线音乐网站为典型应用场景从源码和数据库设计切入介绍如何通过Django的ORM构建歌手-专辑-歌曲等多表关联并落地搜索、播放、评论、收藏等互动模块。同时探讨Ajax异步交互、分页、媒体文件处理等工程实践帮助开发者理清从需求分析到部署上线的完整链路。对于正在准备毕业设计或希望系统掌握Django Web开发的人来说这是一个性价比高且可操作性强的学习参考。 每年到毕业设计选题的时候都会有不少读者私信问我有没有什么题目既不会太简单显得敷衍又不会复杂到做不完我通常会反问一句你介不介意用Python如果答案是不介意那基于PythonDjango的在线音乐网站这个方向性价比真的很高。原因很简单音乐网站把Web开发里常见的技术点全覆盖了——用户认证、文件上传、数据处理、权限控制、搜索、异步交互甚至连媒体文件存储和流式播放这种稍微进阶一点的东西都能沾上边。而且PythonDjango对新手非常友好模型、视图、模板一条链路走下来很容易讲清楚也容易做成一个完成度很高的毕业设计。这篇文章就围绕源码数据库这个关键词把设计与实现过程中真正值得关注的内容拆开讲一遍。1. 毕业设计选这个题目的真实原因音乐网站背后有哪些可展示的技术点1.1 为什么是Python Django而不是Spring Boot / Node.js很多同学选技术栈时都会纠结。我见过不少用Spring Boot做音乐网站翻车的案例代码还没写几行先被Maven依赖和Java配置折腾了两周。这不是说Spring Boot不好而是对于大多数本科毕业设计场景Django的学习曲线更平缓开发效率也更高。我推荐Django的核心逻辑有三点ORM太省事了。写模型类就能自动建表迁移、回滚都方便不用手写SQL也不容易在答辩时被问到你这SQL怎么没加索引这种细节。Admin后台是天然的数据录入工具。音乐网站需要大量歌曲、歌手、专辑数据Django自带的Admin可以直接录入不用单独做后台管理页面。自带安全机制。CSRF防护、XSS转义、SQL注入防护这些Django在框架层面就处理了一部分论文里也有的写。当然如果你是Java方向的学生学校课程强制要求Spring Boot那另说。但如果是自由选技术PythonDjango的交付压力会小很多。下面是三个技术栈的快速对比方便你在论文里也放一张类似的表对比项DjangoSpring BootNode.js Express学习成本低文档全高概念多中生态杂开发效率高自带ORM和Admin中配置多中需要自行拼装数据库支持好迁移方便好功能强一般需要额外ORM适合项目中小型Web系统企业级应用实时交互应用答辩友好度高逻辑清晰中容易陷入细节中异步逻辑不好讲1.2 这个项目能支撑起哪些毕业设计答辩问题选完题之后还要想想答辩老师可能问什么。音乐网站这个题目能延伸出的技术问题其实非常密集用户密码是怎么加密存储的Django的PBKDF2加盐哈希文件上传如何限制类型和大小表单校验 扩展名白名单如何防止SQL注入ORM参数化查询在线播放是怎么实现的HTML5 audio标签直接指向媒体文件URL数据库表之间的关联关系是怎样的一对多、多对多这些问题在项目里都有对应的实现你只要真正跑过一遍基本都能答上来。这也是我为什么说这个题目性价比高——它不是那种查无实据的假项目每一个功能模块都能落到具体的代码和数据库表上。2. 在线音乐网站的功能范围从需求文档到功能拆解2.1 基础功能用户、歌手、歌曲、歌单、排行榜做项目之前先把功能边界画清楚。我见过太多人一上来就想做网易云音乐Plus结果做了三个月连播放页面都没做完。毕业设计不是产品竞赛功能要全但更要可控。一个合格的在线音乐网站基础功能应该是这样的用户模块注册、登录、退出登录、个人中心。音乐展示歌手列表、专辑列表、歌曲列表至少要有列表页和详情页。歌曲播放点击歌曲后可以在线播放最好有上一首/下一首。歌单模块用户可以创建歌单、往歌单里添加歌曲、查看歌单详情。排行榜按播放量、收藏数或发布时间排一个榜单。这些功能做下来已经能覆盖前端页面 后端接口 数据库操作的全部流程。不需要再贪多把每个功能做扎实比堆砌十个半成品功能要好得多。2.2 附加功能评论、收藏、搜索、试听在基础功能之上再加几个锦上添花的功能会显得项目有层次感评论登录用户可以给歌曲或歌单发表评论。收藏用户可以收藏喜欢的歌曲收藏后在个人中心能看到。搜索支持按歌曲名、歌手名、专辑名进行模糊搜索。试听在排行榜或歌曲列表里点播放按钮即可试听不用进入详情页。这些功能实现起来都不难但能让你的功能模块图丰富很多。而且答辩时老师通常会问你做了哪些特色功能你至少有三四个能说的。2.3 非功能需求性能、安全、可维护性论文里除了功能需求一般还要写非功能需求。这块很多人不知道怎么写我提供一个可以直接参考的框架性能列表页每次只加载10条数据翻页才加载更多封面图片统一压缩为小尺寸。安全使用Django内置的CSRF防护所有数据库查询通过ORM完成对上传文件做类型校验。可维护性代码按MVT模式分层视图只做业务处理模板只负责展示模型统一管理数据库字段。别小看这几句话它代表你考虑过上线后的真实使用情况。答辩老师一眼就能看出你有没有真正折腾过项目。3. 数据库设计一张表一张表地捋清楚3.1 核心数据表结构说明在线音乐网站的数据库设计核心就是歌手-专辑-歌曲这条主线再加上用户、评论、歌单这些外围表。下面是我推荐的表结构直接用Django模型表示from django.db import models from django.contrib.auth.models import User class Singer(models.Model): name models.CharField(歌手名, max_length50) avatar models.ImageField(头像, upload_toavatar/, blankTrue) intro models.TextField(简介, blankTrue) def __str__(self): return self.name class Album(models.Model): name models.CharField(专辑名, max_length100) cover models.ImageField(封面, upload_tocover/, blankTrue) publish_date models.DateField(发行日期, nullTrue, blankTrue) singer models.ForeignKey(Singer, on_deletemodels.CASCADE, related_namealbums) def __str__(self): return self.name class Song(models.Model): name models.CharField(歌曲名, max_length100) singer models.ForeignKey(Singer, on_deletemodels.CASCADE, related_namesongs) album models.ForeignKey(Album, on_deletemodels.CASCADE, related_namesongs, nullTrue, blankTrue) audio_file models.FileField(音频文件, upload_toaudio/) cover models.ImageField(歌曲封面, upload_tosong_cover/, blankTrue) duration models.IntegerField(时长秒, default0) lyrics models.TextField(歌词, blankTrue) play_count models.IntegerField(播放量, default0) created_at models.DateTimeField(创建时间, auto_now_addTrue) def __str__(self): return self.name这里我把歌手和专辑设计成一对外键一个歌手可以有多张专辑一张专辑可以有多首歌。歌曲直接挂在歌手下面也挂在专辑下面。这样既能按歌手找歌也能按专辑找歌。3.2 多对多关系的落地歌单与歌曲、用户与收藏歌单和歌曲之间就是典型的多对多关系一个歌单可以包含多首歌一首歌可以出现在多个歌单里。在Django里可以直接用ManyToManyField但如果你需要记录这首歌是在什么时候加进歌单的那就得用中间模型。class Playlist(models.Model): name models.CharField(歌单名, max_length100) user models.ForeignKey(User, on_deletemodels.CASCADE, related_nameplaylists) cover models.ImageField(歌单封面, upload_toplaylist_cover/, blankTrue) description models.TextField(歌单描述, blankTrue) created_at models.DateTimeField(创建时间, auto_now_addTrue) def __str__(self): return self.name class PlaylistSong(models.Model): playlist models.ForeignKey(Playlist, on_deletemodels.CASCADE, related_nameplaylist_songs) song models.ForeignKey(Song, on_deletemodels.CASCADE, related_namein_playlists) added_at models.DateTimeField(添加时间, auto_now_addTrue) class Meta: ordering [added_at]收藏功能类似。最简单的做法是在Song上直接加一个ManyToManyField(User, related_namefavorite_songs)但为了记录收藏时间我一般建议单独建一张Favorite表class Favorite(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, related_namefavorites) song models.ForeignKey(Song, on_deletemodels.CASCADE, related_namefavorited_by) created_at models.DateTimeField(收藏时间, auto_now_addTrue) class Meta: unique_together (user, song)unique_together可以保证同一个用户不能重复收藏同一首歌这是很实用的防重复设计。3.3 排行榜与统计数据的存储策略排行榜的实现思路很多人会想复杂。其实对于毕业设计最简单可靠的方式就是直接按play_count排序hot_songs Song.objects.order_by(-play_count)[:10]如果你还想做本周新歌榜或者收藏榜可以按时间过滤后再排序from django.utils import timezone from datetime import timedelta week_ago timezone.now() - timedelta(days7) new_songs Song.objects.filter(created_at__gteweek_ago).order_by(-play_count)[:10]不需要引入Redis也不用建一堆缓存表。答辩时如果老师问你排行榜怎么保证实时性你完全可以说当前方案是直接查询数据库取Top N数据量在千级以下时性能没有问题如果未来数据量增大可以引入Redis缓存把Top N结果定期刷新。这样的回答既诚实又展示了你的扩展思路。4. 核心功能模块的实现写代码时最容易卡住的几个点4.1 用户注册登录与权限控制用户功能不用自己造轮子。Django自带的django.contrib.auth已经包含了User模型和登录校验方法你只需要在settings.py里确认INSTALLED_APPS里有django.contrib.auth和django.contrib.contenttypes即可。注册视图的写法比较简单但要特别注意表单校验from django.shortcuts import render, redirect from django.contrib.auth.forms import UserCreationForm from django.contrib.auth import login def register(request): if request.method POST: form UserCreationForm(request.POST) if form.is_valid(): user form.save() login(request, user) return redirect(index) else: form UserCreationForm() return render(request, register.html, {form: form})UserCreationForm自带密码复杂度校验和确认密码功能省去不少麻烦。需要给用户增加昵称、头像等字段时一般用OneToOneField扩展User模型from django.db import models from django.contrib.auth.models import User class Profile(models.Model): user models.OneToOneField(User, on_deletemodels.CASCADE, related_nameprofile) nickname models.CharField(昵称, max_length30, blankTrue) avatar models.ImageField(头像, upload_touser_avatar/, blankTrue)登录之后需要登录才能访问的页面直接加装饰器from django.contrib.auth.decorators import login_required login_required def create_playlist(request): # 只有登录用户才能创建歌单 pass4.2 音乐文件上传与管理音乐上传是很多同学卡住的地方。首先要在settings.py里配置好媒体文件路径MEDIA_URL /media/ MEDIA_ROOT BASE_DIR / media然后在urls.py里加一句让开发环境下能访问媒体文件from django.conf import settings from django.conf.urls.static import static urlpatterns [ # 其他路由 ] static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)上传歌曲的表单核心是一个FileFieldfrom django import forms from .models import Song class SongUploadForm(forms.ModelForm): class Meta: model Song fields [name, singer, album, audio_file, cover, lyrics]为了安全要限制上传文件的扩展名。可以在表单的clean方法里校验import os from django.core.exceptions import ValidationError def clean_audio_file(self): audio_file self.cleaned_data.get(audio_file) if audio_file: ext os.path.splitext(audio_file.name)[1].lower() if ext not in [.mp3, .wav, .ogg, .m4a]: raise ValidationError(仅支持上传 mp3/wav/ogg/m4a 格式的音频文件) return audio_file4.3 在线播放与封面图处理在线播放其实没有想象中复杂。Django的FileField上传成功后会生成一个url属性模板里直接用audio标签指向它就行audio controls preloadnone src{{ song.audio_file.url }}/audio如果你想要一个全局的播放条放在页面底部切页面不中断播放那就得用JavaScript维护一个播放器实例。最简单的方式是在base.html里放一个固定的audio标签然后用JS修改它的src并调用play()。封面图处理方面一个常见问题是设计稿里封面是正方形的但用户上传的图片是各种比例。解决方式有两种前端用CSS的object-fit: cover强制裁剪显示。后端用Pillow生成统一尺寸的缩略图。对于毕业设计我推荐第一种简单、零依赖展示效果也足够好看。如果你想展示Pillow的使用也可以在后端加一个图片处理函数from PIL import Image def compress_cover(image): img Image.open(image) img img.resize((300, 300), Image.Resampling.LANCZOS) img.save(image.path, quality85)调用时机放在cover字段的save方法里或者放在表单保存后。4.4 关键词搜索与分页搜索功能最简单的实现是用Q对象做多字段模糊查询from django.db.models import Q from django.views.generic import ListView from .models import Song class SearchView(ListView): model Song template_name search.html context_object_name songs paginate_by 10 def get_queryset(self): keyword self.request.GET.get(q, ).strip() if keyword: return Song.objects.filter( Q(name__icontainskeyword) | Q(singer__name__icontainskeyword) | Q(album__name__icontainskeyword) ) return Song.objects.none()这里有个坑如果你不加.distinct()当同一首歌关联多个匹配字段时查询结果可能出现重复。虽然上面这个例子不太容易触发但稳妥起见建议养成加.distinct()的习惯。分页直接用Django内置的Paginatorfrom django.core.paginator import Paginator def song_list(request): songs Song.objects.all() paginator Paginator(songs, 10) page_number request.GET.get(page) page_obj paginator.get_page(page_number) return render(request, song_list.html, {page_obj: page_obj})模板里用page_obj.has_previous()、page_obj.has_next()渲染上一页/下一页按钮即可。4.5 评论与收藏的功能细节评论功能的关键点在于评论要关联到具体的歌曲也要关联到具体的用户而且要判断用户是否登录。下面是一个典型的评论视图from django.shortcuts import get_object_or_404, redirect from django.contrib.auth.decorators import login_required from .models import Song, Comment login_required def add_comment(request, song_id): song get_object_or_404(Song, pksong_id) if request.method POST: content request.POST.get(content, ).strip() if content: Comment.objects.create( songsong, userrequest.user, contentcontent ) return redirect(song_detail, song_idsong.id)收藏功能最好用Ajax来实现点一下星星变亮的效果否则每次收藏都要刷新页面体验很糟糕。后面章节会专门讲Ajax联调。5. 前端页面与后端联调模板渲染、Ajax异步请求、播放器实现5.1 Django模板与静态文件的组织方式Django的模板继承功能非常实用。我建议所有页面共用base.html里面放导航栏、页脚、底部播放条以及必要的CSS和JS引入。目录结构大致如下templates/ ├── base.html ├── index.html ├── singer_list.html ├── singer_detail.html ├── song_list.html ├── song_detail.html ├── playlist_detail.html └── user/ ├── register.html ├── login.html └── profile.htmlbase.html的核心写法是!DOCTYPE html html langzh-CN head meta charsetUTF-8 title{% block title %}在线音乐{% endblock %}/title link relstylesheet href{% static css/base.css %} /head body nav...导航栏.../nav div classcontainer {% block content %}{% endblock %} /div !-- 底部播放器 -- audio idplayer controls styleposition: fixed; bottom: 0; width: 100%;/audio script src{% static js/base.js %}/script /body /html子模板里只需要{% extends base.html %} {% block title %}歌曲列表{% endblock %} {% block content %} ... {% endblock %}静态文件CSS、JS、图片统一放在根目录下的static/文件夹中并在settings.py里声明STATIC_URL /static/ STATICFILES_DIRS [BASE_DIR / static]5.2 播放器怎么实现底部固定播放条是音乐网站的灵魂。实现思路不复杂在base.html里放一个全局audio标签。用户点击任意歌曲的播放按钮时利用JavaScript设置音频源的URL并调用play()。代码如下// static/js/base.js const player document.getElementById(player); function playSong(audioUrl, songName) { player.src audioUrl; player.play(); const nowPlaying document.getElementById(now-playing); if (nowPlaying) { nowPlaying.textContent songName; } }在模板里每个歌曲条目的播放按钮这样写button onclickplaySong({{ song.audio_file.url }}, {{ song.name }})播放/button如果你想实现列表内切歌思路是一样的给所有播放按钮绑定同一个事件点击时更新player.src并播放。至于上一首/下一首可以通过把当前播放索引存在JavaScript变量里再根据索引找到对应歌曲来实现。5.3 Ajax无刷新加载评论与收藏状态Ajax是让页面动起来的关键。Django中对Ajax请求的处理方式与普通请求基本一致只是返回值改为JSON。先看后端from django.http import JsonResponse from django.shortcuts import get_object_or_404 from django.contrib.auth.decorators import login_required from .models import Song, Favorite login_required def toggle_favorite(request, song_id): song get_object_or_404(Song, pksong_id) favorite, created Favorite.objects.get_or_create(userrequest.user, songsong) if not created: favorite.delete() return JsonResponse({favorited: created})前端用fetch发送Ajax请求// 假设页面上有一个收藏按钮id 为 favorite-btn const favBtn document.getElementById(favorite-btn); if (favBtn) { favBtn.addEventListener(click, function () { fetch(/song/${songId}/favorite/, { method: POST, headers: { X-CSRFToken: getCookie(csrftoken), Content-Type: application/json } }) .then(response response.json()) .then(data { if (data.favorited) { favBtn.textContent 已收藏; } else { favBtn.textContent 收藏; } }); }); }注意Django的CSRF防护Ajax请求必须携带CSRF Token。两种做法在页面的Cookie里读取csrftoken然后在请求头加上X-CSRFToken。在模板里把{% csrf_token %}渲染的值取出来放入请求头。我推荐第一种因为Cookie方式对所有Ajax请求都通用。getCookie函数是Django官方文档里的经典片段可以直接拿过来用。6. 部署与常见坑把项目跑起来才是硬道理6.1 本地环境配置步骤这部分写给第一次碰Django的同学。完整步骤如下# 1. 创建虚拟环境 python -m venv venv # 2. 激活虚拟环境Windows venv\Scripts\activate # 或macOS/Linux source venv/bin/activate # 3. 安装依赖 pip install django pillow # 4. 创建项目 django-admin startproject music_site # 5. 进入项目目录 cd music_site # 6. 创建应用 python manage.py startapp music然后记得把music这个app加到settings.py的INSTALLED_APPS里。6.2 Django项目与App的目录规划项目结构规划得好后面写代码会省很多事。我常用的目录规划如下music_site/ ├── manage.py ├── music_site/ # 项目配置目录 │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── music/ # 主应用目录 │ ├── migrations/ │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── forms.py │ ├── models.py │ ├── urls.py │ ├── views.py │ └── tests.py ├── templates/ ├── static/ ├── media/ └── requirements.txtrequirements.txt用来记录依赖包方便别人快速复现环境pip freeze requirements.txt6.3 数据库迁移与初始数据导入模型定义好之后执行两条命令生成数据库表python manage.py makemigrations python manage.py migrate然后创建超级管理员python manage.py createsuperuser为了让歌曲、歌手、专辑数据能通过后台录入需要在admin.py里注册模型from django.contrib import admin from .models import Singer, Album, Song, Playlist, PlaylistSong, Favorite, Comment admin.site.register(Singer) admin.site.register(Album) admin.site.register(Song) admin.site.register(Playlist) admin.site.register(PlaylistSong) admin.site.register(Favorite) admin.site.register(Comment)如果需要批量导入初始数据Django的fixture机制很实用。先用dumpdata导出数据python manage.py dumpdata music --indent 2 music/fixtures/music_data.json换一台电脑或者重新建库之后导入python manage.py loaddata music_data.json6.4 部署到云服务器时的注意事项如果只是在学校演示python manage.py runserver 0.0.0.0:8000就能应付。但如果要部署到云服务器有几个坑值得提前注意runserver不适合生产环境。至少要用gunicorn启动Djangopip install gunicorn gunicorn music_site.wsgi:application --bind 0.0.0.0:8000静态文件要执行collectstatic收集到统一目录python manage.py collectstatic同时在settings.py里配置STATIC_ROOT BASE_DIR / staticfiles媒体文件用户上传的音频、头像、封面建议单独走一个路径由Nginx直接服务不要通过Django转发。数据库从SQLite切换到MySQL/PostgreSQL时要注意字段兼容性。比如ImageField在MySQL里没问题但音频文件存路径没问题别把文件本身塞进数据库。7. 答辩前准备与项目扩展方向7.1 论文里要交代清楚的技术细节毕业设计论文一般要包含系统架构图、功能模块图、数据库ER图和核心代码说明。这里不展开讲论文格式只提醒几个容易遗漏的技术细节数据库设计部分一定要画ER图并说明每张表的主外键关系。功能模块部分每个功能都要写清前端做了什么、后端做了什么、数据怎么流转。核心代码部分不要贴大段代码选两三个有代表性的片段比如登录校验、音频上传、Ajax收藏每段配3~5行文字解释。7.2 可以继续深挖的优化方向如果学有余力以下几个方向可以作为展望写进论文也能让答辩老师觉得你有思考缓存引用Redis缓存热门歌曲列表减少数据库查询压力。全文检索引入Haystack Whoosh或者直接切换PostgreSQL的全文检索让搜索更智能。推荐系统基于用户收藏记录做一个简单的协同过滤推荐推荐相似歌曲或相似歌单。前后端分离把Django改成纯API后端返回JSON数据前端用Vue或React渲染。这样技术栈更新简历上也更好看。但要注意这些是加分项不是必选项。核心功能已经完整的情况下加一两个优化点就够了千万别为了炫技把项目搞到跑不起来。7.3 关于这个项目我最想提醒你的一件事最后说点实在的。我在帮读者看毕业设计代码时见过最多的一个问题不是功能做不出来而是项目能跑但讲不清。有些同学代码能运行但问他为什么这张表要建这个字段他答不上来问他收藏功能和评论功能的实现差别在哪他也说不清楚。所以我的建议是写完代码之后给自己留出两到三天把项目的故事的逻辑理一遍——用户从注册登录开始到搜索歌曲、播放、收藏、创建歌单、发评论整个流程在代码里是怎么一步步走的。找一个朋友来打断你让他随便问你能把每一步说清楚答辩基本就稳了。这个讲得清比写得好更重要。毕竟答辩评委看到的不是你熬夜敲了多少行代码而是你对这个项目的理解有多深。本文还有配套的精品资源点击获取