重构云文件管理系统

This commit is contained in:
2026-04-02 23:35:19 +08:00
parent 9ff222c22c
commit ceb6c8258c
11 changed files with 294 additions and 147 deletions

2
.idea/compiler.xml generated
View File

@@ -7,6 +7,7 @@
<sourceOutputDir name="target/generated-sources/annotations" /> <sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" /> <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" /> <outputRelativeToContentRoot value="true" />
<module name="system-pro" />
<module name="file-system-backend" /> <module name="file-system-backend" />
</profile> </profile>
</annotationProcessing> </annotationProcessing>
@@ -14,6 +15,7 @@
<component name="JavacSettings"> <component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_OVERRIDE"> <option name="ADDITIONAL_OPTIONS_OVERRIDE">
<module name="file-system-backend" options="-parameters" /> <module name="file-system-backend" options="-parameters" />
<module name="system-pro" options="-parameters" />
</option> </option>
</component> </component>
</project> </project>

2
.idea/misc.xml generated
View File

@@ -8,5 +8,5 @@
</list> </list>
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="java-17" project-jdk-type="JavaSDK" /> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="JDK17" project-jdk-type="JavaSDK" />
</project> </project>

View File

@@ -1,13 +1,11 @@
package com.filesystem.config; package com.filesystem.config;
import com.filesystem.utils.ApiResult;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.io.IOException;
import java.util.Map;
@RestControllerAdvice @RestControllerAdvice
public class GlobalExceptionHandler { public class GlobalExceptionHandler {
@@ -15,29 +13,34 @@ public class GlobalExceptionHandler {
* 处理所有运行时异常 * 处理所有运行时异常
*/ */
@ExceptionHandler(RuntimeException.class) @ExceptionHandler(RuntimeException.class)
public ResponseEntity<?> handleRuntimeException(RuntimeException e) { public ApiResult<?> handleRuntimeException(RuntimeException e) {
e.printStackTrace(); e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) return ApiResult.error(e.getMessage() != null ? e.getMessage() : "服务器内部错误");
.body(Map.of("message", e.getMessage() != null ? e.getMessage() : "服务器内部错误"));
} }
/** /**
* 处理 IO 异常(如 ZIP 压缩失败) * 处理参数异常
*/ */
@ExceptionHandler(IOException.class) @ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<?> handleIOException(IOException e) { public ApiResult<?> handleIllegalArgumentException(IllegalArgumentException e) {
return ApiResult.error(e.getMessage() != null ? e.getMessage() : "请求参数错误");
}
/**
* 处理 IO 异常(如文件操作失败)
*/
@ExceptionHandler(java.io.IOException.class)
public ApiResult<?> handleIOException(java.io.IOException e) {
e.printStackTrace(); e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) return ApiResult.error("文件操作失败: " + (e.getMessage() != null ? e.getMessage() : "未知错误"));
.body(Map.of("message", "文件操作失败: " + (e.getMessage() != null ? e.getMessage() : "未知错误")));
} }
/** /**
* 处理所有其他异常 * 处理所有其他异常
*/ */
@ExceptionHandler(Exception.class) @ExceptionHandler(Exception.class)
public ResponseEntity<?> handleException(Exception e) { public ApiResult<?> handleException(Exception e) {
e.printStackTrace(); e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) return ApiResult.serverError(e.getMessage() != null ? e.getMessage() : "未知错误");
.body(Map.of("message", "服务器错误: " + (e.getMessage() != null ? e.getMessage() : "未知错误")));
} }
} }

View File

@@ -3,8 +3,8 @@ package com.filesystem.controller;
import com.filesystem.entity.User; import com.filesystem.entity.User;
import com.filesystem.security.UserPrincipal; import com.filesystem.security.UserPrincipal;
import com.filesystem.service.UserService; import com.filesystem.service.UserService;
import com.filesystem.utils.ApiResult;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -19,7 +19,7 @@ public class AuthController {
private UserService userService; private UserService userService;
@PostMapping("/login") @PostMapping("/login")
public ResponseEntity<?> login(@RequestBody Map<String, String> request) { public ApiResult<Map<String, Object>> login(@RequestBody Map<String, String> request) {
String username = request.get("username"); String username = request.get("username");
String password = request.get("password"); String password = request.get("password");
@@ -46,47 +46,43 @@ public class AuthController {
result.put("token", token); result.put("token", token);
result.put("user", userData); result.put("user", userData);
Map<String, Object> body = new HashMap<>(); return ApiResult.success("登录成功", result);
body.put("data", result);
body.put("message", "登录成功");
return ResponseEntity.ok(body);
} catch (Exception e) { } catch (Exception e) {
return ResponseEntity.badRequest().body(Map.of("message", e.getMessage())); return ApiResult.error(e.getMessage());
} }
} }
@PostMapping("/register") @PostMapping("/register")
public ResponseEntity<?> register(@RequestBody Map<String, String> request) { public ApiResult<Map<String, Object>> register(@RequestBody Map<String, String> request) {
String username = request.get("username"); String username = request.get("username");
String password = request.get("password"); String password = request.get("password");
String nickname = request.get("nickname"); String nickname = request.get("nickname");
if (userService.findByUsername(username) != null) { if (userService.findByUsername(username) != null) {
return ResponseEntity.badRequest().body(Map.of("message", "用户名已存在")); return ApiResult.error("用户名已存在");
} }
User user = userService.createUser(username, password, nickname != null ? nickname : username); User user = userService.createUser(username, password, nickname != null ? nickname : username);
return ResponseEntity.ok(Map.of("message", "注册成功", "data", Map.of("id", user.getId()))); return ApiResult.success("注册成功", Map.of("id", user.getId()));
} }
@PostMapping("/logout") @PostMapping("/logout")
public ResponseEntity<?> logout(@AuthenticationPrincipal UserPrincipal principal) { public ApiResult<Void> logout(@AuthenticationPrincipal UserPrincipal principal) {
if (principal != null) { if (principal != null) {
userService.logout(principal.getUserId()); userService.logout(principal.getUserId());
} }
return ResponseEntity.ok(Map.of("message", "退出成功")); return ApiResult.success("退出成功");
} }
@GetMapping("/info") @GetMapping("/info")
public ResponseEntity<?> getUserInfo(@AuthenticationPrincipal UserPrincipal principal) { public ApiResult<Map<String, Object>> getUserInfo(@AuthenticationPrincipal UserPrincipal principal) {
if (principal == null) { if (principal == null) {
return ResponseEntity.status(401).body(Map.of("message", "未登录")); return ApiResult.unauthorized("未登录");
} }
User user = userService.findById(principal.getUserId()); User user = userService.findById(principal.getUserId());
if (user == null) { if (user == null) {
return ResponseEntity.status(401).body(Map.of("message", "用户不存在")); return ApiResult.unauthorized("用户不存在");
} }
// 精确重算存储空间 // 精确重算存储空间
@@ -104,9 +100,6 @@ public class AuthController {
userData.put("storageUsed", storageUsed); userData.put("storageUsed", storageUsed);
userData.put("storageLimit", storageLimit); userData.put("storageLimit", storageLimit);
Map<String, Object> body = new HashMap<>(); return ApiResult.success(userData);
body.put("data", userData);
return ResponseEntity.ok(body);
} }
} }

