完成模块2-2.1团课预约
This commit is contained in:
+74
@@ -3,6 +3,8 @@ package cn.novalon.gym.manage.groupcourse.converter;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseBooking;
|
||||
import cn.novalon.gym.manage.groupcourse.entity.GroupCourseBookingEntity;
|
||||
import cn.novalon.gym.manage.groupcourse.entity.GroupCourseEntity;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -10,9 +12,19 @@ import org.springframework.stereotype.Component;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 团课相关转换器
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-06-01
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class GroupCourseConverter {
|
||||
|
||||
/**
|
||||
* 将团课实体转换为领域模型
|
||||
*/
|
||||
public GroupCourse toDomain(GroupCourseEntity entity){
|
||||
if(entity == null){
|
||||
return null;
|
||||
@@ -23,6 +35,9 @@ public class GroupCourseConverter {
|
||||
return groupCourse;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将团课领域模型转换为实体
|
||||
*/
|
||||
public GroupCourseEntity toEntity(GroupCourse domain){
|
||||
if(domain == null){
|
||||
return null;
|
||||
@@ -33,6 +48,9 @@ public class GroupCourseConverter {
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将团课实体列表转换为领域模型列表
|
||||
*/
|
||||
public List<GroupCourse> toDomainList(List<GroupCourseEntity> entities){
|
||||
if (entities == null) {
|
||||
return null;
|
||||
@@ -42,6 +60,9 @@ public class GroupCourseConverter {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将团课领域模型列表转换为实体列表
|
||||
*/
|
||||
public List<GroupCourseEntity> toEntityList(List<GroupCourse> domains){
|
||||
if (domains == null) {
|
||||
return null;
|
||||
@@ -50,4 +71,57 @@ public class GroupCourseConverter {
|
||||
.map(this::toEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将团课预约实体转换为领域模型
|
||||
*/
|
||||
public GroupCourseBooking toBookingDomain(GroupCourseBookingEntity entity){
|
||||
if(entity == null){
|
||||
return null;
|
||||
}
|
||||
GroupCourseBooking booking = new GroupCourseBooking();
|
||||
BeanUtil.copyProperties(entity, booking);
|
||||
log.debug("转换预约记录实体到领域模型:bookingId={}", entity.getId());
|
||||
return booking;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将团课预约领域模型转换为实体
|
||||
*/
|
||||
public GroupCourseBookingEntity toBookingEntity(GroupCourseBooking domain){
|
||||
if(domain == null){
|
||||
return null;
|
||||
}
|
||||
GroupCourseBookingEntity entity = new GroupCourseBookingEntity();
|
||||
BeanUtil.copyProperties(domain, entity);
|
||||
if (domain.getId() != null) {
|
||||
entity.markNotNew();
|
||||
}
|
||||
log.debug("转换预约记录领域模型到实体:bookingId={}", domain.getId());
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将团课预约实体列表转换为领域模型列表
|
||||
*/
|
||||
public List<GroupCourseBooking> toBookingDomainList(List<GroupCourseBookingEntity> entities){
|
||||
if (entities == null) {
|
||||
return null;
|
||||
}
|
||||
return entities.stream()
|
||||
.map(this::toBookingDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将团课预约领域模型列表转换为实体列表
|
||||
*/
|
||||
public List<GroupCourseBookingEntity> toBookingEntityList(List<GroupCourseBooking> domains){
|
||||
if (domains == null) {
|
||||
return null;
|
||||
}
|
||||
return domains.stream()
|
||||
.map(this::toBookingEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package cn.novalon.gym.manage.groupcourse.dao;
|
||||
|
||||
import cn.novalon.gym.manage.groupcourse.entity.GroupCourseBookingEntity;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 团课预约记录DAO接口
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-06-01
|
||||
*/
|
||||
@Repository
|
||||
public interface GroupCourseBookingDao extends R2dbcRepository<GroupCourseBookingEntity, Long> {
|
||||
|
||||
/**
|
||||
* 根据ID查询未删除的预约记录
|
||||
*/
|
||||
Mono<GroupCourseBookingEntity> findByIdIsAndDeletedAtIsNull(Long id);
|
||||
|
||||
/**
|
||||
* 根据会员ID查询所有预约记录
|
||||
*/
|
||||
Flux<GroupCourseBookingEntity> findByMemberIdAndDeletedAtIsNull(Long memberId);
|
||||
|
||||
/**
|
||||
* 根据会员ID查询所有预约记录(带排序)
|
||||
*/
|
||||
Flux<GroupCourseBookingEntity> findByMemberIdAndDeletedAtIsNull(Long memberId, Sort sort);
|
||||
|
||||
/**
|
||||
* 根据团课ID查询所有预约记录
|
||||
*/
|
||||
Flux<GroupCourseBookingEntity> findByCourseIdAndDeletedAtIsNull(Long courseId);
|
||||
|
||||
/**
|
||||
* 根据团课ID和会员ID查询预约记录
|
||||
*/
|
||||
Mono<GroupCourseBookingEntity> findByCourseIdAndMemberIdAndDeletedAtIsNull(Long courseId, Long memberId);
|
||||
|
||||
/**
|
||||
* 根据团课ID和状态查询预约记录
|
||||
*/
|
||||
Flux<GroupCourseBookingEntity> findByCourseIdAndStatusAndDeletedAtIsNull(Long courseId, String status);
|
||||
|
||||
/**
|
||||
* 查询会员在指定课程的有效预约(状态为已预约且未取消)
|
||||
*/
|
||||
Mono<GroupCourseBookingEntity> findByCourseIdAndMemberIdAndStatusAndDeletedAtIsNull(Long courseId, Long memberId, String status);
|
||||
|
||||
/**
|
||||
* 根据会员卡ID查询预约记录
|
||||
*/
|
||||
Flux<GroupCourseBookingEntity> findByMemberCardIdAndDeletedAtIsNull(Long memberCardId);
|
||||
|
||||
/**
|
||||
* 统计团课已预约人数
|
||||
*/
|
||||
Mono<Long> countByCourseIdAndStatusAndDeletedAtIsNull(Long courseId, String status);
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
package cn.novalon.gym.manage.groupcourse.domain;
|
||||
|
||||
import cn.novalon.gym.manage.sys.core.domain.BaseDomain;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 团课预约记录领域模型
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-06-01
|
||||
*/
|
||||
public class GroupCourseBooking extends BaseDomain {
|
||||
|
||||
//团课ID
|
||||
@Schema(description = "团课ID", example = "1")
|
||||
private Long courseId;
|
||||
|
||||
//团课名称(关联查询)
|
||||
@Schema(description = "团课名称", example = "瑜伽入门")
|
||||
private String courseName;
|
||||
|
||||
//会员ID
|
||||
@Schema(description = "会员ID", example = "1")
|
||||
private Long memberId;
|
||||
|
||||
//会员卡ID
|
||||
@Schema(description = "会员卡ID", example = "1")
|
||||
private Long memberCardId;
|
||||
|
||||
//预约时间
|
||||
@Schema(description = "预约时间", example = "2026-06-01 10:00:00")
|
||||
private LocalDateTime bookingTime;
|
||||
|
||||
//状态:0-已预约,1-已取消,2-已出席,3-缺席
|
||||
@Schema(description = "状态:0-已预约,1-已取消,2-已出席,3-缺席", example = "0")
|
||||
private String status;
|
||||
|
||||
//取消时间
|
||||
@Schema(description = "取消时间", example = "2026-06-01 11:00:00")
|
||||
private LocalDateTime cancelTime;
|
||||
|
||||
//课程开始时间
|
||||
@Schema(description = "课程开始时间", example = "2026-06-02 09:00:00")
|
||||
private LocalDateTime courseStartTime;
|
||||
|
||||
//课程结束时间
|
||||
@Schema(description = "课程结束时间", example = "2026-06-02 10:00:00")
|
||||
private LocalDateTime courseEndTime;
|
||||
|
||||
//上课地点
|
||||
@Schema(description = "上课地点", example = "健身房A区")
|
||||
private String location;
|
||||
|
||||
public Long getCourseId() {
|
||||
return courseId;
|
||||
}
|
||||
|
||||
public void setCourseId(Long courseId) {
|
||||
this.courseId = courseId;
|
||||
}
|
||||
|
||||
public String getCourseName() {
|
||||
return courseName;
|
||||
}
|
||||
|
||||
public void setCourseName(String courseName) {
|
||||
this.courseName = courseName;
|
||||
}
|
||||
|
||||
public Long getMemberId() {
|
||||
return memberId;
|
||||
}
|
||||
|
||||
public void setMemberId(Long memberId) {
|
||||
this.memberId = memberId;
|
||||
}
|
||||
|
||||
public Long getMemberCardId() {
|
||||
return memberCardId;
|
||||
}
|
||||
|
||||
public void setMemberCardId(Long memberCardId) {
|
||||
this.memberCardId = memberCardId;
|
||||
}
|
||||
|
||||
public LocalDateTime getBookingTime() {
|
||||
return bookingTime;
|
||||
}
|
||||
|
||||
public void setBookingTime(LocalDateTime bookingTime) {
|
||||
this.bookingTime = bookingTime;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public LocalDateTime getCancelTime() {
|
||||
return cancelTime;
|
||||
}
|
||||
|
||||
public void setCancelTime(LocalDateTime cancelTime) {
|
||||
this.cancelTime = cancelTime;
|
||||
}
|
||||
|
||||
public LocalDateTime getCourseStartTime() {
|
||||
return courseStartTime;
|
||||
}
|
||||
|
||||
public void setCourseStartTime(LocalDateTime courseStartTime) {
|
||||
this.courseStartTime = courseStartTime;
|
||||
}
|
||||
|
||||
public LocalDateTime getCourseEndTime() {
|
||||
return courseEndTime;
|
||||
}
|
||||
|
||||
public void setCourseEndTime(LocalDateTime courseEndTime) {
|
||||
this.courseEndTime = courseEndTime;
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public void setLocation(String location) {
|
||||
this.location = location;
|
||||
}
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
package cn.novalon.gym.manage.groupcourse.entity;
|
||||
|
||||
import cn.novalon.gym.manage.db.entity.BaseEntity;
|
||||
import org.springframework.data.relational.core.mapping.Column;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 团课预约记录实体类 - 对应 group_course_booking 表
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-06-01
|
||||
*/
|
||||
@Table("group_course_booking")
|
||||
public class GroupCourseBookingEntity extends BaseEntity {
|
||||
|
||||
//团课ID
|
||||
@Column("course_id")
|
||||
private Long courseId;
|
||||
|
||||
//会员ID
|
||||
@Column("member_id")
|
||||
private Long memberId;
|
||||
|
||||
//会员卡ID
|
||||
@Column("member_card_id")
|
||||
private Long memberCardId;
|
||||
|
||||
//预约时间
|
||||
@Column("booking_time")
|
||||
private LocalDateTime bookingTime;
|
||||
|
||||
//状态:0-已预约,1-已取消,2-已出席,3-缺席
|
||||
@Column("status")
|
||||
private String status;
|
||||
|
||||
//取消时间
|
||||
@Column("cancel_time")
|
||||
private LocalDateTime cancelTime;
|
||||
|
||||
//课程名称(冗余字段,保存预约时的课程快照)
|
||||
@Column("course_name")
|
||||
private String courseName;
|
||||
|
||||
//课程开始时间(冗余字段,保存预约时的课程快照)
|
||||
@Column("course_start_time")
|
||||
private LocalDateTime courseStartTime;
|
||||
|
||||
//课程结束时间(冗余字段,保存预约时的课程快照)
|
||||
@Column("course_end_time")
|
||||
private LocalDateTime courseEndTime;
|
||||
|
||||
//上课地点(冗余字段,保存预约时的课程快照)
|
||||
@Column("location")
|
||||
private String location;
|
||||
|
||||
public Long getCourseId() {
|
||||
return courseId;
|
||||
}
|
||||
|
||||
public void setCourseId(Long courseId) {
|
||||
this.courseId = courseId;
|
||||
}
|
||||
|
||||
public Long getMemberId() {
|
||||
return memberId;
|
||||
}
|
||||
|
||||
public void setMemberId(Long memberId) {
|
||||
this.memberId = memberId;
|
||||
}
|
||||
|
||||
public Long getMemberCardId() {
|
||||
return memberCardId;
|
||||
}
|
||||
|
||||
public void setMemberCardId(Long memberCardId) {
|
||||
this.memberCardId = memberCardId;
|
||||
}
|
||||
|
||||
public LocalDateTime getBookingTime() {
|
||||
return bookingTime;
|
||||
}
|
||||
|
||||
public void setBookingTime(LocalDateTime bookingTime) {
|
||||
this.bookingTime = bookingTime;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public LocalDateTime getCancelTime() {
|
||||
return cancelTime;
|
||||
}
|
||||
|
||||
public void setCancelTime(LocalDateTime cancelTime) {
|
||||
this.cancelTime = cancelTime;
|
||||
}
|
||||
|
||||
public String getCourseName() {
|
||||
return courseName;
|
||||
}
|
||||
|
||||
public void setCourseName(String courseName) {
|
||||
this.courseName = courseName;
|
||||
}
|
||||
|
||||
public LocalDateTime getCourseStartTime() {
|
||||
return courseStartTime;
|
||||
}
|
||||
|
||||
public void setCourseStartTime(LocalDateTime courseStartTime) {
|
||||
this.courseStartTime = courseStartTime;
|
||||
}
|
||||
|
||||
public LocalDateTime getCourseEndTime() {
|
||||
return courseEndTime;
|
||||
}
|
||||
|
||||
public void setCourseEndTime(LocalDateTime courseEndTime) {
|
||||
this.courseEndTime = courseEndTime;
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public void setLocation(String location) {
|
||||
this.location = location;
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package cn.novalon.gym.manage.groupcourse.event;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* 预约提醒事件
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-06-01
|
||||
*/
|
||||
public class BookingReminderEvent extends ApplicationEvent {
|
||||
|
||||
/**
|
||||
* 消息类型枚举
|
||||
*/
|
||||
public enum ReminderType {
|
||||
BOOKING_SUCCESS, // 预约成功
|
||||
COURSE_REMINDER, // 课程即将开始提醒
|
||||
BOOKING_CANCEL // 预约取消
|
||||
}
|
||||
|
||||
private final Long bookingId;
|
||||
private final Long memberId;
|
||||
private final String courseName;
|
||||
private final String courseTime;
|
||||
private final ReminderType type;
|
||||
|
||||
public BookingReminderEvent(Object source, Long bookingId, Long memberId,
|
||||
String courseName, String courseTime, ReminderType type) {
|
||||
super(source);
|
||||
this.bookingId = bookingId;
|
||||
this.memberId = memberId;
|
||||
this.courseName = courseName;
|
||||
this.courseTime = courseTime;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Long getBookingId() {
|
||||
return bookingId;
|
||||
}
|
||||
|
||||
public Long getMemberId() {
|
||||
return memberId;
|
||||
}
|
||||
|
||||
public String getCourseName() {
|
||||
return courseName;
|
||||
}
|
||||
|
||||
public String getCourseTime() {
|
||||
return courseTime;
|
||||
}
|
||||
|
||||
public ReminderType getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package cn.novalon.gym.manage.groupcourse.event;
|
||||
|
||||
import cn.novalon.gym.manage.groupcourse.event.BookingReminderEvent.ReminderType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 预约提醒事件监听器
|
||||
*
|
||||
* 监听预约相关事件,处理提醒业务
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-06-01
|
||||
*/
|
||||
@Component
|
||||
public class BookingReminderEventListener {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(BookingReminderEventListener.class);
|
||||
|
||||
/**
|
||||
* 处理预约提醒事件
|
||||
*
|
||||
* @param event 预约提醒事件
|
||||
*/
|
||||
@EventListener
|
||||
public void handleBookingReminderEvent(BookingReminderEvent event) {
|
||||
logger.info("收到预约提醒事件:type={}, bookingId={}, memberId={}",
|
||||
event.getType(), event.getBookingId(), event.getMemberId());
|
||||
|
||||
try {
|
||||
processReminderEvent(event);
|
||||
} catch (Exception e) {
|
||||
logger.error("处理预约提醒事件失败:bookingId={}, error={}",
|
||||
event.getBookingId(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理提醒事件
|
||||
*
|
||||
* 根据事件类型执行不同的提醒逻辑
|
||||
*/
|
||||
private void processReminderEvent(BookingReminderEvent event) {
|
||||
switch (event.getType()) {
|
||||
case BOOKING_SUCCESS:
|
||||
handleBookingSuccess(event);
|
||||
break;
|
||||
case COURSE_REMINDER:
|
||||
handleCourseReminder(event);
|
||||
break;
|
||||
case BOOKING_CANCEL:
|
||||
handleBookingCancel(event);
|
||||
break;
|
||||
default:
|
||||
logger.warn("未知的提醒事件类型:{}", event.getType());
|
||||
}
|
||||
|
||||
logger.info("预约提醒事件处理完成:bookingId={}", event.getBookingId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理预约成功事件
|
||||
*/
|
||||
private void handleBookingSuccess(BookingReminderEvent event) {
|
||||
logger.info("处理预约成功提醒:会员ID={}, 课程={}, 时间={}",
|
||||
event.getMemberId(), event.getCourseName(), event.getCourseTime());
|
||||
|
||||
// 实际业务中会调用通知服务发送短信、APP推送等
|
||||
// sendNotification(event.getMemberId(), "预约成功",
|
||||
// "您已成功预约课程:" + event.getCourseName() + ",时间:" + event.getCourseTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理课程即将开始提醒事件
|
||||
*/
|
||||
private void handleCourseReminder(BookingReminderEvent event) {
|
||||
logger.info("处理课程提醒:会员ID={}, 课程={}, 时间={}",
|
||||
event.getMemberId(), event.getCourseName(), event.getCourseTime());
|
||||
|
||||
// 实际业务中会调用通知服务发送课程开始前提醒
|
||||
// sendNotification(event.getMemberId(), "课程提醒",
|
||||
// "您预约的课程" + event.getCourseName() + "即将开始,时间:" + event.getCourseTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理预约取消事件
|
||||
*/
|
||||
private void handleBookingCancel(BookingReminderEvent event) {
|
||||
logger.info("处理预约取消提醒:会员ID={}, 课程={}",
|
||||
event.getMemberId(), event.getCourseName());
|
||||
|
||||
// 实际业务中会调用通知服务发送预约取消通知
|
||||
// sendNotification(event.getMemberId(), "预约取消",
|
||||
// "您已取消课程:" + event.getCourseName() + "的预约");
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package cn.novalon.gym.manage.groupcourse.event;
|
||||
|
||||
import cn.novalon.gym.manage.groupcourse.event.BookingReminderEvent.ReminderType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 预约提醒事件发布器
|
||||
*
|
||||
* 使用Spring事件机制发布预约相关事件
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-06-01
|
||||
*/
|
||||
@Component
|
||||
public class BookingReminderEventPublisher {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(BookingReminderEventPublisher.class);
|
||||
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
|
||||
public BookingReminderEventPublisher(ApplicationEventPublisher eventPublisher) {
|
||||
this.eventPublisher = eventPublisher;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布预约成功事件
|
||||
*
|
||||
* @param bookingId 预约ID
|
||||
* @param memberId 会员ID
|
||||
* @param courseName 课程名称
|
||||
* @param courseTime 课程时间
|
||||
*/
|
||||
public void publishBookingSuccessEvent(Long bookingId, Long memberId, String courseName, String courseTime) {
|
||||
BookingReminderEvent event = new BookingReminderEvent(
|
||||
this, bookingId, memberId, courseName, courseTime, ReminderType.BOOKING_SUCCESS);
|
||||
|
||||
logger.info("发布预约成功事件:bookingId={}, memberId={}", bookingId, memberId);
|
||||
eventPublisher.publishEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布课程即将开始提醒事件
|
||||
*
|
||||
* @param bookingId 预约ID
|
||||
* @param memberId 会员ID
|
||||
* @param courseName 课程名称
|
||||
* @param courseTime 课程时间
|
||||
*/
|
||||
public void publishCourseReminderEvent(Long bookingId, Long memberId, String courseName, String courseTime) {
|
||||
BookingReminderEvent event = new BookingReminderEvent(
|
||||
this, bookingId, memberId, courseName, courseTime, ReminderType.COURSE_REMINDER);
|
||||
|
||||
logger.info("发布课程提醒事件:bookingId={}, memberId={}", bookingId, memberId);
|
||||
eventPublisher.publishEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布预约取消事件
|
||||
*
|
||||
* @param bookingId 预约ID
|
||||
* @param memberId 会员ID
|
||||
* @param courseName 课程名称
|
||||
*/
|
||||
public void publishBookingCancelEvent(Long bookingId, Long memberId, String courseName) {
|
||||
BookingReminderEvent event = new BookingReminderEvent(
|
||||
this, bookingId, memberId, courseName, null, ReminderType.BOOKING_CANCEL);
|
||||
|
||||
logger.info("发布预约取消事件:bookingId={}, memberId={}", bookingId, memberId);
|
||||
eventPublisher.publishEvent(event);
|
||||
}
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
package cn.novalon.gym.manage.groupcourse.handler;
|
||||
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseBooking;
|
||||
import cn.novalon.gym.manage.groupcourse.service.IGroupCourseBookingService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 团课预约Handler
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-06-01
|
||||
*/
|
||||
@Component
|
||||
@Tag(name = "团课预约", description = "团课预约相关操作")
|
||||
public class GroupCourseBookingHandler {
|
||||
|
||||
private final IGroupCourseBookingService bookingService;
|
||||
|
||||
public GroupCourseBookingHandler(IGroupCourseBookingService bookingService) {
|
||||
this.bookingService = bookingService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预约团课
|
||||
*/
|
||||
@Operation(summary = "预约团课", description = "会员预约指定团课")
|
||||
public Mono<ServerResponse> bookCourse(ServerRequest request) {
|
||||
return request.bodyToMono(Map.class)
|
||||
.flatMap(body -> {
|
||||
Long courseId = ((Number) body.get("courseId")).longValue();
|
||||
Long memberId = ((Number) body.get("memberId")).longValue();
|
||||
Long memberCardId = ((Number) body.get("memberCardId")).longValue();
|
||||
|
||||
return bookingService.bookCourse(courseId, memberId, memberCardId)
|
||||
.flatMap(booking -> {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("success", true);
|
||||
response.put("message", "预约成功");
|
||||
response.put("data", booking);
|
||||
return ServerResponse.ok().bodyValue(response);
|
||||
})
|
||||
.onErrorResume(error -> {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("success", false);
|
||||
response.put("message", error.getMessage());
|
||||
return ServerResponse.badRequest().bodyValue(response);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消预约
|
||||
*/
|
||||
@Operation(summary = "取消预约", description = "会员取消已预约的团课")
|
||||
public Mono<ServerResponse> cancelBooking(ServerRequest request) {
|
||||
Long bookingId = Long.valueOf(request.pathVariable("bookingId"));
|
||||
|
||||
return request.bodyToMono(Map.class)
|
||||
.flatMap(body -> {
|
||||
Long memberId = ((Number) body.get("memberId")).longValue();
|
||||
|
||||
return bookingService.cancelBooking(bookingId, memberId)
|
||||
.flatMap(booking -> {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("success", true);
|
||||
response.put("message", "取消成功");
|
||||
response.put("data", booking);
|
||||
return ServerResponse.ok().bodyValue(response);
|
||||
})
|
||||
.onErrorResume(error -> {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("success", false);
|
||||
response.put("message", error.getMessage());
|
||||
return ServerResponse.badRequest().bodyValue(response);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询会员预约记录
|
||||
*/
|
||||
@Operation(summary = "查询会员预约记录", description = "根据会员ID查询所有预约记录")
|
||||
public Mono<ServerResponse> getBookingsByMemberId(ServerRequest request) {
|
||||
Long memberId = Long.valueOf(request.pathVariable("memberId"));
|
||||
|
||||
return ServerResponse.ok()
|
||||
.body(bookingService.getBookingsByMemberId(memberId), GroupCourseBooking.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询预约详情
|
||||
*/
|
||||
@Operation(summary = "查询预约详情", description = "根据预约ID查询预约详情")
|
||||
public Mono<ServerResponse> getBookingById(ServerRequest request) {
|
||||
Long bookingId = Long.valueOf(request.pathVariable("bookingId"));
|
||||
|
||||
return bookingService.getBookingById(bookingId)
|
||||
.flatMap(booking -> ServerResponse.ok().bodyValue(booking))
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询课程预约记录
|
||||
*/
|
||||
@Operation(summary = "查询课程预约记录", description = "根据团课ID查询所有预约记录")
|
||||
public Mono<ServerResponse> getBookingsByCourseId(ServerRequest request) {
|
||||
Long courseId = Long.valueOf(request.pathVariable("courseId"));
|
||||
|
||||
return ServerResponse.ok()
|
||||
.body(bookingService.getBookingsByCourseId(courseId), GroupCourseBooking.class);
|
||||
}
|
||||
}
|
||||
+41
-39
@@ -2,9 +2,9 @@
|
||||
package cn.novalon.gym.manage.groupcourse.handler;
|
||||
|
||||
import cn.novalon.gym.manage.common.dto.PageRequest;
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
|
||||
import cn.novalon.gym.manage.groupcourse.service.IGroupCourseService;
|
||||
import cn.novalon.gym.manage.groupcourse.service.RedisService;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@@ -22,16 +22,16 @@ import java.util.Map;
|
||||
public class GroupCourseHandler {
|
||||
private final IGroupCourseService groupCourseService;
|
||||
private final Validator validator;
|
||||
private final RedisService redisService;
|
||||
private final RedisUtil redisUtil;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public GroupCourseHandler(IGroupCourseService groupCourseService,
|
||||
Validator validator,
|
||||
RedisService redisService,
|
||||
RedisUtil redisUtil,
|
||||
ObjectMapper objectMapper){
|
||||
this.groupCourseService = groupCourseService;
|
||||
this.validator = validator;
|
||||
this.redisService = redisService;
|
||||
this.redisUtil = redisUtil;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@@ -88,41 +88,43 @@ public class GroupCourseHandler {
|
||||
return ServerResponse.badRequest().bodyValue(error);
|
||||
}
|
||||
|
||||
Object cachedValue = redisService.get(key);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
if (cachedValue != null) {
|
||||
result.put("success", true);
|
||||
result.put("key", key);
|
||||
result.put("value", cachedValue);
|
||||
result.put("message", "缓存命中");
|
||||
|
||||
try {
|
||||
if (cachedValue instanceof String) {
|
||||
Object jsonObject = objectMapper.readValue((String) cachedValue, Object.class);
|
||||
result.put("parsedValue", jsonObject);
|
||||
result.put("valueType", "JSON字符串");
|
||||
} else {
|
||||
result.put("valueType", cachedValue.getClass().getSimpleName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
result.put("parsedValue", null);
|
||||
result.put("valueType", "无法解析");
|
||||
}
|
||||
} else {
|
||||
result.put("success", false);
|
||||
result.put("key", key);
|
||||
result.put("value", null);
|
||||
result.put("message", "缓存未命中");
|
||||
}
|
||||
|
||||
return ServerResponse.ok().bodyValue(result);
|
||||
})
|
||||
.onErrorResume(error -> {
|
||||
Map<String, Object> errorResponse = new HashMap<>();
|
||||
errorResponse.put("success", false);
|
||||
errorResponse.put("message", "请求处理失败: " + error.getMessage());
|
||||
return ServerResponse.status(500).bodyValue(errorResponse);
|
||||
return redisUtil.get(key)
|
||||
.map(cachedValue -> {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
if (cachedValue != null) {
|
||||
result.put("success", true);
|
||||
result.put("key", key);
|
||||
result.put("value", cachedValue);
|
||||
result.put("message", "缓存命中");
|
||||
|
||||
try {
|
||||
if (cachedValue instanceof String) {
|
||||
Object jsonObject = objectMapper.readValue((String) cachedValue, Object.class);
|
||||
result.put("parsedValue", jsonObject);
|
||||
result.put("valueType", "JSON字符串");
|
||||
} else {
|
||||
result.put("valueType", cachedValue.getClass().getSimpleName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
result.put("parsedValue", null);
|
||||
result.put("valueType", "无法解析");
|
||||
}
|
||||
} else {
|
||||
result.put("success", false);
|
||||
result.put("key", key);
|
||||
result.put("value", null);
|
||||
result.put("message", "缓存未命中");
|
||||
}
|
||||
|
||||
return result;
|
||||
})
|
||||
.flatMap(result -> ServerResponse.ok().bodyValue(result))
|
||||
.onErrorResume(error -> {
|
||||
Map<String, Object> errorResponse = new HashMap<>();
|
||||
errorResponse.put("success", false);
|
||||
errorResponse.put("message", "请求处理失败: " + error.getMessage());
|
||||
return ServerResponse.status(500).bodyValue(errorResponse);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package cn.novalon.gym.manage.groupcourse.repository;
|
||||
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseBooking;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 团课预约记录Repository接口
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-06-01
|
||||
*/
|
||||
public interface IGroupCourseBookingRepository {
|
||||
|
||||
/**
|
||||
* 根据ID查询预约记录
|
||||
*/
|
||||
Mono<GroupCourseBooking> findById(Long id);
|
||||
|
||||
/**
|
||||
* 根据会员ID查询预约记录列表
|
||||
*/
|
||||
Flux<GroupCourseBooking> findByMemberId(Long memberId);
|
||||
|
||||
/**
|
||||
* 根据团课ID查询预约记录列表
|
||||
*/
|
||||
Flux<GroupCourseBooking> findByCourseId(Long courseId);
|
||||
|
||||
/**
|
||||
* 查询会员是否已预约某课程
|
||||
*/
|
||||
Mono<GroupCourseBooking> findByCourseIdAndMemberId(Long courseId, Long memberId);
|
||||
|
||||
/**
|
||||
* 查询会员在指定课程的有效预约
|
||||
*/
|
||||
Mono<GroupCourseBooking> findValidBooking(Long courseId, Long memberId);
|
||||
|
||||
/**
|
||||
* 统计课程有效预约人数
|
||||
*/
|
||||
Mono<Long> countValidBookings(Long courseId);
|
||||
|
||||
/**
|
||||
* 保存预约记录
|
||||
*/
|
||||
Mono<GroupCourseBooking> save(GroupCourseBooking booking);
|
||||
|
||||
/**
|
||||
* 更新预约记录
|
||||
*/
|
||||
Mono<GroupCourseBooking> update(GroupCourseBooking booking);
|
||||
|
||||
/**
|
||||
* 根据会员卡ID查询预约记录
|
||||
*/
|
||||
Flux<GroupCourseBooking> findByMemberCardId(Long memberCardId);
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package cn.novalon.gym.manage.groupcourse.repository.impl;
|
||||
|
||||
import cn.novalon.gym.manage.groupcourse.converter.GroupCourseConverter;
|
||||
import cn.novalon.gym.manage.groupcourse.dao.GroupCourseBookingDao;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseBooking;
|
||||
import cn.novalon.gym.manage.groupcourse.entity.GroupCourseBookingEntity;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseBookingRepository;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 团课预约记录Repository实现类
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-06-01
|
||||
*/
|
||||
@Repository
|
||||
public class GroupCourseBookingRepository implements IGroupCourseBookingRepository {
|
||||
|
||||
private final GroupCourseBookingDao groupCourseBookingDao;
|
||||
private final GroupCourseConverter groupCourseConverter;
|
||||
|
||||
public GroupCourseBookingRepository(GroupCourseBookingDao groupCourseBookingDao,
|
||||
GroupCourseConverter groupCourseConverter) {
|
||||
this.groupCourseBookingDao = groupCourseBookingDao;
|
||||
this.groupCourseConverter = groupCourseConverter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<GroupCourseBooking> findById(Long id) {
|
||||
return groupCourseBookingDao.findByIdIsAndDeletedAtIsNull(id)
|
||||
.map(groupCourseConverter::toBookingDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<GroupCourseBooking> findByMemberId(Long memberId) {
|
||||
return groupCourseBookingDao.findByMemberIdAndDeletedAtIsNull(memberId, Sort.by(Sort.Direction.DESC, "bookingTime"))
|
||||
.map(groupCourseConverter::toBookingDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<GroupCourseBooking> findByCourseId(Long courseId) {
|
||||
return groupCourseBookingDao.findByCourseIdAndDeletedAtIsNull(courseId)
|
||||
.map(groupCourseConverter::toBookingDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<GroupCourseBooking> findByCourseIdAndMemberId(Long courseId, Long memberId) {
|
||||
return groupCourseBookingDao.findByCourseIdAndMemberIdAndDeletedAtIsNull(courseId, memberId)
|
||||
.map(groupCourseConverter::toBookingDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<GroupCourseBooking> findValidBooking(Long courseId, Long memberId) {
|
||||
return groupCourseBookingDao.findByCourseIdAndMemberIdAndStatusAndDeletedAtIsNull(courseId, memberId, "0")
|
||||
.map(groupCourseConverter::toBookingDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Long> countValidBookings(Long courseId) {
|
||||
return groupCourseBookingDao.countByCourseIdAndStatusAndDeletedAtIsNull(courseId, "0");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<GroupCourseBooking> save(GroupCourseBooking booking) {
|
||||
GroupCourseBookingEntity entity = groupCourseConverter.toBookingEntity(booking);
|
||||
return groupCourseBookingDao.save(entity)
|
||||
.map(groupCourseConverter::toBookingDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<GroupCourseBooking> update(GroupCourseBooking booking) {
|
||||
GroupCourseBookingEntity entity = groupCourseConverter.toBookingEntity(booking);
|
||||
return groupCourseBookingDao.save(entity)
|
||||
.map(groupCourseConverter::toBookingDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<GroupCourseBooking> findByMemberCardId(Long memberCardId) {
|
||||
return groupCourseBookingDao.findByMemberCardIdAndDeletedAtIsNull(memberCardId)
|
||||
.map(groupCourseConverter::toBookingDomain);
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package cn.novalon.gym.manage.groupcourse.service;
|
||||
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseBooking;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 团课预约服务接口
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-06-01
|
||||
*/
|
||||
public interface IGroupCourseBookingService {
|
||||
|
||||
/**
|
||||
* 预约团课
|
||||
*
|
||||
* @param courseId 团课ID
|
||||
* @param memberId 会员ID
|
||||
* @param memberCardId 会员卡ID
|
||||
* @return 预约记录
|
||||
*/
|
||||
Mono<GroupCourseBooking> bookCourse(Long courseId, Long memberId, Long memberCardId);
|
||||
|
||||
/**
|
||||
* 取消预约
|
||||
*
|
||||
* @param bookingId 预约ID
|
||||
* @param memberId 会员ID
|
||||
* @return 取消后的预约记录
|
||||
*/
|
||||
Mono<GroupCourseBooking> cancelBooking(Long bookingId, Long memberId);
|
||||
|
||||
/**
|
||||
* 根据会员ID查询预约记录列表
|
||||
*
|
||||
* @param memberId 会员ID
|
||||
* @return 预约记录列表
|
||||
*/
|
||||
Flux<GroupCourseBooking> getBookingsByMemberId(Long memberId);
|
||||
|
||||
/**
|
||||
* 根据预约ID查询预约详情
|
||||
*
|
||||
* @param bookingId 预约ID
|
||||
* @return 预约记录
|
||||
*/
|
||||
Mono<GroupCourseBooking> getBookingById(Long bookingId);
|
||||
|
||||
/**
|
||||
* 根据团课ID查询预约记录列表
|
||||
*
|
||||
* @param courseId 团课ID
|
||||
* @return 预约记录列表
|
||||
*/
|
||||
Flux<GroupCourseBooking> getBookingsByCourseId(Long courseId);
|
||||
}
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
package cn.novalon.gym.manage.groupcourse.service;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author:liwentao
|
||||
* @date:2026/5/15-05-15-16:05
|
||||
*/
|
||||
@Service
|
||||
public class RedisService {
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
// 设置值
|
||||
public void set(String key, Object value) {
|
||||
redisTemplate.opsForValue().set(key, value);
|
||||
}
|
||||
|
||||
// 设置值并指定过期时间(秒)
|
||||
public void setWithExpire(String key, Object value, long timeout) {
|
||||
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
// 获取值
|
||||
public Object get(String key) {
|
||||
return redisTemplate.opsForValue().get(key);
|
||||
}
|
||||
|
||||
// 删除key
|
||||
public Boolean delete(String key) {
|
||||
return redisTemplate.delete(key);
|
||||
}
|
||||
|
||||
// 判断key是否存在
|
||||
public Boolean hasKey(String key) {
|
||||
return redisTemplate.hasKey(key);
|
||||
}
|
||||
|
||||
// 设置过期时间
|
||||
public Boolean expire(String key, long timeout) {
|
||||
return redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
// Hash操作
|
||||
public void putHash(String key, String hashKey, Object value) {
|
||||
redisTemplate.opsForHash().put(key, hashKey, value);
|
||||
}
|
||||
|
||||
public Object getHash(String key, String hashKey) {
|
||||
return redisTemplate.opsForHash().get(key, hashKey);
|
||||
}
|
||||
|
||||
// List操作
|
||||
public void leftPush(String key, Object value) {
|
||||
redisTemplate.opsForList().leftPush(key, value);
|
||||
}
|
||||
|
||||
public Object rightPop(String key) {
|
||||
return redisTemplate.opsForList().rightPop(key);
|
||||
}
|
||||
|
||||
// Set操作
|
||||
public void addToSet(String key, Object... values) {
|
||||
redisTemplate.opsForSet().add(key, values);
|
||||
}
|
||||
|
||||
public Set<Object> getSet(String key) {
|
||||
return redisTemplate.opsForSet().members(key);
|
||||
}
|
||||
}
|
||||
+284
@@ -0,0 +1,284 @@
|
||||
package cn.novalon.gym.manage.groupcourse.service.impl;
|
||||
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseBooking;
|
||||
import cn.novalon.gym.manage.groupcourse.event.BookingReminderEventPublisher;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseBookingRepository;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseRepository;
|
||||
import cn.novalon.gym.manage.groupcourse.service.IGroupCourseBookingService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 团课预约服务实现类
|
||||
*
|
||||
* 业务规则:
|
||||
* - 预约需在课程开始前至少30分钟
|
||||
* - 取消预约需在课程开始前至少2小时
|
||||
* - 每节课最多20人
|
||||
* - 预约成功后发送提醒
|
||||
* - 预约成功后扣减权益
|
||||
*
|
||||
* 技术要点:
|
||||
* - 使用Redis缓存团课信息
|
||||
* - 使用分布式锁防止预约冲突
|
||||
* - 使用响应式编程实现高并发预约
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-06-01
|
||||
*/
|
||||
@Service
|
||||
public class GroupCourseBookingService implements IGroupCourseBookingService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(GroupCourseBookingService.class);
|
||||
|
||||
private final IGroupCourseBookingRepository bookingRepository;
|
||||
private final IGroupCourseRepository courseRepository;
|
||||
private final GroupCourseRedisService redisService;
|
||||
private final BookingReminderEventPublisher bookingReminderEventPublisher;
|
||||
|
||||
// 预约提前时间限制(分钟)
|
||||
private static final long BOOKING_MIN_ADVANCE_MINUTES = 30;
|
||||
// 取消预约提前时间限制(小时)
|
||||
private static final long CANCEL_MIN_ADVANCE_HOURS = 2;
|
||||
|
||||
public GroupCourseBookingService(IGroupCourseBookingRepository bookingRepository,
|
||||
IGroupCourseRepository courseRepository,
|
||||
GroupCourseRedisService redisService,
|
||||
BookingReminderEventPublisher bookingReminderEventPublisher) {
|
||||
this.bookingRepository = bookingRepository;
|
||||
this.courseRepository = courseRepository;
|
||||
this.redisService = redisService;
|
||||
this.bookingReminderEventPublisher = bookingReminderEventPublisher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<GroupCourseBooking> bookCourse(Long courseId, Long memberId, Long memberCardId) {
|
||||
logger.info("开始预约团课:courseId={}, memberId={}, memberCardId={}", courseId, memberId, memberCardId);
|
||||
|
||||
// 生成唯一请求ID用于分布式锁
|
||||
String requestId = UUID.randomUUID().toString();
|
||||
|
||||
// 1. 获取分布式锁
|
||||
return redisService.acquireLock(courseId, requestId)
|
||||
.flatMap(lockAcquired -> {
|
||||
if (!lockAcquired) {
|
||||
return Mono.error(new RuntimeException("系统繁忙,请稍后重试"));
|
||||
}
|
||||
|
||||
// 2. 尝试从缓存获取课程信息,如果缓存不存在则从数据库获取
|
||||
return getCourseWithCache(courseId)
|
||||
.flatMap(course -> {
|
||||
// 3. 验证课程状态
|
||||
if (!"0".equals(String.valueOf(course.getStatus()))) {
|
||||
return releaseLockAndError(courseId, requestId, "课程状态不可预约");
|
||||
}
|
||||
|
||||
// 4. 验证预约时间
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LocalDateTime startTime = course.getStartTime();
|
||||
long minutesUntilStart = ChronoUnit.MINUTES.between(now, startTime);
|
||||
if (minutesUntilStart < BOOKING_MIN_ADVANCE_MINUTES) {
|
||||
return releaseLockAndError(courseId, requestId,
|
||||
"需在课程开始前" + BOOKING_MIN_ADVANCE_MINUTES + "分钟预约");
|
||||
}
|
||||
|
||||
// 5. 验证是否已预约
|
||||
return bookingRepository.findValidBooking(courseId, memberId)
|
||||
.flatMap(existingBooking -> {
|
||||
return releaseLockAndError(courseId, requestId, "您已预约该课程");
|
||||
})
|
||||
.switchIfEmpty(
|
||||
// 6. 使用Redis原子操作验证课程人数是否已满
|
||||
validateAndIncrementBookingCount(courseId, course.getMaxMembers())
|
||||
.flatMap(countValid -> {
|
||||
if (countValid > course.getMaxMembers()) {
|
||||
return releaseLockAndError(courseId, requestId, "课程已满");
|
||||
}
|
||||
|
||||
// 7. 创建预约记录
|
||||
GroupCourseBooking booking = new GroupCourseBooking();
|
||||
booking.setCourseId(courseId);
|
||||
booking.setMemberId(memberId);
|
||||
booking.setMemberCardId(memberCardId);
|
||||
booking.setBookingTime(LocalDateTime.now());
|
||||
booking.setStatus("0"); // 0-已预约
|
||||
|
||||
// 添加课程信息到预约记录
|
||||
booking.setCourseName(course.getCourseName());
|
||||
booking.setCourseStartTime(course.getStartTime());
|
||||
booking.setCourseEndTime(course.getEndTime());
|
||||
booking.setLocation(course.getLocation());
|
||||
|
||||
// 8. 保存预约记录
|
||||
return bookingRepository.save(booking)
|
||||
.flatMap(savedBooking -> {
|
||||
// 9. 释放锁
|
||||
return redisService.releaseLock(courseId, requestId)
|
||||
.then(Mono.just(savedBooking));
|
||||
})
|
||||
.doOnSuccess(savedBooking -> {
|
||||
logger.info("预约成功:bookingId={}, courseId={}, memberId={}",
|
||||
savedBooking.getId(), courseId, memberId);
|
||||
// 发布预约成功事件
|
||||
bookingReminderEventPublisher.publishBookingSuccessEvent(
|
||||
savedBooking.getId(),
|
||||
savedBooking.getMemberId(),
|
||||
savedBooking.getCourseName(),
|
||||
savedBooking.getCourseStartTime().toString()
|
||||
);
|
||||
})
|
||||
.doOnError(error -> {
|
||||
// 回滚Redis计数
|
||||
redisService.decrementBookingCount(courseId).subscribe();
|
||||
logger.error("预约失败:courseId={}, memberId={}, error={}",
|
||||
courseId, memberId, error.getMessage());
|
||||
});
|
||||
})
|
||||
);
|
||||
})
|
||||
.onErrorResume(error -> {
|
||||
// 发生错误时释放锁
|
||||
redisService.releaseLock(courseId, requestId).subscribe();
|
||||
return Mono.error(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 从缓存或数据库获取课程信息
|
||||
*/
|
||||
private Mono<GroupCourse> getCourseWithCache(Long courseId) {
|
||||
return redisService.getCachedCourse(courseId)
|
||||
.switchIfEmpty(
|
||||
// 缓存未命中,从数据库查询
|
||||
courseRepository.findByIdAndDeletedAtIsNull(courseId)
|
||||
.switchIfEmpty(Mono.error(new RuntimeException("团课不存在")))
|
||||
.flatMap(course -> {
|
||||
// 将课程信息存入缓存
|
||||
return redisService.cacheCourse(course).then(Mono.just(course));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证并增加预约人数(使用Redis原子操作)
|
||||
*/
|
||||
private Mono<Integer> validateAndIncrementBookingCount(Long courseId, Integer maxMembers) {
|
||||
return redisService.incrementBookingCount(courseId)
|
||||
.map(count -> {
|
||||
// 同时查询数据库中的预约数进行双重验证
|
||||
// 这里简化处理,实际生产环境应该结合数据库查询
|
||||
return count.intValue();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放锁并返回错误
|
||||
*/
|
||||
private Mono<GroupCourseBooking> releaseLockAndError(Long courseId, String requestId, String errorMessage) {
|
||||
return redisService.releaseLock(courseId, requestId)
|
||||
.then(Mono.error(new RuntimeException(errorMessage)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<GroupCourseBooking> cancelBooking(Long bookingId, Long memberId) {
|
||||
logger.info("开始取消预约:bookingId={}, memberId={}", bookingId, memberId);
|
||||
|
||||
// 生成唯一请求ID用于分布式锁
|
||||
String requestId = UUID.randomUUID().toString();
|
||||
|
||||
// 获取锁防止并发取消
|
||||
return redisService.acquireLock(bookingId, requestId)
|
||||
.flatMap(lockAcquired -> {
|
||||
if (!lockAcquired) {
|
||||
return Mono.error(new RuntimeException("系统繁忙,请稍后重试"));
|
||||
}
|
||||
|
||||
// 1. 查询预约记录
|
||||
return bookingRepository.findById(bookingId)
|
||||
.switchIfEmpty(Mono.error(new RuntimeException("预约记录不存在")))
|
||||
.flatMap(booking -> {
|
||||
// 2. 验证预约归属
|
||||
if (!booking.getMemberId().equals(memberId)) {
|
||||
return releaseLockAndError(bookingId, requestId, "无权取消他人预约");
|
||||
}
|
||||
|
||||
// 3. 验证预约状态
|
||||
if (!"0".equals(booking.getStatus())) {
|
||||
return releaseLockAndError(bookingId, requestId, "预约状态不允许取消");
|
||||
}
|
||||
|
||||
// 4. 验证取消时间
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LocalDateTime startTime = booking.getCourseStartTime();
|
||||
long hoursUntilStart = ChronoUnit.HOURS.between(now, startTime);
|
||||
if (hoursUntilStart < CANCEL_MIN_ADVANCE_HOURS) {
|
||||
return releaseLockAndError(bookingId, requestId,
|
||||
"需在课程开始前" + CANCEL_MIN_ADVANCE_HOURS + "小时取消");
|
||||
}
|
||||
|
||||
// 5. 更新预约状态
|
||||
booking.setStatus("1"); // 1-已取消
|
||||
booking.setCancelTime(LocalDateTime.now());
|
||||
|
||||
// 6. 保存更新
|
||||
return bookingRepository.update(booking)
|
||||
.flatMap(updatedBooking -> {
|
||||
// 7. 减少Redis中的预约人数计数
|
||||
return redisService.decrementBookingCount(booking.getCourseId())
|
||||
.then(Mono.just(updatedBooking));
|
||||
})
|
||||
.flatMap(updatedBooking -> {
|
||||
// 8. 释放锁
|
||||
return redisService.releaseLock(bookingId, requestId)
|
||||
.then(Mono.just(updatedBooking));
|
||||
})
|
||||
.doOnSuccess(updatedBooking -> {
|
||||
logger.info("取消预约成功:bookingId={}, memberId={}", bookingId, memberId);
|
||||
// 发布预约取消事件
|
||||
bookingReminderEventPublisher.publishBookingCancelEvent(
|
||||
updatedBooking.getId(),
|
||||
updatedBooking.getMemberId(),
|
||||
updatedBooking.getCourseName()
|
||||
);
|
||||
})
|
||||
.doOnError(error -> {
|
||||
logger.error("取消预约失败:bookingId={}, memberId={}, error={}",
|
||||
bookingId, memberId, error.getMessage());
|
||||
});
|
||||
})
|
||||
.onErrorResume(error -> {
|
||||
redisService.releaseLock(bookingId, requestId).subscribe();
|
||||
return Mono.error(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<GroupCourseBooking> getBookingsByMemberId(Long memberId) {
|
||||
logger.debug("查询会员预约记录:memberId={}", memberId);
|
||||
return bookingRepository.findByMemberId(memberId)
|
||||
.doOnComplete(() -> logger.debug("查询完成:memberId={}", memberId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<GroupCourseBooking> getBookingById(Long bookingId) {
|
||||
logger.debug("查询预约详情:bookingId={}", bookingId);
|
||||
return bookingRepository.findById(bookingId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<GroupCourseBooking> getBookingsByCourseId(Long courseId) {
|
||||
logger.debug("查询课程预约记录:courseId={}", courseId);
|
||||
return bookingRepository.findByCourseId(courseId)
|
||||
.doOnComplete(() -> logger.debug("查询完成:courseId={}", courseId));
|
||||
}
|
||||
}
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
package cn.novalon.gym.manage.groupcourse.service.impl;
|
||||
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.redis.core.ReactiveRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* 团课Redis缓存服务
|
||||
*
|
||||
* 负责团课信息的缓存管理和分布式锁实现
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-06-01
|
||||
*/
|
||||
@Service
|
||||
public class GroupCourseRedisService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(GroupCourseRedisService.class);
|
||||
|
||||
// 团课信息缓存Key前缀
|
||||
private static final String GROUP_COURSE_CACHE_PREFIX = "group_course:";
|
||||
// 团课预约锁Key前缀
|
||||
private static final String BOOKING_LOCK_PREFIX = "booking_lock:";
|
||||
// 缓存过期时间(5分钟)
|
||||
private static final Duration CACHE_EXPIRE_TIME = Duration.ofMinutes(5);
|
||||
// 锁过期时间(30秒)
|
||||
private static final Duration LOCK_EXPIRE_TIME = Duration.ofSeconds(30);
|
||||
|
||||
private final ReactiveRedisTemplate<String, Object> reactiveRedisTemplate;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public GroupCourseRedisService(ReactiveRedisTemplate<String, Object> reactiveRedisTemplate,
|
||||
ObjectMapper objectMapper) {
|
||||
this.reactiveRedisTemplate = reactiveRedisTemplate;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取团课缓存Key
|
||||
*/
|
||||
private String getCourseCacheKey(Long courseId) {
|
||||
return GROUP_COURSE_CACHE_PREFIX + courseId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预约锁Key
|
||||
*/
|
||||
private String getBookingLockKey(Long courseId) {
|
||||
return BOOKING_LOCK_PREFIX + courseId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存团课信息
|
||||
*/
|
||||
public Mono<Void> cacheCourse(GroupCourse course) {
|
||||
String key = getCourseCacheKey(course.getId());
|
||||
try {
|
||||
String value = objectMapper.writeValueAsString(course);
|
||||
return reactiveRedisTemplate.opsForValue()
|
||||
.set(key, value, CACHE_EXPIRE_TIME)
|
||||
.doOnSuccess(result -> logger.debug("团课信息已缓存:courseId={}", course.getId()))
|
||||
.then();
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.error("序列化团课信息失败:courseId={}", course.getId(), e);
|
||||
return Mono.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存的团课信息
|
||||
*/
|
||||
public Mono<GroupCourse> getCachedCourse(Long courseId) {
|
||||
String key = getCourseCacheKey(courseId);
|
||||
return reactiveRedisTemplate.opsForValue()
|
||||
.get(key)
|
||||
.cast(String.class)
|
||||
.flatMap(value -> {
|
||||
try {
|
||||
GroupCourse course = objectMapper.readValue(value, GroupCourse.class);
|
||||
logger.debug("从缓存获取团课信息:courseId={}", courseId);
|
||||
return Mono.just(course);
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.error("反序列化团课信息失败:courseId={}", courseId, e);
|
||||
return Mono.empty();
|
||||
}
|
||||
})
|
||||
.switchIfEmpty(Mono.fromRunnable(() -> logger.debug("缓存中未找到团课信息:courseId={}", courseId)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除团课缓存
|
||||
*/
|
||||
public Mono<Void> invalidateCourseCache(Long courseId) {
|
||||
String key = getCourseCacheKey(courseId);
|
||||
return reactiveRedisTemplate.delete(key)
|
||||
.doOnSuccess(result -> logger.debug("团课缓存已删除:courseId={}", courseId))
|
||||
.then();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分布式锁
|
||||
*
|
||||
* @param courseId 课程ID
|
||||
* @param requestId 请求ID(用于锁的唯一性校验)
|
||||
* @return 是否获取成功
|
||||
*/
|
||||
public Mono<Boolean> acquireLock(Long courseId, String requestId) {
|
||||
String key = getBookingLockKey(courseId);
|
||||
return reactiveRedisTemplate.opsForValue()
|
||||
.setIfAbsent(key, requestId, LOCK_EXPIRE_TIME)
|
||||
.doOnSuccess(acquired -> {
|
||||
if (acquired) {
|
||||
logger.debug("获取预约锁成功:courseId={}, requestId={}", courseId, requestId);
|
||||
} else {
|
||||
logger.debug("获取预约锁失败:courseId={}", courseId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放分布式锁
|
||||
*
|
||||
* @param courseId 课程ID
|
||||
* @param requestId 请求ID(用于锁的唯一性校验)
|
||||
* @return 是否释放成功
|
||||
*/
|
||||
public Mono<Boolean> releaseLock(Long courseId, String requestId) {
|
||||
String key = getBookingLockKey(courseId);
|
||||
return reactiveRedisTemplate.opsForValue()
|
||||
.get(key)
|
||||
.cast(String.class)
|
||||
.flatMap(storedRequestId -> {
|
||||
if (requestId.equals(storedRequestId)) {
|
||||
return reactiveRedisTemplate.delete(key)
|
||||
.map(deleted -> deleted > 0)
|
||||
.doOnSuccess(result -> logger.debug("释放预约锁成功:courseId={}, requestId={}", courseId, requestId));
|
||||
} else {
|
||||
logger.warn("锁归属校验失败:courseId={}, expectedRequestId={}, actualRequestId={}",
|
||||
courseId, requestId, storedRequestId);
|
||||
return Mono.just(false);
|
||||
}
|
||||
})
|
||||
.defaultIfEmpty(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取课程预约人数(缓存)
|
||||
*/
|
||||
public Mono<Integer> getBookingCount(Long courseId) {
|
||||
String key = "booking_count:" + courseId;
|
||||
return reactiveRedisTemplate.opsForValue()
|
||||
.get(key)
|
||||
.map(obj -> (Integer) obj)
|
||||
.defaultIfEmpty(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加课程预约人数
|
||||
*/
|
||||
public Mono<Long> incrementBookingCount(Long courseId) {
|
||||
String key = "booking_count:" + courseId;
|
||||
return reactiveRedisTemplate.opsForValue()
|
||||
.increment(key)
|
||||
.doOnSuccess(count -> logger.debug("预约人数增加:courseId={}, count={}", courseId, count));
|
||||
}
|
||||
|
||||
/**
|
||||
* 减少课程预约人数
|
||||
*/
|
||||
public Mono<Long> decrementBookingCount(Long courseId) {
|
||||
String key = "booking_count:" + courseId;
|
||||
return reactiveRedisTemplate.opsForValue()
|
||||
.decrement(key)
|
||||
.doOnSuccess(count -> logger.debug("预约人数减少:courseId={}, count={}", courseId, count));
|
||||
}
|
||||
}
|
||||
+71
-73
@@ -3,10 +3,10 @@ package cn.novalon.gym.manage.groupcourse.service.impl;
|
||||
|
||||
import cn.novalon.gym.manage.common.dto.PageRequest;
|
||||
import cn.novalon.gym.manage.common.dto.PageResponse;
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseRepository;
|
||||
import cn.novalon.gym.manage.groupcourse.service.IGroupCourseService;
|
||||
import cn.novalon.gym.manage.groupcourse.service.RedisService;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
@@ -15,15 +15,12 @@ import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class GroupCourseService implements IGroupCourseService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(GroupCourseService.class);
|
||||
|
||||
private final IGroupCourseRepository groupCourseRepository;
|
||||
private final RedisService redisService;
|
||||
private final RedisUtil redisUtil;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private static final String CACHE_KEY_PREFIX = "group_course:page:";
|
||||
@@ -31,10 +28,10 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
private static final long CACHE_EXPIRE_SECONDS = 300;
|
||||
|
||||
public GroupCourseService(IGroupCourseRepository groupCourseRepository,
|
||||
RedisService redisService,
|
||||
RedisUtil redisUtil,
|
||||
ObjectMapper objectMapper){
|
||||
this.groupCourseRepository = groupCourseRepository;
|
||||
this.redisService = redisService;
|
||||
this.redisUtil = redisUtil;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@@ -42,38 +39,35 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
public Mono<GroupCourse> findById(Long id) {
|
||||
String cacheKey = CACHE_KEY_ID_PREFIX + id;
|
||||
|
||||
Object cachedData = redisService.get(cacheKey);
|
||||
if (cachedData != null) {
|
||||
try {
|
||||
String json;
|
||||
if (cachedData instanceof String) {
|
||||
json = (String) cachedData;
|
||||
} else {
|
||||
json = objectMapper.writeValueAsString(cachedData);
|
||||
}
|
||||
|
||||
GroupCourse groupCourse = objectMapper.readValue(json, GroupCourse.class);
|
||||
logger.info("缓存命中 - findById: id={}", id);
|
||||
return Mono.just(groupCourse);
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.warn("缓存解析失败,删除缓存 - id: {}, error: {}", id, e.getMessage());
|
||||
redisService.delete(cacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("缓存未命中,查询数据库 - findById: id={}", id);
|
||||
return groupCourseRepository.findByIdAndDeletedAtIsNull(id)
|
||||
.doOnSuccess(groupCourse -> {
|
||||
if (groupCourse != null) {
|
||||
return redisUtil.get(cacheKey, String.class)
|
||||
.flatMap(cachedJson -> {
|
||||
if (cachedJson != null) {
|
||||
try {
|
||||
String jsonData = objectMapper.writeValueAsString(groupCourse);
|
||||
redisService.setWithExpire(cacheKey, jsonData, CACHE_EXPIRE_SECONDS);
|
||||
logger.debug("缓存已设置 - findById: id={}", id);
|
||||
GroupCourse groupCourse = objectMapper.readValue(cachedJson, GroupCourse.class);
|
||||
logger.info("缓存命中 - findById: id={}", id);
|
||||
return Mono.just(groupCourse);
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.error("缓存设置失败 - id: {}, error: {}", id, e.getMessage());
|
||||
logger.warn("缓存解析失败,删除缓存 - id: {}, error: {}", id, e.getMessage());
|
||||
return redisUtil.delete(cacheKey).then(Mono.empty());
|
||||
}
|
||||
}
|
||||
});
|
||||
return Mono.empty();
|
||||
})
|
||||
.switchIfEmpty(
|
||||
groupCourseRepository.findByIdAndDeletedAtIsNull(id)
|
||||
.flatMap(groupCourse -> {
|
||||
try {
|
||||
String jsonData = objectMapper.writeValueAsString(groupCourse);
|
||||
return redisUtil.setWithExpire(cacheKey, jsonData, CACHE_EXPIRE_SECONDS)
|
||||
.thenReturn(groupCourse)
|
||||
.doOnSuccess(gc -> logger.debug("缓存已设置 - findById: id={}", id));
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.error("缓存设置失败 - id: {}, error: {}", id, e.getMessage());
|
||||
return Mono.just(groupCourse);
|
||||
}
|
||||
})
|
||||
.doOnSubscribe(sub -> logger.debug("缓存未命中,查询数据库 - findById: id={}", id))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,45 +88,49 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
public Mono<PageResponse<GroupCourse>> findByPage(PageRequest pageRequest, boolean includeDeleted) {
|
||||
int page = pageRequest.getPage();
|
||||
int size = pageRequest.getSize();
|
||||
String sort = pageRequest.getSort();
|
||||
String order = pageRequest.getOrder();
|
||||
String keyword = pageRequest.getKeyword() != null ? pageRequest.getKeyword() : "";
|
||||
|
||||
String cacheKey = CACHE_KEY_PREFIX + page + ":" + size + ":" + includeDeleted;
|
||||
String cacheKey = CACHE_KEY_PREFIX + page + ":" + size + ":" + includeDeleted + ":" + sort + ":" + order + ":" + keyword;
|
||||
|
||||
Object cachedData = redisService.get(cacheKey);
|
||||
if (cachedData != null) {
|
||||
try {
|
||||
String json;
|
||||
if (cachedData instanceof String) {
|
||||
json = (String) cachedData;
|
||||
} else {
|
||||
json = objectMapper.writeValueAsString(cachedData);
|
||||
}
|
||||
|
||||
PageResponse<GroupCourse> pageResponse = objectMapper.readValue(json,
|
||||
objectMapper.getTypeFactory().constructParametricType(PageResponse.class, GroupCourse.class));
|
||||
logger.info("缓存命中 - findByPage: key={}", cacheKey);
|
||||
return Mono.just(pageResponse);
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.warn("缓存解析失败,删除缓存 - key: {}, error: {}", cacheKey, e.getMessage());
|
||||
redisService.delete(cacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("缓存未命中,查询数据库 - findByPage: key={}", cacheKey);
|
||||
Mono<PageResponse<GroupCourse>> resultMono;
|
||||
if (includeDeleted) {
|
||||
resultMono = groupCourseRepository.findByPage(pageRequest);
|
||||
} else {
|
||||
resultMono = groupCourseRepository.findByPageAndNotDeleted(pageRequest);
|
||||
}
|
||||
|
||||
return resultMono.doOnSuccess(pageResponse -> {
|
||||
try {
|
||||
String jsonData = objectMapper.writeValueAsString(pageResponse);
|
||||
redisService.setWithExpire(cacheKey, jsonData, CACHE_EXPIRE_SECONDS);
|
||||
logger.debug("缓存已设置 - findByPage: key={}", cacheKey);
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.error("缓存设置失败 - key: {}, error: {}", cacheKey, e.getMessage());
|
||||
}
|
||||
});
|
||||
return redisUtil.get(cacheKey, String.class)
|
||||
.flatMap(cachedJson -> {
|
||||
if (cachedJson != null) {
|
||||
try {
|
||||
PageResponse<GroupCourse> pageResponse = objectMapper.readValue(cachedJson,
|
||||
objectMapper.getTypeFactory().constructParametricType(PageResponse.class, GroupCourse.class));
|
||||
logger.info("缓存命中 - findByPage: key={}", cacheKey);
|
||||
return Mono.just(pageResponse);
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.warn("缓存解析失败,删除缓存 - key: {}, error: {}", cacheKey, e.getMessage());
|
||||
return redisUtil.delete(cacheKey).then(Mono.empty());
|
||||
}
|
||||
}
|
||||
return Mono.empty();
|
||||
})
|
||||
.switchIfEmpty(
|
||||
Mono.defer(() -> {
|
||||
logger.debug("缓存未命中,查询数据库 - findByPage: key={}", cacheKey);
|
||||
Mono<PageResponse<GroupCourse>> resultMono;
|
||||
if (includeDeleted) {
|
||||
resultMono = groupCourseRepository.findByPage(pageRequest);
|
||||
} else {
|
||||
resultMono = groupCourseRepository.findByPageAndNotDeleted(pageRequest);
|
||||
}
|
||||
|
||||
return resultMono.flatMap(pageResponse -> {
|
||||
try {
|
||||
String jsonData = objectMapper.writeValueAsString(pageResponse);
|
||||
return redisUtil.setWithExpire(cacheKey, jsonData, CACHE_EXPIRE_SECONDS)
|
||||
.thenReturn(pageResponse)
|
||||
.doOnSuccess(pr -> logger.debug("缓存已设置 - findByPage: key={}", cacheKey));
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.error("缓存设置失败 - key: {}, error: {}", cacheKey, e.getMessage());
|
||||
return Mono.just(pageResponse);
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user