跨域(CORS)和缓存中间件(Redis)深度解析

跨域(CORS)和缓存中间件(Redis)深度解析

第一部分:跨域(CORS)完全指南

一、跨域基础概念

1.1 什么是跨域?

同源策略是浏览器的一个安全机制,它限制了一个源的文档或脚本如何与另一个源的资源进行交互。

同源的定义:协议 + 域名 + 端口 完全相同

javascript 复制代码
// 假设当前页面URL:http://localhost:8080/index.html

http://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 跨域错误示例
javascript 复制代码
// 浏览器控制台会显示:
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 代码配置详解
java 复制代码
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    
    @Override
    public 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 细粒度控制
java 复制代码
@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"}
    )
    @RestController
    public class SpecialController {
        // ...
    }
}
3.2 动态CORS配置
java 复制代码
@Configuration
public class DynamicCorsConfig implements WebMvcConfigurer {
    
    @Value("${app.cors.allowed-origins}")
    private String[] allowedOrigins;  // 从配置文件读取
    
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins(allowedOrigins)
                .allowedMethods("*")
                .allowCredentials(true);
    }
}
3.3 安全最佳实践
java 复制代码
@Configuration
public class SecureCorsConfig {
    
    @Bean
    public 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 序列化配置分析
java 复制代码
@Configuration
public class RedisConfig {
    
    @Bean
    public 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 序列化方式对比
java 复制代码
// 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 基础缓存操作
java 复制代码
@Service
public class UserServiceImpl implements UserService {
    
    @Autowired
    private 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 高级缓存策略
java 复制代码
@Component
public class AdvancedCacheService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    // 1️⃣ 防止缓存穿透
    public User getUserWithNullCache(Long id) {
        String key = "user:" + id;
        
        // 检查缓存
        Object cached = redisTemplate.opsForValue().get(key);
        if (cached != null) {
            // 如果是空值标记,返回null
            if ("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️⃣ 缓存预热
    @PostConstruct
    public 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 分布式锁实现
java 复制代码
@Component
public class RedisLockService {
    
    @Autowired
    private 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 数据结构应用
java 复制代码
@Service
public class RedisDataStructureService {
    
    @Autowired
    private 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 缓存优化策略
java 复制代码
@Configuration
@EnableCaching
public class CacheOptimizationConfig {
    
    // 1️⃣ 使用Spring Cache注解
    @Service
    public 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️⃣ 自定义缓存管理器
    @Bean
    public 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监控
java 复制代码
@Component
public class RedisMonitor {
    
    @Autowired
    private 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 缓存问题解决方案
java 复制代码
@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️⃣ 热点数据处理
    @Component
    public 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;
            }
            
            // 二级缓存:Redis
            value = 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缓存完整指南!掌握这些知识,你就能构建高性能、高可用的分布式应用系统。

相关推荐
两锭子32 分钟前
Redis基础
redis
烟沙九洲41 分钟前
Redis 缓存穿透、缓存击穿、缓存雪崩
数据库·redis
字节探索1 小时前
Redis 只会当缓存用?这 10 大实战场景,让你的系统快到飞起
redis·后端
于樱花森上飞舞2 小时前
【Redis】哨兵详解
java·开发语言·数据库·redis
Rain的Java大神之路13 小时前
如何快速上传10G文件
java·spring boot·redis·后端·mysql·spring cloud·面试
Wang's Blog13 小时前
Java 接入Redis: 通用命令与键管理
java·服务器·redis
Wang's Blog13 小时前
Java 接入Redis: 列表集合与有序集合操作命令
java·服务器·redis
Together_CZ14 小时前
DeepSeek-V4.1-Flash:Pushing the Limits of KV Cache Compression——推动 KV 缓存压缩的极限
缓存·llm·compression·kv cache·deepseek·v4.1-flash·推动 kv 缓存压缩的极限
liangshanbo121515 小时前
Redux 的中间件(Middleware)的工作机制
中间件·react·redux
今年下半年21 小时前
SpringBoot整合金蝶中间件
spring boot·中间件·maven