├── .gitignore ├── README.md ├── pom.xml ├── redis-mq-test ├── pom.xml └── src │ └── main │ ├── java │ └── org │ │ └── example │ │ └── redis │ │ └── mq │ │ └── test │ │ ├── SpringRedisMq.java │ │ ├── config │ │ ├── RedisConfig.java │ │ └── SwaggerConfig.java │ │ ├── controller │ │ └── TestController.java │ │ ├── entity │ │ └── Job.java │ │ └── listener │ │ └── RedisMQListener.java │ └── resources │ ├── application.yml │ └── logback.xml └── springboot-starter-redis-mq ├── pom.xml └── src └── main ├── java └── com │ └── jdragon │ └── starter │ └── redis │ └── mq │ ├── RedisMQListenerAutoConfig.java │ ├── annotations │ └── RedisListener.java │ └── core │ ├── RedisListenerAnnotationScanPostProcesser.java │ ├── RedisMQSender.java │ ├── RedisMessageQueueRegister.java │ └── domain │ ├── RedisListenerMethod.java │ └── RedisMessage.java └── resources └── META-INF └── spring.factories /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Java template 3 | # Compiled class file 4 | *.class 5 | 6 | # Log file 7 | *.log 8 | 9 | # BlueJ files 10 | *.ctxt 11 | 12 | # Mobile Tools for Java (J2ME) 13 | .mtj.tmp/ 14 | 15 | # Package Files # 16 | *.jar 17 | *.war 18 | *.nar 19 | *.ear 20 | *.zip 21 | *.tar.gz 22 | *.rar 23 | 24 | *.iml 25 | 26 | .idea/ 27 | 28 | target/ 29 | 30 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 31 | hs_err_pid* 32 | 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## RedisMQ简介 2 | 3 | 在SpringBoot-Starter上,基于RedisTemplate的Redis Stream API实现的消息队列,注解驱动编程,快速上手。 4 | 5 | 6 | 7 | ## Quick Start 8 | 9 | #### 1、拉取编译 10 | 11 | ```shell 12 | git clone http://gitlab.tyu.wiki/jdragon/spring-redis-mq.git 13 | 14 | mvn install 15 | ``` 16 | 17 | 18 | 19 | #### 2、依赖导入 20 | 21 | ```xml 22 | 23 | com.jdragon.starter 24 | springboot-starter-redis-mq 25 | 1.0-SNAPSHOT 26 | 27 | ``` 28 | 29 | 30 | 31 | #### 3、添加配置 32 | 33 | 使用的是springboot自带的RedisTemplate,可通过spring自带参数设置redismq的配置 34 | 35 | ```yml 36 | spring: 37 | redis: 38 | host: 127.0.0.1 39 | port: 6379 40 | ``` 41 | 42 | 43 | 44 | #### 4、生产消费示例 45 | 46 | ```java 47 | @Data 48 | public class Job { 49 | private Integer id; 50 | 51 | private Map param; 52 | 53 | @JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss") 54 | private Date createDate; 55 | 56 | @JsonSerialize(using = LocalDateTimeSerializer.class) 57 | @JsonDeserialize(using = LocalDateTimeDeserializer.class) 58 | private LocalDateTime endDate; 59 | } 60 | ``` 61 | 62 | 63 | 64 | ##### 1)生产者 65 | 66 | 创建一个TestController类,注入`RedisMQSender` 67 | PS:redis发送生产消息的方法`RedisMQSender.send(参数1,参数2)`: 68 | 69 | - 参数1是String类型的消息队列名称 70 | - 参数2是你想传递的任意数据。 71 | 72 | ```java 73 | @RestController 74 | @RequestMapping("test") 75 | public class TestController { 76 | 77 | @Autowired 78 | private RedisMQSender redisMQSender; 79 | 80 | @GetMapping("redisMQSender") 81 | public String redisMQSender() { 82 | String streamKey = "streamKey"; 83 | redisMQSender.send(streamKey, "你好"); 84 | return "成功"; 85 | } 86 | 87 | @GetMapping("redisMQSender2") 88 | public String redisMQSender2() { 89 | String streamKey = "streamKey2"; 90 | 91 | HashMap param = new HashMap<>(); 92 | param.put("id", 1); 93 | param.put("name", "张三"); 94 | param.put("paramInline", new HashMap<>()); 95 | 96 | Job job = new Job(); 97 | job.setId(1); 98 | job.setParam(param); 99 | job.setCreateDate(new Date()); 100 | job.setEndDate(LocalDateTime.now()); 101 | redisMQSender.send(streamKey, job); 102 | return "成功"; 103 | } 104 | } 105 | ``` 106 | 107 | 108 | 109 | ##### 2)消费者 110 | 111 | 创建一个RedisListenerContainer类用于定义redis队列消息监听处理方法。 112 | 113 | @RedisListener注解支持参数 114 | 115 | - queueName:监听队列名称,默认为default_queue,表示该方法你需要处理的哪个队列的消息。 116 | - group:消费者组,默认为default_group,消费者组内存在竞争关系。 117 | - customer:消费者名称,默认为consumer 118 | 119 | 120 | 121 | PS: 实现redis队列监听只需在Spring容器所管理的Bean中的方法上添加注解`@RedisListener(queueName,group,customer)`,注意被@RedisListener修饰的方法只能包含一个参数,这个参数的可以一个`RedisMessage`类型的参数,也可以是你需要传递的直接消息类型。 122 | 123 | ```java 124 | @Slf4j 125 | @Component 126 | public class RedisMQListener { 127 | @RedisListener(queueName = "streamKey") 128 | public void test(RedisMessage redisMessage) { 129 | log.info("redis message 接受到信息:{}", redisMessage.getData()); 130 | } 131 | 132 | @RedisListener(queueName = "streamKey") 133 | public void test2(String redisMessage) { 134 | log.info("接受到信息:{}", redisMessage); 135 | } 136 | 137 | @RedisListener(queueName = "streamKey2") 138 | public void test3(RedisMessage job) { 139 | log.info("redis message 接受到信息:{}", job.getData()); 140 | } 141 | 142 | @RedisListener(queueName = "streamKey2") 143 | public void test4(Job job) { 144 | log.info("接受到信息:{}", job); 145 | } 146 | } 147 | ``` 148 | 149 | 150 | 151 | #### 3)启动结果 152 | 153 | 154 | 155 | 见以下日志打印,即为启动监听成功 156 | 157 | ```powershell 158 | 2021-12-17 17:08:26.787 [main] INFO c.j.s.r.m.c.RedisMessageQueueRegister [run 101] - 启动消息队列监听器:【streamKey2.default_group】 159 | 2021-12-17 17:08:26.871 [main] INFO c.j.s.r.m.c.RedisMessageQueueRegister [run 101] - 启动消息队列监听器:【streamKey.default_group】 160 | ``` 161 | 162 | 163 | 164 | 请求`/test/redisMQSender` 165 | 166 | ```powershell 167 | 2021-12-17 17:11:14.633 [SimpleAsyncTaskExecutor-2] INFO c.j.s.r.m.c.RedisMessageQueueRegister [lambda$run$0 78] - stream message。messageId=1639732274611-0, stream=streamKey, body={"queueName":"streamKey","data":"你好","createTime":"2021-12-17 05:11:14"} 168 | 2021-12-17 17:11:14.634 [SimpleAsyncTaskExecutor-2] INFO o.e.r.m.t.listener.RedisMQListener [test 20] - redis message 接受到信息:你好 169 | 2021-12-17 17:11:14.635 [SimpleAsyncTaskExecutor-2] INFO o.e.r.m.t.listener.RedisMQListener [test3 30] - 接受到信息:你好 170 | ``` 171 | 172 | 173 | 174 | 请求`/test/redisMQSender2` 175 | 176 | ```powershell 177 | 2021-12-17 17:11:20.547 [SimpleAsyncTaskExecutor-1] INFO c.j.s.r.m.c.RedisMessageQueueRegister [lambda$run$0 78] - stream message。messageId=1639732280628-0, stream=streamKey2, body={"queueName":"streamKey2","data":{"id":1,"param":{"paramInline":{},"name":"张三","id":1},"createDate":"2021-12-17 05:11:20","endDate":"2021-12-17T17:11:20.461"},"createTime":"2021-12-17 05:11:20"} 178 | 2021-12-17 17:11:20.547 [SimpleAsyncTaskExecutor-1] INFO o.e.r.m.t.listener.RedisMQListener [test2 25] - redis message 接受到信息:{id=1, param={paramInline={}, name=张三, id=1}, createDate=2021-12-17 05:11:20, endDate=2021-12-17T17:11:20.461} 179 | 2021-12-17 17:11:20.548 [SimpleAsyncTaskExecutor-1] INFO o.e.r.m.t.listener.RedisMQListener [test4 35] - 接受到信息:Job(id=1, param={paramInline={}, name=张三, id=1}, createDate=Fri Dec 17 05:11:20 CST 2021, endDate=2021-12-17T17:11:20.461) 180 | ``` 181 | 182 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 4.0.0 6 | 7 | org.example.redis 8 | spring-redis-mq 9 | 1.0-SNAPSHOT 10 | pom 11 | 12 | 13 | redis-mq-test 14 | springboot-starter-redis-mq 15 | 16 | 17 | -------------------------------------------------------------------------------- /redis-mq-test/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 4.0.0 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 2.5.7 10 | 11 | 12 | org.example.redis.mq.test 13 | redis-mq-test 14 | 1.0-SNAPSHOT 15 | redis-mq-test 16 | 17 | 18 | UTF-8 19 | 1.8 20 | 1.8 21 | 22 | 23 | 24 | 25 | com.jdragon.starter 26 | springboot-starter-redis-mq 27 | 1.0-SNAPSHOT 28 | 29 | 30 | 31 | junit 32 | junit 33 | 4.11 34 | test 35 | 36 | 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-data-redis 41 | 42 | 43 | 44 | org.springframework.data 45 | spring-data-redis 46 | 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-starter-web 51 | 52 | 53 | 54 | org.springframework.boot 55 | spring-boot-starter-test 56 | test 57 | 58 | 59 | 60 | org.projectlombok 61 | lombok 62 | 1.18.2 63 | 64 | 65 | 66 | io.springfox 67 | springfox-boot-starter 68 | 3.0.0 69 | 70 | 71 | 72 | com.github.xiaoymin 73 | knife4j-spring-boot-starter 74 | 3.0.2 75 | 76 | 77 | org.apache.commons 78 | commons-pool2 79 | 80 | 81 | 82 | com.fasterxml.jackson.datatype 83 | jackson-datatype-jsr310 84 | 85 | 86 | 87 | 88 | 89 | org.springframework.boot 90 | spring-boot-maven-plugin 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /redis-mq-test/src/main/java/org/example/redis/mq/test/SpringRedisMq.java: -------------------------------------------------------------------------------- 1 | package org.example.redis.mq.test; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * Hello world! 8 | */ 9 | 10 | @SpringBootApplication 11 | public class SpringRedisMq { 12 | public static void main(String[] args) { 13 | SpringApplication.run(SpringRedisMq.class); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /redis-mq-test/src/main/java/org/example/redis/mq/test/config/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package org.example.redis.mq.test.config; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 4 | import com.fasterxml.jackson.annotation.PropertyAccessor; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.cache.annotation.CachingConfigurerSupport; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.data.redis.connection.RedisConnectionFactory; 11 | import org.springframework.data.redis.core.RedisTemplate; 12 | import org.springframework.data.redis.core.StringRedisTemplate; 13 | import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; 14 | 15 | @Slf4j 16 | @Configuration 17 | public class RedisConfig extends CachingConfigurerSupport { 18 | 19 | /** 20 | * 默认是JDK的序列化策略,这里配置redisTemplate采用的是Jackson2JsonRedisSerializer的序列化策略 21 | * 22 | * @param redisConnectionFactory 23 | * @return 24 | */ 25 | @Bean 26 | public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { 27 | //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式) 28 | Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); 29 | ObjectMapper om = new ObjectMapper(); 30 | // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public 31 | om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 32 | // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会抛出异常 33 | om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); 34 | jackson2JsonRedisSerializer.setObjectMapper(om); 35 | RedisTemplate redisTemplate = new RedisTemplate<>(); 36 | // 配置连接工厂 37 | redisTemplate.setConnectionFactory(redisConnectionFactory); 38 | //使用StringRedisSerializer来序列化和反序列化redis的key值 39 | //redisTemplate.setKeySerializer(new StringRedisSerializer()); 40 | redisTemplate.setKeySerializer(jackson2JsonRedisSerializer); 41 | // 值采用json序列化 42 | redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); 43 | redisTemplate.setHashKeySerializer(jackson2JsonRedisSerializer); 44 | redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); 45 | redisTemplate.afterPropertiesSet(); 46 | return redisTemplate; 47 | } 48 | 49 | /*** 50 | * stringRedisTemplate默认采用的是String的序列化策略 51 | * @param redisConnectionFactory 52 | * @return 53 | */ 54 | @Bean 55 | public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) { 56 | StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(); 57 | stringRedisTemplate.setConnectionFactory(redisConnectionFactory); 58 | return stringRedisTemplate; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /redis-mq-test/src/main/java/org/example/redis/mq/test/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package org.example.redis.mq.test.config; 2 | 3 | import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 7 | import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; 8 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 9 | import springfox.documentation.builders.ApiInfoBuilder; 10 | import springfox.documentation.builders.PathSelectors; 11 | import springfox.documentation.builders.RequestHandlerSelectors; 12 | import springfox.documentation.oas.annotations.EnableOpenApi; 13 | import springfox.documentation.service.ApiInfo; 14 | import springfox.documentation.spi.DocumentationType; 15 | import springfox.documentation.spring.web.plugins.Docket; 16 | 17 | /** 18 | * @Author: Jdragon 19 | * @email: 1061917196@qq.com 20 | * @Date: 2020.03.18 21:30 21 | * @Description: swagger2的api文档配置 22 | */ 23 | @Configuration 24 | @EnableKnife4j 25 | @EnableOpenApi 26 | public class SwaggerConfig implements WebMvcConfigurer { 27 | 28 | @Bean 29 | public Docket createRestApi() { 30 | return new Docket(DocumentationType.OAS_30) 31 | .apiInfo(apiInfo()) 32 | .select() 33 | .apis(RequestHandlerSelectors.basePackage("org.example")) 34 | .paths(PathSelectors.any()) 35 | .build(); 36 | } 37 | 38 | private ApiInfo apiInfo() { 39 | return new ApiInfoBuilder() 40 | .title("spring redis") 41 | .description("spring redis") 42 | .version("1.0.0") 43 | .build(); 44 | } 45 | 46 | @Override 47 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 48 | registry.addResourceHandler("/**").addResourceLocations("classpath:/static/"); 49 | registry.addResourceHandler("swagger-ui.html") 50 | .addResourceLocations("classpath:/META-INF/resources/"); 51 | registry.addResourceHandler("/webjars/**") 52 | .addResourceLocations("classpath:/META-INF/resources/webjars/"); 53 | 54 | registry.addResourceHandler("doc.html") 55 | .addResourceLocations("classpath:/META-INF/resources/"); 56 | } 57 | @Override 58 | public void addViewControllers(ViewControllerRegistry registry) { 59 | // registry.addRedirectViewController("/", "/doc.html"); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /redis-mq-test/src/main/java/org/example/redis/mq/test/controller/TestController.java: -------------------------------------------------------------------------------- 1 | package org.example.redis.mq.test.controller; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import io.swagger.annotations.Api; 6 | import io.swagger.annotations.ApiOperation; 7 | import lombok.extern.slf4j.Slf4j; 8 | import com.jdragon.starter.redis.mq.core.RedisMQSender; 9 | import org.example.redis.mq.test.entity.Job; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.data.redis.connection.stream.ObjectRecord; 12 | import org.springframework.data.redis.connection.stream.StreamRecords; 13 | import org.springframework.data.redis.core.StringRedisTemplate; 14 | import org.springframework.web.bind.annotation.GetMapping; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RestController; 17 | 18 | import java.time.LocalDateTime; 19 | import java.util.Date; 20 | import java.util.HashMap; 21 | 22 | /** 23 | * @Author JDragon 24 | * @Date 2021.12.16 下午 4:47 25 | * @Email 1061917196@qq.com 26 | * @Des: 27 | */ 28 | 29 | @Api 30 | @Slf4j 31 | @RestController 32 | @RequestMapping("test") 33 | public class TestController { 34 | 35 | @Autowired 36 | private ObjectMapper objectMapper; 37 | 38 | @Autowired 39 | private StringRedisTemplate stringRedisTemplate; 40 | 41 | @GetMapping("stream") 42 | @ApiOperation("stream") 43 | public String stream() throws JsonProcessingException { 44 | String streamKey = "streamKey2"; 45 | Job job = new Job(); 46 | job.setId(1); 47 | job.setParam(new HashMap<>()); 48 | job.setCreateDate(new Date()); 49 | job.setEndDate(LocalDateTime.now()); 50 | String json = objectMapper.writeValueAsString(job); 51 | ObjectRecord objectRedisMessageObjectRecord = StreamRecords.objectBacked(json) 52 | .withStreamKey(streamKey); 53 | stringRedisTemplate.opsForStream().add(objectRedisMessageObjectRecord); 54 | return "成功"; 55 | } 56 | 57 | @Autowired 58 | private RedisMQSender redisMQSender; 59 | 60 | @GetMapping("redisMQSender") 61 | @ApiOperation("redisMQSender") 62 | public String redisMQSender() { 63 | String streamKey = "streamKey"; 64 | redisMQSender.send(streamKey, "你好"); 65 | return "成功"; 66 | } 67 | 68 | @GetMapping("redisMQSender2") 69 | @ApiOperation("redisMQSender2") 70 | public String redisMQSender2() { 71 | String streamKey = "streamKey2"; 72 | 73 | HashMap param = new HashMap<>(); 74 | param.put("id", 1); 75 | param.put("name", "张三"); 76 | param.put("paramInline", new HashMap<>()); 77 | 78 | Job job = new Job(); 79 | job.setId(1); 80 | job.setParam(param); 81 | job.setCreateDate(new Date()); 82 | job.setEndDate(LocalDateTime.now()); 83 | redisMQSender.send(streamKey, job); 84 | return "成功"; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /redis-mq-test/src/main/java/org/example/redis/mq/test/entity/Job.java: -------------------------------------------------------------------------------- 1 | package org.example.redis.mq.test.entity; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 5 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 6 | import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; 7 | import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; 8 | import lombok.Data; 9 | 10 | import java.io.Serializable; 11 | import java.time.LocalDateTime; 12 | import java.util.Date; 13 | import java.util.Map; 14 | 15 | /** 16 | * @Author JDragon 17 | * @Date 2021.12.17 上午 11:29 18 | * @Email 1061917196@qq.com 19 | * @Des: 20 | */ 21 | @Data 22 | public class Job implements Serializable { 23 | private Integer id; 24 | 25 | private Map param; 26 | 27 | @JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss") 28 | private Date createDate; 29 | 30 | @JsonSerialize(using = LocalDateTimeSerializer.class) 31 | @JsonDeserialize(using = LocalDateTimeDeserializer.class) 32 | private LocalDateTime endDate; 33 | } 34 | -------------------------------------------------------------------------------- /redis-mq-test/src/main/java/org/example/redis/mq/test/listener/RedisMQListener.java: -------------------------------------------------------------------------------- 1 | package org.example.redis.mq.test.listener; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import com.jdragon.starter.redis.mq.annotations.RedisListener; 5 | import com.jdragon.starter.redis.mq.core.domain.RedisMessage; 6 | import org.example.redis.mq.test.entity.Job; 7 | import org.springframework.stereotype.Component; 8 | 9 | /** 10 | * @Author JDragon 11 | * @Date 2021.12.17 上午 11:06 12 | * @Email 1061917196@qq.com 13 | * @Des: 14 | */ 15 | @Slf4j 16 | @Component 17 | public class RedisMQListener { 18 | @RedisListener(queueName = "streamKey") 19 | public void test(RedisMessage redisMessage) { 20 | log.info("redis message 接受到信息:{}", redisMessage.getData()); 21 | } 22 | 23 | @RedisListener(queueName = "streamKey2") 24 | public void test2(RedisMessage job) { 25 | log.info("redis message 接受到信息:{}", job.getData()); 26 | } 27 | 28 | @RedisListener(queueName = "streamKey") 29 | public void test3(String redisMessage) { 30 | log.info("接受到信息:{}", redisMessage); 31 | } 32 | 33 | @RedisListener(queueName = "streamKey2") 34 | public void test4(Job job) { 35 | log.info("接受到信息:{}", job); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /redis-mq-test/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 19091 3 | servlet: 4 | encoding: 5 | force: true 6 | charset: UTF-8 7 | enabled: true 8 | 9 | spring: 10 | jackson: 11 | date-format: yyyy-MM-dd HH:mm:ss 12 | time-zone: GMT+8 13 | serialization: 14 | FAIL_ON_EMPTY_BEANS: false 15 | default-property-inclusion: non_null 16 | redis: 17 | database: 0 18 | host: remotehost 19 | jedis: 20 | pool: 21 | max-active: 8 22 | max-idle: 8 23 | max-wait: -1ms 24 | min-idle: 0 25 | password: '' 26 | port: 9736 27 | 28 | logging: 29 | config: classpath:logback.xml 30 | level: 31 | root: info -------------------------------------------------------------------------------- /redis-mq-test/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%M %line] - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /springboot-starter-redis-mq/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.5.7 9 | 10 | 11 | 4.0.0 12 | 1.0-SNAPSHOT 13 | com.jdragon.starter 14 | springboot-starter-redis-mq 15 | springboot-starter-redis-mq 16 | 17 | 18 | UTF-8 19 | 1.8 20 | 1.8 21 | 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-data-redis 27 | 28 | 29 | 30 | org.springframework.data 31 | spring-data-redis 32 | 33 | 34 | 35 | org.projectlombok 36 | lombok 37 | 1.18.2 38 | 39 | 40 | 41 | com.fasterxml.jackson.datatype 42 | jackson-datatype-jsr310 43 | 44 | 45 | org.springframework 46 | spring-core 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /springboot-starter-redis-mq/src/main/java/com/jdragon/starter/redis/mq/RedisMQListenerAutoConfig.java: -------------------------------------------------------------------------------- 1 | package com.jdragon.starter.redis.mq; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.jdragon.starter.redis.mq.core.RedisListenerAnnotationScanPostProcesser; 5 | import com.jdragon.starter.redis.mq.core.RedisMQSender; 6 | import com.jdragon.starter.redis.mq.core.RedisMessageQueueRegister; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.core.task.SimpleAsyncTaskExecutor; 10 | import org.springframework.data.redis.connection.RedisConnectionFactory; 11 | import org.springframework.data.redis.connection.stream.ObjectRecord; 12 | import org.springframework.data.redis.core.StringRedisTemplate; 13 | import org.springframework.data.redis.stream.StreamMessageListenerContainer; 14 | 15 | import java.time.Duration; 16 | 17 | /** 18 | * @Author JDragon 19 | * @Date 2021.12.17 上午 9:58 20 | * @Email 1061917196@qq.com 21 | * @Des: 22 | */ 23 | 24 | @Configuration 25 | //@ConditionalOnBean(RedisConnectionFactory.class) 26 | //@ConditionalOnProperty(prefix = "redis.queue.listener", name = "enable", havingValue = "true", matchIfMissing = true) 27 | public class RedisMQListenerAutoConfig { 28 | 29 | @Bean 30 | public RedisListenerAnnotationScanPostProcesser redisListenerAnnotationScanPostProcesser() { 31 | return new RedisListenerAnnotationScanPostProcesser(); 32 | } 33 | 34 | @Bean 35 | public StringRedisTemplate redisMQTemplate(RedisConnectionFactory redisConnectionFactory) { 36 | StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(); 37 | stringRedisTemplate.setConnectionFactory(redisConnectionFactory); 38 | return stringRedisTemplate; 39 | } 40 | 41 | @Bean 42 | public RedisMessageQueueRegister redisMessageQueueRegister() { 43 | return new RedisMessageQueueRegister(); 44 | } 45 | 46 | 47 | @Bean 48 | public RedisMQSender redisMQSender(StringRedisTemplate redisMQTemplate , ObjectMapper objectMapper) { 49 | return new RedisMQSender(redisMQTemplate, objectMapper); 50 | } 51 | 52 | @Bean 53 | public StreamMessageListenerContainer> streamMessageListenerContainer(RedisConnectionFactory redisConnectionFactory) { 54 | StreamMessageListenerContainer.StreamMessageListenerContainerOptions> options = StreamMessageListenerContainer.StreamMessageListenerContainerOptions 55 | .builder() 56 | .pollTimeout(Duration.ofSeconds(2L)) 57 | .targetType(String.class) 58 | .executor(new SimpleAsyncTaskExecutor()) 59 | .build(); 60 | 61 | StreamMessageListenerContainer> stringMapRecordStreamMessageListenerContainer = 62 | StreamMessageListenerContainer.create(redisConnectionFactory, options); 63 | stringMapRecordStreamMessageListenerContainer.start(); 64 | return stringMapRecordStreamMessageListenerContainer; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /springboot-starter-redis-mq/src/main/java/com/jdragon/starter/redis/mq/annotations/RedisListener.java: -------------------------------------------------------------------------------- 1 | package com.jdragon.starter.redis.mq.annotations; 2 | 3 | import org.springframework.core.annotation.AliasFor; 4 | 5 | import java.lang.annotation.Documented; 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | /** 12 | * 13 | */ 14 | @Target(ElementType.METHOD) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Documented 17 | public @interface RedisListener { 18 | 19 | @AliasFor("queueName") 20 | String value() default "default_queue"; 21 | 22 | @AliasFor("value") 23 | String queueName() default "default_queue"; 24 | 25 | String group() default "default_group"; 26 | 27 | String consumer() default "default_consumer"; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /springboot-starter-redis-mq/src/main/java/com/jdragon/starter/redis/mq/core/RedisListenerAnnotationScanPostProcesser.java: -------------------------------------------------------------------------------- 1 | package com.jdragon.starter.redis.mq.core; 2 | 3 | import com.jdragon.starter.redis.mq.annotations.RedisListener; 4 | import com.jdragon.starter.redis.mq.core.domain.RedisListenerMethod; 5 | import org.springframework.beans.BeansException; 6 | import org.springframework.beans.factory.config.BeanPostProcessor; 7 | import org.springframework.core.annotation.AnnotatedElementUtils; 8 | import org.springframework.core.annotation.AnnotationAttributes; 9 | import org.springframework.util.ReflectionUtils; 10 | 11 | import java.lang.reflect.Method; 12 | import java.lang.reflect.Type; 13 | import java.util.HashMap; 14 | import java.util.LinkedList; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | /** 19 | * 注册redis消息队列处理方法,@RedisListener注解扫描器 20 | */ 21 | public class RedisListenerAnnotationScanPostProcesser implements BeanPostProcessor { 22 | 23 | private static final Map> candidates = new HashMap<>(); 24 | 25 | @Override 26 | public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { 27 | Class clazz = bean.getClass(); 28 | Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz); 29 | for (Method method : methods) { 30 | AnnotationAttributes annotationAttributes = AnnotatedElementUtils 31 | .findMergedAnnotationAttributes(method, RedisListener.class, false, false); 32 | if (null != annotationAttributes) { 33 | Class[] parameterTypes = method.getParameterTypes(); 34 | // if (parameterTypes.length == 1 && RedisMessage.class.isAssignableFrom(parameterTypes[0])) { 35 | if (parameterTypes.length == 1) { 36 | String queueName = (String) annotationAttributes.get("queueName"); 37 | String group = (String) annotationAttributes.get("group"); 38 | String consumer = (String) annotationAttributes.get("consumer"); 39 | Type[] genericParameterTypes = method.getGenericParameterTypes(); 40 | RedisListenerMethod rlm = new RedisListenerMethod(); 41 | rlm.setBeanName(beanName); 42 | rlm.setTargetMethod(method); 43 | rlm.setMethodParameterClassName(parameterTypes[0].getName()); 44 | rlm.setParameterClass(parameterTypes[0]); 45 | rlm.setParameterType(genericParameterTypes[0]); 46 | String key = queueName + "-" + group + "-" + consumer; 47 | if (!candidates.containsKey(key)) { 48 | candidates.put(key, new LinkedList<>()); 49 | } 50 | candidates.get(key).add(rlm); 51 | } else { 52 | throw new RuntimeException("有@RedisListener注解的方法有且仅能包含一个参数"); 53 | } 54 | } 55 | } 56 | return bean; 57 | } 58 | 59 | @Override 60 | public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { 61 | return bean; 62 | } 63 | 64 | public static Map> getCandidates() { 65 | return candidates; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /springboot-starter-redis-mq/src/main/java/com/jdragon/starter/redis/mq/core/RedisMQSender.java: -------------------------------------------------------------------------------- 1 | package com.jdragon.starter.redis.mq.core; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.jdragon.starter.redis.mq.core.domain.RedisMessage; 6 | import org.springframework.data.redis.connection.stream.ObjectRecord; 7 | import org.springframework.data.redis.connection.stream.StreamRecords; 8 | import org.springframework.data.redis.core.StringRedisTemplate; 9 | 10 | import java.util.Date; 11 | 12 | public class RedisMQSender { 13 | 14 | private final StringRedisTemplate redisTemplate; 15 | 16 | private final ObjectMapper objectMapper; 17 | 18 | public RedisMQSender(StringRedisTemplate redisTemplate, ObjectMapper objectMapper) { 19 | this.redisTemplate = redisTemplate; 20 | this.objectMapper = objectMapper; 21 | } 22 | 23 | public void send(String queueName, T object) { 24 | if (object == null) return; 25 | try { 26 | RedisMessage redisMessage = new RedisMessage<>(); 27 | redisMessage.setQueueName(queueName); 28 | redisMessage.setCreateTime(new Date()); 29 | redisMessage.setData(object); 30 | String json = objectMapper.writeValueAsString(redisMessage); 31 | ObjectRecord objectRedisMessageObjectRecord = StreamRecords.objectBacked(json) 32 | .withStreamKey(queueName); 33 | redisTemplate.opsForStream().add(objectRedisMessageObjectRecord); 34 | } catch (JsonProcessingException e) { 35 | e.printStackTrace(); 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /springboot-starter-redis-mq/src/main/java/com/jdragon/starter/redis/mq/core/RedisMessageQueueRegister.java: -------------------------------------------------------------------------------- 1 | package com.jdragon.starter.redis.mq.core; 2 | 3 | import com.fasterxml.jackson.databind.JavaType; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.jdragon.starter.redis.mq.core.domain.RedisListenerMethod; 6 | import com.jdragon.starter.redis.mq.core.domain.RedisMessage; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.BeansException; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.beans.factory.annotation.Qualifier; 12 | import org.springframework.boot.ApplicationArguments; 13 | import org.springframework.boot.ApplicationRunner; 14 | import org.springframework.context.ApplicationContext; 15 | import org.springframework.context.ApplicationContextAware; 16 | import org.springframework.data.redis.connection.stream.Consumer; 17 | import org.springframework.data.redis.connection.stream.ObjectRecord; 18 | import org.springframework.data.redis.connection.stream.ReadOffset; 19 | import org.springframework.data.redis.connection.stream.StreamInfo; 20 | import org.springframework.data.redis.connection.stream.StreamOffset; 21 | import org.springframework.data.redis.core.StringRedisTemplate; 22 | import org.springframework.data.redis.stream.StreamMessageListenerContainer; 23 | 24 | import java.io.IOException; 25 | import java.lang.reflect.InvocationTargetException; 26 | import java.lang.reflect.Method; 27 | import java.util.List; 28 | import java.util.Map; 29 | import java.util.stream.Collectors; 30 | 31 | public class RedisMessageQueueRegister implements ApplicationRunner, ApplicationContextAware { 32 | 33 | private final Logger logger = LoggerFactory.getLogger(RedisMessageQueueRegister.class); 34 | 35 | private ApplicationContext applicationContext; 36 | 37 | @Autowired 38 | @Qualifier("redisMQTemplate") 39 | private StringRedisTemplate redisTemplate; 40 | 41 | @Autowired 42 | private StreamMessageListenerContainer> streamMessageListenerContainer; 43 | 44 | @Autowired 45 | private ObjectMapper objectMapper; 46 | 47 | @Override 48 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 49 | this.applicationContext = applicationContext; 50 | } 51 | 52 | @Override 53 | public void run(ApplicationArguments args) throws Exception { 54 | // 启动redis消息队列监听器 55 | Map> candidates = RedisListenerAnnotationScanPostProcesser.getCandidates(); 56 | 57 | for (Map.Entry> entry : candidates.entrySet()) { 58 | String queueKey = entry.getKey(); 59 | List redisListenerMethodList = entry.getValue(); 60 | String[] split = queueKey.split("-"); 61 | String streamKey = split[0]; 62 | String consumerGroupName = split[1]; 63 | String consumerName = split[2]; 64 | try { 65 | List groupName = redisTemplate.opsForStream().groups(streamKey).stream().map(StreamInfo.XInfoGroup::groupName).collect(Collectors.toList()); 66 | if (!groupName.contains(consumerGroupName)) { 67 | redisTemplate.opsForStream().createGroup(streamKey, consumerGroupName); 68 | } 69 | } catch (Exception e) { 70 | redisTemplate.opsForStream().createGroup(streamKey, consumerGroupName); 71 | } 72 | 73 | streamMessageListenerContainer.receive( 74 | Consumer.from(consumerGroupName, consumerName), 75 | StreamOffset.create(streamKey, ReadOffset.lastConsumed()), 76 | message -> { 77 | try { 78 | logger.info("stream message。messageId={}, stream={}, body={}", 79 | message.getId(), message.getStream(), message.getValue()); 80 | String messageJson = message.getValue(); 81 | for (RedisListenerMethod rlm : redisListenerMethodList) { 82 | Method targetMethod = rlm.getTargetMethod(); 83 | try { 84 | if (rlm.getMethodParameterClassName().equals(RedisMessage.class.getName())) { 85 | RedisMessage redisMessage = objectMapper.readValue(messageJson, RedisMessage.class); 86 | targetMethod.invoke(rlm.getBean(applicationContext), redisMessage); 87 | } else { 88 | JavaType javaType = objectMapper.getTypeFactory().constructParametricType(RedisMessage.class, rlm.getParameterClass()); 89 | RedisMessage redisMessage = objectMapper.readValue(messageJson, javaType); 90 | targetMethod.invoke(rlm.getBean(applicationContext), redisMessage.getData()); 91 | } 92 | } catch (IllegalAccessException | InvocationTargetException | IOException e) { 93 | e.printStackTrace(); 94 | } 95 | } 96 | redisTemplate.opsForStream().acknowledge(consumerGroupName, message); 97 | } catch (Exception e) { 98 | logger.error("", e); 99 | } 100 | }); 101 | logger.info("启动消息队列监听器:【" + streamKey + "." + consumerGroupName + "】"); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /springboot-starter-redis-mq/src/main/java/com/jdragon/starter/redis/mq/core/domain/RedisListenerMethod.java: -------------------------------------------------------------------------------- 1 | package com.jdragon.starter.redis.mq.core.domain; 2 | 3 | import org.springframework.context.ApplicationContext; 4 | 5 | import java.lang.reflect.Method; 6 | import java.lang.reflect.Type; 7 | 8 | public class RedisListenerMethod { 9 | private Object bean; 10 | 11 | private String beanName; 12 | 13 | private Method targetMethod; 14 | 15 | private String methodParameterClassName; 16 | 17 | private Class parameterClass; 18 | 19 | private Type parameterType; 20 | 21 | 22 | public String getBeanName() { 23 | return beanName; 24 | } 25 | 26 | public void setBeanName(String beanName) { 27 | this.beanName = beanName; 28 | } 29 | 30 | public Method getTargetMethod() { 31 | return targetMethod; 32 | } 33 | 34 | public void setTargetMethod(Method targetMethod) { 35 | this.targetMethod = targetMethod; 36 | } 37 | 38 | public void setParameterClass(Class parameterClass){ 39 | this.parameterClass = parameterClass; 40 | } 41 | 42 | public Class getParameterClass() { 43 | return parameterClass; 44 | } 45 | 46 | public void setParameterType(Type parameterType) { 47 | this.parameterType = parameterType; 48 | } 49 | 50 | public Type getParameterType() { 51 | return parameterType; 52 | } 53 | 54 | public Object getBean(ApplicationContext applicationContext) { 55 | if (bean == null) { 56 | synchronized (this) { 57 | if (bean == null) { 58 | bean = applicationContext.getBean(beanName); 59 | if (bean == null) { 60 | throw new RuntimeException("获取包含@RedisLister[" + targetMethod.getName() + "]方法的Bean实例失败"); 61 | } 62 | } 63 | } 64 | } 65 | return bean; 66 | } 67 | 68 | public void setBean(Object bean) { 69 | this.bean = bean; 70 | } 71 | 72 | 73 | public String getMethodParameterClassName() { 74 | return methodParameterClassName; 75 | } 76 | 77 | public void setMethodParameterClassName(String methodParameterClassName) { 78 | this.methodParameterClassName = methodParameterClassName; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /springboot-starter-redis-mq/src/main/java/com/jdragon/starter/redis/mq/core/domain/RedisMessage.java: -------------------------------------------------------------------------------- 1 | package com.jdragon.starter.redis.mq.core.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.ToString; 5 | 6 | import java.io.Serializable; 7 | import java.util.Date; 8 | 9 | @ToString 10 | public class RedisMessage implements Serializable { 11 | 12 | private static final long serialVersionUID = 1L; 13 | 14 | private String queueName; 15 | 16 | private String msg; 17 | 18 | private T data; 19 | 20 | @JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss") 21 | private Date createTime; 22 | 23 | public String getQueueName() { 24 | return queueName; 25 | } 26 | 27 | public void setQueueName(String queueName) { 28 | this.queueName = queueName; 29 | } 30 | 31 | public String getMsg() { 32 | return msg; 33 | } 34 | 35 | public void setMsg(String msg) { 36 | this.msg = msg; 37 | } 38 | 39 | public T getData() { 40 | return data; 41 | } 42 | 43 | public void setData(T data) { 44 | this.data = data; 45 | } 46 | 47 | public Date getCreateTime() { 48 | return createTime; 49 | } 50 | 51 | public void setCreateTime(Date createTime) { 52 | this.createTime = createTime; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /springboot-starter-redis-mq/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | # Auto Configure 2 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.jdragon.starter.redis.mq.RedisMQListenerAutoConfig 3 | --------------------------------------------------------------------------------