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

This commit is contained in:
heyi 2025-12-21 21:47:25 +08:00
parent 0820e62b6e
commit 6b7bd6fab3
20 changed files with 453 additions and 67 deletions

BIN
BindPort.class Normal file

Binary file not shown.

24
BindPort.java Normal file
View File

@ -0,0 +1,24 @@
import java.net.ServerSocket;
import java.net.Socket;
public class BindPort {
public static void main(String[] args) {
try {
// 尝试绑定到端口8081
ServerSocket serverSocket = new ServerSocket(8081);
System.out.println("Successfully bound to port 8081");
// 等待客户端连接
System.out.println("Waiting for client connections...");
Socket clientSocket = serverSocket.accept();
System.out.println("Client connected: " + clientSocket.getRemoteSocketAddress());
// 关闭连接
clientSocket.close();
serverSocket.close();
} catch (Exception e) {
System.err.println("Error binding to port 8081: " + e.getMessage());
e.printStackTrace();
}
}
}

BIN
TestApi.class Normal file

Binary file not shown.

39
TestApi.java Normal file
View File

@ -0,0 +1,39 @@
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class TestApi {
public static void main(String[] args) {
try {
URL url = new URL("http://127.0.0.1:8081/api/playlists");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Content-Type", "application/json");
System.out.println("Sending GET request to: " + url.toString());
System.out.println("Connection timeout: " + conn.getConnectTimeout());
System.out.println("Read timeout: " + conn.getReadTimeout());
int responseCode = conn.getResponseCode();
System.out.println("Response Code: " + responseCode);
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
System.out.println("Response Body: " + response.toString());
conn.disconnect();
} catch (Exception e) {
System.out.println("Exception occurred: " + e.getClass().getName());
System.out.println("Exception message: " + e.getMessage());
e.printStackTrace();
}
}
}

BIN
TestPort.class Normal file

Binary file not shown.

13
TestPort.java Normal file
View File

@ -0,0 +1,13 @@
import java.net.Socket;
public class TestPort {
public static void main(String[] args) {
try {
Socket socket = new Socket("127.0.0.1", 8081);
System.out.println("Successfully connected to port 8081");
socket.close();
} catch (Exception e) {
System.out.println("Failed to connect to port 8081: " + e.getMessage());
}
}
}

View File

