☰
Django+Flask+Vue全栈实战:从零搭建酷听音乐网站
2026/10/5 7:22:17 网站建设 项目流程

今年干的一件大事,是给自己搭了一个能打的音乐网站,名字就叫“酷听音乐”。起因很简单:各大音乐平台的版权互相不打通,我想听的老歌散落好几家,歌单没法合并,连播放器的交互体验都不一样。与其继续忍受割裂,不如自己动手写一个顺手的播放器。

技术栈我选了 Python + Vue,开发工具用 PyCharm,后端把 Django 和 Flask 都安排上了。Django 扛主站的用户、歌曲、歌单、评论这些核心业务,Flask 单独拎出来跑推荐服务。这个组合不是说某个框架不行,而是各干各最擅长的事。如果你正在学 Python Web 或者 Vue,但还没碰过完整的前后端分离项目,这篇内容应该能让你少走不少弯路,从环境搭建到部署上线,我把能踩的坑基本都踩了一遍。

1. 项目全貌:需求分析到技术选型

1.1 需求挖掘:音乐网站的标配功能是什么

动手之前我先把需求列清楚,避免写着写着变成“什么都想加”的半成品。对一个个人项目来说,第一版不需要做得多花哨,但核心闭环必须在。

我圈定的 MVP 范围是这样的:

  • 用户端:注册登录、关键词搜索、歌曲播放、收藏、创建歌单、评论。
  • 管理端:歌曲上传、歌手管理、歌单审核,用来方便我自己维护曲库。
  • 个性化:基于用户播放行为,做简单的相似歌曲推荐。

这就像一个社区的公共配套:先把“路和水电”通好,再考虑装修。如果一上来就奔着歌词滚动、桌面歌词、多端同步这种高级功能去,大概率项目会烂尾。我的经验是先做减法,把播放器“能搜到歌、能播、能存歌单”这件事跑顺,其他都是锦上添花。

1.2 为什么是 Django + Flask 双后端,而不是一个框架硬扛

很多朋友问我:一个音乐站,Django 和 Flask 二选一不就行了吗?为什么两个都用?这里我把道理掰开讲。

Django 是一个“全家桶”式的框架,自带 ORM、Admin 后台、表单校验、认证系统,生态里还有现成的 Django REST Framework(DRF)。做音乐站这种业务逻辑密集的系统,用户、歌单、评论这些模型天然适合用 Django 的 ORM 管理,数据库迁移、关联查询、后台管理都能省下大量时间。尤其是我这种一个人维护全部代码的情况,Django Admin 可以直接当管理后台用,歌单审核、歌曲上下架全都现成。

Flask 则是“轻量自由”路线的代表。它不强制你用什么组件,你想接什么库就接什么。我拿它单独跑推荐服务:一个独立的进程,输入歌曲 ID,输出相似歌曲列表,接口少、依赖干净、部署简单。这种边缘业务塞进 Django 里不是不行,但会让主站的代码越来越重,而且推荐算法经常要迭代,独立部署意味着改了可以直接重启一个服务,完全不影响主站。

两个框架之间用 HTTP 接口通信,Django 主站收到前端请求后,如果需要推荐数据,就去调 Flask 的接口。服务拆分不一定要上微服务那套重武器,像这样按业务边界拆一个小服务,个人项目完全够用了。

1.3 整体架构和数据流转

整个系统跑起来之后,数据是这样流转的:

  • 浏览器请求静态页面,由 Nginx 直接托管 Vue 构建出来的文件。
  • 前端调 API,比如 /api/songs、/api/auth/login,这些请求通过 Nginx 反向代理到 Django(默认跑在 8000 端口)。
  • Django 处理业务逻辑,读写 MySQL 数据库,Redis 用来缓存热门歌曲的播放信息和推荐结果。
  • 需要推荐时,Django 把请求转发给 Flask(跑在 5000 端口)的推荐服务。
  • 音频文件和封面图存在对象存储里,通过 CDN 分发,前端拿到的播放地址直接指向 CDN。

开发环境下更简单,前端跑在 Vite 的 5173 端口,我配置了代理,把所有 /api 开头的请求转发到 Django,/recommend 转发到 Flask,前端代码里只需要写相对路径,避免跨域问题。

