Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import jakarta.servlet.http.HttpServletRequest;
import kr.allcll.backend.admin.AdminRequestValidator;
import kr.allcll.backend.admin.seat.dto.SeatStatusResponse;
import kr.allcll.backend.admin.subject.TargetSubjectService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
Expand Down
67 changes: 37 additions & 30 deletions src/main/java/kr/allcll/backend/admin/seat/AdminSeatService.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.concurrent.atomic.AtomicLong;
import kr.allcll.backend.admin.seat.dto.ChangeSubjectsResponse;
import kr.allcll.backend.admin.seat.dto.SeatStatusResponse;
import kr.allcll.backend.admin.subject.TargetSubjectStorage;
import kr.allcll.crawler.client.SeatClient;
import kr.allcll.crawler.client.model.SeatResponse;
import kr.allcll.crawler.client.payload.SeatPayload;
Expand Down Expand Up @@ -89,14 +90,48 @@ private void sendPinSubjectRequest(Credential credential) {
if (crawlerSubject == null) {
return;
}
sendExternalRequestWithOutDetect(crawlerSubject, credential);
sendExternalRequest(crawlerSubject, credential);
}

private void sendGeneralSubjectRequest(Credential credential) {
CrawlerSubject crawlerSubject = targetSubjectStorage.getNextGeneralTarget();
sendExternalRequestWithOutDetect(crawlerSubject, credential);
sendExternalRequest(crawlerSubject, credential);
}

private void sendExternalRequest(CrawlerSubject crawlerSubject, Credential credential) {
try {
log.info("[SeatService] [학교 서버] 요청 시도 과목: {}", crawlerSubject);
SeatPayload requestPayload = SeatPayload.from(crawlerSubject);
SeatResponse response = seatClient.execute(credential, requestPayload);
CrawlerSeat renewedCrawlerSeat = createSeat(response, crawlerSubject);
detectDifferenceAndSave(crawlerSubject, renewedCrawlerSeat);
} catch (CrawlerAllcllException e) {
log.error(
"[여석] 외부 API 호출에 실패했습니다. 과목: " + crawlerSubject.getCuriNo() + "-" + crawlerSubject.getClassName());
}
}

private void detectDifferenceAndSave(CrawlerSubject crawlerSubject, CrawlerSeat renewedCrawlerSeat) {
if (changeDetector.isRemainSeatChanged(crawlerSubject, renewedCrawlerSeat)) {
changeDetector.saveChangeToBuffer(crawlerSubject, renewedCrawlerSeat);

synchronized (getSubjectLock(crawlerSubject.getId())) {
seatPersistenceService.saveSeat(renewedCrawlerSeat);
}
}
}

private CrawlerSeat createSeat(SeatResponse response, CrawlerSubject crawlerSubject) {
return response.toSeat(crawlerSubject, LocalDate.now());
}

private Object getSubjectLock(Long subjectId) {
return ("LOCK_" + subjectId).intern();
}

/**
* deprecated : 변경감지로 정책 변경에 따라 해당 메서드를 사용하지 않습니다.
*/
private void sendExternalRequestWithOutDetect(CrawlerSubject crawlerSubject, Credential credential) {
try {
log.info("[SeatService] [학교 서버] 요청 시도 과목: {}", crawlerSubject);
Expand Down Expand Up @@ -127,32 +162,4 @@ private void sendExternalRequestWithOutDetect(CrawlerSubject crawlerSubject, Cre
"[여석] 외부 API 호출에 실패했습니다. 과목: " + crawlerSubject.getCuriNo() + "-" + crawlerSubject.getClassName());
}
}

/**
* 변경감지로 정책 변경 시 해당 메서드로 변경
*/
private void sendExternalRequest(CrawlerSubject crawlerSubject, Credential credential) {
log.info("[SeatService] [학교 서버] 요청 시도 과목: {}", crawlerSubject);
SeatPayload requestPayload = SeatPayload.from(crawlerSubject);
SeatResponse response = seatClient.execute(credential, requestPayload);
CrawlerSeat renewedCrawlerSeat = createSeat(response, crawlerSubject);
detectDifferenceAndSave(crawlerSubject, renewedCrawlerSeat);
}

