generated from Java-2025Fall/final-vibevault-template
完成作业
This commit is contained in:
parent
0820e62b6e
commit
6b7bd6fab3
BIN
BindPort.class
Normal file
BIN
BindPort.class
Normal file
Binary file not shown.
24
BindPort.java
Normal file
24
BindPort.java
Normal 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
BIN
TestApi.class
Normal file
Binary file not shown.
39
TestApi.java
Normal file
39
TestApi.java
Normal 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
BIN
TestPort.class
Normal file
Binary file not shown.
13
TestPort.java
Normal file
13
TestPort.java
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -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";
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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() {
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/main/java/com/vibevault/repository/SongRepository.java
Normal file
14
src/main/java/com/vibevault/repository/SongRepository.java
Normal 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> {
|
||||||
|
}
|
||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user