2. 环境准备:PyCharm、Python 与 Vue 的初始化细节

2.1 PyCharm 里配置 Python 虚拟环境,别在全局环境里裸奔

这一步看着简单,但坑不少。我见过太多新手直接在全局 Python 环境里 pip install,装到后面依赖冲突,项目一多就乱套。

我用的方案是 PyCharm 新建项目时选择 Virtualenv 或 uv 虚拟环境。以 PyCharm 为例,新建项目后设置里找到 Python 解释器,选择“添加解释器 > 新建虚拟环境”,指定 Python 3.10 或 3.11 的路径,PyCharm 会自动创建一个 .venv 目录。后面所有依赖都装到这个虚拟环境里,跟系统 Python 互不干扰。

装依赖的时候有一个强烈建议:在国内网络环境下,先把 pip 源换成国内镜像,否则装 Django、DRF 这些包会慢到怀疑人生。在 PyCharm 终端执行:

pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

然后安装核心依赖:

pip install django djangorestframework djangorestframework-simplejwt flask flask-cors mysqlclient redis numpy gunicorn

这里简单说下每个包的用途:django 和 djangorestframework 是主站服务的基础;simplejwt 用来做接口的 Token 认证;flask 和 flask-cors 是推荐服务及其跨域配置;mysqlclient 用于连接 MySQL,如果编译报错,可以换成 pymysql,然后在 Django 配置里写上import pymysql; pymysql.install_as_MySQLdb();numpy 在推荐服务里算相似度用得上。

PyCharm 版本方面,社区版完全够用。很多人纠结要不要折腾专业版,我的体会是:做这个项目用到的功能,Python 解释器、虚拟环境、Git、终端,社区版全都支持。专业版多了数据库面板和自带 HTTP Client,但那些功能我用 DataGrip 或直接浏览器也能替代。没必要在这个环节卡住。

2.2 Django 工程与 App 划分

我用 django-admin 命令创建工程,命名为 coolmusic,然后按业务边界拆成几个 App:

django-admin startproject coolmusic cd coolmusic python manage.py startapp users python manage.py startapp songs python manage.py startapp playlists python manage.py startapp interactions

这样拆的好处是每个 App 只管一类事:

  • users:用户注册、登录、个人资料。
  • songs:歌曲、歌手、专辑的管理和查询。
  • playlists:歌单和歌单歌曲关联。
  • interactions:收藏、评论、播放记录。

然后修改 coolmusic/settings.py,把四个 App 和 DRF、simplejwt 加进 INSTALLED_APPS,配置 MySQL 连接,再设置 DRF 的默认认证方式为 JWT。最后执行:

python manage.py makemigrations python manage.py migrate

PyCharm 里可以直接右键 manage.py 运行,也可以直接在终端跑,看个人习惯。

2.3 Vue 3 工程搭建与依赖安装

前端我选的 Vue 3 + Vite,而不是 Vue 2 + Webpack。Vite 冷启动速度快,开发体验好太多。

在项目根目录下执行:

npm create vite@latest frontend -- --template vue cd frontend npm install npm install vue-router@4 pinia axios element-plus

这里必须提醒一个高频踩坑点:npm install 在国内网络环境下经常卡住,或者下载到一半报错。解决方案是给 npm 配置镜像源:

npm config set registry https://registry.npmmirror.com

装完之后,把目录结构规划一下:

  • src/router:路由配置
  • src/store:Pinia 状态管理,主要管播放状态
  • src/api:封装 axios 请求
  • src/views:页面组件
  • src/components:通用组件,比如播放条、歌曲列表
  • src/assets:样式和静态资源

再配置 vite.config.js,加一个开发代理:

export default { server: { proxy: { '/api': 'http://localhost:8000', '/recommend': 'http://localhost:5000' } } }

这样前端代码里请求 /api/songs 时,开发环境下会自动转发到 Django,不会有跨域问题。生产环境上 Nginx 也是同样思路。

3. 数据库设计与后端 API 实现

3.1 核心表结构与关键字段

数据库是整站的基石,表结构设计合理后面少改很多代码。我的模型设计是这样的:

users 用户表:id、username、password_hash、email、avatar、created_at。密码必须存哈希值,Django 默认用的 PBKDF2 算法已经够安全,千万别存明文。

