重构云文件管理系统

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

2
.idea/misc.xml generated
View File

@@ -8,5 +8,5 @@
</list>
</option>
</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>

View File

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

View File

@@ -3,8 +3,8 @@ package com.filesystem.controller;
import com.filesystem.entity.User;
import com.filesystem.security.UserPrincipal;
import com.filesystem.service.UserService;
import com.filesystem.utils.ApiResult;
import jakarta.annotation.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
@@ -19,7 +19,7 @@ public class AuthController {
private UserService userService;
@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 password = request.get("password");
@@ -46,47 +46,43 @@ public class AuthController {
result.put("token", token);
result.put("user", userData);
Map<String, Object> body = new HashMap<>();
body.put("data", result);
body.put("message", "登录成功");
return ResponseEntity.ok(body);
return ApiResult.success("登录成功", result);
} catch (Exception e) {
return ResponseEntity.badRequest().body(Map.of("message", e.getMessage()));
return ApiResult.error(e.getMessage());
}
}
@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 password = request.get("password");
String nickname = request.get("nickname");
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);
return ResponseEntity.ok(Map.of("message", "注册成功", "data", Map.of("id", user.getId())));
return ApiResult.success("注册成功", Map.of("id", user.getId()));
}
@PostMapping("/logout")
public ResponseEntity<?> logout(@AuthenticationPrincipal UserPrincipal principal) {
public ApiResult<Void> logout(@AuthenticationPrincipal UserPrincipal principal) {
if (principal != null) {
userService.logout(principal.getUserId());
}
return ResponseEntity.ok(Map.of("message", "退出成功"));
return ApiResult.success("退出成功");
}
@GetMapping("/info")
public ResponseEntity<?> getUserInfo(@AuthenticationPrincipal UserPrincipal principal) {
public ApiResult<Map<String, Object>> getUserInfo(@AuthenticationPrincipal UserPrincipal principal) {
if (principal == null) {
return ResponseEntity.status(401).body(Map.of("message", "未登录"));
return ApiResult.unauthorized("未登录");
}
User user = userService.findById(principal.getUserId());
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("storageLimit", storageLimit);
Map<String, Object> body = new HashMap<>();
body.put("data", userData);
return ResponseEntity.ok(body);
return ApiResult.success(userData);
}
}

View File

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

View File

@@ -1,14 +1,14 @@
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.RestController;
@Controller
@RestController
public class IndexController {
// 所有非 /api/**、非 /ws/** 的前端路由,统一返回 index.html
@GetMapping({"/", "/login", "/register", "/desktop", "/desktop/**"})
public String forward() {
return "forward:/webapp/index.html";
@GetMapping("/")
public ApiResult<String> index() {
return ApiResult.success("File System API is running");
}
}

View File

@@ -5,6 +5,7 @@ import com.filesystem.entity.User;
import com.filesystem.security.UserPrincipal;
import com.filesystem.service.MessageService;
import com.filesystem.service.UserService;
import com.filesystem.utils.ApiResult;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
@@ -45,12 +46,12 @@ public class MessageController {
// ==================== 聊天文件上传 ====================
@PostMapping("/upload")
public ResponseEntity<?> uploadChatFile(
public ApiResult<Map<String, String>> uploadChatFile(
@AuthenticationPrincipal UserPrincipal principal,
@RequestParam("file") MultipartFile file) throws IOException {
if (file.isEmpty()) {
return ResponseEntity.badRequest().body(Map.of("message", "请选择文件"));
return ApiResult.error("请选择文件");
}
String originalName = file.getOriginalFilename();
@@ -68,10 +69,10 @@ public class MessageController {
file.transferTo(filePath.toFile());
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/**")
public ResponseEntity<byte[]> getChatFile(HttpServletRequest request) throws IOException {
@@ -122,7 +123,7 @@ public class MessageController {
// ==================== 消息收发 ====================
@GetMapping
public ResponseEntity<?> getMessages(
public ApiResult<List<Map<String, Object>>> getMessages(
@AuthenticationPrincipal UserPrincipal principal,
@RequestParam Long userId) {
List<Message> messages = messageService.getMessages(principal.getUserId(), userId);
@@ -169,11 +170,11 @@ public class MessageController {
return m;
}).collect(Collectors.toList());
return ResponseEntity.ok(Map.of("data", result));
return ApiResult.success(result);
}
@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<Map<String, Object>> result = users.stream()
.map(u -> {
@@ -188,11 +189,11 @@ public class MessageController {
return m;
})
.collect(Collectors.toList());
return ResponseEntity.ok(Map.of("data", result));
return ApiResult.success(result);
}
@PostMapping
public ResponseEntity<?> sendMessage(
public ApiResult<Map<String, Object>> sendMessage(
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, Object> request) {
Long toUserId = Long.valueOf(request.get("toUserId").toString());
@@ -227,17 +228,17 @@ public class MessageController {
result.put("fromSignature", fromUser.getSignature());
}
return ResponseEntity.ok(Map.of("data", result, "message", "发送成功"));
return ApiResult.success("发送成功", result);
}
@GetMapping("/unreadCount")
public ResponseEntity<?> getUnreadCount(@AuthenticationPrincipal UserPrincipal principal) {
public ApiResult<Map<String, Integer>> getUnreadCount(@AuthenticationPrincipal UserPrincipal principal) {
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")
public ResponseEntity<?> getUnreadList(@AuthenticationPrincipal UserPrincipal principal) {
public ApiResult<List<Map<String, Object>>> getUnreadList(@AuthenticationPrincipal UserPrincipal principal) {
List<Message> unreadMessages = messageService.getUnreadMessages(principal.getUserId());
// 按发送人分组
@@ -268,12 +269,12 @@ public class MessageController {
result.add(item);
}
return ResponseEntity.ok(Map.of("data", result));
return ApiResult.success(result);
}
@PostMapping("/{id}/read")
public ResponseEntity<?> markAsRead(@PathVariable Long id) {
public ApiResult<Void> markAsRead(@PathVariable Long 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.security.UserPrincipal;
import com.filesystem.service.UserService;
import com.filesystem.utils.ApiResult;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@@ -14,9 +14,9 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
@RestController
@@ -29,16 +29,18 @@ public class UserController {
@Value("${file.storage.path:./uploads}")
private String storagePath;
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy/MM");
/**
* 获取所有可用用户(用于文件共享等场景)
*/
@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<Map<String, Object>> result = users.stream()
.filter(u -> u.getStatus() == 1) // 只返回启用状态的用户
.map(u -> {
Map<String, Object> m = new java.util.HashMap<>();
Map<String, Object> m = new HashMap<>();
m.put("id", u.getId());
m.put("username", u.getUsername());
m.put("nickname", u.getNickname() != null ? u.getNickname() : u.getUsername());
@@ -47,24 +49,24 @@ public class UserController {
return m;
})
.collect(Collectors.toList());
return ResponseEntity.ok(Map.of("data", result));
return ApiResult.success(result);
}
/**
* 上传头像
*/
@PostMapping("/avatar")
public ResponseEntity<?> uploadAvatar(
public ApiResult<Map<String, String>> uploadAvatar(
@AuthenticationPrincipal UserPrincipal principal,
@RequestParam("avatar") MultipartFile file) throws IOException {
if (file.isEmpty()) {
return ResponseEntity.badRequest().body(Map.of("message", "请选择图片"));
return ApiResult.error("请选择图片");
}
// 限制文件大小 2MB
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("."))
: ".jpg";
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);
@@ -88,16 +90,16 @@ public class UserController {
String avatarUrl = "/api/files/avatar/" + datePath + "/" + fileName;
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")
public ResponseEntity<?> getCurrentUser(@AuthenticationPrincipal UserPrincipal principal) {
public ApiResult<Map<String, Object>> getCurrentUser(@AuthenticationPrincipal UserPrincipal principal) {
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("username", user.getUsername());
result.put("nickname", user.getNickname());
@@ -105,14 +107,14 @@ public class UserController {
result.put("signature", user.getSignature());
result.put("email", user.getEmail());
result.put("phone", user.getPhone());
return ResponseEntity.ok(Map.of("data", result));
return ApiResult.success(result);
}
/**
* 更新个人信息
*/
@PutMapping("/profile")
public ResponseEntity<?> updateProfile(
public ApiResult<Void> updateProfile(
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, String> request) {
try {
@@ -121,9 +123,9 @@ public class UserController {
String phone = request.get("phone");
String email = request.get("email");
userService.updateProfile(principal.getUserId(), nickname, signature, phone, email);
return ResponseEntity.ok(Map.of("message", "更新成功"));
return ApiResult.success("更新成功");
} 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")
public ResponseEntity<?> changePassword(
public ApiResult<Void> changePassword(
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, String> request) {
String oldPassword = request.get("oldPassword");
String newPassword = request.get("newPassword");
if (oldPassword == null || oldPassword.isEmpty() || newPassword == null || newPassword.isEmpty()) {
return ResponseEntity.badRequest().body(Map.of("message", "请填写完整信息"));
return ApiResult.error("请填写完整信息");
}
try {
userService.changePassword(principal.getUserId(), oldPassword, newPassword);
return ResponseEntity.ok(Map.of("message", "密码修改成功"));
return ApiResult.success("密码修改成功");
} 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({
baseURL: '/api',
@@ -17,9 +17,35 @@ request.interceptors.request.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 => {
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 也不跳转