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
4 changes: 4 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ services:
image: redis:latest
container_name: redis
restart: always
ports:
- "6379:6379" # 외부 접속 가능하도록 포트 바인딩 추가
command: ["redis-server", "--bind", "0.0.0.0", "--protected-mode", "no"] # 외부 허용 설정
volumes:
- redis_data:/data


spring:
image: mdy3722/bubblog-springboot:latest
container_name: bubblog
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,6 @@ public BlogPostDetailDTO(BlogPost post, List<String> categoryList, List<String>
this.userId = post.getUser().getId();
this.nickname = post.getUser().getNickname();
this.categoryList = categoryList;
this.tags = tags;
this.tags = tags != null ? tags : List.of();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public class BlogPost {
@OneToMany(mappedBy = "post", cascade = CascadeType.REMOVE)
private List<PostLike> likes = new ArrayList<>();

@OneToMany(mappedBy = "post", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
private List<PostTag> postTags = new ArrayList<>();

@OneToMany(mappedBy = "post", fetch = FetchType.LAZY) // Soft delete 이므로 CASCADE 설정 X
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,18 @@
import Bubble.bubblog.global.exception.CustomException;
import Bubble.bubblog.global.exception.ErrorCode;
import Bubble.bubblog.global.service.AiService;
import Bubble.bubblog.global.service.EmbeddingProducer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class BlogPostServiceImpl implements BlogPostService {
Expand All @@ -46,6 +46,7 @@ public class BlogPostServiceImpl implements BlogPostService {
private final PostTagRepository postTagRepository;
private final AiService aiService;
private final CommentRepository commentRepository;
private final EmbeddingProducer embeddingProducer;

@Transactional
@Override
Expand Down Expand Up @@ -73,16 +74,17 @@ public BlogPostDetailDTO createPost(BlogPostRequestDTO request, UUID userId) {

List<String> tags = request.getTags(); // tag 리스트를 프론트에서 받음

for (String tagName : tags) {
Tag tag = tagRepository.findByName(tagName)
.orElseGet(() -> tagRepository.save(new Tag(tagName)));
if (tags != null) {
for (String tagName : tags) {
Tag tag = tagRepository.findByName(tagName)
.orElseGet(() -> tagRepository.save(new Tag(tagName)));

postTagRepository.save(new PostTag(post, tag));
postTagRepository.save(new PostTag(post, tag));
}
}

// AI 서버에 임베딩 요청
aiService.handlePostTitle(post.getId(), post.getTitle());
aiService.handlePostContent(post.getId(), post.getContent());
// Redis 큐로 임베딩 요청 전송
embeddingProducer.sendEmbeddingRequest(post.getId(), true, true);

return new BlogPostDetailDTO(post, categoryList, tags);
}
Expand Down Expand Up @@ -206,14 +208,6 @@ public BlogPostDetailDTO updatePost(Long postId, BlogPostRequestDTO request, UUI
boolean titleChanged = !Objects.equals(oldTitle, request.getTitle());
boolean contentChanged = !Objects.equals(oldContent, request.getContent());

// 분기 처리
if (titleChanged) {
aiService.handlePostTitle(post.getId(), request.getTitle());
}
if (contentChanged) {
aiService.handlePostContent(post.getId(), request.getContent());
}

post.update(
request.getTitle(),
request.getContent(),
Expand All @@ -223,16 +217,33 @@ public BlogPostDetailDTO updatePost(Long postId, BlogPostRequestDTO request, UUI
category
);

// 기존 태그 관계 삭제
postTagRepository.deleteByPost(post);

// 기존 태그 관계를 모두 끊음
post.getPostTags().clear();

// flush()로 예약 되어 있던 clear()명령어를 바로 처리
// 처리하지 않으면 clear()를 예약한 상태에서 Insert가 먼저 진행되고,
// 만약 tag 내용의 변화가 없을 때 수정된 게시글 Insert 시 (postId, tagId)키값의 중복 제약이 걸림
blogPostRepository.flush();

// 요청받은 태그 리스트를 가져옴
List<String> tags = request.getTags();

// 새 태그 저장
for (String tagName : tags) {
Tag tag = tagRepository.findByName(tagName)
.orElseGet(() -> tagRepository.save(new Tag(tagName)));
post.addTag(tag);
if (tags != null) {
for (String tagName : tags) {
Tag tag = tagRepository.findByName(tagName)
.orElseGet(() -> tagRepository.save(new Tag(tagName)));
post.addTag(tag);
}
}

// 레디스 큐로 LPUSH
if(titleChanged || contentChanged){
embeddingProducer.sendEmbeddingRequest(
post.getId(),
titleChanged, // title 수정 여부
contentChanged // content 수정 여부
);
}

return new BlogPostDetailDTO(post, categoryList, tags);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ public CustomException(ErrorCode errorCode) {
this.errorCode = errorCode;
}

public CustomException(ErrorCode errorCode, Throwable cause) {
super(errorCode.getMessage(), cause); // 원인 예외(cause)를 함께 넘겨줍니다.
this.errorCode = errorCode;
}

public int getCode() {
return errorCode.getCode();
}
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/Bubble/bubblog/global/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ public enum ErrorCode {
NO_PERMISSION_TO_EDIT_COMMENT(403, "해당 댓글을 수정할 권한이 없습니다."),
NO_PERMISSION_TO_DELETE_COMMENT(403, "해당 댓글을 삭제할 권한이 없습니다."),
NOT_ROOT_COMMENT(400, "자식 댓글의 스레드를 조회할 수 없습니다. 루트 댓글의 스레드를 조회해주세요."),
CANNOT_LIKE_DELETED_COMMENT(400, "삭제된 댓글에 좋아요를 누를 수 없습니다.");
CANNOT_LIKE_DELETED_COMMENT(400, "삭제된 댓글에 좋아요를 누를 수 없습니다."),

SERIALIZATION_ERROR(500, "데이터 직렬화에 실패했습니다.");
// S3

private final int code;
Expand Down
40 changes: 40 additions & 0 deletions src/main/java/Bubble/bubblog/global/service/EmbeddingProducer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package Bubble.bubblog.global.service;

import Bubble.bubblog.global.exception.CustomException;
import Bubble.bubblog.global.exception.ErrorCode;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Map;

@Slf4j
@Service
@RequiredArgsConstructor
public class EmbeddingProducer {
private final StringRedisTemplate redisTemplate;
private final ObjectMapper objectMapper;

private static final String QUEUE_KEY = "embedding:queue";

public void sendEmbeddingRequest(Long postId, Boolean title, Boolean content) {
try {
Map<String, Object> message = Map.of(
"postId", postId,
"title", title,
"content", content
);

String json = objectMapper.writeValueAsString(message);
redisTemplate.opsForList().leftPush(QUEUE_KEY, json);

log.info("✅ [Redis] Sent embedding request for postId: {}", postId);
} catch (JsonProcessingException e) {
log.error("❌ Failed to serialize embedding request for post {}", postId, e);
throw new CustomException(ErrorCode.SERIALIZATION_ERROR, e);
}
}
}
7 changes: 4 additions & 3 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ springdoc:

logging:
level:
root: DEBUG
org.springframework: DEBUG
org.hibernate.SQL: DEBUG
root: INFO # 애플리케이션 전체는 INFO로
org.springframework: WARN # 스프링 내부 디버그 로그는 최소화
org.hibernate.SQL: DEBUG # SQL 쿼리만 보고 싶으면 DEBUG 유지
org.hibernate.type.descriptor.sql.BasicBinder: TRACE # 바인딩 값 보고 싶으면