artists 歌手表:id、name、avatar、description。一个歌手可以有多首歌,和歌曲表是一对多关系。

songs 歌曲表:id、title、artist(外键)、album、duration、audio_url、cover_url、lyric、play_count。播放地址我存的是对象存储的相对路径,不是完整 URL,这样切换域名或迁移存储时不用动数据库。duration 存秒数,前端拿去格式化分钟。

playlists 歌单表:id、creator(外键指向 users)、name、cover、description。歌单和歌曲是多对多关系,需要一个中间表 playlist_songs,字段包括 id、playlist、song、sort_order、created_at。

comments 评论表:id、user、song、content、created_at。favorites 收藏表:id、user、song、created_at,这个表加了联合唯一约束,防止重复收藏。

这套设计没有设计得特别复杂,但足够支撑一个真实可用的音乐站。还有一点值得注意:播放量这种频繁更新的字段,我建议单独放一张 counter 表或者用 Redis 暂时累积,再定时写回 MySQL,避免每次播放都更新一行导致数据库压力大。

3.2 Django ORM 的查询与删除实战

Django 的 ORM 写起来很爽,但有一些细节新手容易踩。举几个真实场景:

场景一:按歌手查歌曲。歌单详情页需要展示歌曲和歌手名,如果直接查 Song.objects.all(),然后在页面里对每首歌去查一次 artist.name,就是典型的 N+1 问题。一首歌查一次数据库,100 首歌就是 101 次查询,页面会明显变慢。

正确用法是 select_related:

songs = Song.objects.select_related('artist').filter(playlist_id=1)

这样一条 SQL 通过 JOIN 把歌曲和歌手一起查出来,性能差距非常明显。

场景二:删除歌手时的级联策略。Django 默认的 on_delete 参数决定了关联数据的命运。如果歌手下架了,他的歌怎么处理?我选的是on_delete=models.CASCADE,删除歌手时直接把他的歌曲一并删掉,数据不会变孤儿。但这要非常小心,因为 Cascade 是数据库层面的连带删除,误操作是找不回来的。如果你想保留歌曲只删歌手,就得用 SET_NULL 或者 PROTECT。

实际工作中我倾向于 PROTECT,即如果有歌曲关联,歌手不允许被删除。这样业务上更安全,真要删的时候,先处理歌曲再说。删除操作本身很简单:

artist = Artist.objects.get(id=3) artist.delete()

如果因为 PROTECT 抛了 ProtectedError,就先把该歌手下面的歌曲转移或下架。

场景三:播放量自增。不要先读出 play_count,再加一后写回去。多线程环境下会丢数据。正确姿势是用 F 表达式:

from django.db.models import F Song.objects.filter(id=song_id).update(play_count=F('play_count') + 1)

F 表达式把加减操作放在数据库端执行,不会出现并发覆盖。

3.3 Flask 推荐服务:从最简单的规则做起

推荐算法很容易被包装得很玄,但对我这个量级的项目来说,先用最简单有效的方法就对了。

我在 Flask 服务里做的事:读取所有歌曲的播放记录矩阵,用 numpy 计算歌曲之间的余弦相似度,然后为每首歌维护一个“相似歌曲 Top10”列表。

具体流程是:

  • 从 MySQL 查出一首歌被哪些用户播放过,构造一个 user-song 矩阵。
  • 对矩阵做归一化,用 numpy 计算余弦相似度。
  • 把相似度列表缓存在 Redis,key 类似 rec:similar:song:123,24 小时过期。
  • 提供接口 /recommend/ int:song_id ,返回相似歌曲列表。

为什么用余弦相似度而不是更复杂的协同过滤?因为我的用户量小,数据稀疏,复杂模型容易过拟合到冷启动问题。余弦相似度简单、可解释性强,而且 numpy 计算快,个人站完全跑得动。

Flask 接口长这样:

from flask import Flask, jsonify import numpy as np app = Flask(__name__) @app.route('/recommend/<int:song_id>') def recommend(song_id): # 从缓存读取相似度结果,没有则重新计算 similar_ids = get_similar_songs(song_id) return jsonify({'song_id': song_id, 'similar_songs': similar_ids})

