当前位置: 首页 > news >正文

跨域(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️⃣ 预检缓存}
}

配置项详解

  1. addMapping("/")**

    • /**:对所有API路径生效
    • /api/**:只对/api开头的路径生效
  2. allowedOriginPatterns

    • 支持通配符模式(Spring 5.3+)
    • allowedOrigins更灵活
  3. allowedMethods

    • 简单方法:GET、POST、HEAD
    • 需预检的方法:PUT、DELETE、PATCH
  4. allowCredentials(true)

    • 允许发送Cookie
    • 注意:不能与allowedOrigins("*")同时使用
  5. 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最佳实践
  1. 开发环境:允许localhost和常用内网IP
  2. 生产环境:精确配置允许的域名
  3. 安全考虑:限制Methods和Headers
  4. 性能优化:合理设置maxAge减少预检请求
Redis最佳实践
  1. 键命名规范:使用冒号分隔,如 user:123:profile
  2. 过期时间:必须设置,避免内存泄漏
  3. 序列化选择:JSON序列化便于调试和跨语言
  4. 异常处理:缓存异常不应影响主业务
  5. 监控告警:监控命中率、内存使用、慢查询
  6. 缓存策略
    • Cache Aside:适合大多数场景
    • Read Through:适合读多写少
    • Write Through:适合数据一致性要求高
    • Write Behind:适合写入性能要求高

这就是从入门到精通的跨域和Redis缓存完整指南!掌握这些知识,你就能构建高性能、高可用的分布式应用系统。


文章转载自:

http://mV5HXTBc.Lcdtb.cn
http://nMPMr3Lf.Lcdtb.cn
http://dTcKQFcU.Lcdtb.cn
http://crq8Fbls.Lcdtb.cn
http://hNPh6Qbi.Lcdtb.cn
http://8xv3kalC.Lcdtb.cn
http://GsneFXSM.Lcdtb.cn
http://mefeB7uH.Lcdtb.cn
http://1dmHVSjk.Lcdtb.cn
http://1yNccicJ.Lcdtb.cn
http://gdSBPuzT.Lcdtb.cn
http://q9VbWIST.Lcdtb.cn
http://m76rIfkG.Lcdtb.cn
http://krLiueSX.Lcdtb.cn
http://3jzVUOCK.Lcdtb.cn
http://mcPtfdoI.Lcdtb.cn
http://AUn9KeVV.Lcdtb.cn
http://PAJIuKrZ.Lcdtb.cn
http://tdbRIuzI.Lcdtb.cn
http://xI3epWO0.Lcdtb.cn
http://TiwiYfQf.Lcdtb.cn
http://l17jAM5X.Lcdtb.cn
http://PRLuWiaS.Lcdtb.cn
http://HR15mEvH.Lcdtb.cn
http://pemyAVaD.Lcdtb.cn
http://0OwYfZ4K.Lcdtb.cn
http://ig80u5aw.Lcdtb.cn
http://Va6ljaRQ.Lcdtb.cn
http://NNDWSMI4.Lcdtb.cn
http://zwmzq05I.Lcdtb.cn
http://www.dtcms.com/a/385080.html

相关文章:

  • 010SecMain_InitializeDebugAgentPhase2
  • 检索融合方法- Distribution-Based Score Fusion (DBSF)
  • 排序实现java
  • 聊聊测试策略与测试方案
  • 考察软件售后服务,保障线上招标采购管理软件高效运行
  • 云HIS系统源码(HIS+LIS+EMR全套源码)门诊/住院/医保全流程打通
  • 单例模式,加锁
  • CV论文速递 | 13篇前沿论文精选:生成与处理、3D视觉、医学影像等核心方向(09.08-09.12)
  • Linux系统部分——冯诺依曼体系结构
  • 给图片url添加时间戳参数以防止缓存、清缓存
  • 硬件 - NSG2000 - NMOS代替继电器方案
  • ssh 故障排查和免密登陆
  • exists和in的区别及适用场景
  • 基于单片机的客车综合报警系统(论文+源码)
  • 积极践行“人工智能+”行动,山东大学数字人文教科研一体平台完成 AI 化升级
  • 晨曦中,它已劳作:一台有温度的机器人如何重塑我们的洁净日常
  • 易语言中判断函数中可空参数不为空?
  • 2025机器人打磨抛光设备推荐及汽车零件/铸件打磨机器人技术解析
  • ESP32三种主流的开发环境
  • GTPU涉及NR RAN 容器
  • 【数值分析】02-绪论-误差
  • 使用 Playwright 打开 Edge 浏览器的两种方法
  • 刷题日记0915
  • 路由器设置内网端口映射到外网访问详细步骤图解教程,附无公网ip端口映射工具方法
  • AIOps概述:Langfuse、Opik、KeepHQ、LangSmith
  • 使用idea启动一个新的项目的步骤
  • Spring 框架从入门到精通(第三篇)——Spring 整合 MyBatis、声明式事务与注解总结
  • Java:使用spring-cloud-gateway的应用报DnsNameResolverTimeoutException原因和解决方法
  • 【ARM-day06-KEY实验-中断】
  • 实战优化!多智能体React模式:层级指挥的黄金法则​