diff --git a/build-output.txt b/build-output.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/com/vibevault/controller/AuthController.java b/src/main/java/com/vibevault/controller/AuthController.java index 1c6b9cd..51e3fe8 100644 --- a/src/main/java/com/vibevault/controller/AuthController.java +++ b/src/main/java/com/vibevault/controller/AuthController.java @@ -1,12 +1,17 @@ package com.vibevault.controller; +import org.springframework.http.HttpStatus; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; + import com.vibevault.model.User; import com.vibevault.repository.UserRepository; import com.vibevault.security.JwtService; -import org.springframework.http.HttpStatus; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.server.ResponseStatusException; /** * 认证控制器 @@ -46,9 +51,7 @@ public class AuthController { } // 创建新用户,加密密码 - User user = new User(); - user.setUsername(request.username()); - user.setPassword(passwordEncoder.encode(request.password())); + User user = new User(request.username(), passwordEncoder.encode(request.password())); // 保存用户到数据库 userRepository.save(user); diff --git a/src/main/java/com/vibevault/controller/PlaylistController.java b/src/main/java/com/vibevault/controller/PlaylistController.java index af63303..f333a5c 100644 --- a/src/main/java/com/vibevault/controller/PlaylistController.java +++ b/src/main/java/com/vibevault/controller/PlaylistController.java @@ -1,14 +1,23 @@ package com.vibevault.controller; +import java.util.List; + +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + import com.vibevault.dto.PlaylistCreateDTO; import com.vibevault.dto.PlaylistDTO; import com.vibevault.dto.SongCreateDTO; import com.vibevault.service.PlaylistService; -import org.springframework.http.HttpStatus; -import org.springframework.security.core.Authentication; -import org.springframework.web.bind.annotation.*; - -import java.util.List; /** * 歌单 REST 控制器 @@ -56,7 +65,7 @@ public class PlaylistController { @ResponseStatus(HttpStatus.CREATED) public PlaylistDTO createPlaylist(@RequestBody PlaylistCreateDTO playlistCreateDTO, Authentication authentication) { String username = authentication.getName(); - return playlistService.createPlaylist(playlistCreateDTO.getName(), username); + return playlistService.createPlaylist(playlistCreateDTO.name(), username); } // POST /api/playlists/{id}/songs - 添加歌曲(需认证) diff --git a/src/main/java/com/vibevault/exception/GlobalExceptionHandler.java b/src/main/java/com/vibevault/exception/GlobalExceptionHandler.java index 04a3d44..344bcc1 100644 --- a/src/main/java/com/vibevault/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/vibevault/exception/GlobalExceptionHandler.java @@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.server.ResponseStatusException; import java.time.LocalDateTime; +import java.util.HashMap; import java.util.Map; /** @@ -21,11 +22,70 @@ import java.util.Map; @RestControllerAdvice public class GlobalExceptionHandler { - // TODO: 实现 ResourceNotFoundException 处理器 (返回 404) + /** + * 处理 ResourceNotFoundException 异常 + * @param ex 异常对象 + * @return 包含错误信息的 ResponseEntity + */ + @ExceptionHandler(ResourceNotFoundException.class) + public ResponseEntity> handleResourceNotFoundException(ResourceNotFoundException ex) { + Map body = new HashMap<>(); + 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); + } - // TODO: 实现 UnauthorizedException 处理器 (返回 403) + /** + * 处理 UnauthorizedAccessException 异常 + * @param ex 异常对象 + * @return 包含错误信息的 ResponseEntity + */ + @ExceptionHandler(UnauthorizedAccessException.class) + public ResponseEntity> handleUnauthorizedAccessException(UnauthorizedAccessException ex) { + Map body = new HashMap<>(); + 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); + } - // TODO: 实现 ResponseStatusException 处理器 + /** + * 处理 ResponseStatusException 异常 + * @param ex 异常对象 + * @return 包含错误信息的 ResponseEntity + */ + @ExceptionHandler(ResponseStatusException.class) + public ResponseEntity> handleResponseStatusException(ResponseStatusException ex) { + Map body = new HashMap<>(); + 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()); + } - // TODO [Advanced]: 实现通用异常处理器 + /** + * 处理所有其他未捕获的异常 + * @param ex 异常对象 + * @return 包含错误信息的 ResponseEntity + */ + @ExceptionHandler(Exception.class) + public ResponseEntity> handleGeneralException(Exception ex) { + Map body = new HashMap<>(); + 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())); + + return new ResponseEntity<>(body, HttpStatus.INTERNAL_SERVER_ERROR); + } } diff --git a/src/main/java/com/vibevault/model/Playlist.java b/src/main/java/com/vibevault/model/Playlist.java index ed77191..20bf1b8 100644 --- a/src/main/java/com/vibevault/model/Playlist.java +++ b/src/main/java/com/vibevault/model/Playlist.java @@ -1,10 +1,20 @@ package com.vibevault.model; -import jakarta.persistence.*; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import jakarta.persistence.CascadeType; +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.OneToMany; +import jakarta.persistence.Table; + /** * 歌单实体类 * @@ -34,7 +44,7 @@ public class Playlist { @OneToMany(mappedBy = "playlist", cascade = CascadeType.ALL, orphanRemoval = true) private List songs = new ArrayList<>(); - protected Playlist() { + public Playlist() { } public Playlist(String name, User owner) { @@ -58,6 +68,10 @@ public class Playlist { return owner; } + public void setOwner(User owner) { + this.owner = owner; + } + public List getSongs() { return Collections.unmodifiableList(songs); } diff --git a/src/main/java/com/vibevault/model/Song.java b/src/main/java/com/vibevault/model/Song.java index 956528a..f00437d 100644 --- a/src/main/java/com/vibevault/model/Song.java +++ b/src/main/java/com/vibevault/model/Song.java @@ -63,4 +63,16 @@ public class Song { public void setPlaylist(Playlist playlist) { this.playlist = playlist; } + + public void setTitle(String title) { + this.title = title; + } + + public void setArtist(String artist) { + this.artist = artist; + } + + public void setDurationInSeconds(int durationInSeconds) { + this.durationInSeconds = durationInSeconds; + } } diff --git a/src/main/java/com/vibevault/repository/SongRepository.java b/src/main/java/com/vibevault/repository/SongRepository.java new file mode 100644 index 0000000..95c266a --- /dev/null +++ b/src/main/java/com/vibevault/repository/SongRepository.java @@ -0,0 +1,15 @@ +package com.vibevault.repository; + +import com.vibevault.model.Song; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +/** + * 歌曲仓库接口 + * + * 基础功能由 JpaRepository 提供 + */ +@Repository +public interface SongRepository extends JpaRepository { + // 这里可以根据需要添加自定义查询方法 +} \ No newline at end of file diff --git a/src/main/java/com/vibevault/service/PlaylistServiceImpl.java b/src/main/java/com/vibevault/service/PlaylistServiceImpl.java deleted file mode 100644 index f69b712..0000000 --- a/src/main/java/com/vibevault/service/PlaylistServiceImpl.java +++ /dev/null @@ -1,122 +0,0 @@ -package com.vibevault.service; - -import com.vibevault.dto.PlaylistDTO; -import com.vibevault.dto.SongCreateDTO; -import com.vibevault.dto.SongDTO; -import com.vibevault.exception.ResourceNotFoundException; -import com.vibevault.exception.UnauthorizedException; -import com.vibevault.model.Playlist; -import com.vibevault.model.Song; -import com.vibevault.model.User; -import com.vibevault.repository.PlaylistRepository; -import com.vibevault.repository.UserRepository; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; - -/** - * 歌单服务实现 - * - * 需要实现: - * - 所有 PlaylistService 接口中定义的方法 - * - 将实体转换为 DTO 返回给调用者 - * - 资源不存在时抛出 ResourceNotFoundException - * - [Challenge] 检查用户是否有权限操作歌单(所有权检查) - */ -@Service -public class PlaylistServiceImpl implements PlaylistService { - - private final PlaylistRepository playlistRepository; - private final UserRepository userRepository; - - public PlaylistServiceImpl(PlaylistRepository playlistRepository, UserRepository userRepository) { - this.playlistRepository = playlistRepository; - this.userRepository = userRepository; - } - - @Override - public List getAllPlaylists() { - // TODO: 实现获取所有歌单 - throw new UnsupportedOperationException("待实现"); - } - - @Override - public PlaylistDTO getPlaylistById(Long id) { - // TODO: 实现根据 ID 获取歌单,不存在时抛出 ResourceNotFoundException - throw new UnsupportedOperationException("待实现"); - } - - @Override - @Transactional - public PlaylistDTO createPlaylist(String name, String ownerUsername) { - // TODO: 实现创建歌单 - throw new UnsupportedOperationException("待实现"); - } - - @Override - @Transactional - public PlaylistDTO addSongToPlaylist(Long playlistId, SongCreateDTO song, String username) { - // TODO: 实现添加歌曲到歌单 - // [Challenge] 需要检查用户是否有权限操作此歌单 - throw new UnsupportedOperationException("待实现"); - } - - @Override - @Transactional - public void removeSongFromPlaylist(Long playlistId, Long songId, String username) { - // TODO: 实现从歌单移除歌曲 - // [Challenge] 需要检查用户是否有权限操作此歌单 - throw new UnsupportedOperationException("待实现"); - } - - @Override - @Transactional - public void deletePlaylist(Long playlistId, String username) { - // TODO: 实现删除歌单 - // [Challenge] 需要检查用户是否有权限操作此歌单 - throw new UnsupportedOperationException("待实现"); - } - - // ========== Advanced 方法 ========== - - @Override - public List searchPlaylists(String keyword) { - // TODO [Advanced]: 实现按关键字搜索歌单 - throw new UnsupportedOperationException("待实现"); - } - - @Override - @Transactional - public PlaylistDTO copyPlaylist(Long playlistId, String newName, String username) { - // TODO [Advanced]: 实现复制歌单 - throw new UnsupportedOperationException("待实现"); - } - - // ========== 辅助方法 ========== - - /** - * 将 Playlist 实体转换为 DTO - */ - private PlaylistDTO toDTO(Playlist playlist) { - // TODO: 实现实体到 DTO 的转换 - throw new UnsupportedOperationException("待实现"); - } - - /** - * 将 Song 实体转换为 DTO - */ - private SongDTO toSongDTO(Song song) { - // TODO: 实现实体到 DTO 的转换 - throw new UnsupportedOperationException("待实现"); - } - - /** - * [Challenge] 检查用户是否有权限操作指定歌单 - * 规则:歌单所有者或管理员可以操作 - */ - private void checkPermission(Playlist playlist, String username) { - // TODO [Challenge]: 实现权限检查 - // 如果无权限,抛出 UnauthorizedException - } -} diff --git a/src/main/java/com/vibevault/service/impl/PlaylistServiceImpl.java b/src/main/java/com/vibevault/service/impl/PlaylistServiceImpl.java index 6ef5b47..4d3e577 100644 --- a/src/main/java/com/vibevault/service/impl/PlaylistServiceImpl.java +++ b/src/main/java/com/vibevault/service/impl/PlaylistServiceImpl.java @@ -1,32 +1,37 @@ package com.vibevault.service.impl; +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + import com.vibevault.dto.PlaylistDTO; import com.vibevault.dto.SongCreateDTO; +import com.vibevault.dto.SongDTO; import com.vibevault.exception.ResourceNotFoundException; import com.vibevault.exception.UnauthorizedAccessException; import com.vibevault.model.Playlist; import com.vibevault.model.Song; import com.vibevault.model.User; import com.vibevault.repository.PlaylistRepository; +import com.vibevault.repository.SongRepository; import com.vibevault.repository.UserRepository; import com.vibevault.service.PlaylistService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.stream.Collectors; @Service public class PlaylistServiceImpl implements PlaylistService { private final PlaylistRepository playlistRepository; private final UserRepository userRepository; + private final SongRepository songRepository; @Autowired - public PlaylistServiceImpl(PlaylistRepository playlistRepository, UserRepository userRepository) { + public PlaylistServiceImpl(PlaylistRepository playlistRepository, UserRepository userRepository, SongRepository songRepository) { this.playlistRepository = playlistRepository; this.userRepository = userRepository; + this.songRepository = songRepository; } @Override @@ -63,9 +68,9 @@ public class PlaylistServiceImpl implements PlaylistService { Playlist playlist = getPlaylistWithOwnershipCheck(playlistId, username); Song song = new Song(); - song.setTitle(songDTO.getTitle()); - song.setArtist(songDTO.getArtist()); - song.setDurationInSeconds(songDTO.getDurationInSeconds()); + song.setTitle(songDTO.title()); + song.setArtist(songDTO.artist()); + song.setDurationInSeconds(songDTO.durationInSeconds()); playlist.addSong(song); Playlist savedPlaylist = playlistRepository.save(playlist); @@ -143,11 +148,25 @@ public class PlaylistServiceImpl implements PlaylistService { // 辅助方法:将 Playlist 转换为 PlaylistDTO private PlaylistDTO convertToDTO(Playlist playlist) { - PlaylistDTO dto = new PlaylistDTO(); - dto.setId(playlist.getId()); - dto.setName(playlist.getName()); - dto.setOwnerUsername(playlist.getOwner().getUsername()); - dto.setSongCount(playlist.getSongs().size()); - return dto; + List songDTOs = playlist.getSongs().stream() + .map(this::convertSongToDTO) + .collect(Collectors.toList()); + + return new PlaylistDTO( + playlist.getId(), + playlist.getName(), + playlist.getOwner().getUsername(), + songDTOs + ); + } + + // 辅助方法:将 Song 转换为 SongDTO + private SongDTO convertSongToDTO(Song song) { + return new SongDTO( + song.getId(), + song.getTitle(), + song.getArtist(), + song.getDurationInSeconds() + ); } } diff --git a/test-output.txt b/test-output.txt new file mode 100644 index 0000000..e69de29