新增答辩用后台管理系统
This commit is contained in:
+43
@@ -194,4 +194,47 @@ public class CheckInHandler {
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(Map.of("code", 200, "message", "success", "data", stats)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员查询所有签到记录(支持排序)
|
||||
*
|
||||
* GET /api/checkIn/admin/records
|
||||
*/
|
||||
public Mono<ServerResponse> getAllSignInRecords(ServerRequest request) {
|
||||
String startDateStr = request.queryParam("startDate").orElse(null);
|
||||
String endDateStr = request.queryParam("endDate").orElse(null);
|
||||
String sortBy = request.queryParam("sortBy").orElse("signInTime");
|
||||
String sortOrder = request.queryParam("sortOrder").orElse("desc");
|
||||
|
||||
LocalDate startDate = startDateStr != null ? LocalDate.parse(startDateStr, DATE_FORMATTER) : LocalDate.now().minusDays(30);
|
||||
LocalDate endDate = endDateStr != null ? LocalDate.parse(endDateStr, DATE_FORMATTER) : LocalDate.now();
|
||||
|
||||
log.info("管理员查询所有签到记录, startDate: {}, endDate: {}, sortBy: {}, sortOrder: {}", startDate, endDate, sortBy, sortOrder);
|
||||
|
||||
return checkService.getAllSignInRecords(startDate, endDate, sortBy, sortOrder)
|
||||
.collectList()
|
||||
.flatMap(records -> ServerResponse.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(Map.of("code", 200, "message", "success", "data", records)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员查询签到统计(不限会员)
|
||||
*
|
||||
* GET /api/checkIn/admin/statistics
|
||||
*/
|
||||
public Mono<ServerResponse> getAllSignInStatistics(ServerRequest request) {
|
||||
String startDateStr = request.queryParam("startDate").orElse(null);
|
||||
String endDateStr = request.queryParam("endDate").orElse(null);
|
||||
|
||||
LocalDate startDate = startDateStr != null ? LocalDate.parse(startDateStr, DATE_FORMATTER) : LocalDate.now().minusDays(30);
|
||||
LocalDate endDate = endDateStr != null ? LocalDate.parse(endDateStr, DATE_FORMATTER) : LocalDate.now();
|
||||
|
||||
log.info("管理员查询签到统计, startDate: {}, endDate: {}", startDate, endDate);
|
||||
|
||||
return checkService.getAllSignInStats(startDate, endDate)
|
||||
.flatMap(stats -> ServerResponse.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(Map.of("code", 200, "message", "success", "data", stats)));
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -57,6 +57,12 @@ public interface SignInRecordRepository extends R2dbcRepository<SignInRecord, Lo
|
||||
@Query("SELECT * FROM sign_in_record WHERE sign_in_time >= :startTime AND sign_in_time <= :endTime AND is_delete = false ORDER BY sign_in_time DESC")
|
||||
Flux<SignInRecord> findByTimeRange(LocalDateTime startTime, LocalDateTime endTime);
|
||||
|
||||
/**
|
||||
* 根据时间范围查询签到记录(支持动态排序)
|
||||
*/
|
||||
@Query("SELECT * FROM sign_in_record WHERE sign_in_time >= :startTime AND sign_in_time <= :endTime AND is_delete = false ORDER BY sign_in_time DESC")
|
||||
Flux<SignInRecord> findByTimeRangeSorted(LocalDateTime startTime, LocalDateTime endTime);
|
||||
|
||||
/**
|
||||
* 统计会员在时间范围内的签到次数
|
||||
*/
|
||||
|
||||
+16
@@ -78,4 +78,20 @@ public interface ICheckInService {
|
||||
* @return 签到统计VO
|
||||
*/
|
||||
Mono<SignInStatsVO> getDailySignInStats(LocalDate date);
|
||||
|
||||
/**
|
||||
* 管理员查询所有签到记录(支持排序)
|
||||
*
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @param sortBy 排序字段
|
||||
* @param sortOrder 排序方向
|
||||
* @return 签到记录列表(含会员姓名、卡类型)
|
||||
*/
|
||||
Flux<SignInRecordVO> getAllSignInRecords(LocalDate startTime, LocalDate endTime, String sortBy, String sortOrder);
|
||||
|
||||
/**
|
||||
* 管理员查询签到统计(不限会员)
|
||||
*/
|
||||
Mono<SignInStatsVO> getAllSignInStats(LocalDate startTime, LocalDate endTime);
|
||||
}
|
||||
|
||||
+81
-1
@@ -16,11 +16,13 @@ import cn.novalon.gym.manage.checkIn.websocket.MyWebSocketHandler;
|
||||
import cn.novalon.gym.manage.common.constant.RedisKeyConstants;
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.groupcourse.service.IGroupCourseBookingService;
|
||||
import cn.novalon.gym.manage.member.entity.Member;
|
||||
import cn.novalon.gym.manage.member.entity.MemberCard;
|
||||
import cn.novalon.gym.manage.member.entity.MemberCardRecord;
|
||||
import cn.novalon.gym.manage.member.enums.MemberCardType;
|
||||
import cn.novalon.gym.manage.member.repository.MemberCardRecordRepository;
|
||||
import cn.novalon.gym.manage.member.repository.IMemberRepository;
|
||||
import cn.novalon.gym.manage.member.repository.MemberCardRepository;
|
||||
import cn.novalon.gym.manage.member.repository.MemberCardRecordRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -47,6 +49,7 @@ public class CheckServiceImpl implements ICheckInService {
|
||||
private final MemberCardRepository memberCardRepository;
|
||||
private final SignInRecordRepository signInRecordRepository;
|
||||
private final IGroupCourseBookingService groupCourseBookingService;
|
||||
private final IMemberRepository memberRepository;
|
||||
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
@@ -430,6 +433,83 @@ public class CheckServiceImpl implements ICheckInService {
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<SignInRecordVO> getAllSignInRecords(LocalDate startTime, LocalDate endTime, String sortBy, String sortOrder) {
|
||||
LocalDateTime start = startTime.atStartOfDay();
|
||||
LocalDateTime end = endTime.atTime(LocalTime.MAX);
|
||||
|
||||
Flux<SignInRecord> recordFlux = signInRecordRepository.findByTimeRangeSorted(start, end);
|
||||
|
||||
return recordFlux
|
||||
.flatMap(record -> {
|
||||
// fetch member name
|
||||
Mono<String> memberNameMono = memberRepository.findById(record.getMemberId())
|
||||
.map(Member::getNickname)
|
||||
.defaultIfEmpty("未知");
|
||||
// fetch card type name
|
||||
Mono<String> cardTypeMono = record.getMemberCardId() != null
|
||||
? memberCardRepository.findByMemberCardIdAndDeletedAtIsNull(record.getMemberCardId())
|
||||
.map(MemberCard::getMemberCardName)
|
||||
.defaultIfEmpty("未知")
|
||||
: Mono.just("-");
|
||||
return Mono.zip(memberNameMono, cardTypeMono)
|
||||
.map(tuple -> {
|
||||
SignInRecordVO vo = convertToVO(record);
|
||||
vo.setMemberName(tuple.getT1());
|
||||
vo.setMemberCardType(tuple.getT2());
|
||||
return vo;
|
||||
});
|
||||
})
|
||||
.collectList()
|
||||
.flatMapMany(list -> {
|
||||
// in-memory sort
|
||||
boolean asc = "asc".equalsIgnoreCase(sortOrder);
|
||||
java.util.Comparator<SignInRecordVO> comparator;
|
||||
switch (sortBy != null ? sortBy : "signInTime") {
|
||||
case "id":
|
||||
comparator = java.util.Comparator.comparing(SignInRecordVO::getId, java.util.Comparator.nullsLast(Long::compareTo));
|
||||
break;
|
||||
case "memberName":
|
||||
comparator = java.util.Comparator.comparing(SignInRecordVO::getMemberName, java.util.Comparator.nullsLast(String::compareTo));
|
||||
break;
|
||||
case "signInTime":
|
||||
default:
|
||||
comparator = java.util.Comparator.comparing(SignInRecordVO::getSignInTime, java.util.Comparator.nullsLast(java.time.LocalDateTime::compareTo));
|
||||
break;
|
||||
}
|
||||
if (!asc) {
|
||||
comparator = comparator.reversed();
|
||||
}
|
||||
list.sort(comparator);
|
||||
return Flux.fromIterable(list);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<SignInStatsVO> getAllSignInStats(LocalDate startTime, LocalDate endTime) {
|
||||
LocalDateTime start = startTime.atStartOfDay();
|
||||
LocalDateTime end = endTime.atTime(LocalTime.MAX);
|
||||
|
||||
return Mono.zip(
|
||||
(Object[] results) -> {
|
||||
Long total = (Long) results[0];
|
||||
Long success = (Long) results[1];
|
||||
Long members = (Long) results[2];
|
||||
SignInStatsVO stats = new SignInStatsVO();
|
||||
stats.setTotalCount(total);
|
||||
stats.setSuccessCount(success);
|
||||
stats.setStartDate(startTime);
|
||||
stats.setEndDate(endTime);
|
||||
stats.setUniqueMemberCount(members);
|
||||
stats.setSuccessRate(total > 0 ? (double) success / total * 100.0 : 0.0);
|
||||
return stats;
|
||||
},
|
||||
signInRecordRepository.countByTimeRange(start, end),
|
||||
signInRecordRepository.countSuccessByTimeRange(start, end),
|
||||
signInRecordRepository.countDistinctMembersByTimeRange(start, end)
|
||||
);
|
||||
}
|
||||
|
||||
private long getSecondsUntilEndOfDay() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LocalDateTime endOfDay = now.toLocalDate().atTime(23, 59, 59);
|
||||
|
||||
+10
@@ -59,6 +59,16 @@ public class SignInRecordVO {
|
||||
*/
|
||||
private String source;
|
||||
|
||||
/**
|
||||
* 会员姓名(关联查询)
|
||||
*/
|
||||
private String memberName;
|
||||
|
||||
/**
|
||||
* 会员卡类型名称(关联查询)
|
||||
*/
|
||||
private String memberCardType;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user