新增团课多条件查询
This commit was merged in pull request #28.
This commit is contained in:
+185
-2
@@ -1,16 +1,19 @@
|
||||
|
||||
package cn.novalon.gym.manage.groupcourse.dao;
|
||||
|
||||
import cn.novalon.gym.manage.groupcourse.dto.GroupCourseQueryDto;
|
||||
import cn.novalon.gym.manage.groupcourse.entity.GroupCourseEntity;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.r2dbc.repository.Modifying;
|
||||
import org.springframework.data.r2dbc.repository.Query;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface GroupCourseDao extends R2dbcRepository<GroupCourseEntity, Long> {
|
||||
@@ -38,4 +41,184 @@ public interface GroupCourseDao extends R2dbcRepository<GroupCourseEntity, Long>
|
||||
Mono<Integer> softDelete(Long id, LocalDateTime deletedAt);
|
||||
|
||||
Flux<GroupCourseEntity> findByCourseTypeAndDeletedAtIsNull(Long courseType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 多条件查询团课(使用 DatabaseClient 构建动态 SQL)
|
||||
*/
|
||||
default Flux<GroupCourseEntity> searchGroupCourses(DatabaseClient databaseClient, GroupCourseQueryDto query) {
|
||||
StringBuilder sql = new StringBuilder("SELECT * FROM group_course WHERE deleted_at IS NULL");
|
||||
List<String> conditions = new ArrayList<>();
|
||||
|
||||
// 默认不查询不可预约的团课(仅查询 status = '0')
|
||||
conditions.add("status = '0'");
|
||||
|
||||
// 1. 团课名称模糊查询
|
||||
if (query.getCourseName() != null && !query.getCourseName().isEmpty()) {
|
||||
conditions.add("course_name ILIKE :courseName");
|
||||
}
|
||||
|
||||
// 2. 基于团课类型查询
|
||||
if (query.getCourseType() != null) {
|
||||
conditions.add("course_type = :courseType");
|
||||
}
|
||||
|
||||
// 3. 基于日期时间段查询
|
||||
if (query.getStartDate() != null) {
|
||||
conditions.add("start_time >= :startDate");
|
||||
}
|
||||
if (query.getEndDate() != null) {
|
||||
conditions.add("start_time <= :endDate");
|
||||
}
|
||||
|
||||
// 4. 基于早晨/下午/夜晚时间段查询
|
||||
if (query.getTimePeriod() != null && !query.getTimePeriod().isEmpty()) {
|
||||
switch (query.getTimePeriod().toLowerCase()) {
|
||||
case "morning":
|
||||
conditions.add("EXTRACT(HOUR FROM start_time) >= 6 AND EXTRACT(HOUR FROM start_time) < 12");
|
||||
break;
|
||||
case "afternoon":
|
||||
conditions.add("EXTRACT(HOUR FROM start_time) >= 12 AND EXTRACT(HOUR FROM start_time) < 18");
|
||||
break;
|
||||
case "evening":
|
||||
conditions.add("EXTRACT(HOUR FROM start_time) >= 18 AND EXTRACT(HOUR FROM start_time) < 24");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sql.append(" AND ").append(String.join(" AND ", conditions));
|
||||
|
||||
// 5. 价格排序 / 6. 剩余名额最多排序
|
||||
boolean hasPriceSort = query.getPriceSort() != null && !query.getPriceSort().isEmpty();
|
||||
boolean hasRemainingMost = query.getRemainingMost() != null && query.getRemainingMost();
|
||||
|
||||
if (hasPriceSort || hasRemainingMost) {
|
||||
sql.append(" ORDER BY");
|
||||
List<String> orderClauses = new ArrayList<>();
|
||||
|
||||
if (hasRemainingMost) {
|
||||
orderClauses.add(" (max_members - current_members) DESC");
|
||||
}
|
||||
|
||||
if (hasPriceSort) {
|
||||
if ("asc".equalsIgnoreCase(query.getPriceSort())) {
|
||||
orderClauses.add(" stored_value_amount ASC");
|
||||
} else if ("desc".equalsIgnoreCase(query.getPriceSort())) {
|
||||
orderClauses.add(" stored_value_amount DESC");
|
||||
}
|
||||
}
|
||||
|
||||
sql.append(String.join(",", orderClauses));
|
||||
} else {
|
||||
sql.append(" ORDER BY start_time ASC");
|
||||
}
|
||||
|
||||
// 分页
|
||||
int page = query.getPage() != null ? query.getPage() : 0;
|
||||
int size = query.getSize() != null ? query.getSize() : 10;
|
||||
if (size < 1) size = 10;
|
||||
if (size > 100) size = 100;
|
||||
int offset = page * size;
|
||||
sql.append(" LIMIT :limit OFFSET :offset");
|
||||
|
||||
DatabaseClient.GenericExecuteSpec spec = databaseClient.sql(sql.toString());
|
||||
|
||||
if (query.getCourseName() != null && !query.getCourseName().isEmpty()) {
|
||||
spec = spec.bind("courseName", "%" + query.getCourseName() + "%");
|
||||
}
|
||||
if (query.getCourseType() != null) {
|
||||
spec = spec.bind("courseType", query.getCourseType());
|
||||
}
|
||||
if (query.getStartDate() != null) {
|
||||
spec = spec.bind("startDate", query.getStartDate());
|
||||
}
|
||||
if (query.getEndDate() != null) {
|
||||
spec = spec.bind("endDate", query.getEndDate());
|
||||
}
|
||||
spec = spec.bind("limit", size);
|
||||
spec = spec.bind("offset", offset);
|
||||
|
||||
return spec.map((row, meta) -> {
|
||||
GroupCourseEntity entity = new GroupCourseEntity();
|
||||
entity.setId(row.get("id", Long.class));
|
||||
entity.setCourseName(row.get("course_name", String.class));
|
||||
entity.setCoachId(row.get("coach_id", Long.class));
|
||||
entity.setCourseType(row.get("course_type", Long.class));
|
||||
entity.setStartTime(row.get("start_time", LocalDateTime.class));
|
||||
entity.setEndTime(row.get("end_time", LocalDateTime.class));
|
||||
entity.setMaxMembers(row.get("max_members", Integer.class));
|
||||
entity.setCurrentMembers(row.get("current_members", Integer.class));
|
||||
String statusStr = row.get("status", String.class);
|
||||
entity.setStatus(statusStr != null ? Long.parseLong(statusStr) : null);
|
||||
entity.setLocation(row.get("location", String.class));
|
||||
entity.setCoverImage(row.get("cover_image", String.class));
|
||||
entity.setDescription(row.get("description", String.class));
|
||||
entity.setPointCardAmount(row.get("point_card_amount", Integer.class));
|
||||
entity.setStoredValueAmount(row.get("stored_value_amount", java.math.BigDecimal.class));
|
||||
entity.setCreateBy(row.get("create_by", String.class));
|
||||
entity.setUpdateBy(row.get("update_by", String.class));
|
||||
entity.setCreatedAt(row.get("created_at", LocalDateTime.class));
|
||||
entity.setUpdatedAt(row.get("updated_at", LocalDateTime.class));
|
||||
entity.setDeletedAt(row.get("deleted_at", LocalDateTime.class));
|
||||
return entity;
|
||||
}).all();
|
||||
}
|
||||
|
||||
/**
|
||||
* 多条件查询团课总数
|
||||
*/
|
||||
default Mono<Long> countSearchGroupCourses(DatabaseClient databaseClient, GroupCourseQueryDto query) {
|
||||
StringBuilder sql = new StringBuilder("SELECT COUNT(*) FROM group_course WHERE deleted_at IS NULL");
|
||||
List<String> conditions = new ArrayList<>();
|
||||
|
||||
conditions.add("status = '0'");
|
||||
|
||||
if (query.getCourseName() != null && !query.getCourseName().isEmpty()) {
|
||||
conditions.add("course_name ILIKE :courseName");
|
||||
}
|
||||
if (query.getCourseType() != null) {
|
||||
conditions.add("course_type = :courseType");
|
||||
}
|
||||
if (query.getStartDate() != null) {
|
||||
conditions.add("start_time >= :startDate");
|
||||
}
|
||||
if (query.getEndDate() != null) {
|
||||
conditions.add("start_time <= :endDate");
|
||||
}
|
||||
if (query.getTimePeriod() != null && !query.getTimePeriod().isEmpty()) {
|
||||
switch (query.getTimePeriod().toLowerCase()) {
|
||||
case "morning":
|
||||
conditions.add("EXTRACT(HOUR FROM start_time) >= 6 AND EXTRACT(HOUR FROM start_time) < 12");
|
||||
break;
|
||||
case "afternoon":
|
||||
conditions.add("EXTRACT(HOUR FROM start_time) >= 12 AND EXTRACT(HOUR FROM start_time) < 18");
|
||||
break;
|
||||
case "evening":
|
||||
conditions.add("EXTRACT(HOUR FROM start_time) >= 18 AND EXTRACT(HOUR FROM start_time) < 24");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sql.append(" AND ").append(String.join(" AND ", conditions));
|
||||
|
||||
DatabaseClient.GenericExecuteSpec spec = databaseClient.sql(sql.toString());
|
||||
|
||||
if (query.getCourseName() != null && !query.getCourseName().isEmpty()) {
|
||||
spec = spec.bind("courseName", "%" + query.getCourseName() + "%");
|
||||
}
|
||||
if (query.getCourseType() != null) {
|
||||
spec = spec.bind("courseType", query.getCourseType());
|
||||
}
|
||||
if (query.getStartDate() != null) {
|
||||
spec = spec.bind("startDate", query.getStartDate());
|
||||
}
|
||||
if (query.getEndDate() != null) {
|
||||
spec = spec.bind("endDate", query.getEndDate());
|
||||
}
|
||||
|
||||
return spec.map((row, meta) -> row.get(0, Long.class)).one();
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
package cn.novalon.gym.manage.groupcourse.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 团课多条件查询请求DTO
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-06-14
|
||||
*/
|
||||
@Schema(description = "团课多条件查询参数")
|
||||
public class GroupCourseQueryDto {
|
||||
|
||||
@Schema(description = "课程名称(模糊查询)", example = "瑜伽")
|
||||
private String courseName;
|
||||
|
||||
@Schema(description = "课程类型ID", example = "1")
|
||||
private Long courseType;
|
||||
|
||||
@Schema(description = "查询开始日期", example = "2026-06-01T00:00:00")
|
||||
private LocalDateTime startDate;
|
||||
|
||||
@Schema(description = "查询结束日期", example = "2026-06-30T23:59:59")
|
||||
private LocalDateTime endDate;
|
||||
|
||||
@Schema(description = "时间段:morning-早晨(6:00-12:00), afternoon-下午(12:00-18:00), evening-夜晚(18:00-24:00)", example = "morning")
|
||||
private String timePeriod;
|
||||
|
||||
@Schema(description = "价格排序:asc-从低到高, desc-从高到低", example = "asc")
|
||||
private String priceSort;
|
||||
|
||||
@Schema(description = "按剩余名额最多排序", example = "true")
|
||||
private Boolean remainingMost;
|
||||
|
||||
@Schema(description = "页码", example = "0")
|
||||
private Integer page = 0;
|
||||
|
||||
@Schema(description = "每页大小", example = "10")
|
||||
private Integer size = 10;
|
||||
|
||||
// ===== Getters and Setters =====
|
||||
|
||||
public String getCourseName() {
|
||||
return courseName;
|
||||
}
|
||||
|
||||
public void setCourseName(String courseName) {
|
||||
this.courseName = courseName;
|
||||
}
|
||||
|
||||
public Long getCourseType() {
|
||||
return courseType;
|
||||
}
|
||||
|
||||
public void setCourseType(Long courseType) {
|
||||
this.courseType = courseType;
|
||||
}
|
||||
|
||||
public LocalDateTime getStartDate() {
|
||||
return startDate;
|
||||
}
|
||||
|
||||
public void setStartDate(LocalDateTime startDate) {
|
||||
this.startDate = startDate;
|
||||
}
|
||||
|
||||
public LocalDateTime getEndDate() {
|
||||
return endDate;
|
||||
}
|
||||
|
||||
public void setEndDate(LocalDateTime endDate) {
|
||||
this.endDate = endDate;
|
||||
}
|
||||
|
||||
public String getTimePeriod() {
|
||||
return timePeriod;
|
||||
}
|
||||
|
||||
public void setTimePeriod(String timePeriod) {
|
||||
this.timePeriod = timePeriod;
|
||||
}
|
||||
|
||||
public String getPriceSort() {
|
||||
return priceSort;
|
||||
}
|
||||
|
||||
public void setPriceSort(String priceSort) {
|
||||
this.priceSort = priceSort;
|
||||
}
|
||||
|
||||
public Boolean getRemainingMost() {
|
||||
return remainingMost;
|
||||
}
|
||||
|
||||
public void setRemainingMost(Boolean remainingMost) {
|
||||
this.remainingMost = remainingMost;
|
||||
}
|
||||
|
||||
public Integer getPage() {
|
||||
return page;
|
||||
}
|
||||
|
||||
public void setPage(Integer page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
public Integer getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(Integer size) {
|
||||
this.size = size;
|
||||
}
|
||||
}
|
||||
+22
@@ -5,6 +5,7 @@ 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.domain.GroupCourseDetail;
|
||||
import cn.novalon.gym.manage.groupcourse.dto.GroupCourseQueryDto;
|
||||
import cn.novalon.gym.manage.groupcourse.service.IGroupCourseService;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -215,6 +216,27 @@ public class GroupCourseHandler {
|
||||
});
|
||||
}
|
||||
|
||||
@Operation(summary = "多条件查询团课", description = "支持团课名称模糊查询、类型筛选、日期范围、时间段、价格排序、剩余名额排序等多条件组合查询")
|
||||
public Mono<ServerResponse> searchGroupCourses(ServerRequest request) {
|
||||
return request.bodyToMono(GroupCourseQueryDto.class)
|
||||
.flatMap(query -> {
|
||||
return groupCourseService.searchGroupCourses(query)
|
||||
.flatMap(response -> {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("success", true);
|
||||
result.put("message", "查询成功");
|
||||
result.put("data", response);
|
||||
return ServerResponse.ok().bodyValue(result);
|
||||
});
|
||||
})
|
||||
.onErrorResume(error -> {
|
||||
Map<String, Object> errorResponse = new HashMap<>();
|
||||
errorResponse.put("success", false);
|
||||
errorResponse.put("message", error.getMessage());
|
||||
return ServerResponse.badRequest().bodyValue(errorResponse);
|
||||
});
|
||||
}
|
||||
|
||||
@Operation(summary = "测试-根据Key获取Redis缓存", description = "测试接口:根据传入的key值获取Redis中缓存的数据")
|
||||
public Mono<ServerResponse> getCacheByKey(ServerRequest request) {
|
||||
return request.bodyToMono(Map.class)
|
||||
|
||||
+3
@@ -4,6 +4,7 @@ package cn.novalon.gym.manage.groupcourse.repository;
|
||||
import cn.novalon.gym.manage.common.dto.PageRequest;
|
||||
import cn.novalon.gym.manage.common.dto.PageResponse;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
|
||||
import cn.novalon.gym.manage.groupcourse.dto.GroupCourseQueryDto;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -29,4 +30,6 @@ public interface IGroupCourseRepository {
|
||||
Mono<GroupCourse> updateCurrentMembers(Long id, Integer delta);
|
||||
|
||||
Flux<GroupCourse> findByCourseType(Long courseType);
|
||||
|
||||
Mono<PageResponse<GroupCourse>> searchGroupCourses(GroupCourseQueryDto query);
|
||||
}
|
||||
|
||||
+24
@@ -6,6 +6,7 @@ import cn.novalon.gym.manage.common.dto.PageResponse;
|
||||
import cn.novalon.gym.manage.groupcourse.converter.GroupCourseConverter;
|
||||
import cn.novalon.gym.manage.groupcourse.dao.GroupCourseDao;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
|
||||
import cn.novalon.gym.manage.groupcourse.dto.GroupCourseQueryDto;
|
||||
import cn.novalon.gym.manage.groupcourse.entity.GroupCourseEntity;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseRepository;
|
||||
import org.springframework.data.domain.Sort;
|
||||
@@ -184,4 +185,27 @@ public class GroupCourseRepository implements IGroupCourseRepository {
|
||||
return groupCourseDao.findByCourseTypeAndDeletedAtIsNull(courseType)
|
||||
.map(groupCourseConverter::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<PageResponse<GroupCourse>> searchGroupCourses(GroupCourseQueryDto query) {
|
||||
return groupCourseDao.countSearchGroupCourses(r2dbcEntityTemplate.getDatabaseClient(), query)
|
||||
.flatMap(total -> {
|
||||
if (total == 0) {
|
||||
return Mono.just(new PageResponse<>(
|
||||
List.of(), 0, 0L,
|
||||
query.getPage() != null ? query.getPage() : 0,
|
||||
query.getSize() != null ? query.getSize() : 10));
|
||||
}
|
||||
return groupCourseDao.searchGroupCourses(r2dbcEntityTemplate.getDatabaseClient(), query)
|
||||
.map(groupCourseConverter::toDomain)
|
||||
.collectList()
|
||||
.map(courseList -> {
|
||||
int size = query.getSize() != null ? query.getSize() : 10;
|
||||
int totalPages = (int) Math.ceil((double) total / size);
|
||||
return new PageResponse<>(
|
||||
courseList, totalPages, total,
|
||||
query.getPage() != null ? query.getPage() : 0, size);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -5,6 +5,7 @@ import cn.novalon.gym.manage.common.dto.PageRequest;
|
||||
import cn.novalon.gym.manage.common.dto.PageResponse;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseDetail;
|
||||
import cn.novalon.gym.manage.groupcourse.dto.GroupCourseQueryDto;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -25,4 +26,6 @@ public interface IGroupCourseService {
|
||||
Mono<GroupCourse> signIn(Long courseId, Long memberId);
|
||||
|
||||
Mono<Void> delete(Long id);
|
||||
|
||||
Mono<PageResponse<GroupCourse>> searchGroupCourses(GroupCourseQueryDto query);
|
||||
}
|
||||
|
||||
+13
@@ -9,6 +9,7 @@ import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseBooking;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseDetail;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseType;
|
||||
import cn.novalon.gym.manage.groupcourse.dto.GroupCourseQueryDto;
|
||||
import cn.novalon.gym.manage.groupcourse.enums.CourseEvent;
|
||||
import cn.novalon.gym.manage.groupcourse.enums.CourseStatus;
|
||||
import cn.novalon.gym.manage.groupcourse.handler.GroupCourseStateMachine;
|
||||
@@ -488,6 +489,18 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<PageResponse<GroupCourse>> searchGroupCourses(GroupCourseQueryDto query) {
|
||||
logger.info("多条件查询团课 - courseName={}, courseType={}, startDate={}, endDate={}, timePeriod={}, priceSort={}, remainingMost={}",
|
||||
query.getCourseName(), query.getCourseType(), query.getStartDate(), query.getEndDate(),
|
||||
query.getTimePeriod(), query.getPriceSort(), query.getRemainingMost());
|
||||
|
||||
return groupCourseRepository.searchGroupCourses(query)
|
||||
.doOnSuccess(result -> logger.info("多条件查询结果 - total={}, page={}, size={}",
|
||||
result.getTotalElements(), result.getCurrentPage(), result.getPageSize()))
|
||||
.doOnError(error -> logger.error("多条件查询失败 - error: {}", error.getMessage()));
|
||||
}
|
||||
|
||||
private Mono<Void> clearCache() {
|
||||
return redisUtil.deleteByPattern(CACHE_KEY_PREFIX + "*")
|
||||
.then(redisUtil.deleteByPattern(CACHE_KEY_ID_PREFIX + "*"))
|
||||
|
||||
Reference in New Issue
Block a user