Skip to content
6 changes: 3 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ dependencies {
runtimeOnly 'com.mysql:mysql-connector-j'

// MongoDB
/*implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
implementation 'org.mongodb:mongodb-driver-sync'
implementation 'org.mongodb:mongodb-driver-core'*/
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
// implementation 'org.mongodb:mongodb-driver-sync'
// implementation 'org.mongodb:mongodb-driver-core'

// redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;

@EnableJpaAuditing
@SpringBootApplication
@EnableScheduling
@EnableMongoRepositories
public class HyetaekonApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// κ²½λ‘œλ³„ 인가 μž‘μ—…
http
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/api/posts/type", "/api/posts/type/**", "/api/posts/search").permitAll()
.requestMatchers(SecurityPath.ADMIN_ENDPOINTS).hasRole("ADMIN")
.requestMatchers(SecurityPath.USER_ENDPOINTS).hasAnyRole("USER", "ADMIN")
.requestMatchers(HttpMethod.GET, SecurityPath.PUBLIC_GET_ENDPOINTS).permitAll()
.requestMatchers(SecurityPath.PUBLIC_ENDPOINTS).permitAll()
.anyRequest().permitAll()
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,17 @@ public class SecurityPath {
"/api/users/check-duplicate",
"/",
"/api/services",
"/api/services/popular",
"/api/services/category/*",
"/api/services/detail/*"
};

// GET λ©”μ†Œλ“œμ— λŒ€ν•΄ permitAll
public static final String[] PUBLIC_GET_ENDPOINTS = {
"/api/public-data/serviceList/test",
"/api/posts",
"/api/posts/search"
"/api/services/detail/*",
"/api/public-data/serviceList/test"
};


// hasRole("USER")
public static final String[] USER_ENDPOINTS = {
"/api/users/me",
"/api/users/me/**",
"/users/me/recommended/posts",
"/api/logout",
"/api/services/popular",
"/api/services/*/bookmark",
"/api/interests",
"/api/interests/me",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ public enum ErrorCode {

// κ³΅κ³΅μ„œλΉ„μŠ€
// 유효 JACODE 확인
INVALID_ENUM_CODE(HttpStatus.BAD_REQUEST, "ENUM-001", "μœ νš¨ν•˜μ§€ μ•Šμ€ μ½”λ“œ κ°’μž…λ‹ˆλ‹€."),
INVALID_ENUM_CODE(HttpStatus.BAD_REQUEST, "SERVICE-001", "μœ νš¨ν•˜μ§€ μ•Šμ€ μ½”λ“œ κ°’μž…λ‹ˆλ‹€."),
INCOMPLETE_SERVICE_DETAIL(HttpStatus.BAD_REQUEST, "SERVICE-002","μ„œλΉ„μŠ€ 상세 정보가 λΆˆμ™„μ „ν•©λ‹ˆλ‹€."),

SERVICE_CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, "SERVICE-001", "ν•΄λ‹Ή μ„œλΉ„μŠ€ λΆ„μ•Όλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."),
SERVICE_NOT_FOUND_BY_ID(HttpStatus.NOT_FOUND,"SERVICE-002", "ν•΄λ‹Ή μ•„μ΄λ””μ˜ μ„œλΉ„μŠ€λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.hyetaekon.hyetaekon.common.publicdata.mongodb.document;

import java.time.LocalDateTime;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "service_info")
public class PublicData {
@Id
private String id;

private String publicServiceId;
private String serviceName;
private String summaryPurpose;
private String serviceCategory;
private List<String> specialGroup;
private List<String> familyType;

private List<String> occupations;
private List<String> businessTypes;

// Support conditions fields
private String targetGenderMale;
private String targetGenderFemale;
private Integer targetAgeStart;
private Integer targetAgeEnd;
private String incomeLevel;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.hyetaekon.hyetaekon.common.publicdata.mongodb.repository;

import com.hyetaekon.hyetaekon.common.publicdata.mongodb.document.PublicData;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface PublicDataMongoRepository extends MongoRepository<PublicData, String> {
Optional<PublicData> findByPublicServiceId(String publicServiceId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package com.hyetaekon.hyetaekon.common.publicdata.mongodb.service;

import com.hyetaekon.hyetaekon.common.publicdata.mongodb.document.PublicData;
import com.hyetaekon.hyetaekon.common.publicdata.mongodb.repository.PublicDataMongoRepository;
import com.hyetaekon.hyetaekon.publicservice.entity.PublicService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class PublicDataMongoService {

private final PublicDataMongoRepository mongoRepository;
private final MongoTemplate mongoTemplate;

/**
* 단일 κ³΅κ³΅μ„œλΉ„μŠ€ μ—”ν‹°ν‹°λ₯Ό MongoDB에 μ €μž₯
*/
public PublicData saveToMongo(PublicService publicService) {
PublicData document = convertToDocument(publicService);
return mongoRepository.save(document);
}

/**
* μ—¬λŸ¬ κ³΅κ³΅μ„œλΉ„μŠ€ μ—”ν‹°ν‹°λ₯Ό MongoDB에 μ €μž₯
*/
public List<PublicData> saveAllToMongo(List<PublicService> publicServices) {
List<PublicData> documents = publicServices.stream()
.map(this::convertToDocument)
.collect(Collectors.toList());

return mongoRepository.saveAll(documents);
}

/**
* κ³΅κ³΅μ„œλΉ„μŠ€ μ—”ν‹°ν‹°λ₯Ό MongoDB λ¬Έμ„œλ‘œ λ³€ν™˜
*/
private PublicData convertToDocument(PublicService publicService) {
// 특수 κ·Έλ£Ή 정보 μΆ”μΆœ
List<String> specialGroups = publicService.getSpecialGroups().stream()
.map(sg -> sg.getSpecialGroupEnum().getType())
.collect(Collectors.toList());

// κ°€μ‘± μœ ν˜• 정보 μΆ”μΆœ
List<String> familyTypes = publicService.getFamilyTypes().stream()
.map(ft -> ft.getFamilyTypeEnum().getType())
.collect(Collectors.toList());

// 직업 정보 μΆ”μΆœ
List<String> occupations = publicService.getOccupations().stream()
.map(occ -> occ.getOccupationEnum().getType())
.collect(Collectors.toList());

// 사업체 μœ ν˜• 정보 μΆ”μΆœ
List<String> businessTypes = publicService.getBusinessTypes().stream()
.map(bt -> bt.getBusinessTypeEnum().getType())
.collect(Collectors.toList());

// MongoDB λ¬Έμ„œ 생성 및 λ°˜ν™˜
return PublicData.builder()
.publicServiceId(publicService.getId())
.serviceName(publicService.getServiceName())
.summaryPurpose(publicService.getSummaryPurpose())
.serviceCategory(publicService.getServiceCategory().getType())
.specialGroup(specialGroups)
.familyType(familyTypes)
.occupations(occupations)
.businessTypes(businessTypes)
.targetGenderMale(publicService.getTargetGenderMale())
.targetGenderFemale(publicService.getTargetGenderFemale())
.targetAgeStart(publicService.getTargetAgeStart())
.targetAgeEnd(publicService.getTargetAgeEnd())
.incomeLevel(publicService.getIncomeLevel())
.build();
}

/**
* μ„œλΉ„μŠ€ ID둜 λ¬Έμ„œ 쑰회
*/
public Optional<PublicData> findByPublicServiceId(String publicServiceId) {
return mongoRepository.findByPublicServiceId(publicServiceId);
}

/**
* κΈ°μ‘΄ λ¬Έμ„œ μ—…λ°μ΄νŠΈ λ˜λŠ” μƒˆ λ¬Έμ„œ 생성
*/
public PublicData updateOrCreateDocument(PublicService publicService) {
Optional<PublicData> existingDoc = mongoRepository.findByPublicServiceId(publicService.getId());

if (existingDoc.isPresent()) {
// κΈ°μ‘΄ λ¬Έμ„œμ˜ ID μœ μ§€ν•˜λ©΄μ„œ 데이터 μ—…λ°μ΄νŠΈ
PublicData newData = convertToDocument(publicService);
newData.setId(existingDoc.get().getId());
return mongoRepository.save(newData);
} else {
// μƒˆ λ¬Έμ„œ 생성
return saveToMongo(publicService);
}
}

/**
* μ—¬λŸ¬ μ„œλΉ„μŠ€ λ¬Έμ„œ μ—…λ°μ΄νŠΈ λ˜λŠ” 생성
*/
public List<PublicData> updateOrCreateBulkDocuments(List<PublicService> services) {
// κΈ°μ‘΄ ID λͺ©λ‘ κ°€μ Έμ˜€κΈ°
List<String> serviceIds = services.stream()
.map(PublicService::getId)
.collect(Collectors.toList());

// ID에 ν•΄λ‹Ήν•˜λŠ” λ¬Έμ„œ λ§΅ 생성
Map<String, PublicData> existingDocsMap = mongoRepository.findAllById(serviceIds).stream()
.collect(Collectors.toMap(PublicData::getPublicServiceId, doc -> doc, (a, b) -> a));

// 각 μ„œλΉ„μŠ€ 처리
List<PublicData> docsToSave = services.stream()
.map(service -> {
PublicData doc = convertToDocument(service);
if (existingDocsMap.containsKey(service.getId())) {
// κΈ°μ‘΄ λ¬Έμ„œ ID μœ μ§€
doc.setId(existingDocsMap.get(service.getId()).getId());
}
return doc;
})
.collect(Collectors.toList());

// 일괄 μ €μž₯
return mongoRepository.saveAll(docsToSave);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.hyetaekon.hyetaekon.common.publicdata.dto.PublicServiceDataDto;
import com.hyetaekon.hyetaekon.common.publicdata.dto.PublicServiceDetailDataDto;
import com.hyetaekon.hyetaekon.common.publicdata.mapper.PublicServiceDataMapper;
import com.hyetaekon.hyetaekon.common.publicdata.mongodb.service.PublicDataMongoService;
import com.hyetaekon.hyetaekon.common.publicdata.util.PublicDataPath;
import com.hyetaekon.hyetaekon.common.publicdata.util.PublicServiceDataValidate;
import com.hyetaekon.hyetaekon.publicservice.entity.*;
Expand All @@ -30,7 +31,7 @@
@Service
@RequiredArgsConstructor
public class PublicServiceDataServiceImpl implements PublicServiceDataService {

private final PublicDataMongoService publicDataMongoService;
private final PublicServiceRepository publicServiceRepository;
private final PublicServiceDataMapper publicServiceDataMapper;
private final PublicServiceDataProviderService publicServiceDataProviderService;
Expand Down Expand Up @@ -238,14 +239,18 @@ public List<PublicServiceDataDto.Data> upsertServiceData(List<PublicServiceDataD

// 배치 처리 μ΅œμ ν™”: 1000개 λ‹¨μœ„λ‘œ μ €μž₯
if (entitiesToSave.size() >= 1000) {
publicServiceRepository.saveAll(entitiesToSave);
List<PublicService> savedEntities = publicServiceRepository.saveAll(entitiesToSave);

publicDataMongoService.saveAllToMongo(savedEntities);
entitiesToSave.clear();
}
}

// λ‚˜λ¨Έμ§€ 데이터 μ €μž₯
if (!entitiesToSave.isEmpty()) {
publicServiceRepository.saveAll(entitiesToSave);
List<PublicService> savedEntities = publicServiceRepository.saveAll(entitiesToSave);

publicDataMongoService.saveAllToMongo(savedEntities);
}

log.info("κ³΅κ³΅μ„œλΉ„μŠ€ λͺ©λ‘ 데이터 {}건 μ €μž₯ μ™„λ£Œ", validatedData.size());
Expand Down Expand Up @@ -290,14 +295,18 @@ public List<PublicServiceDetailDataDto.Data> upsertServiceDetailData(List<Public

// 배치 처리 μ΅œμ ν™”: 1000개 λ‹¨μœ„λ‘œ μ €μž₯
if (entitiesToSave.size() >= 1000) {
publicServiceRepository.saveAll(entitiesToSave);
List<PublicService> savedEntities = publicServiceRepository.saveAll(entitiesToSave);

publicDataMongoService.saveAllToMongo(savedEntities);
entitiesToSave.clear();
}
}

// λ‚˜λ¨Έμ§€ 데이터 μ €μž₯
if (!entitiesToSave.isEmpty()) {
publicServiceRepository.saveAll(entitiesToSave);
List<PublicService> savedEntities = publicServiceRepository.saveAll(entitiesToSave);

publicDataMongoService.saveAllToMongo(savedEntities);
}

log.info("κ³΅κ³΅μ„œλΉ„μŠ€ 상세정보 데이터 {}건 μ €μž₯ μ™„λ£Œ", validatedData.size());
Expand Down Expand Up @@ -346,14 +355,18 @@ public List<PublicServiceConditionsDataDto.Data> upsertSupportConditionsData(Lis

// 배치 처리 μ΅œμ ν™”: 1000개 λ‹¨μœ„λ‘œ μ €μž₯
if (entitiesToSave.size() >= 1000) {
publicServiceRepository.saveAll(entitiesToSave);
List<PublicService> savedEntities = publicServiceRepository.saveAll(entitiesToSave);

publicDataMongoService.saveAllToMongo(savedEntities);
entitiesToSave.clear();
}
}

// λ‚˜λ¨Έμ§€ 데이터 μ €μž₯
if (!entitiesToSave.isEmpty()) {
publicServiceRepository.saveAll(entitiesToSave);
List<PublicService> savedEntities = publicServiceRepository.saveAll(entitiesToSave);

publicDataMongoService.saveAllToMongo(savedEntities);
}

log.info("κ³΅κ³΅μ„œλΉ„μŠ€ 지원쑰건 데이터 {}건 μ €μž₯ μ™„λ£Œ", validatedData.size());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,19 @@ public boolean validatePublicServiceData(PublicServiceDataDto.Data data) {
return true;
}

// PublicServiceDataValidate.java μˆ˜μ •
public boolean validatePublicServiceDetailData(PublicServiceDetailDataDto.Data data) {
if (data.getServicePurpose() == null || data.getServicePurpose().isEmpty() ||
data.getSupportTarget() == null || data.getSupportTarget().isEmpty() ||
data.getSupportDetail() == null || data.getSupportDetail().isEmpty() ||
data.getSupportType() == null || data.getSupportType().isEmpty() ||
data.getApplicationMethod() == null || data.getApplicationMethod().isEmpty() ||
data.getApplicationDeadline() == null || data.getApplicationDeadline().isEmpty() ||
data.getGoverningAgency() == null || data.getGoverningAgency().isEmpty()) {
// ν•„μˆ˜ ν•„λ“œ 리슀트λ₯Ό λ¨Όμ € 확인
boolean isValid = data.getServicePurpose() != null && !data.getServicePurpose().isEmpty() &&
data.getSupportTarget() != null && !data.getSupportTarget().isEmpty() &&
data.getSupportDetail() != null && !data.getSupportDetail().isEmpty() &&
data.getSupportType() != null && !data.getSupportType().isEmpty() &&
data.getApplicationMethod() != null && !data.getApplicationMethod().isEmpty() &&
data.getApplicationDeadline() != null && !data.getApplicationDeadline().isEmpty() &&
data.getGoverningAgency() != null && !data.getGoverningAgency().isEmpty() &&
data.getContactInfo() != null && !data.getContactInfo().isEmpty();

if (!isValid) {
log.warn("⚠️ 곡곡 μ„œλΉ„μŠ€ μƒμ„Έλ‚΄μš© ID {}에 ν•„μˆ˜ 데이터가 λˆ„λ½λ˜μ—ˆμŠ΅λ‹ˆλ‹€.", data.getServiceId());
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class PostController {
private final PostService postService;

// PostType에 ν•΄λ‹Ήν•˜λŠ” κ²Œμ‹œκΈ€ λͺ©λ‘ 쑰회
@GetMapping
@GetMapping("/type")
public ResponseEntity<Page<PostListResponseDto>> getPosts(
@RequestParam(required = false, defaultValue = "전체") String postType,
@PageableDefault(page = 0, size = 10) Pageable pageable) {
Expand Down
Loading