Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ dependencies {

//OpenFeign: 외부 API 사용
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:4.1.4'

// html 파싱을 위한 jsoup
implementation 'org.jsoup:jsoup:1.16.1'
}

tasks.named('test') {
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/com/woozuda/backend/diary/service/DiaryService.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import com.woozuda.backend.diary.dto.response.SingleDiaryResponseDto;
import com.woozuda.backend.diary.entity.Diary;
import com.woozuda.backend.diary.repository.DiaryRepository;
import com.woozuda.backend.image.service.ImageService;
import com.woozuda.backend.image.type.ImageType;
import com.woozuda.backend.note.dto.request.NoteCondRequestDto;
import com.woozuda.backend.note.dto.response.NoteEntryResponseDto;
import com.woozuda.backend.note.dto.response.NoteResponseDto;
Expand Down Expand Up @@ -40,6 +42,7 @@ public class DiaryService {
private final UserRepository userRepository;
private final TagRepository tagRepository;
private final NoteRepository noteRepository;
private final ImageService imageService;

@Transactional(readOnly = true)
public DiaryListResponseDto getDairyList(String username) {
Expand Down Expand Up @@ -100,6 +103,10 @@ public DiaryIdResponseDto saveDiary(String username, DiarySaveRequestDto request
}

Diary savedDiary = diaryRepository.save(diary);

// 이미지 테이블 변경(다이어리 생성 시)
imageService.afterCreate(ImageType.DIARY, savedDiary.getId(), requestDto.getImgUrl());

return new DiaryIdResponseDto(savedDiary.getId());
}

Expand All @@ -122,6 +129,9 @@ public DiaryIdResponseDto updateDiary(String username, Long diaryId, DiarySaveRe
diary.change(requestDto.getTitle(), tags, requestDto.getImgUrl());
}

// 이미지 테이블 반영 (다이어리 변경 시)
imageService.afterUpdate(ImageType.DIARY, diaryId, requestDto.getImgUrl());

return new DiaryIdResponseDto(foundDiary.get().getId());
}

Expand All @@ -134,6 +144,9 @@ public void removeDiary(String username, Long diaryId) {
throw new IllegalArgumentException("This diary does not belong to the user.");
}

// 이미지 테이블 반영 (다이어리 삭제 시)
imageService.afterDelete(ImageType.DIARY, diaryId);

diaryRepository.deleteById(diaryId);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.List;

@RequestMapping("/api/image")
@RequiredArgsConstructor
Expand All @@ -28,6 +29,14 @@ public ResponseEntity<ImageDto> uploadImage(MultipartFile multipartFile) throws
return ResponseEntity.ok(responseDto);
}

/*
@PostMapping("/delete")
public void deleteImage(){
String url = "https://kr.object.ncloudstorage.com/woozuda-image/test-dummy.png";
imageService.deleteImage(url.split("/")[4]);
}
*/