@ -39,13 +39,31 @@ public class SecurityConfig {
@Bean @Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// TODO: 配置安全规则 http
// 提示 // 配置路径权限
// - 使用 http.authorizeHttpRequests() 配置路径权限 .authorizeHttpRequests(auth -> auth
// - 使用 http.csrf(csrf -> csrf.disable()) 禁用 CSRF // 公开接口无需认证
// - 使用 http.sessionManagement() 配置无状态会话 .requestMatchers("/api/auth/**").permitAll()
// - 使用 http.exceptionHandling() 配置 401 响应 .requestMatchers(HttpMethod.GET, "/api/playlists").permitAll()
// - 使用 http.addFilterBefore() 添加 JWT 过滤器 .requestMatchers(HttpMethod.GET, "/api/playlists/{id}").permitAll()
.requestMatchers(HttpMethod.GET, "/api/playlists/search").permitAll()
// 其他所有请求需要认证
.anyRequest().authenticated()
)
// 禁用 CSRF
.csrf(csrf -> csrf.disable())
// 配置无状态会话
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
// 配置未认证访问受保护资源返回 401
.exceptionHandling(ex -> ex
.authenticationEntryPoint((request, response, authException) -> {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
})
)
// 添加 JWT 过滤器
.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) // 实现 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");
}
// 创建新用户
User user = new User(request.username(), passwordEncoder.encode(request.password()));
// 设置默认角色
user.setRole("ROLE_USER");
// 保存用户到数据库
userRepository.save(user);
// 返回注册响应
return new RegisterResponse("User registered successfully", request.username());
}
// TODO: 实现 POST /api/auth/login // 实现 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.getUsername());
// 返回登录响应
return new LoginResponse(token, user.getUsername());
}
} }
/** /**

View File

@ -0,0 +1,14 @@
package com.vibevault.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/health")
public class HealthCheckController {
@GetMapping
public String healthCheck() {
return "OK";
}
}

View File

@ -39,19 +39,61 @@ public class PlaylistController {
this.playlistService = playlistService; this.playlistService = playlistService;
} }
// TODO: 实现 GET /api/playlists // 实现 GET /api/playlists - 获取所有歌单公开
@GetMapping
public List<PlaylistDTO> getAllPlaylists() {
return playlistService.getAllPlaylists();
}
// TODO: 实现 GET /api/playlists/{id} // 实现 GET /api/playlists/{id} - 获取指定歌单公开
@GetMapping("/{id}")
public PlaylistDTO getPlaylistById(@PathVariable Long id) {
return playlistService.getPlaylistById(id);
}
// TODO: 实现 POST /api/playlists (状态码 201) // 实现 POST /api/playlists - 创建歌单需认证
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public PlaylistDTO createPlaylist(@RequestBody PlaylistCreateDTO playlistCreateDTO, Authentication authentication) {
String username = authentication.getName();
return playlistService.createPlaylist(playlistCreateDTO.name(), username);
}
// TODO: 实现 POST /api/playlists/{id}/songs (状态码 201) // 实现 POST /api/playlists/{id}/songs - 添加歌曲需认证
@PostMapping("/{id}/songs")
@ResponseStatus(HttpStatus.CREATED)
public PlaylistDTO addSongToPlaylist(@PathVariable Long id, @RequestBody SongCreateDTO songCreateDTO, Authentication authentication) {
String username = authentication.getName();
return playlistService.addSongToPlaylist(id, songCreateDTO, username);
}
// TODO: 实现 DELETE /api/playlists/{playlistId}/songs/{songId} (状态码 204) // 实现 DELETE /api/playlists/{playlistId}/songs/{songId} - 移除歌曲需认证
@DeleteMapping("/{playlistId}/songs/{songId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void removeSongFromPlaylist(@PathVariable Long playlistId, @PathVariable Long songId, Authentication authentication) {
String username = authentication.getName();
playlistService.removeSongFromPlaylist(playlistId, songId, username);
}
// TODO: 实现 DELETE /api/playlists/{id} (状态码 204) // 实现 DELETE /api/playlists/{id} - 删除歌单需认证
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deletePlaylist(@PathVariable Long id, Authentication authentication) {
String username = authentication.getName();
playlistService.deletePlaylist(id, username);
}
// TODO [Advanced]: 实现 GET /api/playlists/search?keyword=xxx // [Advanced] 实现 GET /api/playlists/search?keyword=xxx - 搜索歌单
@GetMapping("/search")
public List<PlaylistDTO> searchPlaylists(@RequestParam String keyword) {
return playlistService.searchPlaylists(keyword);
}
// TODO [Advanced]: 实现 POST /api/playlists/{id}/copy?newName=xxx (状态码 201) // [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);
}
} }

View File

@ -16,14 +16,22 @@ 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(name = "name", nullable = false)
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() {
@ -59,7 +67,8 @@ public class Playlist {
* 提示需要维护双向关系 * 提示需要维护双向关系
*/ */
public void addSong(Song song) { public void addSong(Song song) {
// TODO: 实现添加歌曲逻辑 songs.add(song);
song.setPlaylist(this);
} }
/** /**
@ -67,6 +76,7 @@ public class Playlist {
* 提示需要维护双向关系 * 提示需要维护双向关系
*/ */
public void removeSong(Song song) { public void removeSong(Song song) {
// TODO: 实现移除歌曲逻辑 songs.remove(song);
song.setPlaylist(null);
} }
} }

View File

