├── README.md ├── spring-boot-demo-aop ├── .gitignore ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── kucw │ │ ├── DemoAopApplication.java │ │ ├── aop │ │ └── MyAop.java │ │ ├── controller │ │ └── IndexController.java │ │ └── service │ │ └── MyService.java │ └── resources │ └── application.properties ├── spring-boot-demo-async ├── .gitignore ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── kucw │ │ ├── DemoAsyncApplication.java │ │ ├── controller │ │ └── IndexController.java │ │ └── service │ │ └── MyService.java │ └── resources │ └── application.properties ├── spring-boot-demo-hateoas ├── .gitignore ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── kucw │ │ ├── DemoHateoasApplication.java │ │ ├── controller │ │ ├── CourseController.java │ │ └── StudentController.java │ │ └── response │ │ └── StudentResponse.java │ └── resources │ └── application.properties ├── spring-boot-demo-interceptor ├── .gitignore ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── kucw │ │ ├── DemoInterceptorApplication.java │ │ ├── config │ │ └── InterceptorConfig.java │ │ ├── controller │ │ └── IndexController.java │ │ └── interceptor │ │ └── MyInterceptor.java │ └── resources │ └── application.properties ├── spring-boot-demo-jdbc ├── .gitignore ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── kucw │ │ ├── DemoJdbcApplication.java │ │ ├── controller │ │ └── IndexController.java │ │ ├── dao │ │ └── StudentDao.java │ │ └── model │ │ └── Student.java │ └── resources │ └── application.properties ├── spring-boot-demo-jpa ├── .gitignore ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── kucw │ │ ├── DemoJpaApplication.java │ │ ├── controller │ │ └── IndexController.java │ │ ├── entity │ │ └── Student.java │ │ └── repository │ │ └── StudentRepository.java │ └── resources │ └── application.properties ├── spring-boot-demo-oauth1a-twitter ├── .gitignore ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── kucw │ │ ├── DemoOauth1aTwitterApplication.java │ │ ├── controller │ │ ├── Oauth1aBindController.java │ │ └── Oauth1aBindWithScribeController.java │ │ └── model │ │ └── TwitterUser.java │ └── resources │ ├── application.properties │ └── templates │ └── index.ftl ├── spring-boot-demo-oauth2-github ├── .gitignore ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── kucw │ │ ├── DemoOauth2GithubApplication.java │ │ ├── controller │ │ ├── Oauth2BindController.java │ │ └── Oauth2BindWithScribeController.java │ │ └── model │ │ └── GithubUser.java │ └── resources │ ├── application.properties │ └── templates │ └── index.ftl ├── spring-boot-demo-rabbitmq ├── .gitignore ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── kucw │ │ ├── DemoRabbitmqApplication.java │ │ ├── Student.java │ │ ├── direct │ │ ├── DirectConsumer.java │ │ ├── DirectController.java │ │ └── DirectProducer.java │ │ ├── routing │ │ ├── RoutingConsumer1.java │ │ ├── RoutingConsumer2.java │ │ ├── RoutingController.java │ │ └── RoutingProducer.java │ │ ├── subscribe │ │ ├── SubscribeConsumer1.java │ │ ├── SubscribeConsumer2.java │ │ ├── SubscribeConsumer3.java │ │ ├── SubscribeController.java │ │ └── SubscribeProducer.java │ │ └── worker │ │ ├── WorkerConsumer1.java │ │ ├── WorkerConsumer2.java │ │ ├── WorkerController.java │ │ └── WorkerProducer.java │ └── resources │ └── application.properties ├── spring-boot-demo-scheduler ├── .gitignore ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── kucw │ │ ├── DemoSchedulerApplication.java │ │ └── scheduler │ │ └── MyScheduler.java │ └── resources │ └── application.properties └── spring-boot-demo-unit-test ├── .gitignore ├── Spring Boot with Unit Test.pdf ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── kucw │ │ ├── DemoUnitTestApplication.java │ │ ├── config │ │ └── DataSourceConfig.java │ │ ├── controller │ │ └── UserController.java │ │ ├── dao │ │ └── UserDao.java │ │ ├── model │ │ └── User.java │ │ └── service │ │ └── UserService.java └── resources │ └── application.properties └── test ├── java └── com │ └── kucw │ ├── controller │ └── UserControllerTest.java │ ├── dao │ └── UserDaoTest.java │ └── service │ ├── UserServiceMockTest.java │ └── UserServiceSpyTest.java └── resources ├── application.properties ├── data.sql └── schema.sql /README.md: -------------------------------------------------------------------------------- 1 | # Spring Boot Demo Projects 2 | 3 | 4 | 5 | ## Environment 6 | 7 | * JDK 1.8 8 | * Spring Boot 2.x 9 | 10 | 11 | 12 | ## Project Introduction 13 | 14 | | 名稱 | 描述 | 教學 | 15 | | -------------------------------- | ------------------------------------------------- | ------------------------------------------------------------ | 16 | | spring-boot-demo-aop | 在 Spring Boot 中配置 Spring AOP | | 17 | | spring-boot-demo-async | 使用 Spring Boot 的 asynchronous 處理 @Async 功能 | | 18 | | spring-boot-demo-hateoas | 在 Spring Boot 中使用 Spring Hateoas | | 19 | | spring-boot-demo-interceptor | 在 Spring Boot 中配置攔截器 Interceptor | | 20 | | spring-boot-demo-jdbc | 在 Spring Boot 中使用 Spring JDBC Template | | 21 | | spring-boot-demo-jpa | 在 Spring Boot 中使用 Spring Data JPA | | 22 | | spring-boot-demo-oauth1a-twitter | 使用 Spring Boot 實作 OAuth 1.0a 綁定 Twitter | [文章教學](https://kucw.github.io/blog/2019/12/spring-oauth1a-bind-twitter/) | 23 | | spring-boot-demo-oauth2-github | 使用 Spring Boot 實作 OAuth 2.0 綁定 Github | [文章教學](https://kucw.github.io/blog/2019/12/spring-oauth2-bind-github/) | 24 | | spring-boot-demo-rabbitmq | 在 Spring Boot 中使用 RabbitMQ | | 25 | | spring-boot-demo-scheduler | 使用 Spring Boot 的定時任務 @Scheduled 功能 | | 26 | | spring-boot-demo-unit-test | Spring Boot 整合 JUnit、H2、Mockito、MockMvc | [投影片教學](https://github.com/kucw/spring-boot-demo/blob/master/spring-boot-demo-unit-test/Spring%20Boot%20with%20Unit%20Test.pdf) | 27 | 28 | -------------------------------------------------------------------------------- /spring-boot-demo-aop/.gitignore: -------------------------------------------------------------------------------- 1 | # kdiff3 ignore 2 | *.orig 3 | 4 | # maven ignore 5 | target/ 6 | 7 | # eclipse ignore 8 | .settings/ 9 | .project 10 | .classpath 11 | .springBeans 12 | 13 | # idea ignore 14 | .idea/ 15 | *.ipr 16 | *.iml 17 | *.iws 18 | 19 | # temp ignore 20 | *.log 21 | *.cache 22 | *.diff 23 | *.patch 24 | *.tmp 25 | 26 | # system ignore 27 | .DS_Store 28 | Thumbs.db 29 | 30 | # package ignore (optional) 31 | # *.jar 32 | # *.war 33 | # *.zip 34 | # *.tar 35 | # *.tar.gz 36 | /bin/ 37 | -------------------------------------------------------------------------------- /spring-boot-demo-aop/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.springframework.boot 9 | spring-boot-starter-parent 10 | 2.2.8.RELEASE 11 | 12 | 13 | 14 | com.kucw 15 | spring-boot-demo-aop 16 | 0.0.1-SNAPSHOT 17 | spring-boot-demo-aop 18 | 19 | 20 | 1.8 21 | 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-web 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-aop 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-maven-plugin 39 | 40 | true 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /spring-boot-demo-aop/src/main/java/com/kucw/DemoAopApplication.java: -------------------------------------------------------------------------------- 1 | package com.kucw; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class DemoAopApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(DemoAopApplication.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-demo-aop/src/main/java/com/kucw/aop/MyAop.java: -------------------------------------------------------------------------------- 1 | package com.kucw.aop; 2 | 3 | import org.aspectj.lang.annotation.Aspect; 4 | import org.aspectj.lang.annotation.Before; 5 | import org.springframework.stereotype.Component; 6 | 7 | //在這個 class 裡就可以寫 Spring aop 的語法 8 | @Aspect 9 | @Component 10 | public class MyAop { 11 | 12 | @Before("execution(* com.kucw.service.MyService.myMethod())") 13 | public void myMethodBefore(){ 14 | System.out.println("Before"); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /spring-boot-demo-aop/src/main/java/com/kucw/controller/IndexController.java: -------------------------------------------------------------------------------- 1 | package com.kucw.controller; 2 | 3 | import com.kucw.service.MyService; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | @RestController 9 | public class IndexController { 10 | 11 | @Autowired 12 | private MyService myService; 13 | 14 | @RequestMapping("") 15 | public String index(){ 16 | return myService.myMethod(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /spring-boot-demo-aop/src/main/java/com/kucw/service/MyService.java: -------------------------------------------------------------------------------- 1 | package com.kucw.service; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | @Component 6 | public class MyService { 7 | 8 | //這個方法會被 MyAop 的 myMethodBefore 切 9 | public String myMethod() { 10 | return "success"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /spring-boot-demo-aop/src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kucw/spring-boot-demo/e6d8b0cfc2efc588bfbd120f46ec0de3e6a1d42b/spring-boot-demo-aop/src/main/resources/application.properties -------------------------------------------------------------------------------- /spring-boot-demo-async/.gitignore: -------------------------------------------------------------------------------- 1 | # kdiff3 ignore 2 | *.orig 3 | 4 | # maven ignore 5 | target/ 6 | 7 | # eclipse ignore 8 | .settings/ 9 | .project 10 | .classpath 11 | .springBeans 12 | 13 | # idea ignore 14 | .idea/ 15 | *.ipr 16 | *.iml 17 | *.iws 18 | 19 | # temp ignore 20 | *.log 21 | *.cache 22 | *.diff 23 | *.patch 24 | *.tmp 25 | 26 | # system ignore 27 | .DS_Store 28 | Thumbs.db 29 | 30 | # package ignore (optional) 31 | # *.jar 32 | # *.war 33 | # *.zip 34 | # *.tar 35 | # *.tar.gz 36 | /bin/ 37 | -------------------------------------------------------------------------------- /spring-boot-demo-async/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.springframework.boot 9 | spring-boot-starter-parent 10 | 2.2.8.RELEASE 11 | 12 | 13 | 14 | com.kucw 15 | spring-boot-demo-async 16 | 0.0.1-SNAPSHOT 17 | spring-boot-demo-async 18 | 19 | 20 | 1.8 21 | 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-web 27 | 28 | 29 | 30 | 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-maven-plugin 35 | 36 | true 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /spring-boot-demo-async/src/main/java/com/kucw/DemoAsyncApplication.java: -------------------------------------------------------------------------------- 1 | package com.kucw; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.scheduling.annotation.EnableAsync; 6 | 7 | @EnableAsync //一定要加這個 @EnableAsync annotation,@Async 才會有作用 8 | @SpringBootApplication 9 | public class DemoAsyncApplication { 10 | public static void main(String[] args) { 11 | SpringApplication.run(DemoAsyncApplication.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-demo-async/src/main/java/com/kucw/controller/IndexController.java: -------------------------------------------------------------------------------- 1 | package com.kucw.controller; 2 | 3 | import com.kucw.service.MyService; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | import java.util.concurrent.ExecutionException; 9 | import java.util.concurrent.Future; 10 | 11 | @RestController 12 | public class IndexController { 13 | 14 | @Autowired 15 | private MyService myService; 16 | 17 | @RequestMapping("test1") 18 | public String test1(){ 19 | myService.myMethodWithoutReturnValue(); 20 | return "test1 success"; 21 | } 22 | 23 | @RequestMapping("test2") 24 | public String test2(){ 25 | Future resultFuture = myService.myMethodReturnString(); 26 | 27 | try { 28 | String result = resultFuture.get(); //取得 myMethodReturnString 方法的返回值 29 | System.out.println("result: " + result); 30 | } catch (Exception e) { 31 | e.printStackTrace(); 32 | } 33 | 34 | return "test2 success"; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /spring-boot-demo-async/src/main/java/com/kucw/service/MyService.java: -------------------------------------------------------------------------------- 1 | package com.kucw.service; 2 | 3 | import org.springframework.scheduling.annotation.Async; 4 | import org.springframework.scheduling.annotation.AsyncResult; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.concurrent.Future; 8 | 9 | @Component 10 | public class MyService { 11 | 12 | //這個方法因為加上了 @Async annotation,所以會被另一個thread asynchronous的執行,因此call他的人不需要等到它做完 13 | @Async 14 | public void myMethodWithoutReturnValue() { 15 | 16 | System.out.println("myMethodWithoutReturnValue start..."); 17 | 18 | //睡5秒,模擬做一個很久的計算 19 | try { 20 | Thread.sleep(5000); 21 | } catch (InterruptedException e) { 22 | e.printStackTrace(); 23 | } 24 | 25 | System.out.println("myMethodWithoutReturnValue finish!"); 26 | } 27 | 28 | //有返回值的 @Async job,返回值要寫成 Future 的形式 29 | @Async 30 | public Future myMethodReturnString() { 31 | 32 | System.out.println("myMethodReturnString start..."); 33 | 34 | //睡5秒,模擬做一個很久的計算 35 | try { 36 | Thread.sleep(5000); 37 | } catch (InterruptedException e) { 38 | e.printStackTrace(); 39 | } 40 | 41 | System.out.println("myMethodReturnString finish!"); 42 | 43 | return new AsyncResult<>("Finish!"); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /spring-boot-demo-async/src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kucw/spring-boot-demo/e6d8b0cfc2efc588bfbd120f46ec0de3e6a1d42b/spring-boot-demo-async/src/main/resources/application.properties -------------------------------------------------------------------------------- /spring-boot-demo-hateoas/.gitignore: -------------------------------------------------------------------------------- 1 | # kdiff3 ignore 2 | *.orig 3 | 4 | # maven ignore 5 | target/ 6 | 7 | # eclipse ignore 8 | .settings/ 9 | .project 10 | .classpath 11 | .springBeans 12 | 13 | # idea ignore 14 | .idea/ 15 | *.ipr 16 | *.iml 17 | *.iws 18 | 19 | # temp ignore 20 | *.log 21 | *.cache 22 | *.diff 23 | *.patch 24 | *.tmp 25 | 26 | # system ignore 27 | .DS_Store 28 | Thumbs.db 29 | 30 | # package ignore (optional) 31 | # *.jar 32 | # *.war 33 | # *.zip 34 | # *.tar 35 | # *.tar.gz 36 | /bin/ 37 | -------------------------------------------------------------------------------- /spring-boot-demo-hateoas/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.springframework.boot 9 | spring-boot-starter-parent 10 | 2.2.8.RELEASE 11 | 12 | 13 | 14 | com.kucw 15 | spring-boot-demo-hateoas 16 | 0.0.1-SNAPSHOT 17 | spring-boot-demo-hateoas 18 | 19 | 20 | 1.8 21 | 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-web 27 | 28 | 29 | org.springframework.hateoas 30 | spring-hateoas 31 | 1.1.0.RELEASE 32 | 33 | 34 | 35 | 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-maven-plugin 40 | 41 | true 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /spring-boot-demo-hateoas/src/main/java/com/kucw/DemoHateoasApplication.java: -------------------------------------------------------------------------------- 1 | package com.kucw; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class DemoHateoasApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(DemoHateoasApplication.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-demo-hateoas/src/main/java/com/kucw/controller/CourseController.java: -------------------------------------------------------------------------------- 1 | package com.kucw.controller; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.http.ResponseEntity; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RequestParam; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | @RestController 10 | public class CourseController { 11 | 12 | // 取得該學生修的課的detail資訊 13 | @RequestMapping("/course") 14 | public ResponseEntity getCourseByStudentId(@RequestParam Integer studentId) { 15 | // 本來這裡應該也要做 HATEOAS,但是先專注在StudentController上的語法就好,所以這裡就隨意返回值了 16 | return new ResponseEntity<>("success", HttpStatus.OK); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /spring-boot-demo-hateoas/src/main/java/com/kucw/controller/StudentController.java: -------------------------------------------------------------------------------- 1 | package com.kucw.controller; 2 | 3 | import com.kucw.response.StudentResponse; 4 | import org.springframework.hateoas.IanaLinkRelations; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; 12 | import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; 13 | 14 | @RestController 15 | public class StudentController { 16 | 17 | @RequestMapping("/student/{studentId}") 18 | public ResponseEntity getStudent(@PathVariable Integer studentId) { 19 | 20 | // 這部分可以替換為從DB撈data,我這裡是直接寫死了,模擬已經從DB取得到data 21 | StudentResponse studentResponse = new StudentResponse(); 22 | studentResponse.setStudentId(studentId); 23 | studentResponse.setName("John"); 24 | studentResponse.setGender("MALE"); 25 | 26 | // 添加 HATEOAS 的 self link 27 | if (!studentResponse.getLink(IanaLinkRelations.SELF).isPresent()) { 28 | studentResponse.add(linkTo(methodOn(StudentController.class).getStudent(studentId)).withSelfRel()); 29 | } 30 | 31 | // 添加 HATEOAS 的 course link 32 | studentResponse.add(linkTo(methodOn(CourseController.class).getCourseByStudentId(studentId)).withRel("course")); 33 | 34 | // 返回結果 35 | return new ResponseEntity<>(studentResponse, HttpStatus.OK); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /spring-boot-demo-hateoas/src/main/java/com/kucw/response/StudentResponse.java: -------------------------------------------------------------------------------- 1 | package com.kucw.response; 2 | 3 | import org.springframework.hateoas.RepresentationModel; 4 | 5 | public class StudentResponse extends RepresentationModel { 6 | private Integer studentId; 7 | private String name; 8 | private String gender; 9 | 10 | public Integer getStudentId() { 11 | return studentId; 12 | } 13 | 14 | public void setStudentId(Integer studentId) { 15 | this.studentId = studentId; 16 | } 17 | 18 | public String getName() { 19 | return name; 20 | } 21 | 22 | public void setName(String name) { 23 | this.name = name; 24 | } 25 | 26 | public String getGender() { 27 | return gender; 28 | } 29 | 30 | public void setGender(String gender) { 31 | this.gender = gender; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /spring-boot-demo-hateoas/src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kucw/spring-boot-demo/e6d8b0cfc2efc588bfbd120f46ec0de3e6a1d42b/spring-boot-demo-hateoas/src/main/resources/application.properties -------------------------------------------------------------------------------- /spring-boot-demo-interceptor/.gitignore: -------------------------------------------------------------------------------- 1 | # kdiff3 ignore 2 | *.orig 3 | 4 | # maven ignore 5 | target/ 6 | 7 | # eclipse ignore 8 | .settings/ 9 | .project 10 | .classpath 11 | .springBeans 12 | 13 | # idea ignore 14 | .idea/ 15 | *.ipr 16 | *.iml 17 | *.iws 18 | 19 | # temp ignore 20 | *.log 21 | *.cache 22 | *.diff 23 | *.patch 24 | *.tmp 25 | 26 | # system ignore 27 | .DS_Store 28 | Thumbs.db 29 | 30 | # package ignore (optional) 31 | # *.jar 32 | # *.war 33 | # *.zip 34 | # *.tar 35 | # *.tar.gz 36 | /bin/ 37 | -------------------------------------------------------------------------------- /spring-boot-demo-interceptor/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.2.8.RELEASE 9 | 10 | 11 | 12 | com.kucw 13 | spring-boot-demo-interceptor 14 | 0.0.1-SNAPSHOT 15 | spring-boot-demo-interceptor 16 | 17 | 18 | 1.8 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | 28 | 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-maven-plugin 33 | 34 | true 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /spring-boot-demo-interceptor/src/main/java/com/kucw/DemoInterceptorApplication.java: -------------------------------------------------------------------------------- 1 | package com.kucw; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class DemoInterceptorApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(DemoInterceptorApplication.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-demo-interceptor/src/main/java/com/kucw/config/InterceptorConfig.java: -------------------------------------------------------------------------------- 1 | package com.kucw.config; 2 | 3 | import com.kucw.interceptor.MyInterceptor; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Import; 7 | import org.springframework.web.servlet.HandlerInterceptor; 8 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 9 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 10 | 11 | //SpringBoot配置攔截器 12 | @Configuration 13 | @Import(MyInterceptor.class) //把攔截器的bean import進來 14 | public class InterceptorConfig implements WebMvcConfigurer { 15 | @Autowired 16 | private HandlerInterceptor myInterceptor; 17 | 18 | @Override 19 | public void addInterceptors(InterceptorRegistry registry) { 20 | //指定哪些uri要被myInterceptor給攔截 21 | registry.addInterceptor(myInterceptor).addPathPatterns("/api/**"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /spring-boot-demo-interceptor/src/main/java/com/kucw/controller/IndexController.java: -------------------------------------------------------------------------------- 1 | package com.kucw.controller; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class IndexController { 8 | 9 | @RequestMapping("/api/index") 10 | public String index() { 11 | return "index"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-demo-interceptor/src/main/java/com/kucw/interceptor/MyInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.kucw.interceptor; 2 | 3 | import org.springframework.stereotype.Component; 4 | import org.springframework.web.servlet.HandlerInterceptor; 5 | import org.springframework.web.servlet.ModelAndView; 6 | 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | 10 | @Component 11 | public class MyInterceptor implements HandlerInterceptor { 12 | @Override 13 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 14 | System.out.println("執行 MyInterceptor 的 preHandler"); 15 | return true; 16 | } 17 | @Override 18 | public void postHandle(HttpServletRequest request, HttpServletResponse response, Object o, ModelAndView modelAndView) throws Exception { 19 | System.out.println("執行 MyInterceptor 的 postHandle"); 20 | } 21 | @Override 22 | public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object o, Exception e) throws Exception { 23 | System.out.println("執行 MyInterceptor 的 afterCompletion"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /spring-boot-demo-interceptor/src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kucw/spring-boot-demo/e6d8b0cfc2efc588bfbd120f46ec0de3e6a1d42b/spring-boot-demo-interceptor/src/main/resources/application.properties -------------------------------------------------------------------------------- /spring-boot-demo-jdbc/.gitignore: -------------------------------------------------------------------------------- 1 | # kdiff3 ignore 2 | *.orig 3 | 4 | # maven ignore 5 | target/ 6 | 7 | # eclipse ignore 8 | .settings/ 9 | .project 10 | .classpath 11 | .springBeans 12 | 13 | # idea ignore 14 | .idea/ 15 | *.ipr 16 | *.iml 17 | *.iws 18 | 19 | # temp ignore 20 | *.log 21 | *.cache 22 | *.diff 23 | *.patch 24 | *.tmp 25 | 26 | # system ignore 27 | .DS_Store 28 | Thumbs.db 29 | 30 | # package ignore (optional) 31 | # *.jar 32 | # *.war 33 | # *.zip 34 | # *.tar 35 | # *.tar.gz 36 | /bin/ 37 | -------------------------------------------------------------------------------- /spring-boot-demo-jdbc/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.springframework.boot 9 | spring-boot-starter-parent 10 | 2.2.8.RELEASE 11 | 12 | 13 | 14 | com.kucw 15 | spring-boot-demo-jdbc 16 | 0.0.1-SNAPSHOT 17 | spring-boot-demo-jdbc 18 | 19 | 20 | 1.8 21 | 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-web 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-jdbc 31 | 32 | 33 | mysql 34 | mysql-connector-java 35 | 8.0.16 36 | 37 | 38 | 39 | 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-maven-plugin 44 | 45 | true 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /spring-boot-demo-jdbc/src/main/java/com/kucw/DemoJdbcApplication.java: -------------------------------------------------------------------------------- 1 | package com.kucw; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class DemoJdbcApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(DemoJdbcApplication.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-demo-jdbc/src/main/java/com/kucw/controller/IndexController.java: -------------------------------------------------------------------------------- 1 | package com.kucw.controller; 2 | 3 | import com.kucw.dao.StudentDao; 4 | import com.kucw.model.Student; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | @RestController 10 | public class IndexController { 11 | 12 | @Autowired 13 | private StudentDao studentDao; 14 | 15 | @RequestMapping("insert") 16 | public String insert(){ 17 | Student student = new Student(); 18 | student.setName("John"); 19 | student.setAge(20); 20 | 21 | studentDao.insert(student); 22 | return "success"; 23 | } 24 | 25 | @RequestMapping("get") 26 | public Student get(){ 27 | return studentDao.getById(1); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /spring-boot-demo-jdbc/src/main/java/com/kucw/dao/StudentDao.java: -------------------------------------------------------------------------------- 1 | package com.kucw.dao; 2 | 3 | import com.kucw.model.Student; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.jdbc.core.BeanPropertyRowMapper; 6 | import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; 7 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; 8 | import org.springframework.jdbc.support.GeneratedKeyHolder; 9 | import org.springframework.jdbc.support.KeyHolder; 10 | import org.springframework.stereotype.Component; 11 | 12 | import java.util.Collections; 13 | import java.util.HashMap; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | @Component 18 | public class StudentDao { 19 | 20 | private static final BeanPropertyRowMapper STUDENT_MAPPER = new BeanPropertyRowMapper<>(Student.class); 21 | 22 | private static final String SQL_INSERT = "INSERT INTO student(name, age) VALUES(:name, :age)"; 23 | private static final String SQL_GET_BY_ID = "SELECT id, name, age FROM student WHERE id = :id"; 24 | 25 | @Autowired 26 | private NamedParameterJdbcTemplate jdbcTemplate; 27 | 28 | public int insert(Student student) { 29 | 30 | Map paramMap = new HashMap<>(); 31 | paramMap.put("name", student.getName()); 32 | paramMap.put("age", student.getAge()); 33 | 34 | // 插入後自動返回此record的id 35 | String[] keyColumnNames = new String[]{"id"}; 36 | KeyHolder keyHolder = new GeneratedKeyHolder(); 37 | jdbcTemplate.update(SQL_INSERT, new MapSqlParameterSource(paramMap), keyHolder, keyColumnNames); 38 | return keyHolder.getKey().intValue(); 39 | } 40 | 41 | public Student getById(Integer id) { 42 | List list = jdbcTemplate.query(SQL_GET_BY_ID, Collections.singletonMap("id", id), STUDENT_MAPPER); 43 | if (!list.isEmpty()) { 44 | return list.get(0); 45 | } 46 | return null; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /spring-boot-demo-jdbc/src/main/java/com/kucw/model/Student.java: -------------------------------------------------------------------------------- 1 | package com.kucw.model; 2 | 3 | public class Student { 4 | private Integer id; 5 | private String name; 6 | private Integer age; 7 | 8 | public Integer getId() { 9 | return id; 10 | } 11 | 12 | public void setId(Integer id) { 13 | this.id = id; 14 | } 15 | 16 | public String getName() { 17 | return name; 18 | } 19 | 20 | public void setName(String name) { 21 | this.name = name; 22 | } 23 | 24 | public Integer getAge() { 25 | return age; 26 | } 27 | 28 | public void setAge(Integer age) { 29 | this.age = age; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /spring-boot-demo-jdbc/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.driverClassName=com.mysql.jdbc.Driver 2 | spring.datasource.url=jdbc:mysql://localhost:3306/mytest?useSSL=false 3 | spring.datasource.username=root 4 | spring.datasource.password=admin1234 -------------------------------------------------------------------------------- /spring-boot-demo-jpa/.gitignore: -------------------------------------------------------------------------------- 1 | # kdiff3 ignore 2 | *.orig 3 | 4 | # maven ignore 5 | target/ 6 | 7 | # eclipse ignore 8 | .settings/ 9 | .project 10 | .classpath 11 | .springBeans 12 | 13 | # idea ignore 14 | .idea/ 15 | *.ipr 16 | *.iml 17 | *.iws 18 | 19 | # temp ignore 20 | *.log 21 | *.cache 22 | *.diff 23 | *.patch 24 | *.tmp 25 | 26 | # system ignore 27 | .DS_Store 28 | Thumbs.db 29 | 30 | # package ignore (optional) 31 | # *.jar 32 | # *.war 33 | # *.zip 34 | # *.tar 35 | # *.tar.gz 36 | /bin/ 37 | -------------------------------------------------------------------------------- /spring-boot-demo-jpa/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.springframework.boot 9 | spring-boot-starter-parent 10 | 2.2.8.RELEASE 11 | 12 | 13 | 14 | com.kucw 15 | spring-boot-demo-jpa 16 | 0.0.1-SNAPSHOT 17 | spring-boot-demo-jpa 18 | 19 | 20 | 1.8 21 | 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-web 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-data-jpa 31 | 32 | 33 | com.h2database 34 | h2 35 | 1.4.200 36 | 37 | 38 | 39 | 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-maven-plugin 44 | 45 | true 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /spring-boot-demo-jpa/src/main/java/com/kucw/DemoJpaApplication.java: -------------------------------------------------------------------------------- 1 | package com.kucw; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class DemoJpaApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(DemoJpaApplication.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-demo-jpa/src/main/java/com/kucw/controller/IndexController.java: -------------------------------------------------------------------------------- 1 | package com.kucw.controller; 2 | 3 | import com.kucw.entity.Student; 4 | import com.kucw.repository.StudentRepository; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RequestParam; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import java.util.Optional; 11 | 12 | @RestController 13 | public class IndexController { 14 | 15 | @Autowired 16 | private StudentRepository studentRepository; 17 | 18 | @RequestMapping("insert") 19 | public String insert(){ 20 | Student student = new Student(); 21 | student.setId(1); 22 | student.setName("John"); 23 | student.setAge(20); 24 | 25 | studentRepository.save(student); 26 | return "insert success"; 27 | } 28 | 29 | @RequestMapping("getById") 30 | public Student getById(@RequestParam Integer id){ 31 | Optional studentOpt = studentRepository.findById(id); //這裡用到了Java8新增的Optional類型 32 | return studentOpt.get(); 33 | } 34 | 35 | @RequestMapping("getByAge") 36 | public Student getByAge(@RequestParam Integer age){ 37 | return studentRepository.findByAge(age); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /spring-boot-demo-jpa/src/main/java/com/kucw/entity/Student.java: -------------------------------------------------------------------------------- 1 | package com.kucw.entity; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | import javax.persistence.GeneratedValue; 6 | import javax.persistence.Id; 7 | 8 | //定義 JPA 的 entity 類 9 | @Entity 10 | public class Student { 11 | @Id 12 | @GeneratedValue 13 | private Integer id; 14 | 15 | @Column 16 | private String name; 17 | 18 | @Column 19 | private Integer age; 20 | 21 | public Integer getId() { 22 | return id; 23 | } 24 | 25 | public void setId(Integer id) { 26 | this.id = id; 27 | } 28 | 29 | public String getName() { 30 | return name; 31 | } 32 | 33 | public void setName(String name) { 34 | this.name = name; 35 | } 36 | 37 | public Integer getAge() { 38 | return age; 39 | } 40 | 41 | public void setAge(Integer age) { 42 | this.age = age; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /spring-boot-demo-jpa/src/main/java/com/kucw/repository/StudentRepository.java: -------------------------------------------------------------------------------- 1 | package com.kucw.repository; 2 | 3 | import com.kucw.entity.Student; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | //這裡就可以寫query的方法,Hibernate會自動幫我們生成對應的SQL,我們只要照著規則寫方法名就可以了 7 | public interface StudentRepository extends CrudRepository { 8 | 9 | Student findByAge(Integer age); //這裡實際上就會生成 SELECT * FROM student WHERE age = :age 的sql語法 10 | 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-demo-jpa/src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kucw/spring-boot-demo/e6d8b0cfc2efc588bfbd120f46ec0de3e6a1d42b/spring-boot-demo-jpa/src/main/resources/application.properties -------------------------------------------------------------------------------- /spring-boot-demo-oauth1a-twitter/.gitignore: -------------------------------------------------------------------------------- 1 | # kdiff3 ignore 2 | *.orig 3 | 4 | # maven ignore 5 | target/ 6 | 7 | # eclipse ignore 8 | .settings/ 9 | .project 10 | .classpath 11 | .springBeans 12 | 13 | # idea ignore 14 | .idea/ 15 | *.ipr 16 | *.iml 17 | *.iws 18 | 19 | # temp ignore 20 | *.log 21 | *.cache 22 | *.diff 23 | *.patch 24 | *.tmp 25 | 26 | # system ignore 27 | .DS_Store 28 | Thumbs.db 29 | 30 | # package ignore (optional) 31 | # *.jar 32 | # *.war 33 | # *.zip 34 | # *.tar 35 | # *.tar.gz 36 | /bin/ 37 | -------------------------------------------------------------------------------- /spring-boot-demo-oauth1a-twitter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.2.2.RELEASE 9 | 10 | 11 | 12 | com.kucw 13 | spring-boot-demo-oauth1a-twitter 14 | 0.0.1-SNAPSHOT 15 | spring-boot-demo-oauth1a-twitter 16 | 17 | 18 | 1.8 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-freemarker 29 | 30 | 31 | com.github.scribejava 32 | scribejava-apis 33 | 6.9.0 34 | 35 | 36 | org.apache.httpcomponents 37 | httpclient 38 | 4.5.5 39 | 40 | 41 | com.google.guava 42 | guava 43 | 28.1-jre 44 | 45 | 46 | 47 | 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-maven-plugin 52 | 53 | true 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /spring-boot-demo-oauth1a-twitter/src/main/java/com/kucw/DemoOauth1aTwitterApplication.java: -------------------------------------------------------------------------------- 1 | package com.kucw; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class DemoOauth1aTwitterApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(DemoOauth1aTwitterApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-demo-oauth1a-twitter/src/main/java/com/kucw/controller/Oauth1aBindController.java: -------------------------------------------------------------------------------- 1 | package com.kucw.controller; 2 | 3 | import com.kucw.model.TwitterUser; 4 | import com.github.scribejava.core.services.HMACSha1SignatureService; 5 | import com.google.common.base.Splitter; 6 | import org.apache.http.HttpEntity; 7 | import org.apache.http.HttpResponse; 8 | import org.apache.http.client.HttpClient; 9 | import org.apache.http.client.entity.UrlEncodedFormEntity; 10 | import org.apache.http.client.methods.HttpPost; 11 | import org.apache.http.impl.client.HttpClients; 12 | import org.apache.http.message.BasicNameValuePair; 13 | import org.apache.http.util.EntityUtils; 14 | import org.springframework.stereotype.Controller; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RequestParam; 17 | import org.springframework.web.servlet.ModelAndView; 18 | 19 | import java.net.URLEncoder; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.Map; 23 | 24 | /** 25 | * Twitter OAuth1.0a 官方文件 https://developer.twitter.com/en/docs/basics/authentication/oauth-1-0a/obtaining-user-access-tokens 26 | */ 27 | @RequestMapping("") 28 | @Controller 29 | public class Oauth1aBindController { 30 | 31 | private static final String CONSUMER_KEY = "CIZ7N7mJWqztIrMiClu2WWpag"; 32 | private static final String CONSUMER_SECRET = "aSgplBUypZPZPExRGfJoxtT03g0SynYhIOxn7ZkEvIImXKxy8n"; 33 | private static final String CALLBACK_URL = "https://c4ba298b.ngrok.io/callback"; 34 | 35 | String requestToken; 36 | String requestTokenSecret; 37 | 38 | @RequestMapping("") 39 | public ModelAndView index() { 40 | return new ModelAndView("index"); 41 | } 42 | 43 | @RequestMapping("/bind") 44 | public String bind() throws Exception { 45 | // 向 Twitter 獲取臨時token 46 | HttpPost post = new HttpPost("https://api.twitter.com/oauth/request_token"); 47 | 48 | List parameters = new ArrayList<>(); 49 | parameters.add(new BasicNameValuePair("oauth_callback", CALLBACK_URL)); 50 | parameters.add(new BasicNameValuePair("oauth_consumer_key", CONSUMER_KEY)); 51 | parameters.add(new BasicNameValuePair("oauth_signature_method", "HMAC-SHA1")); 52 | parameters.add(new BasicNameValuePair("oauth_nonce", "1452456779")); //模擬隨機生成一個混淆字符串 53 | parameters.add(new BasicNameValuePair("oauth_version", "1.0")); 54 | 55 | String currentTimestamp = String.valueOf(System.currentTimeMillis() / 1000); 56 | parameters.add(new BasicNameValuePair("oauth_timestamp", currentTimestamp)); //隨機生成一個timestamp 57 | 58 | // 分成三部分來 encode,組合成一行 baseString 之後,最後再用 HMAC-SHA1 算法簽起來 59 | String verb = "POST"; 60 | String url = URLEncoder.encode("https://api.twitter.com/oauth/request_token", "utf-8"); 61 | String parameter = URLEncoder.encode("oauth_callback=" + URLEncoder.encode(CALLBACK_URL, "utf-8") + "&" + "oauth_consumer_key=" + CONSUMER_KEY + "&oauth_nonce=1452456779&oauth_signature_method=HMAC-SHA1&oauth_timestamp=" + currentTimestamp + "&oauth_version=1.0", "utf-8"); 62 | String baseString = verb + "&" + url + "&" + parameter; 63 | 64 | parameters.add(new BasicNameValuePair("oauth_signature", generateSignature(baseString, ""))); 65 | post.setEntity(new UrlEncodedFormEntity(parameters, "utf-8")); 66 | 67 | // 取得 response 中的臨時token 68 | HttpClient httpClient = HttpClients.createDefault(); 69 | HttpResponse response = httpClient.execute(post); 70 | HttpEntity httpEntity = response.getEntity(); 71 | String result = EntityUtils.toString(httpEntity, "utf-8"); 72 | 73 | Map map = Splitter.on("&").withKeyValueSeparator("=").split(result); 74 | requestToken = map.get("oauth_token"); 75 | requestTokenSecret = map.get("oauth_token_secret"); 76 | 77 | // redirect 到 Twitter 授權頁 78 | return "redirect:https://api.twitter.com/oauth/authorize?oauth_token=" + requestToken; 79 | } 80 | 81 | @RequestMapping("/callback") 82 | public ModelAndView callback(@RequestParam(name = "oauth_token") String oauthToken, 83 | @RequestParam(name = "oauth_verifier") String oauthVerifier) throws Exception { 84 | // 當 user 在 Twitter 那裡按下確認按鈕後,Twitter 就會將 user 導回這個 callback url,順便告訴我們這是哪個oauthToken的verifier 85 | 86 | // 使用剛剛Twitter confirmed 的 verifier 和前面申請的 臨時token 去交換 accessToken 87 | HttpPost post = new HttpPost("https://api.twitter.com/oauth/access_token"); 88 | 89 | List parameters = new ArrayList<>(); 90 | parameters.add(new BasicNameValuePair("oauth_callback", CALLBACK_URL)); 91 | parameters.add(new BasicNameValuePair("oauth_token", requestToken)); 92 | parameters.add(new BasicNameValuePair("oauth_consumer_key", CONSUMER_KEY)); 93 | parameters.add(new BasicNameValuePair("oauth_signature_method", "HMAC-SHA1")); 94 | parameters.add(new BasicNameValuePair("oauth_nonce", "1452456779")); //模擬隨機生成一個混淆字符串 95 | parameters.add(new BasicNameValuePair("oauth_verifier", oauthVerifier)); 96 | parameters.add(new BasicNameValuePair("oauth_version", "1.0")); 97 | 98 | String currentTimestamp = String.valueOf(System.currentTimeMillis() / 1000); 99 | parameters.add(new BasicNameValuePair("oauth_timestamp", currentTimestamp)); //隨機生成一個timestamp 100 | 101 | // 分成三部分來 encode,組合成一行 baseString 之後,最後再用 HMAC-SHA1 算法簽起來 102 | String verb = "POST"; 103 | String url = URLEncoder.encode("https://api.twitter.com/oauth/access_token", "utf-8"); 104 | String parameter = URLEncoder.encode("oauth_callback=" + URLEncoder.encode(CALLBACK_URL, "utf-8") + "&" + "oauth_consumer_key=" + CONSUMER_KEY + "&oauth_nonce=1452456779&oauth_token= " + requestToken + "&oauth_signature_method=HMAC-SHA1&oauth_timestamp=" + currentTimestamp + "&oauth_verifier=" + oauthVerifier + "&oauth_version=1.0", "utf-8"); 105 | String baseString = verb + "&" + url + "&" + parameter; 106 | 107 | parameters.add(new BasicNameValuePair("oauth_signature", generateSignature(baseString, requestTokenSecret))); 108 | post.setEntity(new UrlEncodedFormEntity(parameters, "utf-8")); 109 | 110 | // 取得 response 中的 accessToken 111 | HttpClient httpClient = HttpClients.createDefault(); 112 | HttpResponse response = httpClient.execute(post); 113 | HttpEntity httpEntity = response.getEntity(); 114 | String result = EntityUtils.toString(httpEntity, "utf-8"); 115 | Map map = Splitter.on("&").withKeyValueSeparator("=").split(result); 116 | String accessToken = map.get("oauth_token"); 117 | String accessTokenSecret = map.get("oauth_token_secret"); 118 | 119 | // 直接將 accessToken 和 accessTokenSecret 展示在前端頁面,後面懶得做了... 120 | ModelAndView mv = new ModelAndView("index"); 121 | mv.addObject("twitterUser", new TwitterUser(accessToken, accessTokenSecret)); 122 | return mv; 123 | } 124 | 125 | private String generateSignature(String baseString, String tokenSecret) { 126 | HMACSha1SignatureService signatureService = new HMACSha1SignatureService(); 127 | return signatureService.getSignature(baseString, CONSUMER_SECRET, tokenSecret); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /spring-boot-demo-oauth1a-twitter/src/main/java/com/kucw/controller/Oauth1aBindWithScribeController.java: -------------------------------------------------------------------------------- 1 | package com.kucw.controller; 2 | 3 | import com.kucw.model.TwitterUser; 4 | import com.fasterxml.jackson.databind.DeserializationFeature; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.github.scribejava.apis.TwitterApi; 7 | import com.github.scribejava.core.builder.ServiceBuilder; 8 | import com.github.scribejava.core.model.*; 9 | import com.github.scribejava.core.oauth.OAuth10aService; 10 | import org.springframework.stereotype.Controller; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RequestParam; 13 | import org.springframework.web.servlet.ModelAndView; 14 | 15 | /** 16 | * Twitter OAuth1.0a 官方文件 https://developer.twitter.com/en/docs/basics/authentication/oauth-1-0a/obtaining-user-access-tokens 17 | * 使用 scribe library 輔助我們進行 OAuth call 18 | */ 19 | @RequestMapping("/scribe") 20 | @Controller 21 | public class Oauth1aBindWithScribeController { 22 | 23 | private static final String CONSUMER_KEY = "CIZ7N7mJWqztIrMiClu2WWpag"; 24 | private static final String CONSUMER_SECRET = "aSgplBUypZPZPExRGfJoxtT03g0SynYhIOxn7ZkEvIImXKxy8n"; 25 | 26 | private OAuth10aService twitterOAuthService = new ServiceBuilder(CONSUMER_KEY) 27 | .apiSecret(CONSUMER_SECRET) 28 | .callback("https://c4ba298b.ngrok.io/callback") 29 | .build(TwitterApi.instance()); 30 | 31 | private OAuth1RequestToken requestToken; 32 | 33 | private ObjectMapper objectMapper = new ObjectMapper() 34 | .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) 35 | .setPropertyNamingStrategy(com.fasterxml.jackson.databind.PropertyNamingStrategy.SNAKE_CASE); 36 | 37 | @RequestMapping("") 38 | public ModelAndView index() { 39 | return new ModelAndView("index"); 40 | } 41 | 42 | @RequestMapping("/bind") 43 | public String bind() throws Exception { 44 | // 向 Twitter 獲取臨時token 45 | requestToken = twitterOAuthService.getRequestToken(); 46 | 47 | // redirect 到 Twitter 授權頁 48 | return "redirect:" + twitterOAuthService.getAuthorizationUrl(requestToken); 49 | } 50 | 51 | @RequestMapping("/callback") 52 | public ModelAndView callback(@RequestParam(name = "oauth_token") String oauthToken, 53 | @RequestParam(name = "oauth_verifier") String oauthVerifier) throws Exception { 54 | // 當 user 在 Twitter 那裡按下確認按鈕後,Twitter 就會將 user 導回這個 callback url,順便告訴我們這是哪個oauthToken的verifier 55 | 56 | // 使用剛剛Twitter confirmed 的 verifier 和前面申請的 臨時token 去交換 accessToken 57 | OAuth1AccessToken accessToken = twitterOAuthService.getAccessToken(requestToken, oauthVerifier); 58 | 59 | // 拿到 accessToken 之後,向 Twitter api 取得該 user 的 data 60 | OAuthRequest request = new OAuthRequest(Verb.GET, "https://api.twitter.com/1.1/account/settings.json"); 61 | twitterOAuthService.signRequest(accessToken, request); 62 | Response response = twitterOAuthService.execute(request); 63 | String json = response.getBody(); 64 | TwitterUser twitterUser = objectMapper.readValue(json, TwitterUser.class); 65 | 66 | // 將 data 展示在前端頁面 67 | ModelAndView mv = new ModelAndView("index"); 68 | mv.addObject("twitterUser", twitterUser); 69 | return mv; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /spring-boot-demo-oauth1a-twitter/src/main/java/com/kucw/model/TwitterUser.java: -------------------------------------------------------------------------------- 1 | package com.kucw.model; 2 | 3 | import java.io.Serializable; 4 | 5 | public class TwitterUser implements Serializable { 6 | private static final long serialVersionUID = 1L; 7 | String screenName; 8 | String language; 9 | String accessToken; 10 | String accessTokenSecret; 11 | 12 | public TwitterUser() {} 13 | 14 | public TwitterUser(String accessToken, String accessTokenSecret) { 15 | this.accessToken = accessToken; 16 | this.accessTokenSecret = accessTokenSecret; 17 | } 18 | 19 | public String getScreenName() { 20 | return screenName; 21 | } 22 | 23 | public void setScreenName(String screenName) { 24 | this.screenName = screenName; 25 | } 26 | 27 | public String getLanguage() { 28 | return language; 29 | } 30 | 31 | public void setLanguage(String language) { 32 | this.language = language; 33 | } 34 | 35 | public String getAccessToken() { 36 | return accessToken; 37 | } 38 | 39 | public void setAccessToken(String accessToken) { 40 | this.accessToken = accessToken; 41 | } 42 | 43 | public String getAccessTokenSecret() { 44 | return accessTokenSecret; 45 | } 46 | 47 | public void setAccessTokenSecret(String accessTokenSecret) { 48 | this.accessTokenSecret = accessTokenSecret; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /spring-boot-demo-oauth1a-twitter/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # freemarker setting 2 | spring.mvc.static-path-pattern=/static/** 3 | spring.freemarker.template-loader-path=classpath:/templates 4 | spring.freemarker.suffix=.ftl 5 | spring.freemarker.cache=false 6 | spring.freemarker.request-context-attribute=request 7 | -------------------------------------------------------------------------------- /spring-boot-demo-oauth1a-twitter/src/main/resources/templates/index.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

