實現功能:用數據庫保存本地導入和在線搜索的歌曲記錄
目錄
一. 保存本地添加的歌曲
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;
}