Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.nova.nova_server.domain.post.client;

import com.fasterxml.jackson.databind.JsonNode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;

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

@Slf4j
@Component
@RequiredArgsConstructor
public class JumpitClient {

private final WebClient webClient;

@Value("${external.jumpit.base-url}")
private String baseUrl;

@Value("${external.jumpit.user-agent}")
private String userAgent;

public List<JsonNode> fetchBackendPositions(int page) {
return fetchPositionsByCategory(page, 2, 10); // 2: Server/Backend, Size: 10
}

public List<JsonNode> fetchFrontendPositions(int page) {
return fetchPositionsByCategory(page, 1, 10); // 1: Frontend, Size: 10
}

private List<JsonNode> fetchPositionsByCategory(int page, int jobCategory, int size) {
try {
String url = String.format("%s/positions?sort=reg_dt&page=%d&jobCategory=%d&size=%d", baseUrl, page,
jobCategory, size);

JsonNode response = webClient.get()
.uri(url)
.header("User-Agent", userAgent)
.header("Accept", "application/json")
.retrieve()
.bodyToMono(JsonNode.class)
.block();

if (response != null && response.has("result") && response.get("result").has("positions")) {
List<JsonNode> positions = new ArrayList<>();
response.get("result").get("positions").forEach(positions::add);
return positions;
}
} catch (Exception e) {
log.error("Jumpit API fetch 오류 (category: {}): {}", jobCategory, e.getMessage());
}
return List.of();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ public Map<String, List<Article>> fetchFromAllSources() {
log.warn("Failed to fetch articles from {}", service.getProviderName(), e);
return List.of();
}
}
));
}));
}

