generated from Java-2025Fall/final-vibevault-template
完成作业
This commit is contained in:
parent
0115a4bf31
commit
d5091273cd
@ -1,8 +1,11 @@
|
|||||||
package com.vibevault.controller;
|
package com.vibevault.controller;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
@ -14,6 +17,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import com.vibevault.dto.CopyPlaylistRequest;
|
||||||
import com.vibevault.dto.PlaylistCreateDTO;
|
import com.vibevault.dto.PlaylistCreateDTO;
|
||||||
import com.vibevault.dto.PlaylistDTO;
|
import com.vibevault.dto.PlaylistDTO;
|
||||||
import com.vibevault.dto.SongCreateDTO;
|
import com.vibevault.dto.SongCreateDTO;
|
||||||
@ -76,6 +80,52 @@ public class PlaylistController {
|
|||||||
return playlistService.addSongToPlaylist(id, songCreateDTO, username);
|
return playlistService.addSongToPlaylist(id, songCreateDTO, username);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// POST /api/playlists/{id}/songs/batch - 批量添加歌曲(需认证)
|
||||||
|
@PostMapping("/{id}/songs/batch")
|
||||||
|
@PreAuthorize("hasRole('USER')")
|
||||||
|
public ResponseEntity<PlaylistDTO> addSongsToPlaylist(@PathVariable Long id, @RequestBody List<SongCreateDTO> songs, Principal principal) {
|
||||||
|
PlaylistDTO updatedPlaylist = playlistService.addSongsToPlaylist(id, songs, principal.getName());
|
||||||
|
return ResponseEntity.ok(updatedPlaylist);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Advanced] 复制歌单
|
||||||
|
*
|
||||||
|
* @param id 要复制的歌单ID
|
||||||
|
* @param request 复制请求(包含新歌单名称)
|
||||||
|
* @param principal 当前用户
|
||||||
|
* @return 新创建的歌单DTO
|
||||||
|
*/
|
||||||
|
@PostMapping("/{id}/copy")
|
||||||
|
@PreAuthorize("hasRole('USER')")
|
||||||
|
public ResponseEntity<PlaylistDTO> copyPlaylist(@PathVariable Long id, @RequestBody CopyPlaylistRequest request, Principal principal) {
|
||||||
|
PlaylistDTO copiedPlaylist = playlistService.copyPlaylist(id, request.getNewName(), principal.getName());
|
||||||
|
return ResponseEntity.status(HttpStatus.CREATED).body(copiedPlaylist);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Advanced] 按条件筛选歌单
|
||||||
|
*
|
||||||
|
* @param ownerName 歌单拥有者名称(可选)
|
||||||
|
* @param nameKeyword 歌单名称关键词(可选)
|
||||||
|
* @param sortBy 排序字段(name或createdAt)
|
||||||
|
* @param sortDirection 排序方向(asc或desc)
|
||||||
|
* @param principal 当前用户
|
||||||
|
* @return 筛选后的歌单列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/filter")
|
||||||
|
@PreAuthorize("hasRole('USER')")
|
||||||
|
public ResponseEntity<List<PlaylistDTO>> filterPlaylists(
|
||||||
|
@RequestParam(required = false) String ownerName,
|
||||||
|
@RequestParam(required = false) String nameKeyword,
|
||||||
|
@RequestParam(required = false, defaultValue = "name") String sortBy,
|
||||||
|
@RequestParam(required = false, defaultValue = "asc") String sortDirection,
|
||||||
|
Principal principal) {
|
||||||
|
List<PlaylistDTO> filteredPlaylists = playlistService.filterPlaylists(
|
||||||
|
principal.getName(), ownerName, nameKeyword, sortBy, sortDirection);
|
||||||
|
return ResponseEntity.ok(filteredPlaylists);
|
||||||
|
}
|
||||||
|
|
||||||
// DELETE /api/playlists/{playlistId}/songs/{songId} - 移除歌曲(需认证)
|
// DELETE /api/playlists/{playlistId}/songs/{songId} - 移除歌曲(需认证)
|
||||||
@DeleteMapping("/{playlistId}/songs/{songId}")
|
@DeleteMapping("/{playlistId}/songs/{songId}")
|
||||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||||
@ -98,11 +148,5 @@ public class PlaylistController {
|
|||||||
return playlistService.searchPlaylists(keyword);
|
return playlistService.searchPlaylists(keyword);
|
||||||
}
|
}
|
||||||
|
|
||||||
// [Advanced] POST /api/playlists/{id}/copy?newName=xxx - 复制歌单
|
|
||||||
@PostMapping("/{id}/copy")
|
|
||||||
@ResponseStatus(HttpStatus.CREATED)
|
|
||||||
public PlaylistDTO copyPlaylist(@PathVariable Long id, @RequestParam String newName, Authentication authentication) {
|
|
||||||
String username = authentication.getName();
|
|
||||||
return playlistService.copyPlaylist(id, newName, username);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
16
src/main/java/com/vibevault/dto/CopyPlaylistRequest.java
Normal file
16
src/main/java/com/vibevault/dto/CopyPlaylistRequest.java
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package com.vibevault.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制歌单请求DTO
|
||||||
|
*/
|
||||||
|
public class CopyPlaylistRequest {
|
||||||
|
private String newName;
|
||||||
|
|
||||||
|
public String getNewName() {
|
||||||
|
return newName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNewName(String newName) {
|
||||||
|
this.newName = newName;
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/main/java/com/vibevault/exception/ErrorResponse.java
Normal file
41
src/main/java/com/vibevault/exception/ErrorResponse.java
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package com.vibevault.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一错误响应类
|
||||||
|
* 用于封装API错误响应的结构
|
||||||
|
*/
|
||||||
|
public class ErrorResponse {
|
||||||
|
private int status;
|
||||||
|
private String error;
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
public ErrorResponse(int status, String error, String message) {
|
||||||
|
this.status = status;
|
||||||
|
this.error = error;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(int status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getError() {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setError(String error) {
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessage(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,10 +6,6 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
|
|||||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 全局异常处理器
|
* 全局异常处理器
|
||||||
*
|
*
|
||||||
@ -28,14 +24,8 @@ public class GlobalExceptionHandler {
|
|||||||
* @return 包含错误信息的 ResponseEntity
|
* @return 包含错误信息的 ResponseEntity
|
||||||
*/
|
*/
|
||||||
@ExceptionHandler(ResourceNotFoundException.class)
|
@ExceptionHandler(ResourceNotFoundException.class)
|
||||||
public ResponseEntity<Map<String, Object>> handleResourceNotFoundException(ResourceNotFoundException ex) {
|
public ResponseEntity<ErrorResponse> handleResourceNotFoundException(ResourceNotFoundException ex) {
|
||||||
Map<String, Object> body = new HashMap<>();
|
return buildErrorResponse(HttpStatus.NOT_FOUND.value(), "Not Found", ex.getMessage());
|
||||||
body.put("timestamp", LocalDateTime.now());
|
|
||||||
body.put("status", HttpStatus.NOT_FOUND.value());
|
|
||||||
body.put("error", "Not Found");
|
|
||||||
body.put("message", ex.getMessage());
|
|
||||||
|
|
||||||
return new ResponseEntity<>(body, HttpStatus.NOT_FOUND);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,14 +34,18 @@ public class GlobalExceptionHandler {
|
|||||||
* @return 包含错误信息的 ResponseEntity
|
* @return 包含错误信息的 ResponseEntity
|
||||||
*/
|
*/
|
||||||
@ExceptionHandler(UnauthorizedAccessException.class)
|
@ExceptionHandler(UnauthorizedAccessException.class)
|
||||||
public ResponseEntity<Map<String, Object>> handleUnauthorizedAccessException(UnauthorizedAccessException ex) {
|
public ResponseEntity<ErrorResponse> handleUnauthorizedAccessException(UnauthorizedAccessException ex) {
|
||||||
Map<String, Object> body = new HashMap<>();
|
return buildErrorResponse(HttpStatus.FORBIDDEN.value(), "Forbidden", ex.getMessage());
|
||||||
body.put("timestamp", LocalDateTime.now());
|
}
|
||||||
body.put("status", HttpStatus.FORBIDDEN.value());
|
|
||||||
body.put("error", "Forbidden");
|
|
||||||
body.put("message", ex.getMessage());
|
|
||||||
|
|
||||||
return new ResponseEntity<>(body, HttpStatus.FORBIDDEN);
|
/**
|
||||||
|
* 处理 UnauthorizedException 异常
|
||||||
|
* @param ex 异常对象
|
||||||
|
* @return 包含错误信息的 ResponseEntity
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(UnauthorizedException.class)
|
||||||
|
public ResponseEntity<ErrorResponse> handleUnauthorizedException(UnauthorizedException ex) {
|
||||||
|
return buildErrorResponse(HttpStatus.FORBIDDEN.value(), "Forbidden", ex.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,14 +54,8 @@ public class GlobalExceptionHandler {
|
|||||||
* @return 包含错误信息的 ResponseEntity
|
* @return 包含错误信息的 ResponseEntity
|
||||||
*/
|
*/
|
||||||
@ExceptionHandler(ResponseStatusException.class)
|
@ExceptionHandler(ResponseStatusException.class)
|
||||||
public ResponseEntity<Map<String, Object>> handleResponseStatusException(ResponseStatusException ex) {
|
public ResponseEntity<ErrorResponse> handleResponseStatusException(ResponseStatusException ex) {
|
||||||
Map<String, Object> body = new HashMap<>();
|
return buildErrorResponse(ex.getStatusCode().value(), ex.getStatusCode().toString(), ex.getMessage());
|
||||||
body.put("timestamp", LocalDateTime.now());
|
|
||||||
body.put("status", ex.getStatusCode().value());
|
|
||||||
body.put("error", ex.getStatusCode().toString());
|
|
||||||
body.put("message", ex.getMessage());
|
|
||||||
|
|
||||||
return new ResponseEntity<>(body, ex.getStatusCode());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -76,16 +64,19 @@ public class GlobalExceptionHandler {
|
|||||||
* @return 包含错误信息的 ResponseEntity
|
* @return 包含错误信息的 ResponseEntity
|
||||||
*/
|
*/
|
||||||
@ExceptionHandler(Exception.class)
|
@ExceptionHandler(Exception.class)
|
||||||
public ResponseEntity<Map<String, Object>> handleGeneralException(Exception ex) {
|
public ResponseEntity<ErrorResponse> handleGeneralException(Exception ex) {
|
||||||
Map<String, Object> body = new HashMap<>();
|
return buildErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Internal Server Error", "An unexpected error occurred");
|
||||||
body.put("timestamp", LocalDateTime.now());
|
}
|
||||||
body.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
|
|
||||||
body.put("error", "Internal Server Error");
|
|
||||||
body.put("message", "An unexpected error occurred");
|
|
||||||
|
|
||||||
// 在开发环境中可以添加异常堆栈信息
|
/**
|
||||||
// body.put("stackTrace", Arrays.toString(ex.getStackTrace()));
|
* 构建统一的错误响应格式
|
||||||
|
* @param status 响应状态码
|
||||||
return new ResponseEntity<>(body, HttpStatus.INTERNAL_SERVER_ERROR);
|
* @param error 错误类型
|
||||||
|
* @param message 错误消息
|
||||||
|
* @return 包含错误信息的 ResponseEntity
|
||||||
|
*/
|
||||||
|
private ResponseEntity<ErrorResponse> buildErrorResponse(int statusCode, String error, String message) {
|
||||||
|
ErrorResponse errorResponse = new ErrorResponse(statusCode, error, message);
|
||||||
|
return ResponseEntity.status(statusCode).body(errorResponse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
package com.vibevault.model;
|
package com.vibevault.model;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.CreationTimestamp;
|
||||||
|
|
||||||
import jakarta.persistence.CascadeType;
|
import jakarta.persistence.CascadeType;
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
@ -44,6 +47,10 @@ public class Playlist {
|
|||||||
@OneToMany(mappedBy = "playlist", cascade = CascadeType.ALL, orphanRemoval = true)
|
@OneToMany(mappedBy = "playlist", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||||
private List<Song> songs = new ArrayList<>();
|
private List<Song> songs = new ArrayList<>();
|
||||||
|
|
||||||
|
@Column(nullable = false, updatable = false)
|
||||||
|
@CreationTimestamp
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
public Playlist() {
|
public Playlist() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,6 +83,10 @@ public class Playlist {
|
|||||||
return Collections.unmodifiableList(songs);
|
return Collections.unmodifiableList(songs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 向歌单添加歌曲
|
* 向歌单添加歌曲
|
||||||
* 提示:需要维护双向关系
|
* 提示:需要维护双向关系
|
||||||
|
|||||||
@ -1,6 +1,17 @@
|
|||||||
package com.vibevault.model;
|
package com.vibevault.model;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.CreationTimestamp;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 歌曲实体类
|
* 歌曲实体类
|
||||||
@ -31,6 +42,10 @@ public class Song {
|
|||||||
@JoinColumn(name = "playlist_id")
|
@JoinColumn(name = "playlist_id")
|
||||||
private Playlist playlist;
|
private Playlist playlist;
|
||||||
|
|
||||||
|
@Column(nullable = false, updatable = false)
|
||||||
|
@CreationTimestamp
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
public Song() {
|
public Song() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,4 +90,8 @@ public class Song {
|
|||||||
public void setDurationInSeconds(int durationInSeconds) {
|
public void setDurationInSeconds(int durationInSeconds) {
|
||||||
this.durationInSeconds = durationInSeconds;
|
this.durationInSeconds = durationInSeconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
package com.vibevault.repository;
|
package com.vibevault.repository;
|
||||||
|
|
||||||
import com.vibevault.model.Playlist;
|
import java.util.List;
|
||||||
import com.vibevault.model.User;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
import java.util.List;
|
import com.vibevault.model.Playlist;
|
||||||
|
import com.vibevault.model.User;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 歌单仓库接口
|
* 歌单仓库接口
|
||||||
@ -21,6 +22,36 @@ public interface PlaylistRepository extends JpaRepository<Playlist, Long> {
|
|||||||
// 按所有者查询歌单
|
// 按所有者查询歌单
|
||||||
List<Playlist> findByOwner(User owner);
|
List<Playlist> findByOwner(User owner);
|
||||||
|
|
||||||
|
// 按所有者查询歌单并按名称排序(升序)
|
||||||
|
List<Playlist> findByOwnerOrderByNameAsc(User owner);
|
||||||
|
|
||||||
|
// 按所有者查询歌单并按名称排序(降序)
|
||||||
|
List<Playlist> findByOwnerOrderByNameDesc(User owner);
|
||||||
|
|
||||||
|
// 按所有者查询歌单并按创建时间排序(升序)
|
||||||
|
List<Playlist> findByOwnerOrderByCreatedAtAsc(User owner);
|
||||||
|
|
||||||
|
// 按所有者查询歌单并按创建时间排序(降序)
|
||||||
|
List<Playlist> findByOwnerOrderByCreatedAtDesc(User owner);
|
||||||
|
|
||||||
// [Advanced]: 按名称模糊搜索歌单
|
// [Advanced]: 按名称模糊搜索歌单
|
||||||
List<Playlist> findByNameContainingIgnoreCase(String keyword);
|
List<Playlist> findByNameContainingIgnoreCase(String keyword);
|
||||||
|
|
||||||
|
// 按名称模糊搜索歌单并按名称排序
|
||||||
|
List<Playlist> findByNameContainingIgnoreCaseOrderByNameAsc(String keyword);
|
||||||
|
|
||||||
|
// 按所有者和名称模糊搜索歌单
|
||||||
|
List<Playlist> findByOwnerAndNameContainingIgnoreCase(User owner, String keyword);
|
||||||
|
|
||||||
|
// 按所有者和名称模糊搜索歌单并按创建时间排序(升序)
|
||||||
|
List<Playlist> findByOwnerAndNameContainingIgnoreCaseOrderByCreatedAtAsc(User owner, String keyword);
|
||||||
|
|
||||||
|
// 按所有者和名称模糊搜索歌单并按创建时间排序(降序)
|
||||||
|
List<Playlist> findByOwnerAndNameContainingIgnoreCaseOrderByCreatedAtDesc(User owner, String keyword);
|
||||||
|
|
||||||
|
// 按所有者和名称模糊搜索歌单并按名称排序(升序)
|
||||||
|
List<Playlist> findByOwnerAndNameContainingIgnoreCaseOrderByNameAsc(User owner, String keyword);
|
||||||
|
|
||||||
|
// 按所有者和名称模糊搜索歌单并按名称排序(降序)
|
||||||
|
List<Playlist> findByOwnerAndNameContainingIgnoreCaseOrderByNameDesc(User owner, String keyword);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +1,35 @@
|
|||||||
package com.vibevault.repository;
|
package com.vibevault.repository;
|
||||||
|
|
||||||
import com.vibevault.model.Song;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import com.vibevault.model.Song;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 歌曲仓库接口
|
* 歌曲仓库接口
|
||||||
*
|
*
|
||||||
* 基础功能由 JpaRepository 提供
|
* 基础功能由 JpaRepository 提供
|
||||||
|
*
|
||||||
|
* [Advanced] 已添加:
|
||||||
|
* - 按标题关键字模糊搜索歌曲
|
||||||
|
* - 按创建时间排序返回结果
|
||||||
*/
|
*/
|
||||||
@Repository
|
@Repository
|
||||||
public interface SongRepository extends JpaRepository<Song, Long> {
|
public interface SongRepository extends JpaRepository<Song, Long> {
|
||||||
// 这里可以根据需要添加自定义查询方法
|
// 按标题关键字模糊搜索歌曲
|
||||||
|
List<Song> findByTitleContainingIgnoreCase(String keyword);
|
||||||
|
|
||||||
|
// 按标题关键字模糊搜索歌曲并按标题排序
|
||||||
|
List<Song> findByTitleContainingIgnoreCaseOrderByTitleAsc(String keyword);
|
||||||
|
|
||||||
|
// 按歌单ID查询歌曲
|
||||||
|
List<Song> findByPlaylistId(Long playlistId);
|
||||||
|
|
||||||
|
// 按歌单ID查询歌曲并按创建时间排序
|
||||||
|
List<Song> findByPlaylistIdOrderByCreatedAtAsc(Long playlistId);
|
||||||
|
|
||||||
|
// 按歌单ID查询歌曲并按标题排序
|
||||||
|
List<Song> findByPlaylistIdOrderByTitleAsc(Long playlistId);
|
||||||
}
|
}
|
||||||
@ -1,10 +1,10 @@
|
|||||||
package com.vibevault.service;
|
package com.vibevault.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import com.vibevault.dto.PlaylistDTO;
|
import com.vibevault.dto.PlaylistDTO;
|
||||||
import com.vibevault.dto.SongCreateDTO;
|
import com.vibevault.dto.SongCreateDTO;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 歌单服务接口
|
* 歌单服务接口
|
||||||
* 定义歌单相关的业务操作
|
* 定义歌单相关的业务操作
|
||||||
@ -45,6 +45,14 @@ public interface PlaylistService {
|
|||||||
*/
|
*/
|
||||||
void removeSongFromPlaylist(Long playlistId, Long songId, String username);
|
void removeSongFromPlaylist(Long playlistId, Long songId, String username);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量向歌单添加歌曲
|
||||||
|
* @param playlistId 歌单 ID
|
||||||
|
* @param songs 歌曲列表
|
||||||
|
* @param username 当前用户名(用于权限检查)
|
||||||
|
*/
|
||||||
|
PlaylistDTO addSongsToPlaylist(Long playlistId, List<SongCreateDTO> songs, String username);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除歌单
|
* 删除歌单
|
||||||
* @param playlistId 歌单 ID
|
* @param playlistId 歌单 ID
|
||||||
@ -60,7 +68,24 @@ public interface PlaylistService {
|
|||||||
List<PlaylistDTO> searchPlaylists(String keyword);
|
List<PlaylistDTO> searchPlaylists(String keyword);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [Advanced] 复制歌单
|
* [Advanced] 复制一个歌单为新的歌单
|
||||||
|
*
|
||||||
|
* @param playlistId 要复制的歌单ID
|
||||||
|
* @param newName 新歌单名称
|
||||||
|
* @param username 当前用户名
|
||||||
|
* @return 新创建的歌单DTO
|
||||||
*/
|
*/
|
||||||
PlaylistDTO copyPlaylist(Long playlistId, String newName, String username);
|
PlaylistDTO copyPlaylist(Long playlistId, String newName, String username);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Advanced] 按条件筛选歌单
|
||||||
|
*
|
||||||
|
* @param username 用户名
|
||||||
|
* @param ownerName 歌单拥有者名称(可选)
|
||||||
|
* @param nameKeyword 歌单名称关键词(可选)
|
||||||
|
* @param sortBy 排序字段(name或createdAt)
|
||||||
|
* @param sortDirection 排序方向(asc或desc)
|
||||||
|
* @return 筛选后的歌单列表
|
||||||
|
*/
|
||||||
|
List<PlaylistDTO> filterPlaylists(String username, String ownerName, String nameKeyword, String sortBy, String sortDirection);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -91,6 +91,23 @@ public class PlaylistServiceImpl implements PlaylistService {
|
|||||||
playlistRepository.save(playlist);
|
playlistRepository.save(playlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public PlaylistDTO addSongsToPlaylist(Long playlistId, List<SongCreateDTO> songDTOs, String username) {
|
||||||
|
Playlist playlist = getPlaylistWithOwnershipCheck(playlistId, username);
|
||||||
|
|
||||||
|
for (SongCreateDTO songDTO : songDTOs) {
|
||||||
|
Song song = new Song();
|
||||||
|
song.setTitle(songDTO.title());
|
||||||
|
song.setArtist(songDTO.artist());
|
||||||
|
song.setDurationInSeconds(songDTO.durationInSeconds());
|
||||||
|
playlist.addSong(song);
|
||||||
|
}
|
||||||
|
|
||||||
|
Playlist savedPlaylist = playlistRepository.save(playlist);
|
||||||
|
return convertToDTO(savedPlaylist);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void deletePlaylist(Long playlistId, String username) {
|
public void deletePlaylist(Long playlistId, String username) {
|
||||||
@ -110,27 +127,133 @@ public class PlaylistServiceImpl implements PlaylistService {
|
|||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public PlaylistDTO copyPlaylist(Long playlistId, String newName, String username) {
|
public PlaylistDTO copyPlaylist(Long playlistId, String newName, String username) {
|
||||||
Playlist originalPlaylist = playlistRepository.findById(playlistId)
|
// 验证原歌单存在且用户有权限访问
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Playlist not found with id: " + playlistId));
|
Playlist originalPlaylist = getPlaylistWithOwnershipCheck(playlistId, username);
|
||||||
|
|
||||||
User newOwner = userRepository.findByUsername(username)
|
// 获取当前用户
|
||||||
|
User user = userRepository.findByUsername(username)
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("User not found with username: " + username));
|
.orElseThrow(() -> new ResourceNotFoundException("User not found with username: " + username));
|
||||||
|
|
||||||
Playlist copiedPlaylist = new Playlist();
|
// 创建新歌单
|
||||||
copiedPlaylist.setName(newName);
|
Playlist newPlaylist = new Playlist();
|
||||||
copiedPlaylist.setOwner(newOwner);
|
newPlaylist.setName(newName);
|
||||||
|
newPlaylist.setOwner(user);
|
||||||
|
|
||||||
// 复制歌曲
|
// 保存新歌单
|
||||||
for (Song originalSong : originalPlaylist.getSongs()) {
|
Playlist savedPlaylist = playlistRepository.save(newPlaylist);
|
||||||
Song copiedSong = new Song();
|
|
||||||
copiedSong.setTitle(originalSong.getTitle());
|
// 复制原歌单的所有歌曲
|
||||||
copiedSong.setArtist(originalSong.getArtist());
|
List<Song> originalSongs = songRepository.findByPlaylistId(originalPlaylist.getId());
|
||||||
copiedSong.setDurationInSeconds(originalSong.getDurationInSeconds());
|
for (Song originalSong : originalSongs) {
|
||||||
copiedPlaylist.addSong(copiedSong);
|
Song newSong = new Song();
|
||||||
|
newSong.setTitle(originalSong.getTitle());
|
||||||
|
newSong.setArtist(originalSong.getArtist());
|
||||||
|
newSong.setDurationInSeconds(originalSong.getDurationInSeconds());
|
||||||
|
newSong.setPlaylist(savedPlaylist);
|
||||||
|
songRepository.save(newSong);
|
||||||
}
|
}
|
||||||
|
|
||||||
Playlist savedPlaylist = playlistRepository.save(copiedPlaylist);
|
// 重新加载新歌单以包含所有歌曲
|
||||||
return convertToDTO(savedPlaylist);
|
Playlist updatedPlaylist = playlistRepository.findById(savedPlaylist.getId())
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundException("Playlist not found with id: " + savedPlaylist.getId()));
|
||||||
|
|
||||||
|
return convertToDTO(updatedPlaylist);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PlaylistDTO> filterPlaylists(String username, String ownerName, String nameKeyword, String sortBy, String sortDirection) {
|
||||||
|
List<Playlist> playlists;
|
||||||
|
User user = userRepository.findByUsername(username)
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundException("User not found with username: " + username));
|
||||||
|
|
||||||
|
// 如果提供了ownerName,则筛选特定用户的歌单
|
||||||
|
if (ownerName != null) {
|
||||||
|
User owner = userRepository.findByUsername(ownerName)
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundException("User not found with username: " + ownerName));
|
||||||
|
|
||||||
|
// 根据是否提供关键词和排序条件选择不同的查询方法
|
||||||
|
if (nameKeyword != null) {
|
||||||
|
if (sortBy != null && sortBy.equals("createdAt")) {
|
||||||
|
if (sortDirection != null && sortDirection.equals("desc")) {
|
||||||
|
// 按名称关键词筛选并按创建时间降序排序
|
||||||
|
playlists = playlistRepository.findByOwnerAndNameContainingIgnoreCaseOrderByCreatedAtDesc(owner, nameKeyword);
|
||||||
|
} else {
|
||||||
|
// 按名称关键词筛选并按创建时间升序排序
|
||||||
|
playlists = playlistRepository.findByOwnerAndNameContainingIgnoreCaseOrderByCreatedAtAsc(owner, nameKeyword);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (sortDirection != null && sortDirection.equals("desc")) {
|
||||||
|
// 按名称关键词筛选并按名称降序排序
|
||||||
|
playlists = playlistRepository.findByOwnerAndNameContainingIgnoreCaseOrderByNameDesc(owner, nameKeyword);
|
||||||
|
} else {
|
||||||
|
// 按名称关键词筛选并按名称升序排序
|
||||||
|
playlists = playlistRepository.findByOwnerAndNameContainingIgnoreCaseOrderByNameAsc(owner, nameKeyword);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (sortBy != null && sortBy.equals("createdAt")) {
|
||||||
|
if (sortDirection != null && sortDirection.equals("desc")) {
|
||||||
|
// 按创建时间降序排序
|
||||||
|
playlists = playlistRepository.findByOwnerOrderByCreatedAtDesc(owner);
|
||||||
|
} else {
|
||||||
|
// 按创建时间升序排序
|
||||||
|
playlists = playlistRepository.findByOwnerOrderByCreatedAtAsc(owner);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (sortDirection != null && sortDirection.equals("desc")) {
|
||||||
|
// 按名称降序排序
|
||||||
|
playlists = playlistRepository.findByOwnerOrderByNameDesc(owner);
|
||||||
|
} else {
|
||||||
|
// 按名称升序排序
|
||||||
|
playlists = playlistRepository.findByOwnerOrderByNameAsc(owner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 未提供ownerName,则筛选当前用户的歌单
|
||||||
|
if (nameKeyword != null) {
|
||||||
|
if (sortBy != null && sortBy.equals("createdAt")) {
|
||||||
|
if (sortDirection != null && sortDirection.equals("desc")) {
|
||||||
|
// 按名称关键词筛选并按创建时间降序排序
|
||||||
|
playlists = playlistRepository.findByOwnerAndNameContainingIgnoreCaseOrderByCreatedAtDesc(user, nameKeyword);
|
||||||
|
} else {
|
||||||
|
// 按名称关键词筛选并按创建时间升序排序
|
||||||
|
playlists = playlistRepository.findByOwnerAndNameContainingIgnoreCaseOrderByCreatedAtAsc(user, nameKeyword);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (sortDirection != null && sortDirection.equals("desc")) {
|
||||||
|
// 按名称关键词筛选并按名称降序排序
|
||||||
|
playlists = playlistRepository.findByOwnerAndNameContainingIgnoreCaseOrderByNameDesc(user, nameKeyword);
|
||||||
|
} else {
|
||||||
|
// 按名称关键词筛选并按名称升序排序
|
||||||
|
playlists = playlistRepository.findByOwnerAndNameContainingIgnoreCaseOrderByNameAsc(user, nameKeyword);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (sortBy != null && sortBy.equals("createdAt")) {
|
||||||
|
if (sortDirection != null && sortDirection.equals("desc")) {
|
||||||
|
// 按创建时间降序排序
|
||||||
|
playlists = playlistRepository.findByOwnerOrderByCreatedAtDesc(user);
|
||||||
|
} else {
|
||||||
|
// 按创建时间升序排序
|
||||||
|
playlists = playlistRepository.findByOwnerOrderByCreatedAtAsc(user);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (sortDirection != null && sortDirection.equals("desc")) {
|
||||||
|
// 按名称降序排序
|
||||||
|
playlists = playlistRepository.findByOwnerOrderByNameDesc(user);
|
||||||
|
} else {
|
||||||
|
// 按名称升序排序
|
||||||
|
playlists = playlistRepository.findByOwnerOrderByNameAsc(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为DTO并返回
|
||||||
|
return playlists.stream()
|
||||||
|
.map(this::convertToDTO)
|
||||||
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 辅助方法:检查歌单所有权
|
// 辅助方法:检查歌单所有权
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user