新增教练端,实现业务闭环:会员登录注册→查询团课→预约团课→扫码签到→教练端开课→记录实际开课时间→教练端结课→记录实际结课时间→后台查询数据
This commit is contained in:
+7
@@ -114,4 +114,11 @@ public interface GroupCourseBookingDao extends R2dbcRepository<GroupCourseBookin
|
||||
@org.springframework.data.r2dbc.repository.Modifying
|
||||
@org.springframework.data.r2dbc.repository.Query("UPDATE group_course_booking SET status = '3', updated_at = :updatedAt WHERE id = :id AND deleted_at IS NULL")
|
||||
Mono<Integer> updateToAbsent(Long id, java.time.LocalDateTime updatedAt);
|
||||
|
||||
/**
|
||||
* 批量更新某课程的预约状态(教练缺席场景)
|
||||
*/
|
||||
@org.springframework.data.r2dbc.repository.Modifying
|
||||
@org.springframework.data.r2dbc.repository.Query("UPDATE group_course_booking SET status = :newStatus, updated_at = NOW() WHERE course_id = :courseId AND status = :oldStatus AND deleted_at IS NULL")
|
||||
Mono<Integer> updateStatusByCourseId(Long courseId, String oldStatus, String newStatus);
|
||||
}
|
||||
+40
-2
@@ -37,9 +37,45 @@ public interface GroupCourseDao extends R2dbcRepository<GroupCourseEntity, Long>
|
||||
@Query("UPDATE group_course SET deleted_at = :deletedAt WHERE id = :id")
|
||||
Mono<Integer> softDelete(Long id, LocalDateTime deletedAt);
|
||||
|
||||
// ---------- 教练相关 SQL 方法 ----------
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE group_course SET status = '2', updated_at = :updatedAt WHERE status = '0' AND end_time <= NOW() AND deleted_at IS NULL")
|
||||
Mono<Integer> completeExpiredCourses(LocalDateTime updatedAt);
|
||||
@Query("UPDATE group_course SET status = :status, actual_start_time = :actualStartTime, updated_at = :updatedAt WHERE id = :id AND deleted_at IS NULL")
|
||||
Mono<Integer> updateStartInfo(Long id, String status, LocalDateTime actualStartTime, LocalDateTime updatedAt);
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE group_course SET status = :status, actual_end_time = :actualEndTime, updated_at = :updatedAt WHERE id = :id AND deleted_at IS NULL")
|
||||
Mono<Integer> updateEndInfo(Long id, String status, LocalDateTime actualEndTime, LocalDateTime updatedAt);
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE group_course SET status = '5', actual_end_time = :actualEndTime, updated_at = :updatedAt WHERE id = :id AND deleted_at IS NULL")
|
||||
Mono<Integer> updateToCoachAbsent(Long id, LocalDateTime actualEndTime, LocalDateTime updatedAt);
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE group_course SET status = '6', actual_end_time = :actualEndTime, updated_at = :updatedAt WHERE id = :id AND deleted_at IS NULL")
|
||||
Mono<Integer> updateToAutoEnded(Long id, LocalDateTime actualEndTime, LocalDateTime updatedAt);
|
||||
|
||||
/**
|
||||
* 查询指定状态且开始时间早于指定时间的课程(用于缺席检查)
|
||||
*/
|
||||
default Flux<GroupCourseEntity> findByStatusAndStartTimeBefore(DatabaseClient databaseClient, String status, LocalDateTime time) {
|
||||
return databaseClient.sql("SELECT * FROM group_course WHERE status = :status AND start_time < :time AND deleted_at IS NULL")
|
||||
.bind("status", status)
|
||||
.bind("time", time)
|
||||
.map((row, meta) -> mapRowToEntity(row))
|
||||
.all();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询指定状态集合且结束时间早于指定时间的课程(用于自动结课检查)
|
||||
*/
|
||||
default Flux<GroupCourseEntity> findByStatusInAndEndTimeBefore(DatabaseClient databaseClient, String[] statuses, LocalDateTime time) {
|
||||
return databaseClient.sql("SELECT * FROM group_course WHERE status = ANY(:statuses::varchar[]) AND end_time < :time AND deleted_at IS NULL")
|
||||
.bind("statuses", statuses)
|
||||
.bind("time", time)
|
||||
.map((row, meta) -> mapRowToEntity(row))
|
||||
.all();
|
||||
}
|
||||
|
||||
Flux<GroupCourseEntity> findByCourseTypeAndDeletedAtIsNull(Long courseType);
|
||||
|
||||
@@ -328,6 +364,8 @@ public interface GroupCourseDao extends R2dbcRepository<GroupCourseEntity, Long>
|
||||
entity.setCoverImage(row.get("cover_image", String.class));
|
||||
entity.setDescription(row.get("description", String.class));
|
||||
entity.setStoredValueAmount(row.get("stored_value_amount", java.math.BigDecimal.class));
|
||||
entity.setActualStartTime(row.get("actual_start_time", LocalDateTime.class));
|
||||
entity.setActualEndTime(row.get("actual_end_time", LocalDateTime.class));
|
||||
entity.setQrCodePath(row.get("qr_code_path", String.class));
|
||||
entity.setCreateBy(row.get("create_by", String.class));
|
||||
entity.setUpdateBy(row.get("update_by", String.class));
|
||||
|
||||
+24
@@ -40,6 +40,14 @@ public class GroupCourse extends BaseDomain{
|
||||
@Schema(description = "课程状态", example = "0")
|
||||
private Long status;
|
||||
|
||||
//实际开课时间
|
||||
@Schema(description = "实际开课时间")
|
||||
private LocalDateTime actualStartTime;
|
||||
|
||||
//实际结课时间
|
||||
@Schema(description = "实际结课时间")
|
||||
private LocalDateTime actualEndTime;
|
||||
|
||||
//上课地点
|
||||
@Schema(description = "上课地点", example = "龙泉驿区幸福路")
|
||||
private String location;
|
||||
@@ -124,6 +132,22 @@ public class GroupCourse extends BaseDomain{
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public LocalDateTime getActualStartTime() {
|
||||
return actualStartTime;
|
||||
}
|
||||
|
||||
public void setActualStartTime(LocalDateTime actualStartTime) {
|
||||
this.actualStartTime = actualStartTime;
|
||||
}
|
||||
|
||||
public LocalDateTime getActualEndTime() {
|
||||
return actualEndTime;
|
||||
}
|
||||
|
||||
public void setActualEndTime(LocalDateTime actualEndTime) {
|
||||
this.actualEndTime = actualEndTime;
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
+25
-1
@@ -38,7 +38,15 @@ public class GroupCourseEntity extends BaseEntity {
|
||||
@Column("current_members")
|
||||
private Integer currentMembers;
|
||||
|
||||
//课程状态:0-正常,1-已取消,2-已结束
|
||||
//实际开课时间
|
||||
@Column("actual_start_time")
|
||||
private LocalDateTime actualStartTime;
|
||||
|
||||
//实际结课时间
|
||||
@Column("actual_end_time")
|
||||
private LocalDateTime actualEndTime;
|
||||
|
||||
//课程状态:0-正常,1-已取消,2-已结束,3-进行中,5-教练缺席,6-自动结束,7-教练迟到
|
||||
@Column("status")
|
||||
private Long status;
|
||||
|
||||
@@ -158,6 +166,22 @@ public class GroupCourseEntity extends BaseEntity {
|
||||
this.storedValueAmount = storedValueAmount;
|
||||
}
|
||||
|
||||
public LocalDateTime getActualStartTime() {
|
||||
return actualStartTime;
|
||||
}
|
||||
|
||||
public void setActualStartTime(LocalDateTime actualStartTime) {
|
||||
this.actualStartTime = actualStartTime;
|
||||
}
|
||||
|
||||
public LocalDateTime getActualEndTime() {
|
||||
return actualEndTime;
|
||||
}
|
||||
|
||||
public void setActualEndTime(LocalDateTime actualEndTime) {
|
||||
this.actualEndTime = actualEndTime;
|
||||
}
|
||||
|
||||
public String getQrCodePath() {
|
||||
return qrCodePath;
|
||||
}
|
||||
|
||||
+4
-1
@@ -11,7 +11,10 @@ public enum CourseStatus {
|
||||
NORMAL(0L, "正常"),
|
||||
CANCELLED(1L, "已取消"),
|
||||
ENDED(2L, "已结束"),
|
||||
IN_PROGRESS(3L, "进行中");
|
||||
IN_PROGRESS(3L, "进行中"),
|
||||
COACH_ABSENT(5L, "教练缺席"),
|
||||
AUTO_ENDED(6L, "自动结束"),
|
||||
COACH_LATE(7L, "教练迟到");
|
||||
|
||||
private final Long value;
|
||||
private final String desc;
|
||||
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
package cn.novalon.gym.manage.groupcourse.scheduler;
|
||||
|
||||
import cn.novalon.gym.manage.groupcourse.dao.GroupCourseDao;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 团课过期状态自动更新定时任务
|
||||
*
|
||||
* 功能:定期检查已过期的团课(end_time <= NOW()),自动将 status 从 0 更新为 2(已结束)
|
||||
*
|
||||
* @date 2026-06-24
|
||||
*/
|
||||
@Component
|
||||
public class GroupCourseExpireScheduler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(GroupCourseExpireScheduler.class);
|
||||
|
||||
private final GroupCourseDao groupCourseDao;
|
||||
|
||||
public GroupCourseExpireScheduler(GroupCourseDao groupCourseDao) {
|
||||
this.groupCourseDao = groupCourseDao;
|
||||
}
|
||||
|
||||
/**
|
||||
* 每分钟检查一次,将已过期但状态仍为 0 的团课标记为已结束(status = 2)
|
||||
*/
|
||||
@Scheduled(fixedRate = 60000)
|
||||
public void completeExpiredCourses() {
|
||||
logger.debug("定时任务开始检查已过期团课,更新状态为已结束");
|
||||
|
||||
groupCourseDao.completeExpiredCourses(LocalDateTime.now())
|
||||
.subscribe(
|
||||
count -> {
|
||||
if (count > 0) {
|
||||
logger.info("定时任务完成,更新了 {} 条过期团课状态为已结束", count);
|
||||
}
|
||||
},
|
||||
error -> logger.error("过期团课状态更新定时任务执行失败:{}", error.getMessage(), error)
|
||||
);
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -741,7 +741,9 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
private Mono<Void> clearCache() {
|
||||
return redisUtil.deleteByPattern(CACHE_KEY_PREFIX + "*")
|
||||
.then(redisUtil.deleteByPattern(CACHE_KEY_ID_PREFIX + "*"))
|
||||
.then(redisUtil.deleteByPattern(CACHE_KEY_DETAIL_PREFIX + "*")).then();
|
||||
.then(redisUtil.deleteByPattern(CACHE_KEY_DETAIL_PREFIX + "*"))
|
||||
.then(redisUtil.deleteByPattern("datacount:statistics:*"))
|
||||
.then();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+10
@@ -21,6 +21,8 @@ public class GroupCourseVO {
|
||||
private Long courseType;
|
||||
private LocalDateTime startTime;
|
||||
private LocalDateTime endTime;
|
||||
private LocalDateTime actualStartTime;
|
||||
private LocalDateTime actualEndTime;
|
||||
private Integer maxMembers;
|
||||
private Integer currentMembers;
|
||||
private Long status;
|
||||
@@ -48,6 +50,8 @@ public class GroupCourseVO {
|
||||
vo.setCourseType(course.getCourseType());
|
||||
vo.setStartTime(course.getStartTime());
|
||||
vo.setEndTime(course.getEndTime());
|
||||
vo.setActualStartTime(course.getActualStartTime());
|
||||
vo.setActualEndTime(course.getActualEndTime());
|
||||
vo.setMaxMembers(course.getMaxMembers());
|
||||
vo.setCurrentMembers(course.getCurrentMembers());
|
||||
vo.setStatus(course.getStatus());
|
||||
@@ -86,6 +90,12 @@ public class GroupCourseVO {
|
||||
public LocalDateTime getEndTime() { return endTime; }
|
||||
public void setEndTime(LocalDateTime endTime) { this.endTime = endTime; }
|
||||
|
||||
public LocalDateTime getActualStartTime() { return actualStartTime; }
|
||||
public void setActualStartTime(LocalDateTime actualStartTime) { this.actualStartTime = actualStartTime; }
|
||||
|
||||
public LocalDateTime getActualEndTime() { return actualEndTime; }
|
||||
public void setActualEndTime(LocalDateTime actualEndTime) { this.actualEndTime = actualEndTime; }
|
||||
|
||||
public Integer getMaxMembers() { return maxMembers; }
|
||||
public void setMaxMembers(Integer maxMembers) { this.maxMembers = maxMembers; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user