mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-04-21 19:37:29 +08:00
Add image upload validations and random naming
This commit is contained in:
@@ -3,6 +3,7 @@ package com.openisle.controller;
|
|||||||
import com.openisle.model.User;
|
import com.openisle.model.User;
|
||||||
import com.openisle.service.ImageUploader;
|
import com.openisle.service.ImageUploader;
|
||||||
import com.openisle.service.UserService;
|
import com.openisle.service.UserService;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -20,6 +21,12 @@ public class UserController {
|
|||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
private final ImageUploader imageUploader;
|
private final ImageUploader imageUploader;
|
||||||
|
|
||||||
|
@Value("${app.upload.check-type:true}")
|
||||||
|
private boolean checkImageType;
|
||||||
|
|
||||||
|
@Value("${app.upload.max-size:5242880}")
|
||||||
|
private long maxUploadSize;
|
||||||
|
|
||||||
@GetMapping("/me")
|
@GetMapping("/me")
|
||||||
public ResponseEntity<UserDto> me(Authentication auth) {
|
public ResponseEntity<UserDto> me(Authentication auth) {
|
||||||
User user = userService.findByUsername(auth.getName()).orElseThrow();
|
User user = userService.findByUsername(auth.getName()).orElseThrow();
|
||||||
@@ -29,6 +36,12 @@ public class UserController {
|
|||||||
@PostMapping("/me/avatar")
|
@PostMapping("/me/avatar")
|
||||||
public ResponseEntity<?> uploadAvatar(@RequestParam("file") MultipartFile file,
|
public ResponseEntity<?> uploadAvatar(@RequestParam("file") MultipartFile file,
|
||||||
Authentication auth) {
|
Authentication auth) {
|
||||||
|
if (checkImageType && (file.getContentType() == null || !file.getContentType().startsWith("image/"))) {
|
||||||
|
return ResponseEntity.badRequest().body(Map.of("error", "File is not an image"));
|
||||||
|
}
|
||||||
|
if (file.getSize() > maxUploadSize) {
|
||||||
|
return ResponseEntity.badRequest().body(Map.of("error", "File too large"));
|
||||||
|
}
|
||||||
String url = null;
|
String url = null;
|
||||||
try {
|
try {
|
||||||
url = imageUploader.upload(file.getBytes(), file.getOriginalFilename()).join();
|
url = imageUploader.upload(file.getBytes(), file.getOriginalFilename()).join();
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
@@ -52,15 +53,22 @@ public class CosImageUploader extends ImageUploader {
|
|||||||
@Override
|
@Override
|
||||||
public CompletableFuture<String> upload(byte[] data, String filename) {
|
public CompletableFuture<String> upload(byte[] data, String filename) {
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
|
String ext = "";
|
||||||
|
int dot = filename.lastIndexOf('.');
|
||||||
|
if (dot != -1) {
|
||||||
|
ext = filename.substring(dot);
|
||||||
|
}
|
||||||
|
String randomName = UUID.randomUUID().toString().replace("-", "") + ext;
|
||||||
|
|
||||||
ObjectMetadata meta = new ObjectMetadata();
|
ObjectMetadata meta = new ObjectMetadata();
|
||||||
meta.setContentLength(data.length);
|
meta.setContentLength(data.length);
|
||||||
PutObjectRequest req = new PutObjectRequest(
|
PutObjectRequest req = new PutObjectRequest(
|
||||||
bucketName,
|
bucketName,
|
||||||
filename,
|
randomName,
|
||||||
new ByteArrayInputStream(data),
|
new ByteArrayInputStream(data),
|
||||||
meta);
|
meta);
|
||||||
cosClient.putObject(req);
|
cosClient.putObject(req);
|
||||||
return baseUrl + "/" + filename;
|
return baseUrl + "/" + randomName;
|
||||||
}, executor);
|
}, executor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,5 +10,9 @@ cos.secret-key=${COS_SECRET_KEY:}
|
|||||||
cos.region=${COS_REGION:ap-guangzhou}
|
cos.region=${COS_REGION:ap-guangzhou}
|
||||||
cos.bucket-name=${COS_BUCKET_NAME:}
|
cos.bucket-name=${COS_BUCKET_NAME:}
|
||||||
|
|
||||||
|
# Image upload configuration
|
||||||
|
app.upload.check-type=${UPLOAD_CHECK_TYPE:true}
|
||||||
|
app.upload.max-size=${UPLOAD_MAX_SIZE:5242880}
|
||||||
|
|
||||||
app.jwt.secret=${JWT_SECRET:ChangeThisSecretKeyForJwt}
|
app.jwt.secret=${JWT_SECRET:ChangeThisSecretKeyForJwt}
|
||||||
app.jwt.expiration=${JWT_EXPIRATION:86400000}
|
app.jwt.expiration=${JWT_EXPIRATION:86400000}
|
||||||
|
|||||||
@@ -57,4 +57,16 @@ class UserControllerTest {
|
|||||||
|
|
||||||
Mockito.verify(userService).updateAvatar("alice", "http://img/a.png");
|
Mockito.verify(userService).updateAvatar("alice", "http://img/a.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void uploadAvatarRejectsNonImage() throws Exception {
|
||||||
|
MockMultipartFile file = new MockMultipartFile("file", "a.txt", MediaType.TEXT_PLAIN_VALUE, "text".getBytes());
|
||||||
|
|
||||||
|
mockMvc.perform(multipart("/api/users/me/avatar").file(file).principal(new UsernamePasswordAuthenticationToken("alice","p")))
|
||||||
|
.andExpect(status().isBadRequest())
|
||||||
|
.andExpect(jsonPath("$.error").value("File is not an image"));
|
||||||
|
|
||||||
|
Mockito.verify(imageUploader, Mockito.never()).upload(any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,5 +11,9 @@ cos.secret-key=dummy
|
|||||||
cos.region=ap-guangzhou
|
cos.region=ap-guangzhou
|
||||||
cos.bucket-name=testbucket
|
cos.bucket-name=testbucket
|
||||||
|
|
||||||
|
# Image upload configuration for tests
|
||||||
|
app.upload.check-type=true
|
||||||
|
app.upload.max-size=1048576
|
||||||
|
|
||||||
app.jwt.secret=TestSecret
|
app.jwt.secret=TestSecret
|
||||||
app.jwt.expiration=3600000
|
app.jwt.expiration=3600000
|
||||||
|
|||||||
Reference in New Issue
Block a user