@Operation(summary = "사용 가능 제공자 목록", description = "현재 시스템에 등록된 기사 수집 소스(Provider) 목록을 반환합니다.")
Expand All @@ -71,49 +70,77 @@ public List<String> getAvailableProviders() {

@Operation(summary = "[뉴스] NewsAPI 호출", description = "NewsAPI에서 최신 기사를 가져옵니다.")
@GetMapping("/newsapi")
public List<Article> fetchFromNewsApi() { return getServiceByName("NewsAPI").fetchArticles(); }
public List<Article> fetchFromNewsApi() {
return getServiceByName("NewsAPI").fetchArticles();
}

@Operation(summary = "[뉴스] Naver News Search 호출", description = "네이버 뉴스 검색 결과를 가져옵니다.")
@GetMapping("/navernewssearch")
public List<Article> fetchFromNaverNews() { return getServiceByName("NaverNewsSearch").fetchArticles(); }
public List<Article> fetchFromNaverNews() {
return getServiceByName("NaverNewsSearch").fetchArticles();
}

@Operation(summary = "[뉴스] DeepSearch 호출", description = "DeepSearch API에서 데이터를 가져옵니다.")
@GetMapping("/deepsearch")
public List<Article> fetchFromDeepSearch() { return getServiceByName("DeepSearch").fetchArticles(); }
public List<Article> fetchFromDeepSearch() {
return getServiceByName("DeepSearch").fetchArticles();
}

@Operation(summary = "[뉴스] NewsData 호출", description = "NewsData.io에서 데이터를 가져옵니다.")
@GetMapping("/newsdata")
public List<Article> fetchFromNewsData() { return getServiceByName("NewsData").fetchArticles(); }
public List<Article> fetchFromNewsData() {
return getServiceByName("NewsData").fetchArticles();
}

@Operation(summary = "[뉴스] GNews 호출", description = "GNews API에서 데이터를 가져옵니다.")
@GetMapping("/gnews")
public List<Article> fetchFromGNews() { return getServiceByName("GNews").fetchArticles(); }
public List<Article> fetchFromGNews() {
return getServiceByName("GNews").fetchArticles();
}

@Operation(summary = "[뉴스] Naver News API 호출", description = "네이버 뉴스 공식 API를 통해 데이터를 가져옵니다.")
@GetMapping("/navernewsapi")
public List<Article> fetchFromNaverNewsApi() { return getServiceByName("NaverNewsAPI").fetchArticles(); }
public List<Article> fetchFromNaverNewsApi() {
return getServiceByName("NaverNewsAPI").fetchArticles();
}

@Operation(summary = "[뉴스] Hacker News 호출", description = "Hacker News의 최신 인기 게시글을 가져옵니다.")
@GetMapping("/hackernews")
public List<Article> fetchFromHackerNews() { return getServiceByName("HackerNews").fetchArticles(); }
public List<Article> fetchFromHackerNews() {
return getServiceByName("HackerNews").fetchArticles();
}

// --- 커뮤니티 소스 ---

@Operation(summary = "[커뮤니티] Dev.to 호출", description = "Dev.to의 최신 개발 포스트를 가져옵니다.")
@GetMapping("/devto")
public List<Article> fetchFromDevTo() { return getServiceByName("DevTo").fetchArticles(); }
public List<Article> fetchFromDevTo() {
return getServiceByName("DevTo").fetchArticles();
}

@Operation(summary = "[커뮤니티] GitHub 호출", description = "GitHub Trending 포스트를 가져옵니다.")
@GetMapping("/github")
public List<Article> fetchFromGitHub() { return getServiceByName("GitHub").fetchArticles(); }
public List<Article> fetchFromGitHub() {
return getServiceByName("GitHub").fetchArticles();
}

@Operation(summary = "[커뮤니티] StackExchange 호출", description = "StackExchange 질문 목록을 가져옵니다.")
@GetMapping("/stackexchange")
public List<Article> fetchFromStackExchange() { return getServiceByName("StackExchange").fetchArticles(); }
public List<Article> fetchFromStackExchange() {
return getServiceByName("StackExchange").fetchArticles();
}

@Operation(summary = "[커뮤니티] TechBlog 호출", description = "기술 블로그 크롤링 데이터를 가져옵니다.")
@GetMapping("/techblog")
public List<Article> fetchFromTechBlog() { return getServiceByName("TechBlog").fetchArticles(); }
public List<Article> fetchFromTechBlog() {
return getServiceByName("TechBlog").fetchArticles();
}

@Operation(summary = "[취업] Jumpit 호출", description = "점핏에서 최신 백엔드 채용 공고를 가져옵니다.")
@GetMapping("/jumpit")
public List<Article> fetchFromJumpit() {
return getServiceByName("Jumpit").fetchArticles();
}

private ArticleApiService getServiceByName(String providerName) {
return articleApiServices.stream()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.nova.nova_server.domain.post.parser;

import com.fasterxml.jackson.databind.JsonNode;
import com.nova.nova_server.domain.cardNews.entity.CardType;
import com.nova.nova_server.domain.post.model.Article;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

@Component
public class JumpitParser {

public List<Article> parse(List<JsonNode> items) {
List<Article> articles = new ArrayList<>();

for (JsonNode item : items) {
try {
String title = item.has("title") ? item.get("title").asText() : "";
String company = item.has("companyName") ? item.get("companyName").asText() : "";

// 기술 스택 및 위치 정보를 조합하여 내용 생성
StringBuilder contentBuilder = new StringBuilder();
if (item.has("techStacks")) {
String techStacks = StreamSupport.stream(item.get("techStacks").spliterator(), false)
.map(JsonNode::asText)
.collect(Collectors.joining(", "));
contentBuilder.append("기술 스택: ").append(techStacks).append("\n");
}

if (item.has("locations")) {
String locations = StreamSupport.stream(item.get("locations").spliterator(), false)
.map(JsonNode::asText)
.collect(Collectors.joining(", "));
contentBuilder.append("위치: ").append(locations);
}

String id = item.has("id") ? item.get("id").asText() : "";
String url = id.isEmpty() ? null : "https://www.jumpit.co.kr/position/" + id;

articles.add(Article.builder()
.title(title)
.content(contentBuilder.toString().trim())
.author(company)
.source("Jumpit")
.publishedAt(LocalDateTime.now(ZoneOffset.UTC))
.cardType(CardType.JOB)
.url(url)
.build());

} catch (Exception e) {
System.err.println("Jumpit 파싱 오류: " + e.getMessage());
}
}

return articles;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.nova.nova_server.domain.post.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.nova.nova_server.domain.post.client.JumpitClient;
import com.nova.nova_server.domain.post.model.Article;
import com.nova.nova_server.domain.post.parser.JumpitParser;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@RequiredArgsConstructor
public class JumpitServiceImpl implements ArticleApiService {

private final JumpitClient client;
private final JumpitParser parser;

@Override
public List<Article> fetchArticles() {
// 백엔드(2)와 프론트엔드(1) 데이터 수집
List<JsonNode> backendItems = client.fetchBackendPositions(1);
List<JsonNode> frontendItems = client.fetchFrontendPositions(1);

List<Article> articles = new java.util.ArrayList<>();
articles.addAll(parser.parse(backendItems));
articles.addAll(parser.parse(frontendItems));

return articles;
}

@Override
public String getProviderName() {
return "Jumpit";
}
}
Loading