vip
This commit is contained in:
parent
d55ab34c46
commit
f4b5c4dc06
@ -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("等级不够,请升级");
|
||||
|
||||
@ -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文件失败");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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)) {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user