完成作业
Some checks failed
autograde-final-vibevault / check-trigger (push) Successful in 13s
autograde-final-vibevault / grade (push) Failing after 49s

This commit is contained in:
liyitian 2025-12-14 15:54:00 +08:00
parent 2f17411b57
commit a120406357
12 changed files with 396 additions and 65 deletions

View File

@ -39,13 +39,33 @@ public class SecurityConfig {
@Bean @Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// TODO: 配置安全规则 http
// 提示 // 1. 禁用 CSRFREST API 通常不需要
// - 使用 http.authorizeHttpRequests() 配置路径权限 .csrf(csrf -> csrf.disable())
// - 使用 http.csrf(csrf -> csrf.disable()) 禁用 CSRF
// - 使用 http.sessionManagement() 配置无状态会话 // 2. 配置无状态会话
// - 使用 http.exceptionHandling() 配置 401 响应 .sessionManagement(session -> session
// - 使用 http.addFilterBefore() 添加 JWT 过滤器 .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 3. 配置路径权限
.authorizeHttpRequests(authorize -> authorize
// 公开接口无需认证
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers(HttpMethod.GET, "/api/playlists").permitAll()
.requestMatchers(HttpMethod.GET, "/api/playlists/{id}").permitAll()
// 其他接口需要认证
.anyRequest().authenticated())
// 4. 配置异常处理未认证返回 401
.exceptionHandling(exception -> exception
.authenticationEntryPoint((request, response, authException) -> {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.getWriter().write("{\"error\":\"Unauthorized\",\"message\":\"Authentication required\"}");
}))
// 5. 添加 JWT 过滤器 UsernamePasswordAuthenticationFilter 之前执行
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build(); return http.build();
} }

View File