@ -10,16 +10,25 @@ 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(name = "title", nullable = false)
private String title; private String title;
@Column(name = "artist", nullable = false)
private String artist; private String artist;
@Column(name = "duration_in_seconds", nullable = false)
private int durationInSeconds; private int durationInSeconds;
@ManyToOne
@JoinColumn(name = "playlist_id", nullable = false)
private Playlist playlist; private Playlist playlist;
public Song() { public Song() {

View File

@ -12,15 +12,22 @@ import jakarta.persistence.*;
* - password 不能为空 * - password 不能为空
* - [Challenge] 支持用户角色 ROLE_USER, ROLE_ADMIN * - [Challenge] 支持用户角色 ROLE_USER, ROLE_ADMIN
*/ */
@Entity
@Table(name = "users")
public class User { public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private Long id;
@Column(name = "username", unique = true, nullable = false)
private String username; private String username;
@Column(name = "password", nullable = false)
private String password; private String password;
// [Challenge] 用户角色默认为 ROLE_USER // [Challenge] 用户角色默认为 ROLE_USER
@Column(name = "role", nullable = false)
private String role = "ROLE_USER"; private String role = "ROLE_USER";
protected User() { protected User() {
@ -53,6 +60,14 @@ public class User {
return role; return role;
} }
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setRole(String role) { public void setRole(String role) {
this.role = role; this.role = role;
} }

View File

@ -18,5 +18,6 @@ import java.util.List;
*/ */
@Repository @Repository
public interface PlaylistRepository extends JpaRepository<Playlist, Long> { public interface PlaylistRepository extends JpaRepository<Playlist, Long> {
// TODO [Advanced]: 添加高级查询方法 List<Playlist> findByOwner(User owner);
List<Playlist> findByNameContainingIgnoreCase(String keyword);
} }

View File

@ -0,0 +1,14 @@
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<Song, Long> {
}

View File

@ -15,5 +15,6 @@ 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,50 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
@NonNull FilterChain filterChain @NonNull FilterChain filterChain
) throws ServletException, IOException { ) throws ServletException, IOException {
// TODO: 实现 JWT 认证逻辑 // 从请求头获取 Authorization
// 1. 从请求头获取 Authorization final String authHeader = request.getHeader("Authorization");
// 2. 检查是否以 "Bearer " 开头 final String jwt;
// 3. 提取 token 并验证 final String username;
// 4. 如果有效创建 Authentication 并设置到 SecurityContextHolder
// // 检查是否以 "Bearer " 开头
// 提示 if (authHeader == null || !authHeader.startsWith("Bearer ")) {
// - 使用 request.getHeader("Authorization") 获取头 filterChain.doFilter(request, response);
// - 使用 jwtService.extractUsername() jwtService.isTokenValid() return;
// - 使用 UsernamePasswordAuthenticationToken 创建认证对象 }
// - 使用 SecurityContextHolder.getContext().setAuthentication() 设置
// 提取 token
jwt = authHeader.substring(7);
// token 中提取用户名
username = jwtService.extractUsername(jwt);
// 如果用户名不为空且当前没有认证信息
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// 从数据库中查找用户
User user = userRepository.findByUsername(username)
.orElse(null);
if (user != null && jwtService.isTokenValid(jwt, username)) {
// 设置用户角色
List<SimpleGrantedAuthority> authorities = Collections.singletonList(
new SimpleGrantedAuthority(user.getRole())
);
// 创建认证对象
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
user.getUsername(),
null,
authorities
);
// 设置认证详情
authToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
// 设置到 SecurityContextHolder
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
} }

View File

@ -1,5 +1,6 @@
package com.vibevault.security; package com.vibevault.security;
import io.jsonwebtoken.Claims;
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;
@ -8,6 +9,7 @@ import org.springframework.stereotype.Service;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Date; import java.util.Date;
import java.util.function.Function;
/** /**
* JWT 服务 * JWT 服务
@ -30,26 +32,60 @@ public class JwtService {
* 为用户生成 JWT token * 为用户生成 JWT token
*/ */
public String generateToken(String username) { public String generateToken(String username) {
// TODO: 实现 token 生成 return Jwts.builder()
// 提示使用 Jwts.builder() .setSubject(username)
throw new UnsupportedOperationException("待实现"); .setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(getSigningKey())
.compact();
} }
/** /**
* token 中提取用户名 * token 中提取用户名
*/ */
public String extractUsername(String token) { public String extractUsername(String token) {
// TODO: 实现用户名提取 return extractClaim(token, Claims::getSubject);
// 提示使用 Jwts.parser()
throw new UnsupportedOperationException("待实现");
} }
/** /**
* 验证 token 是否有效 * 验证 token 是否有效
*/ */
public boolean isTokenValid(String token, String username) { public boolean isTokenValid(String token, String username) {
// TODO: 实现 token 验证 final String extractedUsername = extractUsername(token);
throw new UnsupportedOperationException("待实现"); return (extractedUsername.equals(username)) && !isTokenExpired(token);
}
/**
* JWT 令牌中提取特定声明
*/
private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
/**
* JWT 令牌中提取所有声明
*/
private Claims extractAllClaims(String token) {
return Jwts.parser()
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token)
.getPayload();
}
/**
* 检查令牌是否过期
*/
private boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
/**
* 从令牌中提取过期时间
*/
private Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
} }
/** /**

View File

@ -14,6 +14,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
/** /**
* 歌单服务实现 * 歌单服务实现
@ -37,60 +38,119 @@ public class PlaylistServiceImpl implements PlaylistService {
@Override @Override
public List<PlaylistDTO> getAllPlaylists() { public List<PlaylistDTO> getAllPlaylists() {
// TODO: 实现获取所有歌单 List<Playlist> playlists = playlistRepository.findAll();
throw new UnsupportedOperationException("待实现"); return playlists.stream()
.map(this::toDTO)
.collect(Collectors.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 with username: " + 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 songDTO, String username) {
// TODO: 实现添加歌曲到歌单 Playlist playlist = playlistRepository.findById(playlistId)
// [Challenge] 需要检查用户是否有权限操作此歌单 .orElseThrow(() -> new ResourceNotFoundException("Playlist not found with id: " + playlistId));
throw new UnsupportedOperationException("待实现");
// 检查用户是否有权限操作此歌单
checkPermission(playlist, username);
// 创建新歌曲
Song song = new Song(songDTO.title(), songDTO.artist(), songDTO.durationInSeconds());
// 添加歌曲到歌单
playlist.addSong(song);
// 保存更新后的歌单
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("待实现");
// 检查用户是否有权限操作此歌单
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("待实现");
// 检查用户是否有权限操作此歌单
checkPermission(playlist, username);
// 删除歌单
playlistRepository.delete(playlist);
} }
// ========== Advanced 方法 ========== // ========== Advanced 方法 ==========
@Override @Override
public List<PlaylistDTO> searchPlaylists(String keyword) { public List<PlaylistDTO> searchPlaylists(String keyword) {
// TODO [Advanced]: 实现按关键字搜索歌单 List<Playlist> playlists = playlistRepository.findByNameContainingIgnoreCase(keyword);
throw new UnsupportedOperationException("待实现"); return playlists.stream()
.map(this::toDTO)
.collect(Collectors.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]: 实现复制歌单 Playlist originalPlaylist = playlistRepository.findById(playlistId)
throw new UnsupportedOperationException("待实现"); .orElseThrow(() -> new ResourceNotFoundException("Playlist not found with id: " + playlistId));
User currentUser = userRepository.findByUsername(username)
.orElseThrow(() -> new ResourceNotFoundException("User not found with username: " + username));
// 创建新歌单
Playlist newPlaylist = new Playlist(newName, currentUser);
// 复制所有歌曲
originalPlaylist.getSongs().forEach(song -> {
Song copiedSong = new Song(song.getTitle(), song.getArtist(), song.getDurationInSeconds());
newPlaylist.addSong(copiedSong);
});
// 保存新歌单
Playlist savedPlaylist = playlistRepository.save(newPlaylist);
return toDTO(savedPlaylist);
} }
// ========== 辅助方法 ========== // ========== 辅助方法 ==========
@ -99,16 +159,28 @@ 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().stream()
throw new UnsupportedOperationException("待实现"); .map(this::toSongDTO)
.collect(Collectors.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 +188,15 @@ 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 with username: " + username));
// 检查是否是歌单所有者或管理员
boolean isOwner = playlist.getOwner().getUsername().equals(username);
boolean isAdmin = currentUser.getRole().equals("ROLE_ADMIN");
if (!isOwner && !isAdmin) {
throw new UnauthorizedException("You don't have permission to access this playlist");
}
} }
} }

View File

@ -28,4 +28,5 @@ jwt.secret=your-secret-key-here-should-be-at-least-256-bits-long-for-hs256
jwt.expiration=86400000 jwt.expiration=86400000
# Server # Server
server.port=8080 server.port=8081
server.address=127.0.0.1