Skip to content

Commit 95dede1

Browse files
authored
Fix: [Photo] base/64로 저장 안되게 수정 (#69)
1 parent 050c39d commit 95dede1

6 files changed

Lines changed: 182 additions & 32 deletions

File tree

runtracker/src/main/java/com/runtracker/domain/course/service/CourseService.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import com.runtracker.domain.record.exception.CourseNotFoundForRecordException;
3636
import com.runtracker.domain.record.exception.RecordNotFoundException;
3737
import com.runtracker.domain.record.repository.RecordRepository;
38+
import com.runtracker.global.util.ImageUpload;
3839
import com.runtracker.global.client.FastAPIClient;
3940
import com.runtracker.global.vo.Coordinate;
4041
import lombok.RequiredArgsConstructor;
@@ -70,6 +71,7 @@ public class CourseService {
7071
private final FastAPIClient fastAPIClient;
7172
private final ObjectMapper objectMapper;
7273
private final CourseCacheService courseCacheService;
74+
private final ImageUpload imageUploadHelper;
7375

7476
private void checkAlreadyRunning(Long memberId) {
7577
List<RunningRecord> activeRecords = recordRepository.findAllByMemberIdAndFinishedAtIsNull(memberId);
@@ -369,6 +371,12 @@ public void finishRunning(Long memberId, FinishRunning finishRunning) {
369371

370372
long runningTimeSeconds = Duration.between(startedAt, finishedAt).getSeconds();
371373

374+
// Base64 이미지를 URL로 변환
375+
if (finishRunning.getPhoto() != null) {
376+
String photoUrl = imageUploadHelper.convertBase64ToUrlIfNeeded(finishRunning.getPhoto());
377+
finishRunning.setPhoto(photoUrl);
378+
}
379+
372380
Long courseId = existingRecord.getCourseId();
373381

374382
existingRecord.updateFinishRunning(

runtracker/src/main/java/com/runtracker/domain/crew/service/CrewService.java

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,19 @@
4040
import com.runtracker.domain.member.entity.Member;
4141
import com.runtracker.domain.member.entity.enums.MemberRole;
4242
import com.runtracker.domain.member.repository.MemberRepository;
43+
import com.runtracker.global.util.ImageUpload;
4344
import com.runtracker.global.security.UserDetailsImpl;
4445
import com.runtracker.global.security.CrewAuthorizationUtil;
4546
import com.runtracker.global.jwt.service.TokenBlacklistService;
4647
import lombok.RequiredArgsConstructor;
48+
import lombok.extern.slf4j.Slf4j;
4749
import org.springframework.context.ApplicationEventPublisher;
4850
import org.springframework.stereotype.Service;
4951
import org.springframework.transaction.annotation.Transactional;
5052

5153
import java.util.List;
5254

55+
@Slf4j
5356
@Service
5457
@RequiredArgsConstructor
5558
@Transactional
@@ -61,26 +64,48 @@ public class CrewService {
6164
private final CrewAuthorizationUtil authorizationUtil;
6265
private final TokenBlacklistService tokenBlacklistService;
6366
private final ApplicationEventPublisher eventPublisher;
67+
private final ImageUpload imageUploadHelper;
6468

6569
public void createCrew(CrewCreateDTO.Request request, UserDetailsImpl userDetails) {
6670
memberRepository.findById(userDetails.getMemberId())
6771
.orElseThrow(MemberNotFoundException::new);
68-
72+
6973
List<Crew> existingCrews = crewRepository.findByLeaderId(userDetails.getMemberId());
7074
if (!existingCrews.isEmpty()) {
7175
throw new CrewAlreadyExistsException();
7276
}
73-
74-
Crew crew = request.toEntity(userDetails.getMemberId());
75-
crewRepository.save(crew);
76-
77-
CrewMember crewLeader = CrewMember.builder()
78-
.crewId(crew.getId())
79-
.memberId(userDetails.getMemberId())
80-
.role(MemberRole.CREW_LEADER)
81-
.status(CrewMemberStatus.ACTIVE)
82-
.build();
83-
crewMemberRepository.save(crewLeader);
77+
78+
if (request.getPhoto() != null) {
79+
String photoUrl = imageUploadHelper.convertBase64ToUrlIfNeeded(request.getPhoto());
80+
CrewCreateDTO.Request updatedRequest = CrewCreateDTO.Request.builder()
81+
.title(request.getTitle())
82+
.photo(photoUrl)
83+
.introduce(request.getIntroduce())
84+
.region(request.getRegion())
85+
.difficulty(request.getDifficulty())
86+
.build();
87+
Crew crew = updatedRequest.toEntity(userDetails.getMemberId());
88+
crewRepository.save(crew);
89+
90+
CrewMember crewLeader = CrewMember.builder()
91+
.crewId(crew.getId())
92+
.memberId(userDetails.getMemberId())
93+
.role(MemberRole.CREW_LEADER)
94+
.status(CrewMemberStatus.ACTIVE)
95+
.build();
96+
crewMemberRepository.save(crewLeader);
97+
} else {
98+
Crew crew = request.toEntity(userDetails.getMemberId());
99+
crewRepository.save(crew);
100+
101+
CrewMember crewLeader = CrewMember.builder()
102+
.crewId(crew.getId())
103+
.memberId(userDetails.getMemberId())
104+
.role(MemberRole.CREW_LEADER)
105+
.status(CrewMemberStatus.ACTIVE)
106+
.build();
107+
crewMemberRepository.save(crewLeader);
108+
}
84109
}
85110

86111
public void applyToJoinCrew(Long crewId, UserDetailsImpl userDetails) {
@@ -238,15 +263,16 @@ public void updateCrewMemberRole(Long crewId, CrewMemberUpdateDTO.Request reques
238263

239264
public void updateCrew(Long crewId, CrewUpdateDTO.Request request, UserDetailsImpl userDetails) {
240265
authorizationUtil.validateCrewLeaderPermission(userDetails, crewId);
241-
266+
242267
Crew crew = crewRepository.findById(crewId)
243268
.orElseThrow(CrewNotFoundException::new);
244-
269+
245270
if (request.getTitle() != null) {
246271
crew.updateTitle(request.getTitle());
247272
}
248273
if (request.getPhoto() != null) {
249-
crew.updatePhoto(request.getPhoto());
274+
String photoUrl = imageUploadHelper.convertBase64ToUrlIfNeeded(request.getPhoto());
275+
crew.updatePhoto(photoUrl);
250276
}
251277
if (request.getIntroduce() != null) {
252278
crew.updateIntroduce(request.getIntroduce());
@@ -257,7 +283,7 @@ public void updateCrew(Long crewId, CrewUpdateDTO.Request request, UserDetailsIm
257283
if (request.getDifficulty() != null) {
258284
crew.updateDifficulty(request.getDifficulty());
259285
}
260-
286+
261287
crewRepository.save(crew);
262288
}
263289

runtracker/src/main/java/com/runtracker/domain/member/service/MemberService.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import com.runtracker.domain.member.enums.BackupType;
3333
import com.runtracker.domain.member.enums.MapStyle;
3434
import com.runtracker.domain.course.enums.Difficulty;
35+
import com.runtracker.global.util.ImageUpload;
3536
import com.runtracker.global.jwt.JwtUtil;
3637
import com.fasterxml.jackson.core.JsonProcessingException;
3738
import com.fasterxml.jackson.core.type.TypeReference;
@@ -66,6 +67,7 @@ public class MemberService {
6667
private final ScheduleRepository scheduleRepository;
6768
private final ObjectMapper objectMapper;
6869
private final JwtUtil jwtUtil;
70+
private final ImageUpload imageUploadHelper;
6971

7072
@Transactional
7173
public Member createOrUpdateMember(String socialAttr, String socialId,
@@ -145,8 +147,25 @@ public Member updateProfile(Long memberId, MemberUpdateDTO.Request memberUpdateD
145147
if (memberUpdateDTO.getDifficulty() != null) {
146148
validateDifficulty(memberUpdateDTO.getDifficulty());
147149
}
148-
149-
member.updateProfile(memberUpdateDTO);
150+
151+
if (memberUpdateDTO.getPhoto() != null) {
152+
String photoUrl = imageUploadHelper.convertBase64ToUrlIfNeeded(memberUpdateDTO.getPhoto());
153+
MemberUpdateDTO.Request updatedDTO = MemberUpdateDTO.Request.builder()
154+
.photo(photoUrl)
155+
.name(memberUpdateDTO.getName())
156+
.introduce(memberUpdateDTO.getIntroduce())
157+
.region(memberUpdateDTO.getRegion())
158+
.difficulty(memberUpdateDTO.getDifficulty())
159+
.age(memberUpdateDTO.getAge())
160+
.gender(memberUpdateDTO.getGender())
161+
.searchBlock(memberUpdateDTO.getSearchBlock())
162+
.profileBlock(memberUpdateDTO.getProfileBlock())
163+
.build();
164+
member.updateProfile(updatedDTO);
165+
} else {
166+
member.updateProfile(memberUpdateDTO);
167+
}
168+
150169
return member;
151170
}
152171

runtracker/src/main/java/com/runtracker/domain/upload/service/FileStorageService.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@
1111
import org.springframework.util.StringUtils;
1212
import org.springframework.web.multipart.MultipartFile;
1313

14+
import java.io.ByteArrayInputStream;
1415
import java.io.IOException;
1516
import java.io.InputStream;
1617
import java.net.MalformedURLException;
1718
import java.nio.file.Files;
1819
import java.nio.file.Path;
1920
import java.nio.file.Paths;
2021
import java.nio.file.StandardCopyOption;
22+
import java.util.Base64;
2123
import java.util.UUID;
2224

2325
@Slf4j
@@ -66,6 +68,36 @@ private String storeFile(MultipartFile file) {
6668
}
6769
}
6870

71+
// Base64 인코딩된 이미지를 파일로 저장하고 URL을 반환
72+
public String uploadBase64Image(String base64Data) {
73+
if (base64Data == null || base64Data.trim().isEmpty()) {
74+
return null;
75+
}
76+
77+
try {
78+
String base64Image = base64Data;
79+
if (base64Data.contains(",")) {
80+
base64Image = base64Data.split(",")[1];
81+
}
82+
83+
byte[] imageBytes = Base64.getDecoder().decode(base64Image);
84+
85+
String storedFilename = UUID.randomUUID() + ".webp";
86+
Path targetLocation = Paths.get(fileUploadConfig.getUploadDir()).resolve(storedFilename);
87+
88+
InputStream imageInputStream = new ByteArrayInputStream(imageBytes);
89+
InputStream webpInputStream = ImageConverter.convertToWebP(imageInputStream);
90+
91+
Files.copy(webpInputStream, targetLocation, StandardCopyOption.REPLACE_EXISTING);
92+
93+
return fileUploadConfig.getBaseUrl() + "/api/upload/image/" + storedFilename;
94+
95+
} catch (Exception ex) {
96+
log.error("Failed to store base64 image", ex);
97+
throw new FileStorageFailedException("base64-image");
98+
}
99+
}
100+
69101
public Resource loadFileAsResource(String filename) {
70102
try {
71103
Path filePath = Paths.get(fileUploadConfig.getUploadDir()).resolve(filename).normalize();

runtracker/src/main/java/com/runtracker/global/util/ImageConverter.java

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.springframework.web.multipart.MultipartFile;
88

99
import java.io.ByteArrayInputStream;
10+
import java.io.IOException;
1011
import java.io.InputStream;
1112

1213

@@ -26,24 +27,55 @@ public static InputStream convertToWebP(MultipartFile file) {
2627
public static InputStream convertToWebP(MultipartFile file, int quality) {
2728
try {
2829
ImmutableImage image = ImmutableImage.loader().fromBytes(file.getBytes());
30+
return processImageConversion(image, quality);
31+
} catch (Exception e) {
32+
log.error("Failed to convert image to WebP: {}", file.getOriginalFilename(), e);
33+
throw new ImageConversionFailedException(file.getOriginalFilename());
34+
}
35+
}
36+
37+
// InputStream을 WebP 포맷의 InputStream으로 변환
38+
public static InputStream convertToWebP(InputStream inputStream) {
39+
return convertToWebP(inputStream, DEFAULT_QUALITY);
40+
}
41+
42+
// InputStream을 WebP 포맷의 InputStream으로 변환
43+
public static InputStream convertToWebP(InputStream inputStream, int quality) {
44+
try {
45+
ImmutableImage image = ImmutableImage.loader().fromStream(inputStream);
46+
return processImageConversion(image, quality);
47+
} catch (Exception e) {
48+
log.error("Failed to convert image to WebP from InputStream", e);
49+
throw new ImageConversionFailedException("input-stream");
50+
}
51+
}
2952

30-
if (image.width > MAX_WIDTH || image.height > MAX_HEIGHT) {
31-
double scale = Math.min((double) MAX_WIDTH / image.width, (double) MAX_HEIGHT / image.height);
32-
int newWidth = (int) (image.width * scale);
33-
int newHeight = (int) (image.height * scale);
34-
image = image.scaleTo(newWidth, newHeight);
35-
log.info("Image resized from {}x{} to {}x{}",
36-
image.width, image.height, newWidth, newHeight);
37-
}
53+
// 이미지 처리 및 변환
54+
private static InputStream processImageConversion(ImmutableImage image, int quality) throws Exception {
55+
image = resizeIfNeeded(image);
56+
return convertImageToWebP(image, quality);
57+
}
3858

39-
WebpWriter writer = WebpWriter.DEFAULT.withQ(quality);
40-
byte[] webpBytes = image.bytes(writer);
59+
// 이미지 리사이징이 필요한 경우 리사이징 수행
60+
private static ImmutableImage resizeIfNeeded(ImmutableImage image) {
61+
if (image.width > MAX_WIDTH || image.height > MAX_HEIGHT) {
62+
int originalWidth = image.width;
63+
int originalHeight = image.height;
4164

42-
return new ByteArrayInputStream(webpBytes);
65+
double scale = Math.min((double) MAX_WIDTH / image.width, (double) MAX_HEIGHT / image.height);
66+
int newWidth = (int) (image.width * scale);
67+
int newHeight = (int) (image.height * scale);
4368

44-
} catch (Exception e) {
45-
log.error("Failed to convert image to WebP: {}", file.getOriginalFilename(), e);
46-
throw new ImageConversionFailedException(file.getOriginalFilename());
69+
image = image.scaleTo(newWidth, newHeight);
70+
log.info("Image resized from {}x{} to {}x{}", originalWidth, originalHeight, newWidth, newHeight);
4771
}
72+
return image;
73+
}
74+
75+
// ImmutableImage를 WebP 포맷의 InputStream으로 변환
76+
private static InputStream convertImageToWebP(ImmutableImage image, int quality) throws IOException {
77+
WebpWriter writer = WebpWriter.DEFAULT.withQ(quality);
78+
byte[] webpBytes = image.bytes(writer);
79+
return new ByteArrayInputStream(webpBytes);
4880
}
4981
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.runtracker.global.util;
2+
3+
import com.runtracker.domain.upload.service.FileStorageService;
4+
import lombok.RequiredArgsConstructor;
5+
import lombok.extern.slf4j.Slf4j;
6+
import org.springframework.stereotype.Component;
7+
8+
@Slf4j
9+
@Component
10+
@RequiredArgsConstructor
11+
public class ImageUpload {
12+
13+
private final FileStorageService fileStorageService;
14+
15+
// Base64 이미지 데이터를 URL로 변환
16+
public String convertBase64ToUrlIfNeeded(String data) {
17+
if (data == null || data.trim().isEmpty()) {
18+
return null;
19+
}
20+
21+
try {
22+
// Base64 데이터인지 확인
23+
if (data.startsWith("data:image") || data.length() > 500) {
24+
return fileStorageService.uploadBase64Image(data);
25+
}
26+
// 이미 URL인 경우 그대로 반환
27+
return data;
28+
} catch (Exception e) {
29+
log.error("Failed to convert Base64 image to URL", e);
30+
return data;
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)