From 080d4379bc982fbdf2212a6069d4a0ef5c962ea1 Mon Sep 17 00:00:00 2001 From: coffeesigma Date: Tue, 10 Jun 2025 13:55:30 +0900 Subject: [PATCH] =?UTF-8?q?KW-639/feat:=20log,=20stats=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EB=B0=8F=20log=20grpc=20client=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84,=20=EB=8C=80=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 7 +- .../log/consumer/AreaEnterLogConsumer.java | 73 ------ .../consumer/BuildingEnterLogConsumer.java | 77 ------ .../log/consumer/RetainedCountConsumer.java | 80 ------- .../domain/log/controller/LogController.java | 61 ----- .../domain/log/domain/BuildingEnterLog.java | 76 ------ .../domain/log/domain/Direction.java | 6 - .../domain/log/domain/EnterLog.java | 52 ---- .../domain/log/domain/IssuedLog.java | 85 ------- .../domain/log/domain/IssuedLogArea.java | 43 ---- .../request/CreateAreaEnterLogRequest.java | 4 - .../CreateBuildingEnterLogRequest.java | 13 - .../log/dto/response/EnterLogResponse.java | 11 - .../log/dto/response/HourlyEntryResponse.java | 12 - .../dto/response/HourlyIssuanceResponse.java | 12 - .../log/dto/response/IssuedLogResponse.java | 15 -- .../producer/AreaEnterLogStreamProducer.java | 28 --- .../BuildingEnterLogStreamProducer.java | 30 --- .../BuildingEnterLogRepository.java | 91 ------- .../log/repository/EnterLogRepository.java | 13 - .../repository/IssuedLogAreaRepository.java | 13 - .../log/repository/IssuedLogRepository.java | 29 --- .../domain/log/service/LogService.java | 16 -- .../domain/log/service/LogServiceImpl.java | 128 ---------- .../pass/controller/PassController.java | 23 ++ .../dto/request/UpdatePassStatusRequest.java | 2 +- .../dto/response/PendingPassResponse.java | 2 +- .../domain/pass/service/PassService.java | 2 +- .../domain/pass/service/PassServiceImpl.java | 47 +--- .../controller/EntryStatsController.java | 53 ----- .../stats/controller/PassCountController.java | 23 -- .../stats/domain/DailyRetainedSnapshot.java | 45 ---- .../domain/stats/domain/EntryStatsDaily.java | 61 ----- .../stats/domain/EntryStatsMonthly.java | 43 ---- .../domain/stats/domain/EntryStatsWeekly.java | 44 ---- .../dto/request/PassCountInfoRequest.java | 43 ---- .../request/UpdateDailyEntryStatsRequest.java | 6 - .../response/DailyStatsInfoListResponse.java | 14 -- ...LastWeekBuildingStatsInfoListResponse.java | 6 - ...LastWeekCategoryStatsInfoListResponse.java | 6 - .../MonthlyStatsInfoListResponse.java | 7 - .../dto/response/PassCountInfoResponse.java | 6 - .../response/RetainedStatusInfoResponse.java | 6 - .../response/WeeklyStatsInfoListResponse.java | 9 - .../DailyRetainedSnapshotRepository.java | 13 - .../repository/EntryStatsDailyRepository.java | 35 --- .../EntryStatsMonthlyRepository.java | 19 -- .../EntryStatsWeeklyRepository.java | 20 -- .../repository/IssuedLogQueryRepository.java | 48 ---- .../stats/scheduler/EntryStatsScheduler.java | 43 ---- .../stats/service/PassCountService.java | 9 - .../stats/service/PassCountServiceImpl.java | 58 ----- .../stats/service/StatsBatchService.java | 12 - .../stats/service/StatsBatchServiceImpl.java | 133 ----------- .../domain/stats/service/StatsService.java | 22 -- .../stats/service/StatsServiceImpl.java | 224 ------------------ .../global/util/TimestampUtils.java | 33 +++ .../passservice/grpc/client/LogClient.java | 52 ++++ 58 files changed, 120 insertions(+), 2024 deletions(-) delete mode 100644 src/main/java/com/doubleo/passservice/domain/log/consumer/AreaEnterLogConsumer.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/log/consumer/BuildingEnterLogConsumer.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/log/consumer/RetainedCountConsumer.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/log/controller/LogController.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/log/domain/BuildingEnterLog.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/log/domain/Direction.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/log/domain/EnterLog.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/log/domain/IssuedLog.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/log/domain/IssuedLogArea.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/log/dto/request/CreateAreaEnterLogRequest.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/log/dto/request/CreateBuildingEnterLogRequest.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/log/dto/response/EnterLogResponse.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/log/dto/response/HourlyEntryResponse.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/log/dto/response/HourlyIssuanceResponse.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/log/dto/response/IssuedLogResponse.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/log/producer/AreaEnterLogStreamProducer.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/log/producer/BuildingEnterLogStreamProducer.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/log/repository/BuildingEnterLogRepository.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/log/repository/EnterLogRepository.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/log/repository/IssuedLogAreaRepository.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/log/repository/IssuedLogRepository.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/log/service/LogService.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/log/service/LogServiceImpl.java rename src/main/java/com/doubleo/passservice/domain/{log => pass}/dto/request/UpdatePassStatusRequest.java (87%) rename src/main/java/com/doubleo/passservice/domain/{log => pass}/dto/response/PendingPassResponse.java (85%) delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/controller/EntryStatsController.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/controller/PassCountController.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/domain/DailyRetainedSnapshot.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/domain/EntryStatsDaily.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/domain/EntryStatsMonthly.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/domain/EntryStatsWeekly.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/dto/request/PassCountInfoRequest.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/dto/request/UpdateDailyEntryStatsRequest.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/dto/response/DailyStatsInfoListResponse.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/dto/response/LastWeekBuildingStatsInfoListResponse.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/dto/response/LastWeekCategoryStatsInfoListResponse.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/dto/response/MonthlyStatsInfoListResponse.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/dto/response/PassCountInfoResponse.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/dto/response/RetainedStatusInfoResponse.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/dto/response/WeeklyStatsInfoListResponse.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/repository/DailyRetainedSnapshotRepository.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/repository/EntryStatsDailyRepository.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/repository/EntryStatsMonthlyRepository.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/repository/EntryStatsWeeklyRepository.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/repository/IssuedLogQueryRepository.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/scheduler/EntryStatsScheduler.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/service/PassCountService.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/service/PassCountServiceImpl.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/service/StatsBatchService.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/service/StatsBatchServiceImpl.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/service/StatsService.java delete mode 100644 src/main/java/com/doubleo/passservice/domain/stats/service/StatsServiceImpl.java create mode 100644 src/main/java/com/doubleo/passservice/global/util/TimestampUtils.java create mode 100644 src/main/java/com/doubleo/passservice/grpc/client/LogClient.java diff --git a/build.gradle b/build.gradle index d76c339..3492885 100644 --- a/build.gradle +++ b/build.gradle @@ -72,9 +72,10 @@ dependencies { implementation("org.modelmapper:modelmapper:3.1.1") // (1) hospital-interface 모듈 - implementation "com.doubleo.grpc:hospital-interface:0.2.3" - implementation "com.doubleo.grpc:patient-interface:0.2.3" - implementation "com.doubleo.grpc:member-interface:0.2.3" + implementation "com.doubleo.grpc:hospital-interface:0.2.7" + implementation "com.doubleo.grpc:patient-interface:0.2.7" + implementation "com.doubleo.grpc:member-interface:0.2.7" + implementation "com.doubleo.grpc:log-interface:0.2.7" // (2) gRPC 런타임 implementation "io.grpc:grpc-netty:1.63.0" diff --git a/src/main/java/com/doubleo/passservice/domain/log/consumer/AreaEnterLogConsumer.java b/src/main/java/com/doubleo/passservice/domain/log/consumer/AreaEnterLogConsumer.java deleted file mode 100644 index 8414a8a..0000000 --- a/src/main/java/com/doubleo/passservice/domain/log/consumer/AreaEnterLogConsumer.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.doubleo.passservice.domain.log.consumer; - -import com.doubleo.passservice.domain.log.domain.EnterLog; -import com.doubleo.passservice.domain.log.repository.EnterLogRepository; -import java.util.List; -import java.util.Map; -import javax.annotation.PostConstruct; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.redis.connection.stream.Consumer; -import org.springframework.data.redis.connection.stream.MapRecord; -import org.springframework.data.redis.connection.stream.ReadOffset; -import org.springframework.data.redis.connection.stream.StreamOffset; -import org.springframework.data.redis.connection.stream.StreamReadOptions; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -@RequiredArgsConstructor -public class AreaEnterLogConsumer { - - private final RedisTemplate redisTemplate; - private final EnterLogRepository enterLogRepository; - - private static final String STREAM_KEY = "area:enter:stream"; - private static final String GROUP = "area:enter:group"; - private static final String CONSUMER_NAME = "area-enter-consumer-1"; - - @PostConstruct - public void initGroup() { - try { - redisTemplate.opsForStream().createGroup(STREAM_KEY, GROUP); - } catch (Exception e) { - if (!e.getMessage().contains("BUSYGROUP")) { - log.warn("Stream group 생성 실패: {}", e.getMessage()); - } - } - } - - @Scheduled(fixedDelay = 300000) - public void consumeMessages() { - List> messages = - redisTemplate - .opsForStream() - .read( - Consumer.from(GROUP, CONSUMER_NAME), - StreamReadOptions.empty().count(100), - StreamOffset.create(STREAM_KEY, ReadOffset.lastConsumed())); - - for (MapRecord msg : messages) { - Map data = msg.getValue(); - - EnterLog enterLog = - EnterLog.createEnterLog( - (String) data.get("tenantId"), - Long.parseLong((String) data.get("areaId")), - Long.parseLong((String) data.get("memberId")), - (String) data.get("memberName"), - Long.parseLong((String) data.get("passId"))); - - enterLogRepository.save(enterLog); - redisTemplate.opsForStream().acknowledge(GROUP, msg); - - log.info( - "AreaEnterLog 저장 완료: memberId={}, areaId={}, tenantId={}", - enterLog.getMemberId(), - enterLog.getAreaId(), - enterLog.getTenantId()); - } - } -} diff --git a/src/main/java/com/doubleo/passservice/domain/log/consumer/BuildingEnterLogConsumer.java b/src/main/java/com/doubleo/passservice/domain/log/consumer/BuildingEnterLogConsumer.java deleted file mode 100644 index bb436f1..0000000 --- a/src/main/java/com/doubleo/passservice/domain/log/consumer/BuildingEnterLogConsumer.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.doubleo.passservice.domain.log.consumer; - -import com.doubleo.passservice.domain.log.domain.BuildingEnterLog; -import com.doubleo.passservice.domain.log.domain.Direction; -import com.doubleo.passservice.domain.log.repository.BuildingEnterLogRepository; -import com.doubleo.passservice.domain.pass.enums.VisitCategory; -import java.util.List; -import java.util.Map; -import javax.annotation.PostConstruct; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.redis.connection.stream.Consumer; -import org.springframework.data.redis.connection.stream.MapRecord; -import org.springframework.data.redis.connection.stream.ReadOffset; -import org.springframework.data.redis.connection.stream.StreamOffset; -import org.springframework.data.redis.connection.stream.StreamReadOptions; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -@RequiredArgsConstructor -public class BuildingEnterLogConsumer { - - private final RedisTemplate redisTemplate; - private final BuildingEnterLogRepository buildingEnterLogRepository; - - private static final String STREAM_KEY = "building:enter:stream"; - private static final String GROUP = "building:enter:group"; - private static final String CONSUMER_NAME = "building-enter-consumer-1"; - - @PostConstruct - public void initGroup() { - try { - redisTemplate.opsForStream().createGroup(STREAM_KEY, GROUP); - } catch (Exception e) { - if (!e.getMessage().contains("BUSYGROUP")) { - log.warn("Stream group 생성 실패: {}", e.getMessage()); - } - } - } - - @Scheduled(fixedDelay = 300000) - public void consumeMessages() { - List> messages = - redisTemplate - .opsForStream() - .read( - Consumer.from(GROUP, CONSUMER_NAME), - StreamReadOptions.empty().count(100), - StreamOffset.create(STREAM_KEY, ReadOffset.lastConsumed())); - - for (MapRecord msg : messages) { - Map data = msg.getValue(); - - BuildingEnterLog buildingEnterLog = - BuildingEnterLog.createBuildingEnterLog( - (String) data.get("tenantId"), - Long.parseLong((String) data.get("buildingId")), - Long.parseLong((String) data.get("memberId")), - (String) data.get("memberName"), - Long.parseLong((String) data.get("passId")), - Direction.valueOf(((String) data.get("direction")).toUpperCase()), - VisitCategory.valueOf( - ((String) data.get("visitCategory")).toUpperCase())); - buildingEnterLogRepository.save(buildingEnterLog); - redisTemplate.opsForStream().acknowledge(GROUP, msg); - - log.info( - "BuildingEnterLog 저장 완료: memberId={}, buildingId={}, tenantId={}", - buildingEnterLog.getMemberId(), - buildingEnterLog.getBuildingId(), - buildingEnterLog.getTenantId()); - } - } -} diff --git a/src/main/java/com/doubleo/passservice/domain/log/consumer/RetainedCountConsumer.java b/src/main/java/com/doubleo/passservice/domain/log/consumer/RetainedCountConsumer.java deleted file mode 100644 index acc4edd..0000000 --- a/src/main/java/com/doubleo/passservice/domain/log/consumer/RetainedCountConsumer.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.doubleo.passservice.domain.log.consumer; - -import java.time.Duration; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import javax.annotation.PostConstruct; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.redis.connection.stream.*; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -@Component -@RequiredArgsConstructor -@Slf4j -public class RetainedCountConsumer { - - private final RedisTemplate redisTemplate; - - private static final String STREAM_KEY = "building:enter:stream"; - private static final String GROUP = "retained-counter-group"; - private static final String CONSUMER = "retained-counter-consumer"; - - @PostConstruct - public void createGroup() { - try { - redisTemplate.opsForStream().createGroup(STREAM_KEY, ReadOffset.latest(), GROUP); - } catch (Exception e) { - if (!e.getMessage().contains("BUSYGROUP")) { - log.error("Failed to create Redis Stream Group", e); - } - } - } - - @Scheduled(fixedDelay = 300000) - public void consume() { - List> records = - redisTemplate - .opsForStream() - .read( - Consumer.from(GROUP, CONSUMER), - StreamReadOptions.empty().count(10).block(Duration.ofSeconds(1)), - StreamOffset.create(STREAM_KEY, ReadOffset.lastConsumed())); - - if (records == null) return; - - for (MapRecord record : records) { - try { - Map map = - record.getValue().entrySet().stream() - .collect( - Collectors.toMap( - e -> e.getKey().toString(), - e -> e.getValue().toString())); - - String tenantId = map.get("tenantId"); - String direction = map.get("direction"); - String visitCategory = map.get("visitCategory"); - String timestamp = map.get("timestamp"); - - String date = timestamp.substring(0, 10); - String redisKey = - String.format( - "visit:count:%s:%s:%s:%s", - tenantId, date, visitCategory, direction); - - redisTemplate.opsForValue().increment(redisKey); - - redisTemplate.expire(redisKey, Duration.ofDays(1)); - - redisTemplate.opsForStream().acknowledge(STREAM_KEY, GROUP, record.getId()); - - } catch (Exception e) { - log.error("Error processing stream message", e); - } - } - } -} diff --git a/src/main/java/com/doubleo/passservice/domain/log/controller/LogController.java b/src/main/java/com/doubleo/passservice/domain/log/controller/LogController.java deleted file mode 100644 index 16ef68d..0000000 --- a/src/main/java/com/doubleo/passservice/domain/log/controller/LogController.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.doubleo.passservice.domain.log.controller; - -import com.doubleo.passservice.domain.log.dto.request.UpdatePassStatusRequest; -import com.doubleo.passservice.domain.log.dto.response.*; -import com.doubleo.passservice.domain.log.service.LogService; -import com.doubleo.passservice.domain.pass.dto.response.PassCreateResponse; -import com.doubleo.passservice.domain.pass.service.PassService; -import com.doubleo.passservice.global.util.TenantValidator; -import io.swagger.v3.oas.annotations.Operation; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequestMapping("/pass-logs") -@RequiredArgsConstructor -public class LogController { - - private final LogService logService; - private final PassService passService; - private final TenantValidator tenantValidator; - - @Operation(summary = "All issued log get API", description = "모든 출입증 발급 로그 조회 API") - @GetMapping("/issued") - public Page IssuedLogListGet( - @RequestHeader("X-Admin-Id") Long adminId, Pageable pageable) { - return logService.getAllIssuedLog(pageable); - } - - @Operation(summary = "All issued log get API", description = "모든 출입 로그 조회 API") - @GetMapping("/enter") - public Page EnterLogListGet( - @RequestHeader("X-Admin-Id") Long adminId, Pageable pageable) { - return logService.getAllEnterLog(pageable); - } - - @Operation(summary = "All pending pass get API", description = "모든 발급 대기중인 출입증 조회 API") - @GetMapping("/pending") - public Page PendingPassListGet( - @RequestHeader("X-Admin-Id") Long adminId, Pageable pageable) { - String tenantId = tenantValidator.getTenantId(); - return passService.getPendingPassList(tenantId, pageable); - } - - @Operation(summary = "Accept Guardian application", description = "보호자 출입증 신청 승인 API") - @PostMapping - public PassCreateResponse GuardianApplicationCreate( - @RequestHeader("X-Admin-Id") Long adminId, - @RequestBody UpdatePassStatusRequest request) { - return passService.createGuardianAndUpdatePassStatus( - request.passId(), request.issuanceStatus()); - } - - @Operation(summary = "Number of issues per hour", description = "시간대별 출입증 발급 수 API") - @GetMapping("/hourly-issuance") - public List hourlyIssuanceListGet() { - return logService.getHourlyIssuanceList(); - } -} diff --git a/src/main/java/com/doubleo/passservice/domain/log/domain/BuildingEnterLog.java b/src/main/java/com/doubleo/passservice/domain/log/domain/BuildingEnterLog.java deleted file mode 100644 index 9c29de3..0000000 --- a/src/main/java/com/doubleo/passservice/domain/log/domain/BuildingEnterLog.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.doubleo.passservice.domain.log.domain; - -import com.doubleo.passservice.domain.common.model.BaseEntity; -import com.doubleo.passservice.domain.pass.enums.VisitCategory; -import jakarta.persistence.*; -import lombok.*; - -@Entity -@Table(name = "building_enter_log") -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor(access = AccessLevel.PROTECTED) -public class BuildingEnterLog extends BaseEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "building_enter_log_id") - private Long id; - - @Column(name = "building_id", nullable = false) - private Long buildingId; - - @Column(name = "member_id", nullable = false) - private Long memberId; - - @Column(name = "member_name", nullable = false) - private String memberName; - - @Column(name = "pass_id", nullable = false) - private Long passId; - - @Enumerated(EnumType.STRING) - @Column(name = "direction", nullable = false, length = 10) - private Direction direction; - - @Column(name = "visit_category") - @Enumerated(EnumType.STRING) - private VisitCategory visitCategory; - - @Builder(access = AccessLevel.PRIVATE) - private BuildingEnterLog( - String tenantId, - Long buildingId, - Long memberId, - String memberName, - Long passId, - Direction direction, - VisitCategory visitCategory) { - this.tenantId = tenantId; - this.buildingId = buildingId; - this.memberId = memberId; - this.memberName = memberName; - this.passId = passId; - this.direction = direction; - this.visitCategory = visitCategory; - } - - public static BuildingEnterLog createBuildingEnterLog( - String tenantId, - Long buildingId, - Long memberId, - String memberName, - Long passId, - Direction direction, - VisitCategory visitCategory) { - return BuildingEnterLog.builder() - .tenantId(tenantId) - .buildingId(buildingId) - .memberId(memberId) - .memberName(memberName) - .passId(passId) - .direction(direction) - .visitCategory(visitCategory) - .build(); - } -} diff --git a/src/main/java/com/doubleo/passservice/domain/log/domain/Direction.java b/src/main/java/com/doubleo/passservice/domain/log/domain/Direction.java deleted file mode 100644 index 0b8ec31..0000000 --- a/src/main/java/com/doubleo/passservice/domain/log/domain/Direction.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.doubleo.passservice.domain.log.domain; - -public enum Direction { - IN, - OUT -} diff --git a/src/main/java/com/doubleo/passservice/domain/log/domain/EnterLog.java b/src/main/java/com/doubleo/passservice/domain/log/domain/EnterLog.java deleted file mode 100644 index d1ead74..0000000 --- a/src/main/java/com/doubleo/passservice/domain/log/domain/EnterLog.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.doubleo.passservice.domain.log.domain; - -import com.doubleo.passservice.domain.common.model.BaseEntity; -import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Table(name = "enter_log") -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class EnterLog extends BaseEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "enter_log_id") - private Long id; - - @Column(name = "area_id", nullable = false) - private Long areaId; - - @Column(name = "member_id", nullable = false) - private Long memberId; - - @Column(name = "member_name", nullable = false) - private String memberName; - - @Column(name = "pass_id", nullable = false) - private Long passId; - - @Builder(access = AccessLevel.PRIVATE) - private EnterLog(String tenantId, Long areaId, Long memberId, String memberName, Long passId) { - this.tenantId = tenantId; - this.areaId = areaId; - this.memberId = memberId; - this.memberName = memberName; - this.passId = passId; - } - - public static EnterLog createEnterLog( - String tenantId, Long areaId, Long memberId, String memberName, Long passId) { - return EnterLog.builder() - .tenantId(tenantId) - .areaId(areaId) - .memberId(memberId) - .memberName(memberName) - .passId(passId) - .build(); - } -} diff --git a/src/main/java/com/doubleo/passservice/domain/log/domain/IssuedLog.java b/src/main/java/com/doubleo/passservice/domain/log/domain/IssuedLog.java deleted file mode 100644 index 49ec31c..0000000 --- a/src/main/java/com/doubleo/passservice/domain/log/domain/IssuedLog.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.doubleo.passservice.domain.log.domain; - -import com.doubleo.passservice.domain.common.model.BaseEntity; -import com.doubleo.passservice.domain.pass.enums.VisitCategory; -import jakarta.persistence.*; -import java.time.LocalDateTime; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Table(name = "issued_log") -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class IssuedLog extends BaseEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "issued_log_id") - private Long id; - - @Column(name = "member_id", nullable = false) - private Long memberId; - - @Column(name = "member_name", nullable = false) - private String memberName; - - @Column(name = "member_contact", nullable = false) - private String memberContact; - - @Column(name = "pass_id", nullable = false) - private Long passId; - - @Column(name = "start_at", nullable = false) - private LocalDateTime startAt; - - @Column(name = "expired_at", nullable = false) - private LocalDateTime expiredAt; - - @Column(name = "visit_category") - @Enumerated(EnumType.STRING) - private VisitCategory visitCategory; - - @Builder(access = AccessLevel.PRIVATE) - private IssuedLog( - String tenantId, - Long memberId, - String memberName, - String memberContact, - Long passId, - LocalDateTime startAt, - LocalDateTime expiredAt, - VisitCategory visitCategory) { - this.tenantId = tenantId; - this.memberId = memberId; - this.memberName = memberName; - this.memberContact = memberContact; - this.passId = passId; - this.startAt = startAt; - this.expiredAt = expiredAt; - this.visitCategory = visitCategory; - } - - public static IssuedLog createIssuedLog( - String tenantId, - Long memberId, - String memberName, - String memberContact, - Long passId, - LocalDateTime startAt, - LocalDateTime expiredAt, - VisitCategory visitCategory) { - return IssuedLog.builder() - .tenantId(tenantId) - .memberId(memberId) - .memberName(memberName) - .memberContact(memberContact) - .passId(passId) - .startAt(startAt) - .expiredAt(expiredAt) - .visitCategory(visitCategory) - .build(); - } -} diff --git a/src/main/java/com/doubleo/passservice/domain/log/domain/IssuedLogArea.java b/src/main/java/com/doubleo/passservice/domain/log/domain/IssuedLogArea.java deleted file mode 100644 index f6fd36c..0000000 --- a/src/main/java/com/doubleo/passservice/domain/log/domain/IssuedLogArea.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.doubleo.passservice.domain.log.domain; - -import com.doubleo.passservice.domain.common.model.BaseEntity; -import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Table(name = "issued_log_area") -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class IssuedLogArea extends BaseEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "issued_log_area_id") - private Long id; - - @JoinColumn(name = "issued_log_id", nullable = false) - @ManyToOne(fetch = FetchType.LAZY) - private IssuedLog issuedLog; - - @Column(name = "area_code", nullable = false) - private String areaCode; - - @Builder(access = AccessLevel.PRIVATE) - private IssuedLogArea(String tenantId, IssuedLog issuedLog, String areaCode) { - this.tenantId = tenantId; - this.issuedLog = issuedLog; - this.areaCode = areaCode; - } - - public static IssuedLogArea createIssuedLogArea( - String tenantId, IssuedLog issuedLog, String areaCode) { - return IssuedLogArea.builder() - .tenantId(tenantId) - .issuedLog(issuedLog) - .areaCode(areaCode) - .build(); - } -} diff --git a/src/main/java/com/doubleo/passservice/domain/log/dto/request/CreateAreaEnterLogRequest.java b/src/main/java/com/doubleo/passservice/domain/log/dto/request/CreateAreaEnterLogRequest.java deleted file mode 100644 index b719b08..0000000 --- a/src/main/java/com/doubleo/passservice/domain/log/dto/request/CreateAreaEnterLogRequest.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.doubleo.passservice.domain.log.dto.request; - -public record CreateAreaEnterLogRequest( - String tenantId, Long areaId, Long memberId, String memberName, Long passId) {} diff --git a/src/main/java/com/doubleo/passservice/domain/log/dto/request/CreateBuildingEnterLogRequest.java b/src/main/java/com/doubleo/passservice/domain/log/dto/request/CreateBuildingEnterLogRequest.java deleted file mode 100644 index 12cfbeb..0000000 --- a/src/main/java/com/doubleo/passservice/domain/log/dto/request/CreateBuildingEnterLogRequest.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.doubleo.passservice.domain.log.dto.request; - -import com.doubleo.passservice.domain.log.domain.Direction; -import com.doubleo.passservice.domain.pass.enums.VisitCategory; - -public record CreateBuildingEnterLogRequest( - String tenantId, - Long buildingId, - Long memberId, - String memberName, - Long passId, - Direction direction, - VisitCategory visitCategory) {} diff --git a/src/main/java/com/doubleo/passservice/domain/log/dto/response/EnterLogResponse.java b/src/main/java/com/doubleo/passservice/domain/log/dto/response/EnterLogResponse.java deleted file mode 100644 index 8bffa68..0000000 --- a/src/main/java/com/doubleo/passservice/domain/log/dto/response/EnterLogResponse.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.doubleo.passservice.domain.log.dto.response; - -import java.time.LocalDateTime; - -public record EnterLogResponse( - String areaCode, - String areaName, - Long memberId, - String memberName, - Long passId, - LocalDateTime createdDt) {} diff --git a/src/main/java/com/doubleo/passservice/domain/log/dto/response/HourlyEntryResponse.java b/src/main/java/com/doubleo/passservice/domain/log/dto/response/HourlyEntryResponse.java deleted file mode 100644 index 5fca189..0000000 --- a/src/main/java/com/doubleo/passservice/domain/log/dto/response/HourlyEntryResponse.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.doubleo.passservice.domain.log.dto.response; - -import com.fasterxml.jackson.annotation.JsonFormat; -import java.io.Serializable; -import java.time.LocalDateTime; - -public record HourlyEntryResponse( - int hour, - int total, - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss") - LocalDateTime timestamp) - implements Serializable {} diff --git a/src/main/java/com/doubleo/passservice/domain/log/dto/response/HourlyIssuanceResponse.java b/src/main/java/com/doubleo/passservice/domain/log/dto/response/HourlyIssuanceResponse.java deleted file mode 100644 index 7cc9e5e..0000000 --- a/src/main/java/com/doubleo/passservice/domain/log/dto/response/HourlyIssuanceResponse.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.doubleo.passservice.domain.log.dto.response; - -import com.fasterxml.jackson.annotation.JsonFormat; -import java.io.Serializable; -import java.time.LocalDateTime; - -public record HourlyIssuanceResponse( - int hour, - int total, - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss") - LocalDateTime timestamp) - implements Serializable {} diff --git a/src/main/java/com/doubleo/passservice/domain/log/dto/response/IssuedLogResponse.java b/src/main/java/com/doubleo/passservice/domain/log/dto/response/IssuedLogResponse.java deleted file mode 100644 index c4385e6..0000000 --- a/src/main/java/com/doubleo/passservice/domain/log/dto/response/IssuedLogResponse.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.doubleo.passservice.domain.log.dto.response; - -import com.doubleo.passservice.domain.pass.dto.AreaInfo; -import com.doubleo.passservice.domain.pass.enums.VisitCategory; -import java.time.LocalDateTime; -import java.util.List; - -public record IssuedLogResponse( - Long memberId, - String memberName, - Long passId, - List areas, - LocalDateTime startAt, - LocalDateTime expiredAt, - VisitCategory visitCategory) {} diff --git a/src/main/java/com/doubleo/passservice/domain/log/producer/AreaEnterLogStreamProducer.java b/src/main/java/com/doubleo/passservice/domain/log/producer/AreaEnterLogStreamProducer.java deleted file mode 100644 index 0006c5d..0000000 --- a/src/main/java/com/doubleo/passservice/domain/log/producer/AreaEnterLogStreamProducer.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.doubleo.passservice.domain.log.producer; - -import com.doubleo.passservice.domain.log.dto.request.CreateAreaEnterLogRequest; -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.Map; -import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Component; - -@Component -@RequiredArgsConstructor -public class AreaEnterLogStreamProducer { - - private final RedisTemplate redisTemplate; - - public void sendAreaEnterLogToStream(CreateAreaEnterLogRequest request) { - Map message = new HashMap<>(); - message.put("tenantId", request.tenantId()); - message.put("areaId", request.areaId().toString()); - message.put("memberId", request.memberId().toString()); - message.put("memberName", request.memberName()); - message.put("passId", request.passId().toString()); - message.put("timestamp", LocalDateTime.now().toString()); - - redisTemplate.opsForStream().add("area:enter:stream", message); - } -} diff --git a/src/main/java/com/doubleo/passservice/domain/log/producer/BuildingEnterLogStreamProducer.java b/src/main/java/com/doubleo/passservice/domain/log/producer/BuildingEnterLogStreamProducer.java deleted file mode 100644 index d7094e5..0000000 --- a/src/main/java/com/doubleo/passservice/domain/log/producer/BuildingEnterLogStreamProducer.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.doubleo.passservice.domain.log.producer; - -import com.doubleo.passservice.domain.log.dto.request.CreateBuildingEnterLogRequest; -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.Map; -import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Component; - -@Component -@RequiredArgsConstructor -public class BuildingEnterLogStreamProducer { - - private final RedisTemplate redisTemplate; - - public void sendBuildingEnterLogToStream(CreateBuildingEnterLogRequest request) { - Map message = new HashMap<>(); - message.put("tenantId", request.tenantId()); - message.put("buildingId", request.buildingId().toString()); - message.put("memberId", request.memberId().toString()); - message.put("memberName", request.memberName()); - message.put("passId", request.passId().toString()); - message.put("direction", request.direction().name()); - message.put("visitCategory", request.visitCategory().name()); - message.put("timestamp", LocalDateTime.now().toString()); - - redisTemplate.opsForStream().add("building:enter:stream", message); - } -} diff --git a/src/main/java/com/doubleo/passservice/domain/log/repository/BuildingEnterLogRepository.java b/src/main/java/com/doubleo/passservice/domain/log/repository/BuildingEnterLogRepository.java deleted file mode 100644 index f4a5c10..0000000 --- a/src/main/java/com/doubleo/passservice/domain/log/repository/BuildingEnterLogRepository.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.doubleo.passservice.domain.log.repository; - -import com.doubleo.passservice.domain.log.domain.BuildingEnterLog; -import com.doubleo.passservice.domain.stats.dto.request.UpdateDailyEntryStatsRequest; -import java.time.LocalDateTime; -import java.util.List; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - -public interface BuildingEnterLogRepository extends JpaRepository { - @Query( - """ - SELECT COUNT(*) FROM BuildingEnterLog l - WHERE l.direction = 'IN' - AND l.createdDt >= :start AND l.createdDt < :end -""") - int countInLogsAtHour(@Param("start") LocalDateTime start, @Param("end") LocalDateTime end); - - Page findAllByTenantId(String tenantId, Pageable pageable); - - @Query( - """ - SELECT new com.doubleo.passservice.domain.stats.dto.request.UpdateDailyEntryStatsRequest( - b.buildingId, b.memberName, p.visitCategory, COUNT(b) - ) - FROM BuildingEnterLog b - JOIN Pass p ON b.passId = p.id - WHERE b.direction = 'IN' - AND b.createdDt >= :start - AND b.createdDt < :end - AND b.tenantId = :tenantId - GROUP BY b.buildingId, b.memberName, p.visitCategory -""") - List countDailyGrouped( - @Param("start") LocalDateTime start, - @Param("end") LocalDateTime end, - @Param("tenantId") String tenantId); - - @Query( - """ - SELECT COUNT(b) - FROM BuildingEnterLog b - WHERE b.direction = 'IN' - AND b.createdDt >= :start - AND b.createdDt < :end - AND b.tenantId = :tenantId -""") - Long countEnteredBetween( - @Param("start") LocalDateTime start, - @Param("end") LocalDateTime end, - @Param("tenantId") String tenantId); - - @Query( - """ - SELECT COUNT(b) - FROM BuildingEnterLog b - JOIN Pass p ON b.passId = p.id - WHERE b.direction = 'IN' - AND b.createdDt >= :start - AND b.createdDt < :end - AND b.tenantId = :tenantId - AND p.visitCategory = :visitCategory -""") - int countEnteredByCategory( - @Param("start") LocalDateTime start, - @Param("end") LocalDateTime end, - @Param("tenantId") String tenantId, - @Param("visitCategory") - com.doubleo.passservice.domain.pass.enums.VisitCategory visitCategory); - - @Query( - """ - SELECT COUNT(b) - FROM BuildingEnterLog b - JOIN Pass p ON b.passId = p.id - WHERE b.direction = 'OUT' - AND b.createdDt >= :start - AND b.createdDt < :end - AND b.tenantId = :tenantId - AND p.visitCategory = :visitCategory -""") - int countExitedByCategory( - @Param("start") LocalDateTime start, - @Param("end") LocalDateTime end, - @Param("tenantId") String tenantId, - @Param("visitCategory") - com.doubleo.passservice.domain.pass.enums.VisitCategory visitCategory); -} diff --git a/src/main/java/com/doubleo/passservice/domain/log/repository/EnterLogRepository.java b/src/main/java/com/doubleo/passservice/domain/log/repository/EnterLogRepository.java deleted file mode 100644 index 235ef2b..0000000 --- a/src/main/java/com/doubleo/passservice/domain/log/repository/EnterLogRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.doubleo.passservice.domain.log.repository; - -import com.doubleo.passservice.domain.log.domain.EnterLog; -import java.util.List; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface EnterLogRepository extends JpaRepository { - List findAllByTenantId(String tenantId); - - Page findAllByTenantId(String tenantId, Pageable pageable); -} diff --git a/src/main/java/com/doubleo/passservice/domain/log/repository/IssuedLogAreaRepository.java b/src/main/java/com/doubleo/passservice/domain/log/repository/IssuedLogAreaRepository.java deleted file mode 100644 index 124c822..0000000 --- a/src/main/java/com/doubleo/passservice/domain/log/repository/IssuedLogAreaRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.doubleo.passservice.domain.log.repository; - -import com.doubleo.passservice.domain.log.domain.IssuedLog; -import com.doubleo.passservice.domain.log.domain.IssuedLogArea; -import java.util.List; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - -public interface IssuedLogAreaRepository extends JpaRepository { - @Query("SELECT i.areaCode FROM IssuedLogArea i WHERE i.issuedLog = :issuedLog") - List findAreaCodesByIssuedLog(@Param("issuedLog") IssuedLog issuedLog); -} diff --git a/src/main/java/com/doubleo/passservice/domain/log/repository/IssuedLogRepository.java b/src/main/java/com/doubleo/passservice/domain/log/repository/IssuedLogRepository.java deleted file mode 100644 index 59e7469..0000000 --- a/src/main/java/com/doubleo/passservice/domain/log/repository/IssuedLogRepository.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.doubleo.passservice.domain.log.repository; - -import com.doubleo.passservice.domain.log.domain.IssuedLog; -import java.time.LocalDateTime; -import java.util.List; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - -public interface IssuedLogRepository extends JpaRepository { - List findAllByTenantId(String tenantId); - - Page findAllByTenantId(String tenantId, Pageable pageable); - - @Query( - """ - SELECT COUNT(il) - FROM IssuedLog il - WHERE il.createdDt >= :start - AND il.createdDt < :end - AND il.tenantId = :tenantId - """) - int countInLogsAtHour( - @Param("start") LocalDateTime start, - @Param("end") LocalDateTime end, - @Param("tenantId") String tenantId); -} diff --git a/src/main/java/com/doubleo/passservice/domain/log/service/LogService.java b/src/main/java/com/doubleo/passservice/domain/log/service/LogService.java deleted file mode 100644 index b8556a7..0000000 --- a/src/main/java/com/doubleo/passservice/domain/log/service/LogService.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.doubleo.passservice.domain.log.service; - -import com.doubleo.passservice.domain.log.dto.response.EnterLogResponse; -import com.doubleo.passservice.domain.log.dto.response.HourlyIssuanceResponse; -import com.doubleo.passservice.domain.log.dto.response.IssuedLogResponse; -import java.util.List; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; - -public interface LogService { - Page getAllIssuedLog(Pageable pageable); - - Page getAllEnterLog(Pageable pageable); - - List getHourlyIssuanceList(); -} diff --git a/src/main/java/com/doubleo/passservice/domain/log/service/LogServiceImpl.java b/src/main/java/com/doubleo/passservice/domain/log/service/LogServiceImpl.java deleted file mode 100644 index 4330676..0000000 --- a/src/main/java/com/doubleo/passservice/domain/log/service/LogServiceImpl.java +++ /dev/null @@ -1,128 +0,0 @@ -package com.doubleo.passservice.domain.log.service; - -import com.doubleo.passservice.domain.log.domain.EnterLog; -import com.doubleo.passservice.domain.log.domain.IssuedLog; -import com.doubleo.passservice.domain.log.dto.response.EnterLogResponse; -import com.doubleo.passservice.domain.log.dto.response.HourlyIssuanceResponse; -import com.doubleo.passservice.domain.log.dto.response.IssuedLogResponse; -import com.doubleo.passservice.domain.log.repository.BuildingEnterLogRepository; -import com.doubleo.passservice.domain.log.repository.EnterLogRepository; -import com.doubleo.passservice.domain.log.repository.IssuedLogAreaRepository; -import com.doubleo.passservice.domain.log.repository.IssuedLogRepository; -import com.doubleo.passservice.domain.pass.dto.AreaInfo; -import com.doubleo.passservice.global.util.TenantValidator; -import com.doubleo.passservice.grpc.client.AreaClient; -import java.time.Duration; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@Transactional -@RequiredArgsConstructor -public class LogServiceImpl implements LogService { - - private final EnterLogRepository enterLogRepository; - private final IssuedLogRepository issuedLogRepository; - private final IssuedLogAreaRepository issuedLogAreaRepository; - private final TenantValidator tenantValidator; - private final AreaClient areaClient; - private final BuildingEnterLogRepository buildingEnterLogRepository; - private final RedisTemplate redisTemplate; - - @Override - public Page getAllIssuedLog(Pageable pageable) { - String tenantId = tenantValidator.getTenantId(); - Page issuedLogs = issuedLogRepository.findAllByTenantId(tenantId, pageable); - - return issuedLogs.map( - issuedLog -> { - List areaCodes = - issuedLogAreaRepository.findAreaCodesByIssuedLog(issuedLog); - List areas = - areaCodes.stream() - .map( - code -> { - String areaName = - areaClient - .getAreaFullNameByCode( - tenantId, code) - .getAreaFullName(); - return new AreaInfo(code, areaName); - }) - .toList(); - - return new IssuedLogResponse( - issuedLog.getMemberId(), - issuedLog.getMemberName(), - issuedLog.getPassId(), - areas, - issuedLog.getStartAt(), - issuedLog.getExpiredAt(), - issuedLog.getVisitCategory()); - }); - } - - @Override - public Page getAllEnterLog(Pageable pageable) { - String tenantId = tenantValidator.getTenantId(); - Page enterLogs = enterLogRepository.findAllByTenantId(tenantId, pageable); - return enterLogs.map( - enterLog -> - new EnterLogResponse( - areaClient.getAreaById(enterLog.getAreaId()).getAreaCode(), - areaClient - .getAreaFullNameByCode( - tenantId, - areaClient - .getAreaById(enterLog.getAreaId()) - .getAreaCode()) - .getAreaFullName(), - enterLog.getMemberId(), - enterLog.getMemberName(), - enterLog.getPassId(), - enterLog.getCreatedDt())); - } - - @Override - public List getHourlyIssuanceList() { - LocalDateTime now = LocalDateTime.now().withMinute(0).withSecond(0).withNano(0); - String tenantId = tenantValidator.getTenantId(); - - LocalDateTime end = now; - LocalDateTime start = end.minusHours(24); - - List hourlyList = new ArrayList<>(); - - for (int i = 0; i < 24; i++) { - LocalDateTime hour = start.plusHours(i); - String hourStr = hour.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH")); - String redisKey = "hourlyIssuance:" + tenantId + ":" + hourStr; - - String cachedCount = redisTemplate.opsForValue().get(redisKey); - - int total; - if (cachedCount != null) { - total = Integer.parseInt(cachedCount); - } else { - int count = - issuedLogRepository.countInLogsAtHour(hour, hour.plusHours(1), tenantId); - redisTemplate - .opsForValue() - .set(redisKey, String.valueOf(count), Duration.ofSeconds(10)); - total = count; - } - - hourlyList.add(new HourlyIssuanceResponse(hour.getHour(), total, hour)); - } - - return hourlyList; - } -} diff --git a/src/main/java/com/doubleo/passservice/domain/pass/controller/PassController.java b/src/main/java/com/doubleo/passservice/domain/pass/controller/PassController.java index eb79cb7..f74df5c 100644 --- a/src/main/java/com/doubleo/passservice/domain/pass/controller/PassController.java +++ b/src/main/java/com/doubleo/passservice/domain/pass/controller/PassController.java @@ -2,14 +2,19 @@ import com.doubleo.passservice.domain.pass.dto.request.PassCreateRequest; import com.doubleo.passservice.domain.pass.dto.request.PassDeleteRequest; +import com.doubleo.passservice.domain.pass.dto.request.UpdatePassStatusRequest; import com.doubleo.passservice.domain.pass.dto.response.MemberPassInfoResponse; import com.doubleo.passservice.domain.pass.dto.response.PassCreateResponse; +import com.doubleo.passservice.domain.pass.dto.response.PendingPassResponse; import com.doubleo.passservice.domain.pass.service.PassService; import com.doubleo.passservice.global.exception.CommonException; import com.doubleo.passservice.global.exception.errorcode.PassErrorCode; +import com.doubleo.passservice.global.util.TenantValidator; import io.swagger.v3.oas.annotations.Operation; import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -19,6 +24,7 @@ public class PassController { private final PassService passService; + private final TenantValidator tenantValidator; @Operation(summary = "All Member Pass get API", description = "모든 사용자의 Pass들을 조회하는 API") @GetMapping @@ -62,4 +68,21 @@ public ResponseEntity PassDelete( passService.deletePass(request.passId()); return ResponseEntity.ok().build(); } + + @Operation(summary = "All pending pass get API", description = "모든 발급 대기중인 출입증 조회 API") + @GetMapping("/pending") + public Page PendingPassListGet( + @RequestHeader("X-Admin-Id") Long adminId, Pageable pageable) { + String tenantId = tenantValidator.getTenantId(); + return passService.getPendingPassList(tenantId, pageable); + } + + @Operation(summary = "Accept Guardian application", description = "보호자 출입증 신청 승인 API") + @PostMapping("/approve") + public PassCreateResponse GuardianApplicationCreate( + @RequestHeader("X-Admin-Id") Long adminId, + @RequestBody UpdatePassStatusRequest request) { + return passService.createGuardianAndUpdatePassStatus( + request.passId(), request.issuanceStatus()); + } } diff --git a/src/main/java/com/doubleo/passservice/domain/log/dto/request/UpdatePassStatusRequest.java b/src/main/java/com/doubleo/passservice/domain/pass/dto/request/UpdatePassStatusRequest.java similarity index 87% rename from src/main/java/com/doubleo/passservice/domain/log/dto/request/UpdatePassStatusRequest.java rename to src/main/java/com/doubleo/passservice/domain/pass/dto/request/UpdatePassStatusRequest.java index 6fe3ec0..11cc3fb 100644 --- a/src/main/java/com/doubleo/passservice/domain/log/dto/request/UpdatePassStatusRequest.java +++ b/src/main/java/com/doubleo/passservice/domain/pass/dto/request/UpdatePassStatusRequest.java @@ -1,4 +1,4 @@ -package com.doubleo.passservice.domain.log.dto.request; +package com.doubleo.passservice.domain.pass.dto.request; import com.doubleo.passservice.domain.pass.enums.IssuanceStatus; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/src/main/java/com/doubleo/passservice/domain/log/dto/response/PendingPassResponse.java b/src/main/java/com/doubleo/passservice/domain/pass/dto/response/PendingPassResponse.java similarity index 85% rename from src/main/java/com/doubleo/passservice/domain/log/dto/response/PendingPassResponse.java rename to src/main/java/com/doubleo/passservice/domain/pass/dto/response/PendingPassResponse.java index 9610f9e..a620ad9 100644 --- a/src/main/java/com/doubleo/passservice/domain/log/dto/response/PendingPassResponse.java +++ b/src/main/java/com/doubleo/passservice/domain/pass/dto/response/PendingPassResponse.java @@ -1,4 +1,4 @@ -package com.doubleo.passservice.domain.log.dto.response; +package com.doubleo.passservice.domain.pass.dto.response; import java.time.LocalDateTime; diff --git a/src/main/java/com/doubleo/passservice/domain/pass/service/PassService.java b/src/main/java/com/doubleo/passservice/domain/pass/service/PassService.java index 00d5d13..5e0c1c6 100644 --- a/src/main/java/com/doubleo/passservice/domain/pass/service/PassService.java +++ b/src/main/java/com/doubleo/passservice/domain/pass/service/PassService.java @@ -1,8 +1,8 @@ package com.doubleo.passservice.domain.pass.service; -import com.doubleo.passservice.domain.log.dto.response.PendingPassResponse; import com.doubleo.passservice.domain.pass.dto.response.MemberPassInfoResponse; import com.doubleo.passservice.domain.pass.dto.response.PassCreateResponse; +import com.doubleo.passservice.domain.pass.dto.response.PendingPassResponse; import com.doubleo.passservice.domain.pass.enums.IssuanceStatus; import java.util.List; import org.springframework.data.domain.Page; diff --git a/src/main/java/com/doubleo/passservice/domain/pass/service/PassServiceImpl.java b/src/main/java/com/doubleo/passservice/domain/pass/service/PassServiceImpl.java index 979a858..21b4563 100644 --- a/src/main/java/com/doubleo/passservice/domain/pass/service/PassServiceImpl.java +++ b/src/main/java/com/doubleo/passservice/domain/pass/service/PassServiceImpl.java @@ -2,11 +2,6 @@ import com.doubleo.hospitalservice.domain.area.grpc.server.AreaResponse; import com.doubleo.memberservice.domain.member.grpc.server.MemberResponse; -import com.doubleo.passservice.domain.log.domain.IssuedLog; -import com.doubleo.passservice.domain.log.domain.IssuedLogArea; -import com.doubleo.passservice.domain.log.dto.response.PendingPassResponse; -import com.doubleo.passservice.domain.log.repository.IssuedLogAreaRepository; -import com.doubleo.passservice.domain.log.repository.IssuedLogRepository; import com.doubleo.passservice.domain.notification.dto.request.FcmSendRequest; import com.doubleo.passservice.domain.notification.service.FcmService; import com.doubleo.passservice.domain.pass.domain.Pass; @@ -14,16 +9,14 @@ import com.doubleo.passservice.domain.pass.dto.GuardianInfo; import com.doubleo.passservice.domain.pass.dto.response.MemberPassInfoResponse; import com.doubleo.passservice.domain.pass.dto.response.PassCreateResponse; +import com.doubleo.passservice.domain.pass.dto.response.PendingPassResponse; import com.doubleo.passservice.domain.pass.enums.IssuanceStatus; import com.doubleo.passservice.domain.pass.enums.VisitCategory; import com.doubleo.passservice.domain.pass.repository.PassAreaRepository; import com.doubleo.passservice.domain.pass.repository.PassRepository; import com.doubleo.passservice.global.exception.CommonException; import com.doubleo.passservice.global.exception.errorcode.PassErrorCode; -import com.doubleo.passservice.grpc.client.AreaClient; -import com.doubleo.passservice.grpc.client.GuardianClient; -import com.doubleo.passservice.grpc.client.MemberClient; -import com.doubleo.passservice.grpc.client.PatientClient; +import com.doubleo.passservice.grpc.client.*; import com.doubleo.patientservice.domain.guardian.grpc.server.GuardianResponse; import com.doubleo.patientservice.domain.patient.grpc.server.PatientResponse; import java.time.LocalDate; @@ -54,12 +47,11 @@ public class PassServiceImpl implements PassService { private final PassRepository passRepository; private final PassAreaRepository passAreaRepository; - private final IssuedLogRepository issuedLogRepository; - private final IssuedLogAreaRepository issuedLogAreaRepository; private final MemberClient memberClient; private final AreaClient areaClient; private final PatientClient patientClient; private final GuardianClient guardianClient; + private final LogClient logClient; private final FcmService fcmService; @Override @@ -231,7 +223,7 @@ public PassCreateResponse createGuardianAndUpdatePassStatus( passAreaRepository.findAllByPass(pass).stream() .map(PassArea::getAreaCode) .toList(); - createIssuedLog( + logClient.createIssuedLog( pass.getTenantId(), pass.getMemberId(), member.getMemberName(), @@ -341,7 +333,7 @@ private PassCreateResponse createPass( } if (status == IssuanceStatus.ISSUED) { - createIssuedLog( + logClient.createIssuedLog( tenantId, memberId, member.getMemberName(), @@ -355,35 +347,6 @@ private PassCreateResponse createPass( return new PassCreateResponse(pass.getId()); } - private void createIssuedLog( - String tenantId, - Long memberId, - String memberName, - String memberContact, - Long passId, - LocalDateTime startAt, - LocalDateTime expiredAt, - VisitCategory visitCategory, - List areaCodes) { - IssuedLog issuedLog = - issuedLogRepository.save( - IssuedLog.createIssuedLog( - tenantId, - memberId, - memberName, - memberContact, - passId, - startAt, - expiredAt, - visitCategory)); - List logAreas = - areaCodes.stream() - .map(code -> IssuedLogArea.createIssuedLogArea(tenantId, issuedLog, code)) - .toList(); - - issuedLogAreaRepository.saveAll(logAreas); - } - private LocalDateTime parseDate(String date) { return LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd")).atStartOfDay(); } diff --git a/src/main/java/com/doubleo/passservice/domain/stats/controller/EntryStatsController.java b/src/main/java/com/doubleo/passservice/domain/stats/controller/EntryStatsController.java deleted file mode 100644 index dee604a..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/controller/EntryStatsController.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.doubleo.passservice.domain.stats.controller; - -import com.doubleo.passservice.domain.log.dto.response.HourlyEntryResponse; -import com.doubleo.passservice.domain.stats.dto.response.*; -import com.doubleo.passservice.domain.stats.service.StatsService; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/pass-logs") -@RequiredArgsConstructor -public class EntryStatsController { - - private final StatsService statsService; - - @GetMapping("/hourly") - public List hourlyEntryListGet() { - return statsService.getHourlyEntryList(); - } - - @GetMapping("/period/daily") - public List dailyPeriodStatsListGet() { - return statsService.getDailyPeriodStatsList(); - } - - @GetMapping("/period/weekly") - public List weeklyPeriodStatsListGet() { - return statsService.getLastWeeksStatsList(); - } - - @GetMapping("/period/monthly") - public List monthlyPeriodStatsListGet() { - return statsService.getRecentMonthlyStatsList(); - } - - @GetMapping("/category") - public List lastWeekCategoryStatsListGet() { - return statsService.getLastWeekCategoryStats(); - } - - @GetMapping("/building") - public List lastWeekBuildingStatsListGet() { - return statsService.getLastWeekBuildingStats(); - } - - @GetMapping("/dashboard-summary") - public List currentRetainedStatusGet() { - return statsService.getCurrentRetainedStatus(); - } -} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/controller/PassCountController.java b/src/main/java/com/doubleo/passservice/domain/stats/controller/PassCountController.java deleted file mode 100644 index 1b3287b..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/controller/PassCountController.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.doubleo.passservice.domain.stats.controller; - -import com.doubleo.passservice.domain.stats.dto.request.PassCountInfoRequest; -import com.doubleo.passservice.domain.stats.dto.response.PassCountInfoResponse; -import com.doubleo.passservice.domain.stats.service.PassCountService; -import jakarta.validation.Valid; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequestMapping("/pass-logs") -@RequiredArgsConstructor -public class PassCountController { - - private final PassCountService passCountService; - - @PostMapping("/search") - public List passCountListGet( - @RequestBody @Valid PassCountInfoRequest request) { - return passCountService.getPassCount(request); - } -} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/domain/DailyRetainedSnapshot.java b/src/main/java/com/doubleo/passservice/domain/stats/domain/DailyRetainedSnapshot.java deleted file mode 100644 index 931126a..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/domain/DailyRetainedSnapshot.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.doubleo.passservice.domain.stats.domain; - -import com.doubleo.passservice.domain.common.model.BaseEntity; -import com.doubleo.passservice.domain.pass.enums.VisitCategory; -import jakarta.persistence.*; -import java.time.LocalDate; -import lombok.*; - -@Entity -@Table( - name = "daily_retained_snapshot", - uniqueConstraints = { - @UniqueConstraint(columnNames = {"snapshot_date", "tenant_id", "visit_category"}) - }) -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor(access = AccessLevel.PRIVATE) -public class DailyRetainedSnapshot extends BaseEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "snapshot_id") - private Long id; - - @Column(name = "snapshot_date", nullable = false) - private LocalDate snapshotDate; - - @Enumerated(EnumType.STRING) - @Column(name = "visit_category", nullable = false) - private VisitCategory visitCategory; - - @Column(name = "retained_count", nullable = false) - private int retainedCount; - - public DailyRetainedSnapshot( - String tenantId, - LocalDate snapshotDate, - VisitCategory visitCategory, - int retainedCount) { - this.tenantId = tenantId; - this.snapshotDate = snapshotDate; - this.visitCategory = visitCategory; - this.retainedCount = retainedCount; - } -} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/domain/EntryStatsDaily.java b/src/main/java/com/doubleo/passservice/domain/stats/domain/EntryStatsDaily.java deleted file mode 100644 index edec11b..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/domain/EntryStatsDaily.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.doubleo.passservice.domain.stats.domain; - -import com.doubleo.passservice.domain.common.model.BaseEntity; -import com.doubleo.passservice.domain.pass.enums.VisitCategory; -import jakarta.persistence.*; -import java.time.LocalDate; -import lombok.*; - -@Entity -@Table( - name = "entry_stats_daily", - uniqueConstraints = { - @UniqueConstraint( - columnNames = { - "tenant_id", - "entry_stats_daily_date", - "entry_stats_daily_building_id", - "entry_stats_daily_visit_category" - }) - }) -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Builder -@AllArgsConstructor -public class EntryStatsDaily extends BaseEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(name = "entry_stats_daily_date", nullable = false) - private LocalDate date; - - @Column(name = "entry_stats_daily_building_id", nullable = false) - private Long buildingId; - - @Column(name = "entry_stats_daily_building_name", nullable = false) - private String buildingName; - - @Column(name = "entry_stats_daily_visit_category", nullable = false) - @Enumerated(EnumType.STRING) - private VisitCategory visitCategory; - - @Column(name = "entry_stats_daily_entered", nullable = false) - private Long entered; - - public EntryStatsDaily( - String tenantId, - LocalDate date, - Long buildingId, - String buildingName, - VisitCategory visitCategory, - Long entered) { - this.tenantId = tenantId; - this.date = date; - this.buildingId = buildingId; - this.buildingName = buildingName; - this.visitCategory = visitCategory; - this.entered = entered; - } -} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/domain/EntryStatsMonthly.java b/src/main/java/com/doubleo/passservice/domain/stats/domain/EntryStatsMonthly.java deleted file mode 100644 index b90ab6f..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/domain/EntryStatsMonthly.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.doubleo.passservice.domain.stats.domain; - -import com.doubleo.passservice.domain.common.model.BaseEntity; -import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Table( - name = "entry_stats_monthly", - uniqueConstraints = { - @UniqueConstraint( - columnNames = { - "tenant_id", - "entry_stats_monthly_year", - "entry_stats_monthly_month" - }) - }) -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class EntryStatsMonthly extends BaseEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(name = "entry_stats_monthly_year", nullable = false) - private int year; - - @Column(name = "entry_stats_monthly_month", nullable = false) - private int month; - - @Column(name = "entry_stats_monthly_entered", nullable = false) - private Long entered; - - public EntryStatsMonthly(String tenantId, int year, int month, Long entered) { - this.tenantId = tenantId; - this.year = year; - this.month = month; - this.entered = entered; - } -} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/domain/EntryStatsWeekly.java b/src/main/java/com/doubleo/passservice/domain/stats/domain/EntryStatsWeekly.java deleted file mode 100644 index 1c6be52..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/domain/EntryStatsWeekly.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.doubleo.passservice.domain.stats.domain; - -import com.doubleo.passservice.domain.common.model.BaseEntity; -import jakarta.persistence.*; -import java.time.LocalDate; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Table( - name = "entry_stats_weekly", - uniqueConstraints = { - @UniqueConstraint( - columnNames = { - "tenant_id", - "entry_stats_weekly_start_date", - "entry_stats_weekly_end_date" - }) - }) -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class EntryStatsWeekly extends BaseEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(name = "entry_stats_weekly_start_date", nullable = false) - private LocalDate startDate; - - @Column(name = "entry_stats_weekly_end_date", nullable = false) - private LocalDate endDate; - - @Column(name = "entry_stats_weekly_entered", nullable = false) - private Long entered; - - public EntryStatsWeekly(String tenantId, LocalDate startDate, LocalDate endDate, Long entered) { - this.tenantId = tenantId; - this.startDate = startDate; - this.endDate = endDate; - this.entered = entered; - } -} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/dto/request/PassCountInfoRequest.java b/src/main/java/com/doubleo/passservice/domain/stats/dto/request/PassCountInfoRequest.java deleted file mode 100644 index d014416..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/dto/request/PassCountInfoRequest.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.doubleo.passservice.domain.stats.dto.request; - -import com.doubleo.passservice.domain.pass.enums.VisitCategory; -import com.doubleo.passservice.global.exception.CommonException; -import com.doubleo.passservice.global.exception.errorcode.PassSearchErrorCode; -import java.time.LocalDate; -import java.util.List; - -public record PassCountInfoRequest( - List categories, - Integer period, - LocalDate startDate, - LocalDate endDate, - List areaCodes) { - public boolean isManualDate() { - return period == null; - } - - public LocalDate resolvedStartDate() { - return isManualDate() ? startDate : LocalDate.now().minusDays(period); - } - - public LocalDate resolvedEndDate() { - return isManualDate() ? endDate : LocalDate.now().minusDays(1); - } - - public void validatePeriod() { - if (!isManualDate() && !List.of(7, 14, 28).contains(period)) { - throw new CommonException(PassSearchErrorCode.INVALID_PERIOD); - } - } - - public void validateDateRange() { - if (isManualDate()) { - if (startDate == null || endDate == null) { - throw new CommonException(PassSearchErrorCode.MISSING_DATE_IN_MANUAL_MODE); - } - if (startDate.isAfter(endDate)) { - throw new CommonException(PassSearchErrorCode.INVALID_DATE_RANGE); - } - } - } -} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/dto/request/UpdateDailyEntryStatsRequest.java b/src/main/java/com/doubleo/passservice/domain/stats/dto/request/UpdateDailyEntryStatsRequest.java deleted file mode 100644 index aa259f5..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/dto/request/UpdateDailyEntryStatsRequest.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.doubleo.passservice.domain.stats.dto.request; - -import com.doubleo.passservice.domain.pass.enums.VisitCategory; - -public record UpdateDailyEntryStatsRequest( - Long buildingId, String buildingName, VisitCategory visitCategory, Long enteredCount) {} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/dto/response/DailyStatsInfoListResponse.java b/src/main/java/com/doubleo/passservice/domain/stats/dto/response/DailyStatsInfoListResponse.java deleted file mode 100644 index f1eb939..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/dto/response/DailyStatsInfoListResponse.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.doubleo.passservice.domain.stats.dto.response; - -import java.time.LocalDate; - -public record DailyStatsInfoListResponse(LocalDate date, Long total) { - public static DailyStatsInfoListResponse of(LocalDate date, Long total) { - return new DailyStatsInfoListResponse(date, total); - } - - public DailyStatsInfoListResponse(LocalDate date, Long total) { - this.date = date; - this.total = total; - } -} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/dto/response/LastWeekBuildingStatsInfoListResponse.java b/src/main/java/com/doubleo/passservice/domain/stats/dto/response/LastWeekBuildingStatsInfoListResponse.java deleted file mode 100644 index 9e4601c..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/dto/response/LastWeekBuildingStatsInfoListResponse.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.doubleo.passservice.domain.stats.dto.response; - -import java.time.LocalDate; - -public record LastWeekBuildingStatsInfoListResponse( - LocalDate date, String day, String buildingName, long total) {} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/dto/response/LastWeekCategoryStatsInfoListResponse.java b/src/main/java/com/doubleo/passservice/domain/stats/dto/response/LastWeekCategoryStatsInfoListResponse.java deleted file mode 100644 index f681462..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/dto/response/LastWeekCategoryStatsInfoListResponse.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.doubleo.passservice.domain.stats.dto.response; - -import java.time.LocalDate; - -public record LastWeekCategoryStatsInfoListResponse( - LocalDate date, String day, String category, long total) {} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/dto/response/MonthlyStatsInfoListResponse.java b/src/main/java/com/doubleo/passservice/domain/stats/dto/response/MonthlyStatsInfoListResponse.java deleted file mode 100644 index eaa885d..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/dto/response/MonthlyStatsInfoListResponse.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.doubleo.passservice.domain.stats.dto.response; - -public record MonthlyStatsInfoListResponse(int year, int month, long total) { - public static MonthlyStatsInfoListResponse of(int year, int month, long total) { - return new MonthlyStatsInfoListResponse(year, month, total); - } -} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/dto/response/PassCountInfoResponse.java b/src/main/java/com/doubleo/passservice/domain/stats/dto/response/PassCountInfoResponse.java deleted file mode 100644 index 842d061..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/dto/response/PassCountInfoResponse.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.doubleo.passservice.domain.stats.dto.response; - -import java.time.LocalDate; - -public record PassCountInfoResponse( - LocalDate date, String areaCode, String areaName, long passCount) {} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/dto/response/RetainedStatusInfoResponse.java b/src/main/java/com/doubleo/passservice/domain/stats/dto/response/RetainedStatusInfoResponse.java deleted file mode 100644 index 9c59746..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/dto/response/RetainedStatusInfoResponse.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.doubleo.passservice.domain.stats.dto.response; - -import com.doubleo.passservice.domain.pass.enums.VisitCategory; - -public record RetainedStatusInfoResponse( - VisitCategory category, int entered, int exited, int remaining) {} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/dto/response/WeeklyStatsInfoListResponse.java b/src/main/java/com/doubleo/passservice/domain/stats/dto/response/WeeklyStatsInfoListResponse.java deleted file mode 100644 index 007f7c1..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/dto/response/WeeklyStatsInfoListResponse.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.doubleo.passservice.domain.stats.dto.response; - -import java.time.LocalDate; - -public record WeeklyStatsInfoListResponse(LocalDate startDate, LocalDate endDate, Long entered) { - public static WeeklyStatsInfoListResponse of(LocalDate start, LocalDate end, Long total) { - return new WeeklyStatsInfoListResponse(start, end, total); - } -} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/repository/DailyRetainedSnapshotRepository.java b/src/main/java/com/doubleo/passservice/domain/stats/repository/DailyRetainedSnapshotRepository.java deleted file mode 100644 index 3492fcb..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/repository/DailyRetainedSnapshotRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.doubleo.passservice.domain.stats.repository; - -import com.doubleo.passservice.domain.pass.enums.VisitCategory; -import com.doubleo.passservice.domain.stats.domain.DailyRetainedSnapshot; -import java.time.LocalDate; -import java.util.Optional; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface DailyRetainedSnapshotRepository - extends JpaRepository { - Optional findBySnapshotDateAndTenantIdAndVisitCategory( - LocalDate snapshotDate, String tenantId, VisitCategory visitCategory); -} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/repository/EntryStatsDailyRepository.java b/src/main/java/com/doubleo/passservice/domain/stats/repository/EntryStatsDailyRepository.java deleted file mode 100644 index 60912d1..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/repository/EntryStatsDailyRepository.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.doubleo.passservice.domain.stats.repository; - -import com.doubleo.passservice.domain.stats.domain.EntryStatsDaily; -import com.doubleo.passservice.domain.stats.dto.response.DailyStatsInfoListResponse; -import java.time.LocalDate; -import java.util.List; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; - -public interface EntryStatsDailyRepository extends JpaRepository { - @Query( - """ - SELECT new com.doubleo.passservice.domain.stats.dto.response.DailyStatsInfoListResponse(e.date, SUM(e.entered)) - FROM EntryStatsDaily e - WHERE e.tenantId = :tenantId - AND e.date < :today - AND e.date >= :startDate - GROUP BY e.date - ORDER BY e.date DESC -""") - List findDailyEnteredSumByDate( - String tenantId, LocalDate today, LocalDate startDate); - - @Query( - """ - SELECT e - FROM EntryStatsDaily e - WHERE e.tenantId = :tenantId - AND e.date >= :startDate - AND e.date < :endDate - ORDER BY e.date DESC -""") - List findLastWeekStats( - String tenantId, LocalDate startDate, LocalDate endDate); -} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/repository/EntryStatsMonthlyRepository.java b/src/main/java/com/doubleo/passservice/domain/stats/repository/EntryStatsMonthlyRepository.java deleted file mode 100644 index 184e7c3..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/repository/EntryStatsMonthlyRepository.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.doubleo.passservice.domain.stats.repository; - -import com.doubleo.passservice.domain.stats.domain.EntryStatsMonthly; -import java.util.List; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; - -public interface EntryStatsMonthlyRepository extends JpaRepository { - @Query( - """ - SELECT e - FROM EntryStatsMonthly e - WHERE e.tenantId = :tenantId - AND (e.year < :currentYear OR (e.year = :currentYear AND e.month < :currentMonth)) - ORDER BY e.year DESC, e.month DESC -""") - List findUpToPreviousMonth( - String tenantId, int currentYear, int currentMonth); -} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/repository/EntryStatsWeeklyRepository.java b/src/main/java/com/doubleo/passservice/domain/stats/repository/EntryStatsWeeklyRepository.java deleted file mode 100644 index bfe4d2a..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/repository/EntryStatsWeeklyRepository.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.doubleo.passservice.domain.stats.repository; - -import com.doubleo.passservice.domain.stats.domain.EntryStatsWeekly; -import java.time.LocalDate; -import java.util.List; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; - -public interface EntryStatsWeeklyRepository extends JpaRepository { - @Query( - """ - SELECT e - FROM EntryStatsWeekly e - WHERE e.tenantId = :tenantId - AND e.startDate >= :startDate - AND e.endDate <= :endDate - ORDER BY e.startDate DESC -""") - List findLastWeeks(String tenantId, LocalDate startDate, LocalDate endDate); -} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/repository/IssuedLogQueryRepository.java b/src/main/java/com/doubleo/passservice/domain/stats/repository/IssuedLogQueryRepository.java deleted file mode 100644 index ba27f9e..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/repository/IssuedLogQueryRepository.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.doubleo.passservice.domain.stats.repository; - -import com.doubleo.passservice.domain.pass.enums.VisitCategory; -import jakarta.persistence.EntityManager; -import jakarta.persistence.TypedQuery; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class IssuedLogQueryRepository { - - private final EntityManager em; - - public long countPassesByDateAndArea( - String tenantId, String areaCode, List categories, LocalDate date) { - - if (categories == null || categories.isEmpty()) return 0L; - - LocalDateTime startOfDay = date.atStartOfDay(); - LocalDateTime endOfDay = date.atTime(LocalTime.MAX); - - String jpql = - """ - SELECT COUNT(il) - FROM IssuedLogArea ila - JOIN ila.issuedLog il - WHERE ila.tenantId = :tenantId - AND ila.areaCode = :areaCode - AND il.visitCategory IN :categories - AND il.startAt <= :endOfDay - AND il.expiredAt >= :startOfDay - """; - - TypedQuery query = em.createQuery(jpql, Long.class); - query.setParameter("tenantId", tenantId); - query.setParameter("areaCode", areaCode); - query.setParameter("categories", categories); - query.setParameter("startOfDay", startOfDay); - query.setParameter("endOfDay", endOfDay); - - return query.getSingleResult(); - } -} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/scheduler/EntryStatsScheduler.java b/src/main/java/com/doubleo/passservice/domain/stats/scheduler/EntryStatsScheduler.java deleted file mode 100644 index e7ea50e..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/scheduler/EntryStatsScheduler.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.doubleo.passservice.domain.stats.scheduler; - -import com.doubleo.passservice.domain.stats.service.StatsBatchService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -@RequiredArgsConstructor -public class EntryStatsScheduler { - - private final StatsBatchService statsBatchService; - - @Scheduled(cron = "0 10 0 * * *") - public void runDailyStatsUpdate() { - log.info("[EntryStatsScheduler] Starting daily entry stats aggregation"); - statsBatchService.updateDailyStats(); - log.info("[EntryStatsScheduler] Completed daily entry stats aggregation"); - } - - @Scheduled(cron = "0 20 0 * * MON") - public void runWeeklyStatsUpdate() { - log.info("[EntryStatsScheduler] Starting weekly entry stats aggregation"); - statsBatchService.updateWeeklyStats(); - log.info("[EntryStatsScheduler] Completed weekly entry stats aggregation"); - } - - @Scheduled(cron = "0 30 0 1 * *") - public void runMonthlyStatsUpdate() { - log.info("[EntryStatsScheduler] Starting monthly entry stats aggregation"); - statsBatchService.updateMonthlyStats(); - log.info("[EntryStatsScheduler] Completed monthly entry stats aggregation"); - } - - @Scheduled(cron = "0 5 0 * * *") - public void runDailyRetainedSnapshotSave() { - log.info("[EntryStatsScheduler] Starting daily retained snapshot save"); - statsBatchService.saveDailyRetainedSnapshot(); - log.info("[EntryStatsScheduler] Completed daily retained snapshot save"); - } -} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/service/PassCountService.java b/src/main/java/com/doubleo/passservice/domain/stats/service/PassCountService.java deleted file mode 100644 index 6b6745a..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/service/PassCountService.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.doubleo.passservice.domain.stats.service; - -import com.doubleo.passservice.domain.stats.dto.request.PassCountInfoRequest; -import com.doubleo.passservice.domain.stats.dto.response.PassCountInfoResponse; -import java.util.List; - -public interface PassCountService { - List getPassCount(PassCountInfoRequest request); -} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/service/PassCountServiceImpl.java b/src/main/java/com/doubleo/passservice/domain/stats/service/PassCountServiceImpl.java deleted file mode 100644 index 2b7a38f..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/service/PassCountServiceImpl.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.doubleo.passservice.domain.stats.service; - -import com.doubleo.passservice.domain.pass.enums.VisitCategory; -import com.doubleo.passservice.domain.stats.dto.request.PassCountInfoRequest; -import com.doubleo.passservice.domain.stats.dto.response.PassCountInfoResponse; -import com.doubleo.passservice.domain.stats.repository.IssuedLogQueryRepository; -import com.doubleo.passservice.global.util.TenantValidator; -import com.doubleo.passservice.grpc.client.AreaClient; -import java.time.LocalDate; -import java.util.*; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class PassCountServiceImpl implements PassCountService { - - private final IssuedLogQueryRepository issuedLogQueryRepository; - private final TenantValidator tenantValidator; - private final AreaClient areaClient; - - @Override - public List getPassCount(PassCountInfoRequest request) { - request.validatePeriod(); - request.validateDateRange(); - - String tenantId = tenantValidator.getTenantId(); - LocalDate startDate = request.resolvedStartDate(); - LocalDate endDate = request.resolvedEndDate(); - List areaCodes = request.areaCodes(); - List categories = request.categories(); - - List result = new ArrayList<>(); - - for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) { - - for (String areaCode : areaCodes) { - long count = - issuedLogQueryRepository.countPassesByDateAndArea( - tenantId, areaCode, categories, date); - - String areaName; - try { - areaName = - areaClient.getAreaFullNameByCode(tenantId, areaCode).getAreaFullName(); - } catch (Exception e) { - areaName = ""; - } - - result.add(new PassCountInfoResponse(date, areaCode, areaName, count)); - } - } - - return result; - } -} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/service/StatsBatchService.java b/src/main/java/com/doubleo/passservice/domain/stats/service/StatsBatchService.java deleted file mode 100644 index a87dae8..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/service/StatsBatchService.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.doubleo.passservice.domain.stats.service; - -public interface StatsBatchService { - - void updateDailyStats(); - - void updateWeeklyStats(); - - void updateMonthlyStats(); - - void saveDailyRetainedSnapshot(); -} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/service/StatsBatchServiceImpl.java b/src/main/java/com/doubleo/passservice/domain/stats/service/StatsBatchServiceImpl.java deleted file mode 100644 index 526e6b9..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/service/StatsBatchServiceImpl.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.doubleo.passservice.domain.stats.service; - -import com.doubleo.passservice.domain.log.repository.BuildingEnterLogRepository; -import com.doubleo.passservice.domain.pass.enums.VisitCategory; -import com.doubleo.passservice.domain.stats.domain.DailyRetainedSnapshot; -import com.doubleo.passservice.domain.stats.domain.EntryStatsDaily; -import com.doubleo.passservice.domain.stats.domain.EntryStatsMonthly; -import com.doubleo.passservice.domain.stats.domain.EntryStatsWeekly; -import com.doubleo.passservice.domain.stats.dto.request.UpdateDailyEntryStatsRequest; -import com.doubleo.passservice.domain.stats.repository.DailyRetainedSnapshotRepository; -import com.doubleo.passservice.domain.stats.repository.EntryStatsDailyRepository; -import com.doubleo.passservice.domain.stats.repository.EntryStatsMonthlyRepository; -import com.doubleo.passservice.domain.stats.repository.EntryStatsWeeklyRepository; -import com.doubleo.passservice.global.util.TenantValidator; -import java.time.DayOfWeek; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.List; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Slf4j -@RequiredArgsConstructor -@Service -@Transactional -public class StatsBatchServiceImpl implements StatsBatchService { - - private final BuildingEnterLogRepository buildingEnterLogRepository; - private final EntryStatsDailyRepository entryStatsDailyRepository; - private final EntryStatsWeeklyRepository entryStatsWeeklyRepository; - private final EntryStatsMonthlyRepository entryStatsMonthlyRepository; - private final DailyRetainedSnapshotRepository dailyRetainedSnapshotRepository; - private final TenantValidator tenantValidator; - - public void updateDailyStats() { - String tenantId = tenantValidator.getTenantId(); - LocalDate targetDate = LocalDate.now().minusDays(1); - LocalDateTime start = targetDate.atStartOfDay(); - LocalDateTime end = start.plusDays(1); - - List dtoList = - buildingEnterLogRepository.countDailyGrouped(start, end, tenantId); - - List stats = - dtoList.stream() - .map( - dto -> - new EntryStatsDaily( - tenantId, - targetDate, - dto.buildingId(), - dto.buildingName(), - dto.visitCategory(), - dto.enteredCount())) - .toList(); - - entryStatsDailyRepository.saveAll(stats); - } - - @Override - public void updateWeeklyStats() { - String tenantId = tenantValidator.getTenantId(); - - LocalDate today = LocalDate.now(); - LocalDate thisMonday = today.with(DayOfWeek.MONDAY); - LocalDate lastMonday = thisMonday.minusWeeks(1); - LocalDate lastSunday = thisMonday.minusDays(1); - - LocalDateTime start = lastMonday.atStartOfDay(); - LocalDateTime end = thisMonday.atStartOfDay(); - - Long count = buildingEnterLogRepository.countEnteredBetween(start, end, tenantId); - - EntryStatsWeekly weekly = new EntryStatsWeekly(tenantId, lastMonday, lastSunday, count); - - entryStatsWeeklyRepository.save(weekly); - } - - @Override - public void updateMonthlyStats() { - String tenantId = tenantValidator.getTenantId(); - - LocalDate today = LocalDate.now(); - LocalDate targetMonth = today.minusMonths(1); - - int year = targetMonth.getYear(); - int month = targetMonth.getMonthValue(); - - LocalDate startDate = LocalDate.of(year, month, 1); - LocalDate endDate = startDate.plusMonths(1); - - Long count = - buildingEnterLogRepository.countEnteredBetween( - startDate.atStartOfDay(), endDate.atStartOfDay(), tenantId); - - EntryStatsMonthly monthly = new EntryStatsMonthly(tenantId, year, month, count); - - entryStatsMonthlyRepository.save(monthly); - } - - @Override - public void saveDailyRetainedSnapshot() { - String tenantId = tenantValidator.getTenantId(); - - LocalDate snapshotDate = LocalDate.now().minusDays(1); - LocalDateTime start = snapshotDate.atStartOfDay(); - LocalDateTime end = start.plusDays(1); - - for (VisitCategory category : VisitCategory.values()) { - int base = - dailyRetainedSnapshotRepository - .findBySnapshotDateAndTenantIdAndVisitCategory( - snapshotDate.minusDays(1), tenantId, category) - .map(DailyRetainedSnapshot::getRetainedCount) - .orElse(0); - - int inCount = - buildingEnterLogRepository.countEnteredByCategory( - start, end, tenantId, category); - int outCount = - buildingEnterLogRepository.countExitedByCategory( - start, end, tenantId, category); - int retained = base + inCount - outCount; - - DailyRetainedSnapshot snapshot = - new DailyRetainedSnapshot(tenantId, snapshotDate, category, retained); - - dailyRetainedSnapshotRepository.save(snapshot); - } - } -} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/service/StatsService.java b/src/main/java/com/doubleo/passservice/domain/stats/service/StatsService.java deleted file mode 100644 index 2b7deba..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/service/StatsService.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.doubleo.passservice.domain.stats.service; - -import com.doubleo.passservice.domain.log.dto.response.HourlyEntryResponse; -import com.doubleo.passservice.domain.stats.dto.response.*; -import java.util.List; - -public interface StatsService { - - List getHourlyEntryList(); - - List getDailyPeriodStatsList(); - - List getLastWeeksStatsList(); - - List getRecentMonthlyStatsList(); - - List getLastWeekCategoryStats(); - - List getLastWeekBuildingStats(); - - List getCurrentRetainedStatus(); -} diff --git a/src/main/java/com/doubleo/passservice/domain/stats/service/StatsServiceImpl.java b/src/main/java/com/doubleo/passservice/domain/stats/service/StatsServiceImpl.java deleted file mode 100644 index b4c07b0..0000000 --- a/src/main/java/com/doubleo/passservice/domain/stats/service/StatsServiceImpl.java +++ /dev/null @@ -1,224 +0,0 @@ -package com.doubleo.passservice.domain.stats.service; - -import com.doubleo.passservice.domain.log.dto.response.HourlyEntryResponse; -import com.doubleo.passservice.domain.log.repository.BuildingEnterLogRepository; -import com.doubleo.passservice.domain.pass.enums.VisitCategory; -import com.doubleo.passservice.domain.stats.domain.DailyRetainedSnapshot; -import com.doubleo.passservice.domain.stats.domain.EntryStatsDaily; -import com.doubleo.passservice.domain.stats.dto.response.*; -import com.doubleo.passservice.domain.stats.repository.DailyRetainedSnapshotRepository; -import com.doubleo.passservice.domain.stats.repository.EntryStatsDailyRepository; -import com.doubleo.passservice.domain.stats.repository.EntryStatsMonthlyRepository; -import com.doubleo.passservice.domain.stats.repository.EntryStatsWeeklyRepository; -import com.doubleo.passservice.global.util.TenantValidator; -import com.doubleo.tenantcontext.TenantContextHolder; -import java.time.DayOfWeek; -import java.time.Duration; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.TextStyle; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.stream.Collectors; -import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@Transactional -@RequiredArgsConstructor -public class StatsServiceImpl implements StatsService { - - private final EntryStatsDailyRepository entryStatsDailyRepository; - private final EntryStatsWeeklyRepository entryStatsWeeklyRepository; - private final EntryStatsMonthlyRepository entryStatsMonthlyRepository; - private final BuildingEnterLogRepository buildingEnterLogRepository; - private final DailyRetainedSnapshotRepository dailyRetainedSnapshotRepository; - private final TenantValidator tenantValidator; - private final RedisTemplate redisTemplate; - - @Override - public List getHourlyEntryList() { - LocalDateTime now = LocalDateTime.now().withMinute(0).withSecond(0).withNano(0); - String tenantId = tenantValidator.getTenantId(); - - LocalDateTime end = now; - LocalDateTime start = end.minusHours(24); - - List hourlyList = new ArrayList<>(); - - for (int i = 0; i < 24; i++) { - LocalDateTime hour = start.plusHours(i); - String hourStr = hour.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH")); - String redisKey = "hourlyEntry:" + tenantId + ":" + hourStr; - - String cachedCount = redisTemplate.opsForValue().get(redisKey); - - int total; - if (cachedCount != null) { - total = Integer.parseInt(cachedCount); - } else { - int count = buildingEnterLogRepository.countInLogsAtHour(hour, hour.plusHours(1)); - redisTemplate - .opsForValue() - .set(redisKey, String.valueOf(count), Duration.ofSeconds(10)); - total = count; - } - - hourlyList.add(new HourlyEntryResponse(hour.getHour(), total, hour)); - } - - return hourlyList; - } - - @Override - public List getDailyPeriodStatsList() { - - String tenantId = tenantValidator.getTenantId(); - - LocalDate today = LocalDate.now(); - LocalDate startDate = today.minusDays(15); - - return entryStatsDailyRepository.findDailyEnteredSumByDate(tenantId, today, startDate); - } - - @Override - public List getLastWeeksStatsList() { - String tenantId = tenantValidator.getTenantId(); - LocalDate thisMonday = LocalDate.now().with(DayOfWeek.MONDAY); - LocalDate endDate = thisMonday.minusDays(1); - LocalDate startDate = thisMonday.minusWeeks(5); - - return entryStatsWeeklyRepository.findLastWeeks(tenantId, startDate, endDate).stream() - .map( - weekly -> - WeeklyStatsInfoListResponse.of( - weekly.getStartDate(), - weekly.getEndDate(), - weekly.getEntered())) - .toList(); - } - - public List getRecentMonthlyStatsList() { - LocalDate now = LocalDate.now(); - return entryStatsMonthlyRepository - .findUpToPreviousMonth( - tenantValidator.getTenantId(), now.getYear(), now.getMonthValue()) - .stream() - .limit(12) - .map( - e -> - MonthlyStatsInfoListResponse.of( - e.getYear(), e.getMonth(), e.getEntered())) - .toList(); - } - - public List getLastWeekCategoryStats() { - String tenantId = tenantValidator.getTenantId(); - LocalDate today = LocalDate.now(); - LocalDate startDate = today.minusDays(7); - LocalDate endDate = today; - - List stats = - entryStatsDailyRepository.findLastWeekStats(tenantId, startDate, endDate); - - return stats.stream() - .collect( - Collectors.groupingBy( - EntryStatsDaily::getDate, - Collectors.groupingBy( - EntryStatsDaily::getVisitCategory, - Collectors.summingLong(EntryStatsDaily::getEntered)))) - .entrySet() - .stream() - .flatMap( - entry -> { - LocalDate date = entry.getKey(); - String day = - date.getDayOfWeek() - .getDisplayName(TextStyle.SHORT, Locale.KOREAN); - return entry.getValue().entrySet().stream() - .map( - catEntry -> - new LastWeekCategoryStatsInfoListResponse( - date, - day, - catEntry.getKey().name(), - catEntry.getValue())); - }) - .collect(Collectors.toList()); - } - - public List getLastWeekBuildingStats() { - String tenantId = tenantValidator.getTenantId(); - LocalDate today = LocalDate.now(); - LocalDate startDate = today.minusDays(7); - LocalDate endDate = today; - - List stats = - entryStatsDailyRepository.findLastWeekStats(tenantId, startDate, endDate); - - return stats.stream() - .collect( - Collectors.groupingBy( - EntryStatsDaily::getDate, - Collectors.groupingBy( - EntryStatsDaily::getBuildingName, - Collectors.summingLong(EntryStatsDaily::getEntered)))) - .entrySet() - .stream() - .flatMap( - dateEntry -> { - LocalDate date = dateEntry.getKey(); - String day = - date.getDayOfWeek() - .getDisplayName(TextStyle.SHORT, Locale.KOREAN); - - return dateEntry.getValue().entrySet().stream() - .map( - buildingEntry -> - new LastWeekBuildingStatsInfoListResponse( - date, - day, - buildingEntry.getKey(), - buildingEntry.getValue())); - }) - .collect(Collectors.toList()); - } - - public List getCurrentRetainedStatus() { - String tenantId = TenantContextHolder.getTenantId(); - LocalDate today = LocalDate.now(); - - List result = new ArrayList<>(); - - for (VisitCategory category : VisitCategory.values()) { - - int base = - dailyRetainedSnapshotRepository - .findBySnapshotDateAndTenantIdAndVisitCategory( - today, tenantId, category) - .map(DailyRetainedSnapshot::getRetainedCount) - .orElse(0); - - String inKey = - String.format("visit:count:%s:%s:%s:IN", tenantId, today, category.name()); - String outKey = - String.format("visit:count:%s:%s:%s:OUT", tenantId, today, category.name()); - - String inVal = redisTemplate.opsForValue().get(inKey); - String outVal = redisTemplate.opsForValue().get(outKey); - - int entered = (inVal != null && !inVal.isBlank()) ? Integer.parseInt(inVal) : 0; - int exited = (outVal != null && !outVal.isBlank()) ? Integer.parseInt(outVal) : 0; - int remaining = base + entered - exited; - - result.add(new RetainedStatusInfoResponse(category, entered, exited, remaining)); - } - - return result; - } -} diff --git a/src/main/java/com/doubleo/passservice/global/util/TimestampUtils.java b/src/main/java/com/doubleo/passservice/global/util/TimestampUtils.java new file mode 100644 index 0000000..aab4c95 --- /dev/null +++ b/src/main/java/com/doubleo/passservice/global/util/TimestampUtils.java @@ -0,0 +1,33 @@ +package com.doubleo.passservice.global.util; + +import com.google.protobuf.Timestamp; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; + +public class TimestampUtils { + + private static final ZoneId DEFAULT_ZONE_ID = ZoneId.systemDefault(); + + private TimestampUtils() {} + + public static LocalDateTime toLocalDateTime(Timestamp timestamp) { + if (timestamp == null) { + return null; + } + return LocalDateTime.ofInstant( + Instant.ofEpochSecond(timestamp.getSeconds(), timestamp.getNanos()), + DEFAULT_ZONE_ID); + } + + public static Timestamp fromLocalDateTime(LocalDateTime localDateTime) { + if (localDateTime == null) { + return null; + } + Instant instant = localDateTime.atZone(DEFAULT_ZONE_ID).toInstant(); + return Timestamp.newBuilder() + .setSeconds(instant.getEpochSecond()) + .setNanos(instant.getNano()) + .build(); + } +} diff --git a/src/main/java/com/doubleo/passservice/grpc/client/LogClient.java b/src/main/java/com/doubleo/passservice/grpc/client/LogClient.java new file mode 100644 index 0000000..f1f7b56 --- /dev/null +++ b/src/main/java/com/doubleo/passservice/grpc/client/LogClient.java @@ -0,0 +1,52 @@ +package com.doubleo.passservice.grpc.client; + +import com.doubleo.logservice.grpc.server.CreateIssuedLogRequest; +import com.doubleo.logservice.grpc.server.CreateIssuedLogResponse; +import com.doubleo.logservice.grpc.server.LogServiceGrpc; +import com.doubleo.passservice.domain.pass.enums.VisitCategory; +import com.doubleo.passservice.global.exception.GrpcExceptionUtil; +import com.doubleo.passservice.global.util.TimestampUtils; +import io.grpc.StatusRuntimeException; +import java.time.LocalDateTime; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import net.devh.boot.grpc.client.inject.GrpcClient; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class LogClient { + + @GrpcClient("log-service") + private LogServiceGrpc.LogServiceBlockingStub blockingStub; + + public CreateIssuedLogResponse createIssuedLog( + String tenantId, + Long memberId, + String memberName, + String memberContact, + Long passId, + LocalDateTime startAt, + LocalDateTime expireAt, + VisitCategory visitCategory, + List areaCodes) { + try { + CreateIssuedLogRequest request = + CreateIssuedLogRequest.newBuilder() + .setTenantId(tenantId) + .setMemberId(memberId) + .setMemberName(memberName) + .setMemberContact(memberContact) + .setPassId(passId) + .setStartAt(TimestampUtils.fromLocalDateTime(startAt)) + .setExpiredAt(TimestampUtils.fromLocalDateTime(expireAt)) + .setVisitCategory(visitCategory.toString()) + .addAllAreaCodes(areaCodes) + .build(); + return blockingStub.createIssuedLog(request); + } catch (StatusRuntimeException e) { + log.error(e.getMessage()); + throw GrpcExceptionUtil.fromStatusRuntimeException(e); + } + } +}