Skip to content

Commit 5d4dc9c

Browse files
committed
refactor(system): 重构文件上传和文件访问控制服务
重构文件上传和文件访问控制服务
1 parent f3960f3 commit 5d4dc9c

7 files changed

Lines changed: 296 additions & 344 deletions

File tree

backend/src/main/java/xyz/lingview/dimstack/controller/FileAccessController.java

Lines changed: 8 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22

33
import lombok.extern.slf4j.Slf4j;
44
import org.springframework.beans.factory.annotation.Autowired;
5-
import org.springframework.beans.factory.annotation.Value;
6-
import org.springframework.core.io.Resource;
7-
import org.springframework.core.io.FileSystemResource;
85
import org.springframework.http.HttpHeaders;
96
import org.springframework.http.MediaType;
107
import org.springframework.http.ResponseEntity;
@@ -13,15 +10,7 @@
1310
import org.springframework.web.bind.annotation.RequestMapping;
1411
import org.springframework.web.bind.annotation.RequestParam;
1512
import org.springframework.web.bind.annotation.RestController;
16-
import xyz.lingview.dimstack.domain.UploadAttachment;
17-
import xyz.lingview.dimstack.service.ImageCompressionService;
18-
import xyz.lingview.dimstack.service.SiteConfigService;
19-
import xyz.lingview.dimstack.service.UploadService;
20-
21-
import java.io.UnsupportedEncodingException;
22-
import java.net.URLEncoder;
23-
import java.nio.file.Files;
24-
import java.nio.file.Path;
13+
import xyz.lingview.dimstack.service.FileAccessService;
2514

