download
This commit is contained in:
parent
f4b5c4dc06
commit
b6f3ff6dce
@ -1,5 +1,6 @@
|
|||||||
package top.baogutang.music.client;
|
package top.baogutang.music.client;
|
||||||
|
|
||||||
|
import top.baogutang.music.dao.entity.MusicDownloadRecordEntity;
|
||||||
import top.baogutang.music.dao.entity.MusicRecordEntity;
|
import top.baogutang.music.dao.entity.MusicRecordEntity;
|
||||||
import top.baogutang.music.domain.req.AbstractMusicReq;
|
import top.baogutang.music.domain.req.AbstractMusicReq;
|
||||||
import top.baogutang.music.domain.req.search.MusicSearchReq;
|
import top.baogutang.music.domain.req.search.MusicSearchReq;
|
||||||
@ -8,7 +9,7 @@ import top.baogutang.music.domain.res.download.MusicDownloadRes;
|
|||||||
import top.baogutang.music.domain.res.search.*;
|
import top.baogutang.music.domain.res.search.*;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -39,4 +40,9 @@ public interface ChannelClient<Q extends AbstractMusicReq, S extends AbstractMus
|
|||||||
|
|
||||||
String getDownloadBasePath();
|
String getDownloadBasePath();
|
||||||
|
|
||||||
|
void saveDownloadRecord(Long userId, String batchNo, String musicId);
|
||||||
|
|
||||||
|
List<MusicDownloadRecordEntity> queryDownloadRecord(String batchNo);
|
||||||
|
|
||||||
|
List<MusicRecordEntity> queryMusicByPlatformIdList(List<String> platformIdList);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import top.baogutang.music.annos.ChannelInfo;
|
import top.baogutang.music.annos.ChannelInfo;
|
||||||
|
import top.baogutang.music.dao.entity.MusicDownloadRecordEntity;
|
||||||
import top.baogutang.music.dao.entity.MusicRecordEntity;
|
import top.baogutang.music.dao.entity.MusicRecordEntity;
|
||||||
import top.baogutang.music.domain.req.search.MusicSearchReq;
|
import top.baogutang.music.domain.req.search.MusicSearchReq;
|
||||||
import top.baogutang.music.domain.res.download.MusicDownloadRes;
|
import top.baogutang.music.domain.res.download.MusicDownloadRes;
|
||||||
@ -12,6 +13,7 @@ import top.baogutang.music.enums.AudioFileTypeEnum;
|
|||||||
import top.baogutang.music.enums.ChannelEnum;
|
import top.baogutang.music.enums.ChannelEnum;
|
||||||
import top.baogutang.music.processor.AbstractAudioProcessor;
|
import top.baogutang.music.processor.AbstractAudioProcessor;
|
||||||
import top.baogutang.music.properties.NetEaseMusicProperties;
|
import top.baogutang.music.properties.NetEaseMusicProperties;
|
||||||
|
import top.baogutang.music.service.IMusicDownloadRecordService;
|
||||||
import top.baogutang.music.service.IMusicRecordService;
|
import top.baogutang.music.service.IMusicRecordService;
|
||||||
import top.baogutang.music.utils.OkHttpUtil;
|
import top.baogutang.music.utils.OkHttpUtil;
|
||||||
|
|
||||||
@ -23,6 +25,7 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,6 +47,9 @@ public class NetEaseMusicClient implements ChannelClient<MusicSearchReq, MusicSe
|
|||||||
@Resource
|
@Resource
|
||||||
private IMusicRecordService musicRecordService;
|
private IMusicRecordService musicRecordService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IMusicDownloadRecordService musicDownloadRecordService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MusicSearchRes search(MusicSearchReq req) {
|
public MusicSearchRes search(MusicSearchReq req) {
|
||||||
String searchUrl = String.format(netEaseMusicProperties.getQueryBaseUrl(), req.getKeywords(), req.getLimit(), req.getOffset(), req.getType());
|
String searchUrl = String.format(netEaseMusicProperties.getQueryBaseUrl(), req.getKeywords(), req.getLimit(), req.getOffset(), req.getType());
|
||||||
@ -126,4 +132,20 @@ public class NetEaseMusicClient implements ChannelClient<MusicSearchReq, MusicSe
|
|||||||
return netEaseMusicProperties.getDownloadPath();
|
return netEaseMusicProperties.getDownloadPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveDownloadRecord(Long userId, String batchNo, String musicId) {
|
||||||
|
musicDownloadRecordService.save(userId, batchNo, ChannelEnum.NET_EASE_MUSIC, musicId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<MusicDownloadRecordEntity> queryDownloadRecord(String batchNo) {
|
||||||
|
return musicDownloadRecordService.queryByBatchNo(batchNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<MusicRecordEntity> queryMusicByPlatformIdList(List<String> platformIdList) {
|
||||||
|
return musicRecordService.queryByPlatformIdList(platformIdList);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import top.baogutang.music.annos.ChannelInfo;
|
import top.baogutang.music.annos.ChannelInfo;
|
||||||
|
import top.baogutang.music.dao.entity.MusicDownloadRecordEntity;
|
||||||
import top.baogutang.music.dao.entity.MusicRecordEntity;
|
import top.baogutang.music.dao.entity.MusicRecordEntity;
|
||||||
import top.baogutang.music.domain.req.search.MusicSearchReq;
|
import top.baogutang.music.domain.req.search.MusicSearchReq;
|
||||||
import top.baogutang.music.domain.res.download.MusicDownloadRes;
|
import top.baogutang.music.domain.res.download.MusicDownloadRes;
|
||||||
@ -16,6 +17,7 @@ import top.baogutang.music.enums.SearchTypeEnum;
|
|||||||
import top.baogutang.music.exceptions.BusinessException;
|
import top.baogutang.music.exceptions.BusinessException;
|
||||||
import top.baogutang.music.processor.AbstractAudioProcessor;
|
import top.baogutang.music.processor.AbstractAudioProcessor;
|
||||||
import top.baogutang.music.properties.QQMusicProperties;
|
import top.baogutang.music.properties.QQMusicProperties;
|
||||||
|
import top.baogutang.music.service.IMusicDownloadRecordService;
|
||||||
import top.baogutang.music.service.IMusicRecordService;
|
import top.baogutang.music.service.IMusicRecordService;
|
||||||
import top.baogutang.music.utils.OkHttpUtil;
|
import top.baogutang.music.utils.OkHttpUtil;
|
||||||
|
|
||||||
@ -49,6 +51,9 @@ public class QQMusicClient implements ChannelClient<MusicSearchReq, MusicSearchR
|
|||||||
@Resource
|
@Resource
|
||||||
private IMusicRecordService musicRecordService;
|
private IMusicRecordService musicRecordService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IMusicDownloadRecordService musicDownloadRecordService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MusicSearchRes search(MusicSearchReq req) {
|
public MusicSearchRes search(MusicSearchReq req) {
|
||||||
Integer pageNo = (req.getOffset() / req.getLimit()) + 1;
|
Integer pageNo = (req.getOffset() / req.getLimit()) + 1;
|
||||||
@ -347,4 +352,19 @@ public class QQMusicClient implements ChannelClient<MusicSearchReq, MusicSearchR
|
|||||||
return qqMusicProperties.getDownloadPath();
|
return qqMusicProperties.getDownloadPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveDownloadRecord(Long userId, String batchNo, String musicId) {
|
||||||
|
musicDownloadRecordService.save(userId, batchNo, ChannelEnum.QQ_MUSIC, musicId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<MusicDownloadRecordEntity> queryDownloadRecord(String batchNo) {
|
||||||
|
return musicDownloadRecordService.queryByBatchNo(batchNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<MusicRecordEntity> queryMusicByPlatformIdList(List<String> platformIdList) {
|
||||||
|
return musicRecordService.queryByPlatformIdList(platformIdList);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,15 @@
|
|||||||
package top.baogutang.music.controller;
|
package top.baogutang.music.controller;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import top.baogutang.music.annos.Vip;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import top.baogutang.music.annos.Login;
|
|
||||||
import top.baogutang.music.domain.Results;
|
import top.baogutang.music.domain.Results;
|
||||||
import top.baogutang.music.domain.res.download.MusicDownloadRes;
|
import top.baogutang.music.domain.req.download.MusicDownloadReq;
|
||||||
import top.baogutang.music.domain.res.search.MusicPlaylistRes;
|
|
||||||
import top.baogutang.music.service.IMusicService;
|
import top.baogutang.music.service.IMusicService;
|
||||||
|
import top.baogutang.music.utils.UserThreadLocal;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -29,11 +27,16 @@ public class MusicDownloadController {
|
|||||||
private IMusicService musicService;
|
private IMusicService musicService;
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@Login
|
@Vip
|
||||||
public Results<MusicDownloadRes> download(@RequestParam(name = "channel") Integer channel,
|
public Results<String> download(@RequestBody MusicDownloadReq req) {
|
||||||
@RequestParam(name = "id") String id) {
|
String downloadUrl = musicService.getMusicService(req.getChannel()).downloads(req.getIdList(), UserThreadLocal.get());
|
||||||
MusicDownloadRes res = musicService.getMusicService(channel).download(id);
|
return Results.ok(downloadUrl);
|
||||||
return Results.ok(res);
|
}
|
||||||
|
|
||||||
|
@GetMapping("/file")
|
||||||
|
@Vip
|
||||||
|
public void downloadFile(@RequestParam(name = "batchNo") String batchNo, @RequestParam(name = "channel") Integer channel, HttpServletResponse response) {
|
||||||
|
musicService.getMusicService(channel).downloadFile(batchNo, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,31 @@
|
|||||||
|
package top.baogutang.music.dao.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import top.baogutang.music.enums.ChannelEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description:
|
||||||
|
*
|
||||||
|
* @author: N1KO
|
||||||
|
* @date: 2024/12/16 : 10:01
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@TableName("t_music_download_record")
|
||||||
|
public class MusicDownloadRecordEntity extends BaseEntity {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -1789966661010962297L;
|
||||||
|
|
||||||
|
private String platformId;
|
||||||
|
|
||||||
|
private ChannelEnum channel;
|
||||||
|
|
||||||
|
private String batchNo;
|
||||||
|
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -5,7 +5,6 @@ import lombok.Getter;
|
|||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import top.baogutang.music.enums.AudioFileTypeEnum;
|
import top.baogutang.music.enums.AudioFileTypeEnum;
|
||||||
import top.baogutang.music.enums.ChannelEnum;
|
import top.baogutang.music.enums.ChannelEnum;
|
||||||
import top.baogutang.music.enums.MusicQualityEnum;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
@ -0,0 +1,16 @@
|
|||||||
|
package top.baogutang.music.dao.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import top.baogutang.music.dao.entity.MusicDownloadRecordEntity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description:
|
||||||
|
*
|
||||||
|
* @author: N1KO
|
||||||
|
* @date: 2024/12/16 : 10:08
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface MusicDownloadRecordMapper extends BaseMapper<MusicDownloadRecordEntity> {
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package top.baogutang.music.domain.req.download;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description:
|
||||||
|
*
|
||||||
|
* @author: N1KO
|
||||||
|
* @date: 2024/12/16 : 09:48
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class MusicDownloadReq implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -1159631672581622400L;
|
||||||
|
|
||||||
|
private Integer channel;
|
||||||
|
|
||||||
|
private List<String> idList;
|
||||||
|
}
|
||||||
@ -2,7 +2,11 @@ package top.baogutang.music.service;
|
|||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.tomcat.util.http.fileupload.IOUtils;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
import top.baogutang.music.client.ChannelClient;
|
import top.baogutang.music.client.ChannelClient;
|
||||||
|
import top.baogutang.music.dao.entity.MusicDownloadRecordEntity;
|
||||||
|
import top.baogutang.music.dao.entity.MusicRecordEntity;
|
||||||
import top.baogutang.music.domain.req.AbstractMusicReq;
|
import top.baogutang.music.domain.req.AbstractMusicReq;
|
||||||
import top.baogutang.music.domain.req.search.MusicSearchReq;
|
import top.baogutang.music.domain.req.search.MusicSearchReq;
|
||||||
import top.baogutang.music.domain.res.AbstractMusicRes;
|
import top.baogutang.music.domain.res.AbstractMusicRes;
|
||||||
@ -13,10 +17,19 @@ import top.baogutang.music.exceptions.BusinessException;
|
|||||||
import top.baogutang.music.factory.ChannelClientFactory;
|
import top.baogutang.music.factory.ChannelClientFactory;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
import javax.servlet.ServletOutputStream;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -98,4 +111,97 @@ public abstract class AbstractMusicService<Q extends AbstractMusicReq, S extends
|
|||||||
// }
|
// }
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String downloads(List<String> idList, Long userId) {
|
||||||
|
if (CollectionUtils.isEmpty(idList)) {
|
||||||
|
throw new BusinessException("请选择下载内容");
|
||||||
|
}
|
||||||
|
if (idList.size() > 100) {
|
||||||
|
throw new BusinessException("单次下载限制最多100首");
|
||||||
|
}
|
||||||
|
ChannelEnum channelEnum = getChannelEnum();
|
||||||
|
ChannelClient<Q, S> channelClient = channelClientFactory.getClient(channelEnum);
|
||||||
|
String batchNo = UUID.randomUUID().toString();
|
||||||
|
idList.stream()
|
||||||
|
.map(id ->
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
MusicDownloadRes res = channelClient.download(id);
|
||||||
|
if (Objects.isNull(res)) {
|
||||||
|
log.error(">>>>>>>>>>query detail error! channel:{},song id:{}<<<<<<<<<<", channelEnum.getDesc(), id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank(res.getArtistName())) {
|
||||||
|
String[] split = res.getArtistName().split("/");
|
||||||
|
String artistName = String.join(",", split);
|
||||||
|
res.setArtistName(artistName);
|
||||||
|
}
|
||||||
|
channelClient.saveMusic(res);
|
||||||
|
channelClient.saveDownloadRecord(userId, batchNo, id);
|
||||||
|
try {
|
||||||
|
channelClient.processFile(res);
|
||||||
|
log.info(">>>>>>>>>>download file:{} success<<<<<<<<<<", res.getName());
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(">>>>>>>>>>download error:{}<<<<<<<<<<", e.getMessage(), e);
|
||||||
|
throw new BusinessException("下载异常");
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.forEach(CompletableFuture::join);
|
||||||
|
return batchNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void downloadFile(String batchNo, HttpServletResponse response) {
|
||||||
|
ChannelEnum channelEnum = getChannelEnum();
|
||||||
|
ChannelClient<Q, S> channelClient = channelClientFactory.getClient(channelEnum);
|
||||||
|
List<MusicDownloadRecordEntity> musicDownloadRecordList = channelClient.queryDownloadRecord(batchNo);
|
||||||
|
if (CollectionUtils.isEmpty(musicDownloadRecordList)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<String> platformIdList = musicDownloadRecordList.stream()
|
||||||
|
.map(MusicDownloadRecordEntity::getPlatformId)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
List<MusicRecordEntity> musicRecordList = channelClient.queryMusicByPlatformIdList(platformIdList);
|
||||||
|
if (CollectionUtils.isEmpty(musicRecordList)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String downloadBasePath = channelClient.getDownloadBasePath();
|
||||||
|
|
||||||
|
// 设置响应头
|
||||||
|
response.setContentType("application/zip");
|
||||||
|
response.setHeader("Content-Disposition", "attachment; filename=\"music_files" + batchNo + ".zip\"");
|
||||||
|
|
||||||
|
try (ServletOutputStream out = response.getOutputStream();
|
||||||
|
ZipOutputStream zipOut = new ZipOutputStream(out)) {
|
||||||
|
for (MusicRecordEntity musicRecord : musicRecordList) {
|
||||||
|
// 构建文件路径
|
||||||
|
String filePath = String.format("%s/%s/%s/%s.%s",
|
||||||
|
downloadBasePath,
|
||||||
|
musicRecord.getArtistName(),
|
||||||
|
musicRecord.getAlbumName(),
|
||||||
|
musicRecord.getName(),
|
||||||
|
musicRecord.getFileType().name().toLowerCase());
|
||||||
|
|
||||||
|
File file = new File(filePath);
|
||||||
|
if (!file.exists() || !file.isFile()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将文件写入 ZIP
|
||||||
|
try (FileInputStream fis = new FileInputStream(file)) {
|
||||||
|
String zipEntryName = String.format("%s/%s/%s.%s",
|
||||||
|
musicRecord.getArtistName(),
|
||||||
|
musicRecord.getAlbumName(),
|
||||||
|
musicRecord.getName(),
|
||||||
|
musicRecord.getFileType().name().toLowerCase());
|
||||||
|
zipOut.putNextEntry(new ZipEntry(zipEntryName));
|
||||||
|
IOUtils.copy(fis, zipOut);
|
||||||
|
zipOut.closeEntry();
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(">>>>>>>>>>write file error:{}<<<<<<<<<<", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zipOut.finish();
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(">>>>>>>>>>Error while creating zip responser:{}<<<<<<<<<<", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,21 @@
|
|||||||
|
package top.baogutang.music.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import top.baogutang.music.dao.entity.MusicDownloadRecordEntity;
|
||||||
|
import top.baogutang.music.enums.ChannelEnum;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description:
|
||||||
|
*
|
||||||
|
* @author: N1KO
|
||||||
|
* @date: 2024/12/16 : 10:08
|
||||||
|
*/
|
||||||
|
public interface IMusicDownloadRecordService extends IService<MusicDownloadRecordEntity> {
|
||||||
|
|
||||||
|
void save(Long userId, String batchNo, ChannelEnum channelEnum, String musicId);
|
||||||
|
|
||||||
|
List<MusicDownloadRecordEntity> queryByBatchNo(String batchNo);
|
||||||
|
}
|
||||||
@ -5,6 +5,8 @@ import top.baogutang.music.dao.entity.MusicRecordEntity;
|
|||||||
import top.baogutang.music.domain.res.download.MusicDownloadRes;
|
import top.baogutang.music.domain.res.download.MusicDownloadRes;
|
||||||
import top.baogutang.music.enums.ChannelEnum;
|
import top.baogutang.music.enums.ChannelEnum;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @description:
|
* @description:
|
||||||
@ -17,4 +19,6 @@ public interface IMusicRecordService extends IService<MusicRecordEntity> {
|
|||||||
void save(MusicDownloadRes res, ChannelEnum channel);
|
void save(MusicDownloadRes res, ChannelEnum channel);
|
||||||
|
|
||||||
MusicRecordEntity queryByChannelAndPlatform(ChannelEnum channel, String platformId);
|
MusicRecordEntity queryByChannelAndPlatform(ChannelEnum channel, String platformId);
|
||||||
|
|
||||||
|
List<MusicRecordEntity> queryByPlatformIdList(List<String> platformIdList);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,44 @@
|
|||||||
|
package top.baogutang.music.service.impl;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import top.baogutang.music.dao.entity.MusicDownloadRecordEntity;
|
||||||
|
import top.baogutang.music.dao.mapper.MusicDownloadRecordMapper;
|
||||||
|
import top.baogutang.music.enums.ChannelEnum;
|
||||||
|
import top.baogutang.music.service.IMusicDownloadRecordService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description:
|
||||||
|
*
|
||||||
|
* @author: N1KO
|
||||||
|
* @date: 2024/12/16 : 10:09
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class MusicDownloadRecordServiceImpl extends ServiceImpl<MusicDownloadRecordMapper, MusicDownloadRecordEntity> implements IMusicDownloadRecordService {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(Long userId, String batchNo, ChannelEnum channelEnum, String musicId) {
|
||||||
|
MusicDownloadRecordEntity musicDownloadRecordEntity = new MusicDownloadRecordEntity();
|
||||||
|
musicDownloadRecordEntity.setPlatformId(musicId);
|
||||||
|
musicDownloadRecordEntity.setChannel(channelEnum);
|
||||||
|
musicDownloadRecordEntity.setBatchNo(batchNo);
|
||||||
|
musicDownloadRecordEntity.setUserId(userId);
|
||||||
|
musicDownloadRecordEntity.setCreateTime(LocalDateTime.now());
|
||||||
|
baseMapper.insert(musicDownloadRecordEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<MusicDownloadRecordEntity> queryByBatchNo(String batchNo) {
|
||||||
|
return new LambdaQueryChainWrapper<>(baseMapper)
|
||||||
|
.eq(MusicDownloadRecordEntity::getBatchNo, batchNo)
|
||||||
|
.eq(MusicDownloadRecordEntity::getDeleted, Boolean.FALSE)
|
||||||
|
.list();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,6 +12,7 @@ import top.baogutang.music.enums.MusicQualityEnum;
|
|||||||
import top.baogutang.music.service.IMusicRecordService;
|
import top.baogutang.music.service.IMusicRecordService;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -66,4 +67,13 @@ public class MusicRecordServiceImpl extends ServiceImpl<MusicRecordMapper, Music
|
|||||||
.last(" limit 1 ")
|
.last(" limit 1 ")
|
||||||
.one();
|
.one();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<MusicRecordEntity> queryByPlatformIdList(List<String> platformIdList) {
|
||||||
|
return new LambdaQueryChainWrapper<>(baseMapper)
|
||||||
|
.in(MusicRecordEntity::getPlatformId, platformIdList)
|
||||||
|
.eq(MusicRecordEntity::getDeleted, Boolean.FALSE)
|
||||||
|
.list();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<link rel="icon" type="image/png" href="NIKO.png">
|
<link rel="icon" type="image/png" href="NIKO.png">
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
<title>BAOGUTANG-MUSIC</title>
|
<title>BAOGUTANG-MUSIC</title>
|
||||||
<style>
|
<style>
|
||||||
@ -12,21 +12,24 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
width: 95%;
|
width: 95%;
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
color: #62D2A1;
|
color: #62D2A1;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
font-size: 1.8rem;
|
font-size: 1.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
@ -34,6 +37,7 @@
|
|||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-info {
|
.user-info {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: #333;
|
color: #333;
|
||||||
@ -42,6 +46,7 @@
|
|||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dropdown Menu Styles */
|
/* Dropdown Menu Styles */
|
||||||
.dropdown {
|
.dropdown {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -50,15 +55,17 @@
|
|||||||
background-color: #f9f9f9; /* 修改为浅灰色 */
|
background-color: #f9f9f9; /* 修改为浅灰色 */
|
||||||
border: 1px solid #ccc; /* 较深的边框 */
|
border: 1px solid #ccc; /* 较深的边框 */
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
display: none;
|
display: none;
|
||||||
min-width: 150px;
|
min-width: 150px;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
transition: opacity 0.3s ease; /* 可选:添加过渡效果 */
|
transition: opacity 0.3s ease; /* 可选:添加过渡效果 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown.visible {
|
.dropdown.visible {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown button {
|
.dropdown button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
@ -70,19 +77,23 @@
|
|||||||
color: #333; /* 深色文本 */
|
color: #333; /* 深色文本 */
|
||||||
transition: background-color 0.3s ease; /* 可选:添加过渡效果 */
|
transition: background-color 0.3s ease; /* 可选:添加过渡效果 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown button:hover {
|
.dropdown button:hover {
|
||||||
background-color: #e0e0e0; /* 悬停时颜色变化 */
|
background-color: #e0e0e0; /* 悬停时颜色变化 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group {
|
.form-group {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: #555;
|
color: #555;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="text"] {
|
input[type="text"] {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
@ -91,12 +102,14 @@
|
|||||||
background-color: #f9f9f9;
|
background-color: #f9f9f9;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-list {
|
.item-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
@ -109,18 +122,22 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item:hover {
|
.item:hover {
|
||||||
background-color: #f0f0f0;
|
background-color: #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item.selected {
|
.item.selected {
|
||||||
background-color: #d0f0d0;
|
background-color: #d0f0d0;
|
||||||
border-color: #4CAF50;
|
border-color: #4CAF50;
|
||||||
}
|
}
|
||||||
|
|
||||||
.platform-logo {
|
.platform-logo {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-btn {
|
.search-btn {
|
||||||
background-color: #62D2A1;
|
background-color: #62D2A1;
|
||||||
color: white;
|
color: white;
|
||||||
@ -133,9 +150,11 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-btn:hover {
|
.search-btn:hover {
|
||||||
background-color: #45a049;
|
background-color: #45a049;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background-color: #4CAF50;
|
background-color: #4CAF50;
|
||||||
color: white;
|
color: white;
|
||||||
@ -146,17 +165,21 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.3s;
|
transition: background-color 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
background-color: #45a049;
|
background-color: #45a049;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-result {
|
.search-result {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-header {
|
.list-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -164,6 +187,7 @@
|
|||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-info {
|
.header-info {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 30px 2fr 1fr 1fr;
|
grid-template-columns: 30px 2fr 1fr 1fr;
|
||||||
@ -171,16 +195,20 @@
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-info > div:first-child {
|
.header-info > div:first-child {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.download-btn {
|
.download-btn {
|
||||||
display: none;
|
display: none;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.song-list {
|
.song-list {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.song-row {
|
.song-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 30px 2fr 1fr 1fr;
|
grid-template-columns: 30px 2fr 1fr 1fr;
|
||||||
@ -190,19 +218,23 @@
|
|||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.song-row:hover {
|
.song-row:hover {
|
||||||
background-color: #f7f7f7;
|
background-color: #f7f7f7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.song-checkbox {
|
.song-checkbox {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.song-name, .song-album, .song-artists {
|
.song-name, .song-album, .song-artists {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.header-info, .song-row {
|
.header-info, .song-row {
|
||||||
grid-template-columns: 30px 1fr 1fr 1fr;
|
grid-template-columns: 30px 1fr 1fr 1fr;
|
||||||
@ -217,6 +249,7 @@
|
|||||||
gap: 15px;
|
gap: 15px;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.album-item {
|
.album-item {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
@ -231,15 +264,18 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.album-item:hover {
|
.album-item:hover {
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.album-cover {
|
.album-cover {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.album-name {
|
.album-name {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: #333;
|
color: #333;
|
||||||
@ -250,6 +286,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.album-type {
|
.album-type {
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: #666;
|
color: #666;
|
||||||
@ -262,6 +299,7 @@
|
|||||||
gap: 15px;
|
gap: 15px;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.artist-item {
|
.artist-item {
|
||||||
width: 150px;
|
width: 150px;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
@ -276,15 +314,18 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.artist-item:hover {
|
.artist-item:hover {
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.artist-pic {
|
.artist-pic {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 150px;
|
height: 150px;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.artist-name {
|
.artist-name {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: #333;
|
color: #333;
|
||||||
@ -303,6 +344,7 @@
|
|||||||
gap: 15px;
|
gap: 15px;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.playlist-item {
|
.playlist-item {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
@ -317,15 +359,18 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.playlist-item:hover {
|
.playlist-item:hover {
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.playlist-cover {
|
.playlist-cover {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.playlist-name {
|
.playlist-name {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: #333;
|
color: #333;
|
||||||
@ -341,19 +386,23 @@
|
|||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-container p {
|
.progress-container p {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
progress {
|
progress {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
progress::-webkit-progress-bar {
|
progress::-webkit-progress-bar {
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
progress::-webkit-progress-value {
|
progress::-webkit-progress-value {
|
||||||
background-color: #4CAF50;
|
background-color: #4CAF50;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
@ -381,7 +430,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="text" id="keywords" placeholder="输入关键词" />
|
<input type="text" id="keywords" placeholder="输入关键词"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="search-btn" onclick="startSearch()">搜索</button>
|
<button class="search-btn" onclick="startSearch()">搜索</button>
|
||||||
@ -415,7 +464,7 @@
|
|||||||
let selectedSongs = [];
|
let selectedSongs = [];
|
||||||
let allSongs = [];
|
let allSongs = [];
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
fetchPlatforms();
|
fetchPlatforms();
|
||||||
fetchSearchTypes();
|
fetchSearchTypes();
|
||||||
window.addEventListener('scroll', handleScroll);
|
window.addEventListener('scroll', handleScroll);
|
||||||
@ -424,13 +473,13 @@
|
|||||||
// 点击用户名时,切换下拉菜单的显示状态
|
// 点击用户名时,切换下拉菜单的显示状态
|
||||||
const userInfoDiv = document.getElementById('user-info');
|
const userInfoDiv = document.getElementById('user-info');
|
||||||
const dropdownMenu = document.getElementById('dropdown-menu');
|
const dropdownMenu = document.getElementById('dropdown-menu');
|
||||||
userInfoDiv.addEventListener('click', function(event) {
|
userInfoDiv.addEventListener('click', function (event) {
|
||||||
event.stopPropagation(); // 防止事件冒泡
|
event.stopPropagation(); // 防止事件冒泡
|
||||||
dropdownMenu.classList.toggle('visible');
|
dropdownMenu.classList.toggle('visible');
|
||||||
});
|
});
|
||||||
|
|
||||||
// 点击页面其他部分时,隐藏下拉菜单
|
// 点击页面其他部分时,隐藏下拉菜单
|
||||||
document.addEventListener('click', function() {
|
document.addEventListener('click', function () {
|
||||||
dropdownMenu.classList.remove('visible');
|
dropdownMenu.classList.remove('visible');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -930,7 +979,7 @@
|
|||||||
alert(msg);
|
alert(msg);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.href = '/login.html'; // 确保登录页面的路径正确
|
window.location.href = '/login.html'; // 确保登录页面的路径正确
|
||||||
}, 1000);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 退出登录函数
|
// 退出登录函数
|
||||||
@ -966,7 +1015,7 @@
|
|||||||
// 辅助函数:转义HTML,以防止XSS攻击
|
// 辅助函数:转义HTML,以防止XSS攻击
|
||||||
function escapeHTML(str) {
|
function escapeHTML(str) {
|
||||||
if (!str) return '';
|
if (!str) return '';
|
||||||
return str.replace(/[&<>"'`=\/]/g, function(s) {
|
return str.replace(/[&<>"'`=\/]/g, function (s) {
|
||||||
return ({
|
return ({
|
||||||
'&': '&',
|
'&': '&',
|
||||||
'<': '<',
|
'<': '<',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user