Skip to content
This repository was archived by the owner on Jan 2, 2026. It is now read-only.

Commit 8b5bbee

Browse files
✨Feature/#4 공공데이터 저장
* 📦Chore: API 응답 파싱을 위한 의존성 라이브러리, 환경변수 추가 * ✨Feat: API 요청용 객체 추가 * 📦 Chore: 주석 추가 * ✨ Feat: 의약품 상세정보 API 호출 개발 * ✨ Feat: 샘플 검색 데이터 추출 개발 * ✨ Feat: 공공데이터 API 파싱 개발 * ♻️ Refactor: 파싱을 위한 Wrappeer 클래스 정리 (인터페이스 추가) * ♻️ Refactor: 불필요한 생성자 삭제 * ♻️ Refactor: aricle parsing 가독성 개선 * 🐛 Fix: SectionWrapper parseElement 오타 수정 * ♻️ Refactor: 로직 수정 및 가독성 개선 * ♻️ Refactor: Paragraph 파싱 수정 및 헥사고날 아키텍처 적용 * ♻️ Refactor: 도메인 메소드 접근자 변경 * 📦 gitignore 수정 및 재 커밋 * ♻️ Refactor: 공공데이터 개요정보 API 파싱 수정 * ✨ Feat: API 전체 데이터 저장 * ✨ Feat: api 전체 데이터 저장 * 🐛 Fix: Xml 파서에 null 체크 로직 추가 * ✨ Feat: 공공데이터 전체 저장 기능 개발 * ♻️ Refactor: 전체 데이터 저장 함수 리팩토링 --------- Co-authored-by: HaechangLee <112938092+HaechangLee@users.noreply.github.com> --------- Co-authored-by: HaechangLee <112938092+HaechangLee@users.noreply.github.com>
1 parent 9b73411 commit 8b5bbee

23 files changed

Lines changed: 949 additions & 1 deletion

