vip
This commit is contained in:
parent
d55ab34c46
commit
f4b5c4dc06
@ -1,5 +1,6 @@
|
|||||||
package top.baogutang.music.aspect;
|
package top.baogutang.music.aspect;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.aspectj.lang.ProceedingJoinPoint;
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
@ -88,7 +89,9 @@ public class VipAspect {
|
|||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
redisTemplate,
|
redisTemplate,
|
||||||
() -> userService.queryUserLevel(userId));
|
() -> userService.queryUserLevel(userId),
|
||||||
|
new TypeReference<UserLevel>() {
|
||||||
|
});
|
||||||
UserLevelThreadLocal.set(userLevel);
|
UserLevelThreadLocal.set(userLevel);
|
||||||
if (Objects.equals(UserLevel.NORMAL, userLevel)) {
|
if (Objects.equals(UserLevel.NORMAL, userLevel)) {
|
||||||
throw new VipException("等级不够,请升级");
|
throw new VipException("等级不够,请升级");
|
||||||
|
|||||||
@ -1,21 +1,17 @@
|
|||||||
package top.baogutang.music.controller;
|
package top.baogutang.music.controller;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.*;
|
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.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.IMusicService;
|
||||||
import top.baogutang.music.service.ZipService;
|
|
||||||
import top.baogutang.music.utils.UserThreadLocal;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
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
|
@Resource
|
||||||
private IMusicService musicService;
|
private IMusicService musicService;
|
||||||
|
|
||||||
@Resource
|
|
||||||
private ZipService zipService;
|
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@Vip
|
@Login
|
||||||
public Results<String> download(@RequestParam(name = "channel") Integer channel,
|
public Results<MusicDownloadRes> download(@RequestParam(name = "channel") Integer channel,
|
||||||
@RequestParam(name = "id") List<String> idList) {
|
@RequestParam(name = "id") String id) {
|
||||||
String res = musicService.getMusicService(channel).download(UserThreadLocal.get(), idList);
|
MusicDownloadRes res = musicService.getMusicService(channel).download(id);
|
||||||
return Results.ok(res);
|
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 lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
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.client.ChannelClient;
|
||||||
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;
|
||||||
@ -18,14 +13,10 @@ 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 java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -43,9 +34,6 @@ public abstract class AbstractMusicService<Q extends AbstractMusicReq, S extends
|
|||||||
@Resource(name = "commonExecutor")
|
@Resource(name = "commonExecutor")
|
||||||
private Executor commonExecutor;
|
private Executor commonExecutor;
|
||||||
|
|
||||||
@Resource
|
|
||||||
private ZipService zipService;
|
|
||||||
|
|
||||||
public abstract ChannelEnum getChannelEnum();
|
public abstract ChannelEnum getChannelEnum();
|
||||||
|
|
||||||
public MusicSearchRes search(MusicSearchReq req) {
|
public MusicSearchRes search(MusicSearchReq req) {
|
||||||
@ -78,65 +66,36 @@ public abstract class AbstractMusicService<Q extends AbstractMusicReq, S extends
|
|||||||
return channelClient.artist(id);
|
return channelClient.artist(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String download(Long userId, List<String> idList) {
|
public MusicDownloadRes download(String id) {
|
||||||
if (CollectionUtils.isEmpty(idList)) {
|
|
||||||
throw new BusinessException("请选择文件进行下载");
|
|
||||||
}
|
|
||||||
ChannelEnum channelEnum = getChannelEnum();
|
ChannelEnum channelEnum = getChannelEnum();
|
||||||
ChannelClient<Q, S> channelClient = channelClientFactory.getClient(channelEnum);
|
ChannelClient<Q, S> channelClient = channelClientFactory.getClient(channelEnum);
|
||||||
Map<String, String> filesWithPaths = idList.stream()
|
MusicDownloadRes res = channelClient.download(id);
|
||||||
.map(id ->
|
if (Objects.isNull(res)) {
|
||||||
CompletableFuture.supplyAsync(() -> {
|
log.error(">>>>>>>>>>query detail error! channel:{},song id:{}<<<<<<<<<<", channelEnum.getDesc(), id);
|
||||||
MusicRecordEntity musicRecord = channelClient.queryByPlatform(id);
|
return null;
|
||||||
String filePath;
|
}
|
||||||
String zipEntryPath;
|
if (StringUtils.isNotBlank(res.getArtistName())) {
|
||||||
if (Objects.nonNull(musicRecord)) {
|
String[] split = res.getArtistName().split("/");
|
||||||
// 当前歌曲已经下载过,读取本地文件
|
String artistName = String.join(",", split);
|
||||||
zipEntryPath = musicRecord.getArtistName() + "/" + musicRecord.getAlbumName() + "/" + musicRecord.getName() + "." + musicRecord.getFileType().name().toLowerCase();
|
res.setArtistName(artistName);
|
||||||
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("请稍后重试");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
package top.baogutang.music.utils;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
|
||||||
@ -111,10 +112,11 @@ public class CacheUtil {
|
|||||||
Long timeout,
|
Long timeout,
|
||||||
TimeUnit timeUnit,
|
TimeUnit timeUnit,
|
||||||
RedisTemplate<String, Object> redisTemplate,
|
RedisTemplate<String, Object> redisTemplate,
|
||||||
Supplier<T> supplier) {
|
Supplier<T> supplier,
|
||||||
|
TypeReference<T> typeReference) {
|
||||||
Object cacheObject = redisTemplate.opsForValue().get(cacheKey);
|
Object cacheObject = redisTemplate.opsForValue().get(cacheKey);
|
||||||
if (Objects.nonNull(cacheObject)) {
|
if (Objects.nonNull(cacheObject)) {
|
||||||
return (T) cacheObject;
|
return JacksonUtil.fromJson(JacksonUtil.toJson(cacheObject), typeReference);
|
||||||
}
|
}
|
||||||
T t = supplier.get();
|
T t = supplier.get();
|
||||||
if (Objects.isNull(t)) {
|
if (Objects.isNull(t)) {
|
||||||
|
|||||||
@ -112,6 +112,7 @@ public class OkHttpUtil {
|
|||||||
Response response = OkHttpUtil.getInstance().newCall(request).execute();
|
Response response = OkHttpUtil.getInstance().newCall(request).execute();
|
||||||
if (response.isSuccessful()) {
|
if (response.isSuccessful()) {
|
||||||
String content = Objects.requireNonNull(response.body()).string();
|
String content = Objects.requireNonNull(response.body()).string();
|
||||||
|
log.info("get res:{}", content);
|
||||||
if (StringUtils.isNotBlank(content)) {
|
if (StringUtils.isNotBlank(content)) {
|
||||||
return JacksonUtil.fromJson(content, type);
|
return JacksonUtil.fromJson(content, type);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user