private void detectDifferenceAndSave(CrawlerSubject crawlerSubject, CrawlerSeat renewedCrawlerSeat) {
if (changeDetector.isRemainSeatChanged(crawlerSubject, renewedCrawlerSeat)) {
changeDetector.saveChangeToBuffer(crawlerSubject, renewedCrawlerSeat);
synchronized (getSubjectLock(crawlerSubject.getId())) {
seatPersistenceService.saveSeat(renewedCrawlerSeat);
}
}
}

private CrawlerSeat createSeat(SeatResponse response, CrawlerSubject crawlerSubject) {
return response.toSeat(crawlerSubject, LocalDate.now());
}

private Object getSubjectLock(Long subjectId) {
return ("LOCK_" + subjectId).intern();
}
}
4 changes: 4 additions & 0 deletions src/main/java/kr/allcll/backend/admin/seat/AllSeatBuffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
import kr.allcll.crawler.common.properties.SjptProperties;
import org.springframework.stereotype.Component;

/**
* deprecated : 변경감지로 정책 변경에 따라 해당 클래스를 사용하지 않습니다.
*/

@Component
public class AllSeatBuffer {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package kr.allcll.backend.admin.seat;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import kr.allcll.backend.admin.seat.dto.ChangeSubjectsResponse;
Expand All @@ -20,7 +22,9 @@ public class ChangeDetector {
private final SjptProperties sjptProperties;

public boolean isRemainSeatChanged(CrawlerSubject crawlerSubject, CrawlerSeat renewedCrawlerSeat) {
Optional<CrawlerSeat> previousSeat = crawlerSeatRepository.findByCrawlerSubject(crawlerSubject);
LocalDate today = LocalDate.now();

Optional<CrawlerSeat> previousSeat = crawlerSeatRepository.findByCrawlerSubjectAndCreatedDate(crawlerSubject, today);
if (previousSeat.isEmpty()) {
return true;
}
Expand All @@ -41,7 +45,7 @@ public void saveChangeToBuffer(CrawlerSubject crawlerSubject, CrawlerSeat renewe
} else {
changedSubjectBuffer.add(
ChangeSubjectsResponse.of(crawlerSubject.getId(), ChangeStatus.UPDATE, SeatUtils.getRemainSeat(
renewedCrawlerSeat)));
renewedCrawlerSeat), LocalDateTime.now()));
}
}

Expand Down
11 changes: 6 additions & 5 deletions src/main/java/kr/allcll/backend/admin/seat/LiveBoards.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package kr.allcll.backend.admin.seat;

