Compare commits

6 Commits
master ... dev

Author SHA1 Message Date
575a8e3341 重构云文件管理系统 2026-04-02 23:49:20 +08:00
df1ff66c1f 重构云文件管理系统 2026-04-02 23:48:57 +08:00
453f05cca3 重构云文件管理系统 2026-04-02 23:36:14 +08:00
caa39ed7d2 重构云文件管理系统 2026-04-02 23:36:02 +08:00
38bcbd6ed2 重构云文件管理系统 2026-04-02 23:35:48 +08:00
ceb6c8258c 重构云文件管理系统 2026-04-02 23:35:19 +08:00
57 changed files with 909 additions and 355 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,25 +4,18 @@ 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 com.filesystem.utils.CommonUtil;
import com.filesystem.utils.FileUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
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.List;
import java.util.Map;
@@ -36,121 +29,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;
String name = CommonUtil.getString(request, "name");
Long parentId = CommonUtil.getLong(request, "parentId");
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());
}
}
/**
* 下载文件
*/
@GetMapping("/{id}/download")
public ResponseEntity<byte[]> downloadFile(
@AuthenticationPrincipal UserPrincipal principal,
@@ -160,16 +153,15 @@ public class FileController {
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);
return FileUtil.buildDownloadResponse(content, file.getName());
} catch (RuntimeException e) {
return ResponseEntity.badRequest().build();
}
}
/**
* 预览文件
*/
@GetMapping("/{id}/preview")
public ResponseEntity<byte[]> previewFile(
@AuthenticationPrincipal UserPrincipal principal,
@@ -179,40 +171,36 @@ public class FileController {
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);
return FileUtil.buildPreviewResponse(content, file.getName());
} catch (RuntimeException e) {
return ResponseEntity.badRequest().build();
}
}
@PostMapping("/{id}/shareFile")
public ResponseEntity<?> shareFile(
public ApiResult<FileShare> shareFile(
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable Long id,
@RequestBody Map<String, Object> request) {
try {
Long shareToUserId = Long.valueOf(request.get("userId").toString());
String permission = (String) request.getOrDefault("permission", "view");
Long shareToUserId = CommonUtil.getLong(request, "userId");
String permission = CommonUtil.getString(request, "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());
}
}
@@ -224,167 +212,106 @@ public class FileController {
String uri = request.getRequestURI();
String prefix = "/api/files/avatar/";
String relativePath = uri.substring(uri.indexOf(prefix) + prefix.length());
// relativePath = "2026/04/xxx.jpg"
Path filePath = Paths.get(storagePath).toAbsolutePath().resolve("avatars").resolve(relativePath);
if (!Files.exists(filePath)) {
java.nio.file.Path filePath = java.nio.file.Paths.get(storagePath).toAbsolutePath().resolve("avatars").resolve(relativePath);
if (!java.nio.file.Files.exists(filePath)) {
return ResponseEntity.notFound().build();
}
byte[] content = Files.readAllBytes(filePath);
byte[] content = java.nio.file.Files.readAllBytes(filePath);
String fileName = relativePath.contains("/") ? relativePath.substring(relativePath.lastIndexOf("/") + 1) : relativePath;
String ext = fileName.contains(".") ? fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase() : "jpg";
String contentType = switch (ext) {
case "png" -> "image/png";
case "gif" -> "image/gif";
case "webp" -> "image/webp";
default -> "image/jpeg";
};
return ResponseEntity.ok()
.contentType(org.springframework.http.MediaType.parseMediaType(contentType))
.header(HttpHeaders.CACHE_CONTROL, "max-age=86400")
.body(content);
}
private MediaType getMediaType(String filename) {
String ext = filename.contains(".") ? filename.substring(filename.lastIndexOf(".")).toLowerCase() : "";
return switch (ext) {
case ".jpg", ".jpeg" -> MediaType.IMAGE_JPEG;
case ".png" -> MediaType.IMAGE_PNG;
case ".gif" -> MediaType.IMAGE_GIF;
case ".pdf" -> MediaType.APPLICATION_PDF;
default -> MediaType.APPLICATION_OCTET_STREAM;
};
return FileUtil.buildImageResponse(content, fileName);
}
@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", "名称不能为空"));
if (CommonUtil.isBlank(newName)) {
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) {
Object folderIdObj = request.get("folderId");
Long folderId = null;
if (folderIdObj != null && !folderIdObj.toString().isEmpty() && !"null".equals(folderIdObj.toString()) && !"undefined".equals(folderIdObj.toString())) {
folderId = Long.parseLong(folderIdObj.toString());
}
Long folderId = CommonUtil.getLong(request, "folderId");
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());
}
}
/**
* 批量下载
*/
@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", "请选择要下载的文件"));
}
List<Long> ids = new ArrayList<>();
if (idsObj instanceof List) {
for (Object id : (List<?>) idsObj) {
if (id instanceof Number) {
ids.add(((Number) id).longValue());
} else if (id instanceof String) {
ids.add(Long.parseLong((String) id));
}
}
}
List<Long> ids = CommonUtil.parseIds(request.get("ids"));
if (ids.isEmpty()) {
return ResponseEntity.badRequest().body(Map.of("message", "请选择要下载的文件"));
com.filesystem.utils.ServletUtil.writeErrorJson(response, 400, "请选择要下载的文件");
return;
}
try {
byte[] zipBytes = fileService.createZipArchive(ids, principal.getUserId());
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment; filename=\"download.zip\"");
response.setContentLength(zipBytes.length);
com.filesystem.utils.ServletUtil.setZipDownloadHeaders(response, 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()));
com.filesystem.utils.ServletUtil.writeErrorJson(response, 400, e.getMessage());
}
}
@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", "请选择要取消共享的文件"));
}
List<Long> ids = new ArrayList<>();
if (idsObj instanceof List) {
for (Object id : (List<?>) idsObj) {
if (id instanceof Number) {
ids.add(((Number) id).longValue());
} else if (id instanceof String) {
ids.add(Long.parseLong((String) id));
}
}
List<Long> ids = CommonUtil.parseIds(request.get("ids"));
if (ids.isEmpty()) {
return ApiResult.error("请选择要取消共享的文件");
}
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", "请选择要还原的文件"));
}
List<Long> ids = new ArrayList<>();
if (idsObj instanceof List) {
for (Object id : (List<?>) idsObj) {
if (id instanceof Number) {
ids.add(((Number) id).longValue());
} else if (id instanceof String) {
ids.add(Long.parseLong((String) id));
}
}
List<Long> ids = CommonUtil.parseIds(request.get("ids"));
if (ids.isEmpty()) {
return ApiResult.error("请选择要还原的文件");
}
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,24 +5,23 @@ 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 com.filesystem.utils.CommonUtil;
import com.filesystem.utils.FileUtil;
import com.filesystem.utils.ServletUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
import java.util.LinkedHashMap;
@@ -40,24 +39,21 @@ public class MessageController {
@Value("${file.storage.path:./uploads}")
private String storagePath;
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy/MM");
// ==================== 聊天文件上传 ====================
@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();
String ext = originalName != null && originalName.contains(".")
? originalName.substring(originalName.lastIndexOf(".")) : "";
String ext = FileUtil.getExtensionWithDot(originalName);
String storedName = UUID.randomUUID().toString() + ext;
String datePath = LocalDateTime.now().format(DATE_FMT);
String datePath = CommonUtil.getDatePath();
Path targetDir = Paths.get(storagePath).toAbsolutePath().resolve("chat").resolve(datePath);
if (!Files.exists(targetDir)) {
@@ -68,7 +64,7 @@ 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));
}
// ==================== 聊天文件访问 ====================
@@ -79,7 +75,6 @@ public class MessageController {
String prefix = "/api/messages/file/";
String relativePath = uri.substring(uri.indexOf(prefix) + prefix.length());
// relativePath = "2026/04/xxx.jpg"
int lastSlash = relativePath.lastIndexOf('/');
String datePath = relativePath.substring(0, lastSlash);
String fileName = relativePath.substring(lastSlash + 1);
@@ -92,37 +87,17 @@ public class MessageController {
}
byte[] content = Files.readAllBytes(filePath);
String ext = fileName.contains(".") ? fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase() : "";
boolean isImage = Set.of("png", "jpg", "jpeg", "gif", "webp", "bmp", "svg").contains(ext);
String contentType = switch (ext) {
case "pdf" -> "application/pdf";
case "png" -> "image/png";
case "gif" -> "image/gif";
case "webp" -> "image/webp";
case "jpg", "jpeg" -> "image/jpeg";
case "bmp" -> "image/bmp";
case "svg" -> "image/svg+xml";
default -> "application/octet-stream";
};
String encodedName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replace("+", "%20");
String disposition = isImage
? "inline; filename=\"" + encodedName + "\""
: "attachment; filename=\"" + encodedName + "\"";
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, disposition)
.header(HttpHeaders.CACHE_CONTROL, "max-age=86400")
.body(content);
boolean isImage = FileUtil.isImage(fileName);
return isImage
? FileUtil.buildImageResponse(content, fileName)
: FileUtil.buildDownloadResponse(content, fileName);
}
// ==================== 消息收发 ====================
@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);
@@ -157,7 +132,7 @@ public class MessageController {
m.put("fileName", msg.getFileName());
m.put("fileSize", msg.getFileSize());
m.put("isRead", msg.getIsRead());
m.put("createTime", msg.getCreateTime() != null ? msg.getCreateTime().toString() : "");
m.put("createTime", CommonUtil.formatDateTime(msg.getCreateTime()));
User fromUser = userMap.get(msg.getFromUserId());
if (fromUser != null) {
@@ -169,11 +144,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,19 +163,18 @@ 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());
String content = (String) request.get("content");
String type = (String) request.getOrDefault("type", "text");
String fileName = (String) request.get("fileName");
Object fileSizeObj = request.get("fileSize");
Long fileSize = fileSizeObj != null ? Long.valueOf(fileSizeObj.toString()) : null;
Long toUserId = CommonUtil.getLong(request, "toUserId");
String content = CommonUtil.getString(request, "content");
String type = CommonUtil.getString(request, "type", "text");
String fileName = CommonUtil.getString(request, "fileName");
Long fileSize = CommonUtil.getLong(request, "fileSize");
Message message = messageService.sendMessage(principal.getUserId(), toUserId, content, type);
@@ -218,7 +192,7 @@ public class MessageController {
result.put("type", message.getType());
result.put("fileName", message.getFileName());
result.put("fileSize", message.getFileSize());
result.put("createTime", message.getCreateTime() != null ? message.getCreateTime().toString() : "");
result.put("createTime", CommonUtil.formatDateTime(message.getCreateTime()));
User fromUser = userService.findById(principal.getUserId());
if (fromUser != null) {
@@ -227,20 +201,19 @@ 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());
// 按发送人分组
Map<Long, List<Message>> grouped = unreadMessages.stream()
.collect(Collectors.groupingBy(Message::getFromUserId, LinkedHashMap::new, Collectors.toList()));
@@ -248,7 +221,7 @@ public class MessageController {
for (Map.Entry<Long, List<Message>> entry : grouped.entrySet()) {
Long fromUserId = entry.getKey();
List<Message> msgs = entry.getValue();
Message lastMsg = msgs.get(0); // 已按时间倒序,第一条就是最新的
Message lastMsg = msgs.get(0);
User fromUser = userService.findById(fromUserId);
Map<String, Object> item = new HashMap<>();
@@ -258,7 +231,7 @@ public class MessageController {
? "[图片]" : (lastMsg.getType() != null && lastMsg.getType().equals("file")
? "[文件]" : (lastMsg.getContent() != null && lastMsg.getContent().length() > 30
? lastMsg.getContent().substring(0, 30) + "..." : lastMsg.getContent())));
item.put("lastTime", lastMsg.getCreateTime() != null ? lastMsg.getCreateTime().toString() : "");
item.put("lastTime", CommonUtil.formatDateTime(lastMsg.getCreateTime()));
if (fromUser != null) {
item.put("username", fromUser.getUsername());
item.put("nickname", fromUser.getNickname() != null ? fromUser.getNickname() : fromUser.getUsername());
@@ -268,12 +241,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,11 @@ 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 com.filesystem.utils.CommonUtil;
import com.filesystem.utils.FileUtil;
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 +16,7 @@ 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.util.*;
import java.util.stream.Collectors;
@RestController
@@ -33,12 +33,12 @@ public class UserController {
* 获取所有可用用户(用于文件共享等场景)
*/
@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) // 只返回启用状态的用户
.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,35 +47,29 @@ 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");
}
// 保存文件
String originalFilename = file.getOriginalFilename();
String ext = originalFilename != null && originalFilename.contains(".")
? originalFilename.substring(originalFilename.lastIndexOf("."))
: ".jpg";
String ext = FileUtil.getExtensionWithDot(file.getOriginalFilename());
String fileName = UUID.randomUUID().toString() + ext;
String datePath = java.time.LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy/MM"));
String datePath = CommonUtil.getDatePath();
// 使用配置文件中的路径 + 日期目录
Path uploadPath = Paths.get(storagePath).toAbsolutePath().resolve("avatars").resolve(datePath);
if (!Files.exists(uploadPath)) {
Files.createDirectories(uploadPath);
@@ -84,20 +78,19 @@ public class UserController {
Path filePath = uploadPath.resolve(fileName);
Files.copy(file.getInputStream(), filePath);
// 更新用户头像
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,25 +98,27 @@ 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 {
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", "更新成功"));
userService.updateProfile(
principal.getUserId(),
request.get("nickname"),
request.get("signature"),
request.get("phone"),
request.get("email")
);
return ApiResult.success("更新成功");
} catch (RuntimeException e) {
return ResponseEntity.badRequest().body(Map.of("message", e.getMessage()));
return ApiResult.error(e.getMessage());
}
}
@@ -131,21 +126,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", "请填写完整信息"));
if (CommonUtil.isBlank(oldPassword) || CommonUtil.isBlank(newPassword)) {
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

@@ -14,6 +14,9 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import com.filesystem.utils.CommonUtil;
import com.filesystem.utils.FileUtil;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

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,133 @@
package com.filesystem.utils;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 通用工具类
*/
public class CommonUtil {
private CommonUtil() {}
// ========== 日期格式化 ==========
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter DATE_PATH_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM");
/**
* 格式化日期时间yyyy-MM-dd HH:mm:ss
*/
public static String formatDateTime(LocalDateTime dateTime) {
if (dateTime == null) return "";
return dateTime.format(DATE_TIME_FORMATTER);
}
/**
* 获取日期路径格式yyyy/MM用于文件存储路径
*/
public static String getDatePath() {
return LocalDateTime.now().format(DATE_PATH_FORMATTER);
}
/**
* 获取当前时间字符串
*/
public static String now() {
return formatDateTime(LocalDateTime.now());
}
// ========== ID 列表解析 ==========
/**
* 从 Map 中解析 ID 列表
* 支持 List<Number>、List<String>、Number、String 等多种格式
*/
public static List<Long> parseIds(Object idsObj) {
List<Long> ids = new ArrayList<>();
if (idsObj == null) {
return ids;
}
if (idsObj instanceof List) {
for (Object id : (List<?>) idsObj) {
if (id instanceof Number) {
ids.add(((Number) id).longValue());
} else if (id instanceof String) {
String str = ((String) id).trim();
if (!str.isEmpty()) {
ids.add(Long.parseLong(str));
}
}
}
} else if (idsObj instanceof Number) {
ids.add(((Number) idsObj).longValue());
} else if (idsObj instanceof String) {
String str = ((String) idsObj).trim();
if (!str.isEmpty()) {
ids.add(Long.parseLong(str));
}
}
return ids;
}
/**
* 从请求体中解析 ID 列表
*/
public static List<Long> parseIdsFromRequest(Map<String, Object> request, String key) {
return parseIds(request.get(key));
}
// ========== 参数提取 ==========
/**
* 从 Map 中获取 Long 值
*/
public static Long getLong(Map<String, Object> map, String key) {
Object value = map.get(key);
if (value == null) return null;
if (value instanceof Number) {
return ((Number) value).longValue();
}
String str = value.toString();
if (str.isEmpty() || "null".equals(str) || "undefined".equals(str)) {
return null;
}
return Long.parseLong(str);
}
/**
* 从 Map 中获取 String 值
*/
public static String getString(Map<String, Object> map, String key) {
Object value = map.get(key);
return value != null ? value.toString() : null;
}
/**
* 从 Map 中获取指定默认值的 String
*/
public static String getString(Map<String, Object> map, String key, String defaultValue) {
String value = getString(map, key);
return value != null ? value : defaultValue;
}
/**
* 判断字符串是否为空或空白
*/
public static boolean isBlank(String str) {
return str == null || str.trim().isEmpty();
}
/**
* 判断字符串是否不为空
*/
public static boolean isNotBlank(String str) {
return !isBlank(str);
}
}

View File

@@ -0,0 +1,161 @@
package com.filesystem.utils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* 文件相关工具类
*/
public class FileUtil {
private FileUtil() {}
// ========== 文件名编码 ==========
/**
* URL 编码文件名(用于 Content-Disposition 头)
*/
public static String encodeFileName(String fileName) {
return URLEncoder.encode(fileName, StandardCharsets.UTF_8).replace("+", "%20");
}
// ========== 扩展名提取 ==========
/**
* 获取文件扩展名(不带点,如 "jpg"
*/
public static String getExtension(String fileName) {
if (fileName == null || !fileName.contains(".")) {
return "";
}
return fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
}
/**
* 获取文件扩展名(带点,如 ".jpg"
*/
public static String getExtensionWithDot(String fileName) {
if (fileName == null || !fileName.contains(".")) {
return "";
}
return fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
}
// ========== Content-Type 判断 ==========
/**
* 根据文件名获取图片 Content-Type
*/
public static String getImageContentType(String fileName) {
String ext = getExtension(fileName);
return switch (ext) {
case "png" -> "image/png";
case "gif" -> "image/gif";
case "webp" -> "image/webp";
case "svg" -> "image/svg+xml";
case "bmp" -> "image/bmp";
case "ico" -> "image/x-icon";
default -> "image/jpeg";
};
}
/**
* 根据文件名获取 MediaType用于 Spring ResponseEntity
*/
public static MediaType getMediaType(String fileName) {
String ext = getExtensionWithDot(fileName);
return switch (ext) {
case ".jpg", ".jpeg" -> MediaType.IMAGE_JPEG;
case ".png" -> MediaType.IMAGE_PNG;
case ".gif" -> MediaType.IMAGE_GIF;
case ".webp" -> MediaType.parseMediaType("image/webp");
case ".pdf" -> MediaType.APPLICATION_PDF;
case ".svg" -> MediaType.parseMediaType("image/svg+xml");
default -> MediaType.APPLICATION_OCTET_STREAM;
};
}
/**
* 根据文件名判断是否为图片
*/
public static boolean isImage(String fileName) {
String ext = getExtension(fileName);
return java.util.Set.of("jpg", "jpeg", "png", "gif", "webp", "svg", "bmp", "ico").contains(ext);
}
// ========== 文件类型判断 ==========
/**
* 根据扩展名获取文件类型
*/
public static String getFileType(String extension) {
if (extension == null || extension.isEmpty()) {
return "file";
}
String ext = extension.toLowerCase().replace(".", "");
// 图片
if (java.util.Set.of("jpg", "jpeg", "png", "gif", "webp", "svg", "bmp", "ico").contains(ext)) {
return "image";
}
// 视频
if (java.util.Set.of("mp4", "avi", "mov", "wmv", "flv", "mkv", "webm").contains(ext)) {
return "video";
}
// 音频
if (java.util.Set.of("mp3", "wav", "flac", "aac", "ogg", "wma").contains(ext)) {
return "audio";
}
// PDF
if ("pdf".equals(ext)) {
return "pdf";
}
// 文档
if (java.util.Set.of("doc", "docx", "xls", "xlsx", "ppt", "pptx").contains(ext)) {
return "document";
}
// 压缩包
if (java.util.Set.of("zip", "rar", "7z", "tar", "gz").contains(ext)) {
return "archive";
}
return "file";
}
// ========== 响应构建 ==========
/**
* 构建文件下载响应
*/
public static ResponseEntity<byte[]> buildDownloadResponse(byte[] content, String fileName) {
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + encodeFileName(fileName) + "\"")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(content);
}
/**
* 构建文件预览响应
*/
public static ResponseEntity<byte[]> buildPreviewResponse(byte[] content, String fileName) {
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + encodeFileName(fileName) + "\"")
.contentType(getMediaType(fileName))
.header(HttpHeaders.CACHE_CONTROL, "max-age=86400")
.body(content);
}
/**
* 构建图片响应
*/
public static ResponseEntity<byte[]> buildImageResponse(byte[] content, String fileName) {
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(getImageContentType(fileName)))
.header(HttpHeaders.CACHE_CONTROL, "max-age=86400")
.body(content);
}
}

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

@@ -0,0 +1,74 @@
package com.filesystem.utils;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
/**
* Servlet 响应工具类
*/
public class ServletUtil {
private ServletUtil() {}
/**
* 输出 JSON 响应
*/
public static void writeJson(HttpServletResponse response, int status, String json) throws IOException {
response.setStatus(status);
response.setContentType("application/json;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
PrintWriter writer = response.getWriter();
writer.write(json);
writer.flush();
}
/**
* 输出错误 JSON 响应
*/
public static void writeErrorJson(HttpServletResponse response, int code, String message) throws IOException {
String json = String.format("{\"code\":%d,\"message\":\"%s\"}", code, escapeJson(message));
writeJson(response, code == 400 ? 400 : code, json);
}
/**
* 输出成功 JSON 响应
*/
public static void writeSuccessJson(HttpServletResponse response, String message) throws IOException {
writeJson(response, 200, String.format("{\"code\":200,\"message\":\"%s\"}", escapeJson(message)));
}
/**
* 设置文件下载响应头
*/
public static void setDownloadHeaders(HttpServletResponse response, String fileName, long contentLength) {
response.setContentType("application/octet-stream");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + FileUtil.encodeFileName(fileName) + "\"");
response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(contentLength));
response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache");
}
/**
* 设置 ZIP 下载响应头
*/
public static void setZipDownloadHeaders(HttpServletResponse response, long contentLength) {
response.setContentType("application/zip");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"download.zip\"");
response.setContentLengthLong(contentLength);
}
/**
* 转义 JSON 字符串中的特殊字符
*/
public static String escapeJson(String str) {
if (str == null) return "";
return str.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t");
}
}

View File

@@ -1,7 +1,7 @@
-- 创建数据库
CREATE DATABASE IF NOT EXISTS file_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE IF NOT EXISTS system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE file_system;
USE system;
-- 用户表
CREATE TABLE IF NOT EXISTS sys_user (
@@ -15,7 +15,7 @@ CREATE TABLE IF NOT EXISTS sys_user (
phone VARCHAR(20) COMMENT '手机号',
status INT DEFAULT 1 COMMENT '状态 0-禁用 1-启用',
storage_used BIGINT DEFAULT 0 COMMENT '已用存储空间(字节)',
storage_limit BIGINT DEFAULT 10737418240 COMMENT '存储限制(字节) 默认10GB',
storage_limit BIGINT DEFAULT 21474836480 COMMENT '存储限制(字节) 默认20GB',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
is_deleted INT DEFAULT 0 COMMENT '是否删除 0-否 1-是'
@@ -73,6 +73,7 @@ CREATE TABLE IF NOT EXISTS sys_message (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消息表';
-- 插入默认管理员账户 (密码: admin123)
-- BCrypt hash generated with strength 10
INSERT INTO sys_user (username, password, nickname, status, storage_limit)
VALUES ('admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt6Z5EH', '管理员', 1, 10737418240)
ON DUPLICATE KEY UPDATE username = username;
VALUES ('admin', '$2a$10$EqKcp1WFKVQISheBxmXNGexPR.i7QYXOJC.OFfQDT8iSaHuuPdlrW', '管理员', 1, 21474836480)
ON DUPLICATE KEY UPDATE password = '$2a$10$EqKcp1WFKVQISheBxmXNGexPR.i7QYXOJC.OFfQDT8iSaHuuPdlrW';

View File

@@ -0,0 +1,46 @@
server:
port: 18089
spring:
datasource:
url: jdbc:mysql://192.168.31.182:13306/system?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&createDatabaseIfNotExist=true
username: dream
password: info_dream
driver-class-name: com.mysql.cj.jdbc.Driver
data:
redis:
host: 192.168.31.194
port: 16379
database: 0
timeout: 10000ms
password: admin
servlet:
multipart:
enabled: true
max-file-size: 1024MB
max-request-size: 2048MB
file-size-threshold: 0
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
type-aliases-package: com.filesystem.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto
file:
storage:
path: /ogsapp/uploads
jwt:
secret: mySecretKeyForJWTTokenGenerationThatIsLongEnough256BitsForHS256Algorithm
expiration: 86400000
logging:
level:
com.filesystem: DEBUG

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,78 @@
-- 创建数据库
CREATE DATABASE IF NOT EXISTS file_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE file_system;
-- 用户表
CREATE TABLE IF NOT EXISTS sys_user (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
password VARCHAR(255) NOT NULL COMMENT '密码',
nickname VARCHAR(50) COMMENT '昵称',
avatar VARCHAR(255) COMMENT '头像',
signature VARCHAR(255) COMMENT '个性签名',
email VARCHAR(100) COMMENT '邮箱',
phone VARCHAR(20) COMMENT '手机号',
status INT DEFAULT 1 COMMENT '状态 0-禁用 1-启用',
storage_used BIGINT DEFAULT 0 COMMENT '已用存储空间(字节)',
storage_limit BIGINT DEFAULT 10737418240 COMMENT '存储限制(字节) 默认10GB',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
is_deleted INT DEFAULT 0 COMMENT '是否删除 0-否 1-是'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- 文件表
CREATE TABLE IF NOT EXISTS sys_file (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
name VARCHAR(255) NOT NULL COMMENT '文件名',
type VARCHAR(50) COMMENT '文件类型',
size BIGINT DEFAULT 0 COMMENT '文件大小(字节)',
path VARCHAR(500) COMMENT '存储路径',
folder_id BIGINT COMMENT '所属文件夹ID',
user_id BIGINT NOT NULL COMMENT '所属用户ID',
is_folder INT DEFAULT 0 COMMENT '是否文件夹 0-否 1-是',
is_shared INT DEFAULT 0 COMMENT '是否共享 0-否 1-是',
deleted_at DATETIME COMMENT '删除时间',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
is_deleted INT DEFAULT 0 COMMENT '是否删除 0-否 1-是',
INDEX idx_user_id (user_id),
INDEX idx_folder_id (folder_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件表';
-- 文件共享表
CREATE TABLE IF NOT EXISTS sys_file_share (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
file_id BIGINT NOT NULL COMMENT '文件ID',
owner_id BIGINT NOT NULL COMMENT '所有者ID',
share_to_user_id BIGINT NOT NULL COMMENT '共享给用户ID',
permission VARCHAR(20) DEFAULT 'view' COMMENT '权限 view-查看 edit-编辑',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
is_deleted INT DEFAULT 0 COMMENT '是否删除 0-否 1-是',
INDEX idx_file_id (file_id),
INDEX idx_owner_id (owner_id),
INDEX idx_share_to_user_id (share_to_user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件共享表';
-- 消息表
CREATE TABLE IF NOT EXISTS sys_message (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
from_user_id BIGINT NOT NULL COMMENT '发送者ID',
to_user_id BIGINT NOT NULL COMMENT '接收者ID',
content TEXT COMMENT '消息内容',
type VARCHAR(20) DEFAULT 'text' COMMENT '消息类型 text-文本 image-图片 file-文件 emoji-表情',
file_name VARCHAR(255) COMMENT '文件名',
file_size BIGINT COMMENT '文件大小',
is_read INT DEFAULT 0 COMMENT '是否已读 0-未读 1-已读',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
is_deleted INT DEFAULT 0 COMMENT '是否删除 0-否 1-是',
INDEX idx_from_user_id (from_user_id),
INDEX idx_to_user_id (to_user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消息表';
-- 插入默认管理员账户 (密码: admin123)
INSERT INTO sys_user (username, password, nickname, status, storage_limit)
VALUES ('admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt6Z5EH', '管理员', 1, 10737418240)
ON DUPLICATE KEY UPDATE username = username;

View File

@@ -886,7 +886,6 @@
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/lodash": "*"
}
@@ -1422,15 +1421,13 @@
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/lodash-es": {
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz",
"integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/lodash-unified": {
"version": "1.0.3",
@@ -1637,7 +1634,6 @@
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.43",
@@ -1697,7 +1693,6 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.31.tgz",
"integrity": "sha512-iV/sU9SzOlmA/0tygSmjkEN6Jbs3nPoIPFhCMLD2STrjgOU8DX7ZtzMhg4ahVwf5Rp9KoFzcXeB1ZrVbLBp5/Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/compiler-dom": "3.5.31",
"@vue/compiler-sfc": "3.5.31",

View File

@@ -1,4 +1,4 @@
import axios from 'axios'
import axios from 'axios'
const request = axios.create({
baseURL: '/api',
@@ -17,9 +17,66 @@ request.interceptors.request.use(
)
request.interceptors.response.use(
response => response.data,
response => {
const res = response.data
// 如果是二进制数据blob需要检查是否为错误响应
if (response.config.responseType === 'blob') {
// 检查 Content-Type如果是 JSON 说明是错误响应
const contentType = response.headers['content-type'] || ''
if (contentType.includes('application/json')) {
// 将 blob 转换为 JSON 并抛出错误
return res.text().then(text => {
try {
const json = JSON.parse(text)
const error = new Error(json.message || '请求失败')
error.code = json.code
error.response = response
return Promise.reject(error)
} catch (e) {
return Promise.reject(new Error('请求失败'))
}
})
}
// 真正的二进制数据,直接返回
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
}
// 处理 blob 类型的错误响应
if (data instanceof Blob) {
return data.text().then(text => {
try {
const json = JSON.parse(text)
error.message = json.message || '请求失败'
error.code = json.code
} catch (e) {
// 无法解析为 JSON
}
return Promise.reject(error)
})
}
// 只有 401/403 才清理 token 并跳转登录页
// 但在登录页时不跳转(避免死循环),登录接口的 401 也不跳转

View File

@@ -1,4 +1,4 @@
<template>
<template>
<el-dialog
v-model="visible"
title="消息"
@@ -310,8 +310,8 @@ const getFileColor = (name) => {
const loadContacts = async () => {
try {
const res = await getUsers()
contacts.value = (res.data || []).map((u, i) => ({
const data = await getUsers()
contacts.value = (data || []).map((u, i) => ({
...u,
id: u.id,
name: u.nickname || u.username,
@@ -327,8 +327,8 @@ const loadContacts = async () => {
const loadUnreadChats = async () => {
try {
const res = await getUnreadList()
const list = res.data || []
const data = await getUnreadList()
const list = data || []
if (list.length === 0) return
// 加载联系人后再填充未读数据
list.forEach(item => {
@@ -372,8 +372,8 @@ const selectContact = async (contact) => {
contact.unread = 0
loadingMessages.value = true
try {
const res = await getMessages({ userId: contact.id })
messages.value[contact.id] = (res.data || []).map(msg => {
const data = await getMessages({ userId: contact.id })
messages.value[contact.id] = (data || []).map(msg => {
const isSelf = String(msg.fromUserId) === String(userStore.userId)
return {
...msg,
@@ -474,11 +474,11 @@ const handleImageSelect = async (e) => {
try {
const formData = new FormData()
formData.append('file', file)
const res = await uploadChatFile(formData)
const data = await uploadChatFile(formData)
chatService.send({
type: 'chat',
toUserId: currentContact.value.id,
content: res.url,
content: data.url,
msgType: 'image'
})
updateRecentChat(currentContact.value, '[图片]')
@@ -525,11 +525,11 @@ const handleFileSelect = async (e) => {
try {
const formData = new FormData()
formData.append('file', file)
const res = await uploadChatFile(formData)
const data = await uploadChatFile(formData)
chatService.send({
type: 'chat',
toUserId: currentContact.value.id,
content: res.url,
content: data.url,
msgType: 'file',
fileName: file.name,
fileSize: file.size

View File

@@ -1,4 +1,4 @@
<template>
<template>
<el-dialog
v-model="visible"
title="个人信息"
@@ -136,10 +136,10 @@ const handleAvatarChange = async (e) => {
try {
const formData = new FormData()
formData.append('avatar', file)
const res = await request.post('/users/avatar', formData, {
const data = await request.post('/users/avatar', formData, {
headers: { 'Content-Type': 'multipart/form-data' }
})
const newUrl = res.data?.url || ''
const newUrl = data?.url || ''
avatarUrl.value = newUrl
userStore.setUser({ avatar: newUrl })
ElMessage.success('头像更新成功')

View File

@@ -1,4 +1,4 @@
<template>
<template>
<el-dialog
v-model="visible"
title="共享文件"
@@ -65,8 +65,8 @@ const userList = ref([])
const loadUsers = async () => {
try {
const res = await getUsers()
userList.value = res.data || []
const data = await getUsers()
userList.value = data || []
} catch (e) {
ElMessage.error('获取用户列表失败')
}

View File

@@ -1,4 +1,4 @@
<template>
<template>
<div class="top-navbar">
<div class="navbar-left">
<div class="logo-icon">
@@ -145,8 +145,8 @@ let pollTimer = null
let wsUnsubscribe = null
const checkUnread = () => {
getUnreadCount().then(res => {
totalUnread.value = res.data.count || 0
getUnreadCount().then(data => {
totalUnread.value = data.count || 0
}).catch(() => {})
}

View File

@@ -251,8 +251,7 @@ const remainingStorage = computed(() => {
// 刷新存储数据(从后端精确重算)
const refreshStorage = async () => {
try {
const res = await getCurrentUser()
const data = res.data
const data = await getCurrentUser()
if (data) {
userStore.setUser({
storageUsed: data.storageUsed ?? 0,
@@ -299,26 +298,26 @@ const loadFiles = async () => {
loading.value = true
currentPage.value = 1
try {
let res
let data
switch (activeMenu.value) {
case 'trash': res = await getTrashFiles(currentFolderId.value ? { folderId: currentFolderId.value } : {}); break
case 'trash': data = await getTrashFiles(currentFolderId.value ? { folderId: currentFolderId.value } : {}); break
case 'my-share':
if (currentFolderId.value) {
res = await getSharedByMeFolderFiles(currentFolderId.value)
data = await getSharedByMeFolderFiles(currentFolderId.value)
} else {
res = await getSharedByMe()
data = await getSharedByMe()
}
break
case 'shared-to-me':
if (currentFolderId.value) {
res = await getSharedFolderFiles(currentFolderId.value)
data = await getSharedFolderFiles(currentFolderId.value)
} else {
res = await getSharedToMe()
data = await getSharedToMe()
}
break
default: res = await getFiles({ folderId: currentFolderId.value, keyword: searchKeyword.value })
default: data = await getFiles({ folderId: currentFolderId.value, keyword: searchKeyword.value })
}
files.value = res.data || []
files.value = data || []
} catch (e) {
console.error(e)
} finally {
@@ -601,8 +600,8 @@ const handleBatchMove = async () => {
try {
const selectedIds = selectedFiles.value.map(f => f.id)
const res = await getMovableFolders(selectedIds, currentFolderId.value)
movableFolders.value = res.data || []
const data = await getMovableFolders(selectedIds, currentFolderId.value)
movableFolders.value = data || []
batchMoveVisible.value = true
} catch (e) {
ElMessage.error('获取目录列表失败')

View File

@@ -1,4 +1,4 @@
<template>
<template>
<el-form :model="form" :rules="rules" ref="formRef" @submit.prevent="handleLogin">
<el-form-item prop="username">
<el-input
@@ -57,11 +57,13 @@ const handleLogin = async () => {
loading.value = true
try {
const res = await login(form)
// res.data = { token, user }
emit('success', res.data)
// login 返回的是 res.data经过 request.js 拦截器处理)
// 后端 ApiResult 格式: { code, message, data }
// data = { token, user }
const data = await login(form)
emit('success', data)
} catch (e) {
ElMessage.error(e.response?.data?.message || '账号或密码错误')
ElMessage.error(e.message || '账号或密码错误')
} finally {
loading.value = false
}

View File

@@ -1,4 +1,4 @@
<template>
<template>
<el-form :model="form" :rules="rules" ref="formRef" @submit.prevent="handleRegister">
<el-form-item prop="username">
<el-input
@@ -107,7 +107,7 @@ const handleRegister = async () => {
ElMessage.success('注册成功,请登录')
emit('success')
} catch (e) {
ElMessage.error(e.response?.data?.message || '注册失败,请重试')
ElMessage.error(e.message || '注册失败,请重试')
} finally {
loading.value = false
}