Skip to content
Open
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
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation "org.hibernate.orm:hibernate-spatial:6.6.39.Final"

implementation 'org.springframework.boot:spring-boot-starter-amqp'

}

tasks.withType(JavaCompile) {
Expand Down
16 changes: 16 additions & 0 deletions docker-compose.local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ services:
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro

rabbitmq:
image: rabbitmq:3-management
container_name: rabbitmq
ports:
- "5672:5672" # AMQP
- "15672:15672" # Management UI
environment:
RABBITMQ_DEFAULT_USER: guest
RABBITMQ_DEFAULT_PASS: guest
Comment on lines +31 to +36

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1. 파일 존재 확인
if [ -f "docker-compose.local.yml" ]; then
  echo "[✓] docker-compose.local.yml 파일 발견"
  
  # 2. 파일 크기 확인
  wc -l docker-compose.local.yml
  
  # 3. RabbitMQ 섹션 확인 (28-40 라인 범위)
  echo -e "\n[RabbitMQ 섹션 확인]"
  sed -n '28,40p' docker-compose.local.yml
  
  # 4. 포트 바인딩 패턴 확인
  echo -e "\n[포트 바인딩 패턴]"
  grep -n "5672\|15672" docker-compose.local.yml || echo "RabbitMQ 포트 미발견"
  
  # 5. RABBITMQ 환경변수 확인
  echo -e "\n[RABBITMQ 환경변수]"
  grep -n "RABBITMQ_DEFAULT" docker-compose.local.yml || echo "RABBITMQ 환경변수 미발견"
  
  # 6. .env 파일 확인
  echo -e "\n[.env 파일 확인]"
  if [ -f ".env" ] || [ -f ".env.local" ]; then
    echo ".env 파일 발견"
    [ -f ".env" ] && head -10 .env
    [ -f ".env.local" ] && head -10 .env.local
  else
    echo ".env 파일 미발견"
  fi
else
  echo "[✗] docker-compose.local.yml 파일 미발견"
  echo "사용 가능한 compose 파일:"
  find . -name "docker-compose*.yml" -o -name "docker-compose*.yaml"
fi

Repository: D-ingle/backend

Length of output: 669


RabbitMQ 자격증명과 포트 바인딩 설정 개선 필요

Lines 32-33의 포트가 모든 인터페이스(0.0.0.0)에 바인딩되어 있고, Lines 35-36의 guest/guest 자격증명이 하드코딩되어 있습니다. 로컬 개발환경에서도 다음을 권장합니다:

  1. 포트 바인딩을 127.0.0.1로 제한하여 로컬호스트 접근만 허용
  2. 자격증명을 .env 파일로 분리하여 버전 제어 대상에서 제외
권장 수정안
     ports:
-      - "5672:5672"    # AMQP
-      - "15672:15672"  # Management UI
+      - "127.0.0.1:5672:5672"    # AMQP (로컬 접근만)
+      - "127.0.0.1:15672:15672"  # Management UI (로컬 접근만)
     environment:
-      RABBITMQ_DEFAULT_USER: guest
-      RABBITMQ_DEFAULT_PASS: guest
+      RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER:-guest}
+      RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS:-guest}

.env 파일에 추가:

