+ * 测试操作 MongoDb + *
+ * + * @author yangkai.shen + * @date Created in 2018-12-28 16:35 + */ +@Slf4j +public class ArticleRepositoryTest extends SpringBootDemoMongodbApplicationTests { + @Autowired + private ArticleRepository articleRepo; + + @Autowired + private MongoTemplate mongoTemplate; + + @Autowired + private Snowflake snowflake; + + /** + * 测试新增 + */ + @Test + public void testSave() { + Article article = new Article(1L, RandomUtil.randomString(20), RandomUtil.randomString(150), DateUtil.date(), DateUtil + .date(), 0L, 0L); + articleRepo.save(article); + log.info("【article】= {}", JSONUtil.toJsonStr(article)); + } + + /** + * 测试新增列表 + */ + @Test + public void testSaveList() { + List+ * 文章 Dao + *
+ * + * @author yangkai.shen + * @date Created in 2018-12-28 16:30 + */ +public interface ArticleRepository extends MongoRepository+ * kafka配置类 + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-07 14:49 + */ +@Configuration +@EnableConfigurationProperties({KafkaProperties.class}) +@EnableKafka +@AllArgsConstructor +public class KafkaConfig { + private final KafkaProperties kafkaProperties; + + @Bean + public KafkaTemplate+ * 消息处理器 + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-07 14:58 + */ +@Component +@Slf4j +public class MessageHandler { + + @KafkaListener(topics = KafkaConsts.TOPIC_TEST, containerFactory = "ackContainerFactory") + public void handleMessage(ConsumerRecord record, Acknowledgment acknowledgment) { + try { + String message = (String) record.value(); + log.info("收到消息: {}", message); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + // 手动提交 offset + acknowledgment.acknowledge(); + } + } +} +``` + +## SpringBootDemoMqKafkaApplicationTests.java + +```java +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootDemoMqKafkaApplicationTests { + @Autowired + private KafkaTemplate+ * 启动器 + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-07 14:43 + */ +@SpringBootApplication +public class SpringBootDemoMqKafkaApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoMqKafkaApplication.class, args); + } + +} + diff --git a/spring-boot-demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/config/KafkaConfig.java b/demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/config/KafkaConfig.java similarity index 91% rename from spring-boot-demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/config/KafkaConfig.java rename to demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/config/KafkaConfig.java index 730cb076f..b7d9c75ca 100644 --- a/spring-boot-demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/config/KafkaConfig.java +++ b/demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/config/KafkaConfig.java @@ -16,13 +16,8 @@ * kafka配置类 * * - * @package: com.xkcoding.mq.kafka.config - * @description: kafka配置类 - * @author: yangkai.shen - * @date: Created in 2019-01-07 14:49 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen + * @author yangkai.shen + * @date Created in 2019-01-07 14:49 */ @Configuration @EnableConfigurationProperties({KafkaProperties.class}) diff --git a/demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/constants/KafkaConsts.java b/demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/constants/KafkaConsts.java new file mode 100644 index 000000000..3546abb94 --- /dev/null +++ b/demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/constants/KafkaConsts.java @@ -0,0 +1,21 @@ +package com.xkcoding.mq.kafka.constants; + +/** + *+ * kafka 常量池 + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-07 14:52 + */ +public interface KafkaConsts { + /** + * 默认分区大小 + */ + Integer DEFAULT_PARTITION_NUM = 3; + + /** + * Topic 名称 + */ + String TOPIC_TEST = "test"; +} diff --git a/spring-boot-demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/handler/MessageHandler.java b/demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/handler/MessageHandler.java similarity index 81% rename from spring-boot-demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/handler/MessageHandler.java rename to demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/handler/MessageHandler.java index a55552ebc..61dee1739 100644 --- a/spring-boot-demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/handler/MessageHandler.java +++ b/demo-mq-kafka/src/main/java/com/xkcoding/mq/kafka/handler/MessageHandler.java @@ -12,13 +12,8 @@ * 消息处理器 * * - * @package: com.xkcoding.mq.kafka.handler - * @description: 消息处理器 - * @author: yangkai.shen - * @date: Created in 2019-01-07 14:58 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen + * @author yangkai.shen + * @date Created in 2019-01-07 14:58 */ @Component @Slf4j diff --git a/spring-boot-demo-mq-kafka/src/main/resources/application.yml b/demo-mq-kafka/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-mq-kafka/src/main/resources/application.yml rename to demo-mq-kafka/src/main/resources/application.yml diff --git a/spring-boot-demo-mq-kafka/src/test/java/com/xkcoding/mq/kafka/SpringBootDemoMqKafkaApplicationTests.java b/demo-mq-kafka/src/test/java/com/xkcoding/mq/kafka/SpringBootDemoMqKafkaApplicationTests.java similarity index 100% rename from spring-boot-demo-mq-kafka/src/test/java/com/xkcoding/mq/kafka/SpringBootDemoMqKafkaApplicationTests.java rename to demo-mq-kafka/src/test/java/com/xkcoding/mq/kafka/SpringBootDemoMqKafkaApplicationTests.java diff --git a/spring-boot-demo-mq-rocketmq/.gitignore b/demo-mq-rabbitmq/.gitignore similarity index 100% rename from spring-boot-demo-mq-rocketmq/.gitignore rename to demo-mq-rabbitmq/.gitignore diff --git a/demo-mq-rabbitmq/README.md b/demo-mq-rabbitmq/README.md new file mode 100644 index 000000000..d7fe45e08 --- /dev/null +++ b/demo-mq-rabbitmq/README.md @@ -0,0 +1,534 @@ +# spring-boot-demo-mq-rabbitmq + +> 此 demo 主要演示了 Spring Boot 如何集成 RabbitMQ,并且演示了基于直接队列模式、分列模式、主题模式、延迟队列的消息发送和接收。 + +## 注意 + +作者编写本demo时,RabbitMQ 版本使用 `3.7.7-management`,使用 docker 运行,下面是所有步骤: + +1. 下载镜像:`docker pull rabbitmq:3.7.7-management` + +2. 运行容器:`docker run -d -p 5671:5617 -p 5672:5672 -p 4369:4369 -p 15671:15671 -p 15672:15672 -p 25672:25672 --name rabbit-3.7.7 rabbitmq:3.7.7-management` + +3. 进入容器:`docker exec -it rabbit-3.7.7 /bin/bash` + +4. 给容器安装 下载工具 wget:`apt-get install -y wget` + +5. 下载插件包,因为我们的 `RabbitMQ` 版本为 `3.7.7` 所以我们安装 `3.7.x` 版本的延迟队列插件 + + ```bash + root@f72ac937f2be:/plugins# wget https://dl.bintray.com/rabbitmq/community-plugins/3.7.x/rabbitmq_delayed_message_exchange/rabbitmq_delayed_message_exchange-20171201-3.7.x.zip + ``` + +6. 给容器安装 解压工具 unzip:`apt-get install -y unzip` + +7. 解压插件包 + + ```bash + root@f72ac937f2be:/plugins# unzip rabbitmq_delayed_message_exchange-20171201-3.7.x.zip + Archive: rabbitmq_delayed_message_exchange-20171201-3.7.x.zip + inflating: rabbitmq_delayed_message_exchange-20171201-3.7.x.ez + ``` + +8. 启动延迟队列插件 + + ```yaml + root@f72ac937f2be:/plugins# rabbitmq-plugins enable rabbitmq_delayed_message_exchange + The following plugins have been configured: + rabbitmq_delayed_message_exchange + rabbitmq_management + rabbitmq_management_agent + rabbitmq_web_dispatch + Applying plugin configuration to rabbit@f72ac937f2be... + The following plugins have been enabled: + rabbitmq_delayed_message_exchange + + started 1 plugins. + ``` + +9. 退出容器:`exit` + +10. 停止容器:`docker stop rabbit-3.7.7` + +11. 启动容器:`docker start rabbit-3.7.7` + +## pom.xml + +```xml + ++ * RabbitMQ常量池 + *
+ * + * @author yangkai.shen + * @date Created in 2018-12-29 17:08 + */ +public interface RabbitConsts { + /** + * 直接模式1 + */ + String DIRECT_MODE_QUEUE_ONE = "queue.direct.1"; + + /** + * 队列2 + */ + String QUEUE_TWO = "queue.2"; + + /** + * 队列3 + */ + String QUEUE_THREE = "3.queue"; + + /** + * 分列模式 + */ + String FANOUT_MODE_QUEUE = "fanout.mode"; + + /** + * 主题模式 + */ + String TOPIC_MODE_QUEUE = "topic.mode"; + + /** + * 路由1 + */ + String TOPIC_ROUTING_KEY_ONE = "queue.#"; + + /** + * 路由2 + */ + String TOPIC_ROUTING_KEY_TWO = "*.queue"; + + /** + * 路由3 + */ + String TOPIC_ROUTING_KEY_THREE = "3.queue"; + + /** + * 延迟队列 + */ + String DELAY_QUEUE = "delay.queue"; + + /** + * 延迟队列交换器 + */ + String DELAY_MODE_QUEUE = "delay.mode"; +} +``` + +## RabbitMqConfig.java + +> RoutingKey规则 +> +> - 路由格式必须以 `.` 分隔,比如 `user.email` 或者 `user.aaa.email` +> - 通配符 `*` ,代表一个占位符,或者说一个单词,比如路由为 `user.*`,那么 **`user.email`** 可以匹配,但是 *`user.aaa.email`* 就匹配不了 +> - 通配符 `#` ,代表一个或多个占位符,或者说一个或多个单词,比如路由为 `user.#`,那么 **`user.email`** 可以匹配,**`user.aaa.email `** 也可以匹配 + +```java +/** + *+ * RabbitMQ配置,主要是配置队列,如果提前存在该队列,可以省略本配置类 + *
+ * + * @author yangkai.shen + * @date Created in 2018-12-29 17:03 + */ +@Slf4j +@Configuration +public class RabbitMqConfig { + + @Bean + public RabbitTemplate rabbitTemplate(CachingConnectionFactory connectionFactory) { + connectionFactory.setPublisherConfirms(true); + connectionFactory.setPublisherReturns(true); + RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); + rabbitTemplate.setMandatory(true); + rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> log.info("消息发送成功:correlationData({}),ack({}),cause({})", correlationData, ack, cause)); + rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> log.info("消息丢失:exchange({}),route({}),replyCode({}),replyText({}),message:{}", exchange, routingKey, replyCode, replyText, message)); + return rabbitTemplate; + } + + /** + * 直接模式队列1 + */ + @Bean + public Queue directOneQueue() { + return new Queue(RabbitConsts.DIRECT_MODE_QUEUE_ONE); + } + + /** + * 队列2 + */ + @Bean + public Queue queueTwo() { + return new Queue(RabbitConsts.QUEUE_TWO); + } + + /** + * 队列3 + */ + @Bean + public Queue queueThree() { + return new Queue(RabbitConsts.QUEUE_THREE); + } + + /** + * 分列模式队列 + */ + @Bean + public FanoutExchange fanoutExchange() { + return new FanoutExchange(RabbitConsts.FANOUT_MODE_QUEUE); + } + + /** + * 分列模式绑定队列1 + * + * @param directOneQueue 绑定队列1 + * @param fanoutExchange 分列模式交换器 + */ + @Bean + public Binding fanoutBinding1(Queue directOneQueue, FanoutExchange fanoutExchange) { + return BindingBuilder.bind(directOneQueue).to(fanoutExchange); + } + + /** + * 分列模式绑定队列2 + * + * @param queueTwo 绑定队列2 + * @param fanoutExchange 分列模式交换器 + */ + @Bean + public Binding fanoutBinding2(Queue queueTwo, FanoutExchange fanoutExchange) { + return BindingBuilder.bind(queueTwo).to(fanoutExchange); + } + + /** + * 主题模式队列 + *+ * 直接队列1 处理器 + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-04 15:42 + */ +@Slf4j +@RabbitListener(queues = RabbitConsts.DIRECT_MODE_QUEUE_ONE) +@Component +public class DirectQueueOneHandler { + + /** + * 如果 spring.rabbitmq.listener.direct.acknowledge-mode: auto,则可以用这个方式,会自动ack + */ + // @RabbitHandler + public void directHandlerAutoAck(MessageStruct message) { + log.info("直接队列处理器,接收消息:{}", JSONUtil.toJsonStr(message)); + } + + @RabbitHandler + public void directHandlerManualAck(MessageStruct messageStruct, Message message, Channel channel) { + // 如果手动ACK,消息会被监听消费,但是消息在队列中依旧存在,如果 未配置 acknowledge-mode 默认是会在消费完毕后自动ACK掉 + final long deliveryTag = message.getMessageProperties().getDeliveryTag(); + try { + log.info("直接队列1,手动ACK,接收消息:{}", JSONUtil.toJsonStr(messageStruct)); + // 通知 MQ 消息已被成功消费,可以ACK了 + channel.basicAck(deliveryTag, false); + } catch (IOException e) { + try { + // 处理失败,重新压入MQ + channel.basicRecover(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + } +} +``` + +## SpringBootDemoMqRabbitmqApplicationTests.java + +```java +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootDemoMqRabbitmqApplicationTests { + @Autowired + private RabbitTemplate rabbitTemplate; + + /** + * 测试直接模式发送 + */ + @Test + public void sendDirect() { + rabbitTemplate.convertAndSend(RabbitConsts.DIRECT_MODE_QUEUE_ONE, new MessageStruct("direct message")); + } + + /** + * 测试分列模式发送 + */ + @Test + public void sendFanout() { + rabbitTemplate.convertAndSend(RabbitConsts.FANOUT_MODE_QUEUE, "", new MessageStruct("fanout message")); + } + + /** + * 测试主题模式发送1 + */ + @Test + public void sendTopic1() { + rabbitTemplate.convertAndSend(RabbitConsts.TOPIC_MODE_QUEUE, "queue.aaa.bbb", new MessageStruct("topic message")); + } + + /** + * 测试主题模式发送2 + */ + @Test + public void sendTopic2() { + rabbitTemplate.convertAndSend(RabbitConsts.TOPIC_MODE_QUEUE, "ccc.queue", new MessageStruct("topic message")); + } + + /** + * 测试主题模式发送3 + */ + @Test + public void sendTopic3() { + rabbitTemplate.convertAndSend(RabbitConsts.TOPIC_MODE_QUEUE, "3.queue", new MessageStruct("topic message")); + } + + /** + * 测试延迟队列发送 + */ + @Test + public void sendDelay() { + rabbitTemplate.convertAndSend(RabbitConsts.DELAY_MODE_QUEUE, RabbitConsts.DELAY_QUEUE, new MessageStruct("delay message, delay 5s, " + DateUtil + .date()), message -> { + message.getMessageProperties().setHeader("x-delay", 5000); + return message; + }); + rabbitTemplate.convertAndSend(RabbitConsts.DELAY_MODE_QUEUE, RabbitConsts.DELAY_QUEUE, new MessageStruct("delay message, delay 2s, " + DateUtil + .date()), message -> { + message.getMessageProperties().setHeader("x-delay", 2000); + return message; + }); + rabbitTemplate.convertAndSend(RabbitConsts.DELAY_MODE_QUEUE, RabbitConsts.DELAY_QUEUE, new MessageStruct("delay message, delay 8s, " + DateUtil + .date()), message -> { + message.getMessageProperties().setHeader("x-delay", 8000); + return message; + }); + } + +} +``` + +## 运行效果 + +### 直接模式 + + + +### 分列模式 + + + +### 主题模式 + +#### RoutingKey:`queue.#` + + + +#### RoutingKey:`*.queue` + + + +#### RoutingKey:`3.queue` + + + +### 延迟队列 + + + +## 参考 + +1. SpringQP 官方文档:https://docs.spring.io/spring-amqp/docs/2.1.0.RELEASE/reference/html/ +2. RabbitMQ 官网:http://www.rabbitmq.com/ +3. RabbitMQ延迟队列:https://www.cnblogs.com/vipstone/p/9967649.html diff --git a/demo-mq-rabbitmq/pom.xml b/demo-mq-rabbitmq/pom.xml new file mode 100644 index 000000000..fcb1f2890 --- /dev/null +++ b/demo-mq-rabbitmq/pom.xml @@ -0,0 +1,69 @@ + ++ * 启动器 + *
+ * + * @author yangkai.shen + * @date Created in 2018-12-29 13:58 + */ +@SpringBootApplication +public class SpringBootDemoMqRabbitmqApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoMqRabbitmqApplication.class, args); + } + +} + diff --git a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/config/RabbitMqConfig.java b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/config/RabbitMqConfig.java similarity index 94% rename from spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/config/RabbitMqConfig.java rename to demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/config/RabbitMqConfig.java index e55f01c6d..77addf03f 100644 --- a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/config/RabbitMqConfig.java +++ b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/config/RabbitMqConfig.java @@ -16,13 +16,8 @@ * RabbitMQ配置,主要是配置队列,如果提前存在该队列,可以省略本配置类 * * - * @package: com.xkcoding.mq.rabbitmq.config - * @description: RabbitMQ配置,主要是配置队列,如果提前存在该队列,可以省略本配置类 - * @author: yangkai.shen - * @date: Created in 2018-12-29 17:03 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen + * @author yangkai.shen + * @date Created in 2018-12-29 17:03 */ @Slf4j @Configuration diff --git a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/constants/RabbitConsts.java b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/constants/RabbitConsts.java similarity index 80% rename from spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/constants/RabbitConsts.java rename to demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/constants/RabbitConsts.java index 8c117aab5..75167469a 100644 --- a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/constants/RabbitConsts.java +++ b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/constants/RabbitConsts.java @@ -5,13 +5,8 @@ * RabbitMQ常量池 * * - * @package: com.xkcoding.mq.rabbitmq.constants - * @description: RabbitMQ常量池 - * @author: yangkai.shen - * @date: Created in 2018-12-29 17:08 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen + * @author yangkai.shen + * @date Created in 2018-12-29 17:08 */ public interface RabbitConsts { /** diff --git a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/DelayQueueHandler.java b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/DelayQueueHandler.java similarity index 86% rename from spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/DelayQueueHandler.java rename to demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/DelayQueueHandler.java index 6a07dd174..15f4b24f8 100644 --- a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/DelayQueueHandler.java +++ b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/DelayQueueHandler.java @@ -17,13 +17,8 @@ * 延迟队列处理器 * * - * @package: com.xkcoding.mq.rabbitmq.handler - * @description: 延迟队列处理器 - * @author: yangkai.shen - * @date: Created in 2019-01-04 17:42 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen + * @author yangkai.shen + * @date Created in 2019-01-04 17:42 */ @Slf4j @Component diff --git a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/DirectQueueOneHandler.java b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/DirectQueueOneHandler.java similarity index 88% rename from spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/DirectQueueOneHandler.java rename to demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/DirectQueueOneHandler.java index 6ce77c826..5b7559e6f 100644 --- a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/DirectQueueOneHandler.java +++ b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/DirectQueueOneHandler.java @@ -17,13 +17,8 @@ * 直接队列1 处理器 * * - * @package: com.xkcoding.mq.rabbitmq.handler - * @description: 直接队列1 处理器 - * @author: yangkai.shen - * @date: Created in 2019-01-04 15:42 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen + * @author yangkai.shen + * @date Created in 2019-01-04 15:42 */ @Slf4j @RabbitListener(queues = RabbitConsts.DIRECT_MODE_QUEUE_ONE) diff --git a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/QueueThreeHandler.java b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/QueueThreeHandler.java similarity index 87% rename from spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/QueueThreeHandler.java rename to demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/QueueThreeHandler.java index 79c284db6..af229c1b3 100644 --- a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/QueueThreeHandler.java +++ b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/QueueThreeHandler.java @@ -17,13 +17,8 @@ * 队列2 处理器 * * - * @package: com.xkcoding.mq.rabbitmq.handler - * @description: 队列2 处理器 - * @author: yangkai.shen - * @date: Created in 2019-01-04 15:42 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen + * @author yangkai.shen + * @date Created in 2019-01-04 15:42 */ @Slf4j @RabbitListener(queues = RabbitConsts.QUEUE_THREE) diff --git a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/QueueTwoHandler.java b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/QueueTwoHandler.java similarity index 87% rename from spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/QueueTwoHandler.java rename to demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/QueueTwoHandler.java index 1a217434f..1369ab69e 100644 --- a/spring-boot-demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/QueueTwoHandler.java +++ b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/handler/QueueTwoHandler.java @@ -17,13 +17,8 @@ * 队列2 处理器 * * - * @package: com.xkcoding.mq.rabbitmq.handler - * @description: 队列2 处理器 - * @author: yangkai.shen - * @date: Created in 2019-01-04 15:42 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen + * @author yangkai.shen + * @date Created in 2019-01-04 15:42 */ @Slf4j @RabbitListener(queues = RabbitConsts.QUEUE_TWO) diff --git a/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/message/MessageStruct.java b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/message/MessageStruct.java new file mode 100644 index 000000000..71c1125cd --- /dev/null +++ b/demo-mq-rabbitmq/src/main/java/com/xkcoding/mq/rabbitmq/message/MessageStruct.java @@ -0,0 +1,26 @@ +package com.xkcoding.mq.rabbitmq.message; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + *+ * 测试消息体 + *
+ * + * @author yangkai.shen + * @date Created in 2018-12-29 16:22 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MessageStruct implements Serializable { + private static final long serialVersionUID = 392365881428311040L; + + private String message; +} diff --git a/spring-boot-demo-mq-rabbitmq/src/main/resources/application.yml b/demo-mq-rabbitmq/src/main/resources/application.yml similarity index 100% rename from spring-boot-demo-mq-rabbitmq/src/main/resources/application.yml rename to demo-mq-rabbitmq/src/main/resources/application.yml diff --git a/spring-boot-demo-mq-rabbitmq/src/test/java/com/xkcoding/mq/rabbitmq/SpringBootDemoMqRabbitmqApplicationTests.java b/demo-mq-rabbitmq/src/test/java/com/xkcoding/mq/rabbitmq/SpringBootDemoMqRabbitmqApplicationTests.java similarity index 92% rename from spring-boot-demo-mq-rabbitmq/src/test/java/com/xkcoding/mq/rabbitmq/SpringBootDemoMqRabbitmqApplicationTests.java rename to demo-mq-rabbitmq/src/test/java/com/xkcoding/mq/rabbitmq/SpringBootDemoMqRabbitmqApplicationTests.java index 2a69a2666..27251cc83 100644 --- a/spring-boot-demo-mq-rabbitmq/src/test/java/com/xkcoding/mq/rabbitmq/SpringBootDemoMqRabbitmqApplicationTests.java +++ b/demo-mq-rabbitmq/src/test/java/com/xkcoding/mq/rabbitmq/SpringBootDemoMqRabbitmqApplicationTests.java @@ -61,18 +61,15 @@ public void sendTopic3() { */ @Test public void sendDelay() { - rabbitTemplate.convertAndSend(RabbitConsts.DELAY_MODE_QUEUE, RabbitConsts.DELAY_QUEUE, new MessageStruct("delay message, delay 5s, " + DateUtil - .date()), message -> { + rabbitTemplate.convertAndSend(RabbitConsts.DELAY_MODE_QUEUE, RabbitConsts.DELAY_QUEUE, new MessageStruct("delay message, delay 5s, " + DateUtil.date()), message -> { message.getMessageProperties().setHeader("x-delay", 5000); return message; }); - rabbitTemplate.convertAndSend(RabbitConsts.DELAY_MODE_QUEUE, RabbitConsts.DELAY_QUEUE, new MessageStruct("delay message, delay 2s, " + DateUtil - .date()), message -> { + rabbitTemplate.convertAndSend(RabbitConsts.DELAY_MODE_QUEUE, RabbitConsts.DELAY_QUEUE, new MessageStruct("delay message, delay 2s, " + DateUtil.date()), message -> { message.getMessageProperties().setHeader("x-delay", 2000); return message; }); - rabbitTemplate.convertAndSend(RabbitConsts.DELAY_MODE_QUEUE, RabbitConsts.DELAY_QUEUE, new MessageStruct("delay message, delay 8s, " + DateUtil - .date()), message -> { + rabbitTemplate.convertAndSend(RabbitConsts.DELAY_MODE_QUEUE, RabbitConsts.DELAY_QUEUE, new MessageStruct("delay message, delay 8s, " + DateUtil.date()), message -> { message.getMessageProperties().setHeader("x-delay", 8000); return message; }); diff --git a/spring-boot-demo-multi-datasource-jpa/.gitignore b/demo-mq-rocketmq/.gitignore similarity index 100% rename from spring-boot-demo-multi-datasource-jpa/.gitignore rename to demo-mq-rocketmq/.gitignore diff --git a/spring-boot-demo-mq-rocketmq/README.md b/demo-mq-rocketmq/README.md similarity index 100% rename from spring-boot-demo-mq-rocketmq/README.md rename to demo-mq-rocketmq/README.md diff --git a/demo-mq-rocketmq/pom.xml b/demo-mq-rocketmq/pom.xml new file mode 100644 index 000000000..0369f8fc9 --- /dev/null +++ b/demo-mq-rocketmq/pom.xml @@ -0,0 +1,48 @@ + ++ * JPA多数据源配置 - 主数据源 + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-17 15:58 + */ +@Configuration +public class PrimaryDataSourceConfig { + + /** + * 扫描spring.datasource.primary开头的配置信息 + * + * @return 数据源配置信息 + */ + @Primary + @Bean(name = "primaryDataSourceProperties") + @ConfigurationProperties(prefix = "spring.datasource.primary") + public DataSourceProperties dataSourceProperties() { + return new DataSourceProperties(); + } + + /** + * 获取主库数据源对象 + * + * @param dataSourceProperties 注入名为primaryDataSourceProperties的bean + * @return 数据源对象 + */ + @Primary + @Bean(name = "primaryDataSource") + public DataSource dataSource(@Qualifier("primaryDataSourceProperties") DataSourceProperties dataSourceProperties) { + return dataSourceProperties.initializeDataSourceBuilder().build(); + } + + /** + * 该方法仅在需要使用JdbcTemplate对象时选用 + * + * @param dataSource 注入名为primaryDataSource的bean + * @return 数据源JdbcTemplate对象 + */ + @Primary + @Bean(name = "primaryJdbcTemplate") + public JdbcTemplate jdbcTemplate(@Qualifier("primaryDataSource") DataSource dataSource) { + return new JdbcTemplate(dataSource); + } + +} +``` + +## SecondDataSourceConfig.java + +> 从数据源配置 + +```java +/** + *+ * JPA多数据源配置 - 次数据源 + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-17 15:58 + */ +@Configuration +public class SecondDataSourceConfig { + + /** + * 扫描spring.datasource.second开头的配置信息 + * + * @return 数据源配置信息 + */ + @Bean(name = "secondDataSourceProperties") + @ConfigurationProperties(prefix = "spring.datasource.second") + public DataSourceProperties dataSourceProperties() { + return new DataSourceProperties(); + } + + /** + * 获取主库数据源对象 + * + * @param dataSourceProperties 注入名为secondDataSourceProperties的bean + * @return 数据源对象 + */ + @Bean(name = "secondDataSource") + public DataSource dataSource(@Qualifier("secondDataSourceProperties") DataSourceProperties dataSourceProperties) { + return dataSourceProperties.initializeDataSourceBuilder().build(); + } + + /** + * 该方法仅在需要使用JdbcTemplate对象时选用 + * + * @param dataSource 注入名为secondDataSource的bean + * @return 数据源JdbcTemplate对象 + */ + @Bean(name = "secondJdbcTemplate") + public JdbcTemplate jdbcTemplate(@Qualifier("secondDataSource") DataSource dataSource) { + return new JdbcTemplate(dataSource); + } + +} +``` + +## PrimaryJpaConfig.java + +> 主 JPA 配置 + +```java +/** + *+ * JPA多数据源配置 - 主 JPA 配置 + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-17 16:54 + */ +@Configuration +@EnableTransactionManagement +@EnableJpaRepositories( + // repository包名 + basePackages = PrimaryJpaConfig.REPOSITORY_PACKAGE, + // 实体管理bean名称 + entityManagerFactoryRef = "primaryEntityManagerFactory", + // 事务管理bean名称 + transactionManagerRef = "primaryTransactionManager") +public class PrimaryJpaConfig { + static final String REPOSITORY_PACKAGE = "com.xkcoding.multi.datasource.jpa.repository.primary"; + private static final String ENTITY_PACKAGE = "com.xkcoding.multi.datasource.jpa.entity.primary"; + + + /** + * 扫描spring.jpa.primary开头的配置信息 + * + * @return jpa配置信息 + */ + @Primary + @Bean(name = "primaryJpaProperties") + @ConfigurationProperties(prefix = "spring.jpa.primary") + public JpaProperties jpaProperties() { + return new JpaProperties(); + } + + /** + * 获取主库实体管理工厂对象 + * + * @param primaryDataSource 注入名为primaryDataSource的数据源 + * @param jpaProperties 注入名为primaryJpaProperties的jpa配置信息 + * @param builder 注入EntityManagerFactoryBuilder + * @return 实体管理工厂对象 + */ + @Primary + @Bean(name = "primaryEntityManagerFactory") + public LocalContainerEntityManagerFactoryBean entityManagerFactory(@Qualifier("primaryDataSource") DataSource primaryDataSource, @Qualifier("primaryJpaProperties") JpaProperties jpaProperties, EntityManagerFactoryBuilder builder) { + return builder + // 设置数据源 + .dataSource(primaryDataSource) + // 设置jpa配置 + .properties(jpaProperties.getProperties()) + // 设置实体包名 + .packages(ENTITY_PACKAGE) + // 设置持久化单元名,用于@PersistenceContext注解获取EntityManager时指定数据源 + .persistenceUnit("primaryPersistenceUnit").build(); + } + + /** + * 获取实体管理对象 + * + * @param factory 注入名为primaryEntityManagerFactory的bean + * @return 实体管理对象 + */ + @Primary + @Bean(name = "primaryEntityManager") + public EntityManager entityManager(@Qualifier("primaryEntityManagerFactory") EntityManagerFactory factory) { + return factory.createEntityManager(); + } + + /** + * 获取主库事务管理对象 + * + * @param factory 注入名为primaryEntityManagerFactory的bean + * @return 事务管理对象 + */ + @Primary + @Bean(name = "primaryTransactionManager") + public PlatformTransactionManager transactionManager(@Qualifier("primaryEntityManagerFactory") EntityManagerFactory factory) { + return new JpaTransactionManager(factory); + } + +} +``` + +## SecondJpaConfig.java + +> 从 JPA 配置 + +```java +/** + *+ * JPA多数据源配置 - 次 JPA 配置 + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-17 16:54 + */ +@Configuration +@EnableTransactionManagement +@EnableJpaRepositories( + // repository包名 + basePackages = SecondJpaConfig.REPOSITORY_PACKAGE, + // 实体管理bean名称 + entityManagerFactoryRef = "secondEntityManagerFactory", + // 事务管理bean名称 + transactionManagerRef = "secondTransactionManager") +public class SecondJpaConfig { + static final String REPOSITORY_PACKAGE = "com.xkcoding.multi.datasource.jpa.repository.second"; + private static final String ENTITY_PACKAGE = "com.xkcoding.multi.datasource.jpa.entity.second"; + + + /** + * 扫描spring.jpa.second开头的配置信息 + * + * @return jpa配置信息 + */ + @Bean(name = "secondJpaProperties") + @ConfigurationProperties(prefix = "spring.jpa.second") + public JpaProperties jpaProperties() { + return new JpaProperties(); + } + + /** + * 获取主库实体管理工厂对象 + * + * @param secondDataSource 注入名为secondDataSource的数据源 + * @param jpaProperties 注入名为secondJpaProperties的jpa配置信息 + * @param builder 注入EntityManagerFactoryBuilder + * @return 实体管理工厂对象 + */ + @Bean(name = "secondEntityManagerFactory") + public LocalContainerEntityManagerFactoryBean entityManagerFactory(@Qualifier("secondDataSource") DataSource secondDataSource, @Qualifier("secondJpaProperties") JpaProperties jpaProperties, EntityManagerFactoryBuilder builder) { + return builder + // 设置数据源 + .dataSource(secondDataSource) + // 设置jpa配置 + .properties(jpaProperties.getProperties()) + // 设置实体包名 + .packages(ENTITY_PACKAGE) + // 设置持久化单元名,用于@PersistenceContext注解获取EntityManager时指定数据源 + .persistenceUnit("secondPersistenceUnit").build(); + } + + /** + * 获取实体管理对象 + * + * @param factory 注入名为secondEntityManagerFactory的bean + * @return 实体管理对象 + */ + @Bean(name = "secondEntityManager") + public EntityManager entityManager(@Qualifier("secondEntityManagerFactory") EntityManagerFactory factory) { + return factory.createEntityManager(); + } + + /** + * 获取主库事务管理对象 + * + * @param factory 注入名为secondEntityManagerFactory的bean + * @return 事务管理对象 + */ + @Bean(name = "secondTransactionManager") + public PlatformTransactionManager transactionManager(@Qualifier("secondEntityManagerFactory") EntityManagerFactory factory) { + return new JpaTransactionManager(factory); + } + +} +``` + +## application.yml + +```yaml +spring: + datasource: + primary: + url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 + username: root + password: root + driver-class-name: com.mysql.cj.jdbc.Driver + type: com.zaxxer.hikari.HikariDataSource + hikari: + minimum-idle: 5 + connection-test-query: SELECT 1 FROM DUAL + maximum-pool-size: 20 + auto-commit: true + idle-timeout: 30000 + pool-name: PrimaryHikariCP + max-lifetime: 60000 + connection-timeout: 30000 + second: + url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo-2?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 + username: root + password: root + driver-class-name: com.mysql.cj.jdbc.Driver + type: com.zaxxer.hikari.HikariDataSource + hikari: + minimum-idle: 5 + connection-test-query: SELECT 1 FROM DUAL + maximum-pool-size: 20 + auto-commit: true + idle-timeout: 30000 + pool-name: SecondHikariCP + max-lifetime: 60000 + connection-timeout: 30000 + jpa: + primary: + show-sql: true + generate-ddl: true + hibernate: + ddl-auto: update + properties: + hibernate: + dialect: org.hibernate.dialect.MySQL57InnoDBDialect + open-in-view: true + second: + show-sql: true + generate-ddl: true + hibernate: + ddl-auto: update + properties: + hibernate: + dialect: org.hibernate.dialect.MySQL57InnoDBDialect + open-in-view: true +logging: + level: + com.xkcoding: debug + org.hibernate.SQL: debug + org.hibernate.type: trace +``` + +## SpringBootDemoMultiDatasourceJpaApplicationTests.java + +```java +package com.xkcoding.multi.datasource.jpa; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.lang.Snowflake; +import com.xkcoding.multi.datasource.jpa.entity.primary.PrimaryMultiTable; +import com.xkcoding.multi.datasource.jpa.entity.second.SecondMultiTable; +import com.xkcoding.multi.datasource.jpa.repository.primary.PrimaryMultiTableRepository; +import com.xkcoding.multi.datasource.jpa.repository.second.SecondMultiTableRepository; +import lombok.extern.slf4j.Slf4j; +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; + +import java.util.List; + +@RunWith(SpringRunner.class) +@SpringBootTest +@Slf4j +public class SpringBootDemoMultiDatasourceJpaApplicationTests { + @Autowired + private PrimaryMultiTableRepository primaryRepo; + @Autowired + private SecondMultiTableRepository secondRepo; + @Autowired + private Snowflake snowflake; + + @Test + public void testInsert() { + PrimaryMultiTable primary = new PrimaryMultiTable(snowflake.nextId(),"测试名称-1"); + primaryRepo.save(primary); + + SecondMultiTable second = new SecondMultiTable(); + BeanUtil.copyProperties(primary, second); + secondRepo.save(second); + } + + @Test + public void testUpdate() { + primaryRepo.findAll().forEach(primary -> { + primary.setName("修改后的"+primary.getName()); + primaryRepo.save(primary); + + SecondMultiTable second = new SecondMultiTable(); + BeanUtil.copyProperties(primary, second); + secondRepo.save(second); + }); + } + + @Test + public void testDelete() { + primaryRepo.deleteAll(); + + secondRepo.deleteAll(); + } + + @Test + public void testSelect() { + List+ * 启动器 + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-16 17:34 + */ +@SpringBootApplication +public class SpringBootDemoMultiDatasourceJpaApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoMultiDatasourceJpaApplication.class, args); + } + +} + diff --git a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/PrimaryDataSourceConfig.java b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/PrimaryDataSourceConfig.java similarity index 87% rename from spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/PrimaryDataSourceConfig.java rename to demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/PrimaryDataSourceConfig.java index 3d3825e3a..fcfcb25e4 100644 --- a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/PrimaryDataSourceConfig.java +++ b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/PrimaryDataSourceConfig.java @@ -15,13 +15,8 @@ * JPA多数据源配置 - 主数据源 * * - * @package: com.xkcoding.multi.datasource.jpa.config - * @description: JPA多数据源配置 - 主数据源 - * @author: yangkai.shen - * @date: Created in 2019-01-17 15:58 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen + * @author yangkai.shen + * @date Created in 2019-01-17 15:58 */ @Configuration public class PrimaryDataSourceConfig { diff --git a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/PrimaryJpaConfig.java b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/PrimaryJpaConfig.java similarity index 75% rename from spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/PrimaryJpaConfig.java rename to demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/PrimaryJpaConfig.java index ba9fd5de2..39f93e62f 100644 --- a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/PrimaryJpaConfig.java +++ b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/PrimaryJpaConfig.java @@ -1,6 +1,5 @@ package com.xkcoding.multi.datasource.jpa.config; -import com.zaxxer.hikari.HikariDataSource; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -23,23 +22,18 @@ * JPA多数据源配置 - 主 JPA 配置 * * - * @package: com.xkcoding.multi.datasource.jpa.config - * @description: JPA多数据源配置 - 主 JPA 配置 - * @author: yangkai.shen - * @date: Created in 2019-01-17 16:54 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen + * @author yangkai.shen + * @date Created in 2019-01-17 16:54 */ @Configuration @EnableTransactionManagement @EnableJpaRepositories( - // repository包名 - basePackages = PrimaryJpaConfig.REPOSITORY_PACKAGE, - // 实体管理bean名称 - entityManagerFactoryRef = "primaryEntityManagerFactory", - // 事务管理bean名称 - transactionManagerRef = "primaryTransactionManager") + // repository包名 + basePackages = PrimaryJpaConfig.REPOSITORY_PACKAGE, + // 实体管理bean名称 + entityManagerFactoryRef = "primaryEntityManagerFactory", + // 事务管理bean名称 + transactionManagerRef = "primaryTransactionManager") public class PrimaryJpaConfig { static final String REPOSITORY_PACKAGE = "com.xkcoding.multi.datasource.jpa.repository.primary"; private static final String ENTITY_PACKAGE = "com.xkcoding.multi.datasource.jpa.entity.primary"; @@ -69,14 +63,14 @@ public JpaProperties jpaProperties() { @Bean(name = "primaryEntityManagerFactory") public LocalContainerEntityManagerFactoryBean entityManagerFactory(@Qualifier("primaryDataSource") DataSource primaryDataSource, @Qualifier("primaryJpaProperties") JpaProperties jpaProperties, EntityManagerFactoryBuilder builder) { return builder - // 设置数据源 - .dataSource(primaryDataSource) - // 设置jpa配置 - .properties(jpaProperties.getProperties()) - // 设置实体包名 - .packages(ENTITY_PACKAGE) - // 设置持久化单元名,用于@PersistenceContext注解获取EntityManager时指定数据源 - .persistenceUnit("primaryPersistenceUnit").build(); + // 设置数据源 + .dataSource(primaryDataSource) + // 设置jpa配置 + .properties(jpaProperties.getProperties()) + // 设置实体包名 + .packages(ENTITY_PACKAGE) + // 设置持久化单元名,用于@PersistenceContext注解获取EntityManager时指定数据源 + .persistenceUnit("primaryPersistenceUnit").build(); } /** diff --git a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SecondDataSourceConfig.java b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SecondDataSourceConfig.java similarity index 87% rename from spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SecondDataSourceConfig.java rename to demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SecondDataSourceConfig.java index 4a79ddd04..49b7746ce 100644 --- a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SecondDataSourceConfig.java +++ b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SecondDataSourceConfig.java @@ -14,13 +14,8 @@ * JPA多数据源配置 - 次数据源 * * - * @package: com.xkcoding.multi.datasource.jpa.config - * @description: JPA多数据源配置 - 次数据源 - * @author: yangkai.shen - * @date: Created in 2019-01-17 15:58 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen + * @author yangkai.shen + * @date Created in 2019-01-17 15:58 */ @Configuration public class SecondDataSourceConfig { diff --git a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SecondJpaConfig.java b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SecondJpaConfig.java similarity index 75% rename from spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SecondJpaConfig.java rename to demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SecondJpaConfig.java index 1a4f1fe4c..ebbc349d7 100644 --- a/spring-boot-demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SecondJpaConfig.java +++ b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SecondJpaConfig.java @@ -21,23 +21,18 @@ * JPA多数据源配置 - 次 JPA 配置 * * - * @package: com.xkcoding.multi.datasource.jpa.config - * @description: JPA多数据源配置 - 次 JPA 配置 - * @author: yangkai.shen - * @date: Created in 2019-01-17 16:54 - * @copyright: Copyright (c) 2019 - * @version: V1.0 - * @modified: yangkai.shen + * @author yangkai.shen + * @date Created in 2019-01-17 16:54 */ @Configuration @EnableTransactionManagement @EnableJpaRepositories( - // repository包名 - basePackages = SecondJpaConfig.REPOSITORY_PACKAGE, - // 实体管理bean名称 - entityManagerFactoryRef = "secondEntityManagerFactory", - // 事务管理bean名称 - transactionManagerRef = "secondTransactionManager") + // repository包名 + basePackages = SecondJpaConfig.REPOSITORY_PACKAGE, + // 实体管理bean名称 + entityManagerFactoryRef = "secondEntityManagerFactory", + // 事务管理bean名称 + transactionManagerRef = "secondTransactionManager") public class SecondJpaConfig { static final String REPOSITORY_PACKAGE = "com.xkcoding.multi.datasource.jpa.repository.second"; private static final String ENTITY_PACKAGE = "com.xkcoding.multi.datasource.jpa.entity.second"; @@ -65,14 +60,14 @@ public JpaProperties jpaProperties() { @Bean(name = "secondEntityManagerFactory") public LocalContainerEntityManagerFactoryBean entityManagerFactory(@Qualifier("secondDataSource") DataSource secondDataSource, @Qualifier("secondJpaProperties") JpaProperties jpaProperties, EntityManagerFactoryBuilder builder) { return builder - // 设置数据源 - .dataSource(secondDataSource) - // 设置jpa配置 - .properties(jpaProperties.getProperties()) - // 设置实体包名 - .packages(ENTITY_PACKAGE) - // 设置持久化单元名,用于@PersistenceContext注解获取EntityManager时指定数据源 - .persistenceUnit("secondPersistenceUnit").build(); + // 设置数据源 + .dataSource(secondDataSource) + // 设置jpa配置 + .properties(jpaProperties.getProperties()) + // 设置实体包名 + .packages(ENTITY_PACKAGE) + // 设置持久化单元名,用于@PersistenceContext注解获取EntityManager时指定数据源 + .persistenceUnit("secondPersistenceUnit").build(); } /** diff --git a/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SnowflakeConfig.java b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SnowflakeConfig.java new file mode 100644 index 000000000..2508fb9e1 --- /dev/null +++ b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/config/SnowflakeConfig.java @@ -0,0 +1,22 @@ +package com.xkcoding.multi.datasource.jpa.config; + +import cn.hutool.core.lang.Snowflake; +import cn.hutool.core.util.IdUtil; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *+ * 雪花算法生成器 + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-18 15:50 + */ +@Configuration +public class SnowflakeConfig { + @Bean + public Snowflake snowflake() { + return IdUtil.createSnowflake(1, 1); + } +} diff --git a/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/entity/primary/PrimaryMultiTable.java b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/entity/primary/PrimaryMultiTable.java new file mode 100644 index 000000000..4904e690f --- /dev/null +++ b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/entity/primary/PrimaryMultiTable.java @@ -0,0 +1,37 @@ +package com.xkcoding.multi.datasource.jpa.entity.primary; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + *+ * 多数据源测试表 + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-18 10:06 + */ +@Data +@Entity +@Table(name = "multi_table") +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class PrimaryMultiTable { + /** + * 主键 + */ + @Id + private Long id; + + /** + * 名称 + */ + private String name; +} diff --git a/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/entity/second/SecondMultiTable.java b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/entity/second/SecondMultiTable.java new file mode 100644 index 000000000..3756681f5 --- /dev/null +++ b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/entity/second/SecondMultiTable.java @@ -0,0 +1,37 @@ +package com.xkcoding.multi.datasource.jpa.entity.second; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + *+ * 多数据源测试表 + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-18 10:06 + */ +@Data +@Entity +@Table(name = "multi_table") +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class SecondMultiTable { + /** + * 主键 + */ + @Id + private Long id; + + /** + * 名称 + */ + private String name; +} diff --git a/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/repository/primary/PrimaryMultiTableRepository.java b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/repository/primary/PrimaryMultiTableRepository.java new file mode 100644 index 000000000..91cd78b8b --- /dev/null +++ b/demo-multi-datasource-jpa/src/main/java/com/xkcoding/multi/datasource/jpa/repository/primary/PrimaryMultiTableRepository.java @@ -0,0 +1,17 @@ +package com.xkcoding.multi.datasource.jpa.repository.primary; + +import com.xkcoding.multi.datasource.jpa.entity.primary.PrimaryMultiTable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +/** + *+ * 多数据源测试 repo + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-18 10:11 + */ +@Repository +public interface PrimaryMultiTableRepository extends JpaRepository+ * 多数据源测试 repo + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-18 10:11 + */ +@Repository +public interface SecondMultiTableRepository extends JpaRepository+ * User实体类 + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-21 14:19 + */ +@Data +@TableName("multi_user") +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class User implements Serializable { + private static final long serialVersionUID = -1923859222295750467L; + + /** + * 主键 + */ + @TableId(type = IdType.ID_WORKER) + private Long id; + + /** + * 姓名 + */ + private String name; + + /** + * 年龄 + */ + private Integer age; +} +``` + +## 数据访问层 + +`UserMapper.java` + +> 不需要建对应的xml,只需要继承 BaseMapper 就拥有了大部分单表操作的方法了。 + +```java +/** + *+ * 数据访问层 + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-21 14:28 + */ +public interface UserMapper extends BaseMapper+ * 数据服务层 + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-21 14:31 + */ +public interface UserService extends IService+ * 数据服务层 实现 + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-21 14:37 + */ +@Service +@DS("slave") +public class UserServiceImpl extends ServiceImpl+ * 启动器 + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-21 14:19 + */ +@SpringBootApplication +@MapperScan(basePackages = "com.xkcoding.multi.datasource.mybatis.mapper") +public class SpringBootDemoMultiDatasourceMybatisApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoMultiDatasourceMybatisApplication.class, args); + } + +} +``` + +## 配置文件 + +`application.yml` + +```yaml +spring: + datasource: + dynamic: + datasource: + master: + username: root + password: root + url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 + driver-class-name: com.mysql.cj.jdbc.Driver + slave: + username: root + password: root + url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo-2?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 + driver-class-name: com.mysql.cj.jdbc.Driver + mp-enabled: true +logging: + level: + com.xkcoding.multi.datasource.mybatis: debug +``` + +## 测试类 + +```java +/** + *+ * 测试主从数据源 + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-21 14:45 + */ +@Slf4j +public class UserServiceImplTest extends SpringBootDemoMultiDatasourceMybatisApplicationTests { + @Autowired + private UserService userService; + + /** + * 主从库添加 + */ + @Test + public void addUser() { + User userMaster = User.builder().name("主库添加").age(20).build(); + userService.addUser(userMaster); + + User userSlave = User.builder().name("从库添加").age(20).build(); + userService.save(userSlave); + } + + /** + * 从库查询 + */ + @Test + public void testListUser() { + List+ * 启动器 + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-21 14:19 + */ +@SpringBootApplication +@MapperScan(basePackages = "com.xkcoding.multi.datasource.mybatis.mapper") +public class SpringBootDemoMultiDatasourceMybatisApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoMultiDatasourceMybatisApplication.class, args); + } + +} + diff --git a/demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/mapper/UserMapper.java b/demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/mapper/UserMapper.java new file mode 100644 index 000000000..3e8999ce6 --- /dev/null +++ b/demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/mapper/UserMapper.java @@ -0,0 +1,15 @@ +package com.xkcoding.multi.datasource.mybatis.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.xkcoding.multi.datasource.mybatis.model.User; + +/** + *+ * 数据访问层 + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-21 14:28 + */ +public interface UserMapper extends BaseMapper+ * User实体类 + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-21 14:19 + */ +@Data +@TableName("multi_user") +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class User implements Serializable { + private static final long serialVersionUID = -1923859222295750467L; + + /** + * 主键 + */ + @TableId(type = IdType.ID_WORKER) + private Long id; + + /** + * 姓名 + */ + private String name; + + /** + * 年龄 + */ + private Integer age; +} diff --git a/demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/service/UserService.java b/demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/service/UserService.java new file mode 100644 index 000000000..8e805632a --- /dev/null +++ b/demo-multi-datasource-mybatis/src/main/java/com/xkcoding/multi/datasource/mybatis/service/UserService.java @@ -0,0 +1,22 @@ +package com.xkcoding.multi.datasource.mybatis.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.xkcoding.multi.datasource.mybatis.model.User; + +/** + *+ * 数据服务层 + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-21 14:31 + */ +public interface UserService extends IService+ * 数据服务层 实现 + *
+ * + * @author yangkai.shen + * @date Created in 2019-01-21 14:37 + */ +@Service +@DS("slave") +public class UserServiceImpl extends ServiceImpl+ * 自定义主键策略 + *
+ * + * @author yangkai.shen + * @date Created in 2018-12-24 14:40 + */ +public class CustomIdStrategy implements IdStrategy { + @Override + public Object generateId(Object o) { + return IdUtil.fastUUID(); + } +} +``` + +## 部分Model代码 + +### Student.java + +```java +/** + *+ * 学生节点 + *
+ * + * @author yangkai.shen + * @date Created in 2018-12-24 14:38 + */ +@Data +@NoArgsConstructor +@RequiredArgsConstructor(staticName = "of") +@AllArgsConstructor +@Builder +@NodeEntity +public class Student { + /** + * 主键,自定义主键策略,使用UUID生成 + */ + @Id + @GeneratedValue(strategy = CustomIdStrategy.class) + private String id; + + /** + * 学生姓名 + */ + @NonNull + private String name; + + /** + * 学生选的所有课程 + */ + @Relationship(NeoConsts.R_LESSON_OF_STUDENT) + @NonNull + private List+ * 学生节点Repository + *
+ * + * @author yangkai.shen + * @date Created in 2018-12-24 15:05 + */ +public interface StudentRepository extends Neo4jRepository+ * 测试Neo4j + *
+ * + * @author yangkai.shen + * @date Created in 2018-12-24 15:17 + */ +@Slf4j +public class Neo4jTest extends SpringBootDemoNeo4jApplicationTests { + @Autowired + private NeoService neoService; + + /** + * 测试保存 + */ + @Test + public void testSave() { + neoService.initData(); + } + + /** + * 测试删除 + */ + @Test + public void testDelete() { + neoService.delete(); + } + + /** + * 测试查询 鸣人 学了哪些课程 + */ + @Test + public void testFindLessonsByStudent() { + // 深度为1,则课程的任教老师的属性为null + // 深度为2,则会把课程的任教老师的属性赋值 + List+ * 启动器 + *
+ * + * @author yangkai.shen + * @date Created in 2018-12-22 23:50 + */ +@SpringBootApplication +public class SpringBootDemoNeo4jApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoNeo4jApplication.class, args); + } +} diff --git a/demo-neo4j/src/main/java/com/xkcoding/neo4j/config/CustomIdStrategy.java b/demo-neo4j/src/main/java/com/xkcoding/neo4j/config/CustomIdStrategy.java new file mode 100644 index 000000000..511236b9f --- /dev/null +++ b/demo-neo4j/src/main/java/com/xkcoding/neo4j/config/CustomIdStrategy.java @@ -0,0 +1,19 @@ +package com.xkcoding.neo4j.config; + +import cn.hutool.core.util.IdUtil; +import org.neo4j.ogm.id.IdStrategy; + +/** + *+ * 自定义主键策略 + *
+ * + * @author yangkai.shen + * @date Created in 2018-12-24 14:40 + */ +public class CustomIdStrategy implements IdStrategy { + @Override + public Object generateId(Object o) { + return IdUtil.fastUUID(); + } +} diff --git a/demo-neo4j/src/main/java/com/xkcoding/neo4j/constants/NeoConsts.java b/demo-neo4j/src/main/java/com/xkcoding/neo4j/constants/NeoConsts.java new file mode 100644 index 000000000..b420272d1 --- /dev/null +++ b/demo-neo4j/src/main/java/com/xkcoding/neo4j/constants/NeoConsts.java @@ -0,0 +1,32 @@ +package com.xkcoding.neo4j.constants; + +/** + *+ * 常量池 + *
+ * + * @author yangkai.shen + * @date Created in 2018-12-24 14:45 + */ +public interface NeoConsts { + /** + * 关系:班级拥有的学生 + */ + String R_STUDENT_OF_CLASS = "R_STUDENT_OF_CLASS"; + + /** + * 关系:班级的班主任 + */ + String R_BOSS_OF_CLASS = "R_BOSS_OF_CLASS"; + + /** + * 关系:课程的老师 + */ + String R_TEACHER_OF_LESSON = "R_TEACHER_OF_LESSON"; + + /** + * 关系:学生选的课 + */ + String R_LESSON_OF_STUDENT = "R_LESSON_OF_STUDENT"; + +} diff --git a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Class.java b/demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Class.java similarity index 79% rename from spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Class.java rename to demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Class.java index 8af1f6630..faf58351f 100644 --- a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Class.java +++ b/demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Class.java @@ -13,13 +13,8 @@ * 班级节点 * * - * @package: com.xkcoding.neo4j.model - * @description: 班级节点 - * @author: yangkai.shen - * @date: Created in 2018-12-24 14:44 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen + * @author yangkai.shen + * @date Created in 2018-12-24 14:44 */ @Data @NoArgsConstructor diff --git a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Lesson.java b/demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Lesson.java similarity index 80% rename from spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Lesson.java rename to demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Lesson.java index 8d96d4293..6fa9f4368 100644 --- a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Lesson.java +++ b/demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Lesson.java @@ -13,13 +13,8 @@ * 课程节点 * * - * @package: com.xkcoding.neo4j.model - * @description: 课程节点 - * @author: yangkai.shen - * @date: Created in 2018-12-24 14:55 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen + * @author yangkai.shen + * @date Created in 2018-12-24 14:55 */ @Data @NoArgsConstructor diff --git a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Student.java b/demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Student.java similarity index 83% rename from spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Student.java rename to demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Student.java index 4ce6a852a..56dd4f74f 100644 --- a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Student.java +++ b/demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Student.java @@ -15,13 +15,8 @@ * 学生节点 * * - * @package: com.xkcoding.neo4j.model - * @description: 学生节点 - * @author: yangkai.shen - * @date: Created in 2018-12-24 14:38 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen + * @author yangkai.shen + * @date Created in 2018-12-24 14:38 */ @Data @NoArgsConstructor diff --git a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Teacher.java b/demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Teacher.java similarity index 75% rename from spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Teacher.java rename to demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Teacher.java index 968ddb1f8..62106ed67 100644 --- a/spring-boot-demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Teacher.java +++ b/demo-neo4j/src/main/java/com/xkcoding/neo4j/model/Teacher.java @@ -11,13 +11,8 @@ * 教师节点 * * - * @package: com.xkcoding.neo4j.model - * @description: 教师节点 - * @author: yangkai.shen - * @date: Created in 2018-12-24 14:54 - * @copyright: Copyright (c) 2018 - * @version: V1.0 - * @modified: yangkai.shen + * @author yangkai.shen + * @date Created in 2018-12-24 14:54 */ @Data @NoArgsConstructor diff --git a/demo-neo4j/src/main/java/com/xkcoding/neo4j/payload/ClassmateInfoGroupByLesson.java b/demo-neo4j/src/main/java/com/xkcoding/neo4j/payload/ClassmateInfoGroupByLesson.java new file mode 100644 index 000000000..453d11d6a --- /dev/null +++ b/demo-neo4j/src/main/java/com/xkcoding/neo4j/payload/ClassmateInfoGroupByLesson.java @@ -0,0 +1,29 @@ +package com.xkcoding.neo4j.payload; + +import com.xkcoding.neo4j.model.Student; +import lombok.Data; +import org.springframework.data.neo4j.annotation.QueryResult; + +import java.util.List; + +/** + *+ * 按照课程分组的同学关系 + *
+ * + * @author yangkai.shen + * @date Created in 2018-12-24 19:18 + */ +@Data +@QueryResult +public class ClassmateInfoGroupByLesson { + /** + * 课程名称 + */ + private String lessonName; + + /** + * 学生信息 + */ + private List+ * 师生关系 + *
+ * + * @author yangkai.shen + * @date Created in 2018-12-24 19:18 + */ +@Data +@QueryResult +public class TeacherStudent { + /** + * 教师姓名 + */ + private String teacherName; + + /** + * 学生信息 + */ + private List+ * 班级节点Repository + *
+ * + * @author yangkai.shen + * @date Created in 2018-12-24 15:05 + */ +public interface ClassRepository extends Neo4jRepository+ * 课程节点Repository + *
+ * + * @author yangkai.shen + * @date Created in 2018-12-24 15:05 + */ +public interface LessonRepository extends Neo4jRepository+ * 教师节点Repository + *
+ * + * @author yangkai.shen + * @date Created in 2018-12-24 15:05 + */ +public interface TeacherRepository extends Neo4jRepository+ * 启动器 + *
+ * + * @author yangkai.shen + * @date Created in 2019-02-17 23:52 + */ +@SpringBootApplication +public class SpringBootDemoOauthApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootDemoOauthApplication.class, args); + } + +} + diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/ClientLoginFailureHandler.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/ClientLoginFailureHandler.java new file mode 100644 index 000000000..8fe249deb --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/ClientLoginFailureHandler.java @@ -0,0 +1,29 @@ +package com.xkcoding.oauth.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.net.URLEncoder; + +/** + * 登录失败处理器,失败后携带失败信息重定向到登录地址重新登录. + * + * @author EchoCow + * @date 2020-01-07 13:01 + */ +@Slf4j +@Component +public class ClientLoginFailureHandler implements AuthenticationFailureHandler { + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException { + log.debug("Login failed!"); + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.sendRedirect("/oauth/login?error=" + URLEncoder.encode(exception.getLocalizedMessage(), "UTF-8")); + } +} diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/ClientLogoutSuccessHandler.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/ClientLogoutSuccessHandler.java new file mode 100644 index 000000000..61f9f35c4 --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/ClientLogoutSuccessHandler.java @@ -0,0 +1,30 @@ +package com.xkcoding.oauth.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * 客户团退出登录成功处理器. + * + * @author EchoCow + * @date 2020-01-06 22:11 + */ +@Slf4j +@Component +public class ClientLogoutSuccessHandler implements LogoutSuccessHandler { + + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { + response.setStatus(HttpStatus.FOUND.value()); + // 跳转到客户端的回调地址 + response.sendRedirect(request.getParameter("redirectUrl")); + } + +} diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/Oauth2AuthorizationServerConfig.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/Oauth2AuthorizationServerConfig.java new file mode 100644 index 000000000..228ec8408 --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/Oauth2AuthorizationServerConfig.java @@ -0,0 +1,51 @@ +package com.xkcoding.oauth.config; + +import com.xkcoding.oauth.service.SysClientDetailsService; +import com.xkcoding.oauth.service.SysUserService; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; + +/** + * . + * + * @author EchoCow + * @date 2020-01-06 13:32 + */ +@Configuration +@RequiredArgsConstructor +@EnableAuthorizationServer +public class Oauth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { + private final SysClientDetailsService sysClientDetailsService; + private final SysUserService sysUserService; + private final TokenStore tokenStore; + private final AuthenticationManager authenticationManager; + private final JwtAccessTokenConverter jwtAccessTokenConverter; + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) { + endpoints.authenticationManager(authenticationManager).userDetailsService(sysUserService).tokenStore(tokenStore).accessTokenConverter(jwtAccessTokenConverter); + } + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + // 从数据库读取我们自定义的客户端信息 + clients.withClientDetails(sysClientDetailsService); + } + + @Override + public void configure(AuthorizationServerSecurityConfigurer security) { + security + // 获取 token key 需要进行 basic 认证客户端信息 + .tokenKeyAccess("isAuthenticated()") + // 获取 token 信息同样需要 basic 认证客户端信息 + .checkTokenAccess("isAuthenticated()"); + } +} diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/Oauth2AuthorizationTokenConfig.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/Oauth2AuthorizationTokenConfig.java new file mode 100644 index 000000000..c002434c7 --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/Oauth2AuthorizationTokenConfig.java @@ -0,0 +1,74 @@ +package com.xkcoding.oauth.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.core.io.ClassPathResource; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; +import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory; + +import java.security.KeyPair; + +/** + * token 相关配置. + * + * @author EchoCow + * @date 2020-01-06 13:33 + */ +@Configuration +@RequiredArgsConstructor +public class Oauth2AuthorizationTokenConfig { + + /** + * 声明 内存 TokenStore 实现,用来存储 token 相关. + * 默认实现有 mysql、redis + * + * @return InMemoryTokenStore + */ + @Bean + @Primary + public TokenStore tokenStore() { + return new InMemoryTokenStore(); + } + + /** + * jwt 令牌 配置,非对称加密 + * + * @return 转换器 + */ + @Bean + public JwtAccessTokenConverter jwtAccessTokenConverter() { + final JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter(); + accessTokenConverter.setKeyPair(keyPair()); + return accessTokenConverter; + } + + /** + * 密钥 keyPair. + * 可用于生成 jwt / jwk. + * + * @return keyPair + */ + @Bean + public KeyPair keyPair() { + KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("oauth2.jks"), "123456".toCharArray()); + return keyStoreKeyFactory.getKeyPair("oauth2"); + } + + /** + * 加密方式,使用 BCrypt. + * 参数越大加密次数越多,时间越久. + * 默认为 10. + * + * @return PasswordEncoder + */ + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/WebSecurityConfig.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/WebSecurityConfig.java new file mode 100644 index 000000000..c9d253194 --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/WebSecurityConfig.java @@ -0,0 +1,41 @@ +package com.xkcoding.oauth.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +/** + * 安全配置. + * + * @author EchoCow + * @date 2020-01-06 13:27 + */ +@EnableWebSecurity +@RequiredArgsConstructor +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + private final ClientLogoutSuccessHandler clientLogoutSuccessHandler; + private final ClientLoginFailureHandler clientLoginFailureHandler; + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.formLogin().loginPage("/oauth/login").failureHandler(clientLoginFailureHandler).loginProcessingUrl("/authorization/form").and().logout().logoutUrl("/oauth/logout").logoutSuccessHandler(clientLogoutSuccessHandler).and().authorizeRequests().antMatchers("/oauth/**").permitAll().anyRequest().authenticated(); + } + + /** + * 授权管理. + * + * @return 认证管理对象 + * @throws Exception 认证异常信息 + */ + @Override + @Bean + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } +} diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/package-info.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/package-info.java new file mode 100644 index 000000000..f1cc4ed5c --- /dev/null +++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/config/package-info.java @@ -0,0 +1,21 @@ +/** + * spring security oauth2 的相关配置。 + * 使用 spring boot oauth2 自动配置。 + * {@link com.xkcoding.oauth.config.Oauth2AuthorizationServerConfig} + * 授权服务器相关的配置,主要设置授权服务器如何读取客户端、用户信息和一些端点配置 + * 可以在这里配置更多的东西,例如端点映射,token 增强等 + *+ * {@link com.xkcoding.oauth.config.Oauth2AuthorizationTokenConfig} + * 授权服务器 token 相关的配置,主要设置 jwt、加密方式等信息 + *
+ * {@link com.xkcoding.oauth.config.ClientLogoutSuccessHandler}
+ * 资源服务器退出以后的处理。在授权码模式中,所有的客户端都需要跳转到授权服务器进行登录
+ * 当登录成功以后跳转到回调地址,如果用户需要登出,也要跳转到授权服务器这里进行登出
+ * 但是 spring security oauth2 似乎并没有这个逻辑。
+ * 所以自己给登出端点加了一个 redirect_url 参数,表示登出成功以后要跳转的地址
+ * 这个处理器就是来完成登出成功以后的跳转操作的。
+ *
+ * @author EchoCow
+ * @date 2020-01-07 9:16
+ */
+package com.xkcoding.oauth.config;
diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/AuthorizationController.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/AuthorizationController.java
new file mode 100644
index 000000000..a7e26e238
--- /dev/null
+++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/controller/AuthorizationController.java
@@ -0,0 +1,43 @@
+package com.xkcoding.oauth.controller;
+
+import org.springframework.security.oauth2.provider.AuthorizationRequest;
+import org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.SessionAttributes;
+import org.springframework.web.servlet.ModelAndView;
+
+import java.util.Map;
+
+/**
+ * 自定义确认授权页面.
+ * 需要注意的是: 不能在代码中 setComplete,因为整个授权流程并没有结束
+ * 我们只是在中途修改了它确认的一些信息而已。
+ *
+ * @author EchoCow
+ * @date 2020-01-06 16:42
+ */
+@Controller
+@SessionAttributes("authorizationRequest")
+public class AuthorizationController {
+
+ /**
+ * 自定义确认授权页面
+ * 当然你也可以使用 {@link AuthorizationEndpoint#setUserApprovalPage(String)} 方法
+ * 进行设置,但是 model 就没有那么灵活了
+ *
+ * @param model model
+ * @return ModelAndView
+ */
+ @GetMapping("/oauth/confirm_access")
+ public ModelAndView getAccessConfirmation(Map
+ * {@link com.xkcoding.oauth.controller.Oauth2Controller}
+ * 页面跳转的控制器,这里拿出来是因为真的可以做很多事。比如登录的时候携带点什么
+ * 或者退出的时候携带什么标识,都可以。
+ *
+ * @author EchoCow
+ * @date 2020-01-07 11:25
+ * @see org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint
+ */
+package com.xkcoding.oauth.controller;
diff --git a/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysClientDetails.java b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysClientDetails.java
new file mode 100644
index 000000000..7562d2838
--- /dev/null
+++ b/demo-oauth/oauth-authorization-server/src/main/java/com/xkcoding/oauth/entity/SysClientDetails.java
@@ -0,0 +1,191 @@
+package com.xkcoding.oauth.entity;
+
+import lombok.Data;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.oauth2.provider.ClientDetails;
+import org.springframework.security.oauth2.provider.client.BaseClientDetails;
+
+import javax.persistence.*;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 客户端信息.
+ * 这里实现了 ClientDetails 接口
+ * 个人建议不应该在实体类里面写任何逻辑代码
+ * 而为了避免实体类耦合严重不应该去实现这个接口的
+ * 但是这里为了演示和 {@link SysUser} 不同的方式,所以就选择实现这个接口了
+ * 另一种方式是写一个方法将它转化为默认实现 {@link BaseClientDetails} 比较好一点并且简单很多
+ *
+ * @author EchoCow
+ * @date 2020-01-06 12:54
+ */
+@Data
+@Table
+@Entity
+public class SysClientDetails implements ClientDetails {
+
+ /**
+ * 主键
+ */
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ /**
+ * client id
+ */
+ private String clientId;
+
+ /**
+ * client 密钥
+ */
+ private String clientSecret;
+
+ /**
+ * 资源服务器名称
+ */
+ private String resourceIds;
+
+ /**
+ * 授权域
+ */
+ private String scopes;
+
+ /**
+ * 授权类型
+ */
+ private String grantTypes;
+
+ /**
+ * 重定向地址,授权码时必填
+ */
+ private String redirectUrl;
+
+ /**
+ * 授权信息
+ */
+ private String authorizations;
+
+ /**
+ * 授权令牌有效时间
+ */
+ private Integer accessTokenValiditySeconds;
+
+ /**
+ * 刷新令牌有效时间
+ */
+ private Integer refreshTokenValiditySeconds;
+
+ /**
+ * 自动授权请求域
+ */
+ private String autoApproveScopes;
+
+ /**
+ * 是否安全
+ *
+ * @return 结果
+ */
+ @Override
+ public boolean isSecretRequired() {
+ return this.clientSecret != null;
+ }
+
+ /**
+ * 是否有 scopes
+ *
+ * @return 结果
+ */
+ @Override
+ public boolean isScoped() {
+ return this.scopes != null && !this.scopes.isEmpty();
+ }
+
+ /**
+ * scopes
+ *
+ * @return scopes
+ */
+ @Override
+ public Set ~~~ {{infoText}} 亲爱的用户,你好!
+ 欢迎您注册 云课程考试平台
+
+ 你的邮件的验证码:
+ 验证码 如果仍有问题,请联系我们的管理员: 000-00000000
+
+ * Beetl数据源配置
+ *
+ * UserDao
+ *
+ * User Service
+ *
+ * User Service测试
+ *
+ * 启动类
+ *
+ * UserDao
+ *
+ * 用户实体类
+ *
+ * User Service
+ *
+ * User Service
+ *
+ * User Service测试
+ *
+ * Dao基类
+ * 404 找不到页面
+
(请输入该验证码完成 验证,验证码
+
+ 10 分钟内有效!)