build.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ dependencies {
4242
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml'
4343
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
4444

45-
4645
// Elastic search
4746
implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
4847
implementation 'org.elasticsearch.client:elasticsearch-rest-high-level-client:7.17.10'
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.likelion.backendplus4.yakplus.scraper.drug;
2+
3+
import org.springframework.stereotype.Component;
4+
5+
import com.fasterxml.jackson.core.JsonProcessingException;
6+
import com.fasterxml.jackson.databind.JsonNode;
7+
import com.fasterxml.jackson.databind.ObjectMapper;
8+
9+
import lombok.extern.slf4j.Slf4j;
10+
11+
@Slf4j
12+
public class ApiResponseMapper {
13+
14+
public static JsonNode getItemsFromResponse(String response) {
15+
log.info("응답에서 items 값 추출");
16+
try {
17+
return new ObjectMapper().readTree(response)
18+
.path("body")
19+
.path("items");
20+
} catch (JsonProcessingException e) {
21+
log.error("items 추출 실패");
22+
//TODO: CustomException 만들고, ControllerAdvice로 예외처리 필요
23+
throw new RuntimeException(e);
24+
}
25+
}
26+
27+
public static int getTotalCountFromResponse(String response) {
28+
log.info("응답에서 데이터 사이즈 추출");
29+
try {
30+
return new ObjectMapper().readTree(response)
31+
.path("body")
32+
.path("totalCount")
33+
.asInt();
34+
} catch (JsonProcessingException e) {
35+
log.error("totalCount 추출 실패");
36+
//TODO: CustomException 만들고, ControllerAdvice로 예외처리 필요
37+
throw new RuntimeException(e);
38+
}
39+
}
40+
41+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.likelion.backendplus4.yakplus.scraper.drug;
2+
3+
import org.springframework.context.annotation.Bean;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.web.client.RestTemplate;
6+
7+
/**
8+
* Api 요청을 보내기 위한 RestTemplate 빈 생성
9+
*
10+
* @since 2025-04-15
11+
* @author 함예정
12+
*/
13+
@Configuration
14+
public class ApiRestTemplateConfig {
15+
@Bean
16+
public RestTemplate restTemplate() {
17+
return new RestTemplate();
18+
}
19+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.likelion.backendplus4.yakplus.scraper.drug.detail;
2+
3+
import org.springframework.data.jpa.repository.JpaRepository;
4+
import org.springframework.stereotype.Repository;
5+
6+
import com.likelion.backendplus4.yakplus.scraper.drug.detail.adapter.out.persistence.GovDrugDetailEntity;
7+
8+
@Repository
9+
public interface ApiDataDrugJPARepo extends JpaRepository<GovDrugDetailEntity, Long> {
10+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.likelion.backendplus4.yakplus.scraper.drug.detail.adapter.in.batch;
2+
3+
public class BatchConfig {
4+
//TODO: 추후 배치로 분리
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.likelion.backendplus4.yakplus.scraper.drug.detail.adapter.in.batch;
2+
3+
public class JobLauncher {
4+
//TODO: 추후 배치로 분리
5+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.likelion.backendplus4.yakplus.scraper.drug.detail.adapter.in.web;
2+
3+
import org.springframework.http.ResponseEntity;
4+
import org.springframework.stereotype.Controller;
5+
import org.springframework.web.bind.annotation.GetMapping;
6+
7+
import com.likelion.backendplus4.yakplus.scraper.drug.detail.application.port.in.DrugApprovalDetailScraperUseCase;
8+
9+
import lombok.RequiredArgsConstructor;
10+
11+
@Controller
12+
@RequiredArgsConstructor
13+
public class DrugDetailController {
14+
private final DrugApprovalDetailScraperUseCase scraperUseCase;
15+
16+
@GetMapping("/gov/api/parser/detail/start")
17+
public ResponseEntity saveAPIData(){
18+
scraperUseCase.requestUpdateRawData();
19+
return ResponseEntity.ok().build();
20+
}
21+
22+
@GetMapping("/gov/api/parser/detail/startAll")
23+
public ResponseEntity saveAPIDataAll(){
24+
scraperUseCase.requestUpdateAllRawData();
25+
return ResponseEntity.ok().build();
26+
}
27+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package com.likelion.backendplus4.yakplus.scraper.drug.detail.adapter.out.gov;
2+
3+
import org.springframework.beans.factory.annotation.Value;
4+
import org.springframework.stereotype.Component;
5+
import org.springframework.web.util.UriComponentsBuilder;
6+
7+
import java.net.URI;
8+
9+
/***
10+
* API 요청 URI 객체 생성 빌더
11+
*
12+
* application.yml에서 주입되는 속성 값으로,
13+
* API HOST, PATH를 확인해 URI 객체를 만듭니다.
14+
*
15+
* @since 2025-04-15
16+
* @author 함예정
17+
*/
18+
@Component
19+
public class ApiUriCompBuilder {
20+
private final String SERVICE_KEY;
21+
private final String HOST;
22+
private final String API_DETAIL_PATH;
23+
private final String API_IMG_PATH ;
24+
private final String RESPONSE_TYPE;
25+
26+
public ApiUriCompBuilder(@Value("${gov.host}") String host,
27+
@Value("${gov.serviceKey}") String serviceKey,
28+
@Value("${gov.path.detail}") String pathDetail,
29+
@Value("${gov.path.img}") String pathImg,
30+
@Value("${gov.type}") String type) {
31+
this.HOST = host;
32+
this.SERVICE_KEY = serviceKey;
33+
this.API_DETAIL_PATH = pathDetail;
34+
this.API_IMG_PATH = pathImg;
35+
this.RESPONSE_TYPE = type;
36+
}
37+
38+
/***
39+
* 입력 받은 path를 반영해 URI 객체를 생성, 반환
40+
*
41+
* @param path API 요청 경로
42+
* @return URI
43+
*
44+
* @since 2025-04-15
45+
* @author 함예정
46+
*/
47+
private URI getUri(String path, int pageNo) {
48+
return UriComponentsBuilder.newInstance()
49+
.scheme("https")
50+
.host(HOST)
51+
.port(443)
52+
.path(path)
53+
.queryParam("serviceKey", SERVICE_KEY)
54+
.queryParam("type", RESPONSE_TYPE)
55+
.queryParam("pageNo", pageNo)
56+
.queryParam("numOfRows", 100)
57+
.build(true)
58+
.toUri();
59+
}
60+
61+
/***
62+
* 식품의약품안전처 의약품 제품 허가 상세 정보 URI 반환
63+
* @return URI 제품 허가 상세 정보
64+
*
65+
* @since 2025-04-15
66+
* @author 함예정
67+
*/
68+
public URI getUriForDetailApi(int pageNo) {
69+
return getUri(API_DETAIL_PATH, pageNo);
70+
}
71+
72+
/***
73+
* 식품의약품안전처 의약품 제품 허가 목록 URI 반환
74+
* @return URI 제품 허가 목록
75+
*
76+
* @since 2025-04-15
77+
* @author 함예정
78+
*/
79+
public URI getUriForImgApi(int pageNo) {
80+
return getUri(API_IMG_PATH, pageNo);
81+
}
82+
83+
// TODO 추후 삭제
84+
public URI getUriForImgApiBySeq(String seq) {
85+
// 임시 URI
86+
return UriComponentsBuilder.newInstance()
87+
.scheme("https")
88+
.host(HOST)
89+
.port(443)
90+
.path(API_IMG_PATH)
91+
.queryParam("serviceKey", SERVICE_KEY)
92+
.queryParam("type", RESPONSE_TYPE)
93+
.queryParam("numOfRows", 1)
94+
.queryParam("prdlst_Stdr_code", seq)
95+
.build(true)
96+
.toUri();
97+
98+
}
99+
100+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.likelion.backendplus4.yakplus.scraper.drug.detail.adapter.out.parser;
2+
3+
import com.fasterxml.jackson.core.JsonProcessingException;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import com.fasterxml.jackson.databind.node.ArrayNode;
6+
import com.fasterxml.jackson.databind.node.ObjectNode;
7+
8+
public class MaterialParser {
9+
public static String parseMaterial(String raw) throws Exception {
10+
ObjectMapper result = new ObjectMapper();
11+
ArrayNode resultArray = result.createArrayNode();
12+
String[] blocks = splitBlock(raw);
13+
parsingblocksAndPutArrayItem(blocks, resultArray);
14+
return convertString(result, resultArray);
15+
}
16+
17+
private static void parsingblocksAndPutArrayItem(String[] blocks, ArrayNode resultArray) {
18+
for (String block : blocks) {
19+
block = block.trim();
20+
if (block.isEmpty()) {
21+
continue;
22+
}
23+
String[] pairs = splitByPipe(block);
24+
ObjectNode item = makeItem(pairs);
25+
resultArray.add(item);
26+
}
27+
}
28+
29+
private static String convertString(ObjectMapper objectMapper, ArrayNode arrayNode) {
30+
try {
31+
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(arrayNode);
32+
} catch (JsonProcessingException e) {
33+
//TODO String 변환실패
34+
throw new RuntimeException(e);
35+
}
36+
}
37+
38+
private static ObjectNode makeItem(String[] pairs) {
39+
ObjectNode item = new ObjectMapper().createObjectNode();
40+
for (String pair : pairs) {
41+
String[] kv = pair.split(":", 2);
42+
String key = kv[0].trim();
43+
String value = "";
44+
if(kv.length == 2){
45+
value = kv[1].trim();
46+
}
47+
item.put(key, value);
48+
}
49+
return item;
50+
}
51+
52+
private static String[] splitByPipe(String block) {
53+
return block.split("\\|");
54+
}
55+
56+
private static String[] splitBlock(String raw) {
57+
return raw.split(";");
58+
}
59+
}

0 commit comments

Comments
 (0)