重构云文件管理系统
This commit is contained in:
@@ -5,23 +5,17 @@ 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.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -95,8 +89,8 @@ public class FileController {
|
||||
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 ApiResult.success("创建成功", folder);
|
||||
}
|
||||
@@ -148,7 +142,7 @@ public class FileController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件(返回 ResponseEntity<byte[]> 用于二进制响应)
|
||||
* 下载文件
|
||||
*/
|
||||
@GetMapping("/{id}/download")
|
||||
public ResponseEntity<byte[]> downloadFile(
|
||||
@@ -159,18 +153,14 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览文件(返回 ResponseEntity<byte[]> 用于二进制响应)
|
||||
* 预览文件
|
||||
*/
|
||||
@GetMapping("/{id}/preview")
|
||||
public ResponseEntity<byte[]> previewFile(
|
||||
@@ -181,11 +171,7 @@ 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();
|
||||
}
|
||||
@@ -197,8 +183,8 @@ public class FileController {
|
||||
@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 ApiResult.success("共享成功", share);
|
||||
} catch (RuntimeException e) {
|
||||
@@ -219,43 +205,21 @@ public class FileController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取头像图片(返回 ResponseEntity<byte[]> 用于二进制响应)
|
||||
* 获取头像图片
|
||||
*/
|
||||
@GetMapping("/avatar/**")
|
||||
public ResponseEntity<byte[]> getAvatar(jakarta.servlet.http.HttpServletRequest request) throws IOException {
|
||||
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")
|
||||
@@ -264,7 +228,7 @@ public class FileController {
|
||||
@PathVariable Long id,
|
||||
@RequestBody Map<String, String> request) {
|
||||
String newName = request.get("name");
|
||||
if (newName == null || newName.trim().isEmpty()) {
|
||||
if (CommonUtil.isBlank(newName)) {
|
||||
return ApiResult.error("名称不能为空");
|
||||
}
|
||||
try {
|
||||
@@ -280,11 +244,7 @@ public class FileController {
|
||||
@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 ApiResult.success("移动成功");
|
||||
@@ -294,48 +254,27 @@ public class FileController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量下载(返回 ZIP 文件,使用 HttpServletResponse 直接写入)
|
||||
* 批量下载
|
||||
*/
|
||||
@PostMapping("/batchDownload")
|
||||
public void batchDownload(
|
||||
@AuthenticationPrincipal UserPrincipal principal,
|
||||
@RequestBody Map<String, Object> request,
|
||||
HttpServletResponse response) throws IOException {
|
||||
Object idsObj = request.get("ids");
|
||||
if (idsObj == null) {
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.getWriter().write("{\"code\":400,\"message\":\"请选择要下载的文件\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
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()) {
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.getWriter().write("{\"code\":400,\"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();
|
||||
} catch (RuntimeException e) {
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.getWriter().write("{\"code\":400,\"message\":\"" + e.getMessage().replace("\"", "\\\"") + "\"}");
|
||||
com.filesystem.utils.ServletUtil.writeErrorJson(response, 400, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,20 +291,10 @@ public class FileController {
|
||||
public ApiResult<Void> batchCancelShare(
|
||||
@AuthenticationPrincipal UserPrincipal principal,
|
||||
@RequestBody Map<String, Object> request) {
|
||||
Object idsObj = request.get("ids");
|
||||
if (idsObj == null) {
|
||||
List<Long> ids = CommonUtil.parseIds(request.get("ids"));
|
||||
if (ids.isEmpty()) {
|
||||
return ApiResult.error("请选择要取消共享的文件");
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Long fileId : ids) {
|
||||
fileService.cancelShare(fileId, principal.getUserId());
|
||||
}
|
||||
@@ -376,20 +305,10 @@ public class FileController {
|
||||
public ApiResult<Void> batchRestore(
|
||||
@AuthenticationPrincipal UserPrincipal principal,
|
||||
@RequestBody Map<String, Object> request) {
|
||||
Object idsObj = request.get("ids");
|
||||
if (idsObj == null) {
|
||||
List<Long> ids = CommonUtil.parseIds(request.get("ids"));
|
||||
if (ids.isEmpty()) {
|
||||
return ApiResult.error("请选择要还原的文件");
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Long fileId : ids) {
|
||||
fileService.restoreFile(fileId, principal.getUserId());
|
||||
}
|
||||
|
||||
@@ -6,24 +6,22 @@ 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;
|
||||
@@ -41,8 +39,6 @@ public class MessageController {
|
||||
@Value("${file.storage.path:./uploads}")
|
||||
private String storagePath;
|
||||
|
||||
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy/MM");
|
||||
|
||||
// ==================== 聊天文件上传 ====================
|
||||
|
||||
@PostMapping("/upload")
|
||||
@@ -55,10 +51,9 @@ public class MessageController {
|
||||
}
|
||||
|
||||
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)) {
|
||||
@@ -72,7 +67,7 @@ public class MessageController {
|
||||
return ApiResult.success("上传成功", Map.of("url", fileUrl));
|
||||
}
|
||||
|
||||
// ==================== 聊天文件访问(返回 ResponseEntity<byte[]> 用于二进制响应)====================
|
||||
// ==================== 聊天文件访问 ====================
|
||||
|
||||
@GetMapping("/file/**")
|
||||
public ResponseEntity<byte[]> getChatFile(HttpServletRequest request) throws IOException {
|
||||
@@ -80,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);
|
||||
@@ -93,31 +87,11 @@ public class MessageController {
|
||||
}
|
||||
|
||||
byte[] content = Files.readAllBytes(filePath);
|
||||
String ext = fileName.contains(".") ? fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase() : "";
|
||||
boolean isImage = FileUtil.isImage(fileName);
|
||||
|
||||
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);
|
||||
return isImage
|
||||
? FileUtil.buildImageResponse(content, fileName)
|
||||
: FileUtil.buildDownloadResponse(content, fileName);
|
||||
}
|
||||
|
||||
// ==================== 消息收发 ====================
|
||||
@@ -158,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) {
|
||||
@@ -196,12 +170,11 @@ public class MessageController {
|
||||
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);
|
||||
|
||||
@@ -219,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) {
|
||||
@@ -241,7 +214,6 @@ public class MessageController {
|
||||
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()));
|
||||
|
||||
@@ -249,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<>();
|
||||
@@ -259,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());
|
||||
|
||||
@@ -4,6 +4,8 @@ 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.security.core.annotation.AuthenticationPrincipal;
|
||||
@@ -14,8 +16,6 @@ import java.io.IOException;
|
||||
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;
|
||||
|
||||
@@ -29,8 +29,6 @@ public class UserController {
|
||||
@Value("${file.storage.path:./uploads}")
|
||||
private String storagePath;
|
||||
|
||||
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy/MM");
|
||||
|
||||
/**
|
||||
* 获取所有可用用户(用于文件共享等场景)
|
||||
*/
|
||||
@@ -38,7 +36,7 @@ public class UserController {
|
||||
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 HashMap<>();
|
||||
m.put("id", u.getId());
|
||||
@@ -64,20 +62,14 @@ public class UserController {
|
||||
return ApiResult.error("请选择图片");
|
||||
}
|
||||
|
||||
// 限制文件大小 2MB
|
||||
if (file.getSize() > 2 * 1024 * 1024) {
|
||||
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 = LocalDateTime.now().format(DATE_FMT);
|
||||
String datePath = CommonUtil.getDatePath();
|
||||
|
||||
// 使用配置文件中的路径 + 日期目录
|
||||
Path uploadPath = Paths.get(storagePath).toAbsolutePath().resolve("avatars").resolve(datePath);
|
||||
if (!Files.exists(uploadPath)) {
|
||||
Files.createDirectories(uploadPath);
|
||||
@@ -86,7 +78,6 @@ 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);
|
||||
|
||||
@@ -118,11 +109,13 @@ public class UserController {
|
||||
@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);
|
||||
userService.updateProfile(
|
||||
principal.getUserId(),
|
||||
request.get("nickname"),
|
||||
request.get("signature"),
|
||||
request.get("phone"),
|
||||
request.get("email")
|
||||
);
|
||||
return ApiResult.success("更新成功");
|
||||
} catch (RuntimeException e) {
|
||||
return ApiResult.error(e.getMessage());
|
||||
@@ -139,7 +132,7 @@ public class UserController {
|
||||
String oldPassword = request.get("oldPassword");
|
||||
String newPassword = request.get("newPassword");
|
||||
|
||||
if (oldPassword == null || oldPassword.isEmpty() || newPassword == null || newPassword.isEmpty()) {
|
||||
if (CommonUtil.isBlank(oldPassword) || CommonUtil.isBlank(newPassword)) {
|
||||
return ApiResult.error("请填写完整信息");
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user