import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -27,25 +28,25 @@ public LiveBoards() {
public List<ChangeSubjectsResponse> checkStatus(CrawlerSubject crawlerSubject, Integer remainSeat) {
if (canOnlyIn(crawlerSubject, remainSeat)) {
liveBoardSubjects.put(crawlerSubject, remainSeat);
return List.of(ChangeSubjectsResponse.of(crawlerSubject.getId(), ChangeStatus.IN, remainSeat));
return List.of(ChangeSubjectsResponse.of(crawlerSubject.getId(), ChangeStatus.IN, remainSeat, LocalDateTime.now()));
}
CrawlerSubject maxCrawlerSubject = findMaxRemainSeatSubject();
Integer maxSubjectRemainSeat = liveBoardSubjects.get(maxCrawlerSubject);
if (canInAndOut(crawlerSubject, maxCrawlerSubject, remainSeat)) {
liveBoardSubjects.put(crawlerSubject, remainSeat);
liveBoardSubjects.remove(maxCrawlerSubject);
return List.of(
ChangeSubjectsResponse.of(crawlerSubject.getId(), ChangeStatus.IN, remainSeat),
ChangeSubjectsResponse.of(maxCrawlerSubject.getId(), ChangeStatus.OUT, maxSubjectRemainSeat)
ChangeSubjectsResponse.of(crawlerSubject.getId(), ChangeStatus.IN, remainSeat, LocalDateTime.now()),
ChangeSubjectsResponse.of(maxCrawlerSubject.getId(), ChangeStatus.OUT, maxSubjectRemainSeat, LocalDateTime.now())
);
}
if (canOut(crawlerSubject, remainSeat)) {
liveBoardSubjects.remove(crawlerSubject);
return List.of(ChangeSubjectsResponse.of(crawlerSubject.getId(), ChangeStatus.OUT, remainSeat));
return List.of(ChangeSubjectsResponse.of(crawlerSubject.getId(), ChangeStatus.OUT, remainSeat, LocalDateTime.now()));
}
if (canUpdate(crawlerSubject, remainSeat)) {
liveBoardSubjects.put(crawlerSubject, remainSeat);
return List.of(ChangeSubjectsResponse.of(crawlerSubject.getId(), ChangeStatus.UPDATE, remainSeat));
return List.of(ChangeSubjectsResponse.of(crawlerSubject.getId(), ChangeStatus.UPDATE, remainSeat, LocalDateTime.now()));
}
return Collections.emptyList();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package kr.allcll.backend.admin.seat;

import java.time.LocalDate;
import java.util.Optional;
import kr.allcll.crawler.seat.CrawlerSeat;
import kr.allcll.crawler.seat.CrawlerSeatRepository;
import lombok.RequiredArgsConstructor;
Expand All @@ -15,6 +17,17 @@ public class SeatPersistenceService {

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveSeat(CrawlerSeat crawlerSeat) {
crawlerSeatRepository.save(crawlerSeat);
LocalDate today = LocalDate.now();

Optional<CrawlerSeat> existingSeatOpt = crawlerSeatRepository.findByCrawlerSubjectAndCreatedDate(
crawlerSeat.getCrawlerSubject(),
today
);
if (existingSeatOpt.isPresent()) {
CrawlerSeat existingCrawlerSeat = existingSeatOpt.get();
existingCrawlerSeat.merge(crawlerSeat);
} else {
crawlerSeatRepository.save(crawlerSeat);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package kr.allcll.backend.admin.seat;
package kr.allcll.backend.admin.subject;

import java.util.List;
import java.util.Map;
Expand All @@ -8,7 +8,6 @@
import kr.allcll.crawler.common.exception.CrawlerAllcllException;
import kr.allcll.crawler.subject.CrawlerSubject;
import kr.allcll.crawler.subject.CrawlerSubjectRepository;
import kr.allcll.backend.admin.subject.SubjectFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package kr.allcll.backend.admin.seat;
package kr.allcll.backend.admin.subject;

import java.util.LinkedList;
import java.util.List;
Expand Down
14 changes: 7 additions & 7 deletions src/main/java/kr/allcll/backend/client/ExternalClient.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package kr.allcll.backend.client;

import java.util.List;
import kr.allcll.backend.admin.seat.AllSeatBuffer;
import kr.allcll.backend.admin.seat.TargetSubjectService;
import kr.allcll.backend.admin.seat.ChangedSubjectBuffer;
import kr.allcll.backend.admin.subject.TargetSubjectService;
import kr.allcll.backend.admin.seat.dto.ChangeSubjectsResponse;
import kr.allcll.backend.admin.seat.dto.PinSubjectUpdateRequest;
import kr.allcll.backend.domain.seat.SeatStorage;
Expand All @@ -25,8 +25,8 @@ public class ExternalClient {

// 외부 의존성
private final TargetSubjectService targetSubjectService;
// private final ChangedSubjectBuffer changedSubjectBuffer;
private final AllSeatBuffer allSeatBuffer;
private final ChangedSubjectBuffer changedSubjectBuffer;
// private final AllSeatBuffer allSeatBuffer;

private final SeatStorage seatStorage;
private final SubjectRepository subjectRepository;
Expand All @@ -36,16 +36,16 @@ public void sendPinSubjects(PinSubjectUpdateRequest request) {
}

public void getAllTargetSubjects() {
List<ChangeSubjectsResponse> allChangedSubject = allSeatBuffer.getAllAndFlush();
List<ChangeSubjectsResponse> allChangedSubject = changedSubjectBuffer.getAllAndFlush();
for (ChangeSubjectsResponse eachChange : allChangedSubject) {
Long subjectId = eachChange.subjectId();
Subject subject = subjectRepository.findById(subjectId, Semester.now())
.orElseThrow(() -> new AllcllException(AllcllErrorCode.SUBJECT_NOT_FOUND, subjectId));
seatStorage.add(
new SeatDto(subject,
eachChange.remainSeat(),
eachChange.createdAt()
// eachChange.changeStatus()
eachChange.createdAt(),
eachChange.changeStatus()
)
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package kr.allcll.backend.domain.seat;

import java.util.List;
import kr.allcll.backend.admin.seat.TargetSubjectService;
import kr.allcll.backend.admin.subject.TargetSubjectService;
import kr.allcll.backend.admin.seat.dto.PinSubjectUpdateRequest;
import kr.allcll.backend.domain.seat.dto.DeprecatedSubjectSummaryResponse;
import lombok.RequiredArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package kr.allcll.backend.domain.seat;

import java.util.List;
import kr.allcll.backend.admin.seat.TargetSubjectStorage;
import kr.allcll.backend.admin.subject.TargetSubjectStorage;
import kr.allcll.backend.domain.seat.dto.DeprecatedSubjectSummaryResponse;
import kr.allcll.crawler.subject.CrawlerSubject;
import lombok.RequiredArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public boolean hasActiveSchedule() {

private Runnable getGeneralSeatTask() {
return () -> {
List<SeatDto> generalSeats = seatStorage.getGeneralSeats(QUERY_LIMIT);
List<SeatDto> generalSeats = seatStorage.getGeneralSeats();
sseService.propagate(EVENT_NAME, SeatsResponse.from(generalSeats));
};
}
Expand Down
10 changes: 7 additions & 3 deletions src/main/java/kr/allcll/backend/domain/seat/SeatStorage.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package kr.allcll.backend.domain.seat;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
Expand All @@ -23,13 +25,15 @@ public SeatStorage() {
this.seats = new ConcurrentHashMap<>();
}

public List<SeatDto> getGeneralSeats(int limit) {
public List<SeatDto> getGeneralSeats() {
LocalDateTime now = LocalDateTime.now();

Collection<SeatDto> seatsValue = seats.values();
return seatsValue.stream()
.filter(seat -> seat.getSubject().isNonMajor())
.filter(seat -> seat.getSeatCount() > 0)
.filter(seat ->
Duration.between(seat.getQueryTime(), now).getSeconds() <= LIMIT_QUERY_TIME)
.sorted(Comparator.comparingInt(SeatDto::getSeatCount))
.limit(limit)
.toList();
}

Expand Down
3 changes: 2 additions & 1 deletion src/main/java/kr/allcll/backend/domain/seat/dto/SeatDto.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package kr.allcll.backend.domain.seat.dto;

import java.time.LocalDateTime;
import kr.allcll.backend.admin.seat.ChangeStatus;
import kr.allcll.backend.domain.subject.Subject;
import lombok.AllArgsConstructor;
import lombok.Getter;
Expand All @@ -12,5 +13,5 @@ public class SeatDto {
private Subject subject;
private int seatCount;
private LocalDateTime queryTime;
// private ChangeStatus changeStatus;
private ChangeStatus changeStatus;
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
package kr.allcll.backend.domain.seat.dto;

import java.time.LocalDateTime;
import kr.allcll.backend.admin.seat.ChangeStatus;

public record SeatResponse(
Long subjectId,
Integer seatCount,
LocalDateTime queryTime
LocalDateTime queryTime,
ChangeStatus changeStatus
) {

public static SeatResponse from(SeatDto seatDto) {
return new SeatResponse(
seatDto.getSubject().getId(),
seatDto.getSeatCount(),
seatDto.getQueryTime()
seatDto.getQueryTime(),
seatDto.getChangeStatus()
);
}
}
Loading
Loading