RABBITMQ_DEFAULT_USER=guest
RABBITMQ_DEFAULT_PASS=guest
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docker-compose.local.yml` around lines 31 - 36, Change the port bindings from
"5672:5672" and "15672:15672" to bind only to localhost by using
"127.0.0.1:5672:5672" and "127.0.0.1:15672:15672", and stop hardcoding
credentials by replacing RABBITMQ_DEFAULT_USER: guest and RABBITMQ_DEFAULT_PASS:
guest with environment variable references like RABBITMQ_DEFAULT_USER:
${RABBITMQ_DEFAULT_USER} and RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS};
then add the actual values to your .env (e.g. RABBITMQ_DEFAULT_USER=guest,
RABBITMQ_DEFAULT_PASS=guest) and ensure the .env is excluded from version
control.

volumes:
- rabbitmq-data:/var/lib/rabbitmq
Comment thread
coderabbitai[bot] marked this conversation as resolved.
restart: unless-stopped

app:
build: .
image: dingle-app
Expand All @@ -40,3 +53,6 @@ services:
SPRING_DATASOURCE_URL: jdbc:postgresql://${POSTGRES_URL}:5432/${POSTGRES_DB}
SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER}
SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD}

volumes:
rabbitmq-data:
61 changes: 61 additions & 0 deletions src/main/java/com/example/Dingle/global/config/RabbitConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.example.Dingle.global.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
@EnableRabbit
public class RabbitConfig {

public static final String EXCHANGE = "property.exchange";
public static final String QUEUE = "property.created.queue";
public static final String ROUTING_KEY = "property.created";

@Bean
public DirectExchange propertyExchange() {
return new DirectExchange(EXCHANGE);
}

@Bean
public Queue propertyCreatedQueue() {
return QueueBuilder.durable(QUEUE).build();
}

@Bean
public Binding propertyCreatedBinding(Queue propertyCreatedQueue, DirectExchange propertyExchange) {
return BindingBuilder.bind(propertyCreatedQueue)
.to(propertyExchange)
.with(ROUTING_KEY);
}

@Bean
public Jackson2JsonMessageConverter jackson2JsonMessageConverter(ObjectMapper objectMapper) {
return new Jackson2JsonMessageConverter(objectMapper);
}

@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory cf, Jackson2JsonMessageConverter converter) {
RabbitTemplate template = new RabbitTemplate(cf);
template.setMessageConverter(converter);
return template;
}

@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
ConnectionFactory cf,
Jackson2JsonMessageConverter converter
) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(cf);
factory.setMessageConverter(converter);
return factory;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@

import com.example.Dingle.global.dto.ResponseDTO;
import com.example.Dingle.property.dto.PropertyCompareDTO;
import com.example.Dingle.property.dto.PropertyCreatedEvent;
import com.example.Dingle.property.dto.PropertyListDTO;
import com.example.Dingle.property.dto.PropertyRegisterRequestDTO;
import com.example.Dingle.property.service.PropertyListService;
import com.example.Dingle.property.service.PropertyService;
import com.example.Dingle.user.dto.CustomUserDetails;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDate;
import java.util.List;

@RestController
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.example.Dingle.property.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDate;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class PropertyCreatedEvent {

private Long propertyId;
private LocalDate createdAt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.example.Dingle.property.service;

import com.example.Dingle.property.service.openAI.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

@Service
@RequiredArgsConstructor
public class PropertyAsyncOrchestrator {
private final EnvironmentExplanationService environmentExplanationService;
private final ConvenienceExplanationService convenienceExplanationService;
private final AccessibilityExplanationService accessibilityExplanationService;
private final NoiseExplanationService noiseExplanationService;
private final SafetyExplanationService safetyExplanationService;

public void process(Long propertyId) {
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();

try {
Future<Void> f1 = executor.submit(() -> {
environmentExplanationService.evaluateAndDescribe(propertyId);
return null;
});

Future<Void> f2 = executor.submit(() -> {
convenienceExplanationService.evaluateAndDescribe(propertyId);
return null;
});

Future<Void> f3 = executor.submit(() -> {
accessibilityExplanationService.evaluateAndDescribe(propertyId);
return null;
});

Future<Void> f4 = executor.submit(() -> {
noiseExplanationService.evaluateAndDescribe(propertyId);
return null;
});

Future<Void> f5 = executor.submit(() -> {
safetyExplanationService.evaluateAndDescribe(propertyId);
return null;
});

f1.get();
f2.get();
f3.get();
f4.get();
f5.get();

} catch (Exception e) {
throw new RuntimeException("Property async processing failed", e);

} finally {
executor.close();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.example.Dingle.global.exception.BusinessException;
import com.example.Dingle.global.message.BusinessErrorMessage;
import com.example.Dingle.property.dto.DealRequestDTO;
import com.example.Dingle.property.dto.PropertyCreatedEvent;
import com.example.Dingle.property.dto.PropertyRegisterRequestDTO;
import com.example.Dingle.property.entity.*;
import com.example.Dingle.property.repository.*;
Expand All @@ -14,9 +15,11 @@
import com.example.Dingle.realtor.entity.Realtor;
import com.example.Dingle.realtor.repository.RealtorRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.List;

@Service
Expand All @@ -32,9 +35,10 @@ public class PropertyService {
private final RealtorRepository realtorRepository;
private final DistrictRepository districtRepository;
private final PropertyImageRepository propertyImageRepository;
private final ApplicationEventPublisher applicationEventPublisher;

@Transactional
public void register(PropertyRegisterRequestDTO request) {
public Long register(PropertyRegisterRequestDTO request) {

Realtor realtor = realtorRepository.findById(1L)
.orElseThrow(() -> new BusinessException(BusinessErrorMessage.REALTOR_NOT_EXISTS));
Expand Down Expand Up @@ -65,6 +69,10 @@ public void register(PropertyRegisterRequestDTO request) {
saveOptions(property, request.getOptions());
saveFacilities(property, request.getFacilities());
saveImages(property, request);

applicationEventPublisher.publishEvent(new PropertyCreatedEvent(property.getId(), LocalDate.now()));

return property.getId();
}

private void saveDeal(Property property, DealRequestDTO deal) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.example.Dingle.property.util;

import com.example.Dingle.global.config.RabbitConfig;
import com.example.Dingle.property.dto.PropertyCreatedEvent;
import com.example.Dingle.property.service.PropertyAsyncOrchestrator;
import lombok.RequiredArgsConstructor;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class PropertyCreatedConsumer {
private final PropertyAsyncOrchestrator orchestrator;

@RabbitListener(queues = RabbitConfig.QUEUE)
public void handle(PropertyCreatedEvent event) {
orchestrator.process(event.getPropertyId());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.example.Dingle.property.util;

import com.example.Dingle.global.config.RabbitConfig;
import com.example.Dingle.property.dto.PropertyCreatedEvent;
import lombok.RequiredArgsConstructor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;

@Component
@RequiredArgsConstructor
public class PropertyEventRelay {

private final RabbitTemplate rabbitTemplate;

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void on(PropertyCreatedEvent event) {
rabbitTemplate.convertAndSend(
RabbitConfig.EXCHANGE,
RabbitConfig.ROUTING_KEY,
event
);
}
}