实现功能:用数据库保存本地导入和在线搜索的歌曲记录
目录
一. 保存本地添加的歌曲
1. 使用QSettings
(1)在构造函数中,创建对象。
(2)在导入音乐槽函数中,保存新添加的文件路径,保存到Settings
(3)加载保存的播放列表,loadPlaylist()
2. 使用数据库存储
(1)在构造函数中,初始化数据库。
(2)在导入音乐槽函数中,数据库存储
(3)加载播放列表时从数据库读取函数
(4)创建musicDatabase.cpp和musicDatabase.h文件
(5)创建 playlist.db 文件的方法
(6)在 .qrc 文件中声明 playlist.db
(7)将 .qrc 文件添加到 Qt 项目中
3. 可能出现的问题
二. 保存在线搜索的歌曲
(1)在在线搜索函数的处理数据信息返回函数中保存(在线)歌曲到数据库
(2)实现保存歌曲到数据库的函数
三、运行结果
一. 保存本地添加的歌曲
1. 使用QSettings
使用QSettings保存播放列表路径:记录用户添加的文件路径,每次启动时读取这些路径并重新添加到播放列表。这种方法简单,但需要注意文件路径的有效性,比如文件是否被移动或删除。
(1)在构造函数中,创建对象。
//构造函数中,创建对象m_settings = new QSettings("TT Music", "MusicPlayer"); loadPlaylist(); // 初始化加载播放列表
(2)在导入音乐槽函数中,保存新添加的文件路径,保存到Settings
// 保存新添加的文件路径,保存到Settingsm_lastPlaylist = strMp3FileList;m_settings->setValue("LastPlaylist", m_lastPlaylist);
(3)加载保存的播放列表,loadPlaylist()
//加载保存的播放列表
void OnlineMusicWidget::loadPlaylist() {// 读取保存的播放列表m_lastPlaylist = m_settings->value("LastPlaylist").toStringList();// 清空当前播放列表p_PlayerList->clear();// 添加文件并更新UIfor (const QString &filePath : m_lastPlaylist) {if (QFile::exists(filePath)) { // 验证文件有效性p_PlayerList->addMedia(QUrl::fromLocalFile(filePath));ui->plainTextEdit_SongList->appendPlainText(QFileInfo(filePath).fileName());} else {qDebug() << "警告:文件" << filePath << "不存在,已跳过";}}// 保持最后添加的文件显示在文本框底部ui->plainTextEdit_SongList->moveCursor(QTextCursor::End);
}
QSettings适合保存应用程序的配置信息,如用户偏好、最后访问的位置等,但不适合保存临时或动态生成的数据,尤其是当这些数据需要持久化存储时。
2. 使用数据库存储
(1)在构造函数中,初始化数据库。
m_musicDb = new MusicDatabase(this);if (!m_musicDb->initialize()) {qDebug() << "数据库初始化失败!";}
(2)在导入音乐槽函数中,数据库存储
for (const QString &filePath : strMp3FileList) {// 检查数据库重复(可选)if (m_musicDb->isSongExists(filePath)) {qDebug() << "[数据库] 歌曲已存在,跳过:" << filePath;continue;}elseqDebug() << "不存在:"<<filePath;// 检查播放列表重复(关键)if (isSongInPlaylist(filePath)) {qDebug() << "[播放列表] 歌曲已存在,跳过:" << filePath;continue;}elseqDebug() << "不存在:"<<filePath;// 1. 解析文件名中的歌曲名和歌手QFileInfo fileInfo(filePath);QString fileName = fileInfo.baseName(); // 示例:"富士山下 - 陈奕迅"QStringList parts = fileName.split(" - ");QStringList separators = {" - ","-", "_", "—",""}; // 定义可能的分隔符foreach (const QString &sep, separators) {if (fileName.contains(sep)) {parts = fileName.split(sep);break;}}QString songName = "未知歌曲";QString singer = "未知歌手";if (parts.size() >= 2) {songName = parts[0].trimmed();singer = parts[1].trimmed();} else {songName = fileName; // 无分隔符时直接使用文件名}// 2. 添加到播放列表p_PlayerList->addMedia(QUrl::fromLocalFile(filePath));int musicindex=p_PlayerList->currentIndex();//当前音乐的索引int currentSongid = getCurrentPlayingSongId();// QString strSongId = QString::number(currentSongid); // 直接转换qDebug() << "歌曲------------ID"<<currentSongid;// 3. 保存到数据库(使用解析出的歌曲名和歌手)// MusicDatabase::DatabaseError error = m_musicDb->addSong(filePath);MusicDatabase::DatabaseError error = m_musicDb->saveSongToDatabase(MusicDatabase::Local, musicindex, currentSongid, filePath, songName, singer,"",0);if (error != MusicDatabase::NoError) {qDebug() << "添加(本地)歌曲到数据库失败:" << error;}else{qDebug() << "添加(本地)歌曲到数据库成功";}}emit playlistUpdated(); // 添加歌曲后触发信号}
(3)加载播放列表时从数据库读取函数
void OnlineMusicWidget::loadPlaylistFromDatabase() {qDebug() << "进入 loadPlaylistFromDatabase()";// 检查数据库初始化if (!m_musicDb->initialize()) {qDebug() << "数据库初始化失败:" ;return;}// 从数据库获取播放列表路径QStringList playlist = m_musicDb->loadPlaylist();// p_PlayerList->clear();// 添加调试日志qDebug() << "查询到的歌曲数量:" << playlist.size();// 初始化计数器int newSongsCount = 0;// 遍历数据库中的每首歌曲for (const QString &filePath : playlist) {qDebug() << "加载文件:" << filePath;QString normalizedPath = normalizePath(filePath);// 检查是否已存在if (m_existingPaths.contains(normalizedPath)) {qDebug() << "跳过重复歌曲:" << filePath;continue;}// 新增歌曲:更新计数器和集合newSongsCount++;m_existingPaths.insert(normalizedPath);qDebug() << "新增路径—— :" << normalizedPath;// 添加到播放列表p_PlayerList->addMedia(QUrl::fromLocalFile(filePath));QFileInfo qFileInfo(filePath);// 添加到文本框,带有序号int index = ui->listWidget_SongList->count();ui->listWidget_SongList->addItem(QString("%1. %2").arg(index+1).arg(qFileInfo.fileName()));}// 输出新增歌曲数量qDebug() << "本次新增歌曲数量:" << newSongsCount;qDebug() << "播放列表加载完成";
}
(4)创建musicDatabase.cpp和musicDatabase.h文件
//musicDatabase.cpp
MusicDatabase::MusicDatabase(QObject *parent) : QObject(parent) {// 使用SQLite数据库m_db = QSqlDatabase::addDatabase("QSQLITE");m_db.setDatabaseName("E:/QtProjects/OnlineMusic/playlist.db"); // 使用应用程序资源文件
}//数据库初始化
bool MusicDatabase::initialize() {if (!m_db.open()) {qDebug() << "数据库初始化失败:" << m_db.lastError().text();return false;}// 加载播放列表和收藏列表loadPlaylist();// loadFavorites();// 创建歌曲表(如果不存在)QSqlQuery query;QString createTableSQL ="CREATE TABLE IF NOT EXISTS songs (""id INTEGER PRIMARY KEY AUTOINCREMENT, ""source_type TEXT NOT NULL, ""music_id INTEGER UNIQUE, ""local_filepath TEXT UNIQUE, ""name TEXT NOT NULL, ""singer TEXT NOT NULL, ""album TEXT, ""duration INTEGER, ""is_favorite BOOLEAN DEFAULT 0, ""added_time DATETIME DEFAULT CURRENT_TIMESTAMP"")";if (!query.exec(createTableSQL)) {qDebug() << "创建表失败:" << query.lastError().text();qDebug() << "执行的 SQL:" << createTableSQL; // 打印 SQL 语句return false;}qDebug() << "创建表成功!";return true;
}//将(本地)加载的歌曲保存到数据库(通过路径filePath)
MusicDatabase::DatabaseError MusicDatabase::addSong(const QString &filePath) {if (!QFile::exists(filePath)) {qDebug() << "文件不存在:" << filePath; // 添加调试输出return FileNotFoundError; // 文件不存在}QSqlDatabase db = QSqlDatabase::database();db.transaction(); // 开始事务// 先删除旧记录(如果有)QSqlQuery delQuery;delQuery.prepare("DELETE FROM songs WHERE filepath = :path");delQuery.bindValue(":path", filePath);delQuery.exec();// 插入新记录QSqlQuery insertQuery;insertQuery.prepare("INSERT INTO songs (filepath) VALUES (:path)");insertQuery.bindValue(":path", filePath);// // 检查数据库中是否已存在相同路径QSqlQuery query1;query1.prepare("SELECT COUNT(*) FROM songs WHERE filepath = :path");query1.bindValue(":path", filePath);if (!query1.exec() || !query1.next()) {qDebug() << "查询失败:" << query1.lastError().text();return QueryError;}int count = query1.value(0).toInt();if (count > 0) {db.rollback(); // 回滚事务(可选)qDebug() << "路径已存在,跳过插入";return NoError; // 或自定义重复错误码}//执行插入QSqlQuery query;query.prepare("INSERT INTO songs (filepath) VALUES (:path)");query.bindValue(":path", filePath);if (!query.exec()) {db.rollback(); // 回滚事务qDebug() << "插入失败:" << query.lastError().text();return QueryError;}db.commit(); // 提交事务return NoError;
}QStringList MusicDatabase::loadPlaylist() {QStringList paths;QSqlQuery query;query.prepare("SELECT local_filepath FROM songs WHERE source_type = 'Local'"); // 注意 source_type 值的大小写if (!query.exec()) {qDebug() << "查询失败:" << query.lastError().text();return paths;}while (query.next()) {QString path = query.value("local_filepath").toString();qDebug() << "加载路径:" << path; // 添加调试输出paths.append(path);}return paths;
}
(5)创建 playlist.db
文件的方法
playlist.db
是 SQLite 数据库文件,用于存储应用程序的本地数据(如歌曲列表、播放记录等)
方法一:手动创建
方法二:通过 Qt 代码动态创建
m_db = QSqlDatabase::addDatabase("QSQLITE");
m_db.setDatabaseName("E:/QtProjects/OnlineMusic/playlist.db");
(6)在 .qrc 文件中声明 playlist.db
- 右键点击项目 → 添加新文件 → 资源文件 →
resources.
qrc
- 打开生成的
resources.qrc
文件。 - 点击左侧的 添加现有文件。
- 选择项目目录下的
playlist.db
文件。 - 确保
prefix
设置为/
(根路径)。
(7)将 .qrc
文件添加到 Qt 项目中
在 .pro
文件中声明资源文件
RESOURCES += \images.qrc \music.qrc
3. 可能出现的问题
问题1:数据库里保存了之前导入的歌曲,但是程序启动时播放列表里并没有自动显示之前的歌曲,而是需要再次导入歌曲时才会把之前的歌曲全部显示。
解决方法:
方法一:在主窗口构造函数中添加自动加载,在窗口初始化阶段调用
loadPlaylistFromDatabase()。
方法一:通过
showEvent
触发加载。重写窗口的showEvent
方法,在窗口显示时自动加载:void OnlineMusicWidget::showEvent(QShowEvent *event) {QMainWindow::showEvent(event);loadPlaylistFromDatabase(); //窗口显示时自动加载 }
推荐 showEvent:
1. 更安全的初始化顺序
showEvent
在窗口控件完全初始化(如ui
指针绑定)后触发,避免在构造函数中因组件未就绪导致的崩溃。
2. 代码可维护性
- 将数据加载逻辑与 UI 初始化分离,符合单一职责原则。
-
showEvent
:专注于窗口显示时的数据加载和 UI 更新。
3. 动态刷新支持
- 通过
showEvent
,可以在窗口重新显示时自动刷新播放列表(例如用户关闭后重新打开窗口)。
4. 多窗口实例兼容性
- 如果程序中存在多个
OnlineMusicWidget
实例,showEvent
保证每个窗口独立加载自己的数据。
二. 保存在线搜索的歌曲
(1)在在线搜索函数的处理数据信息返回函数中保存(在线)歌曲到数据库
处理数据信息返回函数:当收到网络回复后,代码解析JSON数据,提取了歌曲的ID、名称、歌手等信息,并将这些信息显示在文本框中,同时将歌曲URL添加到播放列表
MusicDatabase::DatabaseError error = m_musicDb->saveSongToDatabase(MusicDatabase::Online,100, I_MusicID," ",StrMusicName, StrSingerName, "album", 100);if (error != MusicDatabase::NoError) {qDebug() << "添加(在线)歌曲到数据库失败:" << error;}elseqDebug() << "添加(在线)歌曲到数据库成功";
(2)实现保存歌曲到数据库的函数
MusicDatabase::DatabaseError MusicDatabase::saveSongToDatabase(SongSource source,const int id,const int music_id,const QString &filePathOrId,const QString &name,const QString &singer,const QString &album = "",int duration = 0) {QSqlQuery query;QString sql;QVariantList params;// 根据来源类型生成不同的SQL插入语句if (source == Local) {// 本地歌曲(使用文件路径)sql = "INSERT INTO songs (source_type, id, music_id, local_filepath, name, singer, album, duration) ""VALUES (:source_type, :id, :music_id, :local_filepath, :name, :singer, :album, :duration)";query.prepare(sql);query.bindValue(":source_type", "Local");query.bindValue(":local_filepath", filePathOrId); // 文件路径} else {// 在线歌曲(使用在线ID)sql = "INSERT INTO songs (source_type, music_id, name, singer, album, duration) ""VALUES (:source_type, :music_id, :name, :singer, :album, :duration)";query.prepare(sql);query.bindValue(":source_type", "online");query.bindValue(":music_id", filePathOrId.toInt()); // 转换为整数ID}// 绑定公共参数query.bindValue(":name", name);query.bindValue(":singer", singer);query.bindValue(":album", album);query.bindValue(":duration", duration);if (!query.exec()) {QSqlError sqlError = query.lastError();if (sqlError.nativeErrorCode() == "19") { // SQLite 唯一性约束错误码qDebug() << "文件路径已存在:" << filePathOrId;return FileExistsError; // 返回 FileExistsError(值 3)} else {qDebug() << "保存失败:" << sqlError.text();return QueryError; // 其他 SQL 错误}}qDebug() << "保存成功:" << name << "(" << (source == Local ? "本地" : "在线") << ")";return NoError;
}