diff --git a/src/main/java/com/filesystem/config/SecurityConfig.java b/src/main/java/com/filesystem/config/SecurityConfig.java index 986ff0c..3e2b5db 100644 --- a/src/main/java/com/filesystem/config/SecurityConfig.java +++ b/src/main/java/com/filesystem/config/SecurityConfig.java @@ -44,19 +44,16 @@ public class SecurityConfig { .csrf(csrf -> csrf.disable()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth + // API 公开接口 .requestMatchers("/api/auth/login", "/api/auth/register", "/api/auth/logout").permitAll() .requestMatchers("/api/files/test").permitAll() + // WebSocket .requestMatchers("/ws/**").permitAll() - .requestMatchers("/files/avatar/**").permitAll() - .requestMatchers("/api/files/avatar/**").permitAll() - .requestMatchers("/api/messages/file/**").permitAll() - .requestMatchers("/webapp/**").permitAll() - .requestMatchers("/assets/**").permitAll() - .requestMatchers("/login", "/register").permitAll() - .requestMatchers("/").permitAll() - .requestMatchers("/desktop").permitAll() - .requestMatchers("/desktop/**").permitAll() - .requestMatchers("/favicon.ico").permitAll() + // 静态资源 + .requestMatchers("/webapp/**", "/assets/**", "/uploads/**", "/files/avatar/**", "/api/files/avatar/**", "/api/messages/file/**").permitAll() + // 前端页面路由(所有非 /api/ 的路由,由 Vue Router 处理) + .requestMatchers("/", "/login", "/register", "/desktop/**", "/favicon.ico").permitAll() + // 其他所有请求需要认证 .anyRequest().authenticated() ) .exceptionHandling(exception -> exception @@ -112,8 +109,9 @@ public class SecurityConfig { public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOriginPatterns(Arrays.asList("*")); - configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); + configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD")); configuration.setAllowedHeaders(Arrays.asList("*")); + configuration.setExposedHeaders(Arrays.asList("Authorization", "Content-Type")); configuration.setAllowCredentials(true); configuration.setMaxAge(3600L); diff --git a/src/main/java/com/filesystem/controller/AuthController.java b/src/main/java/com/filesystem/controller/AuthController.java index 02ac8bc..7dffca2 100644 --- a/src/main/java/com/filesystem/controller/AuthController.java +++ b/src/main/java/com/filesystem/controller/AuthController.java @@ -52,7 +52,7 @@ public class AuthController { return ResponseEntity.ok(body); } catch (Exception e) { - return ResponseEntity.status(401).body(Map.of("message", e.getMessage())); + return ResponseEntity.badRequest().body(Map.of("message", e.getMessage())); } } diff --git a/src/main/java/com/filesystem/controller/FileController.java b/src/main/java/com/filesystem/controller/FileController.java index eedcc36..5f4fa5b 100644 --- a/src/main/java/com/filesystem/controller/FileController.java +++ b/src/main/java/com/filesystem/controller/FileController.java @@ -109,60 +109,84 @@ public class FileController { public ResponseEntity deleteFile( @AuthenticationPrincipal UserPrincipal principal, @PathVariable Long id) { - fileService.moveToTrash(id, principal.getUserId()); - return ResponseEntity.ok(Map.of("message", "已移至回收站")); + try { + fileService.moveToTrash(id, principal.getUserId()); + return ResponseEntity.ok(Map.of("message", "已移至回收站")); + } catch (RuntimeException e) { + return ResponseEntity.badRequest().body(Map.of("message", e.getMessage())); + } } @PostMapping("/{id}/restore") public ResponseEntity restoreFile( @AuthenticationPrincipal UserPrincipal principal, @PathVariable Long id) { - fileService.restoreFile(id, principal.getUserId()); - return ResponseEntity.ok(Map.of("message", "已还原")); + try { + fileService.restoreFile(id, principal.getUserId()); + return ResponseEntity.ok(Map.of("message", "已还原")); + } catch (RuntimeException e) { + return ResponseEntity.badRequest().body(Map.of("message", e.getMessage())); + } } @DeleteMapping("/{id}/deletePermanent") public ResponseEntity deletePermanently( @AuthenticationPrincipal UserPrincipal principal, @PathVariable Long id) { - fileService.deletePermanently(id, principal.getUserId()); - return ResponseEntity.ok(Map.of("message", "已彻底删除")); + try { + fileService.deletePermanently(id, principal.getUserId()); + return ResponseEntity.ok(Map.of("message", "已彻底删除")); + } catch (RuntimeException e) { + return ResponseEntity.badRequest().body(Map.of("message", e.getMessage())); + } } @DeleteMapping("/emptyTrash") public ResponseEntity emptyTrash(@AuthenticationPrincipal UserPrincipal principal) { - fileService.emptyTrash(principal.getUserId()); - return ResponseEntity.ok(Map.of("message", "已清空回收站")); + try { + fileService.emptyTrash(principal.getUserId()); + return ResponseEntity.ok(Map.of("message", "已清空回收站")); + } catch (RuntimeException e) { + return ResponseEntity.badRequest().body(Map.of("message", e.getMessage())); + } } @GetMapping("/{id}/download") public ResponseEntity downloadFile( @AuthenticationPrincipal UserPrincipal principal, @PathVariable Long id) throws IOException { - FileEntity file = fileService.getById(id); - if (file == null) return ResponseEntity.notFound().build(); - byte[] content = fileService.getFileContent(file); - if (content == null) return ResponseEntity.notFound().build(); - String encodedName = URLEncoder.encode(file.getName(), StandardCharsets.UTF_8).replace("+", "%20"); - return ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + encodedName + "\"") - .contentType(MediaType.APPLICATION_OCTET_STREAM) - .body(content); + try { + FileEntity file = fileService.getById(id); + if (file == null) return ResponseEntity.notFound().build(); + byte[] content = fileService.getFileContent(file); + if (content == null) return ResponseEntity.notFound().build(); + String encodedName = URLEncoder.encode(file.getName(), StandardCharsets.UTF_8).replace("+", "%20"); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + encodedName + "\"") + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .body(content); + } catch (RuntimeException e) { + return ResponseEntity.badRequest().build(); + } } @GetMapping("/{id}/preview") public ResponseEntity previewFile( @AuthenticationPrincipal UserPrincipal principal, @PathVariable Long id) throws IOException { - FileEntity file = fileService.getById(id); - if (file == null) return ResponseEntity.notFound().build(); - byte[] content = fileService.getFileContent(file); - if (content == null) return ResponseEntity.notFound().build(); - String encodedName = URLEncoder.encode(file.getName(), StandardCharsets.UTF_8).replace("+", "%20"); - return ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + encodedName + "\"") - .contentType(getMediaType(file.getName())) - .body(content); + try { + FileEntity file = fileService.getById(id); + if (file == null) return ResponseEntity.notFound().build(); + byte[] content = fileService.getFileContent(file); + if (content == null) return ResponseEntity.notFound().build(); + String encodedName = URLEncoder.encode(file.getName(), StandardCharsets.UTF_8).replace("+", "%20"); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + encodedName + "\"") + .contentType(getMediaType(file.getName())) + .body(content); + } catch (RuntimeException e) { + return ResponseEntity.badRequest().build(); + } } @PostMapping("/{id}/shareFile") @@ -170,18 +194,26 @@ public class FileController { @AuthenticationPrincipal UserPrincipal principal, @PathVariable Long id, @RequestBody Map request) { - Long shareToUserId = Long.valueOf(request.get("userId").toString()); - String permission = (String) request.getOrDefault("permission", "view"); - FileShare share = fileService.shareFile(id, principal.getUserId(), shareToUserId, permission); - return ResponseEntity.ok(Map.of("data", share, "message", "共享成功")); + try { + Long shareToUserId = Long.valueOf(request.get("userId").toString()); + String permission = (String) request.getOrDefault("permission", "view"); + FileShare share = fileService.shareFile(id, principal.getUserId(), shareToUserId, permission); + return ResponseEntity.ok(Map.of("data", share, "message", "共享成功")); + } catch (RuntimeException e) { + return ResponseEntity.badRequest().body(Map.of("message", e.getMessage())); + } } @DeleteMapping("/{id}/cancelShare") public ResponseEntity cancelShare( @AuthenticationPrincipal UserPrincipal principal, @PathVariable Long id) { - fileService.cancelShare(id, principal.getUserId()); - return ResponseEntity.ok(Map.of("message", "已取消共享")); + try { + fileService.cancelShare(id, principal.getUserId()); + return ResponseEntity.ok(Map.of("message", "已取消共享")); + } catch (RuntimeException e) { + return ResponseEntity.badRequest().body(Map.of("message", e.getMessage())); + } } /** diff --git a/src/main/java/com/filesystem/controller/IndexController.java b/src/main/java/com/filesystem/controller/IndexController.java index db34845..7a84cdb 100644 --- a/src/main/java/com/filesystem/controller/IndexController.java +++ b/src/main/java/com/filesystem/controller/IndexController.java @@ -6,14 +6,9 @@ import org.springframework.web.bind.annotation.GetMapping; @Controller public class IndexController { - @GetMapping({"/", "/login", "/register"}) + // 所有非 /api/**、非 /ws/** 的前端路由,统一返回 index.html + @GetMapping({"/", "/login", "/register", "/desktop", "/desktop/**"}) public String forward() { return "forward:/webapp/index.html"; } - - // 捕获所有前端路由,返回 index.html 让 Vue Router 处理 - @GetMapping("/desktop/**") - public String desktop() { - return "forward:/webapp/index.html"; - } } \ No newline at end of file diff --git a/src/main/java/com/filesystem/controller/UserController.java b/src/main/java/com/filesystem/controller/UserController.java index 393dc05..1cbafff 100644 --- a/src/main/java/com/filesystem/controller/UserController.java +++ b/src/main/java/com/filesystem/controller/UserController.java @@ -115,12 +115,16 @@ public class UserController { public ResponseEntity updateProfile( @AuthenticationPrincipal UserPrincipal principal, @RequestBody Map request) { - String nickname = request.get("nickname"); - String signature = request.get("signature"); - String phone = request.get("phone"); - String email = request.get("email"); - userService.updateProfile(principal.getUserId(), nickname, signature, phone, email); - return ResponseEntity.ok(Map.of("message", "更新成功")); + try { + String nickname = request.get("nickname"); + String signature = request.get("signature"); + String phone = request.get("phone"); + String email = request.get("email"); + userService.updateProfile(principal.getUserId(), nickname, signature, phone, email); + return ResponseEntity.ok(Map.of("message", "更新成功")); + } catch (RuntimeException e) { + return ResponseEntity.badRequest().body(Map.of("message", e.getMessage())); + } } /** diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 2158c74..9d889d9 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,5 +1,5 @@ server: - port: 18080 + port: 18089 spring: datasource: diff --git a/web-vue/src/api/request.js b/web-vue/src/api/request.js index a9c8d3c..5170953 100644 --- a/web-vue/src/api/request.js +++ b/web-vue/src/api/request.js @@ -22,7 +22,16 @@ request.interceptors.response.use( const status = error.response?.status // 只有 401/403 才清理 token 并跳转登录页 + // 但在登录页时不跳转(避免死循环),登录接口的 401 也不跳转 if (status === 401 || status === 403) { + const isLoginPage = window.location.pathname === '/login' + const isAuthApi = error.config?.url?.startsWith('/auth/') + + if (isLoginPage || isAuthApi) { + // 登录页或登录接口,不跳转,不清理 + return Promise.reject(error) + } + localStorage.removeItem('token') localStorage.removeItem('username') localStorage.removeItem('userId') @@ -31,12 +40,9 @@ request.interceptors.response.use( localStorage.removeItem('storageUsed') localStorage.removeItem('storageLimit') - if (window.location.pathname !== '/login') { - window.location.href = '/login' - } + window.location.href = '/login' } - // 其他错误(404、500、网络错误等)正常 reject,不跳转 return Promise.reject(error) } ) diff --git a/web-vue/src/views/files/index.vue b/web-vue/src/views/files/index.vue index ba28e92..2241ed5 100644 --- a/web-vue/src/views/files/index.vue +++ b/web-vue/src/views/files/index.vue @@ -619,8 +619,6 @@ const handleConfirmBatchMove = async (targetFolderId) => { finalFolderId = targetFolderId } - console.log('targetFolderId:', targetFolderId, 'finalFolderId:', finalFolderId) - let successCount = 0 let failCount = 0 diff --git a/web-vue/vite.config.js b/web-vue/vite.config.js index c9e84bc..236807d 100644 --- a/web-vue/vite.config.js +++ b/web-vue/vite.config.js @@ -22,19 +22,19 @@ export default defineConfig({ port: 5173, proxy: { '/api': { - target: 'http://localhost:8080', + target: 'http://localhost:18089', changeOrigin: true }, '/ws': { - target: 'ws://localhost:8080', + target: 'ws://localhost:18089', ws: true }, '/uploads': { - target: 'http://localhost:8080', + target: 'http://localhost:18089', changeOrigin: true }, '/files': { - target: 'http://localhost:8080', + target: 'http://localhost:18089', changeOrigin: true } }