package com.tcm.training.admin.statistics.service;
|
|
import com.tcm.training.admin.statistics.mapper.UserStatisticsMapper;
|
import com.tcm.training.admin.statistics.row.UserStatisticsActiveTrendRow;
|
import com.tcm.training.admin.statistics.row.UserStatisticsCompositionRow;
|
import com.tcm.training.admin.statistics.row.UserStatisticsOverviewRow;
|
import com.tcm.training.admin.statistics.row.UserStatisticsRegisterTrendRow;
|
import com.tcm.training.admin.statistics.vo.UserStatisticsMemberCompositionVO;
|
import com.tcm.training.admin.statistics.vo.UserStatisticsOverviewVO;
|
import com.tcm.training.admin.statistics.vo.UserStatisticsTrendItemVO;
|
import com.tcm.training.admin.statistics.vo.UserStatisticsVO;
|
import org.springframework.stereotype.Service;
|
|
import java.math.BigDecimal;
|
import java.math.RoundingMode;
|
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
import java.time.LocalTime;
|
import java.util.ArrayList;
|
import java.util.HashMap;
|
import java.util.List;
|
import java.util.Map;
|
|
@Service
|
public class UserStatisticsAppService {
|
|
private static final String FILTER_ALL = "ALL";
|
private static final String FILTER_NORMAL = "NORMAL";
|
private static final String FILTER_MEMBER = "MEMBER";
|
|
private final UserStatisticsMapper userStatisticsMapper;
|
|
public UserStatisticsAppService(UserStatisticsMapper userStatisticsMapper) {
|
this.userStatisticsMapper = userStatisticsMapper;
|
}
|
|
public UserStatisticsVO statistics(String type, LocalDate dateFrom, LocalDate dateTo) {
|
DateRange range = normalizeRange(dateFrom, dateTo);
|
String normalizedType = normalizeType(type);
|
String mapperUserType = resolveUserType(normalizedType);
|
LocalDateTime startTime = range.getStart().atStartOfDay();
|
LocalDateTime endTime = LocalDateTime.of(range.getEnd(), LocalTime.MAX);
|
|
UserStatisticsOverviewRow overviewRow = userStatisticsMapper.selectOverview(mapperUserType, startTime, endTime);
|
List<UserStatisticsRegisterTrendRow> registerRows = userStatisticsMapper.selectRegisterTrend(mapperUserType, startTime, endTime);
|
List<UserStatisticsActiveTrendRow> activeRows = userStatisticsMapper.selectActiveTrend(mapperUserType, startTime, endTime);
|
UserStatisticsCompositionRow compositionRow = userStatisticsMapper.selectMemberComposition(mapperUserType, startTime, endTime);
|
|
UserStatisticsVO result = new UserStatisticsVO();
|
result.setOverview(buildOverview(overviewRow));
|
result.setTrend(buildTrend(registerRows, activeRows, range));
|
result.setMemberComposition(buildComposition(compositionRow));
|
return result;
|
}
|
|
private UserStatisticsOverviewVO buildOverview(UserStatisticsOverviewRow row) {
|
UserStatisticsOverviewVO overview = new UserStatisticsOverviewVO();
|
overview.setRegisteredUserCount(nvl(row == null ? null : row.getRegisteredUserCount()));
|
overview.setDoctorRegisteredCount(nvl(row == null ? null : row.getDoctorRegisteredCount()));
|
overview.setMemberOpenCount(nvl(row == null ? null : row.getMemberOpenCount()));
|
overview.setCoursePurchaseCount(nvl(row == null ? null : row.getCoursePurchaseCount()));
|
overview.setLiveWatchClickCount(nvl(row == null ? null : row.getLiveWatchClickCount()));
|
overview.setUserActivityRate(buildActivityRate(
|
nvl(row == null ? null : row.getActiveEnterUserCount()),
|
nvl(row == null ? null : row.getTotalUserCount())
|
));
|
return overview;
|
}
|
|
private UserStatisticsMemberCompositionVO buildComposition(UserStatisticsCompositionRow row) {
|
UserStatisticsMemberCompositionVO composition = new UserStatisticsMemberCompositionVO();
|
composition.setMonthMemberPurchaseCount(nvl(row == null ? null : row.getMonthMemberPurchaseCount()));
|
composition.setQuarterMemberPurchaseCount(nvl(row == null ? null : row.getQuarterMemberPurchaseCount()));
|
composition.setCourseMemberUserCount(nvl(row == null ? null : row.getCourseMemberUserCount()));
|
return composition;
|
}
|
|
private List<UserStatisticsTrendItemVO> buildTrend(List<UserStatisticsRegisterTrendRow> registerRows,
|
List<UserStatisticsActiveTrendRow> activeRows,
|
DateRange range) {
|
Map<LocalDate, UserStatisticsRegisterTrendRow> registerRowMap = new HashMap<LocalDate, UserStatisticsRegisterTrendRow>();
|
for (UserStatisticsRegisterTrendRow row : registerRows) {
|
registerRowMap.put(row.getStatDate(), row);
|
}
|
|
Map<LocalDate, UserStatisticsActiveTrendRow> activeRowMap = new HashMap<LocalDate, UserStatisticsActiveTrendRow>();
|
for (UserStatisticsActiveTrendRow row : activeRows) {
|
activeRowMap.put(row.getStatDate(), row);
|
}
|
|
List<UserStatisticsTrendItemVO> result = new ArrayList<UserStatisticsTrendItemVO>();
|
LocalDate cursor = range.getStart();
|
while (!cursor.isAfter(range.getEnd())) {
|
UserStatisticsRegisterTrendRow registerRow = registerRowMap.get(cursor);
|
UserStatisticsActiveTrendRow activeRow = activeRowMap.get(cursor);
|
|
UserStatisticsTrendItemVO item = new UserStatisticsTrendItemVO();
|
item.setDate(cursor.toString());
|
item.setRegisteredUserCount(nvl(registerRow == null ? null : registerRow.getRegisteredUserCount()));
|
item.setDoctorRegisteredCount(nvl(registerRow == null ? null : registerRow.getDoctorRegisteredCount()));
|
item.setActiveEnterUserCount(nvl(activeRow == null ? null : activeRow.getActiveEnterUserCount()));
|
result.add(item);
|
cursor = cursor.plusDays(1);
|
}
|
return result;
|
}
|
|
private BigDecimal buildActivityRate(Long activeEnterUserCount, Long totalUserCount) {
|
if (totalUserCount == null || totalUserCount <= 0) {
|
return BigDecimal.ZERO;
|
}
|
return BigDecimal.valueOf(activeEnterUserCount == null ? 0L : activeEnterUserCount)
|
.multiply(BigDecimal.valueOf(100))
|
.divide(BigDecimal.valueOf(totalUserCount), 2, RoundingMode.HALF_UP);
|
}
|
|
private String normalizeType(String type) {
|
String normalized = trimToNull(type);
|
if (normalized == null) {
|
return FILTER_ALL;
|
}
|
if (FILTER_ALL.equals(normalized) || FILTER_NORMAL.equals(normalized) || FILTER_MEMBER.equals(normalized)) {
|
return normalized;
|
}
|
throw new IllegalArgumentException("用户类型不合法");
|
}
|
|
private String resolveUserType(String normalizedType) {
|
if (FILTER_ALL.equals(normalizedType)) {
|
return null;
|
}
|
return normalizedType;
|
}
|
|
private DateRange normalizeRange(LocalDate dateFrom, LocalDate dateTo) {
|
LocalDate start = dateFrom;
|
LocalDate end = dateTo;
|
if (start == null && end == null) {
|
start = LocalDate.now();
|
end = start;
|
} else if (start == null) {
|
start = end;
|
} else if (end == null) {
|
end = start;
|
}
|
if (start.isAfter(end)) {
|
throw new IllegalArgumentException("开始日期不能晚于结束日期");
|
}
|
return new DateRange(start, end);
|
}
|
|
private Long nvl(Long value) {
|
return value == null ? 0L : value;
|
}
|
|
private String trimToNull(String value) {
|
if (value == null) {
|
return null;
|
}
|
String trimmed = value.trim();
|
return trimmed.isEmpty() ? null : trimmed;
|
}
|
|
private static class DateRange {
|
private final LocalDate start;
|
private final LocalDate end;
|
|
private DateRange(LocalDate start, LocalDate end) {
|
this.start = start;
|
this.end = end;
|
}
|
|
private LocalDate getStart() {
|
return start;
|
}
|
|
private LocalDate getEnd() {
|
return end;
|
}
|
}
|
}
|