增加Redis缓存功能

注:/api/groupCourse/cache/get接口为测试接口
This commit is contained in:
2026-05-15 17:32:03 +08:00
parent 7340f02102
commit 493ecd3021
11 changed files with 422 additions and 30 deletions
@@ -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<ServerResponse> 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<ServerResponse> 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<ServerResponse> getCacheByKey(ServerRequest request) {
return request.bodyToMono(Map.class)
.flatMap(body -> {
String key = (String) body.get("key");
if (key == null || key.isEmpty()) {
Map<String, Object> error = new HashMap<>();
error.put("success", false);
error.put("message", "key参数不能为空");
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);
});
}
}
@@ -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<GroupCourse> findAll(Sort sort);
Flux<GroupCourse> findByDeletedAtIsNull();
Flux<GroupCourse> findByDeletedAtIsNull(Sort sort);
Mono<PageResponse<GroupCourse>> findByPage(PageRequest pageRequest);
Mono<PageResponse<GroupCourse>> findByPageAndNotDeleted(PageRequest pageRequest);
}
@@ -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<PageResponse<GroupCourse>> 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<GroupCourse> courseList = tuple.getT1().stream()
.map(groupCourseConverter::toDomain)
.toList();
return new PageResponse<>(courseList, totalPages, total, page, size);
});
}
@Override
public Mono<PageResponse<GroupCourse>> 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<GroupCourseEntity> allEntities = tuple.getT1();
long total = tuple.getT2();
int fromIndex = page * size;
int toIndex = Math.min(fromIndex + size, allEntities.size());
List<GroupCourse> 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);
});
}
}
@@ -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<GroupCourse> findById(Long id);
Flux<GroupCourse> findAll();
Flux<GroupCourse> findAll(boolean includeDeleted);
Mono<PageResponse<GroupCourse>> findByPage(PageRequest pageRequest, boolean includeDeleted);
}
@@ -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<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);
}
}
@@ -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<GroupCourse> 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<PageResponse<GroupCourse>> 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<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());
}
});
}
}