Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/15: 댓글수집 이벤트 관련 API 구현-1 #25

Merged
merged 10 commits into from
Aug 6, 2024
Merged
7 changes: 7 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,18 @@ dependencies {

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

//selenium
implementation 'org.seleniumhq.selenium:selenium-java:4.1.4'
implementation 'io.github.bonigarcia:webdrivermanager:5.0.3'
implementation 'org.jsoup:jsoup:1.13.1'
testImplementation 'org.seleniumhq.selenium:selenium-java:4.22.0'

compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'junit:junit:4.13.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
package com.cmc.suppin.answer.domain;

import com.cmc.suppin.event.survey.domain.Survey;
import com.cmc.suppin.global.domain.BaseDateTimeEntity;
import com.cmc.suppin.survey.domain.Survey;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.DynamicInsert;

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

@Entity
@Getter
@Builder
@DynamicInsert
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class AnonymousParticipant extends BaseDateTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand Down
9 changes: 8 additions & 1 deletion src/main/java/com/cmc/suppin/answer/domain/Answer.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
package com.cmc.suppin.answer.domain;

import com.cmc.suppin.event.survey.domain.Question;
import com.cmc.suppin.global.domain.BaseDateTimeEntity;
import com.cmc.suppin.survey.domain.Question;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.DynamicInsert;

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

@Entity
@Getter
@Builder
@DynamicInsert
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Answer extends BaseDateTimeEntity {

@Id
Expand Down
9 changes: 8 additions & 1 deletion src/main/java/com/cmc/suppin/answer/domain/AnswerOption.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
package com.cmc.suppin.answer.domain;

import com.cmc.suppin.survey.domain.QuestionOption;
import com.cmc.suppin.event.survey.domain.QuestionOption;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.DynamicInsert;


@Entity
@Getter
@Builder
@DynamicInsert
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class AnswerOption {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand Down
Empty file.
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.cmc.suppin.event.crawl.controller;

import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
@RequiredArgsConstructor
@Validated
@Tag(name = "Event-Comments", description = "Crawling Comments 관련 API")
@RequestMapping("/api/v1/comments")
public class CommentApi {


}
34 changes: 34 additions & 0 deletions src/main/java/com/cmc/suppin/event/crawl/controller/CrawlApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.cmc.suppin.event.crawl.controller;

import com.cmc.suppin.event.crawl.controller.dto.CrawlResponseDTO;
import com.cmc.suppin.event.crawl.service.CrawlService;
import com.cmc.suppin.global.response.ApiResponse;
import com.cmc.suppin.global.response.ResponseCode;
import com.cmc.suppin.global.security.reslover.Account;
import com.cmc.suppin.global.security.reslover.CurrentAccount;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
@RequiredArgsConstructor
@Validated
@Tag(name = "Crawling", description = "Crawling 관련 API")
@RequestMapping("/api/v1/event")
public class CrawlApi {

private final CrawlService crawlService;

@GetMapping("/crawling/youtube/comments")
public ResponseEntity<ApiResponse<CrawlResponseDTO.CrawlResultDTO>> crawlYoutubeComments(@RequestParam String url, @CurrentAccount Account account) {
crawlService.crawlYoutubeComments(url, account.userId());
return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.cmc.suppin.event.crawl.controller.dto;

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

public class CrawlResponseDTO {

@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class CrawlResultDTO {
private String author;
private String commentText;
private String date;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.cmc.suppin.event.crawl.converter;

public class CommentConverter {
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
package com.cmc.suppin.comment.domain;
package com.cmc.suppin.event.crawl.domain;

import com.cmc.suppin.event.domain.Event;
import com.cmc.suppin.event.events.domain.Event;
import com.cmc.suppin.global.domain.BaseDateTimeEntity;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.DynamicInsert;


@Entity
@Getter
@Builder
@DynamicInsert
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Comment extends BaseDateTimeEntity {

@Id
Expand All @@ -19,9 +26,14 @@ public class Comment extends BaseDateTimeEntity {

@Column(columnDefinition = "TEXT", nullable = false)
private String url;
@Column(columnDefinition = "VARCHAR(30)", nullable = false)
private String nickname;

@Column(nullable = false)
private String author;

@Column(columnDefinition = "TEXT", nullable = false)
private String commentText;

@Column(nullable = false)
private String commentDate;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.cmc.suppin.event.crawl.domain.repository;

import com.cmc.suppin.event.crawl.domain.Comment;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface CommentRepository extends JpaRepository<Comment, Long> {
List<Comment> findByEventId(Long eventId);

List<Comment> findByVideoUrl(String videoUrl);
}
91 changes: 91 additions & 0 deletions src/main/java/com/cmc/suppin/event/crawl/service/CrawlService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.cmc.suppin.event.crawl.service;

import com.cmc.suppin.event.crawl.controller.dto.CrawlResponseDTO;
import com.cmc.suppin.event.crawl.domain.Comment;
import com.cmc.suppin.event.crawl.domain.repository.CommentRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Service
@Slf4j
@RequiredArgsConstructor
@Transactional
public class CrawlService {

private final CommentRepository commentRepository;

public List<CrawlResponseDTO.CrawlResultDTO> crawlYoutubeComments(String url, String userId) {
System.setProperty("webdriver.chrome.driver", "src/main/resources/drivers/chromedriver");

ChromeOptions options = new ChromeOptions();
options.addArguments("--disable-gpu");
options.addArguments("--no-sandbox");
options.addArguments("--disable-dev-shm-usage");
options.addArguments("--remote-allow-origins=*");

WebDriver driver = new ChromeDriver(options);
driver.get(url);

List<CrawlResponseDTO.CrawlResultDTO> commentList = new ArrayList<>();
Set<String> uniqueComments = new HashSet<>();

try {
Thread.sleep(5000);

long endTime = System.currentTimeMillis() + 120000;
JavascriptExecutor jsExecutor = (JavascriptExecutor) driver;

while (System.currentTimeMillis() < endTime) {
jsExecutor.executeScript("window.scrollTo(0, document.documentElement.scrollHeight);");
Thread.sleep(1000);

String pageSource = driver.getPageSource();
Document doc = Jsoup.parse(pageSource);
Elements comments = doc.select("ytd-comment-thread-renderer");

for (Element commentElement : comments) {
String author = commentElement.select("#author-text span").text();
String text = commentElement.select("#content yt-attributed-string#content-text").text();
String time = commentElement.select("#header-author #published-time-text").text().replace("(수정됨)", "");

if (!uniqueComments.contains(text)) {
uniqueComments.add(text);
commentList.add(new CrawlResponseDTO.CrawlResultDTO(author, text, time));

// 엔티티 저장
Comment comment = Comment.builder()
.author(author)
.commentText(text)
.commentDate(time)
.url(url)
.build();
commentRepository.save(comment);
}
}
}

return commentList;

} catch (InterruptedException e) {
e.printStackTrace();
return new ArrayList<>();
} finally {
driver.quit();
}
}
}
37 changes: 37 additions & 0 deletions src/main/java/com/cmc/suppin/event/events/controller/EventApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.cmc.suppin.event.events.controller;

import com.cmc.suppin.event.events.controller.dto.EventRequestDTO;
import com.cmc.suppin.event.events.service.EventService;
import com.cmc.suppin.global.response.ApiResponse;
import com.cmc.suppin.global.response.ResponseCode;
import com.cmc.suppin.global.security.reslover.Account;
import com.cmc.suppin.global.security.reslover.CurrentAccount;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
@RequiredArgsConstructor
@Validated
@Tag(name = "Event", description = "Event 관련 API")
@RequestMapping("/api/v1/event")
public class EventApi {

private final EventService eventService;

@PostMapping("/new")
@Operation(summary = "댓글 이벤트 생성 API", description = "request : eventType, title, url, startDate, endDate")
public ResponseEntity<ApiResponse<Void>> createEvent(@RequestBody @Valid EventRequestDTO.CommentEventCreateDTO request, @CurrentAccount Account account) {
eventService.createEvent(request, account.userId());
return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.cmc.suppin.event.events.controller.dto;

import com.cmc.suppin.global.enums.EventType;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

public class EventRequestDTO {

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class CommentEventCreateDTO {
private EventType type;
private String title;
private String url;
private String startDate;
private String endDate;
private String announcementDate;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.cmc.suppin.event.events.controller.dto;

import com.cmc.suppin.global.enums.EventType;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

public class EventResponseDTO {

@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class CommentEventDetailDTO {

private EventType type;
private String title;
private String url;
private String startDate;
private String endDate;
private String announcementDate;
}
}
Loading
Loading