首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Halo 2.22.x 插件集成 Redis 完整指南

Halo 2.22.x 插件集成 Redis 完整指南

作者头像
HandsomeYo
发布2025-12-27 17:56:46
发布2025-12-27 17:56:46
1360
举报

本文记录了在 Halo 2.x 插件中集成 Redis 的完整过程,包括遇到的各种问题和最终解决方案。

背景

在开发 Halo 插件时,可能需要使用 Redis 来实现各种功能,例如:

  • 支付订单状态缓存
  • 支付回调幂等性校验
  • 订单超时自动取消(延迟队列)
  • 分布式锁防止重复支付

最终方案

核心设计

插件复用 Halo 主程序的 Redis 配置,不需要在插件设置里单独配置 Redis 连接信息。

环境

配置来源

说明

本地开发

halo-dev.yaml

通过 additionalConfigFile 传递给 haloServer

生产环境

Docker 环境变量

-e SPRING_DATA_REDIS_HOST=...

为什么用 Jedis?

Halo 插件使用 PF4J 框架,每个插件有独立的类加载器。尝试过的方案:

方案

结果

原因

Spring Data Redis

类加载器冲突

Lettuce

Reactor 依赖冲突

Jedis

纯 Java,无冲突

Jedis 是纯 Java 实现的 Redis 客户端,不依赖 Spring 或 Reactor,避免了类加载器冲突问题。

配置方式

本地开发

  1. 创建 halo-dev.yaml(已在 .gitignore 中):
代码语言:javascript
复制
spring:
  data:
    redis:
      host: localhost
      port: 6379
      database: 0
      password: your_password

halo:
  redis:
    enabled: true
  session:
    store-type: redis
  1. build.gradle 中引用:
代码语言:javascript
复制
halo {
    version = '2.22.2'
    additionalConfigFile = file("${projectDir}/halo-dev.yaml")
}
  1. 启动开发服务器:
代码语言:javascript
复制
./gradlew haloServer

生产环境(Docker)

通过环境变量配置:

代码语言:javascript
复制
docker run -d \
  --name halo \
  -p 8090:8090 \
  -v ~/.halo2:/root/.halo2 \
  -e SPRING_DATA_REDIS_HOST=redis \
  -e SPRING_DATA_REDIS_PORT=6379 \
  -e SPRING_DATA_REDIS_DATABASE=0 \
  -e SPRING_DATA_REDIS_PASSWORD=your_password \
  -e HALO_SESSION_STORE_TYPE=redis \
  -e HALO_REDIS_ENABLED=true \
  halohub/halo:2.22

或使用 Docker Compose:

代码语言:javascript
复制
services:
  halo:
    image: halohub/halo:2.22
    environment:
      - SPRING_DATA_REDIS_HOST=redis
      - SPRING_DATA_REDIS_PORT=6379
      - SPRING_DATA_REDIS_DATABASE=0
      - SPRING_DATA_REDIS_PASSWORD=your_password
      - HALO_SESSION_STORE_TYPE=redis
      - HALO_REDIS_ENABLED=true
    ports:
      - "8090:8090"
    depends_on:
      - redis
      
  redis:
    image: redis:8-alpine
    command: redis-server --requirepass your_password

代码实现

RedisConfig.java

从 Spring Environment 读取 Halo 的 Redis 配置:

代码语言:javascript
复制
@Slf4j
@Component
public class RedisConfig {

    private final Environment environment;
    
    @Nullable
    private volatile JedisPool jedisPool;
    
    @Getter
    private volatile boolean redisAvailable = false;

    public RedisConfig(Environment environment) {
        this.environment = environment;
    }

    public void ensureInitialized() {
        // 检查 Halo 是否启用了 Redis
        String haloRedisEnabled = environment.getProperty("halo.redis.enabled", "false");
        
        if (!"true".equalsIgnoreCase(haloRedisEnabled)) {
            log.info("Halo Redis not enabled");
            return;
        }
        
        // 读取 Redis 配置
        String host = environment.getProperty("spring.data.redis.host", "localhost");
        int port = Integer.parseInt(environment.getProperty("spring.data.redis.port", "6379"));
        String password = environment.getProperty("spring.data.redis.password", "");
        int database = Integer.parseInt(environment.getProperty("spring.data.redis.database", "0"));
        
        initRedis(host, port, password, database);
    }

