diff --git a/src/main/java/top/baogutang/music/aspect/VipAspect.java b/src/main/java/top/baogutang/music/aspect/VipAspect.java index 7ae22cb..8afd359 100644 --- a/src/main/java/top/baogutang/music/aspect/VipAspect.java +++ b/src/main/java/top/baogutang/music/aspect/VipAspect.java @@ -1,5 +1,6 @@ package top.baogutang.music.aspect; +import com.fasterxml.jackson.core.type.TypeReference; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; @@ -88,7 +89,9 @@ public class VipAspect { null, null, redisTemplate, - () -> userService.queryUserLevel(userId)); + () -> userService.queryUserLevel(userId), + new TypeReference() { + }); UserLevelThreadLocal.set(userLevel); if (Objects.equals(UserLevel.NORMAL, userLevel)) { throw new VipException("等级不够,请升级"); diff --git a/src/main/java/top/baogutang/music/controller/MusicDownloadController.java b/src/main/java/top/baogutang/music/controller/MusicDownloadController.java index d97e997..64e5a8b 100644 --- a/src/main/java/top/baogutang/music/controller/MusicDownloadController.java +++ b/src/main/java/top/baogutang/music/controller/MusicDownloadController.java @@ -1,21 +1,17 @@ package top.baogutang.music.controller; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.*; -import org.springframework.web.bind.annotation.*; -import top.baogutang.music.annos.Vip; +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 top.baogutang.music.domain.Results; -import top.baogutang.music.domain.ZipMetadata; +import top.baogutang.music.domain.res.download.MusicDownloadRes; +import top.baogutang.music.domain.res.search.MusicPlaylistRes; import top.baogutang.music.service.IMusicService; -import top.baogutang.music.service.ZipService; -import top.baogutang.music.utils.UserThreadLocal; import javax.annotation.Resource; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.time.LocalDateTime; -import java.util.List; /** * @@ -32,59 +28,13 @@ public class MusicDownloadController { @Resource private IMusicService musicService; - @Resource - private ZipService zipService; - @GetMapping - @Vip - public Results download(@RequestParam(name = "channel") Integer channel, - @RequestParam(name = "id") List idList) { - String res = musicService.getMusicService(channel).download(UserThreadLocal.get(), idList); + @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); } - /** - * 提供一个接口,让前端可以通过URL下载ZIP文件。 - * - * @param zipFileName ZIP文件名 - * @return ZIP文件流 - */ - @GetMapping("/downloads/{zipFileName}") - public ResponseEntity downloadZip(@PathVariable String zipFileName) { - ZipMetadata metadata = zipService.getZipMetadata(zipFileName); - if (metadata == null) { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body("ZIP文件不存在或已过期"); - } - - // 检查是否过期 - if (LocalDateTime.now().isAfter(metadata.getExpirationTime())) { - // 删除过期的ZIP文件 - File expiredZip = new File(metadata.getZipFilePath()); - if (expiredZip.exists()) { - expiredZip.delete(); - } - // 从映射中移除 - zipService.removeZip(zipFileName); - return ResponseEntity.status(HttpStatus.GONE).body("下载链接已过期"); - } - - File zipFile = new File(metadata.getZipFilePath()); - if (!zipFile.exists()) { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body("ZIP文件不存在"); - } - - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); - headers.setContentDisposition(ContentDisposition.builder("attachment").filename(zipFileName).build()); - - try { - byte[] fileContent = Files.readAllBytes(zipFile.toPath()); - return new ResponseEntity<>(fileContent, headers, HttpStatus.OK); - } catch (IOException e) { - log.error(">>>>>>>>>>读取zip文件失败:{}<<<<<<<<<<", e.getMessage(), e); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("读取ZIP文件失败"); - } - } - } diff --git a/src/main/java/top/baogutang/music/schedules/ZipCleanupScheduler.java b/src/main/java/top/baogutang/music/schedules/ZipCleanupScheduler.java deleted file mode 100644 index f2ec2dc..0000000 --- a/src/main/java/top/baogutang/music/schedules/ZipCleanupScheduler.java +++ /dev/null @@ -1,49 +0,0 @@ -package top.baogutang.music.schedules; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; -import top.baogutang.music.domain.ZipMetadata; -import top.baogutang.music.service.ZipService; - -import javax.annotation.Resource; -import java.io.File; -import java.time.LocalDateTime; -import java.util.Iterator; -import java.util.Map; - -@Slf4j -@Component -public class ZipCleanupScheduler { - - @Resource - private ZipService zipService; - - // 每小时执行一次 - @Scheduled(cron = "0 0 * * * ?") - public void cleanupOldZips() { - Map zipStore = zipService.getZipStore(); - - Iterator> iterator = zipStore.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - ZipMetadata metadata = entry.getValue(); - - if (LocalDateTime.now().isAfter(metadata.getExpirationTime())) { - // 删除ZIP文件 - File zipFile = new File(metadata.getZipFilePath()); - if (zipFile.exists()) { - boolean deleted = zipFile.delete(); - if (deleted) { - log.info(">>>>>>>>>>Deleted expired ZIP{}<<<<<<<<<<", metadata.getZipFilePath()); - } else { - log.error(">>>>>>>>>>Failed to delete ZIP:{}<<<<<<<<<<", metadata.getZipFilePath()); - } - } - - // 从映射中移除 - iterator.remove(); - } - } - } -} diff --git a/src/main/java/top/baogutang/music/service/AbstractMusicService.java b/src/main/java/top/baogutang/music/service/AbstractMusicService.java index 52fd674..85801b0 100644 --- a/src/main/java/top/baogutang/music/service/AbstractMusicService.java +++ b/src/main/java/top/baogutang/music/service/AbstractMusicService.java @@ -2,12 +2,7 @@ package top.baogutang.music.service; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.Pair; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.util.CollectionUtils; import top.baogutang.music.client.ChannelClient; -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; @@ -18,14 +13,10 @@ import top.baogutang.music.exceptions.BusinessException; import top.baogutang.music.factory.ChannelClientFactory; import javax.annotation.Resource; -import java.io.File; import java.io.IOException; -import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; -import java.util.stream.Collectors; /** * @@ -43,9 +34,6 @@ public abstract class AbstractMusicService idList) { - if (CollectionUtils.isEmpty(idList)) { - throw new BusinessException("请选择文件进行下载"); - } + public MusicDownloadRes download(String id) { ChannelEnum channelEnum = getChannelEnum(); ChannelClient channelClient = channelClientFactory.getClient(channelEnum); - Map filesWithPaths = idList.stream() - .map(id -> - CompletableFuture.supplyAsync(() -> { - MusicRecordEntity musicRecord = channelClient.queryByPlatform(id); - String filePath; - String zipEntryPath; - if (Objects.nonNull(musicRecord)) { - // 当前歌曲已经下载过,读取本地文件 - zipEntryPath = musicRecord.getArtistName() + "/" + musicRecord.getAlbumName() + "/" + musicRecord.getName() + "." + musicRecord.getFileType().name().toLowerCase(); - filePath = channelClient.getDownloadBasePath() + File.separator + musicRecord.getArtistName() - + File.separator + musicRecord.getAlbumName() - + File.separator + musicRecord.getName() + - "." + musicRecord.getFileType().name().toLowerCase(); - return Pair.of(filePath, zipEntryPath); - } - - // 不存在,先下载 - MusicDownloadRes res = channelClient.download(id); - if (Objects.isNull(res)) { - log.error(">>>>>>>>>>query detail error! channel:{},song id:{}<<<<<<<<<<", channelEnum.getDesc(), id); - return null; - } - if (StringUtils.isNotBlank(res.getArtistName())) { - String[] split = res.getArtistName().split("/"); - String artistName = String.join(",", split); - res.setArtistName(artistName); - } - - channelClient.saveMusic(res); - try { - channelClient.processFile(res); - log.info(">>>>>>>>>>download file:{} success<<<<<<<<<<", res.getName()); - } catch (IOException e) { - log.error(">>>>>>>>>>download error:{}<<<<<<<<<<", e.getMessage(), e); - return null; - } - filePath = channelClient.getDownloadBasePath() + File.separator + res.getArtistName() - + File.separator + res.getAlbumName() - + File.separator + res.getName() + - "." + res.getFileType().name().toLowerCase(); - zipEntryPath = res.getArtistName() + "/" + res.getAlbumName() + "/" + res.getName() + "." + res.getFileType().name().toLowerCase(); - return Pair.of(filePath, zipEntryPath); - }, commonExecutor)) - .map(CompletableFuture::join) - .filter(pair -> Objects.nonNull(pair.getKey())) - .collect(Collectors.toMap(Pair::getKey, Pair::getValue)); - - try { - return zipService.createZip(filesWithPaths,userId); - } catch (IOException e) { - log.error(">>>>>>>>>>gene zip error:{}<<<<<<<<<<", e.getMessage(), e); - throw new BusinessException("请稍后重试"); + MusicDownloadRes res = channelClient.download(id); + if (Objects.isNull(res)) { + log.error(">>>>>>>>>>query detail error! channel:{},song id:{}<<<<<<<<<<", channelEnum.getDesc(), id); + return null; + } + if (StringUtils.isNotBlank(res.getArtistName())) { + String[] split = res.getArtistName().split("/"); + String artistName = String.join(",", split); + res.setArtistName(artistName); } + channelClient.saveMusic(res); + CompletableFuture.runAsync(() -> { + try { + channelClient.processFile(res); + log.info(">>>>>>>>>>download file:{} success<<<<<<<<<<", res.getName()); + } catch (IOException e) { + log.error(">>>>>>>>>>download error:{}<<<<<<<<<<", e.getMessage(), e); + throw new BusinessException("下载异常"); + } + }, commonExecutor); +// try { +// channelClient.processFile(res); +// } catch (IOException e) { +// log.error(">>>>>>>>>>download error:{}<<<<<<<<<<", e.getMessage(), e); +// throw new BusinessException("下载异常"); +// } + return res; } } diff --git a/src/main/java/top/baogutang/music/service/ZipService.java b/src/main/java/top/baogutang/music/service/ZipService.java deleted file mode 100644 index 1d46c89..0000000 --- a/src/main/java/top/baogutang/music/service/ZipService.java +++ /dev/null @@ -1,72 +0,0 @@ -package top.baogutang.music.service; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; -import top.baogutang.music.domain.ZipMetadata; -import top.baogutang.music.utils.ZipUtil; - -import javax.annotation.PostConstruct; -import java.io.File; -import java.io.IOException; -import java.time.LocalDateTime; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -@Slf4j -@Service -public class ZipService { - - - @Value("${baogutang.music.zip.base-path}") - private String zipPath; - - // 存储ZIP文件的元数据,键为ZIP文件名 - private static final ConcurrentMap zipStore = new ConcurrentHashMap<>(); - - // 定义ZIP文件的有效时长(例如,1小时) - private static final long ZIP_VALID_DURATION_HOURS = 1; - - @PostConstruct - public void init() { - File zipDir = new File(zipPath); - if (!zipDir.exists()) { - boolean created = zipDir.mkdirs(); - if (created) { - log.info(">>>>>>>>>>ZIP目录创建成功:{}<<<<<<<<<<", zipPath); - } else { - log.info(">>>>>>>>>>无法创建ZIP目录:{}<<<<<<<<<<", zipPath); - } - } - } - - public String createZip(Map filesWithPaths, Long userId) throws IOException { - String zipFileName = "music_download_" + UUID.randomUUID() + ".zip"; - String zipFilePath = zipPath + File.separator + userId + File.separator + zipFileName; - - // 创建ZIP文件 - ZipUtil.createZip(filesWithPaths, zipFilePath); - - // 计算过期时间 - LocalDateTime expirationTime = LocalDateTime.now().plusHours(ZIP_VALID_DURATION_HOURS); - - // 存储ZIP元数据 - zipStore.put(zipFileName, new ZipMetadata(zipFilePath, expirationTime)); - - return zipFileName; - } - - public ZipMetadata getZipMetadata(String zipFileName) { - return zipStore.get(zipFileName); - } - - public void removeZip(String zipFileName) { - zipStore.remove(zipFileName); - } - - public ConcurrentMap getZipStore() { - return zipStore; - } -} diff --git a/src/main/java/top/baogutang/music/utils/CacheUtil.java b/src/main/java/top/baogutang/music/utils/CacheUtil.java index 28f7b1a..a90ebc6 100644 --- a/src/main/java/top/baogutang/music/utils/CacheUtil.java +++ b/src/main/java/top/baogutang/music/utils/CacheUtil.java @@ -1,5 +1,6 @@ package top.baogutang.music.utils; +import com.fasterxml.jackson.core.type.TypeReference; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; @@ -111,10 +112,11 @@ public class CacheUtil { Long timeout, TimeUnit timeUnit, RedisTemplate redisTemplate, - Supplier supplier) { + Supplier supplier, + TypeReference typeReference) { Object cacheObject = redisTemplate.opsForValue().get(cacheKey); if (Objects.nonNull(cacheObject)) { - return (T) cacheObject; + return JacksonUtil.fromJson(JacksonUtil.toJson(cacheObject), typeReference); } T t = supplier.get(); if (Objects.isNull(t)) { diff --git a/src/main/java/top/baogutang/music/utils/OkHttpUtil.java b/src/main/java/top/baogutang/music/utils/OkHttpUtil.java index cd78764..c72c044 100644 --- a/src/main/java/top/baogutang/music/utils/OkHttpUtil.java +++ b/src/main/java/top/baogutang/music/utils/OkHttpUtil.java @@ -112,6 +112,7 @@ public class OkHttpUtil { Response response = OkHttpUtil.getInstance().newCall(request).execute(); if (response.isSuccessful()) { String content = Objects.requireNonNull(response.body()).string(); + log.info("get res:{}", content); if (StringUtils.isNotBlank(content)) { return JacksonUtil.fromJson(content, type); }