Bind Twitter OAuth 1.0a demo

7 | Click me to start binding 8 | 9 |

Name: <#if twitterUser??>${twitterUser.screenName!}

10 |

Language: <#if twitterUser??>${twitterUser.language!}

11 |

accessToken: <#if twitterUser??>${twitterUser.accessToken}

12 |

accessTokenSecret: <#if twitterUser??>${twitterUser.accessTokenSecret}

13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /spring-boot-demo-oauth2-github/.gitignore: -------------------------------------------------------------------------------- 1 | # kdiff3 ignore 2 | *.orig 3 | 4 | # maven ignore 5 | target/ 6 | 7 | # eclipse ignore 8 | .settings/ 9 | .project 10 | .classpath 11 | .springBeans 12 | 13 | # idea ignore 14 | .idea/ 15 | *.ipr 16 | *.iml 17 | *.iws 18 | 19 | # temp ignore 20 | *.log 21 | *.cache 22 | *.diff 23 | *.patch 24 | *.tmp 25 | 26 | # system ignore 27 | .DS_Store 28 | Thumbs.db 29 | 30 | # package ignore (optional) 31 | # *.jar 32 | # *.war 33 | # *.zip 34 | # *.tar 35 | # *.tar.gz 36 | /bin/ 37 | -------------------------------------------------------------------------------- /spring-boot-demo-oauth2-github/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.2.2.RELEASE 9 | 10 | 11 | 12 | com.kucw 13 | spring-boot-demo-oauth2-github 14 | 0.0.1-SNAPSHOT 15 | spring-boot-demo-oauth2-github 16 | 17 | 18 | 1.8 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-freemarker 29 | 30 | 31 | com.github.scribejava 32 | scribejava-apis 33 | 6.9.0 34 | 35 | 36 | org.apache.httpcomponents 37 | httpclient 38 | 4.5.5 39 | 40 | 41 | com.google.guava 42 | guava 43 | 28.1-jre 44 | 45 | 46 | 47 | 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-maven-plugin 52 | 53 | true 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /spring-boot-demo-oauth2-github/src/main/java/com/kucw/DemoOauth2GithubApplication.java: -------------------------------------------------------------------------------- 1 | package com.kucw; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class DemoOauth2GithubApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(DemoOauth2GithubApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-demo-oauth2-github/src/main/java/com/kucw/controller/Oauth2BindController.java: -------------------------------------------------------------------------------- 1 | package com.kucw.controller; 2 | 3 | import com.kucw.model.GithubUser; 4 | import com.fasterxml.jackson.databind.DeserializationFeature; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.google.common.base.Splitter; 7 | import org.apache.http.HttpEntity; 8 | import org.apache.http.HttpResponse; 9 | import org.apache.http.client.HttpClient; 10 | import org.apache.http.client.entity.UrlEncodedFormEntity; 11 | import org.apache.http.client.methods.HttpGet; 12 | import org.apache.http.client.methods.HttpPost; 13 | import org.apache.http.impl.client.HttpClients; 14 | import org.apache.http.message.BasicNameValuePair; 15 | import org.apache.http.util.EntityUtils; 16 | import org.springframework.stereotype.Controller; 17 | import org.springframework.web.bind.annotation.RequestMapping; 18 | import org.springframework.web.bind.annotation.RequestParam; 19 | import org.springframework.web.servlet.ModelAndView; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | /** 26 | * Github OAuth2 官方文件 https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/ 27 | */ 28 | @RequestMapping("") 29 | @Controller 30 | public class Oauth2BindController { 31 | 32 | private static final String CLIENT_ID = "c84e3e988d00b01a3cba"; 33 | private static final String CLIENT_SECRET = "ecd565c42e2ed0298e916e5d0dad8ad9733d4db1"; 34 | private static final String CALLBACK_URL = "http://localhost:8080/callback"; 35 | 36 | private ObjectMapper objectMapper = new ObjectMapper() 37 | .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) 38 | .setPropertyNamingStrategy(com.fasterxml.jackson.databind.PropertyNamingStrategy.SNAKE_CASE); 39 | 40 | @RequestMapping("") 41 | public ModelAndView index() { 42 | return new ModelAndView("index"); 43 | } 44 | 45 | @RequestMapping("/redirectToGithub") 46 | public String redirectToGithub() { 47 | return "redirect:https://github.com/login/oauth/authorize?client_id=" + CLIENT_ID + "&redirect_uri=" + CALLBACK_URL; 48 | } 49 | 50 | @RequestMapping("/callback") 51 | public ModelAndView callbackWithoutScribe(@RequestParam String code) throws Exception { 52 | // 向 Github 取得 accessToken 53 | HttpPost post = new HttpPost("https://github.com/login/oauth/access_token"); 54 | 55 | List parameters = new ArrayList<>(); 56 | parameters.add(new BasicNameValuePair("client_id", CLIENT_ID)); 57 | parameters.add(new BasicNameValuePair("client_secret", CLIENT_SECRET)); 58 | parameters.add(new BasicNameValuePair("code", code)); 59 | post.setEntity(new UrlEncodedFormEntity(parameters, "utf-8")); 60 | 61 | // 取得 response 中的 accessToken 62 | HttpClient httpClient = HttpClients.createDefault(); 63 | HttpResponse response = httpClient.execute(post); 64 | HttpEntity httpEntity = response.getEntity(); 65 | String result = EntityUtils.toString(httpEntity, "utf-8"); 66 | Map map = Splitter.on("&").withKeyValueSeparator("=").split(result); 67 | String accessToken = map.get("access_token"); 68 | 69 | // 拿到 accessToken 之後,向 Github user api 取得該 user 的 data 70 | HttpGet get = new HttpGet("https://api.github.com/user"); 71 | get.addHeader("Authorization", "token " + accessToken); 72 | HttpResponse getResponse = httpClient.execute(get); 73 | String json = EntityUtils.toString(getResponse.getEntity(), "utf-8"); 74 | GithubUser githubUser = objectMapper.readValue(json, GithubUser.class); 75 | 76 | // 將 data 展示在前端頁面 77 | ModelAndView mv = new ModelAndView("index"); 78 | mv.addObject("githubUser", githubUser); 79 | return mv; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /spring-boot-demo-oauth2-github/src/main/java/com/kucw/controller/Oauth2BindWithScribeController.java: -------------------------------------------------------------------------------- 1 | package com.kucw.controller; 2 | 3 | import com.kucw.model.GithubUser; 4 | import com.fasterxml.jackson.databind.DeserializationFeature; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.github.scribejava.apis.GitHubApi; 7 | import com.github.scribejava.core.builder.ServiceBuilder; 8 | import com.github.scribejava.core.model.OAuth2AccessToken; 9 | import com.github.scribejava.core.model.OAuthRequest; 10 | import com.github.scribejava.core.model.Response; 11 | import com.github.scribejava.core.model.Verb; 12 | import com.github.scribejava.core.oauth.OAuth20Service; 13 | import org.springframework.stereotype.Controller; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RequestParam; 16 | import org.springframework.web.servlet.ModelAndView; 17 | 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | /** 22 | * Github OAuth2 官方文件 https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/ 23 | * 使用 scribe library 輔助我們進行 OAuth call 24 | */ 25 | @RequestMapping("/scribe") 26 | @Controller 27 | public class Oauth2BindWithScribeController { 28 | 29 | private static final String CLIENT_ID = "c84e3e988d00b01a3cba"; 30 | private static final String CLIENT_SECRET = "ecd565c42e2ed0298e916e5d0dad8ad9733d4db1"; 31 | private static final String CALLBACK_URL = "http://localhost:8080/callback"; 32 | 33 | private OAuth20Service githubOAuthService = new ServiceBuilder(CLIENT_ID).apiSecret(CLIENT_SECRET).build(GitHubApi.instance()); 34 | 35 | private ObjectMapper objectMapper = new ObjectMapper() 36 | .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) 37 | .setPropertyNamingStrategy(com.fasterxml.jackson.databind.PropertyNamingStrategy.SNAKE_CASE); 38 | 39 | @RequestMapping("") 40 | public ModelAndView index() { 41 | return new ModelAndView("index"); 42 | } 43 | 44 | @RequestMapping("/redirectToGithub") 45 | public String redirectToGithub() { 46 | Map paramMap = new HashMap<>(); 47 | paramMap.put("client_id", CLIENT_ID); 48 | paramMap.put("redirect_uri", CALLBACK_URL); 49 | return "redirect:" + githubOAuthService.getAuthorizationUrl(paramMap); 50 | } 51 | 52 | @RequestMapping("/callback") 53 | public ModelAndView callback(@RequestParam String code) throws Exception { 54 | // 向 Github 取得 accessToken 55 | OAuth2AccessToken accessToken = githubOAuthService.getAccessToken(code); 56 | 57 | // 拿到 accessToken 之後,向 Github user api 取得該 user 的 data 58 | OAuthRequest request = new OAuthRequest(Verb.GET, "https://api.github.com/user"); 59 | githubOAuthService.signRequest(accessToken, request); 60 | Response response = githubOAuthService.execute(request); 61 | String json = response.getBody(); 62 | GithubUser githubUser = objectMapper.readValue(json, GithubUser.class); 63 | 64 | // 將 data 展示在前端頁面 65 | ModelAndView mv = new ModelAndView("index"); 66 | mv.addObject("githubUser", githubUser); 67 | return mv; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /spring-boot-demo-oauth2-github/src/main/java/com/kucw/model/GithubUser.java: -------------------------------------------------------------------------------- 1 | package com.kucw.model; 2 | 3 | import java.io.Serializable; 4 | 5 | public class GithubUser implements Serializable { 6 | private static final long serialVersionUID = 1L; 7 | String login; 8 | int id; 9 | String nodeId; 10 | String avatarUrl; 11 | String gravatarId; 12 | String url; 13 | String htmlUrl; 14 | String followersUrl; 15 | String followingUrl; 16 | String gistsUrl; 17 | String starredUrl; 18 | String subscriptionsUrl; 19 | String organizationsUrl; 20 | String reposUrl; 21 | String eventsUrl; 22 | String receivedEventsUrl; 23 | String type; 24 | boolean siteAdmin; 25 | String name; 26 | String company; 27 | String blog; 28 | String location; 29 | String email; 30 | String hireable; 31 | String bio; 32 | int publicRepos; 33 | int publicGists; 34 | int followers; 35 | int following; 36 | String createdAt; 37 | String updatedAt; 38 | 39 | public String getLogin() { 40 | return login; 41 | } 42 | 43 | public void setLogin(String login) { 44 | this.login = login; 45 | } 46 | 47 | public int getId() { 48 | return id; 49 | } 50 | 51 | public void setId(int id) { 52 | this.id = id; 53 | } 54 | 55 | public String getNodeId() { 56 | return nodeId; 57 | } 58 | 59 | public void setNodeId(String nodeId) { 60 | this.nodeId = nodeId; 61 | } 62 | 63 | public String getAvatarUrl() { 64 | return avatarUrl; 65 | } 66 | 67 | public void setAvatarUrl(String avatarUrl) { 68 | this.avatarUrl = avatarUrl; 69 | } 70 | 71 | public String getGravatarId() { 72 | return gravatarId; 73 | } 74 | 75 | public void setGravatarId(String gravatarId) { 76 | this.gravatarId = gravatarId; 77 | } 78 | 79 | public String getUrl() { 80 | return url; 81 | } 82 | 83 | public void setUrl(String url) { 84 | this.url = url; 85 | } 86 | 87 | public String getHtmlUrl() { 88 | return htmlUrl; 89 | } 90 | 91 | public void setHtmlUrl(String htmlUrl) { 92 | this.htmlUrl = htmlUrl; 93 | } 94 | 95 | public String getFollowersUrl() { 96 | return followersUrl; 97 | } 98 | 99 | public void setFollowersUrl(String followersUrl) { 100 | this.followersUrl = followersUrl; 101 | } 102 | 103 | public String getFollowingUrl() { 104 | return followingUrl; 105 | } 106 | 107 | public void setFollowingUrl(String followingUrl) { 108 | this.followingUrl = followingUrl; 109 | } 110 | 111 | public String getGistsUrl() { 112 | return gistsUrl; 113 | } 114 | 115 | public void setGistsUrl(String gistsUrl) { 116 | this.gistsUrl = gistsUrl; 117 | } 118 | 119 | public String getStarredUrl() { 120 | return starredUrl; 121 | } 122 | 123 | public void setStarredUrl(String starredUrl) { 124 | this.starredUrl = starredUrl; 125 | } 126 | 127 | public String getSubscriptionsUrl() { 128 | return subscriptionsUrl; 129 | } 130 | 131 | public void setSubscriptionsUrl(String subscriptionsUrl) { 132 | this.subscriptionsUrl = subscriptionsUrl; 133 | } 134 | 135 | public String getOrganizationsUrl() { 136 | return organizationsUrl; 137 | } 138 | 139 | public void setOrganizationsUrl(String organizationsUrl) { 140 | this.organizationsUrl = organizationsUrl; 141 | } 142 | 143 | public String getReposUrl() { 144 | return reposUrl; 145 | } 146 | 147 | public void setReposUrl(String reposUrl) { 148 | this.reposUrl = reposUrl; 149 | } 150 | 151 | public String getEventsUrl() { 152 | return eventsUrl; 153 | } 154 | 155 | public void setEventsUrl(String eventsUrl) { 156 | this.eventsUrl = eventsUrl; 157 | } 158 | 159 | public String getReceivedEventsUrl() { 160 | return receivedEventsUrl; 161 | } 162 | 163 | public void setReceivedEventsUrl(String receivedEventsUrl) { 164 | this.receivedEventsUrl = receivedEventsUrl; 165 | } 166 | 167 | public String getType() { 168 | return type; 169 | } 170 | 171 | public void setType(String type) { 172 | this.type = type; 173 | } 174 | 175 | public boolean isSiteAdmin() { 176 | return siteAdmin; 177 | } 178 | 179 | public void setSiteAdmin(boolean siteAdmin) { 180 | this.siteAdmin = siteAdmin; 181 | } 182 | 183 | public String getName() { 184 | return name; 185 | } 186 | 187 | public void setName(String name) { 188 | this.name = name; 189 | } 190 | 191 | public String getCompany() { 192 | return company; 193 | } 194 | 195 | public void setCompany(String company) { 196 | this.company = company; 197 | } 198 | 199 | public String getBlog() { 200 | return blog; 201 | } 202 | 203 | public void setBlog(String blog) { 204 | this.blog = blog; 205 | } 206 | 207 | public String getLocation() { 208 | return location; 209 | } 210 | 211 | public void setLocation(String location) { 212 | this.location = location; 213 | } 214 | 215 | public String getEmail() { 216 | return email; 217 | } 218 | 219 | public void setEmail(String email) { 220 | this.email = email; 221 | } 222 | 223 | public String getHireable() { 224 | return hireable; 225 | } 226 | 227 | public void setHireable(String hireable) { 228 | this.hireable = hireable; 229 | } 230 | 231 | public String getBio() { 232 | return bio; 233 | } 234 | 235 | public void setBio(String bio) { 236 | this.bio = bio; 237 | } 238 | 239 | public int getPublicRepos() { 240 | return publicRepos; 241 | } 242 | 243 | public void setPublicRepos(int publicRepos) { 244 | this.publicRepos = publicRepos; 245 | } 246 | 247 | public int getPublicGists() { 248 | return publicGists; 249 | } 250 | 251 | public void setPublicGists(int publicGists) { 252 | this.publicGists = publicGists; 253 | } 254 | 255 | public int getFollowers() { 256 | return followers; 257 | } 258 | 259 | public void setFollowers(int followers) { 260 | this.followers = followers; 261 | } 262 | 263 | public int getFollowing() { 264 | return following; 265 | } 266 | 267 | public void setFollowing(int following) { 268 | this.following = following; 269 | } 270 | 271 | public String getCreatedAt() { 272 | return createdAt; 273 | } 274 | 275 | public void setCreatedAt(String createdAt) { 276 | this.createdAt = createdAt; 277 | } 278 | 279 | public String getUpdatedAt() { 280 | return updatedAt; 281 | } 282 | 283 | public void setUpdatedAt(String updatedAt) { 284 | this.updatedAt = updatedAt; 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /spring-boot-demo-oauth2-github/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # freemarker setting 2 | spring.mvc.static-path-pattern=/static/** 3 | spring.freemarker.template-loader-path=classpath:/templates 4 | spring.freemarker.suffix=.ftl 5 | spring.freemarker.cache=false 6 | spring.freemarker.request-context-attribute=request -------------------------------------------------------------------------------- /spring-boot-demo-oauth2-github/src/main/resources/templates/index.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