2615
/**
2716
* @Author: lingview
@@ -35,121 +24,27 @@
3524
public class FileAccessController {
3625

3726
@Autowired
38-
private UploadService uploadService;
39-
40-
@Autowired
41-
private ImageCompressionService imageCompressionService;
42-
43-
@Autowired
44-
private SiteConfigService siteConfigService;
45-
46-
@Value("${file.data-root:.}")
47-
private String dataRoot;
27+
private FileAccessService fileAccessService;
4828

49-
@Value("${file.upload-dir:upload}")
50-
private String uploadDir;
51-
52-
/**
53-
* 获取文件
54-
* @param accessKey 访问密钥
55-
* @param download 是否下载模式 (true=下载, false或不传=预览)
56-
* @return 文件资源
57-
*/
5829
@GetMapping("/{accessKey}")
59-
public ResponseEntity<Resource> getFile(
30+
public ResponseEntity<org.springframework.core.io.Resource> getFile(
6031
@PathVariable String accessKey,
6132
@RequestParam(required = false, defaultValue = "false") Boolean download) {
6233
try {
63-
log.debug("尝试访问文件,访问键: {}, 下载模式: {}", accessKey, download);
34+
FileAccessService.FileAccessResult result = fileAccessService.getFile(accessKey, download);
6435

65-
UploadAttachment attachment = uploadService.selectByAccessKey(accessKey);
66-
if (attachment == null) {
67-
log.warn("未找到访问键对应的文件: {}", accessKey);
36+
if (!result.found()) {
6837
return ResponseEntity.notFound().build();
6938
}
7039

71-
Path basePath = Path.of(dataRoot).toAbsolutePath().normalize();
72-
Path filePath = basePath.resolve(attachment.getAttachment_path()).normalize();
73-
74-
if (!Files.exists(filePath)) {
75-
log.warn("文件不存在: {}", filePath);
76-
return ResponseEntity.notFound().build();
77-
}
78-
79-
String contentType = Files.probeContentType(filePath);
80-
if (contentType == null) {
81-
contentType = "application/octet-stream";
82-
}
83-
84-
boolean isImage = contentType.startsWith("image/");
85-
Resource resource;
86-
87-
if (isImage && !download) {
88-
Integer enableCompression = siteConfigService.getEnableImageCompression();
89-
boolean shouldCompress = enableCompression != null && enableCompression == 1;
90-
91-
if (shouldCompress) {
92-
Path compressedPath = getCompressedImagePath(filePath);
93-
if (Files.exists(compressedPath)) {
94-
log.debug("使用压缩图片: {}", compressedPath);
95-
resource = new FileSystemResource(compressedPath.toFile());
96-
} else {
97-
log.debug("压缩图片不存在,使用原图并触发异步压缩: {}", filePath);
98-
resource = new FileSystemResource(filePath.toFile());
99-
imageCompressionService.compressImageAsync(filePath.toString());
100-
}
101-
} else {
102-
log.debug("图片压缩已禁用,使用原图: {}", filePath);
103-
resource = new FileSystemResource(filePath.toFile());
104-
}
105-
} else {
106-
resource = new FileSystemResource(filePath.toFile());
107-
}
108-
109-
String filename = filePath.getFileName().toString();
110-
111-
String disposition;
112-
if (download) {
113-
disposition = "attachment; filename*=UTF-8''" + encodeFilename(filename);
114-
} else {
115-
disposition = "inline; filename*=UTF-8''" + encodeFilename(filename);
116-
}
117-
118-
log.debug("成功找到文件: {}, 模式: {}", filename, download ? "下载" : "预览");
119-
12040
return ResponseEntity.ok()
121-
.contentType(MediaType.parseMediaType(contentType))
122-
.header(HttpHeaders.CONTENT_DISPOSITION, disposition)
123-
.body(resource);
41+
.contentType(MediaType.parseMediaType(result.contentType()))
42+
.header(HttpHeaders.CONTENT_DISPOSITION, result.filename())
43+
.body(result.resource());
12444

12545
} catch (Exception e) {
12646
log.error("获取文件时发生错误,访问键: {}", accessKey, e);
12747
return ResponseEntity.internalServerError().build();
12848
}
12949
}
130-
131-
private String encodeFilename(String filename) {
132-
try {
133-
return URLEncoder.encode(filename, "UTF-8").replace("+", "%20");
134-
} catch (UnsupportedEncodingException e) {
135-
log.error("编码文件名失败: {}", filename, e);
136-
return filename;
137-
}
138-
}
139-
140-
private Path getCompressedImagePath(Path originalPath) {
141-
Path parentDir = originalPath.getParent();
142-
String fileName = originalPath.getFileName().toString();
143-
int dotIndex = fileName.lastIndexOf('.');
144-
145-
String baseName;
146-
if (dotIndex > 0) {
147-
baseName = fileName.substring(0, dotIndex);
148-
} else {
149-
baseName = fileName;
150-
}
151-
152-
String compressedFileName = baseName + "_compressed.jpg";
153-
return parentDir.resolve("compressor").resolve(compressedFileName);
154-
}
15550
}

backend/src/main/java/xyz/lingview/dimstack/controller/UploadController.java

Lines changed: 45 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import jakarta.servlet.http.HttpServletRequest;
44
import lombok.extern.slf4j.Slf4j;
55
import org.springframework.beans.factory.annotation.Autowired;
6-
import org.springframework.http.ResponseEntity;
76
import org.springframework.web.bind.annotation.*;
87
import org.springframework.web.multipart.MultipartFile;
98
import xyz.lingview.dimstack.annotation.RequiresPermission;
@@ -25,28 +24,26 @@ public class UploadController {
2524
@RequiresPermission("attachment:add")
2625
public ApiResponse<Map<String, String>> uploadAttachment(HttpServletRequest request,
2726
@RequestParam("file") MultipartFile file) {
28-
ResponseEntity<Map<String, String>> result = uploadService.uploadAttachment(request, file);
29-
Map<String, String> body = result.getBody();
30-
if (result.getStatusCode().is2xxSuccessful()) {
31-
String message = body != null && body.get("message") != null ? body.get("message") : "上传成功";
32-
return ApiResponse.success(message, body);
27+
Map<String, String> result = uploadService.uploadAttachment(request, file);
28+
if (result != null && !result.containsKey("error")) {
29+
String message = result.getOrDefault("message", "上传成功");
30+
return ApiResponse.success(message, result);
3331
} else {
34-
String message = body != null && body.get("error") != null ? body.get("error") : "上传失败";
35-
return ApiResponse.error(result.getStatusCode().value(), message, body);
32+
String message = result != null ? result.get("error") : "上传失败";
33+
return ApiResponse.error(400, message, result);
3634
}
3735
}
3836

3937
@PostMapping("/uploadattachment/init")
4038
@RequiresPermission("attachment:add")
4139
public ApiResponse<Map<String, String>> initMultipartUpload(HttpServletRequest request,
4240
@RequestBody Map<String, String> payload) {
43-
ResponseEntity<Map<String, String>> result = uploadService.initMultipartUpload(request, payload);
44-
Map<String, String> body = result.getBody();
45-
if (result.getStatusCode().is2xxSuccessful()) {
46-
return ApiResponse.success(body);
41+
Map<String, String> result = uploadService.initMultipartUpload(request, payload);
42+
if (result != null && !result.containsKey("error")) {
43+
return ApiResponse.success(result);
4744
} else {
48-
String message = body != null && body.get("error") != null ? body.get("error") : "初始化上传失败";
49-
return ApiResponse.error(result.getStatusCode().value(), message, body);
45+
String message = result != null ? result.get("error") : "初始化上传失败";
46+
return ApiResponse.error(400, message, result);
5047
}
5148
}
5249

@@ -57,14 +54,13 @@ public ApiResponse<Map<String, String>> uploadChunk(
5754
@RequestHeader("Upload-Id") String uploadId,
5855
@RequestHeader("Chunk-Index") int chunkIndex,
5956
@RequestBody byte[] chunkData) {
60-
ResponseEntity<Map<String, String>> result = uploadService.uploadChunk(request, uploadId, chunkIndex, chunkData);
61-
Map<String, String> body = result.getBody();
62-
if (result.getStatusCode().is2xxSuccessful()) {
63-
String message = body != null && body.get("message") != null ? body.get("message") : "分片上传成功";
64-
return ApiResponse.success(message, body);
57+
Map<String, String> result = uploadService.uploadChunk(request, uploadId, chunkIndex, chunkData);
58+
if (result != null && !result.containsKey("error")) {
59+
String message = result.getOrDefault("message", "分片上传成功");
60+
return ApiResponse.success(message, result);
6561
} else {
66-
String message = body != null && body.get("error") != null ? body.get("error") : "分片上传失败";
67-
return ApiResponse.error(result.getStatusCode().value(), message, body);
62+
String message = result != null ? result.get("error") : "分片上传失败";
63+
return ApiResponse.error(400, message, result);
6864
}
6965
}
7066

@@ -73,44 +69,39 @@ public ApiResponse<Map<String, String>> uploadChunk(
7369
public ApiResponse<Map<String, String>> completeUpload(
7470
HttpServletRequest request,
7571
@RequestBody Map<String, String> payload) {
76-
ResponseEntity<Map<String, String>> result = uploadService.completeUpload(request, payload);
77-
Map<String, String> body = result.getBody();
78-
if (result.getStatusCode().is2xxSuccessful()) {
79-
return ApiResponse.success(body);
72+
Map<String, String> result = uploadService.completeUpload(request, payload);
73+
if (result != null && !result.containsKey("error")) {
74+
return ApiResponse.success(result);
8075
} else {
81-
String message = body != null && body.get("error") != null ? body.get("error") : "合并上传失败";
82-
return ApiResponse.error(result.getStatusCode().value(), message, body);
76+
String message = result != null ? result.get("error") : "合并上传失败";
77+
return ApiResponse.error(400, message, result);
8378
}
8479
}
8580

8681
@PostMapping("/uploadarticle")
8782
@RequiresPermission({"post:add", "post:edit"})
8883
public ApiResponse<Map<String, Object>> uploadArticle(HttpServletRequest request,
8984
@RequestBody UploadArticle uploadArticle) {
90-
ResponseEntity<Map<String, Object>> result = uploadService.uploadArticle(request, uploadArticle);
91-
Map<String, Object> body = result.getBody();
92-
if (result.getStatusCode().is2xxSuccessful()) {
93-
String message = body != null && body.get("message") != null
94-
? body.get("message").toString() : "文章保存成功";
95-
return ApiResponse.success(message, body);
85+
Map<String, Object> result = uploadService.uploadArticle(request, uploadArticle);
86+
if (result != null && !result.containsKey("error")) {
87+
String message = result.containsKey("message") ? result.get("message").toString() : "文章保存成功";
88+
return ApiResponse.success(message, result);
9689
} else {
97-
String message = body != null && body.get("error") != null
98-
? body.get("error").toString() : "文章保存失败";
99-
return ApiResponse.error(result.getStatusCode().value(), message, body);
90+
String message = result != null ? result.get("error").toString() : "文章保存失败";
91+
return ApiResponse.error(400, message, result);
10092
}
10193
}
10294

10395
@PostMapping("/uploadavatar")
10496
public ApiResponse<Map<String, String>> uploadAvatar(HttpServletRequest request,
10597
@RequestParam("file") MultipartFile file) {
106-
ResponseEntity<Map<String, String>> result = uploadService.uploadAvatar(request, file);
107-
Map<String, String> body = result.getBody();
108-
if (result.getStatusCode().is2xxSuccessful()) {
109-
String message = body != null && body.get("message") != null ? body.get("message") : "上传成功";
110-
return ApiResponse.success(message, body);
98+
Map<String, String> result = uploadService.uploadAvatar(request, file);
99+
if (result != null && !result.containsKey("error")) {
100+
String message = result.getOrDefault("message", "上传成功");
101+
return ApiResponse.success(message, result);
111102
} else {
112-
String message = body != null && body.get("error") != null ? body.get("error") : "上传失败";
113-
return ApiResponse.error(result.getStatusCode().value(), message, body);
103+
String message = result != null ? result.get("error") : "上传失败";
104+
return ApiResponse.error(400, message, result);
114105
}
115106
}
116107

@@ -121,15 +112,13 @@ public ApiResponse<Map<String, String>> adminUploadAvatar(
121112
HttpServletRequest request,
122113
@RequestParam("file") MultipartFile file) {
123114

124-
ResponseEntity<Map<String, String>> result = uploadService.adminUploadAvatar(request, file);
115+
Map<String, String> result = uploadService.adminUploadAvatar(request, file);
125116

126-
if (result.getStatusCode().is2xxSuccessful()) {
127-
Map<String, String> body = result.getBody();
128-
return ApiResponse.success(body);
117+
if (result != null && !result.containsKey("error")) {
118+
return ApiResponse.success(result);
129119
} else {
130-
Map<String, String> body = result.getBody();
131-
String errorMessage = body != null ? body.get("error") : "上传失败";
132-
return ApiResponse.error(result.getStatusCode().value(), errorMessage);
120+
String errorMessage = result != null ? result.get("error") : "上传失败";
121+
return ApiResponse.error(400, errorMessage);
133122
}
134123
}
135124

@@ -140,14 +129,13 @@ public ApiResponse<Map<String, String>> downloadExternalResource(
140129
HttpServletRequest request,
141130
@RequestBody Map<String, String> payload) {
142131
String url = payload.get("url");
143-
ResponseEntity<Map<String, String>> result = uploadService.downloadAndUploadExternalResource(request, url);
144-
Map<String, String> body = result.getBody();
145-
if (result.getStatusCode().is2xxSuccessful()) {
146-
String message = body != null && body.get("message") != null ? body.get("message") : "下载成功";
147-
return ApiResponse.success(message, body);
132+
Map<String, String> result = uploadService.downloadAndUploadExternalResource(request, url);
133+
if (result != null && !result.containsKey("error")) {
134+
String message = result.getOrDefault("message", "下载成功");
135+
return ApiResponse.success(message, result);
148136
} else {
149-
String message = body != null && body.get("error") != null ? body.get("error") : "下载失败";
150-
return ApiResponse.error(result.getStatusCode().value(), message, body);
137+
String message = result != null ? result.get("error") : "下载失败";
138+
return ApiResponse.error(400, message, result);
151139
}
152140
}
153141

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package xyz.lingview.dimstack.service;
2+
3+
import org.springframework.core.io.Resource;
4+
5+
public interface FileAccessService {
6+
7+
/**
8+
* 获取文件资源
9+
* @param accessKey 访问密钥
10+
* @param download 是否下载模式
11+
* @return 文件访问结果
12+
*/
13+
FileAccessResult getFile(String accessKey, boolean download);
14+
15+
/**
16+
* 文件访问结果
17+
*/
18+
record FileAccessResult(
19+
Resource resource,
20+
String contentType,
21+
String filename,
22+
boolean found
23+
) {}
24+
}
Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package xyz.lingview.dimstack.service;
22

3-
import org.springframework.http.ResponseEntity;
43
import org.springframework.web.multipart.MultipartFile;
54
import xyz.lingview.dimstack.domain.UploadAttachment;
65

@@ -16,12 +15,12 @@
1615
public interface UploadService {
1716
UploadAttachment selectByAccessKey(String accessKey);
1817

19-
ResponseEntity<Map<String, String>> uploadAttachment(HttpServletRequest request, MultipartFile file);
20-
ResponseEntity<Map<String, String>> initMultipartUpload(HttpServletRequest request, Map<String, String> payload);
21-
ResponseEntity<Map<String, String>> uploadChunk(HttpServletRequest request, String uploadId, int chunkIndex, byte[] chunkData);
22-
ResponseEntity<Map<String, String>> completeUpload(HttpServletRequest request, Map<String, String> payload);
23-
ResponseEntity<Map<String, Object>> uploadArticle(HttpServletRequest request, xyz.lingview.dimstack.domain.UploadArticle uploadArticle);
24-
ResponseEntity<Map<String, String>> uploadAvatar(HttpServletRequest request, MultipartFile file);
25-
ResponseEntity<Map<String, String>> adminUploadAvatar(HttpServletRequest request, MultipartFile file);
26-
ResponseEntity<Map<String, String>> downloadAndUploadExternalResource(HttpServletRequest request, String url);
18+
Map<String, String> uploadAttachment(HttpServletRequest request, MultipartFile file);
19+
Map<String, String> initMultipartUpload(HttpServletRequest request, Map<String, String> payload);
20+
Map<String, String> uploadChunk(HttpServletRequest request, String uploadId, int chunkIndex, byte[] chunkData);
21+
Map<String, String> completeUpload(HttpServletRequest request, Map<String, String> payload);
22+
Map<String, Object> uploadArticle(HttpServletRequest request, xyz.lingview.dimstack.domain.UploadArticle uploadArticle);
23+
Map<String, String> uploadAvatar(HttpServletRequest request, MultipartFile file);
24+
Map<String, String> adminUploadAvatar(HttpServletRequest request, MultipartFile file);
25+
Map<String, String> downloadAndUploadExternalResource(HttpServletRequest request, String url);
2726
}

0 commit comments

Comments
 (0)