This commit is contained in:
N1KO 2024-12-18 16:37:00 +08:00
parent 914313944b
commit 814b4aff36
4 changed files with 108 additions and 87 deletions

View File

@ -267,13 +267,21 @@ public class QQMusicClient implements ChannelClient<MusicSearchReq, MusicSearchR
@Override
public MusicDownloadRes download(String id) {
String downloadUrl = String.format(qqMusicProperties.getDownloadBaseUrl(), id);
String downloadUrl = String.format(qqMusicProperties.getDownloadBaseUrl1(), id);
QQMusicDownloadRes qqMusicDownloadRes = OkHttpUtil.get(downloadUrl, null, null, new TypeReference<>() {
});
if (Objects.isNull(qqMusicDownloadRes) || Objects.isNull(qqMusicDownloadRes.getMusicUrlInfo())) {
return null;
downloadUrl = String.format(qqMusicProperties.getDownloadBaseUrl2(), id);
qqMusicDownloadRes = OkHttpUtil.get(downloadUrl, null, null, new TypeReference<>() {
});
if (Objects.isNull(qqMusicDownloadRes) || Objects.isNull(qqMusicDownloadRes.getMusicUrlInfo())) {
return null;
}
}
return download(qqMusicDownloadRes);
}
private MusicDownloadRes download(QQMusicDownloadRes qqMusicDownloadRes) {
MusicDownloadRes musicDownloadRes = new MusicDownloadRes();
musicDownloadRes.setAlbumName(qqMusicDownloadRes.getSong().getAlbum());
musicDownloadRes.setArtistName(qqMusicDownloadRes.getSong().getSinger());

View File

@ -22,7 +22,9 @@ public class QQMusicProperties {
private String albumBaseUrl;
private String downloadBaseUrl;
private String downloadBaseUrl1;
private String downloadBaseUrl2;
private String downloadPath;

View File

@ -55,7 +55,8 @@ baogutang:
query-base-url: http://117.72.78.133:5175/search?key=%s&pageNo=%d&pageSize=%d&t=%d
playlist-base-url: http://117.72.78.133:5175/songlist?id=%s
album-base-url: http://117.72.78.133:5175/album/songs?albummid=%s
download-base-url: http://117.72.78.133:5176/song?url=https://y.qq.com/n/ryqq/songDetail/%s
download-base-url1: http://117.72.78.133:5176/song?url=https://y.qq.com/n/ryqq/songDetail/%s
download-base-url2: http://117.72.78.133:5176/song?url=https://u.y.qq.com/n/ryqq/songDetail/%s
download-path: /downloads/music
# download-path: /Users/nikooh/Desktop/downloads/music

View File

@ -241,8 +241,6 @@
}
}
/* 其他样式保持不变 */
.album-list {
display: flex;
flex-wrap: wrap;
@ -337,7 +335,6 @@
width: 100%;
}
/* 歌单列表样式 */
.playlist-list {
display: flex;
flex-wrap: wrap;
@ -435,13 +432,14 @@
<button class="search-btn" onclick="startSearch()">搜索</button>
<div class="search-result" id="result"></div>
<div class="loading" id="loading" style="display: none;">加载中...</div>
<div class="progress-container" id="progress-container">
<p>下载进度:<span id="progress-text">0%</span></p>
<progress id="progress-bar" value="0" max="100"></progress>
<!-- 将下载进度条移动到结果列表上方 -->
<div class="search-result" id="result">
<div class="progress-container" id="progress-container">
<p>下载进度:<span id="progress-text">0%</span></p>
<progress id="progress-bar" value="0" max="100"></progress>
</div>
</div>
<div class="loading" id="loading" style="display: none;">加载中...</div>
</div>
<script>
@ -470,7 +468,6 @@
window.addEventListener('scroll', handleScroll);
displayUsername();
// 点击用户名时,切换下拉菜单的显示状态
const userInfoDiv = document.getElementById('user-info');
const dropdownMenu = document.getElementById('dropdown-menu');
userInfoDiv.addEventListener('click', function (event) {
@ -478,7 +475,6 @@
dropdownMenu.classList.toggle('visible');
});
// 点击页面其他部分时,隐藏下拉菜单
document.addEventListener('click', function () {
dropdownMenu.classList.remove('visible');
});
@ -486,7 +482,7 @@
function fetchPlatforms() {
fetch('/api/v1/music/common?type=channel', {
credentials: 'include' // 确保请求携带cookie
credentials: 'include'
})
.then(response => response.json())
.then(data => {
@ -522,7 +518,7 @@
function fetchSearchTypes() {
fetch('/api/v1/music/common?type=searchType', {
credentials: 'include' // 确保请求携带cookie
credentials: 'include'
})
.then(response => response.json())
.then(data => {
@ -569,11 +565,17 @@
updateDownloadButton();
const resultDiv = document.getElementById('result');
resultDiv.innerHTML = '';
document.getElementById('progress-container').style.display = 'none';
resultDiv.innerHTML = `
<div class="progress-container" id="progress-container" style="display:none;">
<p>下载进度:<span id="progress-text">0%</span></p>
<progress id="progress-bar" value="0" max="100"></progress>
</div>
`; // 重置时也恢复进度条位置
document.getElementById('progress-bar').value = 0;
document.getElementById('progress-text').textContent = '0%';
document.getElementById('loading').style.display = 'none';
fetchSearchData();
}
@ -583,7 +585,7 @@
const keywords = document.getElementById('keywords').value.trim();
fetch(`/api/v1/music/search?channel=${selectedPlatformCode}&keywords=${encodeURIComponent(keywords)}&type=${selectedSearchTypeCode}&offset=${offset}&limit=${limit}`, {
credentials: 'include' // 确保请求携带cookie
credentials: 'include'
})
.then(response => response.json())
.then(data => {
@ -679,10 +681,21 @@
function displayAlbums(result, resultDiv) {
const albums = result.albums || [];
resultDiv.innerHTML = '';
const albumList = document.createElement('div');
albumList.className = 'album-list';
resultDiv.appendChild(albumList);
hasMore = result.hasMore || false;
let albumList = resultDiv.querySelector('.album-list');
if (offset === 0) {
// 保留progress-container不清空整个result而是清空其内部列表
albumList = document.createElement('div');
albumList.className = 'album-list';
resultDiv.appendChild(albumList);
} else {
if (!albumList) {
albumList = document.createElement('div');
albumList.className = 'album-list';
resultDiv.appendChild(albumList);
}
}
albums.forEach(album => {
const albumItem = document.createElement('div');
@ -706,14 +719,28 @@
albumList.appendChild(albumItem);
});
if (hasMore) {
offset += limit;
}
}
function displayArtists(result, resultDiv) {
const artists = result.artists || [];
resultDiv.innerHTML = '';
const artistList = document.createElement('div');
artistList.className = 'artist-list';
resultDiv.appendChild(artistList);
hasMore = result.hasMore || false;
let artistList = resultDiv.querySelector('.artist-list');
if (offset === 0) {
artistList = document.createElement('div');
artistList.className = 'artist-list';
resultDiv.appendChild(artistList);
} else {
if (!artistList) {
artistList = document.createElement('div');
artistList.className = 'artist-list';
resultDiv.appendChild(artistList);
}
}
artists.forEach(artist => {
const artistItem = document.createElement('div');
@ -730,22 +757,29 @@
artistList.appendChild(artistItem);
});
if (hasMore) {
offset += limit;
}
}
function displayPlaylists(result, resultDiv) {
const playlists = result.playlists || [];
hasMore = result.hasMore || false;
let playlistList = resultDiv.querySelector('.playlist-list');
if (offset === 0) {
// 第一次加载清空
resultDiv.innerHTML = '';
const playlistList = document.createElement('div');
playlistList = document.createElement('div');
playlistList.className = 'playlist-list';
resultDiv.appendChild(playlistList);
} else {
if (!playlistList) {
playlistList = document.createElement('div');
playlistList.className = 'playlist-list';
resultDiv.appendChild(playlistList);
}
}
const playlistList = resultDiv.querySelector('.playlist-list');
playlists.forEach(pl => {
const playlistItem = document.createElement('div');
playlistItem.className = 'playlist-item';
@ -768,14 +802,20 @@
function fetchAlbumSongs(albumId) {
fetch(`/api/v1/music/search/album?channel=${selectedPlatformCode}&id=${albumId}`, {
credentials: 'include' // 确保请求携带cookie
credentials: 'include'
})
.then(res => res.json())
.then(data => {
if (data.code === 200) {
const songsData = data.data?.songs || [];
const resultDiv = document.getElementById('result');
resultDiv.innerHTML = '';
// 清空除progress外的内容
resultDiv.innerHTML = `
<div class="progress-container" id="progress-container" style="display:none;">
<p>下载进度:<span id="progress-text">0%</span></p>
<progress id="progress-bar" value="0" max="100"></progress>
</div>
`;
allSongs = [];
selectedSongs = [];
const result = {
@ -794,14 +834,19 @@
function fetchArtistSongs(artistId) {
fetch(`/api/v1/music/search/artist?channel=${selectedPlatformCode}&id=${artistId}`, {
credentials: 'include' // 确保请求携带cookie
credentials: 'include'
})
.then(res => res.json())
.then(data => {
if (data.code === 200) {
const hotSongs = data.data?.hotSongs || [];
const resultDiv = document.getElementById('result');
resultDiv.innerHTML = '';
resultDiv.innerHTML = `
<div class="progress-container" id="progress-container" style="display:none;">
<p>下载进度:<span id="progress-text">0%</span></p>
<progress id="progress-bar" value="0" max="100"></progress>
</div>
`;
allSongs = [];
selectedSongs = [];
const result = {
@ -819,16 +864,20 @@
}
function fetchPlaylistSongs(playlistId) {
// 获取歌单歌曲数据,返回 data.songs
fetch(`/api/v1/music/search/playlist?channel=${selectedPlatformCode}&id=${playlistId}`, {
credentials: 'include' // 确保请求携带cookie
credentials: 'include'
})
.then(res => res.json())
.then(data => {
if (data.code === 200) {
const songsData = data.data?.songs || [];
const resultDiv = document.getElementById('result');
resultDiv.innerHTML = '';
resultDiv.innerHTML = `
<div class="progress-container" id="progress-container" style="display:none;">
<p>下载进度:<span id="progress-text">0%</span></p>
<progress id="progress-bar" value="0" max="100"></progress>
</div>
`;
allSongs = [];
selectedSongs = [];
const result = {
@ -850,13 +899,8 @@
const windowHeight = window.innerHeight;
const bodyHeight = document.body.scrollHeight;
// 当搜索类型是单曲(1)或者歌单(1000)且有更多数据时,下拉加载更多
// 这里假设歌单也可以分页获取更多数据在返回的result.hasMore中判断
// 若需要对不同类型单独判断分页逻辑,可根据实际需求调整
if (scrollTop + windowHeight >= bodyHeight - 100 && hasMore && !loading) {
if (selectedSearchTypeCode == "1" || selectedSearchTypeCode == "1000") {
fetchSearchData();
}
fetchSearchData();
}
}
@ -907,22 +951,20 @@
return;
}
const channel = selectedPlatformCode; // 渠道编号
const channel = selectedPlatformCode;
const progressContainer = document.getElementById('progress-container');
const progressBar = document.getElementById('progress-bar');
const progressText = document.getElementById('progress-text');
// 初始化进度条
progressContainer.style.display = 'block';
progressBar.value = 0;
progressText.textContent = '0%';
const fetchBatchAndDownloadFile = async () => {
try {
// 1. 调用第一个接口,获取 batchNo
const batchRequestBody = {
channel: channel,
idList: selectedSongs, // 假设selectedSongs是歌曲ID列表
idList: selectedSongs
};
const batchResponse = await fetch('/api/v1/music/download', {
method: 'POST',
@ -932,24 +974,15 @@
body: JSON.stringify(batchRequestBody),
credentials: 'include',
});
// const idListParam = selectedSongs.join(','); // 拼接ID集合
// const batchUrl = `/api/v1/music/download?channel=${channel}&idList=${encodeURIComponent(idListParam)}`;
//
// const batchResponse = await fetch(batchUrl, {
// method: 'GET',
// credentials: 'include',
// });
const batchData = await batchResponse.json();
if (batchData.code === 200) {
const batchNo = batchData.data; // 成功获取 batchNo
const batchNo = batchData.data;
// 更新进度条
progressBar.value = 50;
progressText.textContent = '50%';
// 2. 使用 batchNo 请求下载文件流
const fileUrl = `/api/v1/music/download/file?batchNo=${batchNo}&channel=${channel}`;
const fileResponse = await fetch(fileUrl, {
method: 'GET',
@ -957,16 +990,14 @@
});
if (fileResponse.ok) {
// 处理文件流并触发下载
const blob = await fileResponse.blob();
const objectUrl = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = objectUrl;
a.download = 'music_files.zip'; // 下载文件名
a.download = 'music_files.zip';
document.body.appendChild(a);
a.click();
// 清理资源
window.URL.revokeObjectURL(objectUrl);
a.remove();
@ -981,18 +1012,14 @@
console.error("下载过程中发生错误:", error);
alert("请求失败,请检查网络连接!");
} finally {
// 更新进度条为 100%
progressBar.value = 100;
progressText.textContent = '100%';
// alert("下载完成!");
}
};
// 调用主函数
fetchBatchAndDownloadFile();
}
// 显示用户名
function displayUsername() {
const username = getCookie('username');
const userInfoDiv = document.getElementById('user-info');
@ -1003,45 +1030,30 @@
}
}
// 获取Cookie的值
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return decodeURIComponent(parts.pop().split(';').shift());
}
// 设置Cookie的值可选
function setCookie(name, value, days) {
const d = new Date();
d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000));
let expires = "expires=" + d.toUTCString();
document.cookie = name + "=" + encodeURIComponent(value) + "; " + expires + "; path=/";
}
// 显示消息并处理重定向
function showMessage(msg) {
alert(msg);
setTimeout(() => {
window.location.href = '/login.html'; // 确保登录页面的路径正确
window.location.href = '/login.html';
}, 500);
}
// 退出登录函数
function logout() {
fetch('/api/v1/user/logout', { // 假设后端的退出登录接口是 /api/v1/user/logout
fetch('/api/v1/user/logout', {
method: 'POST',
credentials: 'include' // 确保请求携带cookie
credentials: 'include'
})
.then(response => response.json())
.then(data => {
if (data.code === 200) {
// 清除可访问的cookie如username
deleteCookie('username');
// 由于token是HttpOnly无法通过JavaScript清除后端已通过响应头清除
// 跳转回登录页面
window.location.href = '/login.html';
} else {
// 显示错误信息
showMessage(data.msg || '退出登录失败,请稍后重试');
}
})
@ -1051,12 +1063,10 @@
});
}
// 删除Cookie的函数
function deleteCookie(name) {
document.cookie = name + '=; Max-Age=0; path=/';
}
// 辅助函数转义HTML以防止XSS攻击
function escapeHTML(str) {
if (!str) return '';
return str.replace(/[&<>"'`=\/]/g, function (s) {