diff --git a/am_demo/pom.xml b/am_demo/pom.xml new file mode 100644 index 0000000..a1847ef --- /dev/null +++ b/am_demo/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.5.5 + + + org.javaboy + am_demo + 0.0.1-SNAPSHOT + am_demo + Demo project for Spring Boot + + 11 + + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/am_demo/src/main/java/org/javaboy/am_demo/AmDemoApplication.java b/am_demo/src/main/java/org/javaboy/am_demo/AmDemoApplication.java new file mode 100644 index 0000000..78ba70c --- /dev/null +++ b/am_demo/src/main/java/org/javaboy/am_demo/AmDemoApplication.java @@ -0,0 +1,13 @@ +package org.javaboy.am_demo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class AmDemoApplication { + + public static void main(String[] args) { + SpringApplication.run(AmDemoApplication.class, args); + } + +} diff --git a/am_demo/src/main/java/org/javaboy/am_demo/MyException.java b/am_demo/src/main/java/org/javaboy/am_demo/MyException.java new file mode 100644 index 0000000..d9449a1 --- /dev/null +++ b/am_demo/src/main/java/org/javaboy/am_demo/MyException.java @@ -0,0 +1,13 @@ +package org.javaboy.am_demo; + +/** + * @author 江南一点雨 + * @微信公众号 江南一点雨 + * @网站 http://www.itboyhub.com + * @国际站 http://www.javaboy.org + * @微信 a_java_boy + * @GitHub https://github.com/lenve + * @Gitee https://gitee.com/lenve + */ +public class MyException extends RuntimeException { +} diff --git a/am_demo/src/main/java/org/javaboy/am_demo/SecurityConfig.java b/am_demo/src/main/java/org/javaboy/am_demo/SecurityConfig.java new file mode 100644 index 0000000..76f607e --- /dev/null +++ b/am_demo/src/main/java/org/javaboy/am_demo/SecurityConfig.java @@ -0,0 +1,61 @@ +package org.javaboy.am_demo; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.ObjectPostProcessor; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.authentication.configurers.userdetails.AbstractDaoAuthenticationConfigurer; +import org.springframework.security.config.annotation.authentication.configurers.userdetails.DaoAuthenticationConfigurer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; + +import javax.annotation.PostConstruct; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Map; + +/** + * @author 江南一点雨 + * @微信公众号 江南一点雨 + * @网站 http://www.itboyhub.com + * @国际站 http://www.javaboy.org + * @微信 a_java_boy + * @GitHub https://github.com/lenve + * @Gitee https://gitee.com/lenve + */ +@Configuration +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); + daoAuthenticationProvider.setHideUserNotFoundExceptions(false); + InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager(); + userDetailsService.createUser(User.withUsername("javaboy").password("{noop}123").roles("admin").build()); + daoAuthenticationProvider.setUserDetailsService(userDetailsService); + auth.authenticationProvider(daoAuthenticationProvider); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.authorizeRequests() + .anyRequest().authenticated() + .and() + .formLogin() + .failureHandler((request, response, exception) -> System.out.println(exception)) + .permitAll(); + + } + +} diff --git a/am_demo/src/main/java/org/javaboy/am_demo/SpringContextUtils.java b/am_demo/src/main/java/org/javaboy/am_demo/SpringContextUtils.java new file mode 100644 index 0000000..6856b5e --- /dev/null +++ b/am_demo/src/main/java/org/javaboy/am_demo/SpringContextUtils.java @@ -0,0 +1,51 @@ +package org.javaboy.am_demo; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * @author 江南一点雨 + * @微信公众号 江南一点雨 + * @网站 http://www.itboyhub.com + * @国际站 http://www.javaboy.org + * @微信 a_java_boy + * @GitHub https://github.com/lenve + * @Gitee https://gitee.com/lenve + */ +@Component +public class SpringContextUtils implements ApplicationContextAware { + + private static ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + SpringContextUtils.applicationContext = applicationContext; + } + + public static Object getBean(String name) { + return applicationContext.getBean(name); + } + + public static T getBean(Class requiredType) { + return applicationContext.getBean(requiredType); + } + + public static T getBean(String name, Class requiredType) { + return applicationContext.getBean(name, requiredType); + } + + public static boolean containsBean(String name) { + return applicationContext.containsBean(name); + } + + public static boolean isSingleton(String name) { + return applicationContext.isSingleton(name); + } + + public static Class getType(String name) { + return applicationContext.getType(name); + } +} diff --git a/am_demo/src/main/java/org/javaboy/am_demo/UserService.java b/am_demo/src/main/java/org/javaboy/am_demo/UserService.java new file mode 100644 index 0000000..7667d42 --- /dev/null +++ b/am_demo/src/main/java/org/javaboy/am_demo/UserService.java @@ -0,0 +1,22 @@ +package org.javaboy.am_demo; + +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +/** + * @author 江南一点雨 + * @微信公众号 江南一点雨 + * @网站 http://www.itboyhub.com + * @国际站 http://www.javaboy.org + * @微信 a_java_boy + * @GitHub https://github.com/lenve + * @Gitee https://gitee.com/lenve + */ +public class UserService implements UserDetailsService { + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + throw new MyException(); +// return null; + } +} diff --git a/am_demo/src/main/resources/application.properties b/am_demo/src/main/resources/application.properties new file mode 100644 index 0000000..f8fb198 --- /dev/null +++ b/am_demo/src/main/resources/application.properties @@ -0,0 +1,2 @@ +spring.security.user.name=javaboy +spring.security.user.password=123 \ No newline at end of file diff --git a/am_demo/src/test/java/org/javaboy/am_demo/AmDemoApplicationTests.java b/am_demo/src/test/java/org/javaboy/am_demo/AmDemoApplicationTests.java new file mode 100644 index 0000000..0cdc3e2 --- /dev/null +++ b/am_demo/src/test/java/org/javaboy/am_demo/AmDemoApplicationTests.java @@ -0,0 +1,13 @@ +package org.javaboy.am_demo; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class AmDemoApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/demo.zip b/demo.zip new file mode 100644 index 0000000..1bc212b Binary files /dev/null and b/demo.zip differ diff --git a/demo/Archive.zip b/demo/Archive.zip new file mode 100644 index 0000000..f40cc69 Binary files /dev/null and b/demo/Archive.zip differ diff --git a/demo/pom.xml b/demo/pom.xml new file mode 100644 index 0000000..aacf884 --- /dev/null +++ b/demo/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.5.5 + + + org.javaboy + demo + 0.0.1-SNAPSHOT + demo + Demo project for Spring Boot + + 11 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo/src/main/java/org/javaboy/demo/DemoApplication.java b/demo/src/main/java/org/javaboy/demo/DemoApplication.java new file mode 100644 index 0000000..d2e3e52 --- /dev/null +++ b/demo/src/main/java/org/javaboy/demo/DemoApplication.java @@ -0,0 +1,13 @@ +package org.javaboy.demo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DemoApplication { + + public static void main(String[] args) { + SpringApplication.run(DemoApplication.class, args); + } + +} diff --git a/demo/src/main/java/org/javaboy/demo/HelloController.java b/demo/src/main/java/org/javaboy/demo/HelloController.java new file mode 100644 index 0000000..6df20d7 --- /dev/null +++ b/demo/src/main/java/org/javaboy/demo/HelloController.java @@ -0,0 +1,40 @@ +package org.javaboy.demo; + +import org.springframework.boot.system.ApplicationHome; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; +import java.io.*; + +/** + * @author 江南一点雨 + * @微信公众号 江南一点雨 + * @网站 http://www.itboyhub.com + * @国际站 http://www.javaboy.org + * @微信 a_java_boy + * @GitHub https://github.com/lenve + * @Gitee https://gitee.com/lenve + */ +@Controller +public class HelloController { + @GetMapping("/pdf/{file}") + public void pdf(@PathVariable String file, HttpServletResponse resp) throws IOException { + ApplicationHome h = new ApplicationHome(getClass()); + File jarF = h.getSource(); + File parentFile = jarF.getParentFile(); + System.out.println(parentFile.toString()); + File f = new File(parentFile.toString() + File.separator + "pdf", file); + FileInputStream fis = new FileInputStream(f); + int len = 0; + byte[] buf = new byte[1024]; + ServletOutputStream out = resp.getOutputStream(); + while ((len = fis.read(buf)) != -1) { + out.write(buf, 0, len); + } + fis.close(); + } +} diff --git a/demo/src/main/resources/application.properties b/demo/src/main/resources/application.properties new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/demo/src/main/resources/application.properties @@ -0,0 +1 @@ + diff --git a/demo/src/test/java/org/javaboy/demo/DemoApplicationTests.java b/demo/src/test/java/org/javaboy/demo/DemoApplicationTests.java new file mode 100644 index 0000000..192c8a7 --- /dev/null +++ b/demo/src/test/java/org/javaboy/demo/DemoApplicationTests.java @@ -0,0 +1,18 @@ +package org.javaboy.demo; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.system.ApplicationHome; +import org.springframework.boot.test.context.SpringBootTest; + +import java.io.File; + +@SpringBootTest +class DemoApplicationTests { + + @Test + void contextLoads() { + ApplicationHome h = new ApplicationHome(getClass()); + File jarF = h.getSource(); + } + +} diff --git a/maven_demo01/parent/pom.xml b/maven_demo01/parent/pom.xml new file mode 100644 index 0000000..04e4565 --- /dev/null +++ b/maven_demo01/parent/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + + org.javaboy + parent + pom + 1.0-SNAPSHOT + + ../vhr-dao + ../vhr-service + + + + \ No newline at end of file diff --git a/maven_demo01/vhr-dao/pom.xml b/maven_demo01/vhr-dao/pom.xml new file mode 100644 index 0000000..2df47d4 --- /dev/null +++ b/maven_demo01/vhr-dao/pom.xml @@ -0,0 +1,16 @@ + + + + parent + org.javaboy + 1.0-SNAPSHOT + ../parent/pom.xml + + 4.0.0 + + vhr-dao + + + \ No newline at end of file diff --git a/maven_demo01/vhr-service/pom.xml b/maven_demo01/vhr-service/pom.xml new file mode 100644 index 0000000..59c59f6 --- /dev/null +++ b/maven_demo01/vhr-service/pom.xml @@ -0,0 +1,16 @@ + + + + parent + org.javaboy + 1.0-SNAPSHOT + ../parent/pom.xml + + 4.0.0 + + vhr-service + + + \ No newline at end of file diff --git a/maven_parent/pom.xml b/maven_parent/pom.xml new file mode 100644 index 0000000..47b532a --- /dev/null +++ b/maven_parent/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + + org.javaboy + maven_parent + pom + 1.0-SNAPSHOT + + vhr-dao + vhr-service + + + + \ No newline at end of file diff --git a/maven_parent/vhr-dao/pom.xml b/maven_parent/vhr-dao/pom.xml new file mode 100644 index 0000000..c39a15c --- /dev/null +++ b/maven_parent/vhr-dao/pom.xml @@ -0,0 +1,15 @@ + + + + maven_parent + org.javaboy + 1.0-SNAPSHOT + + 4.0.0 + + vhr-dao + + + \ No newline at end of file diff --git a/maven_parent/vhr-service/pom.xml b/maven_parent/vhr-service/pom.xml new file mode 100644 index 0000000..d860bae --- /dev/null +++ b/maven_parent/vhr-service/pom.xml @@ -0,0 +1,15 @@ + + + + maven_parent + org.javaboy + 1.0-SNAPSHOT + + 4.0.0 + + vhr-service + + + \ No newline at end of file diff --git a/rabbitmq_ttl/pom.xml b/rabbitmq_ttl/pom.xml new file mode 100644 index 0000000..f4e88de --- /dev/null +++ b/rabbitmq_ttl/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.5.4 + + + org.javaboy + rabbitmq_ttl + 0.0.1-SNAPSHOT + rabbitmq_ttl + Demo project for Spring Boot + + 11 + + + + org.springframework.boot + spring-boot-starter-amqp + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.amqp + spring-rabbit-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/rabbitmq_ttl/src/main/java/org/javaboy/rabbitmq_ttl/ConsumerDemo.java b/rabbitmq_ttl/src/main/java/org/javaboy/rabbitmq_ttl/ConsumerDemo.java new file mode 100644 index 0000000..d08e42a --- /dev/null +++ b/rabbitmq_ttl/src/main/java/org/javaboy/rabbitmq_ttl/ConsumerDemo.java @@ -0,0 +1,26 @@ +package org.javaboy.rabbitmq_ttl; + +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.stereotype.Component; + +/** + * @author 江南一点雨 + * @微信公众号 江南一点雨 + * @网站 http://www.itboyhub.com + * @国际站 http://www.javaboy.org + * @微信 a_java_boy + * @GitHub https://github.com/lenve + * @Gitee https://gitee.com/lenve + */ +@Component +public class ConsumerDemo { + @RabbitListener(queues = QueueConfig.JAVABOY_QUEUE_DEMO) + public void handle(String msg) { + System.out.println("msg = " + msg); + } + + @RabbitListener(queues = QueueConfig.DLX_QUEUE_NAME) + public void dlxHandle(String msg) { + System.out.println("dlx msg = " + msg); + } +} diff --git a/rabbitmq_ttl/src/main/java/org/javaboy/rabbitmq_ttl/HelloController.java b/rabbitmq_ttl/src/main/java/org/javaboy/rabbitmq_ttl/HelloController.java new file mode 100644 index 0000000..3ef77da --- /dev/null +++ b/rabbitmq_ttl/src/main/java/org/javaboy/rabbitmq_ttl/HelloController.java @@ -0,0 +1,31 @@ +package org.javaboy.rabbitmq_ttl; + +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageBuilder; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author 江南一点雨 + * @微信公众号 江南一点雨 + * @网站 http://www.itboyhub.com + * @国际站 http://www.javaboy.org + * @微信 a_java_boy + * @GitHub https://github.com/lenve + * @Gitee https://gitee.com/lenve + */ +@RestController +public class HelloController { + @Autowired + RabbitTemplate rabbitTemplate; + + @GetMapping("/hello") + public void hello() { + Message message = MessageBuilder.withBody("hello javaboy".getBytes()) +// .setExpiration("10000") + .build(); + rabbitTemplate.convertAndSend(QueueConfig.JAVABOY_QUEUE_DEMO, message); + } +} diff --git a/rabbitmq_ttl/src/main/java/org/javaboy/rabbitmq_ttl/QueueConfig.java b/rabbitmq_ttl/src/main/java/org/javaboy/rabbitmq_ttl/QueueConfig.java new file mode 100644 index 0000000..139ac36 --- /dev/null +++ b/rabbitmq_ttl/src/main/java/org/javaboy/rabbitmq_ttl/QueueConfig.java @@ -0,0 +1,85 @@ +package org.javaboy.rabbitmq_ttl; + +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.BindingBuilder; +import org.springframework.amqp.core.DirectExchange; +import org.springframework.amqp.core.Queue; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author 江南一点雨 + * @微信公众号 江南一点雨 + * @网站 http://www.itboyhub.com + * @国际站 http://www.javaboy.org + * @微信 a_java_boy + * @GitHub https://github.com/lenve + * @Gitee https://gitee.com/lenve + */ +@Configuration +public class QueueConfig { + + public static final String JAVABOY_QUEUE_DEMO = "javaboy_queue_demo"; + public static final String JAVABOY_EXCHANGE_DEMO = "javaboy_exchange_demo"; + public static final String HELLO_ROUTING_KEY = "hello_routing_key"; + public static final String DLX_EXCHANGE_NAME = "dlx_exchange_name"; + public static final String DLX_QUEUE_NAME = "dlx_queue_name"; + public static final String DLX_ROUTING_KEY = "dlx_routing_key"; + + @Bean + Queue queue() { + Map args = new HashMap<>(); + //设置消息过期时间 + args.put("x-message-ttl", 0); + //设置死信交换机 + args.put("x-dead-letter-exchange", DLX_EXCHANGE_NAME); + //设置死信 routing_key,可以不设置,不设置就使用原来的 routing_key + args.put("x-dead-letter-routing-key", DLX_ROUTING_KEY); + return new Queue(JAVABOY_QUEUE_DEMO, true, false, false, args); + } + + /** + * 配置死信交换机 + * + * @return + */ + @Bean + DirectExchange dlxDirectExchange() { + return new DirectExchange(DLX_EXCHANGE_NAME, true, false); + } + + /** + * 配置死信队列 + * @return + */ + @Bean + Queue dlxQueue() { + return new Queue(DLX_QUEUE_NAME); + } + + /** + * 绑定死信队列和死信交换机 + * @return + */ + @Bean + Binding dlxBinding() { + return BindingBuilder.bind(dlxQueue()) + .to(dlxDirectExchange()) + .with(DLX_ROUTING_KEY); + } + + @Bean + DirectExchange directExchange() { + return new DirectExchange(JAVABOY_EXCHANGE_DEMO, true, false); + } + + @Bean + Binding binding() { + return BindingBuilder.bind(queue()) + .to(directExchange()) + .with(HELLO_ROUTING_KEY); + } +} diff --git a/rabbitmq_ttl/src/main/java/org/javaboy/rabbitmq_ttl/RabbitmqTtlApplication.java b/rabbitmq_ttl/src/main/java/org/javaboy/rabbitmq_ttl/RabbitmqTtlApplication.java new file mode 100644 index 0000000..bc8e870 --- /dev/null +++ b/rabbitmq_ttl/src/main/java/org/javaboy/rabbitmq_ttl/RabbitmqTtlApplication.java @@ -0,0 +1,14 @@ +package org.javaboy.rabbitmq_ttl; + +import org.springframework.amqp.core.Queue; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class RabbitmqTtlApplication { + + public static void main(String[] args) { + SpringApplication.run(RabbitmqTtlApplication.class, args); + } + +} diff --git a/rabbitmq_ttl/src/main/resources/application.properties b/rabbitmq_ttl/src/main/resources/application.properties new file mode 100644 index 0000000..92eba58 --- /dev/null +++ b/rabbitmq_ttl/src/main/resources/application.properties @@ -0,0 +1,5 @@ +spring.rabbitmq.host=127.0.0.1 +spring.rabbitmq.port=5672 +spring.rabbitmq.username=guest +spring.rabbitmq.password=guest +spring.rabbitmq.virtual-host=/ \ No newline at end of file diff --git a/rabbitmq_ttl/src/test/java/org/javaboy/rabbitmq_ttl/RabbitmqTtlApplicationTests.java b/rabbitmq_ttl/src/test/java/org/javaboy/rabbitmq_ttl/RabbitmqTtlApplicationTests.java new file mode 100644 index 0000000..8f78e9a --- /dev/null +++ b/rabbitmq_ttl/src/test/java/org/javaboy/rabbitmq_ttl/RabbitmqTtlApplicationTests.java @@ -0,0 +1,18 @@ +package org.javaboy.rabbitmq_ttl; + +import org.junit.jupiter.api.Test; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class RabbitmqTtlApplicationTests { + + @Autowired + RabbitTemplate rabbitTemplate; + @Test + void contextLoads() { + rabbitTemplate.convertAndSend(QueueConfig.JAVABOY_EXCHANGE_DEMO, null, "hello"); + } + +} diff --git a/scheduling/pom.xml b/scheduling/pom.xml new file mode 100644 index 0000000..b83ddd5 --- /dev/null +++ b/scheduling/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.5.4 + + + org.javaboy + scheduling + 0.0.1-SNAPSHOT + scheduling + Demo project for Spring Boot + + 11 + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-web + + + + mysql + mysql-connector-java + runtime + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/scheduling/src/main/java/org/javaboy/scheduling/SchedulingApplication.java b/scheduling/src/main/java/org/javaboy/scheduling/SchedulingApplication.java new file mode 100644 index 0000000..840ed3d --- /dev/null +++ b/scheduling/src/main/java/org/javaboy/scheduling/SchedulingApplication.java @@ -0,0 +1,13 @@ +package org.javaboy.scheduling; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SchedulingApplication { + + public static void main(String[] args) { + SpringApplication.run(SchedulingApplication.class, args); + } + +} diff --git a/scheduling/src/main/java/org/javaboy/scheduling/config/CronTaskRegistrar.java b/scheduling/src/main/java/org/javaboy/scheduling/config/CronTaskRegistrar.java new file mode 100644 index 0000000..0c6a579 --- /dev/null +++ b/scheduling/src/main/java/org/javaboy/scheduling/config/CronTaskRegistrar.java @@ -0,0 +1,83 @@ +package org.javaboy.scheduling.config; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.config.CronTask; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author 江南一点雨 + * @微信公众号 江南一点雨 + * @网站 http://www.itboyhub.com + * @国际站 http://www.javaboy.org + * @微信 a_java_boy + * @GitHub https://github.com/lenve + * @Gitee https://gitee.com/lenve + */ + +/** + * 这个是最最核心的配置类 + */ +@Configuration +public class CronTaskRegistrar implements DisposableBean { + //这个变量中保存着所有的定时任务 + private final Map scheduledTasks = new ConcurrentHashMap<>(16); + + @Autowired + TaskScheduler taskScheduler; + + public TaskScheduler taskScheduler() { + return this.taskScheduler; + } + + /** + * 添加一个定时任务 + * @param task + * @param cronExpression + */ + public void addCronTask(Runnable task,String cronExpression) { + addCronTask(new CronTask(task, cronExpression)); + } + + private void addCronTask(CronTask cronTask) { + if (cronTask != null) { + Runnable runnable = cronTask.getRunnable(); + if (this.scheduledTasks.containsKey(runnable)) { + //说明要添加的定时任务已经存在 + //先把已经存在的定时任务移除,然后再添加新的定时任务 + removeCronTask(runnable); + } + //添加一个定时任务 + this.scheduledTasks.put(runnable, scheduledCronTask(cronTask)); + } + } + + private ScheduledTask scheduledCronTask(CronTask cronTask) { + ScheduledTask scheduledTask = new ScheduledTask(); + scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger()); + return scheduledTask; + } + + public void removeCronTask(Runnable runnable) { + //1. 从 Map 集合中移除 + ScheduledTask task = this.scheduledTasks.remove(runnable); + //2. 取消正在执行的定时任务 + if (task != null) { + task.cancel(); + } + } + + @Override + public void destroy() throws Exception { + //1. 让所有定时任务停止执行 + for (ScheduledTask task : this.scheduledTasks.values()) { + task.cancel(); + } + //2. 清空集合 + this.scheduledTasks.clear(); + } +} diff --git a/scheduling/src/main/java/org/javaboy/scheduling/config/InitTask.java b/scheduling/src/main/java/org/javaboy/scheduling/config/InitTask.java new file mode 100644 index 0000000..e41f774 --- /dev/null +++ b/scheduling/src/main/java/org/javaboy/scheduling/config/InitTask.java @@ -0,0 +1,36 @@ +package org.javaboy.scheduling.config; + +import org.javaboy.scheduling.model.SysJob; +import org.javaboy.scheduling.service.SysJobService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * @author 江南一点雨 + * @微信公众号 江南一点雨 + * @网站 http://www.itboyhub.com + * @国际站 http://www.javaboy.org + * @微信 a_java_boy + * @GitHub https://github.com/lenve + * @Gitee https://gitee.com/lenve + */ +@Component +public class InitTask implements CommandLineRunner { + @Autowired + CronTaskRegistrar cronTaskRegistrar; + @Autowired + SysJobService sysJobService; + + @Override + public void run(String... args) throws Exception { + //获取所有需要执行的定时任务 + List list = sysJobService.getSysJobByStatus(1); + for (SysJob sysJob : list) { + //遍历 list 集合,执行每一个定时任务 + cronTaskRegistrar.addCronTask(new SchedulingRunnable(sysJob.getBeanName(), sysJob.getMethodName(), sysJob.getMethodParams()), sysJob.getCronExpression()); + } + } +} diff --git a/scheduling/src/main/java/org/javaboy/scheduling/config/ScheduledTask.java b/scheduling/src/main/java/org/javaboy/scheduling/config/ScheduledTask.java new file mode 100644 index 0000000..6352cae --- /dev/null +++ b/scheduling/src/main/java/org/javaboy/scheduling/config/ScheduledTask.java @@ -0,0 +1,25 @@ +package org.javaboy.scheduling.config; + +import org.springframework.context.annotation.Configuration; + +import java.util.concurrent.ScheduledFuture; + +/** + * @author 江南一点雨 + * @微信公众号 江南一点雨 + * @网站 http://www.itboyhub.com + * @国际站 http://www.javaboy.org + * @微信 a_java_boy + * @GitHub https://github.com/lenve + * @Gitee https://gitee.com/lenve + */ +public class ScheduledTask { + volatile ScheduledFuture future; + + public void cancel() { + ScheduledFuture future = this.future; + if (future != null) { + future.cancel(true); + } + } +} diff --git a/scheduling/src/main/java/org/javaboy/scheduling/config/SchedulingConfig.java b/scheduling/src/main/java/org/javaboy/scheduling/config/SchedulingConfig.java new file mode 100644 index 0000000..58cdad5 --- /dev/null +++ b/scheduling/src/main/java/org/javaboy/scheduling/config/SchedulingConfig.java @@ -0,0 +1,27 @@ +package org.javaboy.scheduling.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + +/** + * @author 江南一点雨 + * @微信公众号 江南一点雨 + * @网站 http://www.itboyhub.com + * @国际站 http://www.javaboy.org + * @微信 a_java_boy + * @GitHub https://github.com/lenve + * @Gitee https://gitee.com/lenve + */ +@Configuration +public class SchedulingConfig { + @Bean + TaskScheduler taskScheduler() { + ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); + threadPoolTaskScheduler.setPoolSize(4); + threadPoolTaskScheduler.setRemoveOnCancelPolicy(true); + threadPoolTaskScheduler.setThreadNamePrefix("javaboy-scheduling-task-"); + return threadPoolTaskScheduler; + } +} diff --git a/scheduling/src/main/java/org/javaboy/scheduling/config/SchedulingRunnable.java b/scheduling/src/main/java/org/javaboy/scheduling/config/SchedulingRunnable.java new file mode 100644 index 0000000..19ad0ba --- /dev/null +++ b/scheduling/src/main/java/org/javaboy/scheduling/config/SchedulingRunnable.java @@ -0,0 +1,98 @@ +package org.javaboy.scheduling.config; + +/** + * @author 江南一点雨 + * @微信公众号 江南一点雨 + * @网站 http://www.itboyhub.com + * @国际站 http://www.javaboy.org + * @微信 a_java_boy + * @GitHub https://github.com/lenve + * @Gitee https://gitee.com/lenve + */ + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Objects; + +/** + * 每一个定时任务都对应一个子线程 + */ +public class SchedulingRunnable implements Runnable { + + private static final Logger logger = LoggerFactory.getLogger(SchedulingRunnable.class); + + private String beanName; + + private String methodName; + private String params; + private Object targetBean; + private Method method; + + public SchedulingRunnable(String beanName, String methodName) { + this(beanName, methodName, null); + } + + public SchedulingRunnable(String beanName, String methodName, String params) { + this.beanName = beanName; + this.methodName = methodName; + this.params = params; + init(); + } + + public void init() { + try { + targetBean = SpringContextUtils.getBean(beanName); + if (StringUtils.hasText(params)) { + //假设只有定时任务只有一个参数,并且参数类型是 String + method = targetBean.getClass().getDeclaredMethod(methodName, String.class); + }else{ + method = targetBean.getClass().getDeclaredMethod(methodName); + } + //使方法可访问 + ReflectionUtils.makeAccessible(method); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + } + + /** + * 定时任务,时间到了就执行 run 方法 + */ + @Override + public void run() { + logger.info("定时任务开始执行 - bean:{},方法:{},参数:{}", beanName, method, params); + long startTime = System.currentTimeMillis(); + try { + if ((StringUtils.hasText(params))) { + //如果方法有参数 + method.invoke(targetBean, params); + }else{ + method.invoke(targetBean); + } + }catch (Exception e) { + logger.error(String.format("定时任务执行异常 - bean: %s,方法: %s,参数: %s", beanName, methodName, params), e); + } + long endTime = System.currentTimeMillis(); + logger.info("定时任务执行结束 - bean:{},方法:{},参数:{},耗时:{}毫秒", beanName, method, params, (endTime - startTime)); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SchedulingRunnable that = (SchedulingRunnable) o; + return Objects.equals(beanName, that.beanName) && + Objects.equals(methodName, that.methodName) && + Objects.equals(params, that.params); + } + + @Override + public int hashCode() { + return Objects.hash(beanName, methodName, params); + } +} diff --git a/scheduling/src/main/java/org/javaboy/scheduling/config/SpringContextUtils.java b/scheduling/src/main/java/org/javaboy/scheduling/config/SpringContextUtils.java new file mode 100644 index 0000000..68d73b7 --- /dev/null +++ b/scheduling/src/main/java/org/javaboy/scheduling/config/SpringContextUtils.java @@ -0,0 +1,85 @@ +package org.javaboy.scheduling.config; + +/** + * @author 江南一点雨 + * @微信公众号 江南一点雨 ssm + * @网站 http://www.itboyhub.com + * @国际站 http://www.javaboy.org + * @微信 a_java_boy + * @GitHub https://github.com/lenve + * @Gitee https://gitee.com/lenve + */ + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * 主要作用:根据用户传入的 Bean 名称去 Spring 容器中查找相应的 Bean + */ +@Component +public class SpringContextUtils implements ApplicationContextAware { + private static ApplicationContext applicationContext; + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + SpringContextUtils.applicationContext = applicationContext; + } + + /** + * 根据 Bean 的名称去查找一个 Bean + * @param name + * @return + */ + public static Object getBean(String name) { + return applicationContext.getBean(name); + } + + /** + * 根据类型去 Spring 容器中查找 Bean + * @param requiredType + * @param + * @return + */ + public static T getBean(Class requiredType) { + return applicationContext.getBean(requiredType); + } + + /** + * 根据 name+type 去查找一个 Bean + * @param name + * @param requiredType + * @param + * @return + */ + public static T getBean(String name, Class requiredType) { + return applicationContext.getBean(name, requiredType); + } + + /** + * 判断 Spring 容器中是否存在某一个 Bean + * @param name + * @return + */ + public static boolean containsBean(String name) { + return applicationContext.containsBean(name); + } + + /** + * 判断一个 Bean 是否是单例的 + * @param name + * @return + */ + public static boolean isSingleton(String name) { + return applicationContext.isSingleton(name); + } + + /** + * 传入 Bean 的名字,判断 Bean 的类型 + * @param name + * @return + */ + public static Class getType(String name) { + return applicationContext.getType(name); + } +} diff --git a/scheduling/src/main/java/org/javaboy/scheduling/controller/SysJobController.java b/scheduling/src/main/java/org/javaboy/scheduling/controller/SysJobController.java new file mode 100644 index 0000000..6a19c15 --- /dev/null +++ b/scheduling/src/main/java/org/javaboy/scheduling/controller/SysJobController.java @@ -0,0 +1,40 @@ +package org.javaboy.scheduling.controller; + +import org.javaboy.scheduling.model.RespBean; +import org.javaboy.scheduling.model.SysJob; +import org.javaboy.scheduling.service.SysJobService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * @author 江南一点雨 + * @微信公众号 江南一点雨 + * @网站 http://www.itboyhub.com + * @国际站 http://www.javaboy.org + * @微信 a_java_boy + * @GitHub https://github.com/lenve + * @Gitee https://gitee.com/lenve + */ +@RestController +@RequestMapping("/jobs") +public class SysJobController { + @Autowired + SysJobService sysJobService; + + @GetMapping("/") + public List getAllJobs() { + return sysJobService.getAllJobs(); + } + + + @PutMapping("/") + public RespBean updateSysJob(@RequestBody SysJob sysJob) { + Boolean result = sysJobService.updateSysJob(sysJob); + if (result) { + return RespBean.ok("作业更新成功"); + } + return RespBean.error("作业更新失败"); + } +} diff --git a/scheduling/src/main/java/org/javaboy/scheduling/dao/SysJobRepository.java b/scheduling/src/main/java/org/javaboy/scheduling/dao/SysJobRepository.java new file mode 100644 index 0000000..58d2e00 --- /dev/null +++ b/scheduling/src/main/java/org/javaboy/scheduling/dao/SysJobRepository.java @@ -0,0 +1,19 @@ +package org.javaboy.scheduling.dao; + +import org.javaboy.scheduling.model.SysJob; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +/** + * @author 江南一点雨 + * @微信公众号 江南一点雨 + * @网站 http://www.itboyhub.com + * @国际站 http://www.javaboy.org + * @微信 a_java_boy + * @GitHub https://github.com/lenve + * @Gitee https://gitee.com/lenve + */ +public interface SysJobRepository extends JpaRepository { + List findAllByStatus(Integer status); +} diff --git a/scheduling/src/main/java/org/javaboy/scheduling/model/RespBean.java b/scheduling/src/main/java/org/javaboy/scheduling/model/RespBean.java new file mode 100644 index 0000000..e78a386 --- /dev/null +++ b/scheduling/src/main/java/org/javaboy/scheduling/model/RespBean.java @@ -0,0 +1,65 @@ +package org.javaboy.scheduling.model; + +/** + * @author 江南一点雨 + * @微信公众号 江南一点雨 + * @网站 http://www.itboyhub.com + * @国际站 http://www.javaboy.org + * @微信 a_java_boy + * @GitHub https://github.com/lenve + * @Gitee https://gitee.com/lenve + */ +public class RespBean { + private Integer status; + private String msg; + private Object data; + + public static RespBean ok(String msg, Object data) { + return new RespBean(200, msg, data); + } + + public static RespBean ok(String msg) { + return new RespBean(200, msg, null); + } + + public static RespBean error(String msg, Object data) { + return new RespBean(500, msg, data); + } + + public static RespBean error(String msg) { + return new RespBean(500, msg, null); + } + + private RespBean() { + } + + private RespBean(Integer status, String msg, Object data) { + this.status = status; + this.msg = msg; + this.data = data; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public Object getData() { + return data; + } + + public void setData(Object data) { + this.data = data; + } +} diff --git a/scheduling/src/main/java/org/javaboy/scheduling/model/SysJob.java b/scheduling/src/main/java/org/javaboy/scheduling/model/SysJob.java new file mode 100644 index 0000000..cc99ccc --- /dev/null +++ b/scheduling/src/main/java/org/javaboy/scheduling/model/SysJob.java @@ -0,0 +1,137 @@ +package org.javaboy.scheduling.model; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import java.util.Date; +import java.util.Objects; + +/** + * @author 江南一点雨 + * @微信公众号 江南一点雨 + * @网站 http://www.itboyhub.com + * @国际站 http://www.javaboy.org + * @微信 a_java_boy + * @GitHub https://github.com/lenve + * @Gitee https://gitee.com/lenve + * + * + * 一个 SysJob 对象就代表一个定时任务 + */ +@Entity(name = "t_sys_job") +public class SysJob { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer jobId; + + private String beanName; + private String methodName; + private String methodParams; + // Cron 表达式 + private String cronExpression; + //定时任务的状态 0 表示暂停 1 表示运行 + private Integer status; + + private String remark; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + /** + * beanName\methodName\methodParams\cronExpression 这四个如果全部都相同,就认为是同一个定时任务 + * @param o + * @return + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SysJob sysJob = (SysJob) o; + return Objects.equals(beanName, sysJob.beanName) && + Objects.equals(methodName, sysJob.methodName) && + Objects.equals(methodParams, sysJob.methodParams) && + Objects.equals(cronExpression, sysJob.cronExpression); + } + + @Override + public int hashCode() { + return Objects.hash(beanName, methodName, methodParams, cronExpression); + } + + public Integer getJobId() { + return jobId; + } + + public void setJobId(Integer jobId) { + this.jobId = jobId; + } + + public String getBeanName() { + return beanName; + } + + public void setBeanName(String beanName) { + this.beanName = beanName; + } + + public String getMethodName() { + return methodName; + } + + public void setMethodName(String methodName) { + this.methodName = methodName; + } + + public String getMethodParams() { + return methodParams; + } + + public void setMethodParams(String methodParams) { + this.methodParams = methodParams; + } + + public String getCronExpression() { + return cronExpression; + } + + public void setCronExpression(String cronExpression) { + this.cronExpression = cronExpression; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } + + public Date getCreateTime() { + return createTime; + } + + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + public Date getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } +} diff --git a/scheduling/src/main/java/org/javaboy/scheduling/service/SysJobService.java b/scheduling/src/main/java/org/javaboy/scheduling/service/SysJobService.java new file mode 100644 index 0000000..698ffd3 --- /dev/null +++ b/scheduling/src/main/java/org/javaboy/scheduling/service/SysJobService.java @@ -0,0 +1,55 @@ +package org.javaboy.scheduling.service; + +import org.javaboy.scheduling.config.CronTaskRegistrar; +import org.javaboy.scheduling.config.SchedulingRunnable; +import org.javaboy.scheduling.dao.SysJobRepository; +import org.javaboy.scheduling.model.SysJob; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * @author 江南一点雨 + * @微信公众号 江南一点雨 + * @网站 http://www.itboyhub.com + * @国际站 http://www.javaboy.org + * @微信 a_java_boy + * @GitHub https://github.com/lenve + * @Gitee https://gitee.com/lenve + */ +@Service +public class SysJobService { + + @Autowired + SysJobRepository sysJobRepository; + + @Autowired + CronTaskRegistrar cronTaskRegistrar; + + + public List getSysJobByStatus(int status) { + return sysJobRepository.findAllByStatus(status); + } + + public List getAllJobs() { + return sysJobRepository.findAll(); + } + + public Boolean updateSysJob(SysJob sysJob) { + SysJob job = sysJobRepository.saveAndFlush(sysJob); + if (job != null) { + //更新成功 + SchedulingRunnable schedulingRunnable = new SchedulingRunnable(sysJob.getBeanName(), sysJob.getMethodName(), sysJob.getMethodParams()); + if (sysJob.getStatus() == 1) { + //定时任务是开启状态 + cronTaskRegistrar.addCronTask(schedulingRunnable, sysJob.getCronExpression()); + }else{ + //定时任务是关闭状态,移除定时任务 + cronTaskRegistrar.removeCronTask(schedulingRunnable); + } + return true; + } + return false; + } +} diff --git a/scheduling/src/main/java/org/javaboy/scheduling/taskdemo/SchedulingTaskDemo.java b/scheduling/src/main/java/org/javaboy/scheduling/taskdemo/SchedulingTaskDemo.java new file mode 100644 index 0000000..e5c6fb4 --- /dev/null +++ b/scheduling/src/main/java/org/javaboy/scheduling/taskdemo/SchedulingTaskDemo.java @@ -0,0 +1,22 @@ +package org.javaboy.scheduling.taskdemo; + +import org.springframework.stereotype.Component; + +/** + * @author 江南一点雨 + * @微信公众号 江南一点雨 + * @网站 http://www.itboyhub.com + * @国际站 http://www.javaboy.org + * @微信 a_java_boy + * @GitHub https://github.com/lenve + * @Gitee https://gitee.com/lenve + */ +@Component +public class SchedulingTaskDemo { + public void taskWithParams(String params) { + System.out.println("执行带参数的定时任务..." + params); + } + public void taskWithoutParams() { + System.out.println("执行不带参数的定时任务..."); + } +} diff --git a/scheduling/src/main/resources/application.yaml b/scheduling/src/main/resources/application.yaml new file mode 100644 index 0000000..1a8fc4a --- /dev/null +++ b/scheduling/src/main/resources/application.yaml @@ -0,0 +1,14 @@ +spring: + datasource: + username: root + password: 123 + url: jdbc:mysql:///scheduling2?serverTimezone=Asia/Shanghai + + jpa: + database-platform: mysql + database: mysql + hibernate: + ddl-auto: update + properties: + hibernate: + dialect: org.hibernate.dialect.MySQL57Dialect \ No newline at end of file diff --git a/scheduling/src/main/resources/static/index.html b/scheduling/src/main/resources/static/index.html new file mode 100644 index 0000000..6a89173 --- /dev/null +++ b/scheduling/src/main/resources/static/index.html @@ -0,0 +1,132 @@ + + + + + 定时任务管理系统 + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/scheduling/src/test/java/org/javaboy/scheduling/SchedulingApplicationTests.java b/scheduling/src/test/java/org/javaboy/scheduling/SchedulingApplicationTests.java new file mode 100644 index 0000000..23808a3 --- /dev/null +++ b/scheduling/src/test/java/org/javaboy/scheduling/SchedulingApplicationTests.java @@ -0,0 +1,13 @@ +package org.javaboy.scheduling; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class SchedulingApplicationTests { + + @Test + void contextLoads() { + } + +}