Bind Github OAuth 2.0 demo

7 | Click me to start binding 8 | 9 |

Name: <#if githubUser??>${githubUser.name}

10 |

Image: <#if githubUser??>

11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /spring-boot-demo-rabbitmq/.gitignore: -------------------------------------------------------------------------------- 1 | # kdiff3 ignore 2 | *.orig 3 | 4 | # maven ignore 5 | target/ 6 | 7 | # eclipse ignore 8 | .settings/ 9 | .project 10 | .classpath 11 | .springBeans 12 | 13 | # idea ignore 14 | .idea/ 15 | *.ipr 16 | *.iml 17 | *.iws 18 | 19 | # temp ignore 20 | *.log 21 | *.cache 22 | *.diff 23 | *.patch 24 | *.tmp 25 | 26 | # system ignore 27 | .DS_Store 28 | Thumbs.db 29 | 30 | # package ignore (optional) 31 | # *.jar 32 | # *.war 33 | # *.zip 34 | # *.tar 35 | # *.tar.gz 36 | /bin/ 37 | -------------------------------------------------------------------------------- /spring-boot-demo-rabbitmq/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.springframework.boot 9 | spring-boot-starter-parent 10 | 2.2.8.RELEASE 11 | 12 | 13 | 14 | com.kucw 15 | spring-boot-demo-rabbitmq 16 | 0.0.1-SNAPSHOT 17 | spring-boot-demo-rabbitmq 18 | 19 | 20 | 1.8 21 | 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-web 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-amqp 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-maven-plugin 39 | 40 | true 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /spring-boot-demo-rabbitmq/src/main/java/com/kucw/DemoRabbitmqApplication.java: -------------------------------------------------------------------------------- 1 | package com.kucw; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class DemoRabbitmqApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(DemoRabbitmqApplication.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-demo-rabbitmq/src/main/java/com/kucw/Student.java: -------------------------------------------------------------------------------- 1 | package com.kucw; 2 | 3 | import java.io.Serializable; 4 | 5 | public class Student implements Serializable { 6 | 7 | private static final long serialVersionUID = 11234877563926L; 8 | 9 | private Integer id; 10 | private String name; 11 | 12 | public Student(Integer id, String name) { 13 | this.id = id; 14 | this.name = name; 15 | } 16 | 17 | public Integer getId() { 18 | return id; 19 | } 20 | 21 | public void setId(Integer id) { 22 | this.id = id; 23 | } 24 | 25 | public String getName() { 26 | return name; 27 | } 28 | 29 | public void setName(String name) { 30 | this.name = name; 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return "Student{" + 36 | "id=" + id + 37 | ", name='" + name + '\'' + 38 | '}'; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /spring-boot-demo-rabbitmq/src/main/java/com/kucw/direct/DirectConsumer.java: -------------------------------------------------------------------------------- 1 | package com.kucw.direct; 2 | 3 | import com.kucw.Student; 4 | import org.springframework.amqp.rabbit.annotation.RabbitListener; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class DirectConsumer { 9 | 10 | @RabbitListener(queues = "DIRECT_QUEUE") 11 | public void listen(Student student) { 12 | System.out.println("receive message from DIRECT_QUEUE: " + student); 13 | } 14 | } -------------------------------------------------------------------------------- /spring-boot-demo-rabbitmq/src/main/java/com/kucw/direct/DirectController.java: -------------------------------------------------------------------------------- 1 | package com.kucw.direct; 2 | 3 | import com.kucw.Student; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | @RestController 9 | public class DirectController { 10 | 11 | @Autowired 12 | private DirectProducer directProducer; 13 | 14 | @RequestMapping("/direct/send") 15 | public String send() { 16 | directProducer.send(new Student(1, "John")); 17 | return "success"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /spring-boot-demo-rabbitmq/src/main/java/com/kucw/direct/DirectProducer.java: -------------------------------------------------------------------------------- 1 | package com.kucw.direct; 2 | 3 | import com.kucw.Student; 4 | import org.springframework.amqp.core.AmqpTemplate; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class DirectProducer { 10 | 11 | @Autowired 12 | private AmqpTemplate rabbitmqTemplate; 13 | 14 | public void send(Student student) { 15 | rabbitmqTemplate.convertAndSend("DIRECT_QUEUE", student); 16 | System.out.println("send message " + student + " to DIRECT_QUEUE successfully"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /spring-boot-demo-rabbitmq/src/main/java/com/kucw/routing/RoutingConsumer1.java: -------------------------------------------------------------------------------- 1 | package com.kucw.routing; 2 | 3 | import com.kucw.Student; 4 | import org.springframework.amqp.rabbit.annotation.RabbitListener; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class RoutingConsumer1 { 9 | 10 | @RabbitListener(queues = "ROUTING_QUEUE_1") 11 | public void listen(Student student) { 12 | System.out.println("receive message from ROUTING_QUEUE_1: " + student); 13 | } 14 | } -------------------------------------------------------------------------------- /spring-boot-demo-rabbitmq/src/main/java/com/kucw/routing/RoutingConsumer2.java: -------------------------------------------------------------------------------- 1 | package com.kucw.routing; 2 | 3 | import com.kucw.Student; 4 | import org.springframework.amqp.rabbit.annotation.RabbitListener; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class RoutingConsumer2 { 9 | 10 | @RabbitListener(queues = "ROUTING_QUEUE_2") 11 | public void listen(Student student) { 12 | System.out.println("receive message from ROUTING_QUEUE_2: " + student); 13 | } 14 | } -------------------------------------------------------------------------------- /spring-boot-demo-rabbitmq/src/main/java/com/kucw/routing/RoutingController.java: -------------------------------------------------------------------------------- 1 | package com.kucw.routing; 2 | 3 | import com.kucw.Student; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | @RestController 9 | public class RoutingController { 10 | 11 | @Autowired 12 | private RoutingProducer routingProducer; 13 | 14 | @RequestMapping("/routing/send") 15 | public String send() { 16 | routingProducer.send(new Student(1, "John")); 17 | routingProducer.send(new Student(2, "Amy")); 18 | routingProducer.send(new Student(3, "Bob")); 19 | return "success"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spring-boot-demo-rabbitmq/src/main/java/com/kucw/routing/RoutingProducer.java: -------------------------------------------------------------------------------- 1 | package com.kucw.routing; 2 | 3 | import com.kucw.Student; 4 | import org.springframework.amqp.core.AmqpTemplate; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class RoutingProducer { 10 | 11 | @Autowired 12 | private AmqpTemplate rabbitmqTemplate; 13 | 14 | public void send(Student student) { 15 | String routingKey = student.getName(); 16 | rabbitmqTemplate.convertAndSend("ROUTING_EXCHANGE", routingKey, student); 17 | System.out.println("send message " + student + " to ROUTING_EXCHANGE successfully"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /spring-boot-demo-rabbitmq/src/main/java/com/kucw/subscribe/SubscribeConsumer1.java: -------------------------------------------------------------------------------- 1 | package com.kucw.subscribe; 2 | 3 | import com.kucw.Student; 4 | import org.springframework.amqp.rabbit.annotation.RabbitListener; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class SubscribeConsumer1 { 9 | 10 | @RabbitListener(queues = "SUBSCRIBE_QUEUE_1") 11 | public void listen(Student student) { 12 | System.out.println("receive message from SUBSCRIBE_QUEUE_1: " + student); 13 | } 14 | } -------------------------------------------------------------------------------- /spring-boot-demo-rabbitmq/src/main/java/com/kucw/subscribe/SubscribeConsumer2.java: -------------------------------------------------------------------------------- 1 | package com.kucw.subscribe; 2 | 3 | import com.kucw.Student; 4 | import org.springframework.amqp.rabbit.annotation.RabbitListener; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class SubscribeConsumer2 { 9 | 10 | @RabbitListener(queues = "SUBSCRIBE_QUEUE_2") 11 | public void listen(Student student) { 12 | System.out.println("receive message from SUBSCRIBE_QUEUE_2: " + student); 13 | } 14 | } -------------------------------------------------------------------------------- /spring-boot-demo-rabbitmq/src/main/java/com/kucw/subscribe/SubscribeConsumer3.java: -------------------------------------------------------------------------------- 1 | package com.kucw.subscribe; 2 | 3 | import com.kucw.Student; 4 | import org.springframework.amqp.rabbit.annotation.RabbitListener; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class SubscribeConsumer3 { 9 | 10 | @RabbitListener(queues = "SUBSCRIBE_QUEUE_3") 11 | public void listen(Student student) { 12 | System.out.println("receive message from SUBSCRIBE_QUEUE_3: " + student); 13 | } 14 | } -------------------------------------------------------------------------------- /spring-boot-demo-rabbitmq/src/main/java/com/kucw/subscribe/SubscribeController.java: -------------------------------------------------------------------------------- 1 | package com.kucw.subscribe; 2 | 3 | import com.kucw.Student; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | @RestController 9 | public class SubscribeController { 10 | 11 | @Autowired 12 | private SubscribeProducer subscribeProducer; 13 | 14 | @RequestMapping("/subscribe/send") 15 | public String send() { 16 | subscribeProducer.send(new Student(1, "John")); 17 | return "success"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /spring-boot-demo-rabbitmq/src/main/java/com/kucw/subscribe/SubscribeProducer.java: -------------------------------------------------------------------------------- 1 | package com.kucw.subscribe; 2 | 3 | import com.kucw.Student; 4 | import org.springframework.amqp.core.AmqpTemplate; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class SubscribeProducer { 10 | 11 | @Autowired 12 | private AmqpTemplate rabbitmqTemplate; 13 | 14 | public void send(Student student) { 15 | rabbitmqTemplate.convertAndSend("SUBSCRIBE_EXCHANGE", "", student); 16 | System.out.println("send message " + student + " to SUBSCRIBE_EXCHANGE successfully"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /spring-boot-demo-rabbitmq/src/main/java/com/kucw/worker/WorkerConsumer1.java: -------------------------------------------------------------------------------- 1 | package com.kucw.worker; 2 | 3 | import com.kucw.Student; 4 | import org.springframework.amqp.rabbit.annotation.RabbitListener; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class WorkerConsumer1 { 9 | 10 | @RabbitListener(queues = "WORKER_QUEUE") 11 | public void listen(Student student) { 12 | System.out.println("WorkerConsumer1 receive message from WORKER_QUEUE: " + student); 13 | } 14 | } -------------------------------------------------------------------------------- /spring-boot-demo-rabbitmq/src/main/java/com/kucw/worker/WorkerConsumer2.java: -------------------------------------------------------------------------------- 1 | package com.kucw.worker; 2 | 3 | import com.kucw.Student; 4 | import org.springframework.amqp.rabbit.annotation.RabbitListener; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class WorkerConsumer2 { 9 | 10 | @RabbitListener(queues = "WORKER_QUEUE") 11 | public void listen(Student student) { 12 | System.out.println("WorkerConsumer2 receive message from WORKER_QUEUE: " + student); 13 | } 14 | } -------------------------------------------------------------------------------- /spring-boot-demo-rabbitmq/src/main/java/com/kucw/worker/WorkerController.java: -------------------------------------------------------------------------------- 1 | package com.kucw.worker; 2 | 3 | import com.kucw.Student; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | @RestController 9 | public class WorkerController { 10 | 11 | @Autowired 12 | private WorkerProducer workerProducer; 13 | 14 | @RequestMapping("/worker/send") 15 | public String send() { 16 | workerProducer.send(new Student(1, "John")); 17 | workerProducer.send(new Student(2, "Amy")); 18 | workerProducer.send(new Student(3, "Bob")); 19 | workerProducer.send(new Student(4, "Mike")); 20 | workerProducer.send(new Student(5, "Sharon")); 21 | return "send 5 messages to queue successfully"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /spring-boot-demo-rabbitmq/src/main/java/com/kucw/worker/WorkerProducer.java: -------------------------------------------------------------------------------- 1 | package com.kucw.worker; 2 | 3 | import com.kucw.Student; 4 | import org.springframework.amqp.core.AmqpTemplate; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class WorkerProducer { 10 | 11 | @Autowired 12 | private AmqpTemplate rabbitmqTemplate; 13 | 14 | public void send(Student student) { 15 | rabbitmqTemplate.convertAndSend("WORKER_QUEUE", student); 16 | System.out.println("send message " + student + " to WORKER_QUEUE successfully"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /spring-boot-demo-rabbitmq/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.rabbitmq.host=localhost 2 | spring.rabbitmq.virtual-host=/ 3 | spring.rabbitmq.username=root 4 | spring.rabbitmq.password=admin1234 -------------------------------------------------------------------------------- /spring-boot-demo-scheduler/.gitignore: -------------------------------------------------------------------------------- 1 | # kdiff3 ignore 2 | *.orig 3 | 4 | # maven ignore 5 | target/ 6 | 7 | # eclipse ignore 8 | .settings/ 9 | .project 10 | .classpath 11 | .springBeans 12 | 13 | # idea ignore 14 | .idea/ 15 | *.ipr 16 | *.iml 17 | *.iws 18 | 19 | # temp ignore 20 | *.log 21 | *.cache 22 | *.diff 23 | *.patch 24 | *.tmp 25 | 26 | # system ignore 27 | .DS_Store 28 | Thumbs.db 29 | 30 | # package ignore (optional) 31 | # *.jar 32 | # *.war 33 | # *.zip 34 | # *.tar 35 | # *.tar.gz 36 | /bin/ 37 | -------------------------------------------------------------------------------- /spring-boot-demo-scheduler/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.springframework.boot 9 | spring-boot-starter-parent 10 | 2.2.8.RELEASE 11 | 12 | 13 | 14 | com.kucw 15 | spring-boot-demo-scheduler 16 | 0.0.1-SNAPSHOT 17 | spring-boot-demo-scheduler 18 | 19 | 20 | 1.8 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-maven-plugin 31 | 32 | true 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /spring-boot-demo-scheduler/src/main/java/com/kucw/DemoSchedulerApplication.java: -------------------------------------------------------------------------------- 1 | package com.kucw; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.scheduling.annotation.EnableScheduling; 6 | 7 | @EnableScheduling //要加上這個 annotation,@Scheduled 才會生效 8 | @SpringBootApplication 9 | public class DemoSchedulerApplication { 10 | public static void main(String[] args) { 11 | SpringApplication.run(DemoSchedulerApplication.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-demo-scheduler/src/main/java/com/kucw/scheduler/MyScheduler.java: -------------------------------------------------------------------------------- 1 | package com.kucw.scheduler; 2 | 3 | import org.springframework.scheduling.annotation.Scheduled; 4 | import org.springframework.stereotype.Component; 5 | 6 | @Component 7 | public class MyScheduler { 8 | 9 | @Scheduled(fixedRate = 5000) //每5秒執行一次test1()方法 10 | public void test1() { 11 | System.out.println("test1 : 每5秒執行一次"); 12 | } 13 | 14 | @Scheduled(fixedDelay = 10000) //每次執行一次test2()方法後,間隔10秒再執行下一次 15 | public void test2() { 16 | System.out.println("test2 : 10秒後執行下一次"); 17 | } 18 | 19 | @Scheduled(cron = "0 0 5 * * *") //每天早上5點執行一次test3()方法 20 | public void test3() { 21 | System.out.println("test3 : 每天早上5點執行一次"); 22 | } 23 | 24 | @Scheduled(cron = "${my.daily}") //也可以把cron的設定值寫在application.properties裡面,再載入進來 25 | public void test4() { 26 | System.out.println("test4 : 每10分鐘執行一次"); 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /spring-boot-demo-scheduler/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # 每10分鐘執行一次 2 | my.daily=0 */10 * * * * -------------------------------------------------------------------------------- /spring-boot-demo-unit-test/.gitignore: -------------------------------------------------------------------------------- 1 | # kdiff3 ignore 2 | *.orig 3 | 4 | # maven ignore 5 | target/ 6 | 7 | # eclipse ignore 8 | .settings/ 9 | .project 10 | .classpath 11 | .springBeans 12 | 13 | # idea ignore 14 | .idea/ 15 | *.ipr 16 | *.iml 17 | *.iws 18 | 19 | # temp ignore 20 | *.log 21 | *.cache 22 | *.diff 23 | *.patch 24 | *.tmp 25 | 26 | # system ignore 27 | .DS_Store 28 | Thumbs.db 29 | 30 | # package ignore (optional) 31 | # *.jar 32 | # *.war 33 | # *.zip 34 | # *.tar 35 | # *.tar.gz 36 | /bin/ 37 | -------------------------------------------------------------------------------- /spring-boot-demo-unit-test/Spring Boot with Unit Test.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kucw/spring-boot-demo/e6d8b0cfc2efc588bfbd120f46ec0de3e6a1d42b/spring-boot-demo-unit-test/Spring Boot with Unit Test.pdf -------------------------------------------------------------------------------- /spring-boot-demo-unit-test/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.springframework.boot 9 | spring-boot-starter-parent 10 | 2.1.5.RELEASE 11 | 12 | 13 | 14 | com.kucw 15 | spring-boot-demo-unit-test 16 | 0.0.1-SNAPSHOT 17 | spring-boot-demo-unit-test 18 | 19 | 20 | 1.8 21 | UTF-8 22 | UTF-8 23 | 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-web 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-jdbc 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-test 37 | test 38 | 39 | 40 | com.h2database 41 | h2 42 | 1.4.200 43 | 44 | 45 | joda-time 46 | joda-time 47 | 2.10.5 48 | 49 | 50 | org.apache.commons 51 | commons-lang3 52 | 3.9 53 | 54 | 55 | org.apache.commons 56 | commons-collections4 57 | 4.4 58 | 59 | 60 | com.google.guava 61 | guava 62 | 28.1-jre 63 | 64 | 65 | 66 | 67 | 68 | 69 | org.springframework.boot 70 | spring-boot-maven-plugin 71 | 72 | true 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /spring-boot-demo-unit-test/src/main/java/com/kucw/DemoUnitTestApplication.java: -------------------------------------------------------------------------------- 1 | package com.kucw; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class DemoUnitTestApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(DemoUnitTestApplication.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-demo-unit-test/src/main/java/com/kucw/config/DataSourceConfig.java: -------------------------------------------------------------------------------- 1 | package com.kucw.config; 2 | 3 | import org.springframework.beans.factory.annotation.Qualifier; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.boot.jdbc.DataSourceBuilder; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; 9 | 10 | import javax.sql.DataSource; 11 | 12 | @Configuration 13 | public class DataSourceConfig { 14 | @Bean 15 | @ConfigurationProperties(prefix = "spring.datasource") 16 | public DataSource demoDataSource() { 17 | return DataSourceBuilder.create().build(); 18 | } 19 | 20 | @Bean 21 | public NamedParameterJdbcTemplate demoJdbcTemplate(@Qualifier("demoDataSource") DataSource dataSource) { 22 | return new NamedParameterJdbcTemplate(dataSource); 23 | } 24 | } -------------------------------------------------------------------------------- /spring-boot-demo-unit-test/src/main/java/com/kucw/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.kucw.controller; 2 | 3 | import com.kucw.dao.UserDao; 4 | import com.kucw.model.User; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.web.bind.annotation.*; 7 | 8 | import java.util.Date; 9 | 10 | @RestController 11 | public class UserController { 12 | 13 | @Autowired 14 | private UserDao userDao; 15 | 16 | @GetMapping("/user/get") 17 | public User get(@RequestParam Integer id) { 18 | return userDao.getUserById(id); 19 | } 20 | 21 | @PostMapping("/user/insert/{name}") 22 | public Integer insert(@PathVariable String name) { 23 | User user = new User(); 24 | user.setName(name); 25 | user.setUpdateTime(new Date()); 26 | return userDao.insertUser(user); 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /spring-boot-demo-unit-test/src/main/java/com/kucw/dao/UserDao.java: -------------------------------------------------------------------------------- 1 | package com.kucw.dao; 2 | 3 | import com.kucw.model.User; 4 | import org.apache.commons.collections4.CollectionUtils; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.annotation.Qualifier; 7 | import org.springframework.jdbc.core.BeanPropertyRowMapper; 8 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; 9 | import org.springframework.stereotype.Component; 10 | 11 | import java.util.Collections; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | @Component 17 | public class UserDao { 18 | private static final BeanPropertyRowMapper USER_MAPPER = new BeanPropertyRowMapper<>(User.class); 19 | 20 | private static final String SQL_INSERT = "INSERT INTO user (name, update_time) VALUES (:name, :updateTime)"; 21 | private static final String SQL_USER_BY_ID = "SELECT id, name, update_time FROM user WHERE id = :id"; 22 | 23 | @Autowired 24 | @Qualifier("demoJdbcTemplate") 25 | private NamedParameterJdbcTemplate namedParameterJdbcTemplate; 26 | 27 | public Integer insertUser(User user) { 28 | Map param = new HashMap<>(); 29 | param.put("name", user.getName()); 30 | param.put("updateTime", user.getUpdateTime()); 31 | return namedParameterJdbcTemplate.update(SQL_INSERT, param); 32 | } 33 | 34 | public User getUserById(Integer id) { 35 | List userList = namedParameterJdbcTemplate.query(SQL_USER_BY_ID, Collections.singletonMap("id", id), USER_MAPPER); 36 | if (CollectionUtils.isNotEmpty(userList)) { 37 | return userList.get(0); 38 | } 39 | return null; 40 | } 41 | 42 | public void print() { 43 | System.out.println("This is user dao"); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /spring-boot-demo-unit-test/src/main/java/com/kucw/model/User.java: -------------------------------------------------------------------------------- 1 | package com.kucw.model; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | 6 | public class User implements Serializable { 7 | private Integer id; 8 | private String name; 9 | private Date updateTime; 10 | 11 | public User(){} 12 | 13 | public User(Integer id, String name, Date updateTime) { 14 | this.id = id; 15 | this.name = name; 16 | this.updateTime = updateTime; 17 | } 18 | 19 | public Integer getId() { 20 | return id; 21 | } 22 | 23 | public void setId(Integer id) { 24 | this.id = id; 25 | } 26 | 27 | public String getName() { 28 | return name; 29 | } 30 | 31 | public void setName(String name) { 32 | this.name = name; 33 | } 34 | 35 | public Date getUpdateTime() { 36 | return updateTime; 37 | } 38 | 39 | public void setUpdateTime(Date updateTime) { 40 | this.updateTime = updateTime; 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | return "User{" + 46 | "id=" + id + 47 | ", name='" + name + '\'' + 48 | ", updateTime=" + updateTime + 49 | '}'; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /spring-boot-demo-unit-test/src/main/java/com/kucw/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.kucw.service; 2 | 3 | import com.kucw.dao.UserDao; 4 | import com.kucw.model.User; 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.Date; 10 | 11 | @Component 12 | public class UserService { 13 | 14 | @Autowired 15 | private UserDao userDao; 16 | 17 | public User getUserById(Integer id) { 18 | return userDao.getUserById(id); 19 | } 20 | 21 | public Integer insertUser(User user) { 22 | return userDao.insertUser(user); 23 | } 24 | 25 | public void print() { 26 | System.out.println("This is user service!"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /spring-boot-demo-unit-test/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # db setting 2 | spring.datasource.driverClassName=org.h2.Driver 3 | spring.datasource.jdbcUrl=jdbc:h2:mem:testdb 4 | spring.datasource.username=sa 5 | spring.datasource.password=sa 6 | 7 | # h2 console setting 8 | spring.h2.console.enabled=true 9 | spring.h2.console.path=/h2-console -------------------------------------------------------------------------------- /spring-boot-demo-unit-test/src/test/java/com/kucw/controller/UserControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.kucw.controller; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.http.MediaType; 9 | import org.springframework.test.context.junit4.SpringRunner; 10 | import org.springframework.test.web.servlet.MockMvc; 11 | import org.springframework.test.web.servlet.RequestBuilder; 12 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 13 | import org.springframework.test.web.servlet.result.MockMvcResultMatchers; 14 | 15 | @RunWith(SpringRunner.class) 16 | @SpringBootTest 17 | @AutoConfigureMockMvc 18 | public class UserControllerTest { 19 | 20 | @Autowired 21 | private MockMvc mockMvc; 22 | 23 | @Test 24 | public void testGetUser() throws Exception { 25 | RequestBuilder requestBuilder = MockMvcRequestBuilders 26 | .get("/user/get") 27 | .param("id", "1") 28 | .accept(MediaType.APPLICATION_JSON); 29 | 30 | mockMvc.perform(requestBuilder) 31 | .andExpect(MockMvcResultMatchers.status().isOk()) 32 | .andExpect(MockMvcResultMatchers.jsonPath("id").value(1)); 33 | } 34 | 35 | @Test 36 | public void testInsertUser() throws Exception { 37 | RequestBuilder requestBuilder = MockMvcRequestBuilders 38 | .post("/user/insert/{name}", "John"); 39 | 40 | mockMvc.perform(requestBuilder) 41 | .andExpect(MockMvcResultMatchers.status().isOk()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /spring-boot-demo-unit-test/src/test/java/com/kucw/dao/UserDaoTest.java: -------------------------------------------------------------------------------- 1 | package com.kucw.dao; 2 | 3 | import com.kucw.model.User; 4 | import org.junit.Assert; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.context.junit4.SpringRunner; 10 | import org.springframework.transaction.annotation.Transactional; 11 | 12 | import java.util.Date; 13 | 14 | @RunWith(SpringRunner.class) 15 | @SpringBootTest 16 | @Transactional 17 | public class UserDaoTest { 18 | 19 | @Autowired 20 | private UserDao userDao; 21 | 22 | @Test 23 | public void insert() throws Exception { 24 | User user = new User(); 25 | user.setName("test Boss"); 26 | user.setUpdateTime(new Date()); 27 | userDao.insertUser(user); 28 | } 29 | 30 | @Test 31 | public void getUserById() throws Exception { 32 | User user = userDao.getUserById(1); 33 | System.out.println(user.getName()); 34 | Assert.assertNotNull(user); 35 | } 36 | 37 | @Test 38 | public void getUserById4() throws Exception { 39 | User user = userDao.getUserById(4); 40 | System.out.println(user.getName()); 41 | Assert.assertNotNull(user); 42 | } 43 | } -------------------------------------------------------------------------------- /spring-boot-demo-unit-test/src/test/java/com/kucw/service/UserServiceMockTest.java: -------------------------------------------------------------------------------- 1 | package com.kucw.service; 2 | 3 | import com.kucw.model.User; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.mockito.InOrder; 7 | import org.mockito.Mockito; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.boot.test.mock.mockito.MockBean; 10 | import org.springframework.test.context.junit4.SpringRunner; 11 | 12 | import java.util.Date; 13 | 14 | @RunWith(SpringRunner.class) 15 | @SpringBootTest 16 | public class UserServiceMockTest { 17 | 18 | @MockBean 19 | private UserService userService; 20 | 21 | @Test 22 | public void test() throws Exception { 23 | /** 24 | * Mockito 的語法通常是 when(object.methodName()).thenReturn(response) 表示當methodName這個方法被call時,就return response這個結果 25 | * 如果沒有先定義好 when().thenReturn(),就直接去調用該方法時,默認return null 26 | */ 27 | 28 | //當使用任何int值call userService的getUserById方法時,就回傳一個名字為"I'm mockito name"的user 29 | Mockito.when(userService.getUserById(Mockito.anyInt())).thenReturn(new User(200, "I'm mockito name", new Date())); 30 | 31 | //限制只有當input的數字是3時,才會return名字為"I'm no.3"的User 32 | Mockito.when(userService.getUserById(3)).thenReturn(new User(3, "I'm no.3", new Date())); 33 | 34 | //當一個method有定義多次return值時,會從最後定義的那個when開始比對,如果參數符合的話,就返回那個when的return 35 | User user = userService.getUserById(3); //所以這裡會返回 "I'm no.3" User 36 | User user2 = userService.getUserById(5); //而這裡會返回 "I'm mockito name" User 37 | 38 | //當call userService的insertUser時,不管傳進來的User的值是什麼,都回傳100 39 | Mockito.when(userService.insertUser(Mockito.any(User.class))).thenReturn(100); 40 | 41 | Integer i = userService.insertUser(new User()); //會返回100 42 | 43 | /** 44 | * Mockito不僅能模擬方法調用返回值,也能記錄該mock對象的歷史調用記錄,可以使用verify()來檢查mock對象的某個方法是否曾被調用、或是他的方法調用順序 45 | */ 46 | 47 | //檢查調用getUserById、且參數為3的次數是否為1次 48 | Mockito.verify(userService, Mockito.times(1)).getUserById(Mockito.eq(3)) ; 49 | 50 | //驗證調用順序,確保mock對象會先調用getUserById兩次with特定parameter,然後才調用insertUser 51 | InOrder inOrder = Mockito.inOrder(userService); 52 | inOrder.verify(userService).getUserById(3); 53 | inOrder.verify(userService).getUserById(5); 54 | inOrder.verify(userService).insertUser(Mockito.any(User.class)); 55 | 56 | /** 57 | * 除了when().thenReturn()可以設置mock對象的方法返回值之外,也可以使用when().thenThrow()來拋出一個異常 58 | */ 59 | 60 | //當調用getUserById時的參數是9時,拋出一個RuntimeException 61 | Mockito.when(userService.getUserById(9)).thenThrow(new RuntimeException("mock throw exception")); 62 | 63 | //如果方法沒有return值的話,要改用doThrow()拋出Exception 64 | //當call userService的print方法時,拋出一個Exception 65 | Mockito.doThrow(new UnsupportedOperationException("mock throw unsupported exception")).when(userService).print(); 66 | } 67 | } -------------------------------------------------------------------------------- /spring-boot-demo-unit-test/src/test/java/com/kucw/service/UserServiceSpyTest.java: -------------------------------------------------------------------------------- 1 | package com.kucw.service; 2 | 3 | import com.kucw.model.User; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.mockito.Mockito; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.boot.test.mock.mockito.SpyBean; 9 | import org.springframework.test.context.junit4.SpringRunner; 10 | 11 | import java.util.Date; 12 | 13 | @RunWith(SpringRunner.class) 14 | @SpringBootTest 15 | public class UserServiceSpyTest { 16 | 17 | @SpyBean 18 | private UserService userService; 19 | 20 | @Test 21 | public void test() throws Exception { 22 | //雖然我們已經spy了userService,但是因為還沒有自己override任何方法的返回值,所以Spring會去用這個bean原本正常的方法 23 | //所以這裡返回的user才會是正常的John 24 | User user = userService.getUserById(1); 25 | 26 | /** 27 | * spy和mock一樣,也是使用 when(object.methodName()).thenReturn(response) 來設定方法返回值 28 | */ 29 | 30 | //設定當input為3時,返回名字為"I'm no.3"的User 31 | Mockito.when(userService.getUserById(3)).thenReturn(new User(3, "I'm no.3", new Date())); 32 | 33 | //設定當input為任意數時,返回名字為"I'm any"的User 34 | Mockito.when(userService.getUserById(5)).thenReturn(new User(200, "I'm any", new Date())); 35 | 36 | //spy跟mock一樣,當一個method有定義多次return值時,也是從最後定義的那個when開始比對,如果參數符合的話,就返回那個when的doReturn,如果都沒有符合的,就call原本的bean的方法 37 | User user2 = userService.getUserById(3); //所以這裡返回的user會是 I'm any 38 | User user3 = userService.getUserById(5); //這裡返回的user也會是 I'm any 39 | User user4 = userService.getUserById(1); //這裡返回的user則是DB裡的 John 40 | 41 | //thenThrow和verifiy用法都跟mock一樣,就不展開了 42 | } 43 | } -------------------------------------------------------------------------------- /spring-boot-demo-unit-test/src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | # db setting 2 | spring.datasource.driverClassName=org.h2.Driver 3 | spring.datasource.jdbcUrl=jdbc:h2:mem:testdb 4 | spring.datasource.username=sa 5 | spring.datasource.password=sa -------------------------------------------------------------------------------- /spring-boot-demo-unit-test/src/test/resources/data.sql: -------------------------------------------------------------------------------- 1 | -- test record 2 | INSERT INTO user (name, update_time) VALUES ('John', current_date); -------------------------------------------------------------------------------- /spring-boot-demo-unit-test/src/test/resources/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE user ( 2 | id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, 3 | name NVARCHAR(30) NOT NULL, 4 | update_time DATETIME NOT NULL 5 | ); --------------------------------------------------------------------------------