This commit is contained in:
N1KO 2024-12-15 17:22:30 +08:00
parent d55ab34c46
commit f4b5c4dc06
7 changed files with 47 additions and 253 deletions

View File

@ -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<UserLevel>() {
});
UserLevelThreadLocal.set(userLevel);
if (Objects.equals(UserLevel.NORMAL, userLevel)) {
throw new VipException("等级不够,请升级");

View File

@ -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<String> download(@RequestParam(name = "channel") Integer channel,
@RequestParam(name = "id") List<String> idList) {
String res = musicService.getMusicService(channel).download(UserThreadLocal.get(), idList);
@Login
public Results<MusicDownloadRes> 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文件失败");
}
}
}

View File

@ -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<String, ZipMetadata> zipStore = zipService.getZipStore();
Iterator<Map.Entry<String, ZipMetadata>> iterator = zipStore.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, ZipMetadata> 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();
}
}
}
}

View File

@ -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<Q extends AbstractMusicReq, S extends
@Resource(name = "commonExecutor")
private Executor commonExecutor;
@Resource
private ZipService zipService;
public abstract ChannelEnum getChannelEnum();
public MusicSearchRes search(MusicSearchReq req) {
@ -78,65 +66,36 @@ public abstract class AbstractMusicService<Q extends AbstractMusicReq, S extends
return channelClient.artist(id);
}
public String download(Long userId, List<String> idList) {
if (CollectionUtils.isEmpty(idList)) {
throw new BusinessException("请选择文件进行下载");
}
public MusicDownloadRes download(String id) {
ChannelEnum channelEnum = getChannelEnum();
ChannelClient<Q, S> channelClient = channelClientFactory.getClient(channelEnum);
Map<String, String> 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;
}
}

View File

@ -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<String, ZipMetadata> 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<String, String> 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<String, ZipMetadata> getZipStore() {
return zipStore;
}
}

View File

@ -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<String, Object> redisTemplate,
Supplier<T> supplier) {
Supplier<T> supplier,
TypeReference<T> 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)) {

View File

@ -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);
}