fix: remove error message leakage in LicenseController and ContractController

Replaced try-catch blocks returning e.getMessage() in HTTP 500 responses with proper ResponseStatusException propagation through global ApiExceptionHandler. Added file size (50MB) and MIME type whitelist validation to contract attachment upload.

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-05-27 08:36:53 +08:00
parent 0abb60fd2d
commit 25395a648b
2 changed files with 52 additions and 22 deletions
@@ -16,10 +16,13 @@ import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import java.io.File;
import java.io.IOException;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
@@ -34,6 +37,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.server.ResponseStatusException;
@RestController
@RequestMapping("/api/v1/contracts")
@@ -50,6 +54,16 @@ public class ContractController {
this.contractStatusTransitionService = contractStatusTransitionService;
}
private static final long MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB
private static final Set<String> ALLOWED_CONTENT_TYPES = Set.of(
MediaType.APPLICATION_PDF_VALUE,
"image/jpeg", "image/png", "image/tiff",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
);
@GetMapping
public PageResponse<ContractResponse> list(
@RequestParam(value = "page", defaultValue = "0") @Min(0) int page,
@@ -113,28 +127,44 @@ public class ContractController {
public ResponseEntity<Map<String, Object>> uploadAttachment(
@PathVariable Long id,
@RequestParam("file") MultipartFile file) {
try {
String uploadDir = System.getProperty("user.dir") + "/uploads/contracts/" + id + "/";
File dir = new File(uploadDir);
if (!dir.exists()) dir.mkdirs();
String fileName = System.currentTimeMillis() + "_" + file.getOriginalFilename();
File dest = new File(uploadDir + fileName);
file.transferTo(dest);
PlatformContractAttachment attachment = new PlatformContractAttachment();
attachment.setContractId(id);
attachment.setFileName(file.getOriginalFilename());
attachment.setFilePath(dest.getAbsolutePath());
attachment.setFileSize(file.getSize());
attachment.setContentType(file.getContentType());
attachment.setCreatedAt(OffsetDateTime.now());
attachmentMapper.insert(attachment);
return ResponseEntity.ok(Map.of("id", attachment.getId(), "fileName", attachment.getFileName()));
} catch (Exception e) {
return ResponseEntity.status(500).body(Map.of("error", e.getMessage()));
if (file.isEmpty()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "上传文件为空");
}
if (file.getSize() > MAX_FILE_SIZE) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "文件大小超过限制(最大 50MB");
}
String contentType = file.getContentType();
if (contentType == null || !ALLOWED_CONTENT_TYPES.contains(contentType)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
"不支持的文件类型: " + (contentType != null ? contentType : "未知"));
}
String uploadDir = System.getProperty("user.dir") + "/uploads/contracts/" + id + "/";
File dir = new File(uploadDir);
if (!dir.exists()) dir.mkdirs();
String originalName = file.getOriginalFilename();
String ext = originalName != null && originalName.contains(".")
? originalName.substring(originalName.lastIndexOf('.'))
: "";
String storedName = java.util.UUID.randomUUID().toString() + ext;
File dest = new File(uploadDir + storedName);
try {
file.transferTo(dest);
} catch (IOException e) {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "文件存储失败");
}
PlatformContractAttachment attachment = new PlatformContractAttachment();
attachment.setContractId(id);
attachment.setFileName(originalName);
attachment.setFilePath(dest.getAbsolutePath());
attachment.setFileSize(file.getSize());
attachment.setContentType(contentType);
attachment.setCreatedAt(OffsetDateTime.now());
attachmentMapper.insert(attachment);
return ResponseEntity.ok(Map.of("id", attachment.getId(), "fileName", attachment.getFileName()));
}
@GetMapping("/{id}/attachments")
@@ -22,7 +22,7 @@ public class LicenseController {
try {
return ResponseEntity.ok(licenseService.create(request));
} catch (Exception e) {
return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()));
throw new RuntimeException(e);
}
}