diff --git a/gym-manage-api/gym-groupCourse/pom.xml b/gym-manage-api/gym-groupCourse/pom.xml index 3fc53bb..a7bfc02 100644 --- a/gym-manage-api/gym-groupCourse/pom.xml +++ b/gym-manage-api/gym-groupCourse/pom.xml @@ -67,6 +67,13 @@ 2.2.43 compile + + + + + org.springframework.boot + spring-boot-starter-data-redis + diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/GroupCourseHandler.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/GroupCourseHandler.java index 920786f..db6c601 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/GroupCourseHandler.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/GroupCourseHandler.java @@ -1,8 +1,11 @@ package cn.novalon.gym.manage.groupcourse.handler; +import cn.novalon.gym.manage.common.dto.PageRequest; 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; import jakarta.validation.Validator; @@ -11,15 +14,25 @@ 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; + @Component @Tag(name="团课管理",description = "团课相关操作") public class GroupCourseHandler { private final IGroupCourseService groupCourseService; private final Validator validator; + private final RedisService redisService; + private final ObjectMapper objectMapper; - public GroupCourseHandler(IGroupCourseService groupCourseService, Validator validator){ + public GroupCourseHandler(IGroupCourseService groupCourseService, + Validator validator, + RedisService redisService, + ObjectMapper objectMapper){ this.groupCourseService = groupCourseService; this.validator = validator; + this.redisService = redisService; + this.objectMapper = objectMapper; } @Operation(summary = "获取所有团课", description = "获取系统中所有团课列表") @@ -29,10 +42,87 @@ public class GroupCourseHandler { .body(groupCourseService.findAll(includeDeleted), GroupCourse.class); } + @Operation(summary = "分页获取团课", description = "根据分页参数获取团课列表") + public Mono getGroupCoursesByPage(ServerRequest request) { + return request.bodyToMono(PageRequest.class) + .flatMap(pageRequest -> { + boolean includeDeleted = request.queryParam("includeDeleted") + .map(Boolean::parseBoolean) + .orElse(false); + + if (pageRequest.getPage() < 0) { + pageRequest.setPage(0); + } + if (pageRequest.getSize() <= 0 || pageRequest.getSize() > 100) { + pageRequest.setSize(10); + } + if (pageRequest.getSort() == null || pageRequest.getSort().isEmpty()) { + pageRequest.setSort("id"); + } + if (pageRequest.getOrder() == null || pageRequest.getOrder().isEmpty()) { + pageRequest.setOrder("asc"); + } + + return groupCourseService.findByPage(pageRequest, includeDeleted) + .flatMap(response -> ServerResponse.ok().bodyValue(response)); + }); + } + @Operation(summary = "根据ID获取团课", description = "根据ID获取团课详情") public Mono getGroupCourseById(ServerRequest request){ Long id = Long.valueOf(request.pathVariable("id")); return ServerResponse.ok() .body(groupCourseService.findById(id), GroupCourse.class); } + + @Operation(summary = "测试-根据Key获取Redis缓存", description = "测试接口:根据传入的key值获取Redis中缓存的数据") + public Mono getCacheByKey(ServerRequest request) { + return request.bodyToMono(Map.class) + .flatMap(body -> { + String key = (String) body.get("key"); + + if (key == null || key.isEmpty()) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("message", "key参数不能为空"); + return ServerResponse.badRequest().bodyValue(error); + } + + Object cachedValue = redisService.get(key); + + Map 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 errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("message", "请求处理失败: " + error.getMessage()); + return ServerResponse.status(500).bodyValue(errorResponse); + }); + } } diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/IGroupCourseRepository.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/IGroupCourseRepository.java index 4ade673..1a2d402 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/IGroupCourseRepository.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/IGroupCourseRepository.java @@ -1,6 +1,8 @@ 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 org.springframework.data.domain.Sort; import reactor.core.publisher.Flux; @@ -12,4 +14,7 @@ public interface IGroupCourseRepository { Flux findAll(Sort sort); Flux findByDeletedAtIsNull(); Flux findByDeletedAtIsNull(Sort sort); + + Mono> findByPage(PageRequest pageRequest); + Mono> findByPageAndNotDeleted(PageRequest pageRequest); } diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/impl/GroupCourseRepository.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/impl/GroupCourseRepository.java index 5c11325..aa249d1 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/impl/GroupCourseRepository.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/repository/impl/GroupCourseRepository.java @@ -1,16 +1,22 @@ package cn.novalon.gym.manage.groupcourse.repository.impl; +import cn.novalon.gym.manage.common.dto.PageRequest; +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.entity.GroupCourseEntity; import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseRepository; import org.springframework.data.domain.Sort; import org.springframework.data.r2dbc.core.R2dbcEntityTemplate; +import org.springframework.data.relational.core.query.Query; import org.springframework.stereotype.Repository; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.util.List; + @Repository public class GroupCourseRepository implements IGroupCourseRepository { private final GroupCourseDao groupCourseDao; @@ -53,4 +59,73 @@ public class GroupCourseRepository implements IGroupCourseRepository { return groupCourseDao.findAllByDeletedAtIsNull(sort) .map(groupCourseConverter::toDomain); } + + @Override + public Mono> findByPage(PageRequest pageRequest) { + int page = pageRequest.getPage(); + int size = pageRequest.getSize(); + String sort = pageRequest.getSort(); + String order = pageRequest.getOrder(); + + Sort sortObj = Sort.unsorted(); + if (sort != null && !sort.isEmpty()) { + sortObj = Sort.by(Sort.Direction.fromString(order), sort); + } + + org.springframework.data.domain.PageRequest pageable = org.springframework.data.domain.PageRequest.of(page, size, sortObj); + + Query query = Query.empty(); + + return r2dbcEntityTemplate.select(GroupCourseEntity.class) + .matching(query.with(pageable)) + .all() + .collectList() + .zipWith(r2dbcEntityTemplate.count(query, GroupCourseEntity.class)) + .map(tuple -> { + long total = tuple.getT2(); + int totalPages = (int) Math.ceil((double) total / size); + List courseList = tuple.getT1().stream() + .map(groupCourseConverter::toDomain) + .toList(); + return new PageResponse<>(courseList, totalPages, total, page, size); + }); + } + + @Override + public Mono> findByPageAndNotDeleted(PageRequest pageRequest) { + int page = pageRequest.getPage(); + int size = pageRequest.getSize(); + String sort = pageRequest.getSort(); + String order = pageRequest.getOrder(); + + Sort sortObj = Sort.unsorted(); + if (sort != null && !sort.isEmpty()) { + sortObj = Sort.by(Sort.Direction.fromString(order), sort); + } + + org.springframework.data.domain.PageRequest pageable = org.springframework.data.domain.PageRequest.of(page, size, sortObj); + + return groupCourseDao.findAllByDeletedAtIsNull(sortObj) + .collectList() + .zipWith(groupCourseDao.findAllByDeletedAtIsNull().count()) + .map(tuple -> { + List allEntities = tuple.getT1(); + long total = tuple.getT2(); + + int fromIndex = page * size; + int toIndex = Math.min(fromIndex + size, allEntities.size()); + + List courseList; + if (fromIndex < allEntities.size()) { + courseList = allEntities.subList(fromIndex, toIndex).stream() + .map(groupCourseConverter::toDomain) + .toList(); + } else { + courseList = List.of(); + } + + int totalPages = (int) Math.ceil((double) total / size); + return new PageResponse<>(courseList, totalPages, total, page, size); + }); + } } diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/IGroupCourseService.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/IGroupCourseService.java index d43b396..7fa0b70 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/IGroupCourseService.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/IGroupCourseService.java @@ -1,6 +1,8 @@ package cn.novalon.gym.manage.groupcourse.service; +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 reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -9,4 +11,6 @@ public interface IGroupCourseService { Mono findById(Long id); Flux findAll(); Flux findAll(boolean includeDeleted); + + Mono> findByPage(PageRequest pageRequest, boolean includeDeleted); } diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/RedisService.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/RedisService.java new file mode 100644 index 0000000..aa92e38 --- /dev/null +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/RedisService.java @@ -0,0 +1,76 @@ +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 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 getSet(String key) { + return redisTemplate.opsForSet().members(key); + } +} diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseService.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseService.java index 6e409e3..70a43cc 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseService.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseService.java @@ -1,24 +1,79 @@ 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.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; +import org.slf4j.LoggerFactory; 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 final IGroupCourseRepository groupCourseRepository; + private static final Logger logger = LoggerFactory.getLogger(GroupCourseService.class); - public GroupCourseService(IGroupCourseRepository groupCourseRepository){ + private final IGroupCourseRepository groupCourseRepository; + private final RedisService redisService; + private final ObjectMapper objectMapper; + + private static final String CACHE_KEY_PREFIX = "group_course:page:"; + private static final String CACHE_KEY_ID_PREFIX = "group_course:id:"; + private static final long CACHE_EXPIRE_SECONDS = 300; + + public GroupCourseService(IGroupCourseRepository groupCourseRepository, + RedisService redisService, + ObjectMapper objectMapper){ this.groupCourseRepository = groupCourseRepository; + this.redisService = redisService; + this.objectMapper = objectMapper; } @Override public Mono findById(Long id) { - return groupCourseRepository.findByIdAndDeletedAtIsNull(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) { + try { + String jsonData = objectMapper.writeValueAsString(groupCourse); + redisService.setWithExpire(cacheKey, jsonData, CACHE_EXPIRE_SECONDS); + logger.debug("缓存已设置 - findById: id={}", id); + } catch (JsonProcessingException e) { + logger.error("缓存设置失败 - id: {}, error: {}", id, e.getMessage()); + } + } + }); } @Override @@ -34,4 +89,50 @@ public class GroupCourseService implements IGroupCourseService { return groupCourseRepository.findByDeletedAtIsNull(); } } + + @Override + public Mono> findByPage(PageRequest pageRequest, boolean includeDeleted) { + int page = pageRequest.getPage(); + int size = pageRequest.getSize(); + + String cacheKey = CACHE_KEY_PREFIX + page + ":" + size + ":" + includeDeleted; + + Object cachedData = redisService.get(cacheKey); + if (cachedData != null) { + try { + String json; + if (cachedData instanceof String) { + json = (String) cachedData; + } else { + json = objectMapper.writeValueAsString(cachedData); + } + + PageResponse 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> 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()); + } + }); + } } diff --git a/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/config/RedisConfig.java b/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/config/RedisConfig.java new file mode 100644 index 0000000..12d804e --- /dev/null +++ b/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/config/RedisConfig.java @@ -0,0 +1,43 @@ +package cn.novalon.gym.manage.app.config; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * @author:liwentao + * @date:2026/5/15-05-15-16:01 + */ +@Configuration +public class RedisConfig { + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory factory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(factory); + + // 创建ObjectMapper并配置 + ObjectMapper om = new ObjectMapper(); + om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL); + + // 使用GenericJackson2JsonRedisSerializer替代已弃用的方式 + GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(om); + + // 使用StringRedisSerializer来序列化和反序列化redis的key值 + StringRedisSerializer stringSerializer = new StringRedisSerializer(); + template.setKeySerializer(stringSerializer); + template.setValueSerializer(genericJackson2JsonRedisSerializer); + template.setHashKeySerializer(stringSerializer); + template.setHashValueSerializer(genericJackson2JsonRedisSerializer); + + template.afterPropertiesSet(); + return template; + } +} \ No newline at end of file diff --git a/gym-manage-api/manage-app/src/main/resources/application.yml b/gym-manage-api/manage-app/src/main/resources/application.yml index 708973f..39acf0e 100644 --- a/gym-manage-api/manage-app/src/main/resources/application.yml +++ b/gym-manage-api/manage-app/src/main/resources/application.yml @@ -38,11 +38,24 @@ spring: user: name: disabled password: disabled + data: + redis: + host: ${REDIS_HOST:localhost} + port: ${REDIS_PORT:6379} + password: ${REDIS_PASSWORD:novalon123} + timeout: 5000 + lettuce: + pool: + max-active: 8 # 最大连接数 + max-idle: 8 # 最大空闲连接 + min-idle: 0 # 最小空闲连接 + max-wait: -1ms # 连接等待时间 profiles: active: dev config: import: classpath:member-config.yml + management: endpoints: web: diff --git a/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/config/SecurityConfig.java b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/config/SecurityConfig.java index a824830..83ea7df 100644 --- a/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/config/SecurityConfig.java +++ b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/config/SecurityConfig.java @@ -50,7 +50,8 @@ public class SecurityConfig { spec.pathMatchers("/api/auth/**").permitAll() .pathMatchers("/api/public/**").permitAll() .pathMatchers("/ws/**").permitAll() - .pathMatchers("/actuator/**").permitAll(); + .pathMatchers("/actuator/**").permitAll() + .pathMatchers("/api/groupCourse/**").permitAll(); if (isDevOrTest) {