From d87a7fe28e87a39bc06506b21542daa3c2ded6c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=AD=E9=87=91=E5=9F=8E=20=28GSean=29?= Date: Tue, 16 Nov 2021 19:29:02 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(rocketmq):=20rocketmq=E9=9B=86?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../distribuited-lock-demos/pom.xml | 1 + .../redisson-sample/pom.xml | 35 +++++++++ .../com/gsean/redisson/RedissonSampleApp.java | 19 +++++ .../gsean/redisson/config/RedisConfig.java | 45 +++++++++++ .../gsean/redisson/service/UserService.java | 15 ++++ .../com/gsean/redisson/utils/LockUtil.java | 69 +++++++++++++++++ .../src/main/resources/application.yml | 9 +++ .../gsean/redisson/RedissonSampleAppTest.java | 43 +++++++++++ .../rocketmq-demos/rocketmq-sample/pom.xml | 1 + .../rocket/config/MessageQueueChannels.java | 16 ++-- .../gsean/rocket/config/QueueConsumer.java | 75 +++++++++++-------- .../gsean/rocket/config/QueueProducer.java | 22 ++---- .../com/gsean/rocket/dto/UserPayload.java | 21 ++++++ .../src/main/resources/application-v1.yml | 31 ++++++++ .../src/main/resources/application.yml | 49 +----------- .../test/java/com/gsean/rocket/MyTest.java | 25 +++++++ .../com/gsean/rocket/RocketMqAppTest.java | 18 ++++- .../com/gsean/redis/config/RedisConfig.java | 3 + 18 files changed, 391 insertions(+), 106 deletions(-) create mode 100644 distributed-demos/distribuited-lock-demos/redisson-sample/pom.xml create mode 100644 distributed-demos/distribuited-lock-demos/redisson-sample/src/main/java/com/gsean/redisson/RedissonSampleApp.java create mode 100644 distributed-demos/distribuited-lock-demos/redisson-sample/src/main/java/com/gsean/redisson/config/RedisConfig.java create mode 100644 distributed-demos/distribuited-lock-demos/redisson-sample/src/main/java/com/gsean/redisson/service/UserService.java create mode 100644 distributed-demos/distribuited-lock-demos/redisson-sample/src/main/java/com/gsean/redisson/utils/LockUtil.java create mode 100644 distributed-demos/distribuited-lock-demos/redisson-sample/src/main/resources/application.yml create mode 100644 distributed-demos/distribuited-lock-demos/redisson-sample/src/test/java/com/gsean/redisson/RedissonSampleAppTest.java create mode 100644 mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/dto/UserPayload.java create mode 100644 mq-demos/rocketmq-demos/rocketmq-sample/src/main/resources/application-v1.yml create mode 100644 mq-demos/rocketmq-demos/rocketmq-sample/src/test/java/com/gsean/rocket/MyTest.java diff --git a/distributed-demos/distribuited-lock-demos/pom.xml b/distributed-demos/distribuited-lock-demos/pom.xml index 4ebba5f..6a262a8 100644 --- a/distributed-demos/distribuited-lock-demos/pom.xml +++ b/distributed-demos/distribuited-lock-demos/pom.xml @@ -13,6 +13,7 @@ pom redis-lock-demo + redisson-sample diff --git a/distributed-demos/distribuited-lock-demos/redisson-sample/pom.xml b/distributed-demos/distribuited-lock-demos/redisson-sample/pom.xml new file mode 100644 index 0000000..12b30a3 --- /dev/null +++ b/distributed-demos/distribuited-lock-demos/redisson-sample/pom.xml @@ -0,0 +1,35 @@ + + + + distribuited-lock-demos + com.gsean.demos + 1.0-SNAPSHOT + + 4.0.0 + + redisson-sample + + + 8 + 8 + + + + + org.redisson + redisson + 3.15.6 + + + org.springframework.boot + spring-boot-starter-data-redis + + + com.alibaba + fastjson + + + + \ No newline at end of file diff --git a/distributed-demos/distribuited-lock-demos/redisson-sample/src/main/java/com/gsean/redisson/RedissonSampleApp.java b/distributed-demos/distribuited-lock-demos/redisson-sample/src/main/java/com/gsean/redisson/RedissonSampleApp.java new file mode 100644 index 0000000..052d662 --- /dev/null +++ b/distributed-demos/distribuited-lock-demos/redisson-sample/src/main/java/com/gsean/redisson/RedissonSampleApp.java @@ -0,0 +1,19 @@ +package com.gsean.redisson; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @authoer Jincheng.Guo11 + * @description + * @date:created in 2021/11/15 16:50 + * @modificed by + **/ +@SpringBootApplication +public class RedissonSampleApp { + + public static void main(String[] args) { + SpringApplication.run(RedissonSampleApp.class,args); + } + +} diff --git a/distributed-demos/distribuited-lock-demos/redisson-sample/src/main/java/com/gsean/redisson/config/RedisConfig.java b/distributed-demos/distribuited-lock-demos/redisson-sample/src/main/java/com/gsean/redisson/config/RedisConfig.java new file mode 100644 index 0000000..8631a15 --- /dev/null +++ b/distributed-demos/distribuited-lock-demos/redisson-sample/src/main/java/com/gsean/redisson/config/RedisConfig.java @@ -0,0 +1,45 @@ +package com.gsean.redisson.config; + +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.SingleServerConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +/** + * @authoer Jincheng.Guo11 + * @description + * @date:created in 2021/11/15 17:46 + * @modificed by + **/ +@Configuration +public class RedisConfig { + + @Autowired + private Environment env; + + /** + * 自定义注入配置操作Redisson的客户端实例 + * @return + */ + @Bean + public RedissonClient config(){ + //创建配置实例 + Config config=new Config(); + //可以设置传输模式为EPOLL,也可以设置为NIO等等 + //config.setTransportMode(TransportMode.NIO); + //设置服务节点部署模式:集群模式;单一节点模式;主从模式;哨兵模式等等 + //config.useClusterServers().addNodeAddress(env.getProperty("redisson.host.config"),env.getProperty("redisson.host.config")); + config + .useSingleServer() + .setAddress(env.getProperty("redisson.host.config")) + .setPassword(env.getProperty("spring.redis.password")) + .setKeepAlive(true); + //创建并返回操作Redisson的客户端实例 + return Redisson.create(config); + } + +} diff --git a/distributed-demos/distribuited-lock-demos/redisson-sample/src/main/java/com/gsean/redisson/service/UserService.java b/distributed-demos/distribuited-lock-demos/redisson-sample/src/main/java/com/gsean/redisson/service/UserService.java new file mode 100644 index 0000000..dc3b4b7 --- /dev/null +++ b/distributed-demos/distribuited-lock-demos/redisson-sample/src/main/java/com/gsean/redisson/service/UserService.java @@ -0,0 +1,15 @@ +package com.gsean.redisson.service; + +/** + * @authoer Jincheng.Guo11 + * @description + * @date:created in 2021/11/15 18:33 + * @modificed by + **/ +public interface UserService { + + + + Long addUserAndGet(); + +} diff --git a/distributed-demos/distribuited-lock-demos/redisson-sample/src/main/java/com/gsean/redisson/utils/LockUtil.java b/distributed-demos/distribuited-lock-demos/redisson-sample/src/main/java/com/gsean/redisson/utils/LockUtil.java new file mode 100644 index 0000000..af5e5f4 --- /dev/null +++ b/distributed-demos/distribuited-lock-demos/redisson-sample/src/main/java/com/gsean/redisson/utils/LockUtil.java @@ -0,0 +1,69 @@ +package com.gsean.redisson.utils; + +import java.util.concurrent.TimeUnit; +import javax.xml.ws.RequestWrapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * @author linxin.xiong + * @description 分布式锁工具类 + * @date created in 2021/6/21 11:22 + * @modificed by + */ +@Slf4j +@Component +public class LockUtil { + + private static RedissonClient redissonClient; + + @Autowired + public void setRedissonClient(RedissonClient redissonClient) { + LockUtil.redissonClient = redissonClient; + } + + + /** + * 获取分布式redis锁并执行runnable. + * 多个线程获取锁失败时会等待指定时间,仍未获取到锁则跳过执行. + * + * @param lockName lockName + * @param runnable operations after locked + * @param waitTime the maximum time to acquire the lock + * @param leaseTime lock expiration time + */ + public static void tryLockAndExecute(String lockName, Runnable runnable, long waitTime, long leaseTime) { + RLock lock = redissonClient.getLock(lockName); + boolean locked = false; + try { + locked = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS); + } catch (InterruptedException ex) { + log.error("LockUtil#tryLockAndExecute() interrupted... lockName={}", lockName, ex); + Thread.currentThread().interrupt(); + } + if (locked) { + // 不捕获异常,由调用层控制吞并异常或事务回滚 + try { + runnable.run(); + } finally { + lock.unlock(); + } + } + } + + /** + * 获取分布式redis锁并执行runnable. + * 多个线程获取锁失败时没有等待时间,直接跳过,适用于只需获取一次锁的场景,如定时任务. + * + * @param lockName lockName + * @param runnable operations after locked + */ + public static void tryLockWithoutWaiting(String lockName, Runnable runnable) { + tryLockAndExecute(lockName,runnable,0,60); + } + +} diff --git a/distributed-demos/distribuited-lock-demos/redisson-sample/src/main/resources/application.yml b/distributed-demos/distribuited-lock-demos/redisson-sample/src/main/resources/application.yml new file mode 100644 index 0000000..aeb4681 --- /dev/null +++ b/distributed-demos/distribuited-lock-demos/redisson-sample/src/main/resources/application.yml @@ -0,0 +1,9 @@ +redisson: + host: + config: redis://127.0.0.1:6379 + +spring: + redis: + password: 123456 + + diff --git a/distributed-demos/distribuited-lock-demos/redisson-sample/src/test/java/com/gsean/redisson/RedissonSampleAppTest.java b/distributed-demos/distribuited-lock-demos/redisson-sample/src/test/java/com/gsean/redisson/RedissonSampleAppTest.java new file mode 100644 index 0000000..614e828 --- /dev/null +++ b/distributed-demos/distribuited-lock-demos/redisson-sample/src/test/java/com/gsean/redisson/RedissonSampleAppTest.java @@ -0,0 +1,43 @@ +package com.gsean.redisson; + +import static org.junit.Assert.*; + +import com.alibaba.fastjson.JSON; +import io.lettuce.core.RedisClient; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.redisson.api.RedissonClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @authoer Jincheng.Guo11 + * @description + * @date:created in 2021/11/15 17:55 + * @modificed by + **/ +@SpringBootTest +@RunWith(SpringJUnit4ClassRunner.class) +public class RedissonSampleAppTest { + + + @Autowired + private RedissonClient redissonClient; + + + /** + * + * 获取配置 + * @author: Jincheng.Guo11 + * @date: 2021/11/15 18:09 + * @return: void + * @throws: java.lang.Exception + * @modificed by: + */ + @Test + public void testA(){ + System.out.println(JSON.toJSONString(redissonClient.getConfig())); + } + +} \ No newline at end of file diff --git a/mq-demos/rocketmq-demos/rocketmq-sample/pom.xml b/mq-demos/rocketmq-demos/rocketmq-sample/pom.xml index db7e13d..e181695 100644 --- a/mq-demos/rocketmq-demos/rocketmq-sample/pom.xml +++ b/mq-demos/rocketmq-demos/rocketmq-sample/pom.xml @@ -27,6 +27,7 @@ org.springframework.boot spring-boot-starter-web + \ No newline at end of file diff --git a/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/config/MessageQueueChannels.java b/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/config/MessageQueueChannels.java index f9ec1a6..5371b01 100644 --- a/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/config/MessageQueueChannels.java +++ b/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/config/MessageQueueChannels.java @@ -12,25 +12,25 @@ import org.springframework.messaging.SubscribableChannel; */ public interface MessageQueueChannels { - String WECHAT_CODE_PRE_LOAD_OUTPUT = "gsean-test-output"; - String WECHAT_CODE_PRE_LOAD_INPUT = "gsean-test-input"; + String USER_PAYLOD_OUTPUT = "user-paylod-output"; + String USER_PAYLOD_INPUT = "user-paylod-input"; /** - * 二维码预加载事件发送通道. + * 用户负载事件发送通道. * * @return MessageChannel */ - @Output(WECHAT_CODE_PRE_LOAD_OUTPUT) - MessageChannel wechatCodePreLoadOutput(); + @Output(USER_PAYLOD_OUTPUT) + MessageChannel userPaylodOutput(); /** - * 二维码预加载事件接收通道. + * 用户负载事件接收通道. * * @return SubscribableChannel */ - @Input(WECHAT_CODE_PRE_LOAD_INPUT) - SubscribableChannel wechatCodePreLoadInput(); + @Input(USER_PAYLOD_INPUT) + SubscribableChannel userPayloadInput(); } diff --git a/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/config/QueueConsumer.java b/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/config/QueueConsumer.java index e44dabf..b00d373 100644 --- a/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/config/QueueConsumer.java +++ b/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/config/QueueConsumer.java @@ -1,32 +1,43 @@ -//package com.gsean.rocket.config; -// -//import lombok.RequiredArgsConstructor; -//import lombok.extern.slf4j.Slf4j; -//import org.springframework.cloud.stream.annotation.EnableBinding; -//import org.springframework.cloud.stream.annotation.StreamListener; -//import org.springframework.stereotype.Service; -// -///** -// * 消息队列消费. -// * -// * @author linxin.xiong -// */ -//@Slf4j -//@Service -//@EnableBinding({MessageQueueChannels.class}) -//@RequiredArgsConstructor -//public class QueueConsumer { -// -// -// /** -// * 处理二维码预加载消息. -// * -// * @param message 二维码预加载 -// */ -// @StreamListener(MessageQueueChannels.WECHAT_CODE_PRE_LOAD_INPUT) -// public void wechatCodeUrlPreLoadMessageHandler(String message) { -// System.out.println(message); -// } -// -// -//} +package com.gsean.rocket.config; + +import com.alibaba.fastjson.JSON; +import com.gsean.rocket.dto.UserPayload; +import java.util.function.Consumer; +import java.util.stream.Stream; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cloud.stream.annotation.StreamListener; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.stereotype.Component; + +/** + * 消息队列消费. + * + * @author linxin.xiong + */ +@Slf4j +@Component +public class QueueConsumer { + + + /** + * 用户负载消费消息. + * + * @param message 二维码预加载 + */ + @StreamListener(MessageQueueChannels.USER_PAYLOD_INPUT) + public void userPayloadMessageHandler(@Payload UserPayload message) { + log.info("用户负载消费:{}", JSON.toJSONString(message)); +// if(message.getId().equals(1L)){ +// log.error("消费失败"); +// throw new RuntimeException("消费失败"); +// } + log.info("消费成功"); + } + + + + + + + +} diff --git a/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/config/QueueProducer.java b/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/config/QueueProducer.java index d0b5b21..54202ac 100644 --- a/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/config/QueueProducer.java +++ b/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/config/QueueProducer.java @@ -1,5 +1,6 @@ package com.gsean.rocket.config; +import com.gsean.rocket.dto.UserPayload; import javax.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -15,22 +16,13 @@ import org.springframework.stereotype.Service; @Slf4j @Service public class QueueProducer { + @Autowired + private MessageQueueChannels messageQueueChannels; - @Resource(name = MessageQueueChannels.WECHAT_CODE_PRE_LOAD_OUTPUT) - private MessageChannel wechatCodePreLoadOutput; - -// @Resource(name = com.geely.app.config.rocketmq.MessageQueueChannels.FABULOUS_CREATE_OUTPUT) -// private MessageChannel fabulousCreateOutput; - - public void sendWechatCodePreLoadMessage(Object msg) { - try { - boolean send = wechatCodePreLoadOutput.send(MessageBuilder.withPayload(msg).build()); - if (!send) { - log.error("预生成分享二维码消息发送失败..."); - } - } catch (Exception ex) { - log.error("预生成分享二维码消息发送异常...", ex); - } + public void sendUserPayload(UserPayload payload) { + boolean send = messageQueueChannels.userPaylodOutput() + .send(MessageBuilder.withPayload(payload).build()); + log.info("发送用户负载状态:{}",send); } diff --git a/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/dto/UserPayload.java b/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/dto/UserPayload.java new file mode 100644 index 0000000..a49524d --- /dev/null +++ b/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/dto/UserPayload.java @@ -0,0 +1,21 @@ +package com.gsean.rocket.dto; + +import java.time.LocalDate; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * @authoer Jincheng.Guo11 + * @description + * @date:created in 2021/11/16 14:24 + * @modificed by + **/ +@Data +@Accessors(chain = true) +public class UserPayload { + + private Long id; + private String name; + private LocalDate birth; + +} diff --git a/mq-demos/rocketmq-demos/rocketmq-sample/src/main/resources/application-v1.yml b/mq-demos/rocketmq-demos/rocketmq-sample/src/main/resources/application-v1.yml new file mode 100644 index 0000000..ee56f72 --- /dev/null +++ b/mq-demos/rocketmq-demos/rocketmq-sample/src/main/resources/application-v1.yml @@ -0,0 +1,31 @@ +# RocketMQ配置 简单消息配置 +spring: + cloud: + # Spring Cloud Stream 配置项,对应 BindingServiceProperties 类 + stream: + # Spring Cloud Stream RocketMQ 配置项 + rocketmq: + # RocketMQ Binder 配置项,对应 RocketMQBinderConfigurationProperties 类 + binder: + # RocketMQ Namesrv 地址 + name-server: 10.113.75.163:9876;10.113.75.164:9876; + bindings: + user-paylod-input: + consumer: + enabled: true #默认是true,开发可以false +# broadcasting: true #是否开启广播消费,默认集群消费 +# delay-level-when-next-consume: -1 + + # Binding 配置项,对应 BindingProperties Map + bindings: + #定义name为gsean-test-output的binding + user-paylod-output: + destination: USERPAYLOAD-TOPIC + content-type: application/json + #定义name为gsean-test-input的binding + user-paylod-input: + destination: USERPAYLOAD-TOPIC + group: userpayload-group + content-type: application/json + + diff --git a/mq-demos/rocketmq-demos/rocketmq-sample/src/main/resources/application.yml b/mq-demos/rocketmq-demos/rocketmq-sample/src/main/resources/application.yml index 45b8c22..87f504a 100644 --- a/mq-demos/rocketmq-demos/rocketmq-sample/src/main/resources/application.yml +++ b/mq-demos/rocketmq-demos/rocketmq-sample/src/main/resources/application.yml @@ -1,49 +1,2 @@ - -# 短信验证码请求域名(网关地址) -code: - url: http://gateway.cloud-dev.geega.com - -# 助力分享二维码前端页面地址 -fabulous: - share: - page: "package/pages/questions" - -item: - id: "116800100090001" - -store: - id: "2" - -# 小程序appId和appsecret -wechat: - # appId: wx12927de7e3a4dbf2 - # appsecret: 35555a9e98c806654df1176b9b30d3ce - appId: wxce8456066fec3a6a - appsecret: a7cb5f3890e4f4eb3e856aaad3f61823 - -# RocketMQ配置 -spring: - cloud: - # Spring Cloud Stream 配置项,对应 BindingServiceProperties 类 - stream: - # Spring Cloud Stream RocketMQ 配置项 - rocketmq: - # RocketMQ Binder 配置项,对应 RocketMQBinderConfigurationProperties 类 - binder: - # RocketMQ Namesrv 地址 - name-server: 10.113.75.163:9876;10.113.75.164:9876; - # Binding 配置项,对应 BindingProperties Map - bindings: - #定义name为gsean-test-output的binding - gsean-test-output: - destination: TEST-TOPIC - content-type: application/json - #定义name为gsean-test-input的binding - gsean-test-input: - destination: TEST-TOPIC - group: test-group - content-type: application/json - - server: - port: 8082 + port: ${random.int[10000,19999]} diff --git a/mq-demos/rocketmq-demos/rocketmq-sample/src/test/java/com/gsean/rocket/MyTest.java b/mq-demos/rocketmq-demos/rocketmq-sample/src/test/java/com/gsean/rocket/MyTest.java new file mode 100644 index 0000000..4fa9908 --- /dev/null +++ b/mq-demos/rocketmq-demos/rocketmq-sample/src/test/java/com/gsean/rocket/MyTest.java @@ -0,0 +1,25 @@ +package com.gsean.rocket; + +import org.junit.Rule; +import org.junit.Test; +import org.springframework.test.annotation.Repeat; + +/** + * @authoer Jincheng.Guo11 + * @description + * @date:created in 2021/11/16 14:52 + * @modificed by + **/ +public class MyTest { + + + + + + @Test + @Repeat(3) + public void test1(){ + System.out.println("hello"); + } + +} diff --git a/mq-demos/rocketmq-demos/rocketmq-sample/src/test/java/com/gsean/rocket/RocketMqAppTest.java b/mq-demos/rocketmq-demos/rocketmq-sample/src/test/java/com/gsean/rocket/RocketMqAppTest.java index 6be2ee0..c117b31 100644 --- a/mq-demos/rocketmq-demos/rocketmq-sample/src/test/java/com/gsean/rocket/RocketMqAppTest.java +++ b/mq-demos/rocketmq-demos/rocketmq-sample/src/test/java/com/gsean/rocket/RocketMqAppTest.java @@ -1,10 +1,14 @@ package com.gsean.rocket; import com.gsean.rocket.config.QueueProducer; +import com.gsean.rocket.dto.UserPayload; +import java.time.LocalDate; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; /** @@ -15,14 +19,22 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; **/ @SpringBootTest @ExtendWith(SpringExtension.class) +@ActiveProfiles("v1") class RocketMqAppTest { @Autowired private QueueProducer queueProducer; - @Test - public void testA(){ - queueProducer.sendWechatCodePreLoadMessage("hello gsean"); + + @Test + public void testSendUserPayload(){ + Stream.iterate(0,x->x+1).limit(10).forEachOrdered(item->{ + UserPayload userPayload = new UserPayload().setId(1L).setName("gsean"+item).setBirth(LocalDate.now()); + queueProducer.sendUserPayload(userPayload); + }); } + + + } \ No newline at end of file diff --git a/redis-demos/redis-sampleV2/src/main/java/com/gsean/redis/config/RedisConfig.java b/redis-demos/redis-sampleV2/src/main/java/com/gsean/redis/config/RedisConfig.java index 0ec0022..8e1b379 100644 --- a/redis-demos/redis-sampleV2/src/main/java/com/gsean/redis/config/RedisConfig.java +++ b/redis-demos/redis-sampleV2/src/main/java/com/gsean/redis/config/RedisConfig.java @@ -13,6 +13,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; /** @@ -29,6 +30,8 @@ public class RedisConfig { @Autowired private LettuceConnectionFactory connectionFactory; + + @Bean public RedisTemplate redisTemplate() { RedisTemplate redisTemplate = new RedisTemplate<>(); -- Gitee From 85c24392ec55f0bb3019e046bd943ebd4be8a0a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=AD=E9=87=91=E5=9F=8E=20=28GSean=29?= Date: Mon, 22 Nov 2021 15:25:33 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat(swagger):=20swagger=E6=8F=90=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=9E=9A=E4=B8=BE=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rocket/config/MessageQueueChannels.java | 4 +- .../gsean/rocket/config/QueueProducer.java | 19 +++ .../config/TransactionListenerImpl.java | 36 ++++ .../src/main/resources/application-v1.yml | 10 +- .../src/main/resources/application-v2.yml | 31 ++++ .../src/main/resources/application.yml | 4 + .../com/gsean/rocket/RocketMqAppTest.java | 21 ++- .../swagger/SwaggerSampleApplication.java | 2 - swagger-demo/swagger-ui-demo/pom.xml | 9 + .../EnumModelPropertyBuilderPlugin.java | 84 +++++++++ .../config/EnumParameterBuilderPlugin.java | 123 +++++++++++++ .../swaggerui/config/MvcConfiguration.java | 161 ++++++++++++++++++ .../swaggerui/config/SwaggerDisplayEnum.java | 21 +++ .../swaggerui/controller/HelloController.java | 30 +++- .../com/gsean/swaggerui/enums/Status.java | 48 ++++++ .../java/com/gsean/swaggerui/module/User.java | 26 ++- .../gsean/swaggerui/params/StatusParam.java | 21 +++ .../com/gsean/swaggerui/params/UserDTO.java | 44 +++++ .../swaggerui/plugin/EnumConvertMethod.java | 20 +++ .../plugin/EnumMvcConverterFactory.java | 79 +++++++++ .../src/main/resources/application.yml | 8 +- .../swaggerui/SwaggerUIApplicationTest.java | 45 +++++ 22 files changed, 831 insertions(+), 15 deletions(-) create mode 100644 mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/config/TransactionListenerImpl.java create mode 100644 mq-demos/rocketmq-demos/rocketmq-sample/src/main/resources/application-v2.yml create mode 100644 swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/config/EnumModelPropertyBuilderPlugin.java create mode 100644 swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/config/EnumParameterBuilderPlugin.java create mode 100644 swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/config/MvcConfiguration.java create mode 100644 swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/config/SwaggerDisplayEnum.java create mode 100644 swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/enums/Status.java create mode 100644 swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/params/StatusParam.java create mode 100644 swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/params/UserDTO.java create mode 100644 swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/plugin/EnumConvertMethod.java create mode 100644 swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/plugin/EnumMvcConverterFactory.java create mode 100644 swagger-demo/swagger-ui-demo/src/test/java/com/gsean/swaggerui/SwaggerUIApplicationTest.java diff --git a/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/config/MessageQueueChannels.java b/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/config/MessageQueueChannels.java index 5371b01..da8e065 100644 --- a/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/config/MessageQueueChannels.java +++ b/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/config/MessageQueueChannels.java @@ -12,8 +12,8 @@ import org.springframework.messaging.SubscribableChannel; */ public interface MessageQueueChannels { - String USER_PAYLOD_OUTPUT = "user-paylod-output"; - String USER_PAYLOD_INPUT = "user-paylod-input"; + String USER_PAYLOD_OUTPUT = "user-payload-output"; + String USER_PAYLOD_INPUT = "user-payload-input"; /** diff --git a/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/config/QueueProducer.java b/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/config/QueueProducer.java index 54202ac..511eb66 100644 --- a/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/config/QueueProducer.java +++ b/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/config/QueueProducer.java @@ -3,6 +3,7 @@ package com.gsean.rocket.config; import com.gsean.rocket.dto.UserPayload; import javax.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.common.message.MessageConst; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.integration.support.MessageBuilder; import org.springframework.messaging.MessageChannel; @@ -25,5 +26,23 @@ public class QueueProducer { log.info("发送用户负载状态:{}",send); } + /** + * 发送延迟消息 + * + * @param payload + * @author: Jincheng.Guo11 + * @date: 2021/11/19 17:00 + * @return: void + * @throws: java.lang.Exception + * @modificed by: + */ + public void sendDelayUserPayload(UserPayload payload) { + boolean send = messageQueueChannels.userPaylodOutput() + .send(MessageBuilder.withPayload(payload) + .setHeader(MessageConst.PROPERTY_DELAY_TIME_LEVEL, "3") + .build()); + log.info("发送延时用户负载状态:{}",send); + } + } diff --git a/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/config/TransactionListenerImpl.java b/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/config/TransactionListenerImpl.java new file mode 100644 index 0000000..87594f2 --- /dev/null +++ b/mq-demos/rocketmq-demos/rocketmq-sample/src/main/java/com/gsean/rocket/config/TransactionListenerImpl.java @@ -0,0 +1,36 @@ +package com.gsean.rocket.config; + +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener; +import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener; +import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState; +import org.springframework.messaging.Message; + +/** + * @authoer Jincheng.Guo11 + * @description + * @date:created in 2021/11/19 11:43 + * @modificed by + **/ +@Slf4j +@RocketMQTransactionListener(txProducerGroup = "test") +public class TransactionListenerImpl implements RocketMQLocalTransactionListener { + + @Override + public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) { + // 从消息 Header 中解析到 args 参数,并使用 JSON 反序列化 + String args = JSON.parseObject(message.getHeaders().get("args", String.class), + String.class); + // ... local transaction process, return rollback, commit or unknown + log.info("[executeLocalTransaction][执行本地事务,消息:{} args:{}]", message, args); + return RocketMQLocalTransactionState.UNKNOWN; + } + + @Override + public RocketMQLocalTransactionState checkLocalTransaction(Message message) { + // ... check transaction status and return rollback, commit or unknown + log.info("[checkLocalTransaction][回查消息:{}]", message); + return RocketMQLocalTransactionState.COMMIT; + } +} diff --git a/mq-demos/rocketmq-demos/rocketmq-sample/src/main/resources/application-v1.yml b/mq-demos/rocketmq-demos/rocketmq-sample/src/main/resources/application-v1.yml index ee56f72..b2f6c98 100644 --- a/mq-demos/rocketmq-demos/rocketmq-sample/src/main/resources/application-v1.yml +++ b/mq-demos/rocketmq-demos/rocketmq-sample/src/main/resources/application-v1.yml @@ -8,7 +8,9 @@ spring: # RocketMQ Binder 配置项,对应 RocketMQBinderConfigurationProperties 类 binder: # RocketMQ Namesrv 地址 - name-server: 10.113.75.163:9876;10.113.75.164:9876; +# name-server: 10.113.75.163:9876;10.113.75.164:9876; +# name-server: 127.0.0.1:9876 + name-server: 10.113.72.232:9876 bindings: user-paylod-input: consumer: @@ -19,13 +21,13 @@ spring: # Binding 配置项,对应 BindingProperties Map bindings: #定义name为gsean-test-output的binding - user-paylod-output: + user-payload-output: destination: USERPAYLOAD-TOPIC content-type: application/json #定义name为gsean-test-input的binding - user-paylod-input: + user-payload-input: destination: USERPAYLOAD-TOPIC - group: userpayload-group + group: user-payload-group-01 content-type: application/json diff --git a/mq-demos/rocketmq-demos/rocketmq-sample/src/main/resources/application-v2.yml b/mq-demos/rocketmq-demos/rocketmq-sample/src/main/resources/application-v2.yml new file mode 100644 index 0000000..3f5072d --- /dev/null +++ b/mq-demos/rocketmq-demos/rocketmq-sample/src/main/resources/application-v2.yml @@ -0,0 +1,31 @@ +# RocketMQ配置 事务消息配置 +spring: + cloud: + # Spring Cloud Stream 配置项,对应 BindingServiceProperties 类 + stream: + # Spring Cloud Stream RocketMQ 配置项 + rocketmq: + # RocketMQ Binder 配置项,对应 RocketMQBinderConfigurationProperties 类 + binder: + # RocketMQ Namesrv 地址 + name-server: 10.113.75.163:9876;10.113.75.164:9876; + bindings: + user-payload-output: + producer: + group: test + sync: true + transactional: true + + # Binding 配置项,对应 BindingProperties Map + bindings: + #定义name为gsean-test-output的binding + user-payload-output: + destination: TRANSACTION-TOPIC + content-type: application/json + #定义name为gsean-test-input的binding + user-payload-input: + destination: TRANSACTION-TOPIC + group: user-payload-group-02 + content-type: application/json + + diff --git a/mq-demos/rocketmq-demos/rocketmq-sample/src/main/resources/application.yml b/mq-demos/rocketmq-demos/rocketmq-sample/src/main/resources/application.yml index 87f504a..8b22b17 100644 --- a/mq-demos/rocketmq-demos/rocketmq-sample/src/main/resources/application.yml +++ b/mq-demos/rocketmq-demos/rocketmq-sample/src/main/resources/application.yml @@ -1,2 +1,6 @@ server: port: ${random.int[10000,19999]} + +spring: + profiles: + active: v1 diff --git a/mq-demos/rocketmq-demos/rocketmq-sample/src/test/java/com/gsean/rocket/RocketMqAppTest.java b/mq-demos/rocketmq-demos/rocketmq-sample/src/test/java/com/gsean/rocket/RocketMqAppTest.java index c117b31..79e4418 100644 --- a/mq-demos/rocketmq-demos/rocketmq-sample/src/test/java/com/gsean/rocket/RocketMqAppTest.java +++ b/mq-demos/rocketmq-demos/rocketmq-sample/src/test/java/com/gsean/rocket/RocketMqAppTest.java @@ -28,10 +28,25 @@ class RocketMqAppTest { @Test public void testSendUserPayload(){ - Stream.iterate(0,x->x+1).limit(10).forEachOrdered(item->{ - UserPayload userPayload = new UserPayload().setId(1L).setName("gsean"+item).setBirth(LocalDate.now()); +// Stream.iterate(0,x->x+1).limit(3).forEachOrdered(item->{ + UserPayload userPayload = new UserPayload().setId(1L).setName("gsean"+1).setBirth(LocalDate.now()); queueProducer.sendUserPayload(userPayload); - }); +// }); + } + + /** + * + * 延时消息测试 + * @author: Jincheng.Guo11 + * @date: 2021/11/19 17:01 + * @return: void + * @throws: java.lang.Exception + * @modificed by: + */ + @Test + public void testDelayMsg(){ + UserPayload userPayload = new UserPayload().setId(1L).setName("delay").setBirth(LocalDate.now()); + queueProducer.sendDelayUserPayload(userPayload); } diff --git a/swagger-demo/swagger-sample/src/main/java/com/gsean/swagger/SwaggerSampleApplication.java b/swagger-demo/swagger-sample/src/main/java/com/gsean/swagger/SwaggerSampleApplication.java index 49891e9..9a95ce7 100644 --- a/swagger-demo/swagger-sample/src/main/java/com/gsean/swagger/SwaggerSampleApplication.java +++ b/swagger-demo/swagger-sample/src/main/java/com/gsean/swagger/SwaggerSampleApplication.java @@ -19,7 +19,5 @@ import springfox.documentation.swagger2.configuration.Swagger2DocumentationConfi public class SwaggerSampleApplication { public static void main(String[] args) { ConfigurableApplicationContext run = SpringApplication.run(SwaggerSampleApplication.class, args); - Swagger2DocumentationConfiguration bean = run.getBean(Swagger2DocumentationConfiguration.class); - System.out.println(bean==null); } } diff --git a/swagger-demo/swagger-ui-demo/pom.xml b/swagger-demo/swagger-ui-demo/pom.xml index 6d998ee..e9ce104 100644 --- a/swagger-demo/swagger-ui-demo/pom.xml +++ b/swagger-demo/swagger-ui-demo/pom.xml @@ -64,5 +64,14 @@ swagger-bootstrap-ui 1.9.1 + + org.apache.commons + commons-lang3 + + + org.springframework.boot + spring-boot-starter-test + runtime + \ No newline at end of file diff --git a/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/config/EnumModelPropertyBuilderPlugin.java b/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/config/EnumModelPropertyBuilderPlugin.java new file mode 100644 index 0000000..4b3a551 --- /dev/null +++ b/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/config/EnumModelPropertyBuilderPlugin.java @@ -0,0 +1,84 @@ +package com.gsean.swaggerui.config; + +import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; +import com.google.common.base.Optional; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.stereotype.Component; +import org.springframework.util.ReflectionUtils; +import springfox.documentation.builders.ModelPropertyBuilder; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.schema.ModelPropertyBuilderPlugin; +import springfox.documentation.spi.schema.contexts.ModelPropertyContext; + +/** + * @authoer Jincheng.Guo11 + * @description + * @date:created in 2021/11/22 10:10 + * @modificed by + **/ +@Component +public class EnumModelPropertyBuilderPlugin implements ModelPropertyBuilderPlugin +{ + + @Override + public void apply(ModelPropertyContext context) { + Optional optional = context.getBeanPropertyDefinition(); + if (!optional.isPresent()) { + return; + } + + final Class fieldType = optional.get().getField().getRawType(); + + addDescForEnum(context, fieldType); + } + + @Override + public boolean supports(DocumentationType documentationType) { + return true; + } + + private void addDescForEnum(ModelPropertyContext context, Class fieldType) { + if (Enum.class.isAssignableFrom(fieldType)) { + SwaggerDisplayEnum annotation = AnnotationUtils + .findAnnotation(fieldType, SwaggerDisplayEnum.class); + if (annotation != null) { + String index = annotation.index(); + String name = annotation.name(); + + Object[] enumConstants = fieldType.getEnumConstants(); + + List displayValues = + Arrays.stream(enumConstants) + .filter(Objects::nonNull) + .map(item -> { + Class currentClass = item.getClass(); + + Field indexField = ReflectionUtils.findField(currentClass, index); + ReflectionUtils.makeAccessible(indexField); + Object value = ReflectionUtils.getField(indexField, item); + + Field descField = ReflectionUtils.findField(currentClass, name); + ReflectionUtils.makeAccessible(descField); + Object desc = ReflectionUtils.getField(descField, item); + return value + ":" + desc; + + }).collect(Collectors.toList()); + + + ModelPropertyBuilder builder = context.getBuilder(); + Field descField = ReflectionUtils.findField(builder.getClass(), "description"); + ReflectionUtils.makeAccessible(descField); + String joinText = ReflectionUtils.getField(descField, builder) + + " (" + String.join("; ", displayValues) + ")"; + + builder.description(joinText).type(context.getResolver().resolve(Integer.class)); + } + } + + } +} diff --git a/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/config/EnumParameterBuilderPlugin.java b/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/config/EnumParameterBuilderPlugin.java new file mode 100644 index 0000000..f0bf5aa --- /dev/null +++ b/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/config/EnumParameterBuilderPlugin.java @@ -0,0 +1,123 @@ +package com.gsean.swaggerui.config; + +import com.fasterxml.classmate.ResolvedType; +import com.google.common.base.Joiner; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.stereotype.Component; +import org.springframework.util.ReflectionUtils; +import springfox.documentation.builders.OperationBuilder; +import springfox.documentation.builders.ParameterBuilder; +import springfox.documentation.service.AllowableListValues; +import springfox.documentation.service.Parameter; +import springfox.documentation.service.ResolvedMethodParameter; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.service.OperationBuilderPlugin; +import springfox.documentation.spi.service.ParameterBuilderPlugin; +import springfox.documentation.spi.service.contexts.OperationContext; +import springfox.documentation.spi.service.contexts.ParameterContext; + +/** + * @authoer Jincheng.Guo11 + * @description + * @date:created in 2021/11/22 10:13 + * @modificed by + **/ +@Component +public class EnumParameterBuilderPlugin implements ParameterBuilderPlugin, OperationBuilderPlugin { + private static final Joiner joiner = Joiner.on(","); + + @Override + public void apply(OperationContext context) { + Map> map = new HashMap<>(); + List parameters = context.getParameters(); + parameters.forEach(parameter -> { + ResolvedType parameterType = parameter.getParameterType(); + Class clazz = parameterType.getErasedType(); + if (Enum.class.isAssignableFrom(clazz)) { + SwaggerDisplayEnum annotation = AnnotationUtils.findAnnotation(clazz, SwaggerDisplayEnum.class); + if (annotation != null) { + String index = annotation.index(); + String name = annotation.name(); + Object[] enumConstants = clazz.getEnumConstants(); + + List displayValues = Arrays.stream(enumConstants).filter(Objects::nonNull).map(item -> { + Class currentClass = item.getClass(); + + Field indexField = ReflectionUtils.findField(currentClass, index); + ReflectionUtils.makeAccessible(indexField); + Object value = ReflectionUtils.getField(indexField, item); + + Field descField = ReflectionUtils.findField(currentClass, name); + ReflectionUtils.makeAccessible(descField); + Object desc = ReflectionUtils.getField(descField, item); + return value + ":" + desc; + + }).collect(Collectors.toList()); + + map.put(parameter.defaultName().or(""), displayValues); + + OperationBuilder operationBuilder = context.operationBuilder(); + Field parametersField = ReflectionUtils.findField(operationBuilder.getClass(), "parameters"); + ReflectionUtils.makeAccessible(parametersField); + List list = (List) ReflectionUtils.getField(parametersField, operationBuilder); + + map.forEach((k, v) -> { + for (Parameter currentParameter : list) { + if (StringUtils.equals(currentParameter.getName(), k)) { + Field description = ReflectionUtils.findField(currentParameter.getClass(), "description"); + ReflectionUtils.makeAccessible(description); + Object field = ReflectionUtils.getField(description, currentParameter); + ReflectionUtils.setField(description, currentParameter, field + " , " + joiner.join(v)); + break; + } + } + }); + } + } + }); + } + + @Override + public void apply(ParameterContext context) { + Class type = context.resolvedMethodParameter().getParameterType().getErasedType(); + if (Enum.class.isAssignableFrom(type)) { + SwaggerDisplayEnum annotation = AnnotationUtils.findAnnotation(type, SwaggerDisplayEnum.class); + if (annotation != null) { + + String index = annotation.index(); + String name = annotation.name(); + Object[] enumConstants = type.getEnumConstants(); + List displayValues = Arrays.stream(enumConstants).filter(Objects::nonNull).map(item -> { + Class currentClass = item.getClass(); + + Field indexField = ReflectionUtils.findField(currentClass, index); + ReflectionUtils.makeAccessible(indexField); + Object value = ReflectionUtils.getField(indexField, item); + + Field descField = ReflectionUtils.findField(currentClass, name); + ReflectionUtils.makeAccessible(descField); + Object desc = ReflectionUtils.getField(descField, item); + return value.toString(); + + }).collect(Collectors.toList()); + + ParameterBuilder parameterBuilder = context.parameterBuilder(); + AllowableListValues values = new AllowableListValues(displayValues, "LIST"); + parameterBuilder.allowableValues(values); + } + } + } + + @Override + public boolean supports(DocumentationType documentationType) { + return true; + } +} diff --git a/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/config/MvcConfiguration.java b/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/config/MvcConfiguration.java new file mode 100644 index 0000000..03e5b75 --- /dev/null +++ b/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/config/MvcConfiguration.java @@ -0,0 +1,161 @@ +package com.gsean.swaggerui.config; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; +import com.gsean.swaggerui.plugin.EnumMvcConverterFactory; +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import org.apache.commons.lang3.time.DateUtils; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; +import org.springframework.format.FormatterRegistry; +import org.springframework.util.ObjectUtils; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * @author shenjianeng + * @date 2020/4/19 + */ +@Configuration +public class MvcConfiguration implements WebMvcConfigurer { + private final String YYYY_MM_DD = "yyyy-MM-dd"; + + private final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss"; + + private final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; + + private final String HH_MM_SS = "HH:mm:ss"; + + @Bean + public EnumMvcConverterFactory enumMvcConverterFactory() { + return new EnumMvcConverterFactory(); + } + + @Override + public void addFormatters(FormatterRegistry registry) { + // org.springframework.core.convert.support.GenericConversionService.ConvertersForPair.add + // this.converters.addFirst(converter); + // 所以我们自定义的会放在前面 + registry.addConverterFactory(enumMvcConverterFactory()); + registry.addConverter(localDateTimeConverter()); + registry.addConverter(localDateConverter()); + registry.addConverter(localTimeConverter()); + } + + /** + * Json序列化和反序列化转换器,用于转换Post请求体中的json以及将我们的对象序列化为返回响应的json + */ + @Bean + public ObjectMapper customDateObjectMapper(){ + + + + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE); + objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + + //LocalDateTime系列序列化和反序列化模块,继承自jsr310,这里修改了日期格式 + JavaTimeModule javaTimeModule = new JavaTimeModule(); + javaTimeModule.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM_SS))); + javaTimeModule.addSerializer(LocalDate.class,new LocalDateSerializer(DateTimeFormatter.ofPattern(YYYY_MM_DD))); + javaTimeModule.addSerializer(LocalTime.class,new LocalTimeSerializer(DateTimeFormatter.ofPattern(HH_MM_SS))); + javaTimeModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM_SS))); + javaTimeModule.addDeserializer(LocalDate.class,new LocalDateDeserializer(DateTimeFormatter.ofPattern(YYYY_MM_DD))); + javaTimeModule.addDeserializer(LocalTime.class,new LocalTimeDeserializer(DateTimeFormatter.ofPattern(HH_MM_SS))); + + //Date序列化和反序列化 + javaTimeModule.addSerializer(Date.class, new JsonSerializer() { + @Override + public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + SimpleDateFormat formatter = new SimpleDateFormat(YYYY_MM_DD_HH_MM_SS); + String formattedDate = formatter.format(date); + jsonGenerator.writeString(formattedDate); + } + }); + javaTimeModule.addDeserializer(Date.class, new JsonDeserializer() { + @Override + public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + SimpleDateFormat format = new SimpleDateFormat(YYYY_MM_DD_HH_MM_SS); + String date = jsonParser.getText(); + try { + return format.parse(date); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + }); + + objectMapper.registerModule(javaTimeModule); + return objectMapper; + } + + + /** + * LocalDate转换器,用于转换RequestParam和PathVariable参数 + */ + private Converter localDateConverter() { + return new Converter() { + @Override + public LocalDate convert(String source) { + if(!ObjectUtils.isEmpty(source)){ + return LocalDate.parse(source, DateTimeFormatter.ofPattern(YYYY_MM_DD)); + } + return null ; + + } + }; + } + + /** + * LocalDateTime转换器,用于转换RequestParam和PathVariable参数 + */ + private Converter localDateTimeConverter() { + return new Converter() { + @Override + public LocalDateTime convert(String source) { + if(!ObjectUtils.isEmpty(source)) { + return LocalDateTime.parse(source, DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM_SS)); + } + return null ; + } + }; + } + + /** + * LocalTime转换器,用于转换RequestParam和PathVariable参数 + */ + private Converter localTimeConverter() { + return new Converter() { + @Override + public LocalTime convert(String source) { + if(!ObjectUtils.isEmpty(source)) { + return LocalTime.parse(source, DateTimeFormatter.ofPattern(HH_MM_SS)); + } + return null; + } + }; + } +} diff --git a/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/config/SwaggerDisplayEnum.java b/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/config/SwaggerDisplayEnum.java new file mode 100644 index 0000000..6c1c621 --- /dev/null +++ b/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/config/SwaggerDisplayEnum.java @@ -0,0 +1,21 @@ +package com.gsean.swaggerui.config; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @authoer Jincheng.Guo11 + * @description + * @date:created in 2021/11/22 10:09 + * @modificed by + **/ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface SwaggerDisplayEnum { + + String index() default "index"; + String name() default "name"; + +} diff --git a/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/controller/HelloController.java b/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/controller/HelloController.java index e2b4008..249728e 100644 --- a/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/controller/HelloController.java +++ b/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/controller/HelloController.java @@ -1,10 +1,17 @@ package com.gsean.swaggerui.controller; +import com.gsean.swaggerui.enums.Status; import com.gsean.swaggerui.module.User; +import com.gsean.swaggerui.params.StatusParam; +import com.gsean.swaggerui.params.UserDTO; +import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; +import java.time.LocalDate; +import java.time.LocalDateTime; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** @@ -16,6 +23,7 @@ import org.springframework.web.bind.annotation.RestController; * Version: 1.0 */ @RestController +@Api(value = "hello接口管理") public class HelloController { @GetMapping("/hello") @@ -25,7 +33,25 @@ public class HelloController { @PostMapping("/hello2") @ApiOperation(value = "二接口") - public User hello2(@RequestBody User user){ - return user; + public User hello2(@RequestBody UserDTO user){ + User resUser = new User().setId(user.getId()).setName(user.getName()).setStatus(user.getStatus()) + .setBirthDate(user.getBirthDate()).setCreatedTime(user.getCreatedTime()); + return resUser; + } + + + @GetMapping("/hello3") + @ApiOperation(value = "枚举请求体") + public User hello3(@RequestParam Status status){ + User gsaen = new User().setId(1).setName("gsaen").setStatus(status).setBirthDate(LocalDate.now()).setCreatedTime( + LocalDateTime.now()); + return gsaen; + } + + @GetMapping("/hello4") + @ApiOperation(value = "枚举请求体v2") + public User hello4(StatusParam statusParam,@RequestParam Status status){ + User gsaen = new User().setId(statusParam.getId()).setName("gsaen").setStatus(status).setBirthDate(LocalDate.now()); + return gsaen; } } diff --git a/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/enums/Status.java b/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/enums/Status.java new file mode 100644 index 0000000..cb855fb --- /dev/null +++ b/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/enums/Status.java @@ -0,0 +1,48 @@ +package com.gsean.swaggerui.enums; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonValue; +import com.gsean.swaggerui.config.SwaggerDisplayEnum; +import com.gsean.swaggerui.plugin.EnumConvertMethod; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import lombok.Getter; +import org.springframework.lang.Nullable; + +@Getter +@SwaggerDisplayEnum(index = "id", name = "des") +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum Status { + + ACTIVE(1,"可用"), + UNACTIVE(2, "不可用"); + +// @JsonValue + private Integer id; + private String des; + + Status(Integer id,String des) { + this.id=id; + this.des = des; + } + + + private static final Map mappings; + + static { + Map temp = new HashMap<>(); + for (Status courseType : values()) { + temp.put(courseType.id, courseType); + } + mappings = Collections.unmodifiableMap(temp); + } + + @EnumConvertMethod + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + @Nullable + public static Status resolve(int index) { + return mappings.get(index); + } +} diff --git a/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/module/User.java b/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/module/User.java index 6ec8f51..422a6c6 100644 --- a/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/module/User.java +++ b/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/module/User.java @@ -1,7 +1,19 @@ package com.gsean.swaggerui.module; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.gsean.swaggerui.enums.Status; +import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; +import java.time.LocalDate; +import java.time.LocalDateTime; import lombok.Data; +import lombok.experimental.Accessors; /** * ProjectName:gsean-springboot-demos @@ -12,9 +24,21 @@ import lombok.Data; * Version: 1.0 */ @Data - +@ApiModel(value = "用户") +@Accessors(chain = true) public class User { @ApiModelProperty(name = "name",value = "名称") private String name; + @ApiModelProperty(value = "id") private Integer id; + @ApiModelProperty(value = "状态") + private Status status; + @ApiModelProperty(value = "生日") +// @JsonFormat(pattern="yyyy-MM-dd",timezone="GMT+8") + private LocalDate birthDate; + @ApiModelProperty(value = "创建日期") +// @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="GMT+8") + private LocalDateTime createdTime; + + } diff --git a/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/params/StatusParam.java b/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/params/StatusParam.java new file mode 100644 index 0000000..94652ce --- /dev/null +++ b/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/params/StatusParam.java @@ -0,0 +1,21 @@ +package com.gsean.swaggerui.params; + +import com.gsean.swaggerui.enums.Status; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; + +/** + * @authoer Jincheng.Guo11 + * @description + * @date:created in 2021/11/22 11:13 + * @modificed by + **/ +@ApiModel("状态请求体") +@Getter +public class StatusParam { + @ApiModelProperty(value = "id",example = "1",required = true) + private Integer id; + + +} diff --git a/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/params/UserDTO.java b/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/params/UserDTO.java new file mode 100644 index 0000000..650298e --- /dev/null +++ b/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/params/UserDTO.java @@ -0,0 +1,44 @@ +package com.gsean.swaggerui.params; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.gsean.swaggerui.enums.Status; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import java.time.LocalDate; +import java.time.LocalDateTime; +import lombok.Data; +import lombok.Getter; +import lombok.experimental.Accessors; + +/** + * @authoer Jincheng.Guo11 + * @description + * @date:created in 2021/11/22 13:25 + * @modificed by + **/ +@Data +@ApiModel(value = "用户") +@Accessors(chain = true) +public class UserDTO { + @ApiModelProperty(name = "name",value = "名称") + private String name; + @ApiModelProperty(value = "id") + private Integer id; + @ApiModelProperty(value = "状态") + private Status status; + @ApiModelProperty(value = "生日",example = "2021-12-11") +// @JsonDeserialize(using = LocalDateDeserializer.class) +// @JsonFormat(pattern="yyyy-MM-dd",timezone="GMT+8") + private LocalDate birthDate; + @ApiModelProperty(value = "创建日期") +// @JsonDeserialize(using = LocalDateTimeDeserializer.class) +// @JsonFormat(pattern="yyyy-MM-dd",timezone="GMT+8") + private LocalDateTime createdTime; + +} diff --git a/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/plugin/EnumConvertMethod.java b/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/plugin/EnumConvertMethod.java new file mode 100644 index 0000000..1d385d5 --- /dev/null +++ b/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/plugin/EnumConvertMethod.java @@ -0,0 +1,20 @@ +package com.gsean.swaggerui.plugin; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 在自定义枚举类的工厂方法上标记该注解,用于Spring MVC 转换器转换枚举 + * {@link com.github.shen.mvc.plugin.EnumMvcConverterFactory} + * + * @author shenjianeng + * @date 2020/4/19 + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface EnumConvertMethod { +} diff --git a/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/plugin/EnumMvcConverterFactory.java b/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/plugin/EnumMvcConverterFactory.java new file mode 100644 index 0000000..a5ceb6d --- /dev/null +++ b/swagger-demo/swagger-ui-demo/src/main/java/com/gsean/swaggerui/plugin/EnumMvcConverterFactory.java @@ -0,0 +1,79 @@ +package com.gsean.swaggerui.plugin; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import lombok.AllArgsConstructor; +import org.apache.commons.lang3.reflect.MethodUtils; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.converter.ConverterFactory; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; + +/** + * springMVC 枚举类的转换器 + * 如果枚举类中有工厂方法(静态方法)被标记为{@link EnumConvertMethod },则调用该方法转为枚举对象 + * + * @author shenjianeng + * @date 2020/4/19 + */ +@SuppressWarnings("all") +public class EnumMvcConverterFactory implements ConverterFactory> { + + private final ConcurrentMap>, EnumMvcConverterHolder> holderMapper = new ConcurrentHashMap<>(); + + + @Override + public > Converter getConverter(Class targetType) { + EnumMvcConverterHolder holder = holderMapper.computeIfAbsent(targetType, EnumMvcConverterHolder::createHolder); + return (Converter) holder.converter; + } + + + @AllArgsConstructor + static class EnumMvcConverterHolder { + @Nullable + final EnumMvcConverter converter; + + static EnumMvcConverterHolder createHolder(Class targetType) { + List methodList = MethodUtils.getMethodsListWithAnnotation(targetType, EnumConvertMethod.class, false, true); + if (CollectionUtils.isEmpty(methodList)) { + return new EnumMvcConverterHolder(null); + } + Assert.isTrue(methodList.size() == 1, "@EnumConvertMethod 只能标记在一个工厂方法(静态方法)上"); + Method method = methodList.get(0); + Assert.isTrue(Modifier.isStatic(method.getModifiers()), "@EnumConvertMethod 只能标记在工厂方法(静态方法)上"); + return new EnumMvcConverterHolder(new EnumMvcConverter<>(method)); + } + + } + + static class EnumMvcConverter> implements Converter { + + private final Method method; + + public EnumMvcConverter(Method method) { + this.method = method; + this.method.setAccessible(true); + } + + @Override + public T convert(String source) { + if (source.isEmpty()) { + // reset the enum value to null. + return null; + } + try { + return (T) method.invoke(null, Integer.valueOf(source)); + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + } + + } + + +} diff --git a/swagger-demo/swagger-ui-demo/src/main/resources/application.yml b/swagger-demo/swagger-ui-demo/src/main/resources/application.yml index be69f30..338b4ba 100644 --- a/swagger-demo/swagger-ui-demo/src/main/resources/application.yml +++ b/swagger-demo/swagger-ui-demo/src/main/resources/application.yml @@ -1,2 +1,8 @@ server: - port: 20201 \ No newline at end of file + port: 20201 + +#spring: +# jackson: +# # 格式化返回时间 yyyy-MM-dd HH:mm:ss +# date-format: yyyy-MM-dd HH:mm:ss +# time-zone: GMT+8 \ No newline at end of file diff --git a/swagger-demo/swagger-ui-demo/src/test/java/com/gsean/swaggerui/SwaggerUIApplicationTest.java b/swagger-demo/swagger-ui-demo/src/test/java/com/gsean/swaggerui/SwaggerUIApplicationTest.java new file mode 100644 index 0000000..e32f1c7 --- /dev/null +++ b/swagger-demo/swagger-ui-demo/src/test/java/com/gsean/swaggerui/SwaggerUIApplicationTest.java @@ -0,0 +1,45 @@ +package com.gsean.swaggerui; + +import static org.junit.Assert.*; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.gsean.swaggerui.controller.HelloController; +import com.gsean.swaggerui.enums.Status; +import com.gsean.swaggerui.params.UserDTO; +import java.time.LocalDate; +import java.time.LocalDateTime; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * @authoer Jincheng.Guo11 + * @description + * @date:created in 2021/11/22 14:51 + * @modificed by + **/ +@SpringBootTest +@RunWith(SpringRunner.class) +public class SwaggerUIApplicationTest { + @Autowired + private ObjectMapper objectMapper; + + + + + @Test + public void testA() throws Exception{ + UserDTO gsean = new UserDTO().setId(1).setName("gsean").setStatus(Status.ACTIVE) + .setBirthDate(LocalDate.now()).setCreatedTime( + LocalDateTime.now()); + //序列化测试 + String jsonStr = objectMapper.writeValueAsString(gsean); + System.out.println(jsonStr); + UserDTO userDTO = objectMapper.readValue(jsonStr, UserDTO.class); + System.out.println(userDTO.getBirthDate()); + + } + +} \ No newline at end of file -- Gitee