View File

@@ -4,6 +4,7 @@ import com.filesystem.entity.FileEntity;
import com.filesystem.entity.FileShare; import com.filesystem.entity.FileShare;
import com.filesystem.security.UserPrincipal; import com.filesystem.security.UserPrincipal;
import com.filesystem.service.FileService; import com.filesystem.service.FileService;
import com.filesystem.utils.ApiResult;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@@ -19,10 +20,8 @@ import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -36,121 +35,121 @@ public class FileController {
@Value("${file.storage.path:./uploads}") @Value("${file.storage.path:./uploads}")
private String storagePath; private String storagePath;
public FileController() {
}
@GetMapping("/test") @GetMapping("/test")
public ResponseEntity<?> test() { public ApiResult<Map<String, Object>> test() {
return ResponseEntity.ok(Map.of("message", "Backend is running", "timestamp", System.currentTimeMillis())); return ApiResult.success(Map.of("message", "Backend is running", "timestamp", System.currentTimeMillis()));
} }
@GetMapping @GetMapping
public ResponseEntity<?> getFiles( public ApiResult<List<FileEntity>> getFiles(
@AuthenticationPrincipal UserPrincipal principal, @AuthenticationPrincipal UserPrincipal principal,
@RequestParam(required = false) Long folderId, @RequestParam(required = false) Long folderId,
@RequestParam(required = false) String keyword) { @RequestParam(required = false) String keyword) {
List<FileEntity> files = fileService.getFiles(principal.getUserId(), folderId, keyword); List<FileEntity> files = fileService.getFiles(principal.getUserId(), folderId, keyword);
return ResponseEntity.ok(Map.of("data", files)); return ApiResult.success(files);
} }
@GetMapping("/trashFiles") @GetMapping("/trashFiles")
public ResponseEntity<?> getTrashFiles( public ApiResult<List<FileEntity>> getTrashFiles(
@AuthenticationPrincipal UserPrincipal principal, @AuthenticationPrincipal UserPrincipal principal,
@RequestParam(required = false) Long folderId) { @RequestParam(required = false) Long folderId) {
return ResponseEntity.ok(Map.of("data", fileService.getTrashFiles(principal.getUserId(), folderId))); return ApiResult.success(fileService.getTrashFiles(principal.getUserId(), folderId));
} }
@GetMapping("/sharedByMe") @GetMapping("/sharedByMe")
public ResponseEntity<?> getSharedByMe(@AuthenticationPrincipal UserPrincipal principal) { public ApiResult<List<FileEntity>> getSharedByMe(@AuthenticationPrincipal UserPrincipal principal) {
return ResponseEntity.ok(Map.of("data", fileService.getSharedByMe(principal.getUserId()))); return ApiResult.success(fileService.getSharedByMe(principal.getUserId()));
} }
@GetMapping("/sharedByMe/folder") @GetMapping("/sharedByMe/folder")
public ResponseEntity<?> getSharedByMeFolderFiles( public ApiResult<List<FileEntity>> getSharedByMeFolderFiles(
@AuthenticationPrincipal UserPrincipal principal, @AuthenticationPrincipal UserPrincipal principal,
@RequestParam Long folderId) { @RequestParam Long folderId) {
List<FileEntity> files = fileService.getSharedByMeFolderFiles(principal.getUserId(), folderId); List<FileEntity> files = fileService.getSharedByMeFolderFiles(principal.getUserId(), folderId);
return ResponseEntity.ok(Map.of("data", files)); return ApiResult.success(files);
} }
@GetMapping("/sharedToMe") @GetMapping("/sharedToMe")
public ResponseEntity<?> getSharedToMe(@AuthenticationPrincipal UserPrincipal principal) { public ApiResult<List<FileEntity>> getSharedToMe(@AuthenticationPrincipal UserPrincipal principal) {
return ResponseEntity.ok(Map.of("data", fileService.getSharedToMe(principal.getUserId()))); return ApiResult.success(fileService.getSharedToMe(principal.getUserId()));
} }
@GetMapping("/sharedToMe/folder") @GetMapping("/sharedToMe/folder")
public ResponseEntity<?> getSharedFolderFiles( public ApiResult<List<FileEntity>> getSharedFolderFiles(
@AuthenticationPrincipal UserPrincipal principal, @AuthenticationPrincipal UserPrincipal principal,
@RequestParam Long folderId) { @RequestParam Long folderId) {
List<FileEntity> files = fileService.getSharedFolderFiles(principal.getUserId(), folderId); List<FileEntity> files = fileService.getSharedFolderFiles(principal.getUserId(), folderId);
return ResponseEntity.ok(Map.of("data", files)); return ApiResult.success(files);
} }
@PostMapping("/uploadBatch") @PostMapping("/uploadBatch")
public ResponseEntity<?> uploadFiles( public ApiResult<List<FileEntity>> uploadFiles(
@AuthenticationPrincipal UserPrincipal principal, @AuthenticationPrincipal UserPrincipal principal,
@RequestParam("files") List<MultipartFile> files, @RequestParam("files") List<MultipartFile> files,
@RequestParam(required = false) Long folderId) throws IOException { @RequestParam(required = false) Long folderId) throws IOException {
List<FileEntity> uploaded = fileService.uploadFiles(files, principal.getUserId(), folderId); List<FileEntity> uploaded = fileService.uploadFiles(files, principal.getUserId(), folderId);
return ResponseEntity.ok(Map.of("data", uploaded, "message", "上传成功")); return ApiResult.success("上传成功", uploaded);
} }
@PostMapping("/createFolder") @PostMapping("/createFolder")
public ResponseEntity<?> createFolder( public ApiResult<FileEntity> createFolder(
@AuthenticationPrincipal UserPrincipal principal, @AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, Object> request) { @RequestBody Map<String, Object> request) {
String name = (String) request.get("name"); String name = (String) request.get("name");
Long parentId = request.get("parentId") != null ? Long.valueOf(request.get("parentId").toString()) : null; Long parentId = request.get("parentId") != null ? Long.valueOf(request.get("parentId").toString()) : null;
FileEntity folder = fileService.createFolder(name, principal.getUserId(), parentId); FileEntity folder = fileService.createFolder(name, principal.getUserId(), parentId);
return ResponseEntity.ok(Map.of("data", folder, "message", "创建成功")); return ApiResult.success("创建成功", folder);
} }
@DeleteMapping("/{id}") @DeleteMapping("/{id}")
public ResponseEntity<?> deleteFile( public ApiResult<Void> deleteFile(
@AuthenticationPrincipal UserPrincipal principal, @AuthenticationPrincipal UserPrincipal principal,
@PathVariable Long id) { @PathVariable Long id) {
try { try {
fileService.moveToTrash(id, principal.getUserId()); fileService.moveToTrash(id, principal.getUserId());
return ResponseEntity.ok(Map.of("message", "已移至回收站")); return ApiResult.success("已移至回收站");
} catch (RuntimeException e) { } catch (RuntimeException e) {
return ResponseEntity.badRequest().body(Map.of("message", e.getMessage())); return ApiResult.error(e.getMessage());
} }
} }
@PostMapping("/{id}/restore") @PostMapping("/{id}/restore")
public ResponseEntity<?> restoreFile( public ApiResult<Void> restoreFile(
@AuthenticationPrincipal UserPrincipal principal, @AuthenticationPrincipal UserPrincipal principal,
@PathVariable Long id) { @PathVariable Long id) {
try { try {
fileService.restoreFile(id, principal.getUserId()); fileService.restoreFile(id, principal.getUserId());
return ResponseEntity.ok(Map.of("message", "已还原")); return ApiResult.success("已还原");
} catch (RuntimeException e) { } catch (RuntimeException e) {
return ResponseEntity.badRequest().body(Map.of("message", e.getMessage())); return ApiResult.error(e.getMessage());
} }
} }
@DeleteMapping("/{id}/deletePermanent") @DeleteMapping("/{id}/deletePermanent")
public ResponseEntity<?> deletePermanently( public ApiResult<Void> deletePermanently(
@AuthenticationPrincipal UserPrincipal principal, @AuthenticationPrincipal UserPrincipal principal,
@PathVariable Long id) { @PathVariable Long id) {
try { try {
fileService.deletePermanently(id, principal.getUserId()); fileService.deletePermanently(id, principal.getUserId());
return ResponseEntity.ok(Map.of("message", "已彻底删除")); return ApiResult.success("已彻底删除");
} catch (RuntimeException e) { } catch (RuntimeException e) {
return ResponseEntity.badRequest().body(Map.of("message", e.getMessage())); return ApiResult.error(e.getMessage());
} }
} }
@DeleteMapping("/emptyTrash") @DeleteMapping("/emptyTrash")
public ResponseEntity<?> emptyTrash(@AuthenticationPrincipal UserPrincipal principal) { public ApiResult<Void> emptyTrash(@AuthenticationPrincipal UserPrincipal principal) {
try { try {
fileService.emptyTrash(principal.getUserId()); fileService.emptyTrash(principal.getUserId());
return ResponseEntity.ok(Map.of("message", "已清空回收站")); return ApiResult.success("已清空回收站");
} catch (RuntimeException e) { } catch (RuntimeException e) {
return ResponseEntity.badRequest().body(Map.of("message", e.getMessage())); return ApiResult.error(e.getMessage());
} }
} }
/**
* 下载文件(返回 ResponseEntity<byte[]> 用于二进制响应)
*/
@GetMapping("/{id}/download") @GetMapping("/{id}/download")
public ResponseEntity<byte[]> downloadFile( public ResponseEntity<byte[]> downloadFile(
@AuthenticationPrincipal UserPrincipal principal, @AuthenticationPrincipal UserPrincipal principal,
@@ -170,6 +169,9 @@ public class FileController {
} }
} }
/**
* 预览文件(返回 ResponseEntity<byte[]> 用于二进制响应)
*/
@GetMapping("/{id}/preview") @GetMapping("/{id}/preview")
public ResponseEntity<byte[]> previewFile( public ResponseEntity<byte[]> previewFile(
@AuthenticationPrincipal UserPrincipal principal, @AuthenticationPrincipal UserPrincipal principal,
@@ -190,7 +192,7 @@ public class FileController {
} }
@PostMapping("/{id}/shareFile") @PostMapping("/{id}/shareFile")
public ResponseEntity<?> shareFile( public ApiResult<FileShare> shareFile(
@AuthenticationPrincipal UserPrincipal principal, @AuthenticationPrincipal UserPrincipal principal,
@PathVariable Long id, @PathVariable Long id,
@RequestBody Map<String, Object> request) { @RequestBody Map<String, Object> request) {
@@ -198,26 +200,26 @@ public class FileController {
Long shareToUserId = Long.valueOf(request.get("userId").toString()); Long shareToUserId = Long.valueOf(request.get("userId").toString());
String permission = (String) request.getOrDefault("permission", "view"); String permission = (String) request.getOrDefault("permission", "view");
FileShare share = fileService.shareFile(id, principal.getUserId(), shareToUserId, permission); FileShare share = fileService.shareFile(id, principal.getUserId(), shareToUserId, permission);
return ResponseEntity.ok(Map.of("data", share, "message", "共享成功")); return ApiResult.success("共享成功", share);
} catch (RuntimeException e) { } catch (RuntimeException e) {
return ResponseEntity.badRequest().body(Map.of("message", e.getMessage())); return ApiResult.error(e.getMessage());
} }
} }
@DeleteMapping("/{id}/cancelShare") @DeleteMapping("/{id}/cancelShare")
public ResponseEntity<?> cancelShare( public ApiResult<Void> cancelShare(
@AuthenticationPrincipal UserPrincipal principal, @AuthenticationPrincipal UserPrincipal principal,
@PathVariable Long id) { @PathVariable Long id) {
try { try {
fileService.cancelShare(id, principal.getUserId()); fileService.cancelShare(id, principal.getUserId());
return ResponseEntity.ok(Map.of("message", "已取消共享")); return ApiResult.success("已取消共享");
} catch (RuntimeException e) { } catch (RuntimeException e) {
return ResponseEntity.badRequest().body(Map.of("message", e.getMessage())); return ApiResult.error(e.getMessage());
} }
} }
/** /**
* 获取头像图片 * 获取头像图片(返回 ResponseEntity<byte[]> 用于二进制响应)
*/ */
@GetMapping("/avatar/**") @GetMapping("/avatar/**")
public ResponseEntity<byte[]> getAvatar(jakarta.servlet.http.HttpServletRequest request) throws IOException { public ResponseEntity<byte[]> getAvatar(jakarta.servlet.http.HttpServletRequest request) throws IOException {
@@ -257,24 +259,24 @@ public class FileController {
} }
@PutMapping("/{id}/rename") @PutMapping("/{id}/rename")
public ResponseEntity<?> renameFile( public ApiResult<Void> renameFile(
@AuthenticationPrincipal UserPrincipal principal, @AuthenticationPrincipal UserPrincipal principal,
@PathVariable Long id, @PathVariable Long id,
@RequestBody Map<String, String> request) { @RequestBody Map<String, String> request) {
String newName = request.get("name"); String newName = request.get("name");
if (newName == null || newName.trim().isEmpty()) { if (newName == null || newName.trim().isEmpty()) {
return ResponseEntity.badRequest().body(Map.of("message", "名称不能为空")); return ApiResult.error("名称不能为空");
} }
try { try {
fileService.renameFile(id, principal.getUserId(), newName.trim()); fileService.renameFile(id, principal.getUserId(), newName.trim());
return ResponseEntity.ok(Map.of("message", "重命名成功")); return ApiResult.success("重命名成功");
} catch (RuntimeException e) { } catch (RuntimeException e) {
return ResponseEntity.badRequest().body(Map.of("message", e.getMessage())); return ApiResult.error(e.getMessage());
} }
} }
@PutMapping("/{id}/move") @PutMapping("/{id}/move")
public ResponseEntity<?> moveFile( public ApiResult<Void> moveFile(
@AuthenticationPrincipal UserPrincipal principal, @AuthenticationPrincipal UserPrincipal principal,
@PathVariable Long id, @PathVariable Long id,
@RequestBody Map<String, Object> request) { @RequestBody Map<String, Object> request) {
@@ -285,20 +287,25 @@ public class FileController {
} }
try { try {
fileService.moveFile(id, principal.getUserId(), folderId); fileService.moveFile(id, principal.getUserId(), folderId);
return ResponseEntity.ok(Map.of("message", "移动成功")); return ApiResult.success("移动成功");
} catch (RuntimeException e) { } catch (RuntimeException e) {
return ResponseEntity.badRequest().body(Map.of("message", e.getMessage())); return ApiResult.error(e.getMessage());
} }
} }
/**
* 批量下载(返回 ZIP 文件,使用 HttpServletResponse 直接写入)
*/
@PostMapping("/batchDownload") @PostMapping("/batchDownload")
public ResponseEntity<?> batchDownload( public void batchDownload(
@AuthenticationPrincipal UserPrincipal principal, @AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, Object> request, @RequestBody Map<String, Object> request,
HttpServletResponse response) throws IOException { HttpServletResponse response) throws IOException {
Object idsObj = request.get("ids"); Object idsObj = request.get("ids");
if (idsObj == null) { if (idsObj == null) {
return ResponseEntity.badRequest().body(Map.of("message", "请选择要下载的文件")); response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":400,\"message\":\"请选择要下载的文件\"}");
return;
} }
List<Long> ids = new ArrayList<>(); List<Long> ids = new ArrayList<>();
@@ -313,7 +320,9 @@ public class FileController {
} }
if (ids.isEmpty()) { if (ids.isEmpty()) {
return ResponseEntity.badRequest().body(Map.of("message", "请选择要下载的文件")); response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":400,\"message\":\"请选择要下载的文件\"}");
return;
} }
try { try {
@@ -324,29 +333,28 @@ public class FileController {
response.setContentLength(zipBytes.length); response.setContentLength(zipBytes.length);
response.getOutputStream().write(zipBytes); response.getOutputStream().write(zipBytes);
response.getOutputStream().flush(); response.getOutputStream().flush();
return ResponseEntity.ok().build();
} catch (RuntimeException e) { } catch (RuntimeException e) {
return ResponseEntity.badRequest().body(Map.of("message", e.getMessage())); response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":400,\"message\":\"" + e.getMessage().replace("\"", "\\\"") + "\"}");
} }
} }
@GetMapping("/movableFolders") @GetMapping("/movableFolders")
public ResponseEntity<?> getMovableFolders( public ApiResult<List<FileEntity>> getMovableFolders(
@AuthenticationPrincipal UserPrincipal principal, @AuthenticationPrincipal UserPrincipal principal,
@RequestParam(required = false) List<Long> excludeIds, @RequestParam(required = false) List<Long> excludeIds,
@RequestParam(required = false) Long currentFolderId) { @RequestParam(required = false) Long currentFolderId) {
List<FileEntity> folders = fileService.getMovableFolders(principal.getUserId(), excludeIds, currentFolderId); List<FileEntity> folders = fileService.getMovableFolders(principal.getUserId(), excludeIds, currentFolderId);
return ResponseEntity.ok(Map.of("data", folders)); return ApiResult.success(folders);
} }
@PostMapping("/batchCancelShare") @PostMapping("/batchCancelShare")
public ResponseEntity<?> batchCancelShare( public ApiResult<Void> batchCancelShare(
@AuthenticationPrincipal UserPrincipal principal, @AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, Object> request) { @RequestBody Map<String, Object> request) {
Object idsObj = request.get("ids"); Object idsObj = request.get("ids");
if (idsObj == null) { if (idsObj == null) {
return ResponseEntity.badRequest().body(Map.of("message", "请选择要取消共享的文件")); return ApiResult.error("请选择要取消共享的文件");
} }
List<Long> ids = new ArrayList<>(); List<Long> ids = new ArrayList<>();
if (idsObj instanceof List) { if (idsObj instanceof List) {
@@ -361,16 +369,16 @@ public class FileController {
for (Long fileId : ids) { for (Long fileId : ids) {
fileService.cancelShare(fileId, principal.getUserId()); fileService.cancelShare(fileId, principal.getUserId());
} }
return ResponseEntity.ok(Map.of("message", "批量取消共享成功")); return ApiResult.success("批量取消共享成功");
} }
@PostMapping("/batchRestore") @PostMapping("/batchRestore")
public ResponseEntity<?> batchRestore( public ApiResult<Void> batchRestore(
@AuthenticationPrincipal UserPrincipal principal, @AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, Object> request) { @RequestBody Map<String, Object> request) {
Object idsObj = request.get("ids"); Object idsObj = request.get("ids");
if (idsObj == null) { if (idsObj == null) {
return ResponseEntity.badRequest().body(Map.of("message", "请选择要还原的文件")); return ApiResult.error("请选择要还原的文件");
} }
List<Long> ids = new ArrayList<>(); List<Long> ids = new ArrayList<>();
if (idsObj instanceof List) { if (idsObj instanceof List) {
@@ -385,6 +393,6 @@ public class FileController {
for (Long fileId : ids) { for (Long fileId : ids) {
fileService.restoreFile(fileId, principal.getUserId()); fileService.restoreFile(fileId, principal.getUserId());
} }
return ResponseEntity.ok(Map.of("message", "批量还原成功")); return ApiResult.success("批量还原成功");
} }
} }

View File

@@ -1,14 +1,14 @@
package com.filesystem.controller; package com.filesystem.controller;
import org.springframework.stereotype.Controller; import com.filesystem.utils.ApiResult;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@Controller @RestController
public class IndexController { public class IndexController {
// 所有非 /api/**、非 /ws/** 的前端路由,统一返回 index.html @GetMapping("/")
@GetMapping({"/", "/login", "/register", "/desktop", "/desktop/**"}) public ApiResult<String> index() {
public String forward() { return ApiResult.success("File System API is running");
return "forward:/webapp/index.html";
} }
} }

View File

@@ -5,6 +5,7 @@ import com.filesystem.entity.User;
import com.filesystem.security.UserPrincipal; import com.filesystem.security.UserPrincipal;
import com.filesystem.service.MessageService; import com.filesystem.service.MessageService;
import com.filesystem.service.UserService; import com.filesystem.service.UserService;
import com.filesystem.utils.ApiResult;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
@@ -45,12 +46,12 @@ public class MessageController {
// ==================== 聊天文件上传 ==================== // ==================== 聊天文件上传 ====================
@PostMapping("/upload") @PostMapping("/upload")
public ResponseEntity<?> uploadChatFile( public ApiResult<Map<String, String>> uploadChatFile(
@AuthenticationPrincipal UserPrincipal principal, @AuthenticationPrincipal UserPrincipal principal,
@RequestParam("file") MultipartFile file) throws IOException { @RequestParam("file") MultipartFile file) throws IOException {
if (file.isEmpty()) { if (file.isEmpty()) {
return ResponseEntity.badRequest().body(Map.of("message", "请选择文件")); return ApiResult.error("请选择文件");
} }
String originalName = file.getOriginalFilename(); String originalName = file.getOriginalFilename();
@@ -68,10 +69,10 @@ public class MessageController {
file.transferTo(filePath.toFile()); file.transferTo(filePath.toFile());
String fileUrl = "/api/messages/file/" + datePath + "/" + storedName; String fileUrl = "/api/messages/file/" + datePath + "/" + storedName;
return ResponseEntity.ok(Map.of("url", fileUrl, "message", "上传成功")); return ApiResult.success("上传成功", Map.of("url", fileUrl));
} }
// ==================== 聊天文件访问 ==================== // ==================== 聊天文件访问(返回 ResponseEntity<byte[]> 用于二进制响应)====================
@GetMapping("/file/**") @GetMapping("/file/**")
public ResponseEntity<byte[]> getChatFile(HttpServletRequest request) throws IOException { public ResponseEntity<byte[]> getChatFile(HttpServletRequest request) throws IOException {
@@ -122,7 +123,7 @@ public class MessageController {
// ==================== 消息收发 ==================== // ==================== 消息收发 ====================
@GetMapping @GetMapping
public ResponseEntity<?> getMessages( public ApiResult<List<Map<String, Object>>> getMessages(
@AuthenticationPrincipal UserPrincipal principal, @AuthenticationPrincipal UserPrincipal principal,
@RequestParam Long userId) { @RequestParam Long userId) {
List<Message> messages = messageService.getMessages(principal.getUserId(), userId); List<Message> messages = messageService.getMessages(principal.getUserId(), userId);
@@ -169,11 +170,11 @@ public class MessageController {
return m; return m;
}).collect(Collectors.toList()); }).collect(Collectors.toList());
return ResponseEntity.ok(Map.of("data", result)); return ApiResult.success(result);
} }
@GetMapping("/users") @GetMapping("/users")
public ResponseEntity<?> getUsers(@AuthenticationPrincipal UserPrincipal principal) { public ApiResult<List<Map<String, Object>>> getUsers(@AuthenticationPrincipal UserPrincipal principal) {
List<User> users = userService.getAllUsersExcept(principal.getUserId()); List<User> users = userService.getAllUsersExcept(principal.getUserId());
List<Map<String, Object>> result = users.stream() List<Map<String, Object>> result = users.stream()
.map(u -> { .map(u -> {
@@ -188,11 +189,11 @@ public class MessageController {
return m; return m;
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
return ResponseEntity.ok(Map.of("data", result)); return ApiResult.success(result);
} }
@PostMapping @PostMapping
public ResponseEntity<?> sendMessage( public ApiResult<Map<String, Object>> sendMessage(
@AuthenticationPrincipal UserPrincipal principal, @AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, Object> request) { @RequestBody Map<String, Object> request) {
Long toUserId = Long.valueOf(request.get("toUserId").toString()); Long toUserId = Long.valueOf(request.get("toUserId").toString());
@@ -227,17 +228,17 @@ public class MessageController {
result.put("fromSignature", fromUser.getSignature()); result.put("fromSignature", fromUser.getSignature());
} }
return ResponseEntity.ok(Map.of("data", result, "message", "发送成功")); return ApiResult.success("发送成功", result);
} }
@GetMapping("/unreadCount") @GetMapping("/unreadCount")
public ResponseEntity<?> getUnreadCount(@AuthenticationPrincipal UserPrincipal principal) { public ApiResult<Map<String, Integer>> getUnreadCount(@AuthenticationPrincipal UserPrincipal principal) {
int count = messageService.getUnreadCount(principal.getUserId()); int count = messageService.getUnreadCount(principal.getUserId());
return ResponseEntity.ok(Map.of("data", Map.of("count", count))); return ApiResult.success(Map.of("count", count));
} }
@GetMapping("/unreadList") @GetMapping("/unreadList")
public ResponseEntity<?> getUnreadList(@AuthenticationPrincipal UserPrincipal principal) { public ApiResult<List<Map<String, Object>>> getUnreadList(@AuthenticationPrincipal UserPrincipal principal) {
List<Message> unreadMessages = messageService.getUnreadMessages(principal.getUserId()); List<Message> unreadMessages = messageService.getUnreadMessages(principal.getUserId());
// 按发送人分组 // 按发送人分组
@@ -268,12 +269,12 @@ public class MessageController {
result.add(item); result.add(item);
} }
return ResponseEntity.ok(Map.of("data", result)); return ApiResult.success(result);
} }
@PostMapping("/{id}/read") @PostMapping("/{id}/read")
public ResponseEntity<?> markAsRead(@PathVariable Long id) { public ApiResult<Void> markAsRead(@PathVariable Long id) {
messageService.markAsRead(id); messageService.markAsRead(id);
return ResponseEntity.ok(Map.of("message", "已标记已读")); return ApiResult.success("已标记已读");
} }
} }

View File

@@ -3,9 +3,9 @@ package com.filesystem.controller;
import com.filesystem.entity.User; import com.filesystem.entity.User;
import com.filesystem.security.UserPrincipal; import com.filesystem.security.UserPrincipal;
import com.filesystem.service.UserService; import com.filesystem.service.UserService;
import com.filesystem.utils.ApiResult;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@@ -14,9 +14,9 @@ import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.List; import java.time.LocalDateTime;
import java.util.Map; import java.time.format.DateTimeFormatter;
import java.util.UUID; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@RestController @RestController
@@ -29,16 +29,18 @@ public class UserController {
@Value("${file.storage.path:./uploads}") @Value("${file.storage.path:./uploads}")
private String storagePath; private String storagePath;
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy/MM");
/** /**
* 获取所有可用用户(用于文件共享等场景) * 获取所有可用用户(用于文件共享等场景)
*/ */
@GetMapping @GetMapping
public ResponseEntity<?> getAllUsers(@AuthenticationPrincipal UserPrincipal principal) { public ApiResult<List<Map<String, Object>>> getAllUsers(@AuthenticationPrincipal UserPrincipal principal) {
List<User> users = userService.getAllUsersExcept(principal.getUserId()); List<User> users = userService.getAllUsersExcept(principal.getUserId());
List<Map<String, Object>> result = users.stream() List<Map<String, Object>> result = users.stream()
.filter(u -> u.getStatus() == 1) // 只返回启用状态的用户 .filter(u -> u.getStatus() == 1) // 只返回启用状态的用户
.map(u -> { .map(u -> {
Map<String, Object> m = new java.util.HashMap<>(); Map<String, Object> m = new HashMap<>();
m.put("id", u.getId()); m.put("id", u.getId());
m.put("username", u.getUsername()); m.put("username", u.getUsername());
m.put("nickname", u.getNickname() != null ? u.getNickname() : u.getUsername()); m.put("nickname", u.getNickname() != null ? u.getNickname() : u.getUsername());
@@ -47,24 +49,24 @@ public class UserController {
return m; return m;
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
return ResponseEntity.ok(Map.of("data", result)); return ApiResult.success(result);
} }
/** /**
* 上传头像 * 上传头像
*/ */
@PostMapping("/avatar") @PostMapping("/avatar")
public ResponseEntity<?> uploadAvatar( public ApiResult<Map<String, String>> uploadAvatar(
@AuthenticationPrincipal UserPrincipal principal, @AuthenticationPrincipal UserPrincipal principal,
@RequestParam("avatar") MultipartFile file) throws IOException { @RequestParam("avatar") MultipartFile file) throws IOException {
if (file.isEmpty()) { if (file.isEmpty()) {
return ResponseEntity.badRequest().body(Map.of("message", "请选择图片")); return ApiResult.error("请选择图片");
} }
// 限制文件大小 2MB // 限制文件大小 2MB
if (file.getSize() > 2 * 1024 * 1024) { if (file.getSize() > 2 * 1024 * 1024) {
return ResponseEntity.badRequest().body(Map.of("message", "图片大小不能超过2MB")); return ApiResult.error("图片大小不能超过2MB");
} }
// 保存文件 // 保存文件
@@ -73,7 +75,7 @@ public class UserController {
? originalFilename.substring(originalFilename.lastIndexOf(".")) ? originalFilename.substring(originalFilename.lastIndexOf("."))
: ".jpg"; : ".jpg";
String fileName = UUID.randomUUID().toString() + ext; String fileName = UUID.randomUUID().toString() + ext;
String datePath = java.time.LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy/MM")); String datePath = LocalDateTime.now().format(DATE_FMT);
// 使用配置文件中的路径 + 日期目录 // 使用配置文件中的路径 + 日期目录
Path uploadPath = Paths.get(storagePath).toAbsolutePath().resolve("avatars").resolve(datePath); Path uploadPath = Paths.get(storagePath).toAbsolutePath().resolve("avatars").resolve(datePath);
@@ -88,16 +90,16 @@ public class UserController {
String avatarUrl = "/api/files/avatar/" + datePath + "/" + fileName; String avatarUrl = "/api/files/avatar/" + datePath + "/" + fileName;
userService.updateAvatar(principal.getUserId(), avatarUrl); userService.updateAvatar(principal.getUserId(), avatarUrl);
return ResponseEntity.ok(Map.of("data", Map.of("url", avatarUrl), "message", "上传成功")); return ApiResult.success("上传成功", Map.of("url", avatarUrl));
} }
/** /**
* 获取当前用户信息 * 获取当前用户信息
*/ */
@GetMapping("/me") @GetMapping("/me")
public ResponseEntity<?> getCurrentUser(@AuthenticationPrincipal UserPrincipal principal) { public ApiResult<Map<String, Object>> getCurrentUser(@AuthenticationPrincipal UserPrincipal principal) {
User user = userService.findById(principal.getUserId()); User user = userService.findById(principal.getUserId());
Map<String, Object> result = new java.util.HashMap<>(); Map<String, Object> result = new HashMap<>();
result.put("id", user.getId()); result.put("id", user.getId());
result.put("username", user.getUsername()); result.put("username", user.getUsername());
result.put("nickname", user.getNickname()); result.put("nickname", user.getNickname());
@@ -105,14 +107,14 @@ public class UserController {
result.put("signature", user.getSignature()); result.put("signature", user.getSignature());
result.put("email", user.getEmail()); result.put("email", user.getEmail());
result.put("phone", user.getPhone()); result.put("phone", user.getPhone());
return ResponseEntity.ok(Map.of("data", result)); return ApiResult.success(result);
} }
/** /**
* 更新个人信息 * 更新个人信息
*/ */
@PutMapping("/profile") @PutMapping("/profile")
public ResponseEntity<?> updateProfile( public ApiResult<Void> updateProfile(
@AuthenticationPrincipal UserPrincipal principal, @AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, String> request) { @RequestBody Map<String, String> request) {
try { try {
@@ -121,9 +123,9 @@ public class UserController {
String phone = request.get("phone"); String phone = request.get("phone");
String email = request.get("email"); String email = request.get("email");
userService.updateProfile(principal.getUserId(), nickname, signature, phone, email); userService.updateProfile(principal.getUserId(), nickname, signature, phone, email);
return ResponseEntity.ok(Map.of("message", "更新成功")); return ApiResult.success("更新成功");
} catch (RuntimeException e) { } catch (RuntimeException e) {
return ResponseEntity.badRequest().body(Map.of("message", e.getMessage())); return ApiResult.error(e.getMessage());
} }
} }
@@ -131,21 +133,21 @@ public class UserController {
* 修改密码 * 修改密码
*/ */
@PutMapping("/password") @PutMapping("/password")
public ResponseEntity<?> changePassword( public ApiResult<Void> changePassword(
@AuthenticationPrincipal UserPrincipal principal, @AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, String> request) { @RequestBody Map<String, String> request) {
String oldPassword = request.get("oldPassword"); String oldPassword = request.get("oldPassword");
String newPassword = request.get("newPassword"); String newPassword = request.get("newPassword");
if (oldPassword == null || oldPassword.isEmpty() || newPassword == null || newPassword.isEmpty()) { if (oldPassword == null || oldPassword.isEmpty() || newPassword == null || newPassword.isEmpty()) {
return ResponseEntity.badRequest().body(Map.of("message", "请填写完整信息")); return ApiResult.error("请填写完整信息");
} }
try { try {
userService.changePassword(principal.getUserId(), oldPassword, newPassword); userService.changePassword(principal.getUserId(), oldPassword, newPassword);
return ResponseEntity.ok(Map.of("message", "密码修改成功")); return ApiResult.success("密码修改成功");
} catch (Exception e) { } catch (Exception e) {
return ResponseEntity.badRequest().body(Map.of("message", e.getMessage())); return ApiResult.error(e.getMessage());
} }
} }
} }

View File

@@ -0,0 +1,78 @@
package com.filesystem.utils;
import lombok.Data;
/**
* 统一API响应实体类
*/
@Data
public class ApiResult<T> {
private int code;
private String message;
private T data;
public ApiResult() {}
public ApiResult(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
// ========== 成功 ==========
public static <T> ApiResult<T> success() {
return new ApiResult<>(200, "success", null);
}
public static <T> ApiResult<T> success(T data) {
return new ApiResult<>(200, "success", data);
}
public static <T> ApiResult<T> success(String message, T data) {
return new ApiResult<>(200, message, data);
}
public static <T> ApiResult<T> success(String message) {
return new ApiResult<>(200, message, null);
}
// ========== 错误 ==========
public static <T> ApiResult<T> error(String message) {
return new ApiResult<>(400, message, null);
}
public static <T> ApiResult<T> error(int code, String message) {
return new ApiResult<>(code, message, null);
}
public static <T> ApiResult<T> error(int code, String message, T data) {
return new ApiResult<>(code, message, data);
}
// ========== 未授权 ==========
public static <T> ApiResult<T> unauthorized(String message) {
return new ApiResult<>(401, message, null);
}
// ========== 禁止 ==========
public static <T> ApiResult<T> forbidden(String message) {
return new ApiResult<>(403, message, null);
}
// ========== 未找到 ==========
public static <T> ApiResult<T> notFound(String message) {
return new ApiResult<>(404, message, null);
}
// ========== 服务器错误 ==========
public static <T> ApiResult<T> serverError(String message) {
return new ApiResult<>(500, message, null);
}
}

View File

@@ -0,0 +1,34 @@
package com.filesystem.utils;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* 密码工具类
*/
public class PasswordUtil {
private static final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
/**
* 加密密码
*/
public static String encode(String rawPassword) {
return encoder.encode(rawPassword);
}
/**
* 验证密码
*/
public static boolean matches(String rawPassword, String encodedPassword) {
return encoder.matches(rawPassword, encodedPassword);
}
public static void main(String[] args) {
// 生成 admin123 的密码哈希
String password = "system";
String hash = encode(password);
System.out.println("Password: " + password);
System.out.println("BCrypt Hash: " + hash);
System.out.println("Matches: " + matches(password, hash));
}
}

View File

@@ -1,4 +1,4 @@
import axios from 'axios' import axios from 'axios'
const request = axios.create({ const request = axios.create({
baseURL: '/api', baseURL: '/api',
@@ -17,9 +17,35 @@ request.interceptors.request.use(
) )
request.interceptors.response.use( request.interceptors.response.use(
response => response.data, response => {
const res = response.data
// 如果是二进制数据blob直接返回
if (response.config.responseType === 'blob') {
return res
}
// 统一响应格式: { code, message, data }
// code === 200 表示成功,返回 data
if (res.code === 200) {
return res.data
}
// 业务错误,抛出异常
const error = new Error(res.message || '请求失败')
error.code = res.code
error.response = response
return Promise.reject(error)
},
error => { error => {
const status = error.response?.status const status = error.response?.status
const data = error.response?.data
// 如果响应体中有错误信息,优先使用
if (data && typeof data === 'object' && data.message) {
error.message = data.message
error.code = data.code
}
// 只有 401/403 才清理 token 并跳转登录页 // 只有 401/403 才清理 token 并跳转登录页
// 但在登录页时不跳转(避免死循环),登录接口的 401 也不跳转 // 但在登录页时不跳转(避免死循环),登录接口的 401 也不跳转