From b6f3ff6dcee76c8ffaabc5d3139c419f8e792afa Mon Sep 17 00:00:00 2001 From: N1KO Date: Mon, 16 Dec 2024 11:20:46 +0800 Subject: [PATCH] download --- .../baogutang/music/client/ChannelClient.java | 8 +- .../music/client/NetEaseMusicClient.java | 22 ++++ .../baogutang/music/client/QQMusicClient.java | 20 ++++ .../controller/MusicDownloadController.java | 27 +++-- .../dao/entity/MusicDownloadRecordEntity.java | 31 +++++ .../music/dao/entity/MusicRecordEntity.java | 1 - .../dao/mapper/MusicDownloadRecordMapper.java | 16 +++ .../domain/req/download/MusicDownloadReq.java | 23 ++++ .../music/service/AbstractMusicService.java | 106 ++++++++++++++++++ .../service/IMusicDownloadRecordService.java | 21 ++++ .../music/service/IMusicRecordService.java | 4 + .../impl/MusicDownloadRecordServiceImpl.java | 44 ++++++++ .../service/impl/MusicRecordServiceImpl.java | 10 ++ src/main/resources/templates/music.html | 73 ++++++++++-- 14 files changed, 380 insertions(+), 26 deletions(-) create mode 100644 src/main/java/top/baogutang/music/dao/entity/MusicDownloadRecordEntity.java create mode 100644 src/main/java/top/baogutang/music/dao/mapper/MusicDownloadRecordMapper.java create mode 100644 src/main/java/top/baogutang/music/domain/req/download/MusicDownloadReq.java create mode 100644 src/main/java/top/baogutang/music/service/IMusicDownloadRecordService.java create mode 100644 src/main/java/top/baogutang/music/service/impl/MusicDownloadRecordServiceImpl.java diff --git a/src/main/java/top/baogutang/music/client/ChannelClient.java b/src/main/java/top/baogutang/music/client/ChannelClient.java index f7b3ebe..ac1799d 100644 --- a/src/main/java/top/baogutang/music/client/ChannelClient.java +++ b/src/main/java/top/baogutang/music/client/ChannelClient.java @@ -1,5 +1,6 @@ package top.baogutang.music.client; +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.search.MusicSearchReq; @@ -8,7 +9,7 @@ import top.baogutang.music.domain.res.download.MusicDownloadRes; import top.baogutang.music.domain.res.search.*; import java.io.IOException; -import java.io.InputStream; +import java.util.List; /** * @@ -39,4 +40,9 @@ public interface ChannelClient queryDownloadRecord(String batchNo); + + List queryMusicByPlatformIdList(List platformIdList); } diff --git a/src/main/java/top/baogutang/music/client/NetEaseMusicClient.java b/src/main/java/top/baogutang/music/client/NetEaseMusicClient.java index 9d76d1a..0b3d0ab 100644 --- a/src/main/java/top/baogutang/music/client/NetEaseMusicClient.java +++ b/src/main/java/top/baogutang/music/client/NetEaseMusicClient.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import top.baogutang.music.annos.ChannelInfo; +import top.baogutang.music.dao.entity.MusicDownloadRecordEntity; import top.baogutang.music.dao.entity.MusicRecordEntity; import top.baogutang.music.domain.req.search.MusicSearchReq; 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.processor.AbstractAudioProcessor; import top.baogutang.music.properties.NetEaseMusicProperties; +import top.baogutang.music.service.IMusicDownloadRecordService; import top.baogutang.music.service.IMusicRecordService; import top.baogutang.music.utils.OkHttpUtil; @@ -23,6 +25,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import java.util.List; import java.util.Objects; /** @@ -44,6 +47,9 @@ public class NetEaseMusicClient implements ChannelClient queryDownloadRecord(String batchNo) { + return musicDownloadRecordService.queryByBatchNo(batchNo); + } + + @Override + public List queryMusicByPlatformIdList(List platformIdList) { + return musicRecordService.queryByPlatformIdList(platformIdList); + } + + } diff --git a/src/main/java/top/baogutang/music/client/QQMusicClient.java b/src/main/java/top/baogutang/music/client/QQMusicClient.java index 74a6aeb..cb90cad 100644 --- a/src/main/java/top/baogutang/music/client/QQMusicClient.java +++ b/src/main/java/top/baogutang/music/client/QQMusicClient.java @@ -5,6 +5,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import top.baogutang.music.annos.ChannelInfo; +import top.baogutang.music.dao.entity.MusicDownloadRecordEntity; import top.baogutang.music.dao.entity.MusicRecordEntity; import top.baogutang.music.domain.req.search.MusicSearchReq; 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.processor.AbstractAudioProcessor; import top.baogutang.music.properties.QQMusicProperties; +import top.baogutang.music.service.IMusicDownloadRecordService; import top.baogutang.music.service.IMusicRecordService; import top.baogutang.music.utils.OkHttpUtil; @@ -49,6 +51,9 @@ public class QQMusicClient implements ChannelClient queryDownloadRecord(String batchNo) { + return musicDownloadRecordService.queryByBatchNo(batchNo); + } + + @Override + public List queryMusicByPlatformIdList(List platformIdList) { + return musicRecordService.queryByPlatformIdList(platformIdList); + } + } diff --git a/src/main/java/top/baogutang/music/controller/MusicDownloadController.java b/src/main/java/top/baogutang/music/controller/MusicDownloadController.java index 64e5a8b..9486413 100644 --- a/src/main/java/top/baogutang/music/controller/MusicDownloadController.java +++ b/src/main/java/top/baogutang/music/controller/MusicDownloadController.java @@ -1,17 +1,15 @@ package top.baogutang.music.controller; import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import top.baogutang.music.annos.Login; +import org.springframework.web.bind.annotation.*; +import top.baogutang.music.annos.Vip; import top.baogutang.music.domain.Results; -import top.baogutang.music.domain.res.download.MusicDownloadRes; -import top.baogutang.music.domain.res.search.MusicPlaylistRes; +import top.baogutang.music.domain.req.download.MusicDownloadReq; import top.baogutang.music.service.IMusicService; +import top.baogutang.music.utils.UserThreadLocal; import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; /** * @@ -29,11 +27,16 @@ public class MusicDownloadController { private IMusicService musicService; @GetMapping - @Login - public Results download(@RequestParam(name = "channel") Integer channel, - @RequestParam(name = "id") String id) { - MusicDownloadRes res = musicService.getMusicService(channel).download(id); - return Results.ok(res); + @Vip + public Results download(@RequestBody MusicDownloadReq req) { + String downloadUrl = musicService.getMusicService(req.getChannel()).downloads(req.getIdList(), UserThreadLocal.get()); + return Results.ok(downloadUrl); + } + + @GetMapping("/file") + @Vip + public void downloadFile(@RequestParam(name = "batchNo") String batchNo, @RequestParam(name = "channel") Integer channel, HttpServletResponse response) { + musicService.getMusicService(channel).downloadFile(batchNo, response); } diff --git a/src/main/java/top/baogutang/music/dao/entity/MusicDownloadRecordEntity.java b/src/main/java/top/baogutang/music/dao/entity/MusicDownloadRecordEntity.java new file mode 100644 index 0000000..42925ff --- /dev/null +++ b/src/main/java/top/baogutang/music/dao/entity/MusicDownloadRecordEntity.java @@ -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; + + +} diff --git a/src/main/java/top/baogutang/music/dao/entity/MusicRecordEntity.java b/src/main/java/top/baogutang/music/dao/entity/MusicRecordEntity.java index 53b0693..09eaaf7 100644 --- a/src/main/java/top/baogutang/music/dao/entity/MusicRecordEntity.java +++ b/src/main/java/top/baogutang/music/dao/entity/MusicRecordEntity.java @@ -5,7 +5,6 @@ import lombok.Getter; import lombok.Setter; import top.baogutang.music.enums.AudioFileTypeEnum; import top.baogutang.music.enums.ChannelEnum; -import top.baogutang.music.enums.MusicQualityEnum; /** * diff --git a/src/main/java/top/baogutang/music/dao/mapper/MusicDownloadRecordMapper.java b/src/main/java/top/baogutang/music/dao/mapper/MusicDownloadRecordMapper.java new file mode 100644 index 0000000..5e9c9a6 --- /dev/null +++ b/src/main/java/top/baogutang/music/dao/mapper/MusicDownloadRecordMapper.java @@ -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 { +} diff --git a/src/main/java/top/baogutang/music/domain/req/download/MusicDownloadReq.java b/src/main/java/top/baogutang/music/domain/req/download/MusicDownloadReq.java new file mode 100644 index 0000000..d9f8cf7 --- /dev/null +++ b/src/main/java/top/baogutang/music/domain/req/download/MusicDownloadReq.java @@ -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 idList; +} diff --git a/src/main/java/top/baogutang/music/service/AbstractMusicService.java b/src/main/java/top/baogutang/music/service/AbstractMusicService.java index 85801b0..4936fc8 100644 --- a/src/main/java/top/baogutang/music/service/AbstractMusicService.java +++ b/src/main/java/top/baogutang/music/service/AbstractMusicService.java @@ -2,7 +2,11 @@ package top.baogutang.music.service; import lombok.extern.slf4j.Slf4j; 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.dao.entity.MusicDownloadRecordEntity; +import top.baogutang.music.dao.entity.MusicRecordEntity; import top.baogutang.music.domain.req.AbstractMusicReq; import top.baogutang.music.domain.req.search.MusicSearchReq; import top.baogutang.music.domain.res.AbstractMusicRes; @@ -13,10 +17,19 @@ import top.baogutang.music.exceptions.BusinessException; import top.baogutang.music.factory.ChannelClientFactory; 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.util.List; import java.util.Objects; +import java.util.UUID; import java.util.concurrent.CompletableFuture; 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 idList, Long userId) { + if (CollectionUtils.isEmpty(idList)) { + throw new BusinessException("请选择下载内容"); + } + if (idList.size() > 100) { + throw new BusinessException("单次下载限制最多100首"); + } + ChannelEnum channelEnum = getChannelEnum(); + ChannelClient 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 channelClient = channelClientFactory.getClient(channelEnum); + List musicDownloadRecordList = channelClient.queryDownloadRecord(batchNo); + if (CollectionUtils.isEmpty(musicDownloadRecordList)) { + return; + } + List platformIdList = musicDownloadRecordList.stream() + .map(MusicDownloadRecordEntity::getPlatformId) + .collect(Collectors.toList()); + List 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); + } + } } diff --git a/src/main/java/top/baogutang/music/service/IMusicDownloadRecordService.java b/src/main/java/top/baogutang/music/service/IMusicDownloadRecordService.java new file mode 100644 index 0000000..1ef3b4c --- /dev/null +++ b/src/main/java/top/baogutang/music/service/IMusicDownloadRecordService.java @@ -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 { + + void save(Long userId, String batchNo, ChannelEnum channelEnum, String musicId); + + List queryByBatchNo(String batchNo); +} diff --git a/src/main/java/top/baogutang/music/service/IMusicRecordService.java b/src/main/java/top/baogutang/music/service/IMusicRecordService.java index 6de79ed..7c2af30 100644 --- a/src/main/java/top/baogutang/music/service/IMusicRecordService.java +++ b/src/main/java/top/baogutang/music/service/IMusicRecordService.java @@ -5,6 +5,8 @@ import top.baogutang.music.dao.entity.MusicRecordEntity; import top.baogutang.music.domain.res.download.MusicDownloadRes; import top.baogutang.music.enums.ChannelEnum; +import java.util.List; + /** * * @description: @@ -17,4 +19,6 @@ public interface IMusicRecordService extends IService { void save(MusicDownloadRes res, ChannelEnum channel); MusicRecordEntity queryByChannelAndPlatform(ChannelEnum channel, String platformId); + + List queryByPlatformIdList(List platformIdList); } diff --git a/src/main/java/top/baogutang/music/service/impl/MusicDownloadRecordServiceImpl.java b/src/main/java/top/baogutang/music/service/impl/MusicDownloadRecordServiceImpl.java new file mode 100644 index 0000000..c2641a9 --- /dev/null +++ b/src/main/java/top/baogutang/music/service/impl/MusicDownloadRecordServiceImpl.java @@ -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 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 queryByBatchNo(String batchNo) { + return new LambdaQueryChainWrapper<>(baseMapper) + .eq(MusicDownloadRecordEntity::getBatchNo, batchNo) + .eq(MusicDownloadRecordEntity::getDeleted, Boolean.FALSE) + .list(); + } +} diff --git a/src/main/java/top/baogutang/music/service/impl/MusicRecordServiceImpl.java b/src/main/java/top/baogutang/music/service/impl/MusicRecordServiceImpl.java index eb779dd..04fd73d 100644 --- a/src/main/java/top/baogutang/music/service/impl/MusicRecordServiceImpl.java +++ b/src/main/java/top/baogutang/music/service/impl/MusicRecordServiceImpl.java @@ -12,6 +12,7 @@ import top.baogutang.music.enums.MusicQualityEnum; import top.baogutang.music.service.IMusicRecordService; import java.time.LocalDateTime; +import java.util.List; import java.util.Objects; /** @@ -66,4 +67,13 @@ public class MusicRecordServiceImpl extends ServiceImpl queryByPlatformIdList(List platformIdList) { + return new LambdaQueryChainWrapper<>(baseMapper) + .in(MusicRecordEntity::getPlatformId, platformIdList) + .eq(MusicRecordEntity::getDeleted, Boolean.FALSE) + .list(); + } + } diff --git a/src/main/resources/templates/music.html b/src/main/resources/templates/music.html index 69c865d..09b1fab 100644 --- a/src/main/resources/templates/music.html +++ b/src/main/resources/templates/music.html @@ -2,7 +2,7 @@ - + BAOGUTANG-MUSIC