dev add statistic files protected
Test Workflow / test (push) Successful in 2s

This commit is contained in:
vladp
2026-04-16 00:28:27 +07:00
parent be62dd458a
commit 42a5368003
8 changed files with 224 additions and 1 deletions
@@ -43,7 +43,8 @@ public class HandlerConfig {
StatisticTariffInfoFileHandler statisticTariffInfoFileHandler, StatisticTariffInfoFileHandler statisticTariffInfoFileHandler,
StatisticUserDynamicHandler statisticUserDynamicHandler, StatisticUserDynamicHandler statisticUserDynamicHandler,
StatisticProtectedFilesHandler statisticProtectedFilesHandler, StatisticProtectedFilesHandler statisticProtectedFilesHandler,
StatisticViolationHandler statisticViolationHandler StatisticViolationHandler statisticViolationHandler,
StatisticSubscriberHandler statisticSubscriberHandler
) { ) {
Map<Integer, RequestHandler> map = new HashMap<>(); Map<Integer, RequestHandler> map = new HashMap<>();
@@ -79,6 +80,7 @@ public class HandlerConfig {
map.put(30021, statisticUserDynamicHandler); map.put(30021, statisticUserDynamicHandler);
map.put(30022, statisticProtectedFilesHandler); map.put(30022, statisticProtectedFilesHandler);
map.put(30023, statisticViolationHandler); map.put(30023, statisticViolationHandler);
map.put(30024, statisticSubscriberHandler);
return map; return map;
} }
@@ -0,0 +1,7 @@
package ru.soune.nocopy.dto.statistic;
import lombok.Data;
@Data
public class SubscriberStatisticRequest {
}
@@ -0,0 +1,42 @@
package ru.soune.nocopy.dto.statistic;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SubscriberStatisticResponse {
@JsonProperty("monthly")
private PeriodStatistics monthly;
@JsonProperty("yearly")
private PeriodStatistics yearly;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class PeriodStatistics {
@JsonProperty("current_active_subscriptions")
private Long currentActiveSubscriptions;
@JsonProperty("first_time_subscriptions")
private Long firstTimeSubscriptions;
@JsonProperty("renewals")
private Long renewals;
@JsonProperty("upgrades")
private Long upgrades;
@JsonProperty("downgrades")
private Long downgrades;
}
}
@@ -20,6 +20,7 @@ public class TariffInfo {
private String id; private String id;
@Column(name = "status", nullable = false) @Column(name = "status", nullable = false)
@Enumerated(EnumType.STRING)
private TariffStatus status; private TariffStatus status;
@Column(name = "start_tariff") @Column(name = "start_tariff")
@@ -0,0 +1,29 @@
package ru.soune.nocopy.handler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.statistic.SubscriberStatisticResponse;
import ru.soune.nocopy.service.statistic.SubscriberStatisticService;
@Component
@RequiredArgsConstructor
@Slf4j
public class StatisticSubscriberHandler implements RequestHandler {
private final SubscriberStatisticService statisticService;
@Override
public BaseResponse handle(BaseRequest request) throws Exception {
SubscriberStatisticResponse statistics = statisticService.getSubscriberStatistics();
return new BaseResponse(
request.getMsgId(),
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
statistics
);
}
}
@@ -50,4 +50,20 @@ public interface TariffInfoRepository extends JpaRepository<TariffInfo, String>
ORDER BY user_count DESC ORDER BY user_count DESC
""", nativeQuery = true) """, nativeQuery = true)
List<Object[]> getTariffStatisticsNative(@Param("activeOnly") boolean activeOnly); List<Object[]> getTariffStatisticsNative(@Param("activeOnly") boolean activeOnly);
@Query("""
SELECT COUNT(ti.id)
FROM TariffInfo ti
WHERE ti.endTariff >= CURRENT_TIMESTAMP
AND (:term IS NULL OR ti.tariff.tariffTerm = :term)
""")
Long getCurrentActiveSubscriptions(@Param("term") String term);
@Query("""
SELECT ti
FROM TariffInfo ti
WHERE (:term IS NULL OR ti.tariff.tariffTerm = :term)
ORDER BY ti.startTariff
""")
List<TariffInfo> getAllTariffInfosForAnalysis(@Param("term") String term);
} }
@@ -3,10 +3,12 @@ package ru.soune.nocopy.repository;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import ru.soune.nocopy.entity.tarif.TariffInfo; import ru.soune.nocopy.entity.tarif.TariffInfo;
import ru.soune.nocopy.entity.user.User; import ru.soune.nocopy.entity.user.User;
import java.util.List; import java.util.List;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> { public interface UserRepository extends JpaRepository<User, Long> {
User findByEmail(String email); User findByEmail(String email);
@@ -40,4 +42,7 @@ public interface UserRepository extends JpaRepository<User, Long> {
AND created_at < DATE_TRUNC('month', NOW()) AND created_at < DATE_TRUNC('month', NOW())
""", nativeQuery = true) """, nativeQuery = true)
Long getNewUsersPreviousMonth(); Long getNewUsersPreviousMonth();
@Query("SELECT u FROM User u WHERE u.personalTariffInfo.id = :tariffInfoId")
Optional<User> findByPersonalTariffInfoId(@Param("tariffInfoId") String tariffInfoId);
} }
@@ -0,0 +1,121 @@
package ru.soune.nocopy.service.statistic;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import ru.soune.nocopy.dto.statistic.SubscriberStatisticResponse;
import ru.soune.nocopy.entity.tarif.TariffInfo;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.repository.TariffInfoRepository;
import ru.soune.nocopy.repository.UserRepository;
import java.util.*;
@Service
@RequiredArgsConstructor
@Slf4j
public class SubscriberStatisticService {
private final TariffInfoRepository tariffInfoRepository;
private final UserRepository userRepository;
private static final String TERM_MONTHLY = "MONTHLY";
private static final String TERM_YEARLY = "YEAR";
public SubscriberStatisticResponse getSubscriberStatistics() {
return SubscriberStatisticResponse.builder()
.monthly(buildPeriodStatistics(TERM_MONTHLY))
.yearly(buildPeriodStatistics(TERM_YEARLY))
.build();
}
private SubscriberStatisticResponse.PeriodStatistics buildPeriodStatistics(String term) {
Long currentActive = tariffInfoRepository.getCurrentActiveSubscriptions(term);
List<TariffInfo> allTariffs = tariffInfoRepository.getAllTariffInfosForAnalysis(term);
Map<Long, List<TariffInfo>> tariffsByUser = groupTariffsByUser(allTariffs);
Long firstTime = countFirstTimeSubscriptions(tariffsByUser);
Long renewals = countRenewals(tariffsByUser);
UpDownGradeResult gradeResult = analyzeUpgradesAndDowngrades(tariffsByUser);
return SubscriberStatisticResponse.PeriodStatistics.builder()
.currentActiveSubscriptions(currentActive != null ? currentActive : 0L)
.firstTimeSubscriptions(firstTime)
.renewals(renewals)
.upgrades(gradeResult.upgrades)
.downgrades(gradeResult.downgrades)
.build();
}
private Map<Long, List<TariffInfo>> groupTariffsByUser(List<TariffInfo> tariffs) {
Map<Long, List<TariffInfo>> result = new HashMap<>();
for (TariffInfo ti : tariffs) {
Optional<User> userOpt = userRepository.findByPersonalTariffInfoId(ti.getId());
if (userOpt.isPresent()) {
Long userId = userOpt.get().getId();
result.computeIfAbsent(userId, k -> new ArrayList<>()).add(ti);
}
}
return result;
}
private Long countFirstTimeSubscriptions(Map<Long, List<TariffInfo>> tariffsByUser) {
return (long) tariffsByUser.size();
}
private Long countRenewals(Map<Long, List<TariffInfo>> tariffsByUser) {
return tariffsByUser.values().stream()
.filter(list -> list.size() > 1)
.mapToLong(list -> list.size() - 1)
.sum();
}
private UpDownGradeResult analyzeUpgradesAndDowngrades(Map<Long, List<TariffInfo>> tariffsByUser) {
long upgrades = 0L;
long downgrades = 0L;
for (List<TariffInfo> userTariffs : tariffsByUser.values()) {
if (userTariffs.size() < 2) {
continue;
}
userTariffs.sort(Comparator.comparing(TariffInfo::getStartTariff,
Comparator.nullsLast(Comparator.naturalOrder())));
for (int i = 1; i < userTariffs.size(); i++) {
TariffInfo previous = userTariffs.get(i - 1);
TariffInfo current = userTariffs.get(i);
if (previous.getTariff() == null || current.getTariff() == null) {
continue;
}
double previousPrice = previous.getTariff().getPrice();
double currentPrice = current.getTariff().getPrice();
if (currentPrice > previousPrice) {
upgrades++;
} else if (currentPrice < previousPrice) {
downgrades++;
}
}
}
return new UpDownGradeResult(upgrades, downgrades);
}
private static class UpDownGradeResult {
final Long upgrades;
final Long downgrades;
UpDownGradeResult(Long upgrades, Long downgrades) {
this.upgrades = upgrades;
this.downgrades = downgrades;
}
}
}