    private void initRedis(String host, int port, String password, int database) {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(10);
        poolConfig.setTestOnBorrow(true);

        HostAndPort hostAndPort = new HostAndPort(host, port);
        
        DefaultJedisClientConfig.Builder configBuilder = DefaultJedisClientConfig.builder()
            .connectionTimeoutMillis(10000)
            .database(database);
        
        if (password != null && !password.isEmpty()) {
            // Redis 6+ ACL 需要用户名
            configBuilder.user("default");
            configBuilder.password(password);
        }

        jedisPool = new JedisPool(poolConfig, hostAndPort, configBuilder.build());
        
        // 测试连接
        try (var jedis = jedisPool.getResource()) {
            jedis.ping();
            redisAvailable = true;
        }
    }
}

RedisCacheService.java

同步操作包装成 Mono,在 boundedElastic 调度器上执行:

代码语言:javascript
复制
@Service
@RequiredArgsConstructor
public class RedisCacheService {

    private final RedisConfig redisConfig;

    public boolean isAvailable() {
        return redisConfig.getJedisPool() != null;
    }

    public Mono<Long> incrementLikeCount(String postName) {
        return executeAsync(() -> {
            JedisPool pool = redisConfig.getJedisPool();
            if (pool == null) return -1L;
            try (Jedis jedis = pool.getResource()) {
                return jedis.incr(getKey("like:count:" + postName));
            }
        }, -1L);
    }

    public Mono<List<String>> getHotPosts(String circleName, int limit) {
        return executeAsync(() -> {
            JedisPool pool = redisConfig.getJedisPool();
            if (pool == null) return Collections.emptyList();
            try (Jedis jedis = pool.getResource()) {
                return jedis.zrevrange(getKey("hot:posts:" + circleName), 0, limit - 1);
            }
        }, Collections.emptyList());
    }

    private <T> Mono<T> executeAsync(Callable<T> callable, T defaultValue) {
        return Mono.fromCallable(callable)
            .subscribeOn(Schedulers.boundedElastic())
            .onErrorResume(e -> Mono.just(defaultValue));
    }
}

测试接口

访问 /apis/api.public.circle.xhhao.com/v1alpha1/redis/test 查看 Redis 状态:

代码语言:javascript
复制
{
  "redisAvailable": true,
  "haloRedisEnabled": "true",
  "springRedisHost": "localhsot",
  "springRedisPort": "6379",
  "springRedisDatabase": "0",
  "writeSuccess": true,
  "readValue": "Hello from Circle Plugin!",
  "message": "Redis is working!"
}

注意事项

Redis 6+ ACL

Redis 6+ 引入了 ACL,需要同时提供用户名和密码。默认用户名是 default

代码语言:javascript
复制
if (password != null) {
    configBuilder.user("default");  // 关键!
    configBuilder.password(password);
}

优雅降级

当 Redis 不可用时,服务应该能正常工作:

代码语言:javascript
复制
public Mono<Long> getLikeCount(String postName) {
    if (!isAvailable()) {
        return Mono.just(-1L);  // 返回默认值,由调用方从数据库查询
    }
    // ... Redis 操作
}

依赖配置

代码语言:javascript
复制
dependencies {
    implementation platform('run.halo.tools.platform:plugin:2.22.0')
    compileOnly 'run.halo.app:api'
    
    // Redis - Jedis 纯 Java 客户端
    implementation 'redis.clients:jedis:5.1.0'
}

总结

  1. 复用 Halo 配置:插件从 Spring Environment 读取 Halo 主程序的 Redis 配置
  2. 使用 Jedis:避免类加载器冲突
  3. 本地开发:通过 halo-dev.yaml + additionalConfigFile 配置
  4. 生产环境:通过 Docker 环境变量配置
  5. 优雅降级:Redis 不可用时不影响核心功能
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-12-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 最终方案
    • 核心设计
    • 为什么用 Jedis?
  • 配置方式
    • 本地开发
    • 生产环境(Docker)
  • 代码实现
    • RedisConfig.java
    • RedisCacheService.java
  • 测试接口
  • 注意事项
    • Redis 6+ ACL
    • 优雅降级
    • 依赖配置
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档