// 랜덤 이미지 추출
@GetMapping("/random")
public ResponseEntity<ImageDto> getRandomImage(){
Expand Down
36 changes: 36 additions & 0 deletions src/main/java/com/woozuda/backend/image/cron/ImageDeleteTask.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.woozuda.backend.image.cron;

import com.woozuda.backend.image.entity.Image;
import com.woozuda.backend.image.repository.ImageRepository;
import com.woozuda.backend.image.service.ImageService;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@RequiredArgsConstructor
@Component
public class ImageDeleteTask {

private final ImageService imageService;
private final ImageRepository imageRepository;

@Scheduled(cron = "0 0 3 * * *") // 매일 오전 3시
public void cleanUpImages() {
// 게시물과 연결되지 않은 이미지 조회
List<Image> deleteImages = imageRepository.findByIsLinkedToPost(false);
List<Long> deleteImageIds = new ArrayList<>();

// 해당 이미지 object storage 에서 삭제
for(Image deleteImage : deleteImages){
// 지울 이미지들의 Id 추가 (db 에도 반영 위해서)
deleteImageIds.add(deleteImage.getId());
imageService.deleteImage(deleteImage.getImageUrl().split("/")[4]);
}

// db(이미지 테이블) 도 그에 맞추어 삭제
imageRepository.deleteAllById(deleteImageIds);
}
}
23 changes: 20 additions & 3 deletions src/main/java/com/woozuda/backend/image/entity/Image.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@


import com.woozuda.backend.global.entity.BaseTimeEntity;
import com.woozuda.backend.image.type.ImageType;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
@Entity
@Table(name="image")
Expand All @@ -23,12 +22,30 @@ public class Image extends BaseTimeEntity {
@Column(name = "is_linked_to_post")
private Boolean isLinkedToPost;

public Image(String imageUrl, Boolean isLinkedToPost) {
@Column(name = "image_type")
private ImageType imageType;

@Column(name = "connected_id")
private Long connectedId;

private Image(String imageUrl, Boolean isLinkedToPost) {
this.imageUrl = imageUrl;
this.isLinkedToPost = isLinkedToPost;
}

public static Image of(String imageUrl, Boolean isLinkedToPost) {
return new Image(imageUrl, isLinkedToPost);
}

public void changeLinkedToPost(Boolean isLinkedToPost) {
this.isLinkedToPost = isLinkedToPost;
}

public void changeImageType(ImageType imageType){
this.imageType = imageType;
}

public void changeConectedId(Long connectedId){
this.connectedId = connectedId;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
package com.woozuda.backend.image.repository;

import com.woozuda.backend.image.entity.Image;
import com.woozuda.backend.image.type.ImageType;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface ImageRepository extends JpaRepository<Image,Long> {

// ImageType 과 ConnectedId 가 일치하는 이미지를 찾는 쿼리
public List<Image> findByImageTypeAndConnectedId(ImageType imageType, Long connectedId);

// imageUrl이 일치하는 이미지를 찾는 쿼리 (select * from image where image_url IN (image_url1 , image_url2 , ....)
public List<Image> findByImageUrlIn(List<String> imageUrls);

// isLinkedToPost 가 true/false 인 이미지 찾기.
public List<Image> findByIsLinkedToPost(Boolean isLinkedToPost);

public Image findByImageUrl(String imageUrl);
}
121 changes: 118 additions & 3 deletions src/main/java/com/woozuda/backend/image/service/ImageService.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,34 @@
import com.woozuda.backend.image.dto.ImageDto;
import com.woozuda.backend.image.entity.Image;
import com.woozuda.backend.image.repository.ImageRepository;
import com.woozuda.backend.image.type.ImageType;
import jakarta.persistence.Column;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.UUID;

@Service
@RequiredArgsConstructor
@Transactional
@Slf4j
public class ImageService {

private final S3Client s3Client;
private final ImageRepository imageRepository;



@Transactional
public ImageDto uploadImage(MultipartFile multipartFile) throws IOException {

// 올릴 이미지가 첨부되지 않았을 경우
Expand All @@ -39,6 +49,9 @@ public ImageDto uploadImage(MultipartFile multipartFile) throws IOException {

// 파일 이름 추출
String filename = multipartFile.getOriginalFilename();
String uuid = UUID.randomUUID().toString();
filename = uuid + "_" +filename;

String bucketname = s3Client.getBucketName();

// 메타데이터 생성
Expand All @@ -62,7 +75,109 @@ public ImageDto uploadImage(MultipartFile multipartFile) throws IOException {
return new ImageDto(imgUrl);
}

@Transactional
public void deleteImage(String filename){
AmazonS3 s3 = s3Client.getAmazonS3();
s3.deleteObject(s3Client.getBucketName(), filename);
}

// urlContent 를 가공해주는 메서드
public List<String> makeImgsUrl(ImageType imageType, String urlContent){

List<String> imgs = new ArrayList<>();

if(imageType == ImageType.NOTE){
// Note는 content가 <p> 안녕하세요 </p> <img src ="https://~~> 처럼 되어 있어서 파싱 해야함
Document doc = Jsoup.parse(urlContent);
Elements imgTags = doc.select("img");

for(Element img: imgTags){
imgs.add(img.attr("src"));
}
}else if(imageType == ImageType.DIARY) {
//다이어리 같은 경우에는 imgUrl 이 바로 들어있어서 배열에 넣기만 하면 됨
imgs.add(urlContent);
}

return imgs;
}

// 다이어리 - 랜덤 이미지 생성 (기본 이미지 10장) 은 연산에서 제외 되어야 함 - 기본 제공 이미지들.
// 현재 기본 이미지가 이미지 테이블에 저장되지는 않았으나, 혹시 모를 변수(프론트의 약간의 착오 등...) 를 위해 예외 처리 합니다.
public List<String> excludeBasicImage(List<String> imageStrings){
imageStrings.removeIf(image -> image.startsWith("https://kr.object.ncloudstorage.com/woozuda-image/random-image"));
return imageStrings;
}

public void afterCreate(ImageType imageType, Long connectedId, String content){
//content에서 imageUrl 추출( 리스트로 변환)
List<String> imagesUrl = makeImgsUrl(imageType, content);

//기본 이미지 - 주어지는 default 이미지는 제외 (필수는 아니나 혹시 해서 넣어둔 로직)
imagesUrl = excludeBasicImage(imagesUrl);

List<Image> images = imageRepository.findByImageUrlIn(imagesUrl);

// 영속 상태의 엔티티라 save() 하지 않아도 반영됨
for(Image image : images){
image.changeLinkedToPost(true);
image.changeImageType(imageType);
image.changeConectedId(connectedId);
}
}

public void afterUpdate(ImageType imageType, Long connectedId, String content){

List<String> imagesUrl = makeImgsUrl(imageType, content);

//기본 이미지 - 주어지는 default 이미지는 제외 (필수는 아니나 혹시 해서 넣어둔 로직)
imagesUrl = excludeBasicImage(imagesUrl);

// 이번에 업데이트할 글에 저장되어있는 그림들 리스트
List<Image> afterImages = imageRepository.findByImageUrlIn(imagesUrl);
boolean[] afterImagesBool = new boolean[afterImages.size()];

//기존에 글(다이어리)에 저장되어있던 그림들을 불러옴
List<Image> beforeImages = imageRepository.findByImageTypeAndConnectedId(imageType, connectedId);
boolean[] beforeImagesBool = new boolean[beforeImages.size()];

//수정 전과 수정 후에 둘다 있는 이미지는 true 처리 - 그리고 이 이미지들은 수정할 필요 없음.
for(int i=0; i < beforeImages.size(); i++){
for(int j=0; j < afterImages.size(); j++) {
if (beforeImages.get(i).getImageUrl().equals(afterImages.get(j).getImageUrl())) {
beforeImagesBool[i] = true;
afterImagesBool[j] = true;
}
}
}

// 수정 전 이미지 리스트 중 false 인 것들은 이번에 수정되면서 삭제된 이미지 임 - 고로 삭제 처리 해줘야 함
List<Long> deleteImagesId = new ArrayList<>();
for(int i=0; i < beforeImages.size(); i++){
if(!beforeImagesBool[i]){
deleteImagesId.add(beforeImages.get(i).getId());
}
}
imageRepository.deleteAllById(deleteImagesId);

// 수정 후 이미지 리스트 중 false 인 것들은 이번에 수정되면서 추가된 이미지 임 - 고로 추가 처리 해줘야 함
for(int j=0; j < afterImages.size(); j++){
if(!afterImagesBool[j]){
Image nowImage = afterImages.get(j);

nowImage.changeLinkedToPost(true);
nowImage.changeImageType(imageType);
nowImage.changeConectedId(connectedId);
}
}

}

public void afterDelete(ImageType imageType, Long connectedId){
List<Image> images = imageRepository.findByImageTypeAndConnectedId(imageType, connectedId);
imageRepository.deleteAll(images);
}

@Transactional(readOnly = true)
public ImageDto getRandomImage() {
Random random = new Random();
int imageNumber = random.nextInt(10) + 1;
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/com/woozuda/backend/image/type/ImageType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.woozuda.backend.image.type;

public enum ImageType {
DIARY,
NOTE
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import com.woozuda.backend.diary.dto.response.NoteIdResponseDto;
import com.woozuda.backend.diary.entity.Diary;
import com.woozuda.backend.diary.repository.DiaryRepository;
import com.woozuda.backend.image.service.ImageService;
import com.woozuda.backend.image.type.ImageType;
import com.woozuda.backend.note.dto.request.CommonNoteModifyRequestDto;
import com.woozuda.backend.note.dto.request.CommonNoteSaveRequestDto;
import com.woozuda.backend.note.dto.response.NoteResponseDto;
Expand Down Expand Up @@ -33,6 +35,7 @@ public class CommonNoteService {
private final NoteRepository noteRepository;
private final DiaryRepository diaryRepository;
private final AlarmService alarmService;
private final ImageService imageService;

public NoteIdResponseDto saveCommonNote(String username, CommonNoteSaveRequestDto requestDto) {
Diary foundDiary = diaryRepository.searchDiary(requestDto.getDiaryId(), username);
Expand All @@ -57,6 +60,9 @@ public NoteIdResponseDto saveCommonNote(String username, CommonNoteSaveRequestDt
// 이번에 저장한 자유일기가 그 주의 3번째 일기라면(자유일기 + 질문일기 기준), 알람을 발생합니다.
alarmService.threePostAlarm(username, requestDto.getDate());

// 이미지 테이블 반영 (자유일기 생성 후)
imageService.afterCreate(ImageType.NOTE, savedCommonNote.getId(), requestDto.getContent());

return NoteIdResponseDto.of(savedCommonNote.getId());
}

Expand Down Expand Up @@ -85,6 +91,9 @@ public NoteIdResponseDto updateCommonNote(String username, Long noteId, CommonNo
requestDto.getContent()
);

// 이미지 테이블 반영 (자유일기 변경 후)
imageService.afterUpdate(ImageType.NOTE, noteId, requestDto.getContent());

return NoteIdResponseDto.of(foundNote.getId());
}
}
Loading
Loading