开发时 Flask 默认跑在 5000 端口,注意 Django 里如果要用 urllib 或 requests 调它,网络地址要写成http://localhost:5000。跨域问题用 flask-cors 直接全局开掉:

from flask_cors import CORS CORS(app)

推荐服务部署就更容易了,用 gunicorn 起一个进程,配一个 systemd 服务,开机自启。因为不涉及用户上传,不需要复杂文件存储,整个服务非常轻。

4. Vue 前端页面与播放器实战

4.1 路由规划与页面骨架,动态路由别硬编码

前端路由我用 vue-router 4,整体规划如下:

  • /:首页,推荐歌单和热门歌曲
  • /search:搜索结果页
  • /playlist/:id:歌单详情页
  • /artist/:id:歌手页
  • /player:正在播放的完整页面
  • /login:登录注册
  • /admin:管理后台入口

这里用到的动态路由,其实就是带参数的路由。比如路由定义/playlist/:id,在页面里通过route.params.id拿到歌单 ID,然后调接口。这种模式对音乐站特别合适,因为歌单、歌手页都是同一套模板,只是数据不同。只要后端接口设计好,前端一行代码不用改。

导航守卫必须加:只有登录用户才能访问个人中心和创建歌单。我在 router/index.js 里写了一个简单的 beforeEach,检查 localStorage 有没有 token:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.meta.requiresAuth && !token) { next('/login') } else { next() } })

4.2 播放器核心组件,兼容 MP3 与 HLS 播放

播放器是整个网站的灵魂组件。我的方案是做一个全局的 PlayerBar,固定在页面底部,不管用户在哪个页面,播放条都在。播放状态放在 Pinia store 里,跨页面共享。

核心逻辑用原生的 audio 元素加少量封装:

const audio = new Audio() audio.src = currentSong.audio_url audio.play()

然后监听 timeupdate、ended 事件,分别用来更新进度条和自动切到下一首。播放队列我用一个数组管理,支持列表循环和单曲循环,切换逻辑几行代码就搞定。

有一个点要提一下:如果你有的音频是 m3u8 分片格式(也就是 HLS 流),浏览器是不能直接用原生 audio 播放的。这种情况下有两个方案:

  • 用 hls.js 库,在前端把 m3u8 转成普通流再喂给 audio,用户不用装任何插件。
  • 在后端用 ffmpeg 把 m3u8 转成 MP3 或 AAC,虽然中转一次,但前端零依赖。

我实测下来,hls.js 的方案更省事,播放到哪算哪,不用等全部转完。安装很简单:

npm install hls.js

然后在播放器里做一个兼容分支,src 以 .m3u8 结尾时走 hls.js,否则直接播放。

音量控制、倍速播放、进度条拖拽这些都属于锦上添花。我给进度条做了一个简单的拖动逻辑:按下时记录鼠标位置,松开时把 audio.currentTime 设成对应秒数。这个小交互看起来不起眼,但直接决定播放器好不好用。

4.3 歌单、搜索与个人中心页面

歌单详情页是整个站比较复杂的页面,包含歌单信息、歌曲列表、评论区和收藏按钮。歌曲列表我用了一个通用表格组件,这里正好用到 Vue 的插槽(slot)功能。表格负责渲染固定字段,但操作列是外部的,通过插槽传入按钮,这样组件不用写死逻辑,复用到首页、搜索页都行。

插槽是 Vue 里非常实用但不复杂的功能,只需要在子组件里写:

<slot name="actions" :song="song"></slot>

在父组件使用时:

<template #actions="{ song }"> <button @click="addToPlaylist(song)">收藏</button> </template>

这样的好处是表格组件完全通用,歌单、搜索结果、排行榜都能用,只是传进去的数据和操作按钮不同。把通用性和定制性隔离开,开发效率提升非常明显。

搜索页我做了输入防抖:用户停止输入 300 毫秒后才发请求,避免每敲一个字就请求一次后端,给数据库省点压力。个人中心展示用户收藏的歌单、收藏的歌曲和最近播放记录,都是列表页暴力堆接口就行,没有太多技术含量,主要处理好每个列表的 loading 状态和空数据展示。

4.4 后端接口对接与状态管理

前端和后端的对接,我统一封装在 src/api 目录里。每个模块一个文件,比如 songs.js、playlist.js、auth.js,里面全部用 axios 发请求,统一处理错误码和 loading 状态。

状态管理用的是 Pinia。播放器 store 里放了五个字段:currentSong、playQueue、playIndex、isPlaying、volume。这个 store 是全局的,所以切换页面不会影响播放状态。页面底部 PlayerBar 就绑定这个 store,页面里的“点击播放”按钮只是往 store 里塞数据然后调 play()。

还有一点体验细节:音频文件如果放在对象存储上并且配了 CDN,跨域访问要小心。某些 CDN 会限制 Referer,导致前端拿不到音频流,表现就是audio.play()之后立刻跳到 ended 事件。这个坑我排查了很久,最后在 CDN 控制台把跨域头 Access-Control-Allow-Origin 配成*,才彻底解决。

5. 部署上线与性能优化

5.1 Django 与 Flask 各自的生产部署,别用 runserver 裸奔

开发时 Django 自带的 runserver 很方便,但生产环境并发能力很弱,必须换 gunicorn。启动命令很简单:

gunicorn coolmusic.wsgi:application -w 4 -b 127.0.0.1:8000

-w 4 表示起 4 个 worker 进程,个人站这个配置足够。Django 的静态文件(比如 Admin 后台的 CSS)需要 whitenoise 来处理,不然 Nginx 静态文件目录还要单独映射,麻烦。安装并配置好 whitenoise 之后,静态资源访问就不依赖单独的目录了。

Flask 推荐服务同理:

gunicorn recommand_server:app -b 127.0.0.1:5000

两个服务都建议用 systemd 做成开机自启。网上有现成的 service 文件模板,把 ExecStart 改一改就行。但我要多说一句:个人项目也要把启动日志和错误日志分开存,出事能快速定位。我就在 systemd 配置里把 StandardOutput 和 StandardError 分别指向两个 log 文件,搜索 bug 时省了太多时间。

5.2 前端构建与 Nginx 配置,刷新不 404 是底线

前端构建很简单:

npm run build

产物在 dist 目录,把这个目录整个同步到服务器上,Nginx 托管静态文件就行。

但这里有一个必须处理的细节:Vue Router 默认是 history 模式,用户在 /playlist/123 按 F5 刷新,Nginx 会去查这个路径对应的文件,结果 404。解法是 Nginx 的 try_files,让所有路径都回退到 index.html。

我最终的 Nginx 配置关键块长这样:

location / { root /var/www/coolmusic; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8000; } location /recommend/ { proxy_pass http://127.0.0.1:5000; }

这样 /api 走 Django、/recommend 走 Flask、其余路径全部交给 Vue 应用处理,刷新也不会白屏。

5.3 流媒体优化与缓存策略

音乐站的流量大头是音频文件。最开始的版本我把音频存在服务器本地磁盘上,结果带宽和数据盘 IO 顶不住。后来改成对象存储加 CDN,体验提升是质变级别的。

音频文件上传到对象存储后,会得到一个类似https://cdn.example.com/music/xxx.mp3的地址。这个地址直接配在歌里,CDN 节点会自动缓存到边缘,用户播放时下载速度飞快,源站压力也很小。

缓存层用的 Redis,在几个地方派上用场:

  • 热门歌曲的播放地址,Redis 缓存 1 小时,避免每次播放都穿透到 DB。
  • 推荐结果的相似歌曲列表,24 小时过期。
  • 用户登录的 JWT Token,做基于 Redis 的黑名单,注销时把 Token 拉黑。

还有一个细节:图片和封面不要一次性加载太多。歌单页可能有几十张封面图,如果全部同步加载,首屏会卡。我给图片组件加了一个懒加载指令,滚动到可视区域才开始加载。配合 CDN,页面速度刷刷的。

6. 常见问题排查与经验总结

6.1 开发期踩坑记录,给你当避坑手册

CORS 跨域报错:开发时前端 5173 端口访问 Django 8000 端口,如果没走 Vite 代理而是直接写绝对路径,就会触发跨域。报错信息通常是 “Access to XMLHttpRequest at ... has been blocked by CORS policy”。解法有两条路:要么配置 Vite 代理(推荐),要么在 Django 里装 django-cors-headers 开白名单。生产环境走了 Nginx 同源之后,跨域问题自动消失。

刷新页面 404:Vue Router history 模式 + Nginx 未配 try_files,这个就是上面提到的坑。症状是你从首页点进歌单正常,但直接按 F5 刷新就 404。加一行try_files $uri $uri/ /index.html解决。

JWT 过期后接口报 401:用户登录后拿到的 Token 默认有效期很短,如果前端不做静默刷新,用户用着用着突然全部接口 401。我在 axios 拦截器里加了一个逻辑:收到 401 时,尝试用 refresh token 换新 token,换成功就把失败请求重新发一遍,换失败才跳登录页。这个逻辑可以让用户长时间停在页面上也不掉线。

音频播放失败:CDN 没有配置正确的跨域头和 Referer 白名单,播放器一打开就异常。先看 DevTools 的网络面板,确认音频请求是 200 还是 403,403 基本都是 CDN 防盗链问题。

PyCharm 无法识别虚拟环境里的依赖:换一台电脑拉代码后,PyCharm 的 Python 解释器忘了选虚拟环境,导致 import 全飘红。很简单,在解释器设置里重新指向 .venv 里的 python 可执行文件就行。

MySQL 驱动安装失败:mysqlclient 在 Windows 上编译特别容易报错。直接换成 pymysql,然后改 Django 配置,一分钟解决,没必要死磕。

npm install 卡住或报错:除了换镜像源,还可以试试先删掉 node_modules 目录再重新安装,经常能解决依赖树不一致的问题。

搜索接口慢:如果你在歌曲表上用了 LIKE 前缀匹配,比如WHERE title LIKE '%关键词%',数据量上来之后会越来越慢。基础做法是给 title 字段加索引,但更推荐用全文索引或者单独引入搜索引擎,个人项目可以先用索引过渡。

6.2 我的个人经验:做这类全栈项目的节奏比技术更重要

这个项目踩过这么多坑之后,我最大的体会是:节奏比技术重要。一开始急着把功能堆全,结果前端页面还没写几个,后端接口已经因为设计不合理改了三轮。后来我换成先定义接口文档,再并行走前后端,项目的完成速度反而快了很多。

具体做法是:先用 Markdown 把核心接口列出来,比如歌曲详情接口返回什么字段、歌单接口返回什么字段,前端和后端都按这个文档干。这样两个人协作不扯皮,一个人做也没问题。我写接口文档时会明确标注每个字段的类型、是否可空、关联关系,前端照着类型定义 TypeScript 接口,后端照着写 serializer,两边天然对齐。

另外,音乐网站这种项目特别适合拿来练手,因为它的功能足够丰富,但又不像电商那样对并发、支付有变态要求。你可以完全掌控技术栈,从数据库表设计到前端组件封装,每个环节都能学到东西。做完这个站,我对 Django ORM、Vue 状态管理、Nginx 部署的掌握程度,比看十遍教程都深。

6.3 后续还可以这样扩展

项目基础跑通之后,我下一步想加的是歌词滚动同步。原理是前端监听 audio 的 timeupdate 事件,根据当前播放时间在 LRC 歌词数组里二分查找当前行,然后让歌词块滚动到中间位置并高亮。逻辑不复杂,但体验提升很明显。

还有一块是现在挺火的 AI 歌单生成:把你收藏的歌和听歌记录发给大模型,让它根据情绪或场景生成歌单。这个扩展我计划做成一个独立的服务,接入大模型 API,然后把生成的歌单写入数据库关联到用户账号下。Django 主站这边只需要加一个“AI 生成歌单”的按钮和对应接口,前端弹窗展示生成结果,用户确认后保存。技术上没有难点,关键是 API 成本和响应时间的控制,建议放在 Flask 那边做异步任务,避免阻塞主站。

最后再提醒一句:这种个人项目别追求一步到位。先把基础版的酷听音乐跑起来,让朋友用上,再根据真实反馈迭代。有人用你才会知道哪里卡、哪里难用,那时候你的优化才有方向。我自己就是在朋友说“怎么搜索慢得像拨号上网”之后,才认真优化了搜索接口的索引和缓存配置。好产品不是设计出来的,是改出来的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询