作为一个音乐爱好者兼全栈开发者,我一直想打造一个属于自己的在线音乐平台。经过三个月的开发迭代,终于完成了这个基于Flask+Vue.js的在线音乐播放系统。这个项目不仅实现了基本的音乐播放功能,还包含了歌单管理、用户推荐等高级特性,日均能稳定承载5000+的并发请求。
整个系统采用前后端分离架构,后端使用Python的Flask框架提供RESTful API,前端用Vue.js构建响应式单页应用。数据库选用MySQL存储结构化数据,Redis作为缓存层提升性能。特别在音频处理方面,我们创新性地采用了librosa进行音乐特征提取,实现了基于内容的推荐算法。
提示:在开始开发类似项目前,建议先准备好至少20GB的存储空间用于存放音频文件,并确保服务器具备足够的CPU处理能力进行音频转码。
为什么选择Flask而不是Django?这是我在项目初期最纠结的问题。经过详细对比,最终选择Flask主要基于以下几点考虑:
python复制# 典型Flask应用初始化代码
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://user:pass@localhost/music'
db = SQLAlchemy(app)
Vue.js被选为前端框架主要因为:
javascript复制// Vue组件示例
export default {
data() {
return {
currentSong: null,
isPlaying: false
}
},
methods: {
playSong(song) {
this.currentSong = song
this.isPlaying = true
}
}
}
MySQL作为主数据库存储结构化数据,主要表包括:
Redis用于缓存:
code复制客户端 (Vue.js) ←HTTP→ Nginx ←WSGI→ Flask API
↑
↓
Redis缓存
↑
↓
MySQL数据库
↑
↓
Celery异步队列
采用JWT(JSON Web Token)实现无状态认证,相比传统session有以下优势:
python复制# JWT认证示例
from flask_jwt_extended import create_access_token
@app.route('/login', methods=['POST'])
def login():
username = request.json.get('username')
user = User.query.filter_by(username=username).first()
access_token = create_access_token(identity=user.id)
return jsonify(access_token=access_token)
实现功能:
注意:音频上传需要配置单独的文件存储服务,建议使用七牛云或阿里云OSS,不要直接存储在服务器本地
记录关键指标:
数据流向:
客户端 → Flask API → Celery异步任务 → MySQL + Redis
python复制class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password_hash = db.Column(db.String(120), nullable=False)
avatar = db.Column(db.String(256)) # 头像URL
last_login = db.Column(db.DateTime)
class Song(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(120), nullable=False)
duration = db.Column(db.Integer) # 秒数
file_path = db.Column(db.String(256), nullable=False)
artist_id = db.Column(db.Integer, db.ForeignKey('artist.id'))
album_id = db.Column(db.Integer, db.ForeignKey('album.id'))
class Playlist(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(120))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
created_at = db.Column(db.DateTime, default=datetime.utcnow)
python复制# 多对多关联表示例
playlist_songs = db.Table('playlist_songs',
db.Column('playlist_id', db.Integer, db.ForeignKey('playlist.id')),
db.Column('song_id', db.Integer, db.ForeignKey('song.id'))
)
python复制@app.route('/api/songs', methods=['GET'])
def get_songs():
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 20, type=int)
query = Song.query
if 'artist_id' in request.args:
query = query.filter_by(artist_id=request.args['artist_id'])
pagination = query.paginate(page, per_page)
return jsonify({
'items': [song.to_dict() for song in pagination.items],
'total': pagination.total,
'pages': pagination.pages
})
python复制@app.route('/api/play/<int:song_id>', methods=['POST'])
@jwt_required()
def record_play(song_id):
current_user = get_jwt_identity()
# 异步记录播放
record_play.delay(current_user, song_id)
# 实时更新Redis计数器
redis.zincrby('hot_songs', 1, song_id)
return jsonify({'status': 'success'})
核心功能:
vue复制<template>
<div class="player">
<audio ref="audio" @timeupdate="updateProgress"></audio>
<button @click="togglePlay">{{ isPlaying ? '暂停' : '播放' }}</button>
<input type="range" v-model="progress" @change="seek">
</div>
</template>
<script>
export default {
data() {
return {
isPlaying: false,
progress: 0
}
},
methods: {
togglePlay() {
if (this.isPlaying) {
this.$refs.audio.pause()
} else {
this.$refs.audio.play()
}
this.isPlaying = !this.isPlaying
},
seek(e) {
this.$refs.audio.currentTime = e.target.value
}
}
}
</script>
实现功能:
code复制客户端 → CDN(静态资源) → Nginx(负载均衡) → Gunicorn(Flask应用)
↓
Redis集群
↓
MySQL主从
dockerfile复制# backend/Dockerfile
FROM python:3.8-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["gunicorn", "--workers=4", "--bind=0.0.0.0:5000", "app:app"]
nginx复制server {
listen 80;
server_name music.example.com;
location / {
proxy_pass http://flask:5000;
proxy_set_header Host $host;
}
location /static/ {
alias /var/www/static/;
expires 30d;
}
}
python复制class Song(db.Model):
__table_args__ = (
db.Index('idx_title_artist', 'title', 'artist_id'),
)
多级缓存:
缓存失效:
使用Celery处理:
python复制@app.route('/upload', methods=['POST'])
def upload_song():
file = request.files['file']
temp_path = save_temp_file(file)
transcode.delay(temp_path) # 异步转码
return jsonify({'status': 'processing'})
问题:部分移动端浏览器无法自动播放
解决方案:
问题:前端开发时访问API出现CORS错误
解决方案:
python复制from flask_cors import CORS
CORS(app, resources={r"/api/*": {"origins": "*"}})
问题:大文件上传失败
解决方案:
nginx复制http {
client_max_body_size 100M;
}
社交功能:
智能推荐增强:
多端支持:
这个音乐平台项目从零开始构建,让我深刻体会到全栈开发的挑战与乐趣。最大的收获是学会了如何在性能与开发效率之间寻找平衡点。比如在音频处理环节,最初使用纯Python实现转码,后来发现性能瓶颈后,转而使用FFmpeg命令行工具,处理速度提升了8倍。