Skip to content

Commit

Permalink
Merge pull request #25 from Central-MakeUs/feature/15
Browse files Browse the repository at this point in the history
Feature/15: 댓글수집 이벤트 관련 API 구현-1
  • Loading branch information
yxhwxn authored Aug 6, 2024
2 parents 91c3ca7 + 4178763 commit 7897cca
Show file tree
Hide file tree
Showing 40 changed files with 471 additions and 19 deletions.
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

0 comments on commit 7897cca

Please sign in to comment.