RedisConfig.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. package com.fuint.common.config;
  2. import com.fasterxml.jackson.databind.ObjectMapper;
  3. import lombok.AllArgsConstructor;
  4. import org.slf4j.Logger;
  5. import org.slf4j.LoggerFactory;
  6. import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  7. import org.springframework.cache.annotation.CachingConfigurerSupport;
  8. import org.springframework.cache.annotation.EnableCaching;
  9. import org.springframework.cache.interceptor.KeyGenerator;
  10. import org.springframework.context.annotation.Bean;
  11. import org.springframework.context.annotation.Configuration;
  12. import org.springframework.data.redis.connection.RedisConnectionFactory;
  13. import org.springframework.data.redis.core.RedisTemplate;
  14. import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
  15. import org.springframework.data.redis.serializer.StringRedisSerializer;
  16. import org.springframework.session.data.redis.config.ConfigureRedisAction;
  17. import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
  18. /**
  19. * 配置redis缓存
  20. *
  21. * Created by FSQ
  22. * CopyRight https://www.fuint.cn
  23. */
  24. @Configuration
  25. @EnableCaching
  26. @AllArgsConstructor
  27. @EnableRedisHttpSession
  28. @ConditionalOnProperty(name = "spring.redis.enabled", havingValue = "true", matchIfMissing = true)
  29. public class RedisConfig extends CachingConfigurerSupport {
  30. private static final Logger logger = LoggerFactory.getLogger(RedisConfig.class);
  31. private RedisConnectionFactory redisConnectionFactory;
  32. @Bean
  33. public KeyGenerator keyGenerator() {
  34. return (target, method, params) -> {
  35. StringBuilder sb = new StringBuilder();
  36. sb.append(target.getClass().getName());
  37. sb.append(method.getName());
  38. for (Object obj : params) {
  39. sb.append(obj.toString());
  40. }
  41. return sb.toString();
  42. };
  43. }
  44. @Bean
  45. public static ConfigureRedisAction configureRedisAction() {
  46. return ConfigureRedisAction.NO_OP;
  47. }
  48. @Bean
  49. public ObjectMapper objectMapper() {
  50. return new ObjectMapper();
  51. }
  52. @Bean
  53. Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
  54. Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(
  55. Object.class);
  56. jackson2JsonRedisSerializer.setObjectMapper(objectMapper());
  57. return jackson2JsonRedisSerializer;
  58. }
  59. @Bean
  60. RedisTemplate<String, Object> redisTemplate(Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer) {
  61. try {
  62. RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
  63. redisTemplate.setConnectionFactory(redisConnectionFactory);
  64. redisTemplate.setDefaultSerializer(jackson2JsonRedisSerializer);
  65. StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
  66. redisTemplate.setKeySerializer(stringRedisSerializer);
  67. redisTemplate.setHashKeySerializer(stringRedisSerializer);
  68. // 测试连接 - 增加重试机制
  69. testRedisConnectionWithRetry(redisTemplate, 3);
  70. logger.info("Redis连接配置成功");
  71. return redisTemplate;
  72. } catch (Exception e) {
  73. // 打印详细的Redis连接配置信息
  74. printRedisConfigInfo();
  75. logger.error("Redis连接配置失败: {}", e.getMessage(), e);
  76. // 提供更详细的错误信息
  77. String errorMsg = buildDetailedErrorMessage(e);
  78. logger.error(errorMsg);
  79. // 如果是开发环境,可以选择降级处理而不是直接抛出异常
  80. String envProfile = System.getProperty("env.profile", "dev");
  81. if ("dev".equals(envProfile)) {
  82. logger.warn("开发环境检测到Redis连接失败,系统将继续运行但部分功能可能受限");
  83. // 返回一个mock的RedisTemplate或者禁用Redis功能
  84. return createMockRedisTemplate();
  85. } else {
  86. throw new RuntimeException("Redis连接配置失败,请检查Redis服务是否启动和配置是否正确", e);
  87. }
  88. }
  89. }
  90. /**
  91. * 打印Redis连接配置信息
  92. */
  93. private void printRedisConfigInfo() {
  94. try {
  95. // 获取Redis连接工厂的配置信息
  96. if (redisConnectionFactory != null) {
  97. logger.error("=== Redis连接配置信息 ===");
  98. logger.error("Redis Host: {}", getRedisHost());
  99. logger.error("Redis Port: {}", getRedisPort());
  100. logger.error("Redis Database: {}", getRedisDatabase());
  101. logger.error("Redis Password: {}", getRedisPassword() != null && !getRedisPassword().isEmpty() ? "******" : "(empty)");
  102. logger.error("Connection Timeout: {}ms", getRedisTimeout());
  103. logger.error("=========================");
  104. }
  105. } catch (Exception ex) {
  106. logger.error("获取Redis配置信息失败: {}", ex.getMessage());
  107. }
  108. }
  109. /**
  110. * 获取Redis主机地址
  111. */
  112. private String getRedisHost() {
  113. try {
  114. return redisConnectionFactory instanceof org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory
  115. ? ((org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory) redisConnectionFactory).getHostName()
  116. : "unknown";
  117. } catch (Exception e) {
  118. return "unknown";
  119. }
  120. }
  121. /**
  122. * 获取Redis端口
  123. */
  124. private int getRedisPort() {
  125. try {
  126. return redisConnectionFactory instanceof org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory
  127. ? ((org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory) redisConnectionFactory).getPort()
  128. : 6379;
  129. } catch (Exception e) {
  130. return 6379;
  131. }
  132. }
  133. /**
  134. * 获取Redis数据库索引
  135. */
  136. private int getRedisDatabase() {
  137. try {
  138. return redisConnectionFactory instanceof org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory
  139. ? ((org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory) redisConnectionFactory).getDatabase()
  140. : 0;
  141. } catch (Exception e) {
  142. return 0;
  143. }
  144. }
  145. /**
  146. * 获取Redis密码
  147. */
  148. private String getRedisPassword() {
  149. try {
  150. return redisConnectionFactory instanceof org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory
  151. ? ((org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory) redisConnectionFactory).getPassword()
  152. : null;
  153. } catch (Exception e) {
  154. return null;
  155. }
  156. }
  157. /**
  158. * 获取连接超时时间
  159. */
  160. private long getRedisTimeout() {
  161. try {
  162. return redisConnectionFactory instanceof org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory
  163. ? ((org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory) redisConnectionFactory).getTimeout()
  164. : 2000L;
  165. } catch (Exception e) {
  166. return 2000L;
  167. }
  168. }
  169. /**
  170. * 测试Redis连接(带重试机制)
  171. */
  172. private void testRedisConnectionWithRetry(RedisTemplate<String, Object> redisTemplate, int maxRetries) {
  173. Exception lastException = null;
  174. for (int i = 1; i <= maxRetries; i++) {
  175. try {
  176. logger.info("第{}次尝试连接Redis...", i);
  177. redisTemplate.getConnectionFactory().getConnection().ping();
  178. logger.info("Redis连接测试成功");
  179. return; // 成功则返回
  180. } catch (Exception e) {
  181. lastException = e;
  182. logger.warn("第{}次Redis连接尝试失败: {}", i, e.getMessage());
  183. if (i < maxRetries) {
  184. try {
  185. Thread.sleep(1000 * i); // 递增延迟
  186. } catch (InterruptedException ie) {
  187. Thread.currentThread().interrupt();
  188. throw new RuntimeException("连接重试被中断", ie);
  189. }
  190. }
  191. }
  192. }
  193. // 所有重试都失败
  194. throw new RuntimeException("Redis连接重试" + maxRetries + "次后仍然失败", lastException);
  195. }
  196. /**
  197. * 构建详细的错误信息
  198. */
  199. private String buildDetailedErrorMessage(Exception e) {
  200. StringBuilder sb = new StringBuilder();
  201. sb.append("\n=== Redis连接详细错误信息 ===\n");
  202. // 基础错误信息
  203. sb.append("错误类型: ").append(e.getClass().getSimpleName()).append("\n");
  204. sb.append("错误消息: ").append(e.getMessage()).append("\n");
  205. // 常见问题诊断
  206. sb.append("\n可能的原因:\n");
  207. sb.append("1. Redis服务未启动或端口被占用\n");
  208. sb.append("2. Redis配置的主机地址或端口不正确\n");
  209. sb.append("3. Redis密码配置错误\n");
  210. sb.append("4. 网络连接问题或防火墙阻止\n");
  211. sb.append("5. Redis连接超时设置过短\n");
  212. sb.append("\n建议解决方案:\n");
  213. sb.append("1. 检查Redis服务状态: net start redis 或 redis-server\n");
  214. sb.append("2. 验证端口是否监听: netstat -an | findstr 6379\n");
  215. sb.append("3. 检查配置文件中的Redis密码是否正确\n");
  216. sb.append("4. 尝试telnet 127.0.0.1 6379测试连接\n");
  217. sb.append("===============================\n");
  218. return sb.toString();
  219. }
  220. /**
  221. * 创建Mock的RedisTemplate用于开发环境降级
  222. */
  223. private RedisTemplate<String, Object> createMockRedisTemplate() {
  224. logger.warn("创建Mock RedisTemplate,Redis相关功能将不可用");
  225. RedisTemplate<String, Object> mockTemplate = new RedisTemplate<String, Object>() {
  226. @Override
  227. public void afterPropertiesSet() {
  228. // 不执行实际初始化
  229. logger.debug("Mock RedisTemplate已创建");
  230. }
  231. };
  232. // 设置基本序列化器
  233. StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
  234. Jackson2JsonRedisSerializer<Object> jacksonSerializer = jackson2JsonRedisSerializer();
  235. mockTemplate.setKeySerializer(stringRedisSerializer);
  236. mockTemplate.setHashKeySerializer(stringRedisSerializer);
  237. mockTemplate.setDefaultSerializer(jacksonSerializer);
  238. return mockTemplate;
  239. }
  240. }