Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 575a8e3341 | |||
| df1ff66c1f | |||
| 453f05cca3 | |||
| caa39ed7d2 | |||
| 38bcbd6ed2 | |||
| ceb6c8258c |
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@@ -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
2
.idea/misc.xml
generated
@@ -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>
|
||||
@@ -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() : "未知错误");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("批量还原成功");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("已标记已读");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
78
src/main/java/com/filesystem/utils/ApiResult.java
Normal file
78
src/main/java/com/filesystem/utils/ApiResult.java
Normal 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);
|
||||
}
|
||||
}
|
||||
133
src/main/java/com/filesystem/utils/CommonUtil.java
Normal file
133
src/main/java/com/filesystem/utils/CommonUtil.java
Normal 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);
|
||||
}
|
||||
}
|
||||
161
src/main/java/com/filesystem/utils/FileUtil.java
Normal file
161
src/main/java/com/filesystem/utils/FileUtil.java
Normal 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);
|
||||
}
|
||||
}
|
||||
34
src/main/java/com/filesystem/utils/PasswordUtil.java
Normal file
34
src/main/java/com/filesystem/utils/PasswordUtil.java
Normal 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));
|
||||
}
|
||||
}
|
||||
74
src/main/java/com/filesystem/utils/ServletUtil.java
Normal file
74
src/main/java/com/filesystem/utils/ServletUtil.java
Normal 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");
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
46
target/classes/application.yml
Normal file
46
target/classes/application.yml
Normal 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
|
||||
BIN
target/classes/com/filesystem/FileSystemApplication.class
Normal file
BIN
target/classes/com/filesystem/FileSystemApplication.class
Normal file
Binary file not shown.
Binary file not shown.
BIN
target/classes/com/filesystem/config/MyBatisPlusConfig.class
Normal file
BIN
target/classes/com/filesystem/config/MyBatisPlusConfig.class
Normal file
Binary file not shown.
BIN
target/classes/com/filesystem/config/RedisConfig.class
Normal file
BIN
target/classes/com/filesystem/config/RedisConfig.class
Normal file
Binary file not shown.
BIN
target/classes/com/filesystem/config/SecurityConfig$1.class
Normal file
BIN
target/classes/com/filesystem/config/SecurityConfig$1.class
Normal file
Binary file not shown.
BIN
target/classes/com/filesystem/config/SecurityConfig$2.class
Normal file
BIN
target/classes/com/filesystem/config/SecurityConfig$2.class
Normal file
Binary file not shown.
BIN
target/classes/com/filesystem/config/SecurityConfig.class
Normal file
BIN
target/classes/com/filesystem/config/SecurityConfig.class
Normal file
Binary file not shown.
BIN
target/classes/com/filesystem/config/WebConfig.class
Normal file
BIN
target/classes/com/filesystem/config/WebConfig.class
Normal file
Binary file not shown.
Binary file not shown.
BIN
target/classes/com/filesystem/config/WebSocketConfig.class
Normal file
BIN
target/classes/com/filesystem/config/WebSocketConfig.class
Normal file
Binary file not shown.
BIN
target/classes/com/filesystem/controller/AuthController.class
Normal file
BIN
target/classes/com/filesystem/controller/AuthController.class
Normal file
Binary file not shown.
BIN
target/classes/com/filesystem/controller/FileController.class
Normal file
BIN
target/classes/com/filesystem/controller/FileController.class
Normal file
Binary file not shown.
BIN
target/classes/com/filesystem/controller/IndexController.class
Normal file
BIN
target/classes/com/filesystem/controller/IndexController.class
Normal file
Binary file not shown.
BIN
target/classes/com/filesystem/controller/MessageController.class
Normal file
BIN
target/classes/com/filesystem/controller/MessageController.class
Normal file
Binary file not shown.
BIN
target/classes/com/filesystem/controller/UserController.class
Normal file
BIN
target/classes/com/filesystem/controller/UserController.class
Normal file
Binary file not shown.
BIN
target/classes/com/filesystem/entity/BaseEntity.class
Normal file
BIN
target/classes/com/filesystem/entity/BaseEntity.class
Normal file
Binary file not shown.
BIN
target/classes/com/filesystem/entity/FileEntity.class
Normal file
BIN
target/classes/com/filesystem/entity/FileEntity.class
Normal file
Binary file not shown.
BIN
target/classes/com/filesystem/entity/FileShare.class
Normal file
BIN
target/classes/com/filesystem/entity/FileShare.class
Normal file
Binary file not shown.
BIN
target/classes/com/filesystem/entity/Message.class
Normal file
BIN
target/classes/com/filesystem/entity/Message.class
Normal file
Binary file not shown.
BIN
target/classes/com/filesystem/entity/User.class
Normal file
BIN
target/classes/com/filesystem/entity/User.class
Normal file
Binary file not shown.
BIN
target/classes/com/filesystem/mapper/FileMapper.class
Normal file
BIN
target/classes/com/filesystem/mapper/FileMapper.class
Normal file
Binary file not shown.
BIN
target/classes/com/filesystem/mapper/FileShareMapper.class
Normal file
BIN
target/classes/com/filesystem/mapper/FileShareMapper.class
Normal file
Binary file not shown.
BIN
target/classes/com/filesystem/mapper/MessageMapper.class
Normal file
BIN
target/classes/com/filesystem/mapper/MessageMapper.class
Normal file
Binary file not shown.
BIN
target/classes/com/filesystem/mapper/UserMapper.class
Normal file
BIN
target/classes/com/filesystem/mapper/UserMapper.class
Normal file
Binary file not shown.
Binary file not shown.
BIN
target/classes/com/filesystem/security/JwtUtil.class
Normal file
BIN
target/classes/com/filesystem/security/JwtUtil.class
Normal file
Binary file not shown.
BIN
target/classes/com/filesystem/security/UserPrincipal.class
Normal file
BIN
target/classes/com/filesystem/security/UserPrincipal.class
Normal file
Binary file not shown.
BIN
target/classes/com/filesystem/service/FileService.class
Normal file
BIN
target/classes/com/filesystem/service/FileService.class
Normal file
Binary file not shown.
BIN
target/classes/com/filesystem/service/MessageService.class
Normal file
BIN
target/classes/com/filesystem/service/MessageService.class
Normal file
Binary file not shown.
BIN
target/classes/com/filesystem/service/UserService.class
Normal file
BIN
target/classes/com/filesystem/service/UserService.class
Normal file
Binary file not shown.
BIN
target/classes/com/filesystem/websocket/ChatHandler.class
Normal file
BIN
target/classes/com/filesystem/websocket/ChatHandler.class
Normal file
Binary file not shown.
78
target/classes/db/init.sql
Normal file
78
target/classes/db/init.sql
Normal 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;
|
||||
9
web-vue/package-lock.json
generated
9
web-vue/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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 也不跳转
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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('头像更新成功')
|
||||
|
||||
@@ -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('获取用户列表失败')
|
||||
}
|
||||
|
||||
@@ -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(() => {})
|
||||
}
|
||||
|
||||
|
||||
@@ -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('获取目录列表失败')
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user