@ -36,9 +36,45 @@ public class AuthController {
this.jwtService = jwtService; this.jwtService = jwtService;
} }
// TODO: 实现 POST /api/auth/register (状态码 201) @PostMapping("/register")
@ResponseStatus(HttpStatus.CREATED)
public RegisterResponse register(@RequestBody RegisterRequest request) {
// 检查用户名是否已存在
if (userRepository.existsByUsername(request.username())) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "Username already exists");
}
// 加密密码
String encodedPassword = passwordEncoder.encode(request.password());
// 创建新用户
User user = new User();
user.setUsername(request.username());
user.setPassword(encodedPassword);
user.setRole("ROLE_USER"); // 默认角色
// 保存用户
userRepository.save(user);
return new RegisterResponse("User registered successfully", request.username());
}
// TODO: 实现 POST /api/auth/login @PostMapping("/login")
public LoginResponse login(@RequestBody LoginRequest request) {
// 查找用户
User user = userRepository.findByUsername(request.username())
.orElseThrow(() -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid credentials"));
// 验证密码
if (!passwordEncoder.matches(request.password(), user.getPassword())) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid credentials");
}
// 生成JWT token
String token = jwtService.generateToken(user);
return new LoginResponse(token, user.getUsername());
}
} }
/** /**

View File

@ -5,8 +5,10 @@ import com.vibevault.dto.PlaylistDTO;
import com.vibevault.dto.SongCreateDTO; import com.vibevault.dto.SongCreateDTO;
import com.vibevault.service.PlaylistService; import com.vibevault.service.PlaylistService;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;
import java.util.List; import java.util.List;
@ -39,19 +41,58 @@ public class PlaylistController {
this.playlistService = playlistService; this.playlistService = playlistService;
} }
// TODO: 实现 GET /api/playlists @GetMapping
public ResponseEntity<List<PlaylistDTO>> getAllPlaylists() {
List<PlaylistDTO> playlists = playlistService.getAllPlaylists();
return ResponseEntity.ok(playlists);
}
// TODO: 实现 GET /api/playlists/{id} @GetMapping("/{id}")
public ResponseEntity<PlaylistDTO> getPlaylistById(@PathVariable Long id) {
PlaylistDTO playlist = playlistService.getPlaylistById(id);
return ResponseEntity.ok(playlist);
}
// TODO: 实现 POST /api/playlists (状态码 201) @PostMapping
public ResponseEntity<PlaylistDTO> createPlaylist(@Valid @RequestBody PlaylistCreateDTO playlistDTO, Authentication authentication) {
String username = authentication.getName();
PlaylistDTO createdPlaylist = playlistService.createPlaylist(playlistDTO.name(), username);
return ResponseEntity.status(HttpStatus.CREATED).body(createdPlaylist);
}
// TODO: 实现 POST /api/playlists/{id}/songs (状态码 201) @PostMapping("/{id}/songs")
public ResponseEntity<PlaylistDTO> addSong(@PathVariable Long id, @Valid @RequestBody SongCreateDTO songDTO, Authentication authentication) {
String username = authentication.getName();
PlaylistDTO updatedPlaylist = playlistService.addSongToPlaylist(id, songDTO, username);
return ResponseEntity.status(HttpStatus.CREATED).body(updatedPlaylist);
}
// TODO: 实现 DELETE /api/playlists/{playlistId}/songs/{songId} (状态码 204) @DeleteMapping("/{playlistId}/songs/{songId}")
public ResponseEntity<Void> removeSong(@PathVariable Long playlistId, @PathVariable Long songId, Authentication authentication) {
String username = authentication.getName();
playlistService.removeSongFromPlaylist(playlistId, songId, username);
return ResponseEntity.noContent().build();
}
// TODO: 实现 DELETE /api/playlists/{id} (状态码 204) @DeleteMapping("/{id}")
public ResponseEntity<Void> deletePlaylist(@PathVariable Long id, Authentication authentication) {
String username = authentication.getName();
playlistService.deletePlaylist(id, username);
return ResponseEntity.noContent().build();
}
// TODO [Advanced]: 实现 GET /api/playlists/search?keyword=xxx // ========== Advanced 轨道进阶 ==========
// TODO [Advanced]: 实现 POST /api/playlists/{id}/copy?newName=xxx (状态码 201) @GetMapping("/search")
public ResponseEntity<List<PlaylistDTO>> searchPlaylists(@RequestParam String keyword) {
List<PlaylistDTO> playlists = playlistService.searchPlaylists(keyword);
return ResponseEntity.ok(playlists);
}
@PostMapping("/{id}/copy")
public ResponseEntity<PlaylistDTO> copyPlaylist(@PathVariable Long id, @RequestParam String newName, Authentication authentication) {
String username = authentication.getName();
PlaylistDTO copiedPlaylist = playlistService.copyPlaylist(id, newName, username);
return ResponseEntity.status(HttpStatus.CREATED).body(copiedPlaylist);
}
} }

View File

@ -1,6 +1,7 @@
package com.vibevault.model; package com.vibevault.model;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotEmpty;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -16,14 +17,23 @@ import java.util.List;
* - 一个歌单包含多首歌曲一对多关系 * - 一个歌单包含多首歌曲一对多关系
* - 删除歌单时应级联删除其中的歌曲 * - 删除歌单时应级联删除其中的歌曲
*/ */
@Entity
@Table(name = "playlists")
public class Playlist { public class Playlist {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private Long id;
@Column(nullable = false)
@NotEmpty(message = "歌单名称不能为空")
private String name; private String name;
@ManyToOne
@JoinColumn(name = "owner_id", nullable = false)
private User owner; private User owner;
@OneToMany(mappedBy = "playlist", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Song> songs = new ArrayList<>(); private List<Song> songs = new ArrayList<>();
protected Playlist() { protected Playlist() {
@ -45,6 +55,14 @@ public class Playlist {
public void setName(String name) { public void setName(String name) {
this.name = name; this.name = name;
} }
public void setOwner(User owner) {
this.owner = owner;
}
public void setId(Long id) {
this.id = id;
}
public User getOwner() { public User getOwner() {
return owner; return owner;
@ -59,7 +77,8 @@ public class Playlist {
* 提示需要维护双向关系 * 提示需要维护双向关系
*/ */
public void addSong(Song song) { public void addSong(Song song) {
// TODO: 实现添加歌曲逻辑 songs.add(song);
song.setPlaylist(this);
} }
/** /**
@ -67,6 +86,7 @@ public class Playlist {
* 提示需要维护双向关系 * 提示需要维护双向关系
*/ */
public void removeSong(Song song) { public void removeSong(Song song) {
// TODO: 实现移除歌曲逻辑 songs.remove(song);
song.setPlaylist(null);
} }
} }

View File

@ -1,6 +1,8 @@
package com.vibevault.model; package com.vibevault.model;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Positive;
/** /**
* 歌曲实体类 * 歌曲实体类
@ -10,16 +12,28 @@ import jakarta.persistence.*;
* - id 作为自增主键 * - id 作为自增主键
* - 每首歌曲属于一个歌单多对一关系 * - 每首歌曲属于一个歌单多对一关系
*/ */
@Entity
@Table(name = "songs")
public class Song { public class Song {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private Long id;
@Column(nullable = false)
@NotEmpty(message = "歌曲标题不能为空")
private String title; private String title;
@Column(nullable = false)
@NotEmpty(message = "歌曲艺术家不能为空")
private String artist; private String artist;
@Column(nullable = false)
@Positive(message = "歌曲时长必须为正数")
private int durationInSeconds; private int durationInSeconds;
@ManyToOne
@JoinColumn(name = "playlist_id", nullable = false)
private Playlist playlist; private Playlist playlist;
public Song() { public Song() {
@ -54,4 +68,20 @@ public class Song {
public void setPlaylist(Playlist playlist) { public void setPlaylist(Playlist playlist) {
this.playlist = playlist; this.playlist = playlist;
} }
public void setId(Long id) {
this.id = id;
}
public void setTitle(String title) {
this.title = title;
}
public void setArtist(String artist) {
this.artist = artist;
}
public void setDurationInSeconds(int durationInSeconds) {
this.durationInSeconds = durationInSeconds;
}
} }

View File

@ -1,6 +1,13 @@
package com.vibevault.model; package com.vibevault.model;
import jakarta.persistence.*; import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import jakarta.validation.constraints.NotEmpty;
/** /**
* 用户实体类 * 用户实体类
@ -12,18 +19,29 @@ import jakarta.persistence.*;
* - password 不能为空 * - password 不能为空
* - [Challenge] 支持用户角色 ROLE_USER, ROLE_ADMIN * - [Challenge] 支持用户角色 ROLE_USER, ROLE_ADMIN
*/ */
@Entity
@Table(name = "users", uniqueConstraints = {@UniqueConstraint(columnNames = "username")})
public class User { public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private Long id;
@Column(nullable = false, unique = true)
@NotEmpty(message = "用户名不能为空")
private String username; private String username;
@Column(nullable = false)
@NotEmpty(message = "密码不能为空")
private String password; private String password;
// [Challenge] 用户角色默认为 ROLE_USER // [Challenge] 用户角色默认为 ROLE_USER
@Column(nullable = false)
private String role = "ROLE_USER"; private String role = "ROLE_USER";
protected User() { public User() {
// JPA 要求的无参构造函数
this.role = "ROLE_USER";
} }
public User(String username, String password) { public User(String username, String password) {
@ -56,4 +74,16 @@ public class User {
public void setRole(String role) { public void setRole(String role) {
this.role = role; this.role = role;
} }
public void setPassword(String password) {
this.password = password;
}
public void setUsername(String username) {
this.username = username;
}
public void setId(Long id) {
this.id = id;
}
} }

View File

@ -18,5 +18,9 @@ import java.util.List;
*/ */
@Repository @Repository
public interface PlaylistRepository extends JpaRepository<Playlist, Long> { public interface PlaylistRepository extends JpaRepository<Playlist, Long> {
// TODO [Advanced]: 添加高级查询方法 // [Advanced] 按所有者查询歌单
List<Playlist> findByOwner(User owner);
// [Advanced] 按名称模糊搜索歌单
List<Playlist> findByNameContainingIgnoreCase(String keyword);
} }

View File

@ -15,5 +15,9 @@ import java.util.Optional;
*/ */
@Repository @Repository
public interface UserRepository extends JpaRepository<User, Long> { public interface UserRepository extends JpaRepository<User, Long> {
// TODO: 添加必要的查询方法 // 根据用户名查找用户
Optional<User> findByUsername(String username);
// 检查用户名是否已存在
boolean existsByUsername(String username);
} }

View File

@ -45,17 +45,45 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
@NonNull FilterChain filterChain @NonNull FilterChain filterChain
) throws ServletException, IOException { ) throws ServletException, IOException {
// TODO: 实现 JWT 认证逻辑
// 1. 从请求头获取 Authorization // 1. 从请求头获取 Authorization
String authorizationHeader = request.getHeader("Authorization");
String token = null;
String username = null;
// 2. 检查是否以 "Bearer " 开头 // 2. 检查是否以 "Bearer " 开头
// 3. 提取 token 并验证 if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
token = authorizationHeader.substring(7);
try {
// 3. 提取用户名
username = jwtService.extractUsername(token);
} catch (Exception e) {
// 解析失败继续过滤器链
filterChain.doFilter(request, response);
return;
}
}
// 4. 如果有效创建 Authentication 并设置到 SecurityContextHolder // 4. 如果有效创建 Authentication 并设置到 SecurityContextHolder
// if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// 提示 User user = userRepository.findByUsername(username).orElse(null);
// - 使用 request.getHeader("Authorization") 获取头
// - 使用 jwtService.extractUsername() jwtService.isTokenValid() if (user != null && jwtService.isTokenValid(token, username)) {
// - 使用 UsernamePasswordAuthenticationToken 创建认证对象 // [Challenge] 获取用户角色
// - 使用 SecurityContextHolder.getContext().setAuthentication() 设置 List<SimpleGrantedAuthority> authorities = Collections.singletonList(
new SimpleGrantedAuthority(user.getRole())
);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
username,
null,
authorities
);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
} }

View File

@ -1,5 +1,6 @@
package com.vibevault.security; package com.vibevault.security;
import com.vibevault.model.User;
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@ -29,27 +30,53 @@ public class JwtService {
/** /**
* 为用户生成 JWT token * 为用户生成 JWT token
*/ */
public String generateToken(String username) { public String generateToken(User user) {
// TODO: 实现 token 生成 Date now = new Date();
// 提示使用 Jwts.builder() Date expiryDate = new Date(now.getTime() + expiration);
throw new UnsupportedOperationException("待实现");
return Jwts.builder()
.subject(user.getUsername())
.issuedAt(now)
.expiration(expiryDate)
.signWith(getSigningKey())
.compact();
} }
/** /**
* token 中提取用户名 * token 中提取用户名
*/ */
public String extractUsername(String token) { public String extractUsername(String token) {
// TODO: 实现用户名提取 return Jwts.parser()
// 提示使用 Jwts.parser() .verifyWith(getSigningKey())
throw new UnsupportedOperationException("待实现"); .build()
.parseSignedClaims(token)
.getPayload()
.getSubject();
} }
/** /**
* 验证 token 是否有效 * 验证 token 是否有效
*/ */
public boolean isTokenValid(String token, String username) { public boolean isTokenValid(String token, String username) {
// TODO: 实现 token 验证 String extractedUsername = extractUsername(token);
throw new UnsupportedOperationException("待实现"); boolean isUsernameMatch = extractedUsername.equals(username);
boolean isExpired = isTokenExpired(token);
return isUsernameMatch && !isExpired;
}
/**
* 检查 token 是否已过期
*/
private boolean isTokenExpired(String token) {
Date expiration = Jwts.parser()
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token)
.getPayload()
.getExpiration();
return expiration.before(new Date());
} }
/** /**

View File

@ -59,6 +59,11 @@ public interface PlaylistService {
*/ */
List<PlaylistDTO> searchPlaylists(String keyword); List<PlaylistDTO> searchPlaylists(String keyword);
/**
* [Advanced] 按所有者查询歌单
*/
List<PlaylistDTO> getPlaylistsByOwner(String username);
/** /**
* [Advanced] 复制歌单 * [Advanced] 复制歌单
*/ */

View File

@ -37,60 +37,128 @@ public class PlaylistServiceImpl implements PlaylistService {
@Override @Override
public List<PlaylistDTO> getAllPlaylists() { public List<PlaylistDTO> getAllPlaylists() {
// TODO: 实现获取所有歌单 return playlistRepository.findAll()
throw new UnsupportedOperationException("待实现"); .stream()
.map(this::toDTO)
.toList();
} }
@Override @Override
public PlaylistDTO getPlaylistById(Long id) { public PlaylistDTO getPlaylistById(Long id) {
// TODO: 实现根据 ID 获取歌单不存在时抛出 ResourceNotFoundException Playlist playlist = playlistRepository.findById(id)
throw new UnsupportedOperationException("待实现"); .orElseThrow(() -> new ResourceNotFoundException("Playlist not found with id: " + id));
return toDTO(playlist);
} }
@Override @Override
@Transactional @Transactional
public PlaylistDTO createPlaylist(String name, String ownerUsername) { public PlaylistDTO createPlaylist(String name, String ownerUsername) {
// TODO: 实现创建歌单 User owner = userRepository.findByUsername(ownerUsername)
throw new UnsupportedOperationException("待实现"); .orElseThrow(() -> new ResourceNotFoundException("User not found: " + ownerUsername));
Playlist playlist = new Playlist(name, owner);
Playlist savedPlaylist = playlistRepository.save(playlist);
return toDTO(savedPlaylist);
} }
@Override @Override
@Transactional @Transactional
public PlaylistDTO addSongToPlaylist(Long playlistId, SongCreateDTO song, String username) { public PlaylistDTO addSongToPlaylist(Long playlistId, SongCreateDTO song, String username) {
// TODO: 实现添加歌曲到歌单 Playlist playlist = playlistRepository.findById(playlistId)
// [Challenge] 需要检查用户是否有权限操作此歌单 .orElseThrow(() -> new ResourceNotFoundException("Playlist not found with id: " + playlistId));
throw new UnsupportedOperationException("待实现");
// [Challenge] 检查用户是否有权限操作此歌单
checkPermission(playlist, username);
Song newSong = new Song(song.title(), song.artist(), song.durationInSeconds());
playlist.addSong(newSong);
Playlist updatedPlaylist = playlistRepository.save(playlist);
return toDTO(updatedPlaylist);
} }
@Override @Override
@Transactional @Transactional
public void removeSongFromPlaylist(Long playlistId, Long songId, String username) { public void removeSongFromPlaylist(Long playlistId, Long songId, String username) {
// TODO: 实现从歌单移除歌曲 Playlist playlist = playlistRepository.findById(playlistId)
// [Challenge] 需要检查用户是否有权限操作此歌单 .orElseThrow(() -> new ResourceNotFoundException("Playlist not found with id: " + playlistId));
throw new UnsupportedOperationException("待实现");
// [Challenge] 检查用户是否有权限操作此歌单
checkPermission(playlist, username);
Song song = playlist.getSongs().stream()
.filter(s -> s.getId().equals(songId))
.findFirst()
.orElseThrow(() -> new ResourceNotFoundException("Song not found with id: " + songId));
playlist.removeSong(song);
playlistRepository.save(playlist);
} }
@Override @Override
@Transactional @Transactional
public void deletePlaylist(Long playlistId, String username) { public void deletePlaylist(Long playlistId, String username) {
// TODO: 实现删除歌单 Playlist playlist = playlistRepository.findById(playlistId)
// [Challenge] 需要检查用户是否有权限操作此歌单 .orElseThrow(() -> new ResourceNotFoundException("Playlist not found with id: " + playlistId));
throw new UnsupportedOperationException("待实现");
// [Challenge] 检查用户是否有权限操作此歌单
checkPermission(playlist, username);
playlistRepository.delete(playlist);
} }
// ========== Advanced 方法 ========== // ========== Advanced 方法 ==========
@Override @Override
@Transactional(readOnly = true)
public List<PlaylistDTO> getPlaylistsByOwner(String username) {
User owner = userRepository.findByUsername(username)
.orElseThrow(() -> new ResourceNotFoundException("User not found: " + username));
return playlistRepository.findByOwner(owner)
.stream()
.map(this::toDTO)
.toList();
}
@Override
@Transactional(readOnly = true)
public List<PlaylistDTO> searchPlaylists(String keyword) { public List<PlaylistDTO> searchPlaylists(String keyword) {
// TODO [Advanced]: 实现按关键字搜索歌单 return playlistRepository.findByNameContainingIgnoreCase(keyword)
throw new UnsupportedOperationException("待实现"); .stream()
.map(this::toDTO)
.toList();
} }
@Override @Override
@Transactional @Transactional
public PlaylistDTO copyPlaylist(Long playlistId, String newName, String username) { public PlaylistDTO copyPlaylist(Long playlistId, String newName, String username) {
// TODO [Advanced]: 实现复制歌单 // 获取要复制的歌单
throw new UnsupportedOperationException("待实现"); Playlist originalPlaylist = playlistRepository.findById(playlistId)
.orElseThrow(() -> new ResourceNotFoundException("Playlist not found with id: " + playlistId));
// 获取当前用户新歌单的所有者
User currentUser = userRepository.findByUsername(username)
.orElseThrow(() -> new ResourceNotFoundException("User not found: " + username));
// 创建新歌单
Playlist copiedPlaylist = new Playlist(newName, currentUser);
// 复制原歌单中的所有歌曲
originalPlaylist.getSongs().forEach(originalSong -> {
Song copiedSong = new Song(
originalSong.getTitle(),
originalSong.getArtist(),
originalSong.getDurationInSeconds()
);
copiedPlaylist.addSong(copiedSong);
});
// 保存新歌单
Playlist savedPlaylist = playlistRepository.save(copiedPlaylist);
return toDTO(savedPlaylist);
} }
// ========== 辅助方法 ========== // ========== 辅助方法 ==========
@ -99,16 +167,29 @@ public class PlaylistServiceImpl implements PlaylistService {
* Playlist 实体转换为 DTO * Playlist 实体转换为 DTO
*/ */
private PlaylistDTO toDTO(Playlist playlist) { private PlaylistDTO toDTO(Playlist playlist) {
// TODO: 实现实体到 DTO 的转换 List<SongDTO> songDTOs = playlist.getSongs()
throw new UnsupportedOperationException("待实现"); .stream()
.map(this::toSongDTO)
.toList();
return new PlaylistDTO(
playlist.getId(),
playlist.getName(),
playlist.getOwner().getUsername(),
songDTOs
);
} }
/** /**
* Song 实体转换为 DTO * Song 实体转换为 DTO
*/ */
private SongDTO toSongDTO(Song song) { private SongDTO toSongDTO(Song song) {
// TODO: 实现实体到 DTO 的转换 return new SongDTO(
throw new UnsupportedOperationException("待实现"); song.getId(),
song.getTitle(),
song.getArtist(),
song.getDurationInSeconds()
);
} }
/** /**
@ -116,7 +197,12 @@ public class PlaylistServiceImpl implements PlaylistService {
* 规则歌单所有者或管理员可以操作 * 规则歌单所有者或管理员可以操作
*/ */
private void checkPermission(Playlist playlist, String username) { private void checkPermission(Playlist playlist, String username) {
// TODO [Challenge]: 实现权限检查 User currentUser = userRepository.findByUsername(username)
// 如果无权限抛出 UnauthorizedException .orElseThrow(() -> new ResourceNotFoundException("User not found: " + username));
// 检查是否是歌单所有者或管理员
if (!playlist.getOwner().getUsername().equals(username) && !currentUser.getRole().equals("ROLE_ADMIN")) {
throw new UnauthorizedException("You don't have permission to modify this playlist");
}
} }
} }