跨域(CORS)和缓存中间件(Redis)深度解析
跨域(CORS)和缓存中间件(Redis)深度解析
第一部分:跨域(CORS)完全指南
一、跨域基础概念
1.1 什么是跨域?
同源策略是浏览器的一个安全机制,它限制了一个源的文档或脚本如何与另一个源的资源进行交互。
同源的定义:协议 + 域名 + 端口 完全相同
// 假设当前页面URL:http://localhost:8080/index.htmlhttp://localhost:8080/api/users ✅ 同源
https://localhost:8080/api/users ❌ 跨域(协议不同)
http://localhost:3000/api/users ❌ 跨域(端口不同)
http://127.0.0.1:8080/api/users ❌ 跨域(域名不同)
http://localhost:8080/api/users ✅ 同源
1.2 为什么会有跨域问题?
前后端分离架构中的典型场景:前端Vue项目:http://localhost:8081
后端Spring Boot:http://localhost:8080浏览器:前端想访问后端API?不行!跨域了!🚫
1.3 跨域错误示例
// 浏览器控制台会显示:
Access to XMLHttpRequest at 'http://localhost:8080/api/users'
from origin 'http://localhost:8081' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
二、CORS解决方案详解
2.1 CORS工作原理
简单请求流程:
浏览器 → 发送请求(自动加Origin头) → 服务器
浏览器 ← 响应(含Access-Control-*头) ← 服务器预检请求流程(复杂请求):
1. 浏览器 → OPTIONS预检请求 → 服务器
2. 浏览器 ← 预检响应(CORS头) ← 服务器
3. 浏览器 → 实际请求 → 服务器
4. 浏览器 ← 实际响应 ← 服务器
2.2 代码配置详解
@Configuration
public class CorsConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**") // 1️⃣ 路径映射.allowedOriginPatterns( // 2️⃣ 允许的源"http://localhost:*","http://127.0.0.1:*","http://192.168.*:*","http://10.*:*").allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 3️⃣ HTTP方法.allowedHeaders("*") // 4️⃣ 请求头.allowCredentials(true) // 5️⃣ 认证信息.maxAge(3600); // 6️⃣ 预检缓存}
}
配置项详解:
-
addMapping("/")**
/**
:对所有API路径生效/api/**
:只对/api开头的路径生效
-
allowedOriginPatterns
- 支持通配符模式(Spring 5.3+)
- 比
allowedOrigins
更灵活
-
allowedMethods
- 简单方法:GET、POST、HEAD
- 需预检的方法:PUT、DELETE、PATCH
-
allowCredentials(true)
- 允许发送Cookie
- 注意:不能与
allowedOrigins("*")
同时使用
-
maxAge(3600)
- 预检请求缓存1小时
- 减少OPTIONS请求次数
三、CORS进阶配置
3.1 细粒度控制
@RestController
@RequestMapping("/api")
public class UserController {// 方法级别的跨域配置@CrossOrigin(origins = "http://specific-domain.com")@GetMapping("/sensitive-data")public Result getSensitiveData() {// ...}// 类级别的跨域配置@CrossOrigin(origins = {"http://app1.com", "http://app2.com"},methods = {RequestMethod.GET, RequestMethod.POST},maxAge = 3600,allowedHeaders = {"X-Custom-Header"},exposedHeaders = {"X-Response-Header"})@RestControllerpublic class SpecialController {// ...}
}
3.2 动态CORS配置
@Configuration
public class DynamicCorsConfig implements WebMvcConfigurer {@Value("${app.cors.allowed-origins}")private String[] allowedOrigins; // 从配置文件读取@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/api/**").allowedOrigins(allowedOrigins).allowedMethods("*").allowCredentials(true);}
}
3.3 安全最佳实践
@Configuration
public class SecureCorsConfig {@Beanpublic CorsConfigurationSource corsConfigurationSource() {CorsConfiguration configuration = new CorsConfiguration();// 🔒 生产环境:精确配置允许的源configuration.setAllowedOrigins(Arrays.asList("https://app.production.com","https://www.production.com"));// 🔒 限制允许的方法configuration.setAllowedMethods(Arrays.asList("GET", "POST"));// 🔒 限制允许的请求头configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type","X-Requested-With"));// 🔒 暴露必要的响应头configuration.setExposedHeaders(Arrays.asList("X-Total-Count","X-Page-Number"));// 🔒 根据需要设置认证configuration.setAllowCredentials(true);// 🔒 合理设置预检缓存时间configuration.setMaxAge(3600L);UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", configuration);return source;}
}
第二部分:Redis缓存中间件完全指南
一、Redis基础概念
1.1 什么是Redis?
Redis(Remote Dictionary Server)是一个开源的内存数据结构存储系统:
- 内存数据库:数据存储在内存中,速度极快
- 持久化支持:可以将数据保存到磁盘
- 数据结构丰富:支持String、List、Set、Hash、ZSet等
1.2 为什么需要缓存?
没有缓存的场景:
用户请求 → Controller → Service → Mapper → 数据库 → 返回
每次都查询数据库,压力大,响应慢!有缓存的场景:
用户请求 → Controller → Service → Redis缓存(命中) → 返回↓(未命中)Mapper → 数据库 → 存入Redis → 返回
二、Redis配置详解
2.1 序列化配置分析
@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);// 🔑 关键配置:序列化器// 1. GenericJackson2JsonRedisSerializer// 优点:自动处理类型信息,支持多态// 缺点:存储空间稍大(包含类型信息)GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();// 2. StringRedisSerializer// 用于key序列化,便于Redis客户端查看StringRedisSerializer stringSerializer = new StringRedisSerializer();// 配置序列化器template.setKeySerializer(stringSerializer); // key序列化template.setHashKeySerializer(stringSerializer); // hash key序列化template.setValueSerializer(jsonSerializer); // value序列化template.setHashValueSerializer(jsonSerializer); // hash value序列化template.afterPropertiesSet();return template;}
}
2.2 序列化方式对比
// 1. JDK序列化(默认,不推荐)
User user = new User();
// Redis存储:\xAC\xED\x00\x05sr\x00... (二进制,不可读)// 2. String序列化(只能存字符串)
template.opsForValue().set("name", "张三");
// Redis存储:张三// 3. JSON序列化(推荐)
User user = new User();
user.setUsername("admin");
// Redis存储:{"@class":"com.example.User","username":"admin"}// 4. 自定义Jackson配置
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL
);
三、Redis缓存实战应用
3.1 基础缓存操作
@Service
public class UserServiceImpl implements UserService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;// 1️⃣ 缓存读取模式public User getUserById(Long id) {String key = "user:" + id;// 查询缓存User user = (User) redisTemplate.opsForValue().get(key);if (user != null) {log.info("缓存命中: {}", key);return user;}// 查询数据库user = userMapper.selectById(id);if (user != null) {// 写入缓存,设置过期时间redisTemplate.opsForValue().set(key, user, 2, TimeUnit.HOURS);log.info("写入缓存: {}", key);}return user;}// 2️⃣ 缓存更新模式public User updateUser(User user) {// 更新数据库userMapper.update(user);// 删除缓存(Cache Aside模式)String key = "user:" + user.getId();redisTemplate.delete(key);return user;}// 3️⃣ 缓存删除public boolean deleteUser(Long id) {// 删除数据库int result = userMapper.deleteById(id);// 删除缓存String key = "user:" + id;redisTemplate.delete(key);return result > 0;}
}
3.2 高级缓存策略
@Component
public class AdvancedCacheService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;// 1️⃣ 防止缓存穿透public User getUserWithNullCache(Long id) {String key = "user:" + id;// 检查缓存Object cached = redisTemplate.opsForValue().get(key);if (cached != null) {// 如果是空值标记,返回nullif ("NULL".equals(cached)) {return null;}return (User) cached;}// 查询数据库User user = userMapper.selectById(id);if (user != null) {redisTemplate.opsForValue().set(key, user, 2, TimeUnit.HOURS);} else {// 缓存空值,防止缓存穿透redisTemplate.opsForValue().set(key, "NULL", 5, TimeUnit.MINUTES);}return user;}// 2️⃣ 缓存预热@PostConstructpublic void preloadCache() {// 启动时预加载热点数据List<User> hotUsers = userMapper.selectHotUsers();for (User user : hotUsers) {String key = "user:" + user.getId();redisTemplate.opsForValue().set(key, user, 4, TimeUnit.HOURS);}log.info("缓存预热完成,加载{}条数据", hotUsers.size());}// 3️⃣ 批量操作优化public List<User> getUsersByIds(List<Long> ids) {// 构建key列表List<String> keys = ids.stream().map(id -> "user:" + id).collect(Collectors.toList());// 批量获取List<Object> cached = redisTemplate.opsForValue().multiGet(keys);List<User> result = new ArrayList<>();List<Long> missedIds = new ArrayList<>();for (int i = 0; i < cached.size(); i++) {if (cached.get(i) != null) {result.add((User) cached.get(i));} else {missedIds.add(ids.get(i));}}// 查询未命中的数据if (!missedIds.isEmpty()) {List<User> missedUsers = userMapper.selectByIds(missedIds);result.addAll(missedUsers);// 批量写入缓存Map<String, Object> toCache = new HashMap<>();for (User user : missedUsers) {toCache.put("user:" + user.getId(), user);}redisTemplate.opsForValue().multiSet(toCache);}return result;}
}
3.3 分布式锁实现
@Component
public class RedisLockService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;// 获取分布式锁public boolean tryLock(String lockKey, String clientId, long expireTime) {Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, expireTime, TimeUnit.SECONDS);return Boolean.TRUE.equals(result);}// 释放锁(使用Lua脚本保证原子性)public boolean releaseLock(String lockKey, String clientId) {String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +" return redis.call('del', KEYS[1]) " +"else " +" return 0 " +"end";RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), clientId);return Long.valueOf(1).equals(result);}// 使用示例public void doBusinessWithLock() {String lockKey = "lock:order:create";String clientId = UUID.randomUUID().toString();try {// 尝试获取锁,最多等待5秒boolean locked = tryLock(lockKey, clientId, 5);if (!locked) {throw new RuntimeException("获取锁失败");}// 执行业务逻辑// ...} finally {// 释放锁releaseLock(lockKey, clientId);}}
}
四、Redis进阶特性
4.1 数据结构应用
@Service
public class RedisDataStructureService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;// 1️⃣ String类型:计数器public Long incrementPageView(String pageId) {String key = "pv:" + pageId;return redisTemplate.opsForValue().increment(key);}// 2️⃣ Hash类型:对象存储public void saveUserInfo(Long userId, Map<String, Object> userInfo) {String key = "user:info:" + userId;redisTemplate.opsForHash().putAll(key, userInfo);redisTemplate.expire(key, 1, TimeUnit.HOURS);}// 3️⃣ List类型:消息队列public void pushMessage(String queueName, Object message) {redisTemplate.opsForList().leftPush("queue:" + queueName, message);}public Object popMessage(String queueName) {return redisTemplate.opsForList().rightPop("queue:" + queueName);}// 4️⃣ Set类型:共同好友public Set<Object> getCommonFriends(Long userId1, Long userId2) {String key1 = "friends:" + userId1;String key2 = "friends:" + userId2;return redisTemplate.opsForSet().intersect(key1, key2);}// 5️⃣ ZSet类型:排行榜public void updateScore(String rankingName, String userId, double score) {String key = "ranking:" + rankingName;redisTemplate.opsForZSet().add(key, userId, score);}public Set<ZSetOperations.TypedTuple<Object>> getTopN(String rankingName, int n) {String key = "ranking:" + rankingName;return redisTemplate.opsForZSet().reverseRangeWithScores(key, 0, n - 1);}
}
4.2 缓存优化策略
@Configuration
@EnableCaching
public class CacheOptimizationConfig {// 1️⃣ 使用Spring Cache注解@Servicepublic class AnnotationCacheService {@Cacheable(value = "users", key = "#id")public User getUserById(Long id) {return userMapper.selectById(id);}@CachePut(value = "users", key = "#user.id")public User updateUser(User user) {userMapper.update(user);return user;}@CacheEvict(value = "users", key = "#id")public void deleteUser(Long id) {userMapper.deleteById(id);}@CacheEvict(value = "users", allEntries = true)public void clearAllUserCache() {// 清除所有用户缓存}}// 2️⃣ 自定义缓存管理器@Beanpublic RedisCacheManager cacheManager(RedisConnectionFactory factory) {RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1)) // 默认过期时间.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));// 针对不同缓存设置不同的过期时间Map<String, RedisCacheConfiguration> configMap = new HashMap<>();configMap.put("users", config.entryTtl(Duration.ofHours(2)));configMap.put("products", config.entryTtl(Duration.ofMinutes(30)));return RedisCacheManager.builder(factory).cacheDefaults(config).withInitialCacheConfigurations(configMap).build();}
}
五、性能监控与调优
5.1 Redis监控
@Component
public class RedisMonitor {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;// 监控Redis性能public Map<String, Object> getRedisStats() {return redisTemplate.execute((RedisCallback<Map<String, Object>>) connection -> {Properties info = connection.info();Map<String, Object> stats = new HashMap<>();// 内存使用stats.put("used_memory", info.getProperty("used_memory_human"));stats.put("used_memory_peak", info.getProperty("used_memory_peak_human"));// 连接数stats.put("connected_clients", info.getProperty("connected_clients"));// 命令统计stats.put("total_commands_processed", info.getProperty("total_commands_processed"));// 命中率String hits = info.getProperty("keyspace_hits");String misses = info.getProperty("keyspace_misses");if (hits != null && misses != null) {long h = Long.parseLong(hits);long m = Long.parseLong(misses);double hitRate = h / (double)(h + m) * 100;stats.put("hit_rate", String.format("%.2f%%", hitRate));}return stats;});}// 慢查询日志@Scheduled(fixedDelay = 60000)public void checkSlowLogs() {List<Object> slowLogs = redisTemplate.execute((RedisCallback<List<Object>>) connection -> {return connection.slowLogGet(10);});if (!slowLogs.isEmpty()) {log.warn("发现Redis慢查询: {}", slowLogs);}}
}
5.2 缓存问题解决方案
@Component
public class CacheProblemSolver {// 1️⃣ 缓存雪崩解决方案public void preventCacheAvalanche() {// 设置不同的过期时间Random random = new Random();int baseExpire = 3600; // 基础1小时int randomExpire = random.nextInt(600); // 随机0-10分钟redisTemplate.expire("key", baseExpire + randomExpire, TimeUnit.SECONDS);}// 2️⃣ 缓存击穿解决方案public User getUserWithMutex(Long id) {String key = "user:" + id;User user = (User) redisTemplate.opsForValue().get(key);if (user == null) {// 使用互斥锁String lockKey = "lock:user:" + id;String clientId = UUID.randomUUID().toString();try {boolean locked = tryLock(lockKey, clientId, 10);if (locked) {// 再次检查缓存(双重检查)user = (User) redisTemplate.opsForValue().get(key);if (user == null) {// 查询数据库user = userMapper.selectById(id);if (user != null) {redisTemplate.opsForValue().set(key, user, 2, TimeUnit.HOURS);}}} else {// 等待一段时间后重试Thread.sleep(100);return getUserWithMutex(id);}} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {releaseLock(lockKey, clientId);}}return user;}// 3️⃣ 热点数据处理@Componentpublic class HotDataCache {private final Map<String, Object> localCache = new ConcurrentHashMap<>();public Object getHotData(String key) {// 一级缓存:本地缓存Object value = localCache.get(key);if (value != null) {return value;}// 二级缓存:Redisvalue = redisTemplate.opsForValue().get(key);if (value != null) {localCache.put(key, value);// 设置本地缓存过期scheduleLocalCacheExpire(key, 60);return value;}// 三级:数据库// ...return value;}private void scheduleLocalCacheExpire(String key, int seconds) {ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);executor.schedule(() -> localCache.remove(key), seconds, TimeUnit.SECONDS);}}
}
六、最佳实践总结
CORS最佳实践
- 开发环境:允许localhost和常用内网IP
- 生产环境:精确配置允许的域名
- 安全考虑:限制Methods和Headers
- 性能优化:合理设置maxAge减少预检请求
Redis最佳实践
- 键命名规范:使用冒号分隔,如
user:123:profile
- 过期时间:必须设置,避免内存泄漏
- 序列化选择:JSON序列化便于调试和跨语言
- 异常处理:缓存异常不应影响主业务
- 监控告警:监控命中率、内存使用、慢查询
- 缓存策略:
- Cache Aside:适合大多数场景
- Read Through:适合读多写少
- Write Through:适合数据一致性要求高
- Write Behind:适合写入性能要求高
这就是从入门到精通的跨域和Redis缓存完整指南!掌握这些知识,你就能构建高性